Remove singleton from LeAudioNativeInterface
Create a new final field `LeAudioService mService`.
This improves testability and simplifies dependency management.
Bug: 418183047
Test: atest AdapterServiceTest
Test: atest LeAudioServiceTest
Test: atest LeAudioBroadcastServiceTest
Flag: EXEMPT refactor no-op
Change-Id: I6932a5a7f2fba19009fcdf91a8e80048c9d2bdea
diff --git a/android/app/jni/com_android_bluetooth_le_audio.cpp b/android/app/jni/com_android_bluetooth_le_audio.cpp
index 99b61e5..a2e61f8 100644
--- a/android/app/jni/com_android_bluetooth_le_audio.cpp
+++ b/android/app/jni/com_android_bluetooth_le_audio.cpp
@@ -173,7 +173,7 @@
if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) {
return;
}
- sCallbackEnv->CallStaticVoidMethod(class_LeAudioNativeInterface, method_onInitialized);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onInitialized);
}
void OnConnectionState(ConnectionState state, const RawAddress& bd_addr) override {
@@ -1581,7 +1581,7 @@
{"onGroupNodeStatus", "([BII)V", &method_onGroupNodeStatus},
{"onAudioConf", "(IIIII)V", &method_onAudioConf},
{"onSinkAudioLocationAvailable", "([BI)V", &method_onSinkAudioLocationAvailable},
- {"onInitialized", "()V", &method_onInitialized, true},
+ {"onInitialized", "()V", &method_onInitialized},
{"onConnectionStateChanged", "(I[B)V", &method_onConnectionStateChanged},
{"onAudioLocalCodecCapabilities",
"([Landroid/bluetooth/BluetoothLeAudioCodecConfig;"
diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java
index 6d725c3..483a9e2 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java
@@ -31,7 +31,6 @@
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Arrays;
@@ -41,32 +40,11 @@
private static final String TAG = LeAudioNativeInterface.class.getSimpleName();
private final AdapterService mAdapterService;
+ private final LeAudioService mService;
- @GuardedBy("INSTANCE_LOCK")
- private static LeAudioNativeInterface sInstance;
-
- private static final Object INSTANCE_LOCK = new Object();
-
- private LeAudioNativeInterface(AdapterService adapterService) {
+ LeAudioNativeInterface(AdapterService adapterService, LeAudioService service) {
mAdapterService = requireNonNull(adapterService);
- }
-
- /** Get singleton instance. */
- public static LeAudioNativeInterface getInstance(AdapterService adapterService) {
- synchronized (INSTANCE_LOCK) {
- if (sInstance == null) {
- sInstance = new LeAudioNativeInterface(adapterService);
- }
- return sInstance;
- }
- }
-
- /** Set singleton instance. */
- @VisibleForTesting
- public static void setInstance(LeAudioNativeInterface instance) {
- synchronized (INSTANCE_LOCK) {
- sInstance = instance;
- }
+ mService = requireNonNull(service);
}
private static byte[] getByteAddress(BluetoothDevice device) {
@@ -76,15 +54,6 @@
return Utils.getBytesFromAddress(device.getAddress());
}
- private static void sendMessageToService(LeAudioStackEvent event) {
- LeAudioService service = LeAudioService.getLeAudioService();
- if (service != null) {
- service.messageFromNative(event);
- } else {
- Log.e(TAG, "Event ignored, service not available: " + event);
- }
- }
-
private BluetoothDevice getDevice(byte[] address) {
return mAdapterService.getRemoteDevice(Utils.getAddressStringFromByte(address));
}
@@ -92,12 +61,12 @@
// Callbacks from the native stack back into the Java framework.
// All callbacks are routed via the Service which will disambiguate which
// state machine the message should be routed to.
- private static void onInitialized() {
+ private void onInitialized() {
LeAudioStackEvent event =
new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_NATIVE_INITIALIZED);
Log.d(TAG, "onInitialized: " + event);
- sendMessageToService(event);
+ mService.messageFromNative(event);
}
@VisibleForTesting
@@ -108,7 +77,7 @@
event.valueInt1 = state;
Log.d(TAG, "onConnectionStateChanged: " + event);
- sendMessageToService(event);
+ mService.messageFromNative(event);
}
@VisibleForTesting
@@ -119,7 +88,7 @@
event.valueInt2 = groupStatus;
Log.d(TAG, "onGroupStatus: " + event);
- sendMessageToService(event);
+ mService.messageFromNative(event);
}
@VisibleForTesting
@@ -131,7 +100,7 @@
event.device = getDevice(address);
Log.d(TAG, "onGroupNodeStatus: " + event);
- sendMessageToService(event);
+ mService.messageFromNative(event);
}
@VisibleForTesting
@@ -150,7 +119,7 @@
event.valueInt5 = availableContexts;
Log.d(TAG, "onAudioConf: " + event);
- sendMessageToService(event);
+ mService.messageFromNative(event);
}
@VisibleForTesting
@@ -161,7 +130,7 @@
event.valueInt1 = sinkAudioLocation;
Log.d(TAG, "onSinkAudioLocationAvailable: " + event);
- sendMessageToService(event);
+ mService.messageFromNative(event);
}
@VisibleForTesting
@@ -176,7 +145,7 @@
event.valueCodecList2 = Arrays.asList(localOutputCodecCapabilities);
Log.d(TAG, "onAudioLocalCodecCapabilities: " + event);
- sendMessageToService(event);
+ mService.messageFromNative(event);
}
@VisibleForTesting
@@ -193,7 +162,7 @@
event.valueCodec2 = outputCodecConfig;
Log.d(TAG, "onAudioGroupCurrentCodecConf: " + event);
- sendMessageToService(event);
+ mService.messageFromNative(event);
}
@VisibleForTesting
@@ -210,7 +179,7 @@
event.valueCodecList2 = Arrays.asList(outputSelectableCodecConfig);
Log.d(TAG, "onAudioGroupSelectableCodecConf: " + event);
- sendMessageToService(event);
+ mService.messageFromNative(event);
}
@VisibleForTesting
@@ -221,7 +190,7 @@
event.valueInt1 = action;
Log.d(TAG, "onHealthBasedRecommendationAction: " + event);
- sendMessageToService(event);
+ mService.messageFromNative(event);
}
@VisibleForTesting
@@ -233,7 +202,7 @@
event.valueInt2 = action;
Log.d(TAG, "onHealthBasedGroupRecommendationAction: " + event);
- sendMessageToService(event);
+ mService.messageFromNative(event);
}
@VisibleForTesting
@@ -244,7 +213,7 @@
event.valueInt2 = status;
Log.d(TAG, "onUnicastMonitorModeStatus: " + event);
- sendMessageToService(event);
+ mService.messageFromNative(event);
}
@VisibleForTesting
@@ -255,7 +224,7 @@
event.valueInt2 = groupStreamStatus;
Log.d(TAG, "onGroupStreamStatus: " + event);
- sendMessageToService(event);
+ mService.messageFromNative(event);
}
/**
@@ -263,12 +232,12 @@
*
* <p>priorities to configure.
*/
- public void init(BluetoothLeAudioCodecConfig[] codecConfigOffloading) {
+ void init(BluetoothLeAudioCodecConfig[] codecConfigOffloading) {
initNative(codecConfigOffloading);
}
/** Cleanup the native interface. */
- public void cleanup() {
+ void cleanup() {
cleanupNative();
}
@@ -278,7 +247,7 @@
* @param device the remote device
* @return true on success, otherwise false.
*/
- public boolean connectLeAudio(BluetoothDevice device) {
+ boolean connectLeAudio(BluetoothDevice device) {
return connectLeAudioNative(getByteAddress(device));
}
@@ -288,7 +257,7 @@
* @param device the remote device
* @return true on success, otherwise false.
*/
- public boolean disconnectLeAudio(BluetoothDevice device) {
+ boolean disconnectLeAudio(BluetoothDevice device) {
return disconnectLeAudioNative(getByteAddress(device));
}
@@ -299,7 +268,7 @@
* @param enabled true if enabled, false to disabled
* @return true on success, otherwise false.
*/
- public boolean setEnableState(BluetoothDevice device, boolean enabled) {
+ boolean setEnableState(BluetoothDevice device, boolean enabled) {
return setEnableStateNative(getByteAddress(device), enabled);
}
@@ -309,7 +278,7 @@
* @param groupId group identifier
* @param device remote device
*/
- public boolean groupAddNode(int groupId, BluetoothDevice device) {
+ boolean groupAddNode(int groupId, BluetoothDevice device) {
return groupAddNodeNative(groupId, getByteAddress(device));
}
@@ -319,7 +288,7 @@
* @param groupId group identifier
* @param device remote device
*/
- public boolean groupRemoveNode(int groupId, BluetoothDevice device) {
+ boolean groupRemoveNode(int groupId, BluetoothDevice device) {
return groupRemoveNodeNative(groupId, getByteAddress(device));
}
@@ -328,7 +297,7 @@
*
* @param groupId group ID to set as active
*/
- public void groupSetActive(int groupId) {
+ void groupSetActive(int groupId) {
groupSetActiveNative(groupId);
}
@@ -339,7 +308,7 @@
* @param inputCodecConfig input codec configuration
* @param outputCodecConfig output codec configuration
*/
- public void setCodecConfigPreference(
+ void setCodecConfigPreference(
int groupId,
BluetoothLeAudioCodecConfig inputCodecConfig,
BluetoothLeAudioCodecConfig outputCodecConfig) {
@@ -352,7 +321,7 @@
* @param ccid content control id
* @param contextType assigned contextType
*/
- public void setCcidInformation(int ccid, int contextType) {
+ void setCcidInformation(int ccid, int contextType) {
Log.d(TAG, "setCcidInformation ccid: " + ccid + " context type: " + contextType);
setCcidInformationNative(ccid, contextType);
}
@@ -362,7 +331,7 @@
*
* @param inCall true when device in call (any state), false otherwise
*/
- public void setInCall(boolean inCall) {
+ void setInCall(boolean inCall) {
Log.d(TAG, "setInCall inCall: " + inCall);
setInCallNative(inCall);
}
@@ -374,7 +343,7 @@
* @param enable true when LE Audio device should be listening for streaming status on direction
* stream. false otherwise
*/
- public void setUnicastMonitorMode(int direction, boolean enable) {
+ void setUnicastMonitorMode(int direction, boolean enable) {
Log.d(TAG, "setUnicastMonitorMode enable: " + enable + ", direction : " + direction);
setUnicastMonitorModeNative(direction, enable);
}
@@ -386,7 +355,7 @@
* @param isOutputPreferenceLeAudio whether LEA is preferred for OUTPUT_ONLY
* @param isDuplexPreferenceLeAudio whether LEA is preferred for DUPLEX
*/
- public void sendAudioProfilePreferences(
+ void sendAudioProfilePreferences(
int groupId, boolean isOutputPreferenceLeAudio, boolean isDuplexPreferenceLeAudio) {
Log.d(
TAG,
@@ -407,8 +376,7 @@
* @param sinkContextTypes sink context types that would be allowed to stream
* @param sourceContextTypes source context types that would be allowed to stream
*/
- public void setGroupAllowedContextMask(
- int groupId, int sinkContextTypes, int sourceContextTypes) {
+ void setGroupAllowedContextMask(int groupId, int sinkContextTypes, int sourceContextTypes) {
Log.d(
TAG,
"setGroupAllowedContextMask groupId="
diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
index 4fd3e93..8cac3fb 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
@@ -37,6 +37,7 @@
import static com.android.bluetooth.flags.Flags.leaudioUseAudioRecordingListener;
import static java.util.Objects.requireNonNull;
+import static java.util.Objects.requireNonNullElseGet;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
@@ -245,13 +246,15 @@
BluetoothLeScanner mAudioServersScanner;
public LeAudioService(AdapterService adapterService) {
- this(adapterService, LeAudioNativeInterface.getInstance(adapterService));
+ this(adapterService, null);
}
@VisibleForTesting
LeAudioService(AdapterService adapterService, LeAudioNativeInterface nativeInterface) {
super(requireNonNull(adapterService));
- mNativeInterface = requireNonNull(nativeInterface);
+ mNativeInterface =
+ requireNonNullElseGet(
+ nativeInterface, () -> new LeAudioNativeInterface(adapterService, this));
mDatabaseManager = requireNonNull(mAdapterService.getDatabase());
mAudioManager = requireNonNull(obtainSystemService(AudioManager.class));
diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioNativeInterfaceTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioNativeInterfaceTest.java
index 060b415..1bd39fa 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioNativeInterfaceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioNativeInterfaceTest.java
@@ -33,7 +33,6 @@
import com.android.bluetooth.btservice.AdapterService;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -61,14 +60,7 @@
.when(mAdapterService)
.getRemoteDevice(anyString());
when(mMockService.isAvailable()).thenReturn(true);
- LeAudioService.setLeAudioService(mMockService);
- mNativeInterface = LeAudioNativeInterface.getInstance(mAdapterService);
- }
-
- @After
- public void tearDown() {
- LeAudioService.setLeAudioService(null);
- LeAudioNativeInterface.setInstance(null);
+ mNativeInterface = new LeAudioNativeInterface(mAdapterService, mMockService);
}
@Test