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