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

Commit e2ce58f7 authored by Jakub Tyszkowski's avatar Jakub Tyszkowski
Browse files

MediaControl: Extract CCCD storage out of MediaControlGattService

It is required for multiple MCS GATT service instances to have their own
unique set of CCC values stored. CCC descriptor values cannot be shared between
the instances. Since the service-owning profile knows the context in which
the service is intantiated, it is able to store the CCC descriptor values with
the proper context.

This patch provides CCC descriptor value storage for the single Generic
Media Control Service instance, stored under the BluetoothDevice.METADATA_GMCS_CCCD
metadata. Other instances will not get their values stored, and empty list
will be provided if they try to restore them. This is fine for now as
there is no support for multiple MCS instances enabled yet.

Bug: 276885705
Test: atest BluetoothInstrumentationTests
Change-Id: I58fc952ed6202d0586cf1e0fc7b7e2758f28e4aa
parent 4dca7627
Loading
Loading
Loading
Loading
+24 −1
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.bluetooth.IBluetoothMcpServiceManager;
import android.content.AttributionSource;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelUuid;
import android.sysprop.BluetoothProperties;
import android.util.Log;

@@ -33,7 +34,9 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

import java.util.HashMap;
import java.util.Collections;
import java.util.Map;
import java.util.List;

