From 42b8fbb74f7bcf99cad98727a9f2157c7a855afe Mon Sep 17 00:00:00 2001 From: Yiyi Shen Date: Fri, 23 Feb 2024 15:11:11 +0800 Subject: [PATCH] [Audiosharing] Impl audio sharing feature provider in Settings Add createAvailableMediaDeviceGroupController interface to provide different controller in Settings and SettingsGoogle. Bug: 324023639 Test: atest Change-Id: Ibf2ea2620c878e609eb937ff6947f5aaa0b89e7a --- res/xml/connected_devices.xml | 3 +- .../AvailableMediaDeviceGroupController.java | 169 ++++++------------ .../ConnectedDeviceDashboardFragment.java | 24 +-- .../AudioSharingFeatureProvider.java | 8 +- .../AudioSharingFeatureProviderImpl.java | 11 +- ...ailableMediaDeviceGroupControllerTest.java | 75 ++------ .../ConnectedDeviceDashboardFragmentTest.java | 27 +++ .../AudioSharingFeatureProviderImplTest.java | 9 + .../testutils/shadow/ShadowAudioManager.java | 7 +- 9 files changed, 136 insertions(+), 197 deletions(-) rename tests/robotests/{src => testutils}/com/android/settings/testutils/shadow/ShadowAudioManager.java (92%) diff --git a/res/xml/connected_devices.xml b/res/xml/connected_devices.xml index 34a57989d25..e9ec19e899b 100644 --- a/res/xml/connected_devices.xml +++ b/res/xml/connected_devices.xml @@ -41,8 +41,7 @@ + android:title="@string/connected_device_media_device_title"/> controllers = new ArrayList<>(); - if (AudioSharingUtils.isFeatureEnabled()) { - AbstractPreferenceController audioSharingController = - FeatureFactory.getFeatureFactory() - .getAudioSharingFeatureProvider() - .createAudioSharingDevicePreferenceController( - context, fragment, lifecycle); - if (audioSharingController != null) { - controllers.add(audioSharingController); - } + AbstractPreferenceController availableMediaController = + FeatureFactory.getFeatureFactory() + .getAudioSharingFeatureProvider() + .createAvailableMediaDeviceGroupController(context, fragment, lifecycle); + controllers.add(availableMediaController); + AbstractPreferenceController audioSharingController = + FeatureFactory.getFeatureFactory() + .getAudioSharingFeatureProvider() + .createAudioSharingDevicePreferenceController(context, fragment, lifecycle); + if (audioSharingController != null) { + controllers.add(audioSharingController); } return controllers; } diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProvider.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProvider.java index c71a368640c..9fe4d50ad6d 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProvider.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProvider.java @@ -20,12 +20,12 @@ import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.lifecycle.Lifecycle; import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; /** Feature provider for the audio sharing related features, */ public interface AudioSharingFeatureProvider { @@ -37,6 +37,12 @@ public interface AudioSharingFeatureProvider { @Nullable DashboardFragment fragment, @Nullable Lifecycle lifecycle); + /** Create available media device preference controller. */ + AbstractPreferenceController createAvailableMediaDeviceGroupController( + @NonNull Context context, + @Nullable DashboardFragment fragment, + @Nullable Lifecycle lifecycle); + /** * Check if the device match the audio sharing filter. * diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImpl.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImpl.java index 05a6a6383af..259ed7a16b4 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImpl.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImpl.java @@ -20,12 +20,13 @@ import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.lifecycle.Lifecycle; +import com.android.settings.connecteddevice.AvailableMediaDeviceGroupController; import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; public class AudioSharingFeatureProviderImpl implements AudioSharingFeatureProvider { @@ -38,6 +39,14 @@ public class AudioSharingFeatureProviderImpl implements AudioSharingFeatureProvi return null; } + @Override + public AbstractPreferenceController createAvailableMediaDeviceGroupController( + @NonNull Context context, + @Nullable DashboardFragment fragment, + @Nullable Lifecycle lifecycle) { + return new AvailableMediaDeviceGroupController(context, fragment, lifecycle); + } + @Override public boolean isAudioSharingFilterMatched( @NonNull CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) { diff --git a/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java index e5964d06e5c..357420af221 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java @@ -22,27 +22,23 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; -import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothLeBroadcastAssistant; import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothStatusCodes; import android.content.Context; import android.content.pm.PackageManager; import android.media.AudioManager; -import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.LifecycleOwner; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceManager; @@ -51,19 +47,16 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater; import com.android.settings.bluetooth.Utils; -import com.android.settings.flags.Flags; import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; import com.android.settings.testutils.shadow.ShadowAudioManager; -import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settings.testutils.shadow.ShadowBluetoothUtils; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.BluetoothEventManager; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.HearingAidInfo; -import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; import org.junit.Rule; @@ -76,16 +69,12 @@ import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.shadow.api.Shadow; - -import java.util.concurrent.Executor; /** Tests for {@link AvailableMediaDeviceGroupController}. */ @RunWith(RobolectricTestRunner.class) @Config( shadows = { ShadowAudioManager.class, - ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class, ShadowAlertDialogCompat.class, }) @@ -105,9 +94,7 @@ public class AvailableMediaDeviceGroupControllerTest { @Mock private PackageManager mPackageManager; @Mock private BluetoothEventManager mEventManager; @Mock private LocalBluetoothManager mLocalBluetoothManager; - @Mock private LocalBluetoothProfileManager mLocalBtProfileManager; @Mock private CachedBluetoothDeviceManager mCachedDeviceManager; - @Mock private LocalBluetoothLeBroadcastAssistant mAssistant; @Mock private CachedBluetoothDevice mCachedBluetoothDevice; private PreferenceGroup mPreferenceGroup; @@ -115,13 +102,16 @@ public class AvailableMediaDeviceGroupControllerTest { private Preference mPreference; private AvailableMediaDeviceGroupController mAvailableMediaDeviceGroupController; private AudioManager mAudioManager; - private ShadowBluetoothAdapter mShadowBluetoothAdapter; + private LifecycleOwner mLifecycleOwner; + private Lifecycle mLifecycle; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); mPreference = new Preference(mContext); mPreference.setKey(PREFERENCE_KEY_1); mPreferenceGroup = spy(new PreferenceScreen(mContext, null)); @@ -130,24 +120,17 @@ public class AvailableMediaDeviceGroupControllerTest { doReturn(mPackageManager).when(mContext).getPackageManager(); doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); - mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); - mShadowBluetoothAdapter.setEnabled(true); - mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( - BluetoothStatusCodes.FEATURE_NOT_SUPPORTED); - mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( - BluetoothStatusCodes.FEATURE_NOT_SUPPORTED); ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; mLocalBluetoothManager = Utils.getLocalBtManager(mContext); mAudioManager = mContext.getSystemService(AudioManager.class); doReturn(mEventManager).when(mLocalBluetoothManager).getEventManager(); - when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBtProfileManager); when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); when(mCachedDeviceManager.findDevice(any(BluetoothDevice.class))) .thenReturn(mCachedBluetoothDevice); when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); mAvailableMediaDeviceGroupController = - spy(new AvailableMediaDeviceGroupController(mContext)); + spy(new AvailableMediaDeviceGroupController(mContext, null, mLifecycle)); mAvailableMediaDeviceGroupController.setBluetoothDeviceUpdater( mAvailableMediaBluetoothDeviceUpdater); mAvailableMediaDeviceGroupController.setFragmentManager( @@ -197,7 +180,7 @@ public class AvailableMediaDeviceGroupControllerTest { @Test public void testRegister() { // register the callback in onStart() - mAvailableMediaDeviceGroupController.onStart(); + mAvailableMediaDeviceGroupController.onStart(mLifecycleOwner); verify(mAvailableMediaBluetoothDeviceUpdater).registerCallback(); verify(mLocalBluetoothManager.getEventManager()) @@ -205,36 +188,15 @@ public class AvailableMediaDeviceGroupControllerTest { verify(mAvailableMediaBluetoothDeviceUpdater).refreshPreference(); } - @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) - public void testRegister_audioSharingOn() { - setUpBroadcast(); - // register the callback in onStart() - mAvailableMediaDeviceGroupController.onStart(); - verify(mAssistant) - .registerServiceCallBack( - any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class)); - } - @Test public void testUnregister() { // unregister the callback in onStop() - mAvailableMediaDeviceGroupController.onStop(); + mAvailableMediaDeviceGroupController.onStop(mLifecycleOwner); verify(mAvailableMediaBluetoothDeviceUpdater).unregisterCallback(); verify(mLocalBluetoothManager.getEventManager()) .unregisterCallback(any(BluetoothCallback.class)); } - @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) - public void testUnregister_audioSharingOn() { - setUpBroadcast(); - // unregister the callback in onStop() - mAvailableMediaDeviceGroupController.onStop(); - verify(mAssistant) - .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class)); - } - @Test public void testGetAvailabilityStatus_noBluetoothFeature_returnUnSupported() { doReturn(false).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); @@ -274,7 +236,7 @@ public class AvailableMediaDeviceGroupControllerTest { mAvailableMediaDeviceGroupController.mLocalBluetoothManager = null; // Shouldn't crash - mAvailableMediaDeviceGroupController.onStart(); + mAvailableMediaDeviceGroupController.onStart(mLifecycleOwner); } @Test @@ -282,7 +244,7 @@ public class AvailableMediaDeviceGroupControllerTest { mAvailableMediaDeviceGroupController.mLocalBluetoothManager = null; // Shouldn't crash - mAvailableMediaDeviceGroupController.onStop(); + mAvailableMediaDeviceGroupController.onStop(mLifecycleOwner); } @Test @@ -300,19 +262,4 @@ public class AvailableMediaDeviceGroupControllerTest { final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); assertThat(dialog.isShowing()).isTrue(); } - - private void setUpBroadcast() { - mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( - BluetoothStatusCodes.FEATURE_SUPPORTED); - mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( - BluetoothStatusCodes.FEATURE_SUPPORTED); - when(mLocalBtProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant); - doNothing() - .when(mAssistant) - .registerServiceCallBack( - any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class)); - doNothing() - .when(mAssistant) - .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class)); - } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java index 09f7a384244..ee4f952ca39 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java @@ -20,19 +20,25 @@ import static com.android.settings.connecteddevice.ConnectedDeviceDashboardFragm import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.PackageManager; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.SearchIndexableResource; import com.android.settings.R; +import com.android.settings.connecteddevice.fastpair.FastPairDeviceUpdater; import com.android.settings.core.BasePreferenceController; import com.android.settings.core.PreferenceControllerListHelper; +import com.android.settings.flags.Flags; import com.android.settings.slices.SlicePreferenceController; +import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settings.testutils.shadow.ShadowConnectivityManager; import com.android.settings.testutils.shadow.ShadowUserManager; @@ -60,6 +66,8 @@ public class ConnectedDeviceDashboardFragmentTest { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final String KEY_NEARBY_DEVICES = "bt_nearby_slice"; private static final String KEY_DISCOVERABLE_FOOTER = "discoverable_footer"; private static final String KEY_SAVED_DEVICE_SEE_ALL = "previously_connected_devices_see_all"; @@ -75,8 +83,11 @@ public class ConnectedDeviceDashboardFragmentTest { private static final String TEST_ACTION = "com.testapp.settings.ACTION_START"; @Mock private PackageManager mPackageManager; + @Mock private FastPairDeviceUpdater mFastPairDeviceUpdater; private Context mContext; private ConnectedDeviceDashboardFragment mFragment; + private FakeFeatureFactory mFeatureFactory; + private AvailableMediaDeviceGroupController mMediaDeviceGroupController; @Before public void setUp() { @@ -84,6 +95,22 @@ public class ConnectedDeviceDashboardFragmentTest { mContext = spy(RuntimeEnvironment.application); mFragment = new ConnectedDeviceDashboardFragment(); + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + when(mFeatureFactory + .getFastPairFeatureProvider() + .getFastPairDeviceUpdater( + any(Context.class), any(DevicePreferenceCallback.class))) + .thenReturn(mFastPairDeviceUpdater); + when(mFeatureFactory + .getAudioSharingFeatureProvider() + .createAudioSharingDevicePreferenceController(mContext, null, null)) + .thenReturn(null); + mMediaDeviceGroupController = new AvailableMediaDeviceGroupController(mContext, null, null); + when(mFeatureFactory + .getAudioSharingFeatureProvider() + .createAvailableMediaDeviceGroupController(mContext, null, null)) + .thenReturn(mMediaDeviceGroupController); doReturn(mPackageManager).when(mContext).getPackageManager(); doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImplTest.java index 0edbc7709ba..1965bffaeba 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImplTest.java @@ -22,6 +22,7 @@ import android.content.Context; import androidx.test.core.app.ApplicationProvider; +import com.android.settings.connecteddevice.AvailableMediaDeviceGroupController; import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -59,6 +60,14 @@ public class AudioSharingFeatureProviderImplTest { .isNull(); } + @Test + public void createAvailableMediaDeviceGroupController_returnsNull() { + assertThat( + mFeatureProvider.createAvailableMediaDeviceGroupController( + mContext, /* fragment= */ null, /* lifecycle= */ null)) + .isInstanceOf(AvailableMediaDeviceGroupController.class); + } + @Test public void isAudioSharingFilterMatched_returnsFalse() { assertThat(mFeatureProvider.isAudioSharingFilterMatched(mCachedDevice, mLocalBtManager)) diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAudioManager.java b/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowAudioManager.java similarity index 92% rename from tests/robotests/src/com/android/settings/testutils/shadow/ShadowAudioManager.java rename to tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowAudioManager.java index 9c066655307..b465a4159a3 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAudioManager.java +++ b/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowAudioManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2024 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. @@ -38,6 +38,7 @@ import org.robolectric.shadow.api.Shadow; import java.util.ArrayList; import java.util.List; +/** Robolectric shadow for the AudioManager. */ @Implements(value = AudioManager.class) public class ShadowAudioManager extends org.robolectric.shadows.ShadowAudioManager { private int mRingerMode; @@ -58,11 +59,13 @@ public class ShadowAudioManager extends org.robolectric.shadows.ShadowAudioManag mRingerMode = mode; } + /** Register audio device callback. */ @Implementation public void registerAudioDeviceCallback(AudioDeviceCallback callback, Handler handler) { mDeviceCallbacks.add(callback); } + /** Unregister audio device callback. */ @Implementation public void unregisterAudioDeviceCallback(AudioDeviceCallback callback) { if (mDeviceCallbacks.contains(callback)) { @@ -79,10 +82,12 @@ public class ShadowAudioManager extends org.robolectric.shadows.ShadowAudioManag return mMusicActiveRemotely; } + /** Set output device. */ public void setOutputDevice(int deviceCodes) { mDeviceCodes = deviceCodes; } + /** Get devices for stream. */ @Implementation public int getDevicesForStream(int streamType) { switch (streamType) {