[Bluetooth] Convert BluetoothController logs to log buffer so we'll
always have them.

We're mainly interested in the activeDeviceChanged log for b/243755182
but it'll be good to have all the logs.

Bug: 243755182
Fixes: 234774643
Test: dumped log buffer from device
Test: BluetoothControllerImplTest
Change-Id: Id1b12dbf802cae07120f1d6cc2c0b2e3967a1c75
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
index 3152e65..cbdac61 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
@@ -16,6 +16,20 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothAdapter.STATE_CONNECTED;
+import static android.bluetooth.BluetoothAdapter.STATE_CONNECTING;
+import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTED;
+import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTING;
+import static android.bluetooth.BluetoothAdapter.STATE_OFF;
+import static android.bluetooth.BluetoothAdapter.STATE_ON;
+import static android.bluetooth.BluetoothAdapter.STATE_TURNING_OFF;
+import static android.bluetooth.BluetoothAdapter.STATE_TURNING_ON;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * BluetoothCallback provides a callback interface for the settings
@@ -33,7 +47,7 @@
      * {@link android.bluetooth.BluetoothAdapter#STATE_ON},
      * {@link android.bluetooth.BluetoothAdapter#STATE_TURNING_OFF}.
      */
-    default void onBluetoothStateChanged(int bluetoothState) {}
+    default void onBluetoothStateChanged(@AdapterState int bluetoothState) {}
 
     /**
      * It will be called when the local Bluetooth adapter has started
@@ -89,7 +103,9 @@
      * {@link android.bluetooth.BluetoothAdapter#STATE_CONNECTED},
      * {@link android.bluetooth.BluetoothAdapter#STATE_DISCONNECTING}.
      */
-    default void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {}
+    default void onConnectionStateChanged(
+            @Nullable CachedBluetoothDevice cachedDevice,
+            @ConnectionState int state) {}
 
     /**
      * It will be called when device been set as active for {@code bluetoothProfile}
@@ -124,8 +140,10 @@
      * {@link android.bluetooth.BluetoothProfile#STATE_DISCONNECTING}.
      * @param bluetoothProfile the BluetoothProfile id.
      */
-    default void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice,
-            int state, int bluetoothProfile) {
+    default void onProfileConnectionStateChanged(
+            CachedBluetoothDevice cachedDevice,
+            @ConnectionState int state,
+            int bluetoothProfile) {
     }
 
     /**
@@ -140,4 +158,22 @@
      */
     default void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
     }
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "STATE_" }, value = {
+            STATE_DISCONNECTED,
+            STATE_CONNECTING,
+            STATE_CONNECTED,
+            STATE_DISCONNECTING,
+    })
+    @interface ConnectionState {}
+
+    @IntDef(prefix = { "STATE_" }, value = {
+            STATE_OFF,
+            STATE_TURNING_ON,
+            STATE_ON,
+            STATE_TURNING_OFF,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface AdapterState {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
new file mode 100644
index 0000000..b5da7b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.systemui.bluetooth
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.BluetoothLog
+import javax.inject.Inject
+
+/** Helper class for logging bluetooth events. */
+@SysUISingleton
+class BluetoothLogger @Inject constructor(@BluetoothLog private val logBuffer: LogBuffer) {
+    fun logActiveDeviceChanged(address: String, profileId: Int) =
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = address
+                int1 = profileId
+            },
+            { "ActiveDeviceChanged. address=$str1 profileId=$int1" }
+        )
+
+    fun logDeviceConnectionStateChanged(address: String?, state: String) =
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = address
+                str2 = state
+            },
+            { "DeviceConnectionStateChanged. address=$str1 state=$str2" }
+        )
+
+    fun logAclConnectionStateChanged(address: String, state: String) =
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = address
+                str2 = state
+            },
+            { "AclConnectionStateChanged. address=$str1 state=$str2" }
+        )
+
+    fun logProfileConnectionStateChanged(address: String, state: String, profileId: Int) =
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = address
+                str2 = state
+                int1 = profileId
+            },
+            { "ProfileConnectionStateChanged. address=$str1 state=$str2 profileId=$int1" }
+        )
+
+    fun logStateChange(state: String) =
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = state },
+            { "BluetoothStateChanged. state=$str1" }
+        )
+
+    fun logBondStateChange(address: String, state: Int) =
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = address
+                int1 = state
+            },
+            { "DeviceBondStateChanged. address=$str1 state=$int1" }
+        )
+
+    fun logDeviceAdded(address: String) =
+        logBuffer.log(TAG, LogLevel.DEBUG, { str1 = address }, { "DeviceAdded. address=$str1" })
+
+    fun logDeviceDeleted(address: String) =
+        logBuffer.log(TAG, LogLevel.DEBUG, { str1 = address }, { "DeviceDeleted. address=$str1" })
+
+    fun logDeviceAttributesChanged() =
+        logBuffer.log(TAG, LogLevel.DEBUG, {}, { "DeviceAttributesChanged." })
+}
+
+private const val TAG = "BluetoothLog"
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index a3dc779..659f286 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -601,7 +601,7 @@
 
     private final class BluetoothCallbackHandler implements BluetoothCallback {
         @Override
-        public void onBluetoothStateChanged(int bluetoothState) {
+        public void onBluetoothStateChanged(@BluetoothCallback.AdapterState int bluetoothState) {
             mHandler.obtainMessage(MSG_ON_BLUETOOTH_STATE_CHANGED,
                     bluetoothState, 0).sendToTarget();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt
new file mode 100644
index 0000000..4887b6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for bluetooth. */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class BluetoothLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
index 323ee21..b551125 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
@@ -1,4 +1,9 @@
 package com.android.systemui.log.dagger
 
+import javax.inject.Qualifier
+
 /** A [com.android.systemui.log.LogBuffer] for KeyguardUpdateMonitor. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
 annotation class KeyguardUpdateMonitorLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index c2a8764..eeba6b3 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -305,4 +305,14 @@
     public static LogBuffer provideKeyguardUpdateMonitorLogBuffer(LogBufferFactory factory) {
         return factory.create("KeyguardUpdateMonitorLog", 200);
     }
+
+    /**
+     * Provides a {@link LogBuffer} for bluetooth-related logs.
+     */
+    @Provides
+    @SysUISingleton
+    @BluetoothLog
+    public static LogBuffer providerBluetoothLogBuffer(LogBufferFactory factory) {
+        return factory.create("BluetoothLog", 50);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index e7fa6d2..c7ea3c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -27,7 +27,6 @@
 import android.os.Message;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.util.Log;
 
 import androidx.annotation.NonNull;
 
@@ -37,6 +36,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.systemui.bluetooth.BluetoothLogger;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -57,9 +57,9 @@
 public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
         CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener {
     private static final String TAG = "BluetoothController";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final DumpManager mDumpManager;
+    private final BluetoothLogger mLogger;
     private final LocalBluetoothManager mLocalBluetoothManager;
     private final UserManager mUserManager;
     private final int mCurrentUser;
@@ -70,6 +70,7 @@
     private final List<CachedBluetoothDevice> mConnectedDevices = new ArrayList<>();
 
     private boolean mEnabled;
+    @ConnectionState
     private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
     private boolean mAudioProfileOnly;
     private boolean mIsActive;
@@ -83,10 +84,12 @@
     public BluetoothControllerImpl(
             Context context,
             DumpManager dumpManager,
+            BluetoothLogger logger,
             @Background Looper bgLooper,
             @Main Looper mainLooper,
             @Nullable LocalBluetoothManager localBluetoothManager) {
         mDumpManager = dumpManager;
+        mLogger = logger;
         mLocalBluetoothManager = localBluetoothManager;
         mBgHandler = new Handler(bgLooper);
         mHandler = new H(mainLooper);
@@ -116,7 +119,7 @@
             return;
         }
         pw.print("  mEnabled="); pw.println(mEnabled);
-        pw.print("  mConnectionState="); pw.println(stateToString(mConnectionState));
+        pw.print("  mConnectionState="); pw.println(connectionStateToString(mConnectionState));
         pw.print("  mAudioProfileOnly="); pw.println(mAudioProfileOnly);
         pw.print("  mIsActive="); pw.println(mIsActive);
         pw.print("  mConnectedDevices="); pw.println(getConnectedDevices());
@@ -127,7 +130,7 @@
         }
     }
 
-    private static String stateToString(int state) {
+    private static String connectionStateToString(@ConnectionState int state) {
         switch (state) {
             case BluetoothAdapter.STATE_CONNECTED:
                 return "CONNECTED";
@@ -320,8 +323,8 @@
     }
 
     @Override
-    public void onBluetoothStateChanged(int bluetoothState) {
-        if (DEBUG) Log.d(TAG, "BluetoothStateChanged=" + stateToString(bluetoothState));
+    public void onBluetoothStateChanged(@AdapterState int bluetoothState) {
+        mLogger.logStateChange(BluetoothAdapter.nameForState(bluetoothState));
         mEnabled = bluetoothState == BluetoothAdapter.STATE_ON
                 || bluetoothState == BluetoothAdapter.STATE_TURNING_ON;
         mState = bluetoothState;
@@ -331,7 +334,7 @@
 
     @Override
     public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
-        if (DEBUG) Log.d(TAG, "DeviceAdded=" + cachedDevice.getAddress());
+        mLogger.logDeviceAdded(cachedDevice.getAddress());
         cachedDevice.registerCallback(this);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
@@ -339,7 +342,7 @@
 
     @Override
     public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
-        if (DEBUG) Log.d(TAG, "DeviceDeleted=" + cachedDevice.getAddress());
+        mLogger.logDeviceDeleted(cachedDevice.getAddress());
         mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
@@ -347,7 +350,7 @@
 
     @Override
     public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
-        if (DEBUG) Log.d(TAG, "DeviceBondStateChanged=" + cachedDevice.getAddress());
+        mLogger.logBondStateChange(cachedDevice.getAddress(), bondState);
         mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
@@ -355,29 +358,29 @@
 
     @Override
     public void onDeviceAttributesChanged() {
-        if (DEBUG) Log.d(TAG, "DeviceAttributesChanged");
+        mLogger.logDeviceAttributesChanged();
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
     }
 
     @Override
-    public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
-        if (DEBUG) {
-            Log.d(TAG, "ConnectionStateChanged=" + cachedDevice.getAddress() + " "
-                    + stateToString(state));
-        }
+    public void onConnectionStateChanged(
+            @Nullable CachedBluetoothDevice cachedDevice,
+            @ConnectionState int state) {
+        String address = cachedDevice == null ? null : cachedDevice.getAddress();
+        mLogger.logDeviceConnectionStateChanged(address, connectionStateToString(state));
         mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
     }
 
     @Override
-    public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice,
-            int state, int bluetoothProfile) {
-        if (DEBUG) {
-            Log.d(TAG, "ProfileConnectionStateChanged=" + cachedDevice.getAddress() + " "
-                    + stateToString(state) + " profileId=" + bluetoothProfile);
-        }
+    public void onProfileConnectionStateChanged(
+            CachedBluetoothDevice cachedDevice,
+            @ConnectionState int state,
+            int bluetoothProfile) {
+        mLogger.logProfileConnectionStateChanged(
+                cachedDevice.getAddress(), connectionStateToString(state), bluetoothProfile);
         mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
@@ -385,20 +388,15 @@
 
     @Override
     public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
-        if (DEBUG) {
-            Log.d(TAG, "ActiveDeviceChanged=" + activeDevice.getAddress()
-                    + " profileId=" + bluetoothProfile);
-        }
+        mLogger.logActiveDeviceChanged(activeDevice.getAddress(), bluetoothProfile);
         updateActive();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
     }
 
     @Override
     public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
-        if (DEBUG) {
-            Log.d(TAG, "ACLConnectionStateChanged=" + cachedDevice.getAddress() + " "
-                    + stateToString(state));
-        }
+        mLogger.logAclConnectionStateChanged(
+                cachedDevice.getAddress(), connectionStateToString(state));
         mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 3dd36d1..cf5e878 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -41,6 +41,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bluetooth.BluetoothLogger;
 import com.android.systemui.dump.DumpManager;
 
 import org.junit.Before;
@@ -81,6 +82,7 @@
 
         mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
                 mMockDumpManager,
+                mock(BluetoothLogger.class),
                 mTestableLooper.getLooper(),
                 mTestableLooper.getLooper(),
                 mMockBluetoothManager);