Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit da278920 authored by Jakub Pawlowski's avatar Jakub Pawlowski
Browse files

Media Control Profile for LE Audio

Media Control Profile implements the Generic Media Control Service
and media playback controls by hooking into the Media Session Framework.
It represents the global media state and allows to controll it,
similar to AVRCP.

Bug: 150670922
Tag: #feature
Test: atest BluetoothInstrumentationTests
Sponsor: jpawlowski@
Change-Id: Ic141ef2ee6aa21426f923dbc47308b383496c7be
parent 313e8806
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -371,7 +371,7 @@
             android:exported="true"
             android:enabled="@bool/profile_supported_mcp_server">
            <intent-filter>
                <action android:name="android.bluetooth.IBluetoothMcpService" />
                <action android:name="android.bluetooth.IBluetoothMcpServiceManager" />
            </intent-filter>
        </service>
        <service
+1 −1
Original line number Diff line number Diff line
@@ -15,7 +15,7 @@
 * limitations under the License.
 */

package com.android.le_audio;
package com.android.bluetooth.le_audio;

import java.util.SortedSet;
import java.util.TreeSet;
+13 −0
Original line number Diff line number Diff line
@@ -36,7 +36,9 @@ import android.util.Log;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.mcp.McpService;
import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
@@ -62,6 +64,7 @@ public class LeAudioService extends ProfileService {
    private DatabaseManager mDatabaseManager;
    private HandlerThread mStateMachinesThread;
    private BluetoothDevice mPreviousAudioDevice;
    ServiceFactory mServiceFactory = new ServiceFactory();

    LeAudioNativeInterface mLeAudioNativeInterface;
    AudioManager mAudioManager;
@@ -580,6 +583,11 @@ public class LeAudioService extends ProfileService {
            if (!mGroupIdConnectedMap.getOrDefault(myGroupId, false)) {
                mGroupIdConnectedMap.put(myGroupId, true);
            }

            McpService mcpService = mServiceFactory.getMcpService();
            if (mcpService != null) {
                mcpService.setDeviceAuthorized(device, true);
            }
        }
        if (fromState == BluetoothProfile.STATE_CONNECTED && getConnectedDevices().isEmpty()) {
            setActiveDevice(null);
@@ -595,6 +603,11 @@ public class LeAudioService extends ProfileService {
                }
                removeStateMachine(device);
            }

            McpService mcpService = mServiceFactory.getMcpService();
            if (mcpService != null) {
                mcpService.setDeviceAuthorized(device, false);
            }
        }
    }

+36 −8
Original line number Diff line number Diff line
@@ -24,6 +24,9 @@ import android.util.Log;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.ProfileService;

import java.util.HashMap;
import java.util.Map;

/**
 * Provides Media Control Profile, as a service in the Bluetooth application.
 * @hide
@@ -31,10 +34,13 @@ import com.android.bluetooth.btservice.ProfileService;
public class McpService extends ProfileService {
    private static final boolean DBG = true;
    private static final boolean VDBG = false;
    private static final String TAG = "McpService";
    private static final String TAG = "BluetoothMcpService";

    private static McpService sMcpService;

    private static MediaControlProfile mGmcs;
    private Map<BluetoothDevice, Integer> mDeviceAuthorizations = new HashMap<>();

    private static synchronized void setMcpService(McpService instance) {
        if (VDBG) {
            Log.d(TAG, "setMcpService(): set to: " + instance);
@@ -55,6 +61,10 @@ public class McpService extends ProfileService {
        return sMcpService;
    }

    public static void setMediaControlProfileForTesting(MediaControlProfile mediaControlProfile) {
        mGmcs = mediaControlProfile;
    }

    @Override
    protected IProfileServiceBinder initBinder() {
        return new BluetoothMcpServiceBinder(this);
@@ -80,6 +90,13 @@ public class McpService extends ProfileService {
        // Mark service as started
        setMcpService(this);

        if (mGmcs == null) {
            // Initialize the Media Control Service Server
            mGmcs = new MediaControlProfile(this);
            // Requires this service to be already started thus we have to make it an async call
            this.getMainThreadHandler().post(() -> mGmcs.init());
        }

        return true;
    }

@@ -94,9 +111,12 @@ public class McpService extends ProfileService {
            return true;
        }

        if (mGmcs != null) {
            mGmcs.cleanup();
        }

        // Mark service as stopped
        setMcpService(null);

        return true;
    }

@@ -108,17 +128,21 @@ public class McpService extends ProfileService {
    }

    public void onDeviceUnauthorized(BluetoothDevice device) {
        // TODO: For now just reject authorization for other than LeAudio device already authorized.
        //       Consider intent based authorization mechanism for non-LeAudio devices.
        setDeviceAuthorized(device, false);
    }

    public int getDeviceAuthorization(BluetoothDevice device) {
        return BluetoothDevice.ACCESS_ALLOWED;
    }
    public void setDeviceAuthorized(BluetoothDevice device, boolean isAuthorized) {
        int authorization = isAuthorized ? BluetoothDevice.ACCESS_ALLOWED
                : BluetoothDevice.ACCESS_REJECTED;
        mDeviceAuthorizations.put(device, authorization);

    void setDeviceAuthorized(BluetoothDevice device, boolean isAuthorized) {
        mGmcs.onDeviceAuthorizationSet(device);
    }

    public int getDeviceAuthorization(BluetoothDevice device) {
        // TODO: For now just reject authorization for other than LeAudio device already authorized.
        //       Consider intent based authorization mechanism for non-LeAudio devices.
        return mDeviceAuthorizations.getOrDefault(device, BluetoothDevice.ACCESS_UNKNOWN);
    }

    /**
@@ -153,6 +177,9 @@ public class McpService extends ProfileService {

        @Override
        public void cleanup() {
            if (mService != null) {
                mService.cleanup();
            }
            mService = null;
        }
    }
@@ -160,5 +187,6 @@ public class McpService extends ProfileService {
    @Override
    public void dump(StringBuilder sb) {
        super.dump(sb);
        mGmcs.dump(sb);
    }
}
+11 −3
Original line number Diff line number Diff line
@@ -903,8 +903,15 @@ public class MediaControlGattService implements MediaControlGattServiceInterface

        if (mBluetoothGattServer == null) {
            BluetoothManager manager = mContext.getSystemService(BluetoothManager.class);
            mBluetoothGattServer = new BluetoothGattServerProxy(
                    manager.openGattServer(mContext, mServerCallback), manager);
            BluetoothGattServer server = manager.openGattServer(mContext, mServerCallback);
            if (server == null) {
                Log.e(TAG, "Failed to start BluetoothGattServer for MCP");
                //TODO: This now effectively makes MCP unusable, but fixes tests
                // Handle this error more gracefully, verify BluetoothInstrumentationTests
                // are passing after fix is applied
                return false;
            }
            mBluetoothGattServer = new BluetoothGattServerProxy(server, manager);
        }

        mGattService =
@@ -1237,7 +1244,8 @@ public class MediaControlGattService implements MediaControlGattServiceInterface
        if (DBG) {
            Log.d(TAG, "Destroy");
        }
        if (mBluetoothGattServer.removeService(mGattService)) {
        if (mBluetoothGattServer != null
                && mBluetoothGattServer.removeService(mGattService)) {
            if (mCallbacks != null) {
                mCallbacks.onServiceInstanceUnregistered(ServiceStatus.OK);
            }
Loading