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());
+ }
}