Merge "[Audiosharing] Log action when change primary device" into main
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java
index e848f88..b623ba8 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java
@@ -18,6 +18,7 @@
 
 import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.SETTINGS_KEY_FALLBACK_DEVICE_GROUP_ID;
 
+import android.app.settings.SettingsEnums;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
@@ -36,19 +37,22 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.fragment.app.Fragment;
 import androidx.lifecycle.LifecycleOwner;
 import androidx.preference.PreferenceScreen;
 
 import com.android.settings.R;
 import com.android.settings.bluetooth.Utils;
-import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.bluetooth.BluetoothCallback;
 import com.android.settingslib.bluetooth.BluetoothEventManager;
 import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 import com.android.settingslib.utils.ThreadUtils;
 
 import com.google.common.collect.ImmutableList;
@@ -67,18 +71,26 @@
     private static final String TAG = "CallsAndAlarmsPreferenceController";
     private static final String PREF_KEY = "calls_and_alarms";
 
+    @VisibleForTesting
+    protected enum ChangeCallAudioType {
+        UNKNOWN,
+        CONNECTED_EARLIER,
+        CONNECTED_LATER
+    }
+
     @Nullable private final LocalBluetoothManager mBtManager;
-    @Nullable private final LocalBluetoothProfileManager mProfileManager;
     @Nullable private final BluetoothEventManager mEventManager;
     @Nullable private final ContentResolver mContentResolver;
     @Nullable private final LocalBluetoothLeBroadcastAssistant mAssistant;
+    @Nullable private final CachedBluetoothDeviceManager mCacheManager;
     private final Executor mExecutor;
     private final ContentObserver mSettingsObserver;
-    @Nullable private DashboardFragment mFragment;
+    private final MetricsFeatureProvider mMetricsFeatureProvider;
+    @Nullable private Fragment mFragment;
     Map<Integer, List<CachedBluetoothDevice>> mGroupedConnectedDevices = new HashMap<>();
     private List<AudioSharingDeviceItem> mDeviceItemsInSharingSession = new ArrayList<>();
-    private AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false);
-    private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
+    private final AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false);
+    private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
             new BluetoothLeBroadcastAssistant.Callback() {
                 @Override
                 public void onSearchStarted(int reason) {}
@@ -136,15 +148,18 @@
     public AudioSharingCallAudioPreferenceController(Context context) {
         super(context, PREF_KEY);
         mBtManager = Utils.getLocalBtManager(mContext);
-        mProfileManager = mBtManager == null ? null : mBtManager.getProfileManager();
+        LocalBluetoothProfileManager profileManager =
+                mBtManager == null ? null : mBtManager.getProfileManager();
         mEventManager = mBtManager == null ? null : mBtManager.getEventManager();
         mAssistant =
-                mProfileManager == null
+                profileManager == null
                         ? null
-                        : mProfileManager.getLeAudioBroadcastAssistantProfile();
+                        : profileManager.getLeAudioBroadcastAssistantProfile();
+        mCacheManager = mBtManager == null ? null : mBtManager.getCachedDeviceManager();
         mExecutor = Executors.newSingleThreadExecutor();
         mContentResolver = context.getContentResolver();
         mSettingsObserver = new FallbackDeviceGroupIdSettingsObserver();
+        mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
     }
 
     private class FallbackDeviceGroupIdSettingsObserver extends ContentObserver {
@@ -155,7 +170,9 @@
         @Override
         public void onChange(boolean selfChange) {
             Log.d(TAG, "onChange, fallback device group id has been changed");
-            var unused = ThreadUtils.postOnBackgroundThread(() -> updateSummary());
+            var unused =
+                    ThreadUtils.postOnBackgroundThread(
+                            AudioSharingCallAudioPreferenceController.this::updateSummary);
         }
     }
 
@@ -177,15 +194,23 @@
                             return true;
                         }
                         updateDeviceItemsInSharingSession();
