Merge "Add new resource for time delay of CLCC polling"
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index 2ac4253..84efadd 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -3394,12 +3394,12 @@
             }
 
             // Copy the traffic objects whose byte counts are > 0
-            final UidTraffic[] result = arrayLen > 0 ? new UidTraffic[arrayLen] : null;
+            final List<UidTraffic> result = new ArrayList<>();
             int putIdx = 0;
             for (int i = 0; i < mUidTraffic.size(); i++) {
                 final UidTraffic traffic = mUidTraffic.valueAt(i);
                 if (traffic.getTxBytes() != 0 || traffic.getRxBytes() != 0) {
-                    result[putIdx++] = traffic.clone();
+                    result.add(traffic.clone());
                 }
             }
 
diff --git a/src/com/android/bluetooth/le_audio/LeAudioService.java b/src/com/android/bluetooth/le_audio/LeAudioService.java
index f0abbc8..b03b161 100644
--- a/src/com/android/bluetooth/le_audio/LeAudioService.java
+++ b/src/com/android/bluetooth/le_audio/LeAudioService.java
@@ -1513,6 +1513,8 @@
     @Override
     public void dump(StringBuilder sb) {
         super.dump(sb);
-        // TODO: Dump all state machines
+        for (LeAudioStateMachine sm : mStateMachines.values()) {
+            sm.dump(sb);
+        }
     }
 }
