From 61b4f199ca651c07d97ae1c656405cff282a5ba9 Mon Sep 17 00:00:00 2001 From: Yiyi Shen Date: Wed, 5 Jun 2024 16:32:19 +0800 Subject: [PATCH] [Audiosharing] Log action when change primary device P2 for add audio sharing loggings Bug: 331515891 Test: atest Change-Id: I9b806312c831320b46b63942acd3119b5ff40ae4 --- ...oSharingCallAudioPreferenceController.java | 104 +++++++++++--- ...ringCallAudioPreferenceControllerTest.java | 132 +++++++++++++++++- 2 files changed, 214 insertions(+), 22 deletions(-) diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java index e848f88576b..b623ba85b15 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java @@ -18,6 +18,7 @@ package com.android.settings.connecteddevice.audiosharing; 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 android.util.Log; 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 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP 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> mGroupedConnectedDevices = new HashMap<>(); private List 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 class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP 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 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP @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 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP 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 devices = mGroupedConnectedDevices.getOrDefault( item.getGroupId(), ImmutableList.of()); - @Nullable CachedBluetoothDevice lead = AudioSharingUtils.getLeadDevice(devices); if (lead != null) { @@ -195,11 +220,12 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP + 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 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP /** * 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 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP 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 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP 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 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 bdfc71fed0e..af817d2987d 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.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_ 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.database.ContentObserver; 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 androidx.test.core.app.ApplicationProvider; 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.android.settingslib.flags.Flags; 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.mockito.junit.MockitoRule; 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 @@ import java.util.List; ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class, ShadowThreadUtils.class, + ShadowAlertDialogCompat.class, }) public class AudioSharingCallAudioPreferenceControllerTest { private static final String PREF_KEY = "calls_and_alarms"; @@ -121,10 +134,11 @@ public class AudioSharingCallAudioPreferenceControllerTest { 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 @@ public class AudioSharingCallAudioPreferenceControllerTest { 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 @@ public class AudioSharingCallAudioPreferenceControllerTest { 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 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()); + } }