Loading android/app/src/com/android/bluetooth/le_audio/LeAudioObjectsFactory.java 0 → 100644 +73 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.le_audio; import android.content.Context; import android.util.Log; import com.android.bluetooth.Utils; import com.android.internal.annotations.VisibleForTesting; /** * Factory class for object initialization to help with unit testing */ public class LeAudioObjectsFactory { private static final String TAG = LeAudioObjectsFactory.class.getSimpleName(); private static LeAudioObjectsFactory sInstance; private static final Object INSTANCE_LOCK = new Object(); private LeAudioObjectsFactory() {} /** * Get the singleton instance of object factory * * @return the singleton instance, guaranteed not null */ public static LeAudioObjectsFactory getInstance() { synchronized (INSTANCE_LOCK) { if (sInstance == null) { sInstance = new LeAudioObjectsFactory(); } } return sInstance; } /** * Allow unit tests to substitute {@link LeAudioObjectsFactory} with a test instance * * @param objectsFactory a test instance of the {@link LeAudioObjectsFactory} */ @VisibleForTesting public static void setInstanceForTesting(LeAudioObjectsFactory objectsFactory) { Utils.enforceInstrumentationTestMode(); synchronized (INSTANCE_LOCK) { Log.d(TAG, "setInstanceForTesting(), set to " + objectsFactory); sInstance = objectsFactory; } } /** * Get a {@link LeAudioTmapGattServer} object * * @param context local context * @return */ public LeAudioTmapGattServer getTmapGattServer(Context context) { return new LeAudioTmapGattServer( new LeAudioTmapGattServer.BluetoothGattServerProxy(context)); } } android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +21 −0 Original line number Diff line number Diff line Loading @@ -121,6 +121,7 @@ public class LeAudioService extends ProfileService { LeAudioBroadcasterNativeInterface mLeAudioBroadcasterNativeInterface = null; @VisibleForTesting AudioManager mAudioManager; LeAudioTmapGattServer mTmapGattServer; @VisibleForTesting RemoteCallbackList<IBluetoothLeBroadcastCallback> mBroadcastCallbacks; Loading Loading @@ -236,6 +237,9 @@ public class LeAudioService extends ProfileService { registerReceiver(mConnectionStateChangedReceiver, filter); mLeAudioCallbacks = new RemoteCallbackList<IBluetoothLeAudioCallback>(); int tmapRoleMask = LeAudioTmapGattServer.TMAP_ROLE_FLAG_CG | LeAudioTmapGattServer.TMAP_ROLE_FLAG_UMS; // Initialize Broadcast native interface if (mAdapterService.isLeAudioBroadcastSourceSupported()) { mBroadcastCallbacks = new RemoteCallbackList<IBluetoothLeBroadcastCallback>(); Loading @@ -243,9 +247,18 @@ public class LeAudioService extends ProfileService { LeAudioBroadcasterNativeInterface.getInstance(), "LeAudioBroadcasterNativeInterface cannot be null when LeAudioService starts"); mLeAudioBroadcasterNativeInterface.init(); tmapRoleMask |= LeAudioTmapGattServer.TMAP_ROLE_FLAG_BMS; } else { Log.w(TAG, "Le Audio Broadcasts not supported."); } // the role mask is fixed in Android if (mTmapGattServer != null) { throw new IllegalStateException("TMAP GATT server started before start() is called"); } mTmapGattServer = LeAudioObjectsFactory.getInstance().getTmapGattServer(this); mTmapGattServer.start(tmapRoleMask); // Mark service as started setLeAudioService(this); Loading Loading @@ -276,6 +289,14 @@ public class LeAudioService extends ProfileService { } setActiveDevice(null); if (mTmapGattServer == null) { Log.w(TAG, "TMAP GATT server should never be null before stop() is called"); } else { mTmapGattServer.stop(); mTmapGattServer = null; } //Don't wait for async call with INACTIVE group status, clean active //device for active group. synchronized (mGroupLock) { Loading android/app/src/com/android/bluetooth/le_audio/LeAudioTmapGattServer.java 0 → 100644 +210 −0 Original line number Diff line number Diff line /* * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.le_audio; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattServer; import android.bluetooth.BluetoothGattServerCallback; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.content.Context; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.util.Arrays; import java.util.List; import java.util.UUID; /** * A GATT server for Telephony and Media Audio Profile (TMAP) */ @VisibleForTesting public class LeAudioTmapGattServer { private static final boolean DBG = true; private static final String TAG = "LeAudioTmapGattServer"; /* Telephony and Media Audio Profile Role Characteristic UUID */ @VisibleForTesting public static final UUID UUID_TMAP_ROLE = UUID.fromString("00002B51-0000-1000-8000-00805f9b34fb"); /* TMAP Role: Call Gateway */ public static final int TMAP_ROLE_FLAG_CG = 1; /* TMAP Role: Call Terminal */ public static final int TMAP_ROLE_FLAG_CT = 1 << 1; /* TMAP Role: Unicast Media Sender */ public static final int TMAP_ROLE_FLAG_UMS = 1 << 2; /* TMAP Role: Unicast Media Receiver */ public static final int TMAP_ROLE_FLAG_UMR = 1 << 3; /* TMAP Role: Broadcast Media Sender */ public static final int TMAP_ROLE_FLAG_BMS = 1 << 4; /* TMAP Role: Broadcast Media Receiver */ public static final int TMAP_ROLE_FLAG_BMR = 1 << 5; private final BluetoothGattServerProxy mBluetoothGattServer; /*package*/ LeAudioTmapGattServer(BluetoothGattServerProxy gattServer) { mBluetoothGattServer = gattServer; } /** * Init TMAP server * @param roleMask bit mask of supported roles. */ @VisibleForTesting public void start(int roleMask) { if (DBG) { Log.d(TAG, "start(roleMask:" + roleMask + ")"); } if (!mBluetoothGattServer.open(mBluetoothGattServerCallback)) { throw new IllegalStateException("Could not open Gatt server"); } BluetoothGattService service = new BluetoothGattService(BluetoothUuid.TMAP.getUuid(), BluetoothGattService.SERVICE_TYPE_PRIMARY); BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic( UUID_TMAP_ROLE, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED); characteristic.setValue(roleMask, BluetoothGattCharacteristic.FORMAT_UINT16, 0); service.addCharacteristic(characteristic); if (!mBluetoothGattServer.addService(service)) { throw new IllegalStateException("Failed to add service for TMAP"); } } /** * Stop TMAP server */ @VisibleForTesting public void stop() { if (DBG) { Log.d(TAG, "stop()"); } if (mBluetoothGattServer == null) { Log.w(TAG, "mBluetoothGattServer should not be null when stop() is called"); return; } mBluetoothGattServer.close(); } /** * Callback to handle incoming requests to the GATT server. * All read/write requests for characteristics and descriptors are handled here. */ private final BluetoothGattServerCallback mBluetoothGattServerCallback = new BluetoothGattServerCallback() { @Override public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) { byte[] value = characteristic.getValue(); if (DBG) { Log.d(TAG, "value " + value); } if (value != null) { Log.e(TAG, "value null"); value = Arrays.copyOfRange(value, offset, value.length); } mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value); } }; /** * A proxy class that facilitates testing. * * This is necessary due to the "final" attribute of the BluetoothGattServer class. */ public static class BluetoothGattServerProxy { private final Context mContext; private final BluetoothManager mBluetoothManager; private BluetoothGattServer mBluetoothGattServer; /** * Create a new GATT server proxy object * @param context context to use */ public BluetoothGattServerProxy(Context context) { mContext = context; mBluetoothManager = context.getSystemService(BluetoothManager.class); } /** * Open with GATT server callback * @param callback callback to invoke * @return true on success */ public boolean open(BluetoothGattServerCallback callback) { mBluetoothGattServer = mBluetoothManager.openGattServer(mContext, callback); return mBluetoothGattServer != null; } /** * Close the GATT server, should be called as soon as the server is not needed */ public void close() { if (mBluetoothGattServer == null) { Log.w(TAG, "BluetoothGattServerProxy.close() called without open()"); return; } mBluetoothGattServer.close(); mBluetoothGattServer = null; } /** * Add a GATT service * @param service added service * @return true on success */ public boolean addService(BluetoothGattService service) { return mBluetoothGattServer.addService(service); } /** * Send GATT response to remote * @param device remote device * @param requestId request id * @param status status of response * @param offset offset of the value * @param value value content * @return true on success */ public boolean sendResponse( BluetoothDevice device, int requestId, int status, int offset, byte[] value) { return mBluetoothGattServer.sendResponse(device, requestId, status, offset, value); } /** * Gatt a list of devices connected to this GATT server * @return list of connected devices at this moment */ public List<BluetoothDevice> getConnectedDevices() { return mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT_SERVER); } } } android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java +9 −0 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeoutException; Loading @@ -70,6 +71,8 @@ public class LeAudioBroadcastServiceTest { private AudioManager mAudioManager; @Mock private LeAudioBroadcasterNativeInterface mNativeInterface; @Mock private LeAudioTmapGattServer mTmapGattServer; @Spy private LeAudioObjectsFactory mObjectsFactory = LeAudioObjectsFactory.getInstance(); private static final String TEST_MAC_ADDRESS = "00:11:22:33:44:55"; private static final int TEST_BROADCAST_ID = 42; Loading Loading @@ -155,6 +158,12 @@ public class LeAudioBroadcastServiceTest { // Set up mocks and test assets MockitoAnnotations.initMocks(this); // Use spied objects factory doNothing().when(mTmapGattServer).start(anyInt()); doNothing().when(mTmapGattServer).stop(); LeAudioObjectsFactory.setInstanceForTesting(mObjectsFactory); doReturn(mTmapGattServer).when(mObjectsFactory).getTmapGattServer(any()); if (Looper.myLooper() == null) { Looper.prepare(); } Loading android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java +15 −10 Original line number Diff line number Diff line Loading @@ -24,18 +24,16 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.nullable; import static org.mockito.Mockito.when; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.eq; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothLeAudioCodecConfig; import android.bluetooth.BluetoothLeAudioCodecStatus; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.bluetooth.IBluetoothLeAudioCallback; Loading @@ -44,7 +42,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.media.BluetoothProfileConnectionInfo; import android.os.ParcelUuid; import androidx.test.InstrumentationRegistry; Loading @@ -63,13 +60,11 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; Loading Loading @@ -104,6 +99,9 @@ public class LeAudioServiceTest { @Mock private DatabaseManager mDatabaseManager; @Mock private LeAudioNativeInterface mNativeInterface; @Mock private AudioManager mAudioManager; @Mock private LeAudioTmapGattServer mTmapGattServer; @Spy private LeAudioObjectsFactory mObjectsFactory = LeAudioObjectsFactory.getInstance(); @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); Loading Loading @@ -135,7 +133,6 @@ public class LeAudioServiceTest { add(LC3_48KHZ_16KHZ_CONFIG); }}; private static final List<BluetoothLeAudioCodecConfig> INPUT_SELECTABLE_CONFIG = new ArrayList() {{ add(LC3_16KHZ_CONFIG); Loading @@ -153,6 +150,12 @@ public class LeAudioServiceTest { // Set up mocks and test assets MockitoAnnotations.initMocks(this); // Use spied objects factory doNothing().when(mTmapGattServer).start(anyInt()); doNothing().when(mTmapGattServer).stop(); LeAudioObjectsFactory.setInstanceForTesting(mObjectsFactory); doReturn(mTmapGattServer).when(mObjectsFactory).getTmapGattServer(any()); TestUtils.setAdapterService(mAdapterService); doReturn(MAX_LE_AUDIO_CONNECTIONS).when(mAdapterService).getMaxConnectedAudioDevices(); doReturn(new ParcelUuid[]{BluetoothUuid.LE_AUDIO}).when(mAdapterService) Loading @@ -160,7 +163,9 @@ public class LeAudioServiceTest { doReturn(mDatabaseManager).when(mAdapterService).getDatabase(); doReturn(true, false).when(mAdapterService).isStartedProfile(anyString()); mAdapter = BluetoothAdapter.getDefaultAdapter(); BluetoothManager manager = mTargetContext.getSystemService(BluetoothManager.class); assertThat(manager).isNotNull(); mAdapter = manager.getAdapter(); // Mock methods in AdapterService doAnswer(invocation -> mBondedDevices.toArray(new BluetoothDevice[]{})).when( mAdapterService).getBondedDevices(); Loading Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioObjectsFactory.java 0 → 100644 +73 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.le_audio; import android.content.Context; import android.util.Log; import com.android.bluetooth.Utils; import com.android.internal.annotations.VisibleForTesting; /** * Factory class for object initialization to help with unit testing */ public class LeAudioObjectsFactory { private static final String TAG = LeAudioObjectsFactory.class.getSimpleName(); private static LeAudioObjectsFactory sInstance; private static final Object INSTANCE_LOCK = new Object(); private LeAudioObjectsFactory() {} /** * Get the singleton instance of object factory * * @return the singleton instance, guaranteed not null */ public static LeAudioObjectsFactory getInstance() { synchronized (INSTANCE_LOCK) { if (sInstance == null) { sInstance = new LeAudioObjectsFactory(); } } return sInstance; } /** * Allow unit tests to substitute {@link LeAudioObjectsFactory} with a test instance * * @param objectsFactory a test instance of the {@link LeAudioObjectsFactory} */ @VisibleForTesting public static void setInstanceForTesting(LeAudioObjectsFactory objectsFactory) { Utils.enforceInstrumentationTestMode(); synchronized (INSTANCE_LOCK) { Log.d(TAG, "setInstanceForTesting(), set to " + objectsFactory); sInstance = objectsFactory; } } /** * Get a {@link LeAudioTmapGattServer} object * * @param context local context * @return */ public LeAudioTmapGattServer getTmapGattServer(Context context) { return new LeAudioTmapGattServer( new LeAudioTmapGattServer.BluetoothGattServerProxy(context)); } }
android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +21 −0 Original line number Diff line number Diff line Loading @@ -121,6 +121,7 @@ public class LeAudioService extends ProfileService { LeAudioBroadcasterNativeInterface mLeAudioBroadcasterNativeInterface = null; @VisibleForTesting AudioManager mAudioManager; LeAudioTmapGattServer mTmapGattServer; @VisibleForTesting RemoteCallbackList<IBluetoothLeBroadcastCallback> mBroadcastCallbacks; Loading Loading @@ -236,6 +237,9 @@ public class LeAudioService extends ProfileService { registerReceiver(mConnectionStateChangedReceiver, filter); mLeAudioCallbacks = new RemoteCallbackList<IBluetoothLeAudioCallback>(); int tmapRoleMask = LeAudioTmapGattServer.TMAP_ROLE_FLAG_CG | LeAudioTmapGattServer.TMAP_ROLE_FLAG_UMS; // Initialize Broadcast native interface if (mAdapterService.isLeAudioBroadcastSourceSupported()) { mBroadcastCallbacks = new RemoteCallbackList<IBluetoothLeBroadcastCallback>(); Loading @@ -243,9 +247,18 @@ public class LeAudioService extends ProfileService { LeAudioBroadcasterNativeInterface.getInstance(), "LeAudioBroadcasterNativeInterface cannot be null when LeAudioService starts"); mLeAudioBroadcasterNativeInterface.init(); tmapRoleMask |= LeAudioTmapGattServer.TMAP_ROLE_FLAG_BMS; } else { Log.w(TAG, "Le Audio Broadcasts not supported."); } // the role mask is fixed in Android if (mTmapGattServer != null) { throw new IllegalStateException("TMAP GATT server started before start() is called"); } mTmapGattServer = LeAudioObjectsFactory.getInstance().getTmapGattServer(this); mTmapGattServer.start(tmapRoleMask); // Mark service as started setLeAudioService(this); Loading Loading @@ -276,6 +289,14 @@ public class LeAudioService extends ProfileService { } setActiveDevice(null); if (mTmapGattServer == null) { Log.w(TAG, "TMAP GATT server should never be null before stop() is called"); } else { mTmapGattServer.stop(); mTmapGattServer = null; } //Don't wait for async call with INACTIVE group status, clean active //device for active group. synchronized (mGroupLock) { Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioTmapGattServer.java 0 → 100644 +210 −0 Original line number Diff line number Diff line /* * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.le_audio; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattServer; import android.bluetooth.BluetoothGattServerCallback; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.content.Context; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.util.Arrays; import java.util.List; import java.util.UUID; /** * A GATT server for Telephony and Media Audio Profile (TMAP) */ @VisibleForTesting public class LeAudioTmapGattServer { private static final boolean DBG = true; private static final String TAG = "LeAudioTmapGattServer"; /* Telephony and Media Audio Profile Role Characteristic UUID */ @VisibleForTesting public static final UUID UUID_TMAP_ROLE = UUID.fromString("00002B51-0000-1000-8000-00805f9b34fb"); /* TMAP Role: Call Gateway */ public static final int TMAP_ROLE_FLAG_CG = 1; /* TMAP Role: Call Terminal */ public static final int TMAP_ROLE_FLAG_CT = 1 << 1; /* TMAP Role: Unicast Media Sender */ public static final int TMAP_ROLE_FLAG_UMS = 1 << 2; /* TMAP Role: Unicast Media Receiver */ public static final int TMAP_ROLE_FLAG_UMR = 1 << 3; /* TMAP Role: Broadcast Media Sender */ public static final int TMAP_ROLE_FLAG_BMS = 1 << 4; /* TMAP Role: Broadcast Media Receiver */ public static final int TMAP_ROLE_FLAG_BMR = 1 << 5; private final BluetoothGattServerProxy mBluetoothGattServer; /*package*/ LeAudioTmapGattServer(BluetoothGattServerProxy gattServer) { mBluetoothGattServer = gattServer; } /** * Init TMAP server * @param roleMask bit mask of supported roles. */ @VisibleForTesting public void start(int roleMask) { if (DBG) { Log.d(TAG, "start(roleMask:" + roleMask + ")"); } if (!mBluetoothGattServer.open(mBluetoothGattServerCallback)) { throw new IllegalStateException("Could not open Gatt server"); } BluetoothGattService service = new BluetoothGattService(BluetoothUuid.TMAP.getUuid(), BluetoothGattService.SERVICE_TYPE_PRIMARY); BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic( UUID_TMAP_ROLE, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED); characteristic.setValue(roleMask, BluetoothGattCharacteristic.FORMAT_UINT16, 0); service.addCharacteristic(characteristic); if (!mBluetoothGattServer.addService(service)) { throw new IllegalStateException("Failed to add service for TMAP"); } } /** * Stop TMAP server */ @VisibleForTesting public void stop() { if (DBG) { Log.d(TAG, "stop()"); } if (mBluetoothGattServer == null) { Log.w(TAG, "mBluetoothGattServer should not be null when stop() is called"); return; } mBluetoothGattServer.close(); } /** * Callback to handle incoming requests to the GATT server. * All read/write requests for characteristics and descriptors are handled here. */ private final BluetoothGattServerCallback mBluetoothGattServerCallback = new BluetoothGattServerCallback() { @Override public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) { byte[] value = characteristic.getValue(); if (DBG) { Log.d(TAG, "value " + value); } if (value != null) { Log.e(TAG, "value null"); value = Arrays.copyOfRange(value, offset, value.length); } mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value); } }; /** * A proxy class that facilitates testing. * * This is necessary due to the "final" attribute of the BluetoothGattServer class. */ public static class BluetoothGattServerProxy { private final Context mContext; private final BluetoothManager mBluetoothManager; private BluetoothGattServer mBluetoothGattServer; /** * Create a new GATT server proxy object * @param context context to use */ public BluetoothGattServerProxy(Context context) { mContext = context; mBluetoothManager = context.getSystemService(BluetoothManager.class); } /** * Open with GATT server callback * @param callback callback to invoke * @return true on success */ public boolean open(BluetoothGattServerCallback callback) { mBluetoothGattServer = mBluetoothManager.openGattServer(mContext, callback); return mBluetoothGattServer != null; } /** * Close the GATT server, should be called as soon as the server is not needed */ public void close() { if (mBluetoothGattServer == null) { Log.w(TAG, "BluetoothGattServerProxy.close() called without open()"); return; } mBluetoothGattServer.close(); mBluetoothGattServer = null; } /** * Add a GATT service * @param service added service * @return true on success */ public boolean addService(BluetoothGattService service) { return mBluetoothGattServer.addService(service); } /** * Send GATT response to remote * @param device remote device * @param requestId request id * @param status status of response * @param offset offset of the value * @param value value content * @return true on success */ public boolean sendResponse( BluetoothDevice device, int requestId, int status, int offset, byte[] value) { return mBluetoothGattServer.sendResponse(device, requestId, status, offset, value); } /** * Gatt a list of devices connected to this GATT server * @return list of connected devices at this moment */ public List<BluetoothDevice> getConnectedDevices() { return mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT_SERVER); } } }
android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java +9 −0 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeoutException; Loading @@ -70,6 +71,8 @@ public class LeAudioBroadcastServiceTest { private AudioManager mAudioManager; @Mock private LeAudioBroadcasterNativeInterface mNativeInterface; @Mock private LeAudioTmapGattServer mTmapGattServer; @Spy private LeAudioObjectsFactory mObjectsFactory = LeAudioObjectsFactory.getInstance(); private static final String TEST_MAC_ADDRESS = "00:11:22:33:44:55"; private static final int TEST_BROADCAST_ID = 42; Loading Loading @@ -155,6 +158,12 @@ public class LeAudioBroadcastServiceTest { // Set up mocks and test assets MockitoAnnotations.initMocks(this); // Use spied objects factory doNothing().when(mTmapGattServer).start(anyInt()); doNothing().when(mTmapGattServer).stop(); LeAudioObjectsFactory.setInstanceForTesting(mObjectsFactory); doReturn(mTmapGattServer).when(mObjectsFactory).getTmapGattServer(any()); if (Looper.myLooper() == null) { Looper.prepare(); } Loading
android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java +15 −10 Original line number Diff line number Diff line Loading @@ -24,18 +24,16 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.nullable; import static org.mockito.Mockito.when; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.eq; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothLeAudioCodecConfig; import android.bluetooth.BluetoothLeAudioCodecStatus; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.bluetooth.IBluetoothLeAudioCallback; Loading @@ -44,7 +42,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.media.BluetoothProfileConnectionInfo; import android.os.ParcelUuid; import androidx.test.InstrumentationRegistry; Loading @@ -63,13 +60,11 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; Loading Loading @@ -104,6 +99,9 @@ public class LeAudioServiceTest { @Mock private DatabaseManager mDatabaseManager; @Mock private LeAudioNativeInterface mNativeInterface; @Mock private AudioManager mAudioManager; @Mock private LeAudioTmapGattServer mTmapGattServer; @Spy private LeAudioObjectsFactory mObjectsFactory = LeAudioObjectsFactory.getInstance(); @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); Loading Loading @@ -135,7 +133,6 @@ public class LeAudioServiceTest { add(LC3_48KHZ_16KHZ_CONFIG); }}; private static final List<BluetoothLeAudioCodecConfig> INPUT_SELECTABLE_CONFIG = new ArrayList() {{ add(LC3_16KHZ_CONFIG); Loading @@ -153,6 +150,12 @@ public class LeAudioServiceTest { // Set up mocks and test assets MockitoAnnotations.initMocks(this); // Use spied objects factory doNothing().when(mTmapGattServer).start(anyInt()); doNothing().when(mTmapGattServer).stop(); LeAudioObjectsFactory.setInstanceForTesting(mObjectsFactory); doReturn(mTmapGattServer).when(mObjectsFactory).getTmapGattServer(any()); TestUtils.setAdapterService(mAdapterService); doReturn(MAX_LE_AUDIO_CONNECTIONS).when(mAdapterService).getMaxConnectedAudioDevices(); doReturn(new ParcelUuid[]{BluetoothUuid.LE_AUDIO}).when(mAdapterService) Loading @@ -160,7 +163,9 @@ public class LeAudioServiceTest { doReturn(mDatabaseManager).when(mAdapterService).getDatabase(); doReturn(true, false).when(mAdapterService).isStartedProfile(anyString()); mAdapter = BluetoothAdapter.getDefaultAdapter(); BluetoothManager manager = mTargetContext.getSystemService(BluetoothManager.class); assertThat(manager).isNotNull(); mAdapter = manager.getAdapter(); // Mock methods in AdapterService doAnswer(invocation -> mBondedDevices.toArray(new BluetoothDevice[]{})).when( mAdapterService).getBondedDevices(); Loading