diff --git a/src/com/android/bluetooth/le_audio/LeAudioStackEvent.java b/src/com/android/bluetooth/le_audio/LeAudioStackEvent.java
index e433915..ab40c8b 100644
--- a/src/com/android/bluetooth/le_audio/LeAudioStackEvent.java
+++ b/src/com/android/bluetooth/le_audio/LeAudioStackEvent.java
@@ -149,8 +149,6 @@
 
     private static String eventTypeValue3ToString(int type, int value) {
         switch (type) {
-            case EVENT_TYPE_GROUP_STATUS_CHANGED:
-                return "{group_flags:" + Integer.toString(value) + "}";
             case EVENT_TYPE_AUDIO_CONF_CHANGED:
                 // FIXME: It should have proper location names here
                 return "{snk_audio_loc:" + value + "}";
diff --git a/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java b/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java
index 6d9d9c9..a838cbb 100644
--- a/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java
+++ b/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java
@@ -63,6 +63,11 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Scanner;
+
 final class LeAudioStateMachine extends StateMachine {
     private static final boolean DBG = false;
     private static final String TAG = "LeAudioStateMachine";
@@ -553,6 +558,24 @@
         return Integer.toString(state);
     }
 
+    public void dump(StringBuilder sb) {
+        ProfileService.println(sb, "mDevice: " + mDevice);
+        ProfileService.println(sb, "  StateMachine: " + this);
+        // Dump the state machine logs
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+        super.dump(new FileDescriptor(), printWriter, new String[]{});
+        printWriter.flush();
+        stringWriter.flush();
+        ProfileService.println(sb, "  StateMachineLog:");
+        Scanner scanner = new Scanner(stringWriter.toString());
+        while (scanner.hasNextLine()) {
+            String line = scanner.nextLine();
+            ProfileService.println(sb, "    " + line);
+        }
+        scanner.close();
+    }
+
     @Override
     protected void log(String msg) {
         if (DBG) {
diff --git a/src/com/android/bluetooth/telephony/BluetoothInCallService.java b/src/com/android/bluetooth/telephony/BluetoothInCallService.java
index 410dc09..dacfa06 100644
--- a/src/com/android/bluetooth/telephony/BluetoothInCallService.java
+++ b/src/com/android/bluetooth/telephony/BluetoothInCallService.java
@@ -26,6 +26,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -55,6 +56,9 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Used to receive updates about calls from the Telecom component. This service is bound to Telecom
@@ -89,6 +93,8 @@
     // Indicates that no BluetoothCall is ringing
     private static final int DEFAULT_RINGING_ADDRESS_TYPE = 128;
 
+    private static final int DISCONNECT_TONE_TIMEOUT_SECONDS = 1;
+
     private int mNumActiveCalls = 0;
     private int mNumHeldCalls = 0;
     private int mNumChildrenOfActiveCall = 0;
@@ -102,6 +108,13 @@
     private static final Object LOCK = new Object();
     private BluetoothHeadsetProxy mBluetoothHeadset;
 
+    private Semaphore mDisconnectionToneSemaphore = new Semaphore(0);
+    private int mAudioMode = AudioManager.MODE_INVALID;
+    private final Object mAudioModeLock = new Object();
+
+    @VisibleForTesting
+    public AudioManager mAudioManager;
+
     @VisibleForTesting
     public TelephonyManager mTelephonyManager;
 
@@ -291,6 +304,19 @@
         }
     }
 
+    class BluetoothOnModeChangedListener implements AudioManager.OnModeChangedListener {
+        @Override
+        public void onModeChanged(int mode) {
+            synchronized (mAudioModeLock) {
+                mAudioMode = mode;
+            }
+            if (mode == AudioManager.MODE_NORMAL) {
+                mDisconnectionToneSemaphore.release();
+            }
+        }
+    }
+    private BluetoothOnModeChangedListener mBluetoothOnModeChangedListener;
+
     @Override
     public IBinder onBind(Intent intent) {
         Log.i(TAG, "onBind. Intent: " + intent);
@@ -525,6 +551,26 @@
             return;
         }
         Log.d(TAG, "onCallRemoved");
+        BluetoothCall heldCall = mCallInfo.getHeldCall();
+        if (mCallInfo.isNullCall(heldCall)) {
+            // current call is the only call
+
+            mDisconnectionToneSemaphore.drainPermits();
+            boolean isAudioModeNormal = false;
+            synchronized (mAudioModeLock) {
+                isAudioModeNormal = (mAudioMode == AudioManager.MODE_NORMAL);
+            }
+            if (!isAudioModeNormal) {
+                Log.d(TAG, "Acquiring mDisconnectionToneSemaphore");
+                try {
+                  boolean result = mDisconnectionToneSemaphore.tryAcquire(
+                    DISCONNECT_TONE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+                  Log.d(TAG, "Acquiring mDisconnectionToneSemaphore result " + result);
+                } catch (InterruptedException e) {
+                  Log.w(TAG, "Failed to acquire mDisconnectionToneSemaphore");
+                }
+            }
+        }
         CallStateCallback callback = getCallback(call);
         if (callback != null) {
             call.unregisterCallback(callback);
@@ -565,11 +611,19 @@
         mBluetoothAdapterReceiver = new BluetoothAdapterReceiver();
         IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
         registerReceiver(mBluetoothAdapterReceiver, intentFilter);
+        mBluetoothOnModeChangedListener = new BluetoothOnModeChangedListener();
+        mAudioManager = getSystemService(AudioManager.class);
+        mAudioManager.addOnModeChangedListener(
+                Executors.newSingleThreadExecutor(), mBluetoothOnModeChangedListener);
     }
 
     @Override
     public void onDestroy() {
         Log.d(TAG, "onDestroy");
+        if (mBluetoothOnModeChangedListener != null) {
+            mAudioManager.removeOnModeChangedListener(mBluetoothOnModeChangedListener);
+            mBluetoothOnModeChangedListener = null;
+        }
         if (mBluetoothAdapterReceiver != null) {
             unregisterReceiver(mBluetoothAdapterReceiver);
             mBluetoothAdapterReceiver = null;
diff --git a/tests/unit/AndroidTest.xml b/tests/unit/AndroidTest.xml
index 6edd9a5..8cea43c 100644
--- a/tests/unit/AndroidTest.xml
+++ b/tests/unit/AndroidTest.xml
@@ -22,8 +22,8 @@
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="settings put global ble_scan_always_enabled 0" />
-        <option name="run-command" value="svc bluetooth disable" />
-        <option name="teardown-command" value="svc bluetooth enable" />
+        <option name="run-command" value="su u$(am get-current-user)_system svc bluetooth disable" />
+        <option name="teardown-command" value="su u$(am get-current-user)_system svc bluetooth enable" />
         <option name="teardown-command" value="settings put global ble_scan_always_enabled 1" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.FolderSaver">
diff --git a/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java b/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
index 90c3953..c0132aa 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
@@ -1070,7 +1070,7 @@
         device.put("migrated", false);
         assertThat(db.insert("metadata", SQLiteDatabase.CONFLICT_IGNORE, device),
                 CoreMatchers.not(-1));
-        // Migrate database from 106 to 107
+        // Migrate database from 107 to 108
         db.close();
         db = testHelper.runMigrationsAndValidate(DB_NAME, 108, true,
                 MetadataDatabase.MIGRATION_107_108);