-                        if (mDeviceItemsInSharingSession.size() >= 1) {
+                        if (!mDeviceItemsInSharingSession.isEmpty()) {
                             AudioSharingCallAudioDialogFragment.show(
                                     mFragment,
                                     mDeviceItemsInSharingSession,
                                     (AudioSharingDeviceItem item) -> {
+                                        int currentGroupId =
+                                                AudioSharingUtils.getFallbackActiveGroupId(
+                                                        mContext);
+                                        if (item.getGroupId() == currentGroupId) {
+                                            Log.d(
+                                                    TAG,
+                                                    "Skip set fallback active device: unchanged");
+                                            return;
+                                        }
                                         List<CachedBluetoothDevice> devices =
                                                 mGroupedConnectedDevices.getOrDefault(
                                                         item.getGroupId(), ImmutableList.of());
-                                        @Nullable
                                         CachedBluetoothDevice lead =
                                                 AudioSharingUtils.getLeadDevice(devices);
                                         if (lead != null) {
@@ -195,11 +220,12 @@
                                                             + lead.getDevice()
                                                                     .getAnonymizedAddress());
                                             lead.setActive();
+                                            logCallAudioDeviceChange(currentGroupId, lead);
                                         } else {
-                                            Log.w(
+                                            Log.d(
                                                     TAG,
-                                                    "Fail to set fallback active device: no lead"
-                                                            + " device");
+                                                    "Fail to set fallback active device: no"
+                                                            + " lead device");
                                         }
                                     });
                         }
@@ -237,9 +263,9 @@
     /**
      * Initialize the controller.
      *
-     * @param fragment The fragment to host the {@link CallsAndAlarmsDialogFragment} dialog.
+     * @param fragment The fragment to host the {@link AudioSharingCallAudioDialogFragment} dialog.
      */