/**
 * Provides Media Control Profile, as a service in the Bluetooth application.
@@ -47,7 +50,7 @@ public class McpService extends ProfileService {
    private static McpService sMcpService;
    private static MediaControlProfile sGmcsForTesting;

    private Object mLock = new Object();
    private final Object mLock = new Object();
    @GuardedBy("mLock")
    private MediaControlProfile mGmcs;
    private Map<BluetoothDevice, Integer> mDeviceAuthorizations = new HashMap<>();
@@ -248,6 +251,26 @@ public class McpService extends ProfileService {
        return BluetoothDevice.ACCESS_UNKNOWN;
    }

    List<ParcelUuid> getNotificationSubscriptions(int ccid, BluetoothDevice device) {
        synchronized (mLock) {
            MediaControlProfile gmcs = getGmcsLocked();
            if (gmcs != null) {
                return gmcs.getNotificationSubscriptions(ccid, device);
            }
        }
        return Collections.emptyList();
    }

    void setNotificationSubscription(
            int ccid, BluetoothDevice device, ParcelUuid charUuid, boolean doNotify) {
        synchronized (mLock) {
            MediaControlProfile gmcs = getGmcsLocked();
            if (gmcs != null) {
                gmcs.setNotificationSubscription(ccid, device, charUuid, doNotify);
            }
        }
    }

    @GuardedBy("mLock")
    private MediaControlProfile getGmcsLocked() {
        if (sGmcsForTesting != null) {
+21 −70
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@

package com.android.bluetooth.mcp;

import static android.bluetooth.BluetoothDevice.METADATA_GMCS_CCCD;
import static android.bluetooth.BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED;
import static android.bluetooth.BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED;
import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_NOTIFY;
@@ -790,13 +789,14 @@ public class MediaControlGattService implements MediaControlGattServiceInterface

    private void restoreCccValuesForStoredDevices() {
        for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
            byte[] gmcs_cccd = device.getMetadata(METADATA_GMCS_CCCD);

            if ((gmcs_cccd == null) || (gmcs_cccd.length == 0)) {
                return;
            }

            List<ParcelUuid> uuidList = Arrays.asList(Utils.byteArrayToUuid(gmcs_cccd));
            List<ParcelUuid> uuidList = mMcpService.getNotificationSubscriptions(mCcid, device);
            mEventLogger.logd(
                    DBG,
                    TAG,
                    "restoreCccValuesForStoredDevices: device= "
                            + device
                            + ", num_uuids= "
                            + uuidList.size());

            /* Restore CCCD values for device */
            for (ParcelUuid uuid : uuidList) {
@@ -1308,63 +1308,6 @@ public class MediaControlGattService implements MediaControlGattServiceInterface
        return mBluetoothGattServer.addService(mGattService);
    }

    private void removeUuidFromMetadata(ParcelUuid charUuid, BluetoothDevice device) {
        List<ParcelUuid> uuidList;
        byte[] gmcs_cccd = device.getMetadata(METADATA_GMCS_CCCD);

        if ((gmcs_cccd == null) || (gmcs_cccd.length == 0)) {
            uuidList = new ArrayList<ParcelUuid>();
        } else {
            uuidList = new ArrayList<>(Arrays.asList(Utils.byteArrayToUuid(gmcs_cccd)));

            if (!uuidList.contains(charUuid)) {
                Log.d(TAG, "Characteristic CCCD can't be removed (not cached): "
                        + charUuid.toString());
                return;
            }
        }

        uuidList.remove(charUuid);

        mEventLogger.logd(
                DBG,
                TAG,
                "removeUuidFromMetadata: device= " + device + ", char= " + charUuid.toString());
        if (!device.setMetadata(METADATA_GMCS_CCCD,
                Utils.uuidsToByteArray(uuidList.toArray(new ParcelUuid[0])))) {
            Log.e(TAG, "Can't set CCCD for GMCS characteristic UUID: " + charUuid.toString()
                    + ", (remove)");
        }
    }

    private void addUuidToMetadata(ParcelUuid charUuid, BluetoothDevice device) {
        List<ParcelUuid> uuidList;
        byte[] gmcs_cccd = device.getMetadata(METADATA_GMCS_CCCD);

        if ((gmcs_cccd == null) || (gmcs_cccd.length == 0)) {
            uuidList = new ArrayList<ParcelUuid>();
        } else {
            uuidList = new ArrayList<>(Arrays.asList(Utils.byteArrayToUuid(gmcs_cccd)));

            if (uuidList.contains(charUuid)) {
                Log.d(TAG, "Characteristic CCCD already added: " + charUuid.toString());
                return;
            }
        }

        uuidList.add(charUuid);

        mEventLogger.logd(
                DBG,
                TAG,
                "addUuidToMetadata: device= " + device + ", char= " + charUuid.toString());
        if (!device.setMetadata(METADATA_GMCS_CCCD,
                Utils.uuidsToByteArray(uuidList.toArray(new ParcelUuid[0])))) {
            Log.e(TAG, "Can't set CCCD for GMCS characteristic UUID: " + charUuid.toString()
                    + ", (add)");
        }
    }

    @VisibleForTesting
    void setCcc(BluetoothDevice device, UUID charUuid, int offset, byte[] value, boolean store) {
        Map<UUID, Short> characteristicCcc = mCccDescriptorValues.get(device.getAddress());
@@ -1381,13 +1324,13 @@ public class MediaControlGattService implements MediaControlGattServiceInterface
        }

        if (Arrays.equals(value, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
            mEventLogger.add("setCcc: device= " + device + ", notify= " + true);
            addUuidToMetadata(new ParcelUuid(charUuid), device);
            mEventLogger.logd(DBG, TAG, "setCcc: device= " + device + ", notify: " + true);
            mMcpService.setNotificationSubscription(mCcid, device, new ParcelUuid(charUuid), true);
        } else if (Arrays.equals(value, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) {
            mEventLogger.add("setCcc: device= " + device + ", notify= " + false);
            removeUuidFromMetadata(new ParcelUuid(charUuid), device);
            mEventLogger.logd(DBG, TAG, "setCcc: device= " + device + ", notify: " + false);
            mMcpService.setNotificationSubscription(mCcid, device, new ParcelUuid(charUuid), false);
        } else {
            Log.e(TAG, "Not handled CCC value: " + Arrays.toString(value));
            mEventLogger.loge(TAG, "Not handled CCC value: " + Arrays.toString(value));
        }
    }

@@ -1660,6 +1603,14 @@ public class MediaControlGattService implements MediaControlGattServiceInterface
        return mCcid;
    }

    @Override
    public UUID getServiceUuid() {
        if (mGattService != null) {
            return mGattService.getUuid();
        }
        return new UUID(0, 0);
    }

    @Override
    public void onDeviceAuthorizationSet(BluetoothDevice device) {
        int auth = getDeviceAuthorization(device);
+2 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ package com.android.bluetooth.mcp;
import android.bluetooth.BluetoothDevice;

import java.util.Map;
import java.util.UUID;

/**
 * Media Control Service interface. These are sent Media Players => GATT Servers
@@ -46,6 +47,7 @@ public interface MediaControlGattServiceInterface {
    void setSearchRequestResult(SearchRequest request,
            SearchRequest.Results resultStatus, long resultObjectId);
    int getContentControlId();
    UUID getServiceUuid();
    void onDeviceAuthorizationSet(BluetoothDevice device);
    void destroy();
    void dump(StringBuilder sb);
+64 −0
Original line number Diff line number Diff line
@@ -27,16 +27,21 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.media.session.PlaybackState;
import android.os.Looper;
import android.os.ParcelUuid;
import android.os.SystemClock;
import android.util.Log;

import com.android.bluetooth.BluetoothEventLogger;
import com.android.bluetooth.Utils;
import com.android.bluetooth.audio_util.MediaData;
import com.android.bluetooth.audio_util.MediaPlayerList;
import com.android.bluetooth.audio_util.MediaPlayerWrapper;
import com.android.bluetooth.le_audio.ContentControlIdKeeper;
import com.android.internal.annotations.VisibleForTesting;

import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -859,6 +864,65 @@ public class MediaControlProfile implements MediaControlServiceCallbacks {
        }
    }

    private boolean isGenericMediaService(int ccid) {
        for (MediaControlGattServiceInterface svc : mServiceMap.values()) {
            if (svc.getContentControlId() == ccid) {
                return svc.getServiceUuid().equals(BluetoothUuid.GENERIC_MEDIA_CONTROL.getUuid());
            }
        }
        return false;
    }

    List<ParcelUuid> getNotificationSubscriptions(int ccid, BluetoothDevice device) {
        // TODO: Support multiple MCS instances
        if (isGenericMediaService(ccid)) {
            byte[] gmcs_cccd = device.getMetadata(BluetoothDevice.METADATA_GMCS_CCCD);
            if ((gmcs_cccd != null) && (gmcs_cccd.length != 0)) {
                return Arrays.asList(Utils.byteArrayToUuid(gmcs_cccd));
            }
        }
        return Collections.emptyList();
    }

    void setNotificationSubscription(
            int ccid, BluetoothDevice device, ParcelUuid charUuid, boolean doNotify) {
        // TODO: Support multiple MCS instances
        if (isGenericMediaService(ccid)) {
            byte[] gmcs_cccd = device.getMetadata(BluetoothDevice.METADATA_GMCS_CCCD);
            List<ParcelUuid> uuidList;

            if ((gmcs_cccd == null) || (gmcs_cccd.length == 0)) {
                uuidList = new ArrayList<ParcelUuid>();
            } else {
                uuidList =
                        new ArrayList<ParcelUuid>(Arrays.asList(Utils.byteArrayToUuid(gmcs_cccd)));
            }

            boolean updateDb = false;
            if (doNotify) {
                if (!uuidList.contains(charUuid)) {
                    uuidList.add(charUuid);
                    updateDb = true;
                }
            } else if (uuidList.contains(charUuid)) {
                uuidList.remove(charUuid);
                updateDb = true;
            }

            if (updateDb) {
                if (!device.setMetadata(
                        BluetoothDevice.METADATA_GMCS_CCCD,
                        Utils.uuidsToByteArray(uuidList.toArray(new ParcelUuid[0])))) {
                    Log.e(
                            TAG,
                            "Can't set CCCD for GMCS characteristic UUID: "
                                    + charUuid.toString()
                                    + ", (remove)");
                }
            }
        }
    }

    public void dump(StringBuilder sb) {
        sb.append("Media Control Service instance list:\n");
        for (MediaControlGattServiceInterface svc : mServiceMap.values()) {
+1 −0
Original line number Diff line number Diff line
@@ -131,6 +131,7 @@ public class MediaControlGattServiceTest {

        doReturn(mMandatoryFeatures).when(mMockMcsCallbacks).onGetFeatureFlags();
        Assert.assertTrue(mMcpService.init(UUID_GMCS));
        Assert.assertEquals(mMcpService.getServiceUuid(), UUID_GMCS);
        Assert.assertEquals(mMcpService.getContentControlId(), TEST_CCID);

        doReturn(true).when(mMockGattServer).removeService(any(BluetoothGattService.class));
Loading