Loading android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +16 −21 Original line number Diff line number Diff line Loading @@ -2352,6 +2352,18 @@ public class LeAudioService extends ProfileService { return mMcpService; } void setAuthorizationForRelatedProfiles(BluetoothDevice device, boolean authorize) { McpService mcpService = getMcpService(); if (mcpService != null) { mcpService.setDeviceAuthorized(device, authorize); } TbsService tbsService = getTbsService(); if (tbsService != null) { tbsService.setDeviceAuthorized(device, authorize); } } /** * This function is called when the framework registers a callback with the service for this * first time. This is used as an indication that Bluetooth has been enabled. Loading @@ -2373,20 +2385,9 @@ public class LeAudioService extends ProfileService { } } McpService mcpService = getMcpService(); if (mcpService == null) { Log.e(TAG, "mcpService not available "); return; } synchronized (mGroupLock) { for (Map.Entry<BluetoothDevice, LeAudioDeviceDescriptor> entry : mDeviceDescriptors.entrySet()) { if (entry.getValue().mGroupId == LE_AUDIO_GROUP_ID_INVALID) { continue; } mcpService.setDeviceAuthorized(entry.getKey(), true); for (BluetoothDevice device : mDeviceDescriptors.keySet()) { setAuthorizationForRelatedProfiles(device, true); } } } Loading Loading @@ -2441,10 +2442,7 @@ public class LeAudioService extends ProfileService { } if (mBluetoothEnabled) { McpService mcpService = getMcpService(); if (mcpService != null) { mcpService.setDeviceAuthorized(device, true); } setAuthorizationForRelatedProfiles(device, true); } } Loading Loading @@ -2516,10 +2514,7 @@ public class LeAudioService extends ProfileService { notifyGroupNodeRemoved(device, groupId); } McpService mcpService = getMcpService(); if (mcpService != null) { mcpService.setDeviceAuthorized(device, false); } setAuthorizationForRelatedProfiles(device, false); } private void notifyGroupNodeRemoved(BluetoothDevice device, int groupId) { Loading android/app/src/com/android/bluetooth/tbs/TbsGatt.java +71 −2 Original line number Diff line number Diff line Loading @@ -151,6 +151,7 @@ public class TbsGatt { private Callback mCallback; private AdapterService mAdapterService; private HashMap<BluetoothDevice, HashMap<UUID, Short>> mCccDescriptorValues; private TbsService mTbsService; public static abstract class Callback { Loading @@ -168,7 +169,17 @@ public class TbsGatt { public abstract boolean isInbandRingtoneEnabled(BluetoothDevice device); } TbsGatt(Context context) { private static class GattOpContext { public enum Operation { READ_CHARACTERISTIC, WRITE_CHARACTERISTIC, READ_DESCRIPTOR, WRITE_DESCRIPTOR, } } TbsGatt(TbsService tbsService) { mContext = tbsService; mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), "AdapterService shouldn't be null when creating MediaControlCattService"); IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager(); Loading @@ -180,7 +191,6 @@ public class TbsGatt { } } mContext = context; mBearerProviderNameCharacteristic = new GattCharacteristic(UUID_BEARER_PROVIDER_NAME, BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY, Loading Loading @@ -227,6 +237,8 @@ public class TbsGatt { BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED); mTbsService = tbsService; mBluetoothGattServer = null; } Loading Loading @@ -888,6 +900,33 @@ public class TbsGatt { } }; private int getDeviceAuthorization(BluetoothDevice device) { return mTbsService.getDeviceAuthorization(device); } private void onRejectedAuthorizationGattOperation(BluetoothDevice device, GattOpContext.Operation op, boolean responseNeeded, int requestId, int offset) { Log.w(TAG, "onRejectedAuthorizationGattOperation device: " + device + ", operation: " + op); switch (op) { case READ_CHARACTERISTIC: case READ_DESCRIPTOR: mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_INSUFFICIENT_AUTHORIZATION, offset, null); break; case WRITE_CHARACTERISTIC: case WRITE_DESCRIPTOR: if (responseNeeded) { mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_INSUFFICIENT_AUTHORIZATION, offset, null); } break; default: break; } } /** * Callback to handle incoming requests to the GATT server. All read/write requests for * characteristics and descriptors are handled here. Loading @@ -912,6 +951,13 @@ public class TbsGatt { if (DBG) { Log.d(TAG, "onCharacteristicReadRequest: device=" + device); } if (getDeviceAuthorization(device) != BluetoothDevice.ACCESS_ALLOWED) { onRejectedAuthorizationGattOperation(device, GattOpContext.Operation.READ_CHARACTERISTIC, false, requestId, offset); return; } byte[] value; if (characteristic.getUuid().equals(UUID_STATUS_FLAGS)) { value = new byte[2]; Loading Loading @@ -949,6 +995,14 @@ public class TbsGatt { if (DBG) { Log.d(TAG, "onCharacteristicWriteRequest: device=" + device); } if (getDeviceAuthorization(device) != BluetoothDevice.ACCESS_ALLOWED) { onRejectedAuthorizationGattOperation(device, GattOpContext.Operation.WRITE_CHARACTERISTIC, preparedWrite, requestId, offset); return; } GattCharacteristic gattCharacteristic = (GattCharacteristic) characteristic; int status; if (preparedWrite) { Loading @@ -971,6 +1025,13 @@ public class TbsGatt { if (DBG) { Log.d(TAG, "onDescriptorReadRequest: device=" + device); } if (getDeviceAuthorization(device) != BluetoothDevice.ACCESS_ALLOWED) { onRejectedAuthorizationGattOperation(device, GattOpContext.Operation.READ_DESCRIPTOR, false, requestId, offset); return; } ClientCharacteristicConfigurationDescriptor cccd = (ClientCharacteristicConfigurationDescriptor) descriptor; byte[] value = cccd.getValue(device); Loading @@ -992,6 +1053,14 @@ public class TbsGatt { if (DBG) { Log.d(TAG, "onDescriptorWriteRequest: device=" + device); } if (getDeviceAuthorization(device) != BluetoothDevice.ACCESS_ALLOWED) { onRejectedAuthorizationGattOperation(device, GattOpContext.Operation.WRITE_DESCRIPTOR, preparedWrite, requestId, offset); return; } ClientCharacteristicConfigurationDescriptor cccd = (ClientCharacteristicConfigurationDescriptor) descriptor; int status; Loading android/app/src/com/android/bluetooth/tbs/TbsService.java +62 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.bluetooth.tbs; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeCall; import android.bluetooth.BluetoothProfile; import android.bluetooth.IBluetoothLeCallControl; import android.bluetooth.IBluetoothLeCallControlCallback; import android.content.AttributionSource; Loading @@ -31,9 +32,12 @@ import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission; import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.le_audio.LeAudioService; import com.android.internal.annotations.VisibleForTesting; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; public class TbsService extends ProfileService { Loading @@ -42,6 +46,7 @@ public class TbsService extends ProfileService { private static final boolean DBG = true; private static TbsService sTbsService; private Map<BluetoothDevice, Integer> mDeviceAuthorizations = new HashMap<>(); private final TbsGeneric mTbsGeneric = new TbsGeneric(); Loading Loading @@ -104,6 +109,7 @@ public class TbsService extends ProfileService { if (DBG) { Log.d(TAG, "cleanup()"); } mDeviceAuthorizations.clear(); } /** Loading Loading @@ -133,6 +139,62 @@ public class TbsService extends ProfileService { sTbsService = instance; } /** * Sets device authorization for TBS. * * @param device device that would be authorized * @param isAuthorized boolean value of authorization permission * @hide */ public void setDeviceAuthorized(BluetoothDevice device, boolean isAuthorized) { Log.i(TAG, "setDeviceAuthorized(): device: " + device + ", isAuthorized: " + isAuthorized); int authorization = isAuthorized ? BluetoothDevice.ACCESS_ALLOWED : BluetoothDevice.ACCESS_REJECTED; mDeviceAuthorizations.put(device, authorization); } /** * Returns authorization value for given device. * * @param device device that would be authorized * @return authorization value for device * * Possible authorization values: * {@link BluetoothDevice.ACCESS_UNKNOWN}, * {@link BluetoothDevice.ACCESS_ALLOWED} * @hide */ public int getDeviceAuthorization(BluetoothDevice device) { /* Telephony Bearer Service is allowed for * 1. in PTS mode * 2. authorized devices * 3. Any LeAudio devices which are allowed to connect */ int authorization = mDeviceAuthorizations.getOrDefault(device, Utils.isPtsTestMode() ? BluetoothDevice.ACCESS_ALLOWED : BluetoothDevice.ACCESS_UNKNOWN); if (authorization != BluetoothDevice.ACCESS_UNKNOWN) { return authorization; } LeAudioService leAudioService = LeAudioService.getLeAudioService(); if (leAudioService == null) { Log.e(TAG, "TBS access not permited. LeAudioService not available"); return BluetoothDevice.ACCESS_UNKNOWN; } if (leAudioService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { if (DBG) { Log.d(TAG, "TBS authorization allowed based on supported LeAudio service"); } setDeviceAuthorized(device, true); return BluetoothDevice.ACCESS_ALLOWED; } Log.e(TAG, "TBS access not permited"); return BluetoothDevice.ACCESS_UNKNOWN; } /** * Set inband ringtone for the device. * When set, notification will be sent to given device. Loading android/app/tests/unit/src/com/android/bluetooth/tbs/TbsGattTest.java +5 −8 Original line number Diff line number Diff line Loading @@ -70,8 +70,6 @@ import java.util.UUID; @MediumTest @RunWith(AndroidJUnit4.class) public class TbsGattTest { private static Context sContext; private BluetoothAdapter mAdapter; private BluetoothDevice mFirstDevice; private BluetoothDevice mSecondDevice; Loading @@ -91,6 +89,8 @@ public class TbsGattTest { private BluetoothGattServerProxy mMockGattServer; @Mock private TbsGatt.Callback mMockTbsGattCallback; @Mock private TbsService mMockTbsService; @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); Loading @@ -98,11 +98,6 @@ public class TbsGattTest { @Captor private ArgumentCaptor<BluetoothGattService> mGattServiceCaptor; @BeforeClass public static void setUpOnce() { sContext = getInstrumentation().getTargetContext(); } @Before public void setUp() throws Exception { if (Looper.myLooper() == null) { Loading @@ -118,8 +113,10 @@ public class TbsGattTest { doReturn(true).when(mMockGattServer).addService(any(BluetoothGattService.class)); doReturn(true).when(mMockGattServer).open(any(BluetoothGattServerCallback.class)); doReturn(BluetoothDevice.ACCESS_ALLOWED).when(mMockTbsService) .getDeviceAuthorization(any(BluetoothDevice.class)); mTbsGatt = new TbsGatt(sContext); mTbsGatt = new TbsGatt(mMockTbsService); mTbsGatt.setBluetoothGattServerForTesting(mMockGattServer); mFirstDevice = TestUtils.getTestDevice(mAdapter, 0); Loading Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +16 −21 Original line number Diff line number Diff line Loading @@ -2352,6 +2352,18 @@ public class LeAudioService extends ProfileService { return mMcpService; } void setAuthorizationForRelatedProfiles(BluetoothDevice device, boolean authorize) { McpService mcpService = getMcpService(); if (mcpService != null) { mcpService.setDeviceAuthorized(device, authorize); } TbsService tbsService = getTbsService(); if (tbsService != null) { tbsService.setDeviceAuthorized(device, authorize); } } /** * This function is called when the framework registers a callback with the service for this * first time. This is used as an indication that Bluetooth has been enabled. Loading @@ -2373,20 +2385,9 @@ public class LeAudioService extends ProfileService { } } McpService mcpService = getMcpService(); if (mcpService == null) { Log.e(TAG, "mcpService not available "); return; } synchronized (mGroupLock) { for (Map.Entry<BluetoothDevice, LeAudioDeviceDescriptor> entry : mDeviceDescriptors.entrySet()) { if (entry.getValue().mGroupId == LE_AUDIO_GROUP_ID_INVALID) { continue; } mcpService.setDeviceAuthorized(entry.getKey(), true); for (BluetoothDevice device : mDeviceDescriptors.keySet()) { setAuthorizationForRelatedProfiles(device, true); } } } Loading Loading @@ -2441,10 +2442,7 @@ public class LeAudioService extends ProfileService { } if (mBluetoothEnabled) { McpService mcpService = getMcpService(); if (mcpService != null) { mcpService.setDeviceAuthorized(device, true); } setAuthorizationForRelatedProfiles(device, true); } } Loading Loading @@ -2516,10 +2514,7 @@ public class LeAudioService extends ProfileService { notifyGroupNodeRemoved(device, groupId); } McpService mcpService = getMcpService(); if (mcpService != null) { mcpService.setDeviceAuthorized(device, false); } setAuthorizationForRelatedProfiles(device, false); } private void notifyGroupNodeRemoved(BluetoothDevice device, int groupId) { Loading
android/app/src/com/android/bluetooth/tbs/TbsGatt.java +71 −2 Original line number Diff line number Diff line Loading @@ -151,6 +151,7 @@ public class TbsGatt { private Callback mCallback; private AdapterService mAdapterService; private HashMap<BluetoothDevice, HashMap<UUID, Short>> mCccDescriptorValues; private TbsService mTbsService; public static abstract class Callback { Loading @@ -168,7 +169,17 @@ public class TbsGatt { public abstract boolean isInbandRingtoneEnabled(BluetoothDevice device); } TbsGatt(Context context) { private static class GattOpContext { public enum Operation { READ_CHARACTERISTIC, WRITE_CHARACTERISTIC, READ_DESCRIPTOR, WRITE_DESCRIPTOR, } } TbsGatt(TbsService tbsService) { mContext = tbsService; mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), "AdapterService shouldn't be null when creating MediaControlCattService"); IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager(); Loading @@ -180,7 +191,6 @@ public class TbsGatt { } } mContext = context; mBearerProviderNameCharacteristic = new GattCharacteristic(UUID_BEARER_PROVIDER_NAME, BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY, Loading Loading @@ -227,6 +237,8 @@ public class TbsGatt { BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED); mTbsService = tbsService; mBluetoothGattServer = null; } Loading Loading @@ -888,6 +900,33 @@ public class TbsGatt { } }; private int getDeviceAuthorization(BluetoothDevice device) { return mTbsService.getDeviceAuthorization(device); } private void onRejectedAuthorizationGattOperation(BluetoothDevice device, GattOpContext.Operation op, boolean responseNeeded, int requestId, int offset) { Log.w(TAG, "onRejectedAuthorizationGattOperation device: " + device + ", operation: " + op); switch (op) { case READ_CHARACTERISTIC: case READ_DESCRIPTOR: mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_INSUFFICIENT_AUTHORIZATION, offset, null); break; case WRITE_CHARACTERISTIC: case WRITE_DESCRIPTOR: if (responseNeeded) { mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_INSUFFICIENT_AUTHORIZATION, offset, null); } break; default: break; } } /** * Callback to handle incoming requests to the GATT server. All read/write requests for * characteristics and descriptors are handled here. Loading @@ -912,6 +951,13 @@ public class TbsGatt { if (DBG) { Log.d(TAG, "onCharacteristicReadRequest: device=" + device); } if (getDeviceAuthorization(device) != BluetoothDevice.ACCESS_ALLOWED) { onRejectedAuthorizationGattOperation(device, GattOpContext.Operation.READ_CHARACTERISTIC, false, requestId, offset); return; } byte[] value; if (characteristic.getUuid().equals(UUID_STATUS_FLAGS)) { value = new byte[2]; Loading Loading @@ -949,6 +995,14 @@ public class TbsGatt { if (DBG) { Log.d(TAG, "onCharacteristicWriteRequest: device=" + device); } if (getDeviceAuthorization(device) != BluetoothDevice.ACCESS_ALLOWED) { onRejectedAuthorizationGattOperation(device, GattOpContext.Operation.WRITE_CHARACTERISTIC, preparedWrite, requestId, offset); return; } GattCharacteristic gattCharacteristic = (GattCharacteristic) characteristic; int status; if (preparedWrite) { Loading @@ -971,6 +1025,13 @@ public class TbsGatt { if (DBG) { Log.d(TAG, "onDescriptorReadRequest: device=" + device); } if (getDeviceAuthorization(device) != BluetoothDevice.ACCESS_ALLOWED) { onRejectedAuthorizationGattOperation(device, GattOpContext.Operation.READ_DESCRIPTOR, false, requestId, offset); return; } ClientCharacteristicConfigurationDescriptor cccd = (ClientCharacteristicConfigurationDescriptor) descriptor; byte[] value = cccd.getValue(device); Loading @@ -992,6 +1053,14 @@ public class TbsGatt { if (DBG) { Log.d(TAG, "onDescriptorWriteRequest: device=" + device); } if (getDeviceAuthorization(device) != BluetoothDevice.ACCESS_ALLOWED) { onRejectedAuthorizationGattOperation(device, GattOpContext.Operation.WRITE_DESCRIPTOR, preparedWrite, requestId, offset); return; } ClientCharacteristicConfigurationDescriptor cccd = (ClientCharacteristicConfigurationDescriptor) descriptor; int status; Loading
android/app/src/com/android/bluetooth/tbs/TbsService.java +62 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.bluetooth.tbs; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeCall; import android.bluetooth.BluetoothProfile; import android.bluetooth.IBluetoothLeCallControl; import android.bluetooth.IBluetoothLeCallControlCallback; import android.content.AttributionSource; Loading @@ -31,9 +32,12 @@ import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission; import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.le_audio.LeAudioService; import com.android.internal.annotations.VisibleForTesting; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; public class TbsService extends ProfileService { Loading @@ -42,6 +46,7 @@ public class TbsService extends ProfileService { private static final boolean DBG = true; private static TbsService sTbsService; private Map<BluetoothDevice, Integer> mDeviceAuthorizations = new HashMap<>(); private final TbsGeneric mTbsGeneric = new TbsGeneric(); Loading Loading @@ -104,6 +109,7 @@ public class TbsService extends ProfileService { if (DBG) { Log.d(TAG, "cleanup()"); } mDeviceAuthorizations.clear(); } /** Loading Loading @@ -133,6 +139,62 @@ public class TbsService extends ProfileService { sTbsService = instance; } /** * Sets device authorization for TBS. * * @param device device that would be authorized * @param isAuthorized boolean value of authorization permission * @hide */ public void setDeviceAuthorized(BluetoothDevice device, boolean isAuthorized) { Log.i(TAG, "setDeviceAuthorized(): device: " + device + ", isAuthorized: " + isAuthorized); int authorization = isAuthorized ? BluetoothDevice.ACCESS_ALLOWED : BluetoothDevice.ACCESS_REJECTED; mDeviceAuthorizations.put(device, authorization); } /** * Returns authorization value for given device. * * @param device device that would be authorized * @return authorization value for device * * Possible authorization values: * {@link BluetoothDevice.ACCESS_UNKNOWN}, * {@link BluetoothDevice.ACCESS_ALLOWED} * @hide */ public int getDeviceAuthorization(BluetoothDevice device) { /* Telephony Bearer Service is allowed for * 1. in PTS mode * 2. authorized devices * 3. Any LeAudio devices which are allowed to connect */ int authorization = mDeviceAuthorizations.getOrDefault(device, Utils.isPtsTestMode() ? BluetoothDevice.ACCESS_ALLOWED : BluetoothDevice.ACCESS_UNKNOWN); if (authorization != BluetoothDevice.ACCESS_UNKNOWN) { return authorization; } LeAudioService leAudioService = LeAudioService.getLeAudioService(); if (leAudioService == null) { Log.e(TAG, "TBS access not permited. LeAudioService not available"); return BluetoothDevice.ACCESS_UNKNOWN; } if (leAudioService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { if (DBG) { Log.d(TAG, "TBS authorization allowed based on supported LeAudio service"); } setDeviceAuthorized(device, true); return BluetoothDevice.ACCESS_ALLOWED; } Log.e(TAG, "TBS access not permited"); return BluetoothDevice.ACCESS_UNKNOWN; } /** * Set inband ringtone for the device. * When set, notification will be sent to given device. Loading
android/app/tests/unit/src/com/android/bluetooth/tbs/TbsGattTest.java +5 −8 Original line number Diff line number Diff line Loading @@ -70,8 +70,6 @@ import java.util.UUID; @MediumTest @RunWith(AndroidJUnit4.class) public class TbsGattTest { private static Context sContext; private BluetoothAdapter mAdapter; private BluetoothDevice mFirstDevice; private BluetoothDevice mSecondDevice; Loading @@ -91,6 +89,8 @@ public class TbsGattTest { private BluetoothGattServerProxy mMockGattServer; @Mock private TbsGatt.Callback mMockTbsGattCallback; @Mock private TbsService mMockTbsService; @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); Loading @@ -98,11 +98,6 @@ public class TbsGattTest { @Captor private ArgumentCaptor<BluetoothGattService> mGattServiceCaptor; @BeforeClass public static void setUpOnce() { sContext = getInstrumentation().getTargetContext(); } @Before public void setUp() throws Exception { if (Looper.myLooper() == null) { Loading @@ -118,8 +113,10 @@ public class TbsGattTest { doReturn(true).when(mMockGattServer).addService(any(BluetoothGattService.class)); doReturn(true).when(mMockGattServer).open(any(BluetoothGattServerCallback.class)); doReturn(BluetoothDevice.ACCESS_ALLOWED).when(mMockTbsService) .getDeviceAuthorization(any(BluetoothDevice.class)); mTbsGatt = new TbsGatt(sContext); mTbsGatt = new TbsGatt(mMockTbsService); mTbsGatt.setBluetoothGattServerForTesting(mMockGattServer); mFirstDevice = TestUtils.getTestDevice(mAdapter, 0); Loading