-    public void init(DashboardFragment fragment) {
+    public void init(Fragment fragment) {
         this.mFragment = fragment;
     }
 
@@ -325,7 +351,7 @@
                 if (item.getGroupId() == fallbackActiveGroupId) {
                     Log.d(
                             TAG,
-                            "updatePreference: set summary tp fallback group "
+                            "updatePreference: set summary to fallback group "
                                     + fallbackActiveGroupId);
                     AudioSharingUtils.postOnMainThread(
                             mContext,
@@ -357,4 +383,48 @@
                 AudioSharingUtils.buildOrderedConnectedLeadAudioSharingDeviceItem(
                         mBtManager, mGroupedConnectedDevices, /* filterByInSharing= */ true);
     }
+
+    @VisibleForTesting
+    protected void logCallAudioDeviceChange(int currentGroupId, CachedBluetoothDevice target) {
+        var unused =
+                ThreadUtils.postOnBackgroundThread(
+                        () -> {
+                            ChangeCallAudioType type = ChangeCallAudioType.UNKNOWN;
+                            if (mCacheManager != null) {
+                                int targetDeviceGroupId = AudioSharingUtils.getGroupId(target);
+                                List<BluetoothDevice> mostRecentDevices =
+                                        BluetoothAdapter.getDefaultAdapter()
+                                                .getMostRecentlyConnectedDevices();
+                                int targetDeviceIdx = -1;
+                                int currentDeviceIdx = -1;
+                                for (int idx = 0; idx < mostRecentDevices.size(); idx++) {
+                                    BluetoothDevice device = mostRecentDevices.get(idx);
+                                    CachedBluetoothDevice cachedDevice =
+                                            mCacheManager.findDevice(device);
+                                    int groupId =
+                                            cachedDevice != null
+                                                    ? AudioSharingUtils.getGroupId(cachedDevice)
+                                                    : BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+                                    if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+                                        if (groupId == targetDeviceGroupId) {
+                                            targetDeviceIdx = idx;
+                                        } else if (groupId == currentGroupId) {
+                                            currentDeviceIdx = idx;
+                                        }
+                                    }
+                                    if (targetDeviceIdx != -1 && currentDeviceIdx != -1) break;
+                                }
+                                if (targetDeviceIdx != -1 && currentDeviceIdx != -1) {
+                                    type =
+                                            targetDeviceIdx < currentDeviceIdx
+                                                    ? ChangeCallAudioType.CONNECTED_LATER
+                                                    : ChangeCallAudioType.CONNECTED_EARLIER;
+                                }
+                            }
+                            mMetricsFeatureProvider.action(
+                                    mContext,
+                                    SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_CALL_AUDIO,
+                                    type.ordinal());
+                        });
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceControllerTest.java
index bdfc71f..af817d2 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceControllerTest.java
@@ -23,11 +23,14 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.robolectric.Shadows.shadowOf;
 
+import android.app.settings.SettingsEnums;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
@@ -41,7 +44,12 @@
 import android.os.Looper;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
+import android.view.View;
+import android.widget.CheckedTextView;
 
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
 import androidx.lifecycle.LifecycleOwner;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
@@ -49,6 +57,8 @@
 
 import com.android.settings.R;
 import com.android.settings.bluetooth.Utils;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
 import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
 import com.android.settings.testutils.shadow.ShadowThreadUtils;
@@ -65,6 +75,7 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -77,6 +88,7 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.androidx.fragment.FragmentController;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -87,6 +99,7 @@
             ShadowBluetoothAdapter.class,
             ShadowBluetoothUtils.class,
             ShadowThreadUtils.class,
+            ShadowAlertDialogCompat.class,
         })
 public class AudioSharingCallAudioPreferenceControllerTest {
     private static final String PREF_KEY = "calls_and_alarms";
@@ -121,10 +134,11 @@
     private AudioSharingCallAudioPreferenceController mController;
     @Spy private ContentObserver mContentObserver;
     private ShadowBluetoothAdapter mShadowBluetoothAdapter;
-    private LocalBluetoothManager mBtManager;
+    private FakeFeatureFactory mFeatureFactory;
     private Lifecycle mLifecycle;
     private LifecycleOwner mLifecycleOwner;
     private Preference mPreference;
+    private Fragment mParentFragment;
 
     @Before
     public void setUp() {
@@ -136,11 +150,18 @@
                 BluetoothStatusCodes.FEATURE_SUPPORTED);
         mLifecycleOwner = () -> mLifecycle;
         mLifecycle = new Lifecycle(mLifecycleOwner);
+        mParentFragment = new Fragment();
+        FragmentController.setupFragment(
+                mParentFragment,
+                FragmentActivity.class,
+                0 /* containerViewId */,
+                null /* bundle */);
         ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
-        mBtManager = Utils.getLocalBtManager(mContext);
-        when(mBtManager.getEventManager()).thenReturn(mBtEventManager);
-        when(mBtManager.getProfileManager()).thenReturn(mBtProfileManager);
-        when(mBtManager.getCachedDeviceManager()).thenReturn(mCacheManager);
+        LocalBluetoothManager btManager = Utils.getLocalBtManager(mContext);
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
+        when(btManager.getEventManager()).thenReturn(mBtEventManager);
+        when(btManager.getProfileManager()).thenReturn(mBtProfileManager);
+        when(btManager.getCachedDeviceManager()).thenReturn(mCacheManager);
         when(mBtProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
         when(mBtProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
         when(mBtProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControl);
@@ -378,4 +399,105 @@
         shadowOf(Looper.getMainLooper()).idle();
         assertThat(mPreference.getSummary().toString()).isEmpty();
     }
+
+    @Test
+    public void displayPreference_clickToShowCorrectDialog() {
+        AlertDialog latestAlertDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+        if (latestAlertDialog != null) {
+            latestAlertDialog.dismiss();
+            ShadowAlertDialogCompat.reset();
+        }
+        Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID1);
+        when(mCachedDevice1.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID1);
+        when(mCachedDevice1.getName()).thenReturn(TEST_DEVICE_NAME1);
+        when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
+        when(mCachedDevice2.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID2);
+        when(mCachedDevice2.getName()).thenReturn(TEST_DEVICE_NAME2);
+        when(mCachedDevice2.getDevice()).thenReturn(mDevice2);
+        when(mCacheManager.findDevice(mDevice1)).thenReturn(mCachedDevice1);
+        when(mCacheManager.findDevice(mDevice2)).thenReturn(mCachedDevice2);
+        mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(List.of(mDevice1, mDevice2));
+        when(mBroadcast.isEnabled(any())).thenReturn(true);
+        when(mAssistant.getDevicesMatchingConnectionStates(
+                        new int[] {BluetoothProfile.STATE_CONNECTED}))
+                .thenReturn(ImmutableList.of(mDevice1, mDevice2));
+        when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState));
+        mController.init(mParentFragment);
+        mController.displayPreference(mScreen);
+        shadowOf(Looper.getMainLooper()).idle();
+        mPreference.performClick();
+        shadowOf(Looper.getMainLooper()).idle();
+        AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+        assertThat(dialog.isShowing()).isTrue();
+        assertThat(dialog.getListView().getCount()).isEqualTo(2);
+        ArrayList<View> outViews = new ArrayList<>();
+        dialog.getListView()
+                .findViewsWithText(outViews, TEST_DEVICE_NAME1, View.FIND_VIEWS_WITH_TEXT);
+        assertThat(outViews.size()).isEqualTo(1);
+        View view = Iterables.getOnlyElement(outViews);
+        assertThat(view instanceof CheckedTextView).isTrue();
+        assertThat(((CheckedTextView) view).isChecked()).isTrue();
+        verify(mFeatureFactory.metricsFeatureProvider)
+                .visible(
+                        /* context= */ eq(null),
+                        /* source= */ anyInt(),
+                        eq(SettingsEnums.DIALOG_AUDIO_SHARING_CALL_AUDIO),
+                        /* latency= */ anyInt());
+    }
+
+    @Test
+    public void logCallAudioDeviceChange_changeCallAudioToEarlierConnectedDevice() {
+        when(mCachedDevice1.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID1);
+        when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
+        when(mCachedDevice2.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID2);
+        when(mCachedDevice2.getDevice()).thenReturn(mDevice2);
+        when(mCacheManager.findDevice(mDevice1)).thenReturn(mCachedDevice1);
+        when(mCacheManager.findDevice(mDevice2)).thenReturn(mCachedDevice2);
+        mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(List.of(mDevice1, mDevice2));
+        mController.logCallAudioDeviceChange(TEST_DEVICE_GROUP_ID1, mCachedDevice2);
+        verify(mFeatureFactory.metricsFeatureProvider)
+                .action(
+                        mContext,
+                        SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_CALL_AUDIO,
+                        AudioSharingCallAudioPreferenceController.ChangeCallAudioType
+                                .CONNECTED_EARLIER
+                                .ordinal());
+    }
+
+    @Test
+    public void logCallAudioDeviceChange_changeCallAudioToLaterConnectedDevice() {
+        when(mCachedDevice1.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID1);
+        when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
+        when(mCachedDevice2.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID2);
+        when(mCachedDevice2.getDevice()).thenReturn(mDevice2);
+        when(mCacheManager.findDevice(mDevice1)).thenReturn(mCachedDevice1);
+        when(mCacheManager.findDevice(mDevice2)).thenReturn(mCachedDevice2);
+        mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(List.of(mDevice1, mDevice2));
+        mController.logCallAudioDeviceChange(TEST_DEVICE_GROUP_ID2, mCachedDevice1);
+        verify(mFeatureFactory.metricsFeatureProvider)
+                .action(
+                        mContext,
+                        SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_CALL_AUDIO,
+                        AudioSharingCallAudioPreferenceController.ChangeCallAudioType
+                                .CONNECTED_LATER
+                                .ordinal());
+    }
+
+    @Test
+    public void logCallAudioDeviceChange_deviceNotFoundInRecentList_unknownChangeType() {
+        when(mCachedDevice1.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID1);
+        when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
+        when(mCachedDevice2.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID2);
+        when(mCachedDevice2.getDevice()).thenReturn(mDevice2);
+        when(mCacheManager.findDevice(mDevice1)).thenReturn(mCachedDevice1);
+        when(mCacheManager.findDevice(mDevice2)).thenReturn(mCachedDevice2);
+        mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(List.of(mDevice1));
+        mController.logCallAudioDeviceChange(TEST_DEVICE_GROUP_ID1, mCachedDevice2);
+        verify(mFeatureFactory.metricsFeatureProvider)
+                .action(
+                        mContext,
+                        SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_CALL_AUDIO,
+                        AudioSharingCallAudioPreferenceController.ChangeCallAudioType.UNKNOWN
+                                .ordinal());
+    }
 }