From cd97aa3376fc5abe7e8bcc9f0f2023916b3feb2a Mon Sep 17 00:00:00 2001 From: Mir Noshin Jahan Date: Tue, 11 Mar 2025 21:42:06 -0700 Subject: [PATCH 01/11] Add Settings contract keys for adaptive Wi-Fi and mobile network toggle switches Test: Build pass Bug: 393645580 Flag: com.android.settings.flags.enable_nested_toggle_switches Change-Id: Ia9b658519e7fae7304871c2eb48e07dc9f1620c2 --- src/com/android/settings/contract/SettingsContract.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/com/android/settings/contract/SettingsContract.kt b/src/com/android/settings/contract/SettingsContract.kt index da3867f35a4..f9ae9c91ef7 100644 --- a/src/com/android/settings/contract/SettingsContract.kt +++ b/src/com/android/settings/contract/SettingsContract.kt @@ -69,6 +69,12 @@ const val KEY_SCREEN_ATTENTION = "screen_attention" /** Contract key for the "Use adaptive connectivity" setting. */ const val KEY_ADAPTIVE_CONNECTIVITY = "adaptive_connectivity" +/** Contract key for the "Auto-switch Wi-Fi to Cellular" setting. */ +const val KEY_ADAPTIVE_WIFI_SCORER = "adaptive_wifi_scorer" + +/** Contract key for the " Auto-switch mobile network for battery life" setting. */ +const val KEY_ADAPTIVE_MOBILE_NETWORK = "adaptive_mobile_network" + /** Contract key for the "WiFi hotspot" setting. */ const val KEY_WIFI_HOTSPOT = "enable_wifi_ap" From 4d0be536c77c949e757cefbf12b14b0159429d16 Mon Sep 17 00:00:00 2001 From: Xiaomiao Zhang Date: Wed, 12 Mar 2025 21:49:31 +0000 Subject: [PATCH 02/11] Create data store for supervision safe sites preference. Test: atest SupervisionWebContentFiltersScreenTest Test: atest SupervisionSafeSitesPreferenceTest Test: deployed locally to a physical device Flag: android.app.supervision.flags.enable_web_content_filters_screen Bug: 401568468 Change-Id: I7fe8a9c5932b4c8f63c4067ba6914eb73d0e2373 --- .../SupervisionSafeSitesDataStore.kt | 75 ++++++++++++++++++ .../SupervisionSafeSitesPreference.kt | 18 ++--- .../SupervisionWebContentFiltersScreen.kt | 5 +- .../SupervisionSafeSitesPreferenceTest.kt | 79 ++++++++++++++++++- .../SupervisionWebContentFiltersScreenTest.kt | 52 +++++++++++- 5 files changed, 214 insertions(+), 15 deletions(-) create mode 100644 src/com/android/settings/supervision/SupervisionSafeSitesDataStore.kt diff --git a/src/com/android/settings/supervision/SupervisionSafeSitesDataStore.kt b/src/com/android/settings/supervision/SupervisionSafeSitesDataStore.kt new file mode 100644 index 00000000000..4f283b8c22c --- /dev/null +++ b/src/com/android/settings/supervision/SupervisionSafeSitesDataStore.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2025 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.settings.supervision + +import android.content.Context +import android.provider.Settings.Secure.BROWSER_CONTENT_FILTERS_ENABLED +import com.android.settingslib.datastore.AbstractKeyedDataObservable +import com.android.settingslib.datastore.HandlerExecutor +import com.android.settingslib.datastore.KeyValueStore +import com.android.settingslib.datastore.KeyedObserver +import com.android.settingslib.datastore.SettingsSecureStore +import com.android.settingslib.datastore.SettingsStore + +/** Datastore of the safe sites preference. */ +@Suppress("UNCHECKED_CAST") +class SupervisionSafeSitesDataStore( + private val context: Context, + private val settingsStore: SettingsStore = SettingsSecureStore.get(context), +) : AbstractKeyedDataObservable(), KeyedObserver, KeyValueStore { + + override fun contains(key: String) = + key == SupervisionBlockExplicitSitesPreference.KEY || + key == SupervisionAllowAllSitesPreference.KEY + + override fun getValue(key: String, valueType: Class): T? { + val settingValue = (settingsStore.getBoolean(BROWSER_CONTENT_FILTERS_ENABLED) == true) + return when (key) { + SupervisionAllowAllSitesPreference.KEY -> !settingValue + + SupervisionBlockExplicitSitesPreference.KEY -> settingValue + + else -> null + } + as T? + } + + override fun setValue(key: String, valueType: Class, value: T?) { + if (value !is Boolean) return + when (key) { + SupervisionAllowAllSitesPreference.KEY -> + settingsStore.setBoolean(BROWSER_CONTENT_FILTERS_ENABLED, !value) + + SupervisionBlockExplicitSitesPreference.KEY -> + settingsStore.setBoolean(BROWSER_CONTENT_FILTERS_ENABLED, value) + } + } + + override fun onFirstObserverAdded() { + // observe the underlying storage key + settingsStore.addObserver(BROWSER_CONTENT_FILTERS_ENABLED, this, HandlerExecutor.main) + } + + override fun onKeyChanged(key: String, reason: Int) { + // forward data change to preference hierarchy key + notifyChange(SupervisionBlockExplicitSitesPreference.KEY, reason) + notifyChange(SupervisionAllowAllSitesPreference.KEY, reason) + } + + override fun onLastObserverRemoved() { + settingsStore.removeObserver(BROWSER_CONTENT_FILTERS_ENABLED, this) + } +} diff --git a/src/com/android/settings/supervision/SupervisionSafeSitesPreference.kt b/src/com/android/settings/supervision/SupervisionSafeSitesPreference.kt index aaa5a333a86..d78afb299ac 100644 --- a/src/com/android/settings/supervision/SupervisionSafeSitesPreference.kt +++ b/src/com/android/settings/supervision/SupervisionSafeSitesPreference.kt @@ -18,9 +18,7 @@ package com.android.settings.supervision import android.content.Context import androidx.preference.Preference import com.android.settings.R -import com.android.settingslib.datastore.KeyValueStore import com.android.settingslib.datastore.Permissions -import com.android.settingslib.datastore.SettingsSecureStore import com.android.settingslib.metadata.BooleanValuePreference import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.ReadWritePermit @@ -30,9 +28,10 @@ import com.android.settingslib.preference.forEachRecursively import com.android.settingslib.widget.SelectorWithWidgetPreference /** Base class of web content filters Safe sites preferences. */ -sealed class SupervisionSafeSitesPreference : - BooleanValuePreference, SelectorWithWidgetPreference.OnClickListener, PreferenceBinding { - override fun storage(context: Context): KeyValueStore = SettingsSecureStore.get(context) +sealed class SupervisionSafeSitesPreference( + protected val dataStore: SupervisionSafeSitesDataStore +) : BooleanValuePreference, SelectorWithWidgetPreference.OnClickListener, PreferenceBinding { + override fun storage(context: Context) = dataStore override fun getReadPermissions(context: Context) = Permissions.EMPTY @@ -64,15 +63,15 @@ sealed class SupervisionSafeSitesPreference : override fun bind(preference: Preference, metadata: PreferenceMetadata) { super.bind(preference, metadata) (preference as SelectorWithWidgetPreference).also { - // TODO(b/401568468): Set the isChecked value using stored values. - it.isChecked = (it.key == SupervisionAllowAllSitesPreference.KEY) + it.isChecked = (dataStore.getBoolean(it.key) == true) it.setOnClickListener(this) } } } /** The "Try to block explicit sites" preference. */ -class SupervisionBlockExplicitSitesPreference : SupervisionSafeSitesPreference() { +class SupervisionBlockExplicitSitesPreference(dataStore: SupervisionSafeSitesDataStore) : + SupervisionSafeSitesPreference(dataStore) { override val key get() = KEY @@ -89,7 +88,8 @@ class SupervisionBlockExplicitSitesPreference : SupervisionSafeSitesPreference() } /** The "Allow all sites" preference. */ -class SupervisionAllowAllSitesPreference : SupervisionSafeSitesPreference() { +class SupervisionAllowAllSitesPreference(dataStore: SupervisionSafeSitesDataStore) : + SupervisionSafeSitesPreference(dataStore) { override val key get() = KEY diff --git a/src/com/android/settings/supervision/SupervisionWebContentFiltersScreen.kt b/src/com/android/settings/supervision/SupervisionWebContentFiltersScreen.kt index 0a2891b5c5e..c76b6cdd5b4 100644 --- a/src/com/android/settings/supervision/SupervisionWebContentFiltersScreen.kt +++ b/src/com/android/settings/supervision/SupervisionWebContentFiltersScreen.kt @@ -47,8 +47,9 @@ class SupervisionWebContentFiltersScreen : PreferenceScreenCreator { R.string.supervision_web_content_filters_browser_title, ) += { - +SupervisionBlockExplicitSitesPreference() - +SupervisionAllowAllSitesPreference() + val dataStore = SupervisionSafeSitesDataStore(context) + +SupervisionBlockExplicitSitesPreference(dataStore) + +SupervisionAllowAllSitesPreference(dataStore) } // TODO(b/401569571) implement the SafeSearch group. } diff --git a/tests/robotests/src/com/android/settings/supervision/SupervisionSafeSitesPreferenceTest.kt b/tests/robotests/src/com/android/settings/supervision/SupervisionSafeSitesPreferenceTest.kt index 5be7a1167e4..a3aca69cf32 100644 --- a/tests/robotests/src/com/android/settings/supervision/SupervisionSafeSitesPreferenceTest.kt +++ b/tests/robotests/src/com/android/settings/supervision/SupervisionSafeSitesPreferenceTest.kt @@ -16,20 +16,33 @@ package com.android.settings.supervision import android.content.Context +import android.provider.Settings +import android.provider.Settings.Secure.BROWSER_CONTENT_FILTERS_ENABLED +import android.provider.Settings.SettingNotFoundException import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.R +import com.android.settingslib.preference.createAndBindWidget +import com.android.settingslib.widget.SelectorWithWidgetPreference import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertThrows +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SupervisionSafeSitesPreferenceTest { private val context: Context = ApplicationProvider.getApplicationContext() + private lateinit var dataStore: SupervisionSafeSitesDataStore + private lateinit var allowAllSitesPreference: SupervisionAllowAllSitesPreference + private lateinit var blockExplicitSitesPreference: SupervisionBlockExplicitSitesPreference - private val allowAllSitesPreference = SupervisionAllowAllSitesPreference() - - private val blockExplicitSitesPreference = SupervisionBlockExplicitSitesPreference() + @Before + fun setUp() { + dataStore = SupervisionSafeSitesDataStore(context) + allowAllSitesPreference = SupervisionAllowAllSitesPreference(dataStore) + blockExplicitSitesPreference = SupervisionBlockExplicitSitesPreference(dataStore) + } @Test fun getTitle_allowAllSites() { @@ -50,4 +63,64 @@ class SupervisionSafeSitesPreferenceTest { R.string.supervision_web_content_filters_browser_block_explicit_sites_summary ) } + + @Test + fun allowAllSitesIsChecked_whenNoValueIsSet() { + assertThrows(SettingNotFoundException::class.java) { + Settings.Secure.getInt(context.getContentResolver(), BROWSER_CONTENT_FILTERS_ENABLED) + } + assertThat(getBlockExplicitSitesWidget().isChecked).isFalse() + assertThat(getAllowAllSitesWidget().isChecked).isTrue() + } + + @Test + fun blockExplicitSitesIsChecked_whenPreviouslyEnabled() { + Settings.Secure.putInt(context.getContentResolver(), BROWSER_CONTENT_FILTERS_ENABLED, 1) + assertThat(getAllowAllSitesWidget().isChecked).isFalse() + assertThat(getBlockExplicitSitesWidget().isChecked).isTrue() + } + + @Test + fun clickBlockExplicitSites_enablesFilter() { + Settings.Secure.putInt(context.getContentResolver(), BROWSER_CONTENT_FILTERS_ENABLED, 0) + val blockExplicitSitesWidget = getBlockExplicitSitesWidget() + assertThat(blockExplicitSitesWidget.isChecked).isFalse() + + blockExplicitSitesWidget.performClick() + + assertThat( + Settings.Secure.getInt( + context.getContentResolver(), + BROWSER_CONTENT_FILTERS_ENABLED, + ) + ) + .isEqualTo(1) + assertThat(blockExplicitSitesWidget.isChecked).isTrue() + } + + @Test + fun clickAllowAllSites_disablesFilter() { + Settings.Secure.putInt(context.getContentResolver(), BROWSER_CONTENT_FILTERS_ENABLED, 1) + val allowAllSitesWidget = getAllowAllSitesWidget() + assertThat(allowAllSitesWidget.isChecked).isFalse() + + allowAllSitesWidget.performClick() + + assertThat( + Settings.Secure.getInt( + context.getContentResolver(), + BROWSER_CONTENT_FILTERS_ENABLED, + ) + ) + .isEqualTo(0) + assertThat(allowAllSitesWidget.isChecked).isTrue() + } + + private fun getBlockExplicitSitesWidget(): SelectorWithWidgetPreference { + return blockExplicitSitesPreference.createAndBindWidget(context) + } + + private fun getAllowAllSitesWidget(): SelectorWithWidgetPreference { + return allowAllSitesPreference.createAndBindWidget(context) + } } diff --git a/tests/robotests/src/com/android/settings/supervision/SupervisionWebContentFiltersScreenTest.kt b/tests/robotests/src/com/android/settings/supervision/SupervisionWebContentFiltersScreenTest.kt index 351cbdeaa19..31bdbd29405 100644 --- a/tests/robotests/src/com/android/settings/supervision/SupervisionWebContentFiltersScreenTest.kt +++ b/tests/robotests/src/com/android/settings/supervision/SupervisionWebContentFiltersScreenTest.kt @@ -15,18 +15,32 @@ */ package com.android.settings.supervision +import android.app.supervision.flags.Flags import android.content.Context +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import androidx.fragment.app.testing.FragmentScenario import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.R +import com.android.settingslib.widget.SelectorWithWidgetPreference import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SupervisionWebContentFiltersScreenTest { + @get:Rule val setFlagsRule = SetFlagsRule() private val context: Context = ApplicationProvider.getApplicationContext() - private val supervisionWebContentFiltersScreen = SupervisionWebContentFiltersScreen() + private lateinit var supervisionWebContentFiltersScreen: SupervisionWebContentFiltersScreen + + @Before + fun setUp() { + supervisionWebContentFiltersScreen = SupervisionWebContentFiltersScreen() + } @Test fun key() { @@ -39,4 +53,40 @@ class SupervisionWebContentFiltersScreenTest { assertThat(supervisionWebContentFiltersScreen.title) .isEqualTo(R.string.supervision_web_content_filters_title) } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WEB_CONTENT_FILTERS_SCREEN) + fun flagEnabled() { + assertThat(supervisionWebContentFiltersScreen.isFlagEnabled(context)).isTrue() + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_WEB_CONTENT_FILTERS_SCREEN) + fun flagDisabled() { + assertThat(supervisionWebContentFiltersScreen.isFlagEnabled(context)).isFalse() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WEB_CONTENT_FILTERS_SCREEN) + fun switchSafeSitesPreferences() { + FragmentScenario.launchInContainer(supervisionWebContentFiltersScreen.fragmentClass()) + .onFragment { fragment -> + val allowAllSitesPreference = + fragment.findPreference( + SupervisionAllowAllSitesPreference.KEY + )!! + val blockExplicitSitesPreference = + fragment.findPreference( + SupervisionBlockExplicitSitesPreference.KEY + )!! + + assertThat(allowAllSitesPreference.isChecked).isTrue() + assertThat(blockExplicitSitesPreference.isChecked).isFalse() + + blockExplicitSitesPreference.performClick() + + assertThat(blockExplicitSitesPreference.isChecked).isTrue() + assertThat(allowAllSitesPreference.isChecked).isFalse() + } + } } From b830e703f3f409957e6c4a2fcfbb896b365fcd3f Mon Sep 17 00:00:00 2001 From: Shivangi Dubey Date: Mon, 17 Mar 2025 05:11:25 -0700 Subject: [PATCH 03/11] Integrate refactored device-state auto-rotate setting manager Added DeviceStateAutoRotateSettingManagerProvider to provide appropriate implementation of DeviceStateAutoRotateSettingManager based on flag. Integrate DeviceStateAutoRotateSettingManagerImpl to be used when auto-rotate refactor flag is ON. For more info:go/auto-rotate-refactor Bug: 394303723 Bug: 394303731 Flag: com.android.window.flags.enable_device_state_auto_rotate_setting_refactor Test: atest DeviceStateAutoRotateSettingManagerProviderTest Change-Id: I63494b6548f1f533a9a1979f2b19640c3ad1dc8d --- ...eviceStateAutoRotateSettingController.java | 6 +- ...ceStateAutoRotateSettingManagerProvider.kt | 62 ++++++++++ .../DeviceStateAutoRotationHelper.java | 11 +- .../display/SmartAutoRotateController.java | 8 +- ...iceStateAutoRotateDetailsFragmentTest.java | 8 +- ...eStateAutoRotateSettingControllerTest.java | 6 +- ...ateAutoRotateSettingManagerProviderTest.kt | 111 ++++++++++++++++++ .../SmartAutoRotateControllerTest.java | 12 +- ...SmartAutoRotatePreferenceFragmentTest.java | 8 +- 9 files changed, 201 insertions(+), 31 deletions(-) create mode 100644 src/com/android/settings/display/DeviceStateAutoRotateSettingManagerProvider.kt create mode 100644 tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateSettingManagerProviderTest.kt diff --git a/src/com/android/settings/display/DeviceStateAutoRotateSettingController.java b/src/com/android/settings/display/DeviceStateAutoRotateSettingController.java index d3950ee1396..5c2dec21810 100644 --- a/src/com/android/settings/display/DeviceStateAutoRotateSettingController.java +++ b/src/com/android/settings/display/DeviceStateAutoRotateSettingController.java @@ -35,7 +35,6 @@ import com.android.settings.core.TogglePreferenceController; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager; -import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager; import com.android.settingslib.search.SearchIndexableRaw; import java.util.List; @@ -46,7 +45,7 @@ public class DeviceStateAutoRotateSettingController extends TogglePreferenceCont private TwoStatePreference mPreference; - private final DeviceStateRotationLockSettingsManager mAutoRotateSettingsManager; + private final DeviceStateAutoRotateSettingManager mAutoRotateSettingsManager; private final int mOrder; private final DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener mDeviceStateAutoRotateSettingListener = () -> updateState(mPreference); @@ -62,7 +61,8 @@ public class DeviceStateAutoRotateSettingController extends TogglePreferenceCont mMetricsFeatureProvider = metricsFeatureProvider; mDeviceState = deviceState; mDeviceStateDescription = deviceStateDescription; - mAutoRotateSettingsManager = DeviceStateRotationLockSettingsManager.getInstance(context); + mAutoRotateSettingsManager = + DeviceStateAutoRotateSettingManagerProvider.getSingletonInstance(context); mOrder = order; } diff --git a/src/com/android/settings/display/DeviceStateAutoRotateSettingManagerProvider.kt b/src/com/android/settings/display/DeviceStateAutoRotateSettingManagerProvider.kt new file mode 100644 index 00000000000..906ffe4b566 --- /dev/null +++ b/src/com/android/settings/display/DeviceStateAutoRotateSettingManagerProvider.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2025 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.settings.display + +import android.content.Context +import android.hardware.devicestate.DeviceStateManager +import android.os.Build +import android.os.Handler +import android.os.Looper +import com.android.internal.annotations.VisibleForTesting +import com.android.settingslib.devicestate.AndroidSecureSettings +import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager +import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManagerProvider.createInstance +import com.android.settingslib.devicestate.PosturesHelper +import com.android.settingslib.utils.ThreadUtils +import com.android.window.flags.Flags + +/** + * Provides appropriate instance of [DeviceStateAutoRotateSettingManager], based on the value of + * [Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR]. + */ +object DeviceStateAutoRotateSettingManagerProvider { + private var nullableSingletonSettingManager: DeviceStateAutoRotateSettingManager? = null + + /** + * Provides a singleton instance of [DeviceStateAutoRotateSettingManager], based on the + * value of[Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR]. It is supposed to + * be used by apps that don't support dagger to provide and manager instance. + */ + @JvmStatic + fun getSingletonInstance(context: Context) = + nullableSingletonSettingManager ?: createInstance( + context, + ThreadUtils.getBackgroundExecutor(), + AndroidSecureSettings(context.contentResolver), + Handler(Looper.getMainLooper()), + PosturesHelper(context, context.getSystemService(DeviceStateManager::class.java)) + ).also { + nullableSingletonSettingManager = it + } + + /** Resets the singleton instance of [DeviceStateAutoRotateSettingManager]. */ + @JvmStatic + @VisibleForTesting + fun resetInstance() { + nullableSingletonSettingManager = null + } +} diff --git a/src/com/android/settings/display/DeviceStateAutoRotationHelper.java b/src/com/android/settings/display/DeviceStateAutoRotationHelper.java index 3bf9def2316..7b285248e1c 100644 --- a/src/com/android/settings/display/DeviceStateAutoRotationHelper.java +++ b/src/com/android/settings/display/DeviceStateAutoRotationHelper.java @@ -16,6 +16,8 @@ package com.android.settings.display; +import static com.android.settingslib.devicestate.DeviceStateAutoRotateSettingUtils.isDeviceStateRotationLockEnabled; + import android.content.Context; import android.util.Log; @@ -25,7 +27,6 @@ import com.android.internal.view.RotationPolicy; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager; import com.android.settingslib.devicestate.SettableDeviceState; import com.android.settingslib.search.SearchIndexableRaw; @@ -51,8 +52,8 @@ public class DeviceStateAutoRotationHelper { static ImmutableList createPreferenceControllers( Context context) { - List settableDeviceStates = DeviceStateRotationLockSettingsManager - .getInstance(context).getSettableDeviceStates(); + List settableDeviceStates = DeviceStateAutoRotateSettingManagerProvider + .getSingletonInstance(context).getSettableDeviceStates(); int numDeviceStates = settableDeviceStates.size(); if (numDeviceStates == 0) { return ImmutableList.of(); @@ -99,7 +100,7 @@ public class DeviceStateAutoRotationHelper { /** Returns whether the device state based auto-rotation settings are enabled. */ public static boolean isDeviceStateRotationEnabled(Context context) { return RotationPolicy.isRotationLockToggleVisible(context) - && DeviceStateRotationLockSettingsManager.isDeviceStateRotationLockEnabled(context); + && isDeviceStateRotationLockEnabled(context); } /** @@ -108,6 +109,6 @@ public class DeviceStateAutoRotationHelper { */ public static boolean isDeviceStateRotationEnabledForA11y(Context context) { return RotationPolicy.isRotationSupported(context) - && DeviceStateRotationLockSettingsManager.isDeviceStateRotationLockEnabled(context); + && isDeviceStateRotationLockEnabled(context); } } diff --git a/src/com/android/settings/display/SmartAutoRotateController.java b/src/com/android/settings/display/SmartAutoRotateController.java index c99b2f853ca..4bc28ff41e0 100644 --- a/src/com/android/settings/display/SmartAutoRotateController.java +++ b/src/com/android/settings/display/SmartAutoRotateController.java @@ -47,7 +47,6 @@ import com.android.settings.core.TogglePreferenceController; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager; -import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager; /** * SmartAutoRotateController controls whether auto rotation is enabled @@ -75,7 +74,7 @@ public class SmartAutoRotateController extends TogglePreferenceController implem } }; - private final DeviceStateRotationLockSettingsManager mDeviceStateAutoRotateSettingsManager; + private final DeviceStateAutoRotateSettingManager mDeviceStateAutoRotateSettingsManager; private final DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener mDeviceStateAutoRotateSettingListener = () -> updateState(mPreference); private RotationPolicy.RotationPolicyListener mRotationPolicyListener; @@ -85,8 +84,9 @@ public class SmartAutoRotateController extends TogglePreferenceController implem mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); mPrivacyManager = SensorPrivacyManager.getInstance(context); mPowerManager = context.getSystemService(PowerManager.class); - mDeviceStateAutoRotateSettingsManager = DeviceStateRotationLockSettingsManager.getInstance( - context); + mDeviceStateAutoRotateSettingsManager = + DeviceStateAutoRotateSettingManagerProvider.getSingletonInstance( + context); } @Override diff --git a/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateDetailsFragmentTest.java b/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateDetailsFragmentTest.java index d1c32a2281f..6fce9a83390 100644 --- a/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateDetailsFragmentTest.java +++ b/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateDetailsFragmentTest.java @@ -39,7 +39,6 @@ import android.hardware.devicestate.DeviceStateManager; import com.android.settings.R; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager; import org.junit.Before; import org.junit.Test; @@ -144,16 +143,15 @@ public class DeviceStateAutoRotateDetailsFragmentTest { } private void enableDeviceStateSettableRotationStates(String[] settableStates, - String[] settableStatesDescriptions) { + String[] settableStatesDescriptions) { when(mResources.getStringArray( com.android.internal.R.array.config_perDeviceStateRotationLockDefaults)).thenReturn( settableStates); when(mResources.getStringArray( R.array.config_settableAutoRotationDeviceStatesDescriptions)).thenReturn( settableStatesDescriptions); - DeviceStateRotationLockSettingsManager.resetInstance(); - DeviceStateRotationLockSettingsManager.getInstance(mContext) - .resetStateForTesting(mResources); + DeviceStateAutoRotateSettingManagerProvider.resetInstance(); + when(mContext.getResources()).thenReturn(mResources); } // Sets up posture mappings for PosturesHelper diff --git a/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateSettingControllerTest.java b/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateSettingControllerTest.java index cb1be85f881..72a8cd58d42 100644 --- a/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateSettingControllerTest.java +++ b/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateSettingControllerTest.java @@ -37,7 +37,7 @@ import com.android.settings.R; import com.android.settings.testutils.shadow.ShadowDeviceStateRotationLockSettingsManager; import com.android.settings.testutils.shadow.ShadowRotationPolicy; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; -import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager; +import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager; import com.android.settingslib.search.SearchIndexableRaw; import org.junit.Before; @@ -66,7 +66,7 @@ public class DeviceStateAutoRotateSettingControllerTest { private static final int DEFAULT_ORDER = -10; private final Context mContext = Mockito.spy(RuntimeEnvironment.application); - private DeviceStateRotationLockSettingsManager mAutoRotateSettingsManager; + private DeviceStateAutoRotateSettingManager mAutoRotateSettingsManager; @Mock private MetricsFeatureProvider mMetricsFeatureProvider; @Mock private DeviceStateManager mDeviceStateManager; @@ -82,7 +82,7 @@ public class DeviceStateAutoRotateSettingControllerTest { doReturn(List.of(DEFAULT_DEVICE_STATE)).when( mDeviceStateManager).getSupportedDeviceStates(); mAutoRotateSettingsManager = - DeviceStateRotationLockSettingsManager.getInstance(mContext); + DeviceStateAutoRotateSettingManagerProvider.getSingletonInstance(mContext); mController = new DeviceStateAutoRotateSettingController( mContext, DEFAULT_DEVICE_STATE.getIdentifier(), diff --git a/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateSettingManagerProviderTest.kt b/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateSettingManagerProviderTest.kt new file mode 100644 index 00000000000..f2e59c5a005 --- /dev/null +++ b/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateSettingManagerProviderTest.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2025 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.settings.display; + +import android.content.Context +import android.content.res.Resources +import android.hardware.devicestate.DeviceStateManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.R +import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManagerImpl +import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager +import com.android.window.flags.Flags +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Assert.assertNotSame +import org.junit.Assert.assertSame +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit +import org.mockito.Mockito.`when` as whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class DeviceStateAutoRotateSettingManagerProviderTest { + + @get:Rule + val setFlagsRule: SetFlagsRule = SetFlagsRule() + @get:Rule + val rule = MockitoJUnit.rule() + + @Mock + private lateinit var mockContext: Context + @Mock + private lateinit var mockDeviceStateManager: DeviceStateManager + @Mock + private lateinit var mockResources: Resources + + private val context: Context = ApplicationProvider.getApplicationContext() + + @Before + fun setup() { + whenever(mockContext.contentResolver).thenReturn(context.contentResolver) + whenever(mockContext.getSystemService(DeviceStateManager::class.java)).thenReturn( + mockDeviceStateManager + ) + whenever(mockContext.resources).thenReturn(mockResources) + whenever(mockResources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults)) + .thenReturn(arrayOf()) + } + + @After + fun tearDown() { + DeviceStateAutoRotateSettingManagerProvider.resetInstance() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR) + fun getSingletonInstance_refactorFlagEnabled_returnsRefactoredManager() { + val manager = DeviceStateAutoRotateSettingManagerProvider.getSingletonInstance(mockContext) + + assertThat(manager).isInstanceOf(DeviceStateAutoRotateSettingManagerImpl::class.java) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR) + fun getSingletonInstance_refactorFlagDisabled_returnsLegacyManager() { + val manager = DeviceStateAutoRotateSettingManagerProvider.getSingletonInstance(mockContext) + + assertThat(manager).isInstanceOf(DeviceStateRotationLockSettingsManager::class.java) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR) + fun getSingletonInstance_resetInstance_returnsNewInstance() { + val manager1 = DeviceStateAutoRotateSettingManagerProvider.getSingletonInstance(mockContext) + DeviceStateAutoRotateSettingManagerProvider.resetInstance() + val manager2 = DeviceStateAutoRotateSettingManagerProvider.getSingletonInstance(mockContext) + + assertNotSame(manager1, manager2) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR) + fun getSingletonInstance_getInstanceTwice_returnsSameInstance() { + val manager1 = DeviceStateAutoRotateSettingManagerProvider.getSingletonInstance(mockContext) + val manager2 = DeviceStateAutoRotateSettingManagerProvider.getSingletonInstance(mockContext) + + assertSame(manager1, manager2) + } +} diff --git a/tests/robotests/src/com/android/settings/display/SmartAutoRotateControllerTest.java b/tests/robotests/src/com/android/settings/display/SmartAutoRotateControllerTest.java index e2542b0d55b..525c20ec3d9 100644 --- a/tests/robotests/src/com/android/settings/display/SmartAutoRotateControllerTest.java +++ b/tests/robotests/src/com/android/settings/display/SmartAutoRotateControllerTest.java @@ -45,7 +45,7 @@ import com.android.settings.testutils.shadow.ShadowDeviceStateRotationLockSettin import com.android.settings.testutils.shadow.ShadowRotationPolicy; import com.android.settings.testutils.shadow.ShadowSensorPrivacyManager; import com.android.settings.testutils.shadow.ShadowSystemSettings; -import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager; +import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager; import org.junit.Before; import org.junit.Test; @@ -74,7 +74,7 @@ public class SmartAutoRotateControllerTest { @Mock private DeviceStateManager mDeviceStateManager; private ContentResolver mContentResolver; - private DeviceStateRotationLockSettingsManager mDeviceStateAutoRotateSettingsManager; + private DeviceStateAutoRotateSettingManager mDeviceStateAutoRotateSettingManager; @Before public void setUp() { @@ -91,8 +91,8 @@ public class SmartAutoRotateControllerTest { doReturn(context).when(context).getApplicationContext(); doReturn(mDeviceStateManager).when(context).getSystemService(DeviceStateManager.class); doReturn(getDeviceStateList()).when(mDeviceStateManager).getSupportedDeviceStates(); - mDeviceStateAutoRotateSettingsManager = DeviceStateRotationLockSettingsManager.getInstance( - context); + mDeviceStateAutoRotateSettingManager = + DeviceStateAutoRotateSettingManagerProvider.getSingletonInstance(context); mController = Mockito.spy(new SmartAutoRotateController(context, "test_key")); when(mController.isCameraLocked()).thenReturn(false); @@ -187,13 +187,13 @@ public class SmartAutoRotateControllerTest { private void lockDeviceStateRotation() { ShadowDeviceStateRotationLockSettingsManager shadowManager = - Shadow.extract(mDeviceStateAutoRotateSettingsManager); + Shadow.extract(mDeviceStateAutoRotateSettingManager); shadowManager.setRotationLockedForAllStates(true); } private void unlockDeviceStateRotation() { ShadowDeviceStateRotationLockSettingsManager shadowManager = - Shadow.extract(mDeviceStateAutoRotateSettingsManager); + Shadow.extract(mDeviceStateAutoRotateSettingManager); shadowManager.setRotationLockedForAllStates(false); } diff --git a/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceFragmentTest.java index 16155384cf8..4b95aedd3a9 100644 --- a/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceFragmentTest.java @@ -58,7 +58,6 @@ import com.android.settings.testutils.ResolveInfoBuilder; import com.android.settings.testutils.shadow.ShadowDeviceStateRotationLockSettingsManager; import com.android.settings.testutils.shadow.ShadowRotationPolicy; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager; import org.junit.Before; import org.junit.Test; @@ -258,15 +257,14 @@ public class SmartAutoRotatePreferenceFragmentTest { private void enableDeviceStateSettableRotationStates( String[] settableStates, String[] settableStatesDescriptions) { when(mResources.getStringArray( - com.android.internal.R.array.config_perDeviceStateRotationLockDefaults)) + com.android.internal.R.array.config_perDeviceStateRotationLockDefaults)) .thenReturn(settableStates); when(mResources.getStringArray(R.array.config_settableAutoRotationDeviceStatesDescriptions)) .thenReturn(settableStatesDescriptions); when(mResources.getBoolean(R.bool.config_auto_rotate_face_detection_available)) .thenReturn(true); - DeviceStateRotationLockSettingsManager.resetInstance(); - DeviceStateRotationLockSettingsManager.getInstance(mContext) - .resetStateForTesting(mResources); + DeviceStateAutoRotateSettingManagerProvider.resetInstance(); + when(mContext.getResources()).thenReturn(mResources); } // Sets up posture mappings for PosturesHelper From e845919cbf7859da0c547ba80d5eb090af7f6ef3 Mon Sep 17 00:00:00 2001 From: tom hsu Date: Mon, 17 Mar 2025 19:36:47 +0000 Subject: [PATCH 04/11] [Satellite] Add log for the bug analysis Flag: EXEMPT bug fix Bug: b/404265450 Test: Manual test Change-Id: I05a01151615437603ba71e3d3021a9d4f0da069a --- .../satellite/SatelliteSettingsPreferenceCategoryController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/com/android/settings/network/telephony/satellite/SatelliteSettingsPreferenceCategoryController.java b/src/com/android/settings/network/telephony/satellite/SatelliteSettingsPreferenceCategoryController.java index f2fb347852c..d0cb1bcc5ca 100644 --- a/src/com/android/settings/network/telephony/satellite/SatelliteSettingsPreferenceCategoryController.java +++ b/src/com/android/settings/network/telephony/satellite/SatelliteSettingsPreferenceCategoryController.java @@ -159,6 +159,7 @@ public class SatelliteSettingsPreferenceCategoryController @Override public void onResult(Boolean result) { mIsSatelliteSupported.set(result); + Log.d(TAG, "Satellite requestIsSupported : " + result); SatelliteSettingsPreferenceCategoryController.this.displayPreference(); } }); From 2897cf5defc6a13b763fd5deba1b319a349628ee Mon Sep 17 00:00:00 2001 From: Xiaomiao Zhang Date: Fri, 14 Mar 2025 22:32:24 +0000 Subject: [PATCH 05/11] Add SafeSearch content filters preference. Test: atest SupervisionSafeSearchPreferenceTest Test: locally deployed on physical device Bug: 401569571 Flag: android.app.supervision.flags.enable_web_content_filters_screen Change-Id: Ia889c4c8f4df8d1b714909b19a7e0b5ef40154cf --- res/values/strings.xml | 10 ++ .../SupervisionSafeSearchPreference.kt | 106 ++++++++++++++++++ .../SupervisionWebContentFiltersScreen.kt | 10 +- .../SupervisionSafeSearchPreferenceTest.kt | 60 ++++++++++ 4 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 src/com/android/settings/supervision/SupervisionSafeSearchPreference.kt create mode 100644 tests/robotests/src/com/android/settings/supervision/SupervisionSafeSearchPreferenceTest.kt diff --git a/res/values/strings.xml b/res/values/strings.xml index ac99f2d5160..6d865c21096 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -14360,6 +14360,16 @@ Data usage charges may apply. No filter is perfect, but this should help hide sexually explicit sites Allow all sites + + Google Search + + SafeSearch filtering ON + + Helps filter out explicit images, text, and links from search results on this device + + SafeSearch filtering OFF + + Account settings may still filter or blur explicit results Enter supervision PIN diff --git a/src/com/android/settings/supervision/SupervisionSafeSearchPreference.kt b/src/com/android/settings/supervision/SupervisionSafeSearchPreference.kt new file mode 100644 index 00000000000..617a3451207 --- /dev/null +++ b/src/com/android/settings/supervision/SupervisionSafeSearchPreference.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2025 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.settings.supervision + +import android.content.Context +import androidx.preference.Preference +import com.android.settings.R +import com.android.settingslib.datastore.KeyValueStore +import com.android.settingslib.datastore.Permissions +import com.android.settingslib.datastore.SettingsSecureStore +import com.android.settingslib.metadata.BooleanValuePreference +import com.android.settingslib.metadata.PreferenceMetadata +import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.SensitivityLevel +import com.android.settingslib.preference.PreferenceBinding +import com.android.settingslib.preference.forEachRecursively +import com.android.settingslib.widget.SelectorWithWidgetPreference + +/** Base class of web content filters SafeSearch preferences. */ +sealed class SupervisionSafeSearchPreference : + BooleanValuePreference, SelectorWithWidgetPreference.OnClickListener, PreferenceBinding { + override fun storage(context: Context): KeyValueStore = SettingsSecureStore.get(context) + + override fun getReadPermissions(context: Context) = Permissions.EMPTY + + override fun getWritePermissions(context: Context) = Permissions.EMPTY + + override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) = + ReadWritePermit.ALLOW + + override fun getWritePermit( + context: Context, + value: Boolean?, + callingPid: Int, + callingUid: Int, + ) = ReadWritePermit.DISALLOW + + override val sensitivityLevel + get() = SensitivityLevel.NO_SENSITIVITY + + override fun createWidget(context: Context) = SelectorWithWidgetPreference(context) + + override fun onRadioButtonClicked(emiter: SelectorWithWidgetPreference) { + emiter.parent?.forEachRecursively { + if (it is SelectorWithWidgetPreference) { + it.isChecked = it == emiter + } + } + } + + override fun bind(preference: Preference, metadata: PreferenceMetadata) { + super.bind(preference, metadata) + (preference as SelectorWithWidgetPreference).also { + // TODO(b/401568995): Set the isChecked value using stored values. + it.isChecked = (it.key == SupervisionSearchFilterOffPreference.KEY) + it.setOnClickListener(this) + } + } +} + +/** The SafeSearch filter on preference. */ +class SupervisionSearchFilterOnPreference : SupervisionSafeSearchPreference() { + + override val key + get() = KEY + + override val title + get() = R.string.supervision_web_content_filters_search_filter_on_title + + override val summary + get() = R.string.supervision_web_content_filters_search_filter_on_summary + + companion object { + const val KEY = "web_content_filters_search_filter_on" + } +} + +/** The SafeSearch filter off preference. */ +class SupervisionSearchFilterOffPreference : SupervisionSafeSearchPreference() { + + override val key + get() = KEY + + override val title + get() = R.string.supervision_web_content_filters_search_filter_off_title + + override val summary + get() = R.string.supervision_web_content_filters_search_filter_off_summary + + companion object { + const val KEY = "web_content_filters_search_filter_off" + } +} diff --git a/src/com/android/settings/supervision/SupervisionWebContentFiltersScreen.kt b/src/com/android/settings/supervision/SupervisionWebContentFiltersScreen.kt index c76b6cdd5b4..994165a8b36 100644 --- a/src/com/android/settings/supervision/SupervisionWebContentFiltersScreen.kt +++ b/src/com/android/settings/supervision/SupervisionWebContentFiltersScreen.kt @@ -51,11 +51,19 @@ class SupervisionWebContentFiltersScreen : PreferenceScreenCreator { +SupervisionBlockExplicitSitesPreference(dataStore) +SupervisionAllowAllSitesPreference(dataStore) } - // TODO(b/401569571) implement the SafeSearch group. + +PreferenceCategory( + SEARCH_RADIO_BUTTON_GROUP, + R.string.supervision_web_content_filters_search_title, + ) += + { + +SupervisionSearchFilterOnPreference() + +SupervisionSearchFilterOffPreference() + } } companion object { const val KEY = "supervision_web_content_filters" internal const val BROWSER_RADIO_BUTTON_GROUP = "browser_radio_button_group" + internal const val SEARCH_RADIO_BUTTON_GROUP = "search_radio_button_group" } } diff --git a/tests/robotests/src/com/android/settings/supervision/SupervisionSafeSearchPreferenceTest.kt b/tests/robotests/src/com/android/settings/supervision/SupervisionSafeSearchPreferenceTest.kt new file mode 100644 index 00000000000..371cca3f2b5 --- /dev/null +++ b/tests/robotests/src/com/android/settings/supervision/SupervisionSafeSearchPreferenceTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2025 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.settings.supervision + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.R +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SupervisionSafeSearchPreferenceTest { + private val context: Context = ApplicationProvider.getApplicationContext() + + private val searchFilterOnPreference = SupervisionSearchFilterOnPreference() + + private val searchFilterOffPreference = SupervisionSearchFilterOffPreference() + + @Test + fun getTitle_filterOn() { + assertThat(searchFilterOnPreference.title) + .isEqualTo(R.string.supervision_web_content_filters_search_filter_on_title) + } + + + @Test + fun getSummary_filterOn() { + assertThat(searchFilterOnPreference.summary) + .isEqualTo(R.string.supervision_web_content_filters_search_filter_on_summary) + } + + @Test + fun getTitle_filterOff() { + assertThat(searchFilterOffPreference.title) + .isEqualTo(R.string.supervision_web_content_filters_search_filter_off_title) + } + + @Test + fun getSummary_filterOff() { + assertThat(searchFilterOffPreference.summary) + .isEqualTo( + R.string.supervision_web_content_filters_search_filter_off_summary + ) + } +} From 383d2c165e59a53019781a7ad496d23f66864e66 Mon Sep 17 00:00:00 2001 From: Yiyi Shen Date: Tue, 18 Mar 2025 14:30:31 +0800 Subject: [PATCH 06/11] [Audiosharing] Use metadata from callback to add source Instead of query metadata from LocalBluetoothLeBroadcast. In some corner case, LocalBluetoothLeBroadcast receive callbacks later than Settings app so it can not return latest metadata. Test: atest Flag: EXEMPT small fix Bug: 402199690 Change-Id: I0d1d36d0e594dc997b05b2f1d302ce1e2787990a --- .../AudioSharingSwitchBarController.java | 57 ++++++++++++------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java index 92ebc57bf52..f1fcd694b0a 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java @@ -192,7 +192,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController Log.d(TAG, "Skip handleOnBroadcastReady, not in starting process"); return; } - handleOnBroadcastReady(); + handleOnBroadcastReady(metadata); } @Override @@ -273,7 +273,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController + mSinksInAdding); if (mSinksToWaitFor.contains(sink)) { mSinksToWaitFor.remove(sink); - if (mSinksToWaitFor.isEmpty()) { + if (mSinksToWaitFor.isEmpty() && mBroadcast != null) { // To avoid users advance to share then pair flow before the // primary/active sinks successfully join the audio sharing, // popup dialog till adding source complete for mSinksToWaitFor. @@ -284,7 +284,8 @@ public class AudioSharingSwitchBarController extends BasePreferenceController /* userTriggered= */ false, /* deviceCountInSharing= */ 1, /* candidateDeviceCount= */ 0); - showAudioSharingDialog(eventData); + showJoinAudioSharingDialog(eventData, + mBroadcast.getLatestBluetoothLeBroadcastMetadata()); } } } @@ -501,9 +502,10 @@ public class AudioSharingSwitchBarController extends BasePreferenceController mBtManager == null ? null : mBtManager.getCachedDeviceManager(); CachedBluetoothDevice cachedDevice = deviceManager == null ? null : deviceManager.findDevice(device); - if (cachedDevice != null) { + if (cachedDevice != null && mBroadcast != null) { Log.d(TAG, "handleAutoAddSourceAfterPair, device = " + device.getAnonymizedAddress()); - addSourceToTargetSinks(ImmutableList.of(device), cachedDevice.getName()); + addSourceToTargetSinks(ImmutableList.of(device), cachedDevice.getName(), + mBroadcast.getLatestBluetoothLeBroadcastMetadata()); } } @@ -642,7 +644,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController return mAssistant != null && mAssistant.getAllConnectedDevices().isEmpty(); } - private void handleOnBroadcastReady() { + private void handleOnBroadcastReady(@NonNull BluetoothLeBroadcastMetadata metadata) { List targetActiveSinks = mTargetActiveItem == null ? ImmutableList.of() : mGroupedConnectedDevices.getOrDefault( mTargetActiveItem.getGroupId(), ImmutableList.of()); @@ -656,7 +658,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController // Auto add primary/active sinks w/o user interactions. if (!targetActiveSinks.isEmpty() && mTargetActiveItem != null) { Log.d(TAG, "handleOnBroadcastReady: automatically add source to active sinks."); - addSourceToTargetSinks(targetActiveSinks, mTargetActiveItem.getName()); + addSourceToTargetSinks(targetActiveSinks, mTargetActiveItem.getName(), metadata); // To avoid users advance to share then pair flow before the primary/active sinks // successfully join the audio sharing, save the primary/active sinks in mSinksToWaitFor // and popup dialog till adding source complete for these sinks. @@ -677,7 +679,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController AudioSharingDeviceItem target = mDeviceItemsForSharing.get(0); List targetSinks = mGroupedConnectedDevices.getOrDefault( target.getGroupId(), ImmutableList.of()); - addSourceToTargetSinks(targetSinks, target.getName()); + addSourceToTargetSinks(targetSinks, target.getName(), metadata); cleanUpStatesForStartSharing(); // TODO: Add metric for auto add by intent return; @@ -698,20 +700,21 @@ public class AudioSharingSwitchBarController extends BasePreferenceController // successfully join the audio sharing, popup dialog till adding source complete for // mSinksToWaitFor. if (mSinksToWaitFor.isEmpty() && !mStoppingSharing.get()) { - showAudioSharingDialog(eventData); + showJoinAudioSharingDialog(eventData, metadata); } } - private void showAudioSharingDialog(Pair[] eventData) { + private void showJoinAudioSharingDialog(Pair[] eventData, + @Nullable BluetoothLeBroadcastMetadata metadata) { if (!BluetoothUtils.isBroadcasting(mBtManager)) { - Log.d(TAG, "Skip showAudioSharingDialog, broadcast is stopped"); + Log.d(TAG, "Skip showJoinAudioSharingDialog, broadcast is stopped"); return; } AudioSharingDialogFragment.DialogEventListener listener = new AudioSharingDialogFragment.DialogEventListener() { @Override public void onPositiveClick() { - // Could go to other pages, dismiss the progress dialog. + // Could go to other pages (pair new device), dismiss the progress dialog. dismissProgressDialogIfNeeded(); cleanUpStatesForStartSharing(); } @@ -720,19 +723,17 @@ public class AudioSharingSwitchBarController extends BasePreferenceController public void onItemClick(@NonNull AudioSharingDeviceItem item) { List targetSinks = mGroupedConnectedDevices.getOrDefault( item.getGroupId(), ImmutableList.of()); - addSourceToTargetSinks(targetSinks, item.getName()); + addSourceToTargetSinks(targetSinks, item.getName(), metadata); cleanUpStatesForStartSharing(); } @Override public void onCancelClick() { - // Could go to other pages, dismiss the progress dialog. + // Could go to other pages (show qr code), dismiss the progress dialog. dismissProgressDialogIfNeeded(); cleanUpStatesForStartSharing(); } }; - BluetoothLeBroadcastMetadata metadata = mBroadcast == null ? null - : mBroadcast.getLatestBluetoothLeBroadcastMetadata(); AudioSharingUtils.postOnMainThread( mContext, () -> AudioSharingDialogFragment.show( @@ -828,13 +829,27 @@ public class AudioSharingSwitchBarController extends BasePreferenceController }); } - private void addSourceToTargetSinks(List targetActiveSinks, - @NonNull String sinkName) { - mSinksInAdding.addAll(targetActiveSinks); + private void addSourceToTargetSinks(List targetGroupedSinks, + @NonNull String targetSinkName, @Nullable BluetoothLeBroadcastMetadata metadata) { + if (targetGroupedSinks.isEmpty()) { + Log.d(TAG, "Skip addSourceToTargetSinks, no sinks."); + return; + } + if (metadata == null) { + Log.d(TAG, "Skip addSourceToTargetSinks, metadata is null"); + return; + } + if (mAssistant == null) { + Log.d(TAG, "skip addSourceToTargetDevices, assistant profile is null."); + return; + } + mSinksInAdding.addAll(targetGroupedSinks); String progressMessage = mContext.getString( - R.string.audio_sharing_progress_dialog_add_source_content, sinkName); + R.string.audio_sharing_progress_dialog_add_source_content, targetSinkName); showProgressDialog(progressMessage); - AudioSharingUtils.addSourceToTargetSinks(targetActiveSinks, mBtManager); + for (BluetoothDevice sink : targetGroupedSinks) { + mAssistant.addSource(sink, metadata, /* isGroupOp= */ false); + } } private void showProgressDialog(@NonNull String progressMessage) { From b9fa9201afa4979f8ccf674660db29fd6f05161f Mon Sep 17 00:00:00 2001 From: songferngwang Date: Tue, 18 Mar 2025 09:00:36 +0000 Subject: [PATCH 07/11] Don't create the preference when isAvailable() is false Bug: 402672865 Test: manual test Flag: EXEMPT bugfix Change-Id: Icb80a0aec6e07578e6049ed2ff5ac159e57ca940 --- .../settings/deviceinfo/PhoneNumberPreferenceController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java b/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java index b49d62d444f..bb39d88245c 100644 --- a/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java +++ b/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java @@ -65,9 +65,10 @@ public class PhoneNumberPreferenceController extends BasePreferenceController { @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); - if (!SubscriptionUtil.isSimHardwareVisible(mContext)) { + if (!isAvailable()) { return; } + final Preference preference = screen.findPreference(getPreferenceKey()); final PreferenceCategory category = screen.findPreference(KEY_PREFERENCE_CATEGORY); mPreferenceList.add(preference); From cb181c50b5b0a04f7aec81aaa96657ca5c22c611 Mon Sep 17 00:00:00 2001 From: Bill Lin Date: Tue, 18 Mar 2025 00:20:36 -0700 Subject: [PATCH 08/11] Revert "Launch multiple biometric enrollment when no biometric is enrolled" This reverts commit c4ee2b83e5b129793bbd6f433b9f54519b4636fe. Reason for revert: b/404133188 Change-Id: I523ab1cc638ac9782a6b60a7f640f620bb3aae22 --- .../safetycenter/BiometricSourcesUtils.java | 13 --------- .../safetycenter/FaceSafetySource.java | 16 ++--------- .../safetycenter/FingerprintSafetySource.java | 8 +----- .../safetycenter/FaceSafetySourceTest.java | 27 +------------------ .../FingerprintSafetySourceTest.java | 27 +------------------ 5 files changed, 5 insertions(+), 86 deletions(-) diff --git a/src/com/android/settings/safetycenter/BiometricSourcesUtils.java b/src/com/android/settings/safetycenter/BiometricSourcesUtils.java index 786e3cf3865..9cadbbb5fcf 100644 --- a/src/com/android/settings/safetycenter/BiometricSourcesUtils.java +++ b/src/com/android/settings/safetycenter/BiometricSourcesUtils.java @@ -19,15 +19,11 @@ package com.android.settings.safetycenter; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.hardware.face.FaceManager; -import android.hardware.fingerprint.FingerprintManager; import android.safetycenter.SafetyEvent; import android.safetycenter.SafetySourceData; import android.safetycenter.SafetySourceIssue; import android.safetycenter.SafetySourceStatus; -import com.android.settings.Utils; - /** Static helpers for setting SafetyCenter data for biometric safety sources. */ public final class BiometricSourcesUtils { @@ -93,15 +89,6 @@ public final class BiometricSourcesUtils { .setSafetySourceData(context, safetySourceId, safetySourceData, safetyEvent); } - /** Check whether the multiple biometrics enrollment is needed. */ - public static boolean isMultipleBiometricsEnrollmentNeeded(Context context, int userId) { - FaceManager faceManager = Utils.getFaceManagerOrNull(context); - FingerprintManager fingerprintManager = Utils.getFingerprintManagerOrNull(context); - return Utils.isMultipleBiometricsSupported(context) - && !faceManager.hasEnrolledTemplates(userId) - && !fingerprintManager.hasEnrolledFingerprints(userId); - } - /** Helper method for creating a pending intent. */ public static PendingIntent createPendingIntent( Context context, Intent intent, int requestCode) { diff --git a/src/com/android/settings/safetycenter/FaceSafetySource.java b/src/com/android/settings/safetycenter/FaceSafetySource.java index c5bde6efb60..73b6df56803 100644 --- a/src/com/android/settings/safetycenter/FaceSafetySource.java +++ b/src/com/android/settings/safetycenter/FaceSafetySource.java @@ -16,7 +16,6 @@ package com.android.settings.safetycenter; -import static com.android.settings.biometrics.BiometricEnrollActivity.EXTRA_LAUNCH_FACE_ENROLL_FIRST; import static com.android.settings.safetycenter.BiometricSourcesUtils.REQUEST_CODE_FACE_SETTING; import android.content.Context; @@ -28,7 +27,6 @@ import android.os.UserManager; import android.safetycenter.SafetyEvent; import com.android.settings.Utils; -import com.android.settings.biometrics.BiometricEnrollActivity; import com.android.settings.biometrics.BiometricNavigationUtils; import com.android.settings.biometrics.face.FaceStatusUtils; import com.android.settings.flags.Flags; @@ -75,16 +73,6 @@ public final class FaceSafetySource { Context profileParentContext = context.createContextAsUser(profileParentUserHandle, 0); if (Utils.hasFaceHardware(context)) { - boolean isMultipleBiometricsEnrollmentNeeded = - BiometricSourcesUtils.isMultipleBiometricsEnrollmentNeeded(context, userId); - String settingClassName = isMultipleBiometricsEnrollmentNeeded - ? BiometricEnrollActivity.class.getName() - : faceStatusUtils.getSettingsClassName(); - Bundle bundle = new Bundle(); - if (isMultipleBiometricsEnrollmentNeeded) { - // Launch face enrollment first then fingerprint enrollment. - bundle.putBoolean(EXTRA_LAUNCH_FACE_ENROLL_FIRST, true); - } RestrictedLockUtils.EnforcedAdmin disablingAdmin = faceStatusUtils.getDisablingAdmin(); BiometricSourcesUtils.setBiometricSafetySourceData( SAFETY_SOURCE_ID, @@ -96,9 +84,9 @@ public final class FaceSafetySource { biometricNavigationUtils .getBiometricSettingsIntent( context, - settingClassName, + faceStatusUtils.getSettingsClassName(), disablingAdmin, - bundle) + Bundle.EMPTY) .setIdentifier(Integer.toString(userId)), REQUEST_CODE_FACE_SETTING), disablingAdmin == null /* enabled */, diff --git a/src/com/android/settings/safetycenter/FingerprintSafetySource.java b/src/com/android/settings/safetycenter/FingerprintSafetySource.java index 752fa69ba95..62f218bd519 100644 --- a/src/com/android/settings/safetycenter/FingerprintSafetySource.java +++ b/src/com/android/settings/safetycenter/FingerprintSafetySource.java @@ -27,7 +27,6 @@ import android.os.UserManager; import android.safetycenter.SafetyEvent; import com.android.settings.Utils; -import com.android.settings.biometrics.BiometricEnrollActivity; import com.android.settings.biometrics.BiometricNavigationUtils; import com.android.settings.biometrics.fingerprint.FingerprintStatusUtils; import com.android.settings.flags.Flags; @@ -75,11 +74,6 @@ public final class FingerprintSafetySource { Context profileParentContext = context.createContextAsUser(profileParentUserHandle, 0); if (Utils.hasFingerprintHardware(context)) { - boolean isMultipleBiometricsEnrollmentNeeded = - BiometricSourcesUtils.isMultipleBiometricsEnrollmentNeeded(context, userId); - String settingClassName = isMultipleBiometricsEnrollmentNeeded - ? BiometricEnrollActivity.class.getName() - : fingerprintStatusUtils.getSettingsClassName(); RestrictedLockUtils.EnforcedAdmin disablingAdmin = fingerprintStatusUtils.getDisablingAdmin(); BiometricSourcesUtils.setBiometricSafetySourceData( @@ -92,7 +86,7 @@ public final class FingerprintSafetySource { biometricNavigationUtils .getBiometricSettingsIntent( context, - settingClassName, + fingerprintStatusUtils.getSettingsClassName(), disablingAdmin, Bundle.EMPTY) .setIdentifier(Integer.toString(userId)), diff --git a/tests/unit/src/com/android/settings/safetycenter/FaceSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/FaceSafetySourceTest.java index bd53e42eb74..9335ced149c 100644 --- a/tests/unit/src/com/android/settings/safetycenter/FaceSafetySourceTest.java +++ b/tests/unit/src/com/android/settings/safetycenter/FaceSafetySourceTest.java @@ -37,7 +37,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.hardware.face.FaceManager; -import android.hardware.fingerprint.FingerprintManager; import android.os.UserHandle; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; @@ -53,7 +52,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.internal.widget.LockPatternUtils; import com.android.settings.Settings; -import com.android.settings.biometrics.BiometricEnrollActivity; import com.android.settings.biometrics.face.FaceEnrollIntroductionInternal; import com.android.settings.flags.Flags; import com.android.settings.testutils.FakeFeatureFactory; @@ -85,7 +83,6 @@ public class FaceSafetySourceTest { @Mock private PackageManager mPackageManager; @Mock private DevicePolicyManager mDevicePolicyManager; @Mock private FaceManager mFaceManager; - @Mock private FingerprintManager mFingerprintManager; @Mock private LockPatternUtils mLockPatternUtils; @Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper; @Mock private SupervisionManager mSupervisionManager; @@ -100,8 +97,6 @@ public class FaceSafetySourceTest { when(mApplicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE)) .thenReturn(mDevicePolicyManager); when(mApplicationContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager); - when(mApplicationContext.getSystemService(Context.FINGERPRINT_SERVICE)) - .thenReturn(mFingerprintManager); when(mApplicationContext.getSystemService(Context.SUPERVISION_SERVICE)) .thenReturn(mSupervisionManager); FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest(); @@ -215,12 +210,10 @@ public class FaceSafetySourceTest { @Test @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) - public void setSafetySourceData_onlyFaceNotEnrolled_whenNotDisabledByAdmin_setsData() { + public void setSafetySourceData_withFaceNotEnrolled_whenNotDisabledByAdmin_setsData() { when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); when(mFaceManager.isHardwareDetected()).thenReturn(true); when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false); - when(mFingerprintManager.isHardwareDetected()).thenReturn(true); - when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(true); when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0); FaceSafetySource.setSafetySourceData(mApplicationContext, EVENT_SOURCE_STATE_CHANGED); @@ -231,24 +224,6 @@ public class FaceSafetySourceTest { FaceEnrollIntroductionInternal.class.getName()); } - @Test - @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) - public void setSafetySourceData_noBiometricEnrolled_whenNotDisabledByAdmin_setsData() { - when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); - when(mFaceManager.isHardwareDetected()).thenReturn(true); - when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false); - when(mFingerprintManager.isHardwareDetected()).thenReturn(true); - when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false); - when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0); - - FaceSafetySource.setSafetySourceData(mApplicationContext, EVENT_SOURCE_STATE_CHANGED); - - assertSafetySourceEnabledDataSetWithSingularSummary( - "security_settings_face_preference_title_new", - "security_settings_face_preference_summary_none_new", - BiometricEnrollActivity.class.getName()); - } - @Test @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) @DisableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS) diff --git a/tests/unit/src/com/android/settings/safetycenter/FingerprintSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/FingerprintSafetySourceTest.java index 33551885da2..91e7953fd5f 100644 --- a/tests/unit/src/com/android/settings/safetycenter/FingerprintSafetySourceTest.java +++ b/tests/unit/src/com/android/settings/safetycenter/FingerprintSafetySourceTest.java @@ -36,7 +36,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.hardware.face.FaceManager; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.os.UserHandle; @@ -53,7 +52,6 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.internal.widget.LockPatternUtils; -import com.android.settings.biometrics.BiometricEnrollActivity; import com.android.settings.biometrics.fingerprint.FingerprintSettings; import com.android.settings.flags.Flags; import com.android.settings.testutils.FakeFeatureFactory; @@ -89,7 +87,6 @@ public class FingerprintSafetySourceTest { @Mock private PackageManager mPackageManager; @Mock private DevicePolicyManager mDevicePolicyManager; @Mock private FingerprintManager mFingerprintManager; - @Mock private FaceManager mFaceManager; @Mock private LockPatternUtils mLockPatternUtils; @Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper; @Mock private SupervisionManager mSupervisionManager; @@ -103,7 +100,6 @@ public class FingerprintSafetySourceTest { when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true); when(mApplicationContext.getSystemService(Context.FINGERPRINT_SERVICE)) .thenReturn(mFingerprintManager); - when(mApplicationContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager); when(mApplicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE)) .thenReturn(mDevicePolicyManager); when(mApplicationContext.getSystemService(Context.SUPERVISION_SERVICE)) @@ -231,12 +227,10 @@ public class FingerprintSafetySourceTest { @Test @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) - public void setSafetySourceData_onlyFingerprintNotEnrolled_whenNotDisabledByAdmin_setsData() { + public void setSafetySourceData_withFingerprintNotEnrolled_whenNotDisabledByAdmin_setsData() { when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); when(mFingerprintManager.isHardwareDetected()).thenReturn(true); when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false); - when(mFaceManager.isHardwareDetected()).thenReturn(true); - when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true); when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0); FingerprintSafetySource.setSafetySourceData( @@ -248,25 +242,6 @@ public class FingerprintSafetySourceTest { FingerprintSettings.class.getName()); } - @Test - @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) - public void setSafetySourceData_noBiometricEnrolled_whenNotDisabledByAdmin_setsData() { - when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); - when(mFingerprintManager.isHardwareDetected()).thenReturn(true); - when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false); - when(mFaceManager.isHardwareDetected()).thenReturn(true); - when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false); - when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0); - - FingerprintSafetySource.setSafetySourceData( - mApplicationContext, EVENT_SOURCE_STATE_CHANGED); - - assertSafetySourceEnabledDataSetWithSingularSummary( - "security_settings_fingerprint", - "security_settings_fingerprint_preference_summary_none_new", - BiometricEnrollActivity.class.getName()); - } - @Test @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) @DisableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS) From fc10fdf30bfd7740d97dce6b73a9f4746161257a Mon Sep 17 00:00:00 2001 From: Haijie Hong Date: Tue, 18 Mar 2025 18:56:24 +0800 Subject: [PATCH 09/11] Check bond state when bluetooth key is missing Bug: 403847818 Test: local tested Flag: EXEMPT minor fix Change-Id: I3f310b5758ebecd7591d76988b351d3c6a7dfc67 --- .../settings/bluetooth/BluetoothKeyMissingReceiver.java | 9 +++++++++ .../bluetooth/BluetoothKeyMissingReceiverTest.java | 2 ++ 2 files changed, 11 insertions(+) diff --git a/src/com/android/settings/bluetooth/BluetoothKeyMissingReceiver.java b/src/com/android/settings/bluetooth/BluetoothKeyMissingReceiver.java index e7e0b4a100e..cfe9c056d39 100644 --- a/src/com/android/settings/bluetooth/BluetoothKeyMissingReceiver.java +++ b/src/com/android/settings/bluetooth/BluetoothKeyMissingReceiver.java @@ -55,9 +55,18 @@ public final class BluetoothKeyMissingReceiver extends BroadcastReceiver { } BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (device == null) { + return; + } PowerManager powerManager = context.getSystemService(PowerManager.class); if (TextUtils.equals(action, BluetoothDevice.ACTION_KEY_MISSING)) { Log.d(TAG, "Receive ACTION_KEY_MISSING"); + if (device.getBondState() == BluetoothDevice.BOND_NONE) { + Log.d( + TAG, + "Device " + device.getAnonymizedAddress() + " is already unbonded, skip."); + return; + } Integer keyMissingCount = BluetoothUtils.getKeyMissingCount(device); if (keyMissingCount != null && keyMissingCount != 1) { Log.d(TAG, "Key missing count is " + keyMissingCount + ", skip."); diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothKeyMissingReceiverTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothKeyMissingReceiverTest.java index a183d8d7e68..42d7105c6f3 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothKeyMissingReceiverTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothKeyMissingReceiverTest.java @@ -127,6 +127,7 @@ public class BluetoothKeyMissingReceiverTest { public void broadcastReceiver_background_showNotification() { Intent intent = spy(new Intent(BluetoothDevice.ACTION_KEY_MISSING)); when(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)).thenReturn(mBluetoothDevice); + when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); BluetoothKeyMissingReceiver bluetoothKeyMissingReceiver = getReceiver(intent); bluetoothKeyMissingReceiver.onReceive(mContext, intent); @@ -141,6 +142,7 @@ public class BluetoothKeyMissingReceiverTest { when(mLocalBtManager.isForegroundActivity()).thenReturn(true); Intent intent = spy(new Intent(BluetoothDevice.ACTION_KEY_MISSING)); when(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)).thenReturn(mBluetoothDevice); + when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); BluetoothKeyMissingReceiver bluetoothKeyMissingReceiver = getReceiver(intent); bluetoothKeyMissingReceiver.onReceive(mContext, intent); From 9e3b9146dd8972cd06f97dbcfdd5841a873449f2 Mon Sep 17 00:00:00 2001 From: Shivangi Dubey Date: Tue, 18 Mar 2025 04:03:43 -0700 Subject: [PATCH 10/11] Refactor ShadowDeviceStateAutoRotateSettingManager Update ShadowDeviceSttaeAutoRotateSettingManager as per the latest changes to DeviceStateAutoRotateSettingManager. Test: atest DeviceStateAutoRotateSettingControllerTest Bug: 394303723 Bug: 394303731 Flag: com.android.window.flags.enable_device_state_auto_rotate_setting_refactor Change-Id: I8ea694709ccfd08310b241713b5ec67f2e070e2c --- ...creenRotationPreferenceControllerTest.java | 36 ++++++++-------- ...StateAutoRotateOverviewControllerTest.java | 34 +++++++++++---- ...eStateAutoRotateSettingControllerTest.java | 19 +++++---- .../SmartAutoRotateControllerTest.java | 19 ++++++--- ...artAutoRotatePreferenceControllerTest.java | 10 ++--- ...SmartAutoRotatePreferenceFragmentTest.java | 7 ++-- ...DeviceStateAutoRotateSettingTestUtils.java | 41 +++++++++++++++++++ ...wDeviceStateAutoRotateSettingManager.java} | 14 +------ 8 files changed, 120 insertions(+), 60 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/testutils/DeviceStateAutoRotateSettingTestUtils.java rename tests/robotests/src/com/android/settings/testutils/shadow/{ShadowDeviceStateRotationLockSettingsManager.java => ShadowDeviceStateAutoRotateSettingManager.java} (73%) diff --git a/tests/robotests/src/com/android/settings/accessibility/LockScreenRotationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/LockScreenRotationPreferenceControllerTest.java index c98ad3df5a3..88623ac6889 100644 --- a/tests/robotests/src/com/android/settings/accessibility/LockScreenRotationPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/LockScreenRotationPreferenceControllerTest.java @@ -16,9 +16,14 @@ package com.android.settings.accessibility; +import static com.android.settings.testutils.DeviceStateAutoRotateSettingTestUtils.setDeviceStateRotationLockEnabled; + import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + import android.content.Context; +import android.content.res.Resources; import android.os.UserHandle; import android.provider.Settings; @@ -26,12 +31,14 @@ import androidx.preference.SwitchPreference; import com.android.internal.view.RotationPolicy; import com.android.settings.core.BasePreferenceController; -import com.android.settings.testutils.shadow.ShadowDeviceStateRotationLockSettingsManager; import com.android.settings.testutils.shadow.ShadowRotationPolicy; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @@ -41,48 +48,45 @@ import org.robolectric.annotation.Config; com.android.settings.testutils.shadow.ShadowSystemSettings.class, }) public class LockScreenRotationPreferenceControllerTest { - + @Mock + private Resources mResources; private Context mContext; private SwitchPreference mPreference; private LockScreenRotationPreferenceController mController; @Before public void setUp() { - mContext = RuntimeEnvironment.application; + MockitoAnnotations.initMocks(this); + mContext = Mockito.spy(RuntimeEnvironment.application); mPreference = new SwitchPreference(mContext); + when(mContext.getResources()).thenReturn(mResources); + mController = new LockScreenRotationPreferenceController(mContext, "lock_screen"); } @Test - @Config(shadows = { - ShadowRotationPolicy.class, - ShadowDeviceStateRotationLockSettingsManager.class - }) + @Config(shadows = {ShadowRotationPolicy.class}) public void getAvailabilityStatus_supportedRotation_shouldReturnAvailable() { ShadowRotationPolicy.setRotationSupported(true /* supported */); + setDeviceStateRotationLockEnabled(false, mResources); assertThat(mController.getAvailabilityStatus()).isEqualTo( BasePreferenceController.AVAILABLE); } @Test - @Config(shadows = { - ShadowRotationPolicy.class, - ShadowDeviceStateRotationLockSettingsManager.class - }) + @Config(shadows = {ShadowRotationPolicy.class}) public void getAvailabilityStatus_deviceStateRotationEnabled_returnsUnsupported() { ShadowRotationPolicy.setRotationSupported(true /* supported */); - ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(true); + setDeviceStateRotationLockEnabled(true, mResources); assertThat(mController.getAvailabilityStatus()).isEqualTo( BasePreferenceController.UNSUPPORTED_ON_DEVICE); } @Test - @Config(shadows = { - ShadowRotationPolicy.class, - ShadowDeviceStateRotationLockSettingsManager.class - }) public void getAvailabilityStatus_unsupportedRotation_shouldReturnUnsupportedOnDevice() { + @Config(shadows = {ShadowRotationPolicy.class}) + public void getAvailabilityStatus_unsupportedRotation_shouldReturnUnsupportedOnDevice() { ShadowRotationPolicy.setRotationSupported(false /* supported */); assertThat(mController.getAvailabilityStatus()).isEqualTo( diff --git a/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateOverviewControllerTest.java b/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateOverviewControllerTest.java index a5416e70413..4c2c694a4bd 100644 --- a/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateOverviewControllerTest.java +++ b/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateOverviewControllerTest.java @@ -18,30 +18,48 @@ package com.android.settings.display; import static com.android.settings.core.BasePreferenceController.AVAILABLE; import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; +import static com.android.settings.testutils.DeviceStateAutoRotateSettingTestUtils.setDeviceStateRotationLockEnabled; import static com.google.common.truth.Truth.assertThat; -import com.android.settings.testutils.shadow.ShadowDeviceStateRotationLockSettingsManager; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; + import com.android.settings.testutils.shadow.ShadowRotationPolicy; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowRotationPolicy.class, ShadowDeviceStateRotationLockSettingsManager.class}) +@Config(shadows = {ShadowRotationPolicy.class}) public class DeviceStateAutoRotateOverviewControllerTest { + @Mock + private Resources mResources; + private DeviceStateAutoRotateOverviewController mController; - private final DeviceStateAutoRotateOverviewController mController = - new DeviceStateAutoRotateOverviewController( - RuntimeEnvironment.application, "device_state_auto_rotate"); + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + Context context = Mockito.spy(RuntimeEnvironment.application); + when(context.getResources()).thenReturn(mResources); + + mController = new DeviceStateAutoRotateOverviewController( + context, "device_state_auto_rotate"); + } @Test public void getAvailabilityStatus_rotationAndDeviceStateRotationEnabled_returnsAvailable() { ShadowRotationPolicy.setRotationSupported(true); - ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(true); + setDeviceStateRotationLockEnabled(true, mResources); int availability = mController.getAvailabilityStatus(); @@ -51,7 +69,7 @@ public class DeviceStateAutoRotateOverviewControllerTest { @Test public void getAvailabilityStatus_rotationNotSupported_returnsUnsupportedOnDevice() { ShadowRotationPolicy.setRotationSupported(false); - ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(true); + setDeviceStateRotationLockEnabled(true, mResources); int availability = mController.getAvailabilityStatus(); @@ -61,7 +79,7 @@ public class DeviceStateAutoRotateOverviewControllerTest { @Test public void getAvailabilityStatus_deviceStateRotationNotSupported_returnsUnsupportedOnDevice() { ShadowRotationPolicy.setRotationSupported(true); - ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(false); + setDeviceStateRotationLockEnabled(false, mResources); int availability = mController.getAvailabilityStatus(); diff --git a/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateSettingControllerTest.java b/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateSettingControllerTest.java index 72a8cd58d42..63a4af21e91 100644 --- a/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateSettingControllerTest.java +++ b/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateSettingControllerTest.java @@ -18,14 +18,17 @@ package com.android.settings.display; import static com.android.settings.core.BasePreferenceController.AVAILABLE; import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; +import static com.android.settings.testutils.DeviceStateAutoRotateSettingTestUtils.setDeviceStateRotationLockEnabled; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.settings.SettingsEnums; import android.content.Context; +import android.content.res.Resources; import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; @@ -34,7 +37,6 @@ import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; import com.android.settings.R; -import com.android.settings.testutils.shadow.ShadowDeviceStateRotationLockSettingsManager; import com.android.settings.testutils.shadow.ShadowRotationPolicy; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager; @@ -54,10 +56,7 @@ import java.util.ArrayList; import java.util.List; @RunWith(RobolectricTestRunner.class) -@Config(shadows = { - ShadowRotationPolicy.class, - ShadowDeviceStateRotationLockSettingsManager.class -}) +@Config(shadows = {ShadowRotationPolicy.class}) public class DeviceStateAutoRotateSettingControllerTest { private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState( @@ -70,6 +69,7 @@ public class DeviceStateAutoRotateSettingControllerTest { @Mock private MetricsFeatureProvider mMetricsFeatureProvider; @Mock private DeviceStateManager mDeviceStateManager; + @Mock private Resources mResources; private DeviceStateAutoRotateSettingController mController; @@ -78,11 +78,14 @@ public class DeviceStateAutoRotateSettingControllerTest { MockitoAnnotations.initMocks(this); doReturn(mContext).when(mContext).getApplicationContext(); + when(mContext.getResources()).thenReturn(mResources); doReturn(mDeviceStateManager).when(mContext).getSystemService(DeviceStateManager.class); doReturn(List.of(DEFAULT_DEVICE_STATE)).when( mDeviceStateManager).getSupportedDeviceStates(); + setDeviceStateRotationLockEnabled(false, mResources); mAutoRotateSettingsManager = DeviceStateAutoRotateSettingManagerProvider.getSingletonInstance(mContext); + mController = new DeviceStateAutoRotateSettingController( mContext, DEFAULT_DEVICE_STATE.getIdentifier(), @@ -108,7 +111,7 @@ public class DeviceStateAutoRotateSettingControllerTest { @Test public void getAvailabilityStatus_rotationAndDeviceStateRotationEnabled_returnsAvailable() { ShadowRotationPolicy.setRotationSupported(true); - ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(true); + setDeviceStateRotationLockEnabled(true, mResources); int availability = mController.getAvailabilityStatus(); @@ -118,7 +121,7 @@ public class DeviceStateAutoRotateSettingControllerTest { @Test public void getAvailabilityStatus_deviceStateRotationDisabled_returnsUnsupported() { ShadowRotationPolicy.setRotationSupported(true); - ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(false); + setDeviceStateRotationLockEnabled(false, mResources); int availability = mController.getAvailabilityStatus(); @@ -128,7 +131,7 @@ public class DeviceStateAutoRotateSettingControllerTest { @Test public void getAvailabilityStatus_rotationDisabled_returnsUnsupported() { ShadowRotationPolicy.setRotationSupported(false); - ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(true); + setDeviceStateRotationLockEnabled(true, mResources); int availability = mController.getAvailabilityStatus(); diff --git a/tests/robotests/src/com/android/settings/display/SmartAutoRotateControllerTest.java b/tests/robotests/src/com/android/settings/display/SmartAutoRotateControllerTest.java index 525c20ec3d9..a1eb89c249a 100644 --- a/tests/robotests/src/com/android/settings/display/SmartAutoRotateControllerTest.java +++ b/tests/robotests/src/com/android/settings/display/SmartAutoRotateControllerTest.java @@ -19,6 +19,7 @@ package com.android.settings.display; import static com.android.settings.core.BasePreferenceController.AVAILABLE; import static com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING; import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; +import static com.android.settings.testutils.DeviceStateAutoRotateSettingTestUtils.setDeviceStateRotationLockEnabled; import static com.google.common.truth.Truth.assertThat; @@ -33,6 +34,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.res.Resources; import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.os.UserHandle; @@ -41,7 +43,7 @@ import android.provider.Settings; import androidx.preference.Preference; import com.android.settings.testutils.ResolveInfoBuilder; -import com.android.settings.testutils.shadow.ShadowDeviceStateRotationLockSettingsManager; +import com.android.settings.testutils.shadow.ShadowDeviceStateAutoRotateSettingManager; import com.android.settings.testutils.shadow.ShadowRotationPolicy; import com.android.settings.testutils.shadow.ShadowSensorPrivacyManager; import com.android.settings.testutils.shadow.ShadowSystemSettings; @@ -73,6 +75,8 @@ public class SmartAutoRotateControllerTest { private Preference mPreference; @Mock private DeviceStateManager mDeviceStateManager; + @Mock + private Resources mResources; private ContentResolver mContentResolver; private DeviceStateAutoRotateSettingManager mDeviceStateAutoRotateSettingManager; @@ -81,9 +85,11 @@ public class SmartAutoRotateControllerTest { MockitoAnnotations.initMocks(this); final Context context = Mockito.spy(RuntimeEnvironment.application); mContentResolver = RuntimeEnvironment.application.getContentResolver(); + mResources = Mockito.spy(RuntimeEnvironment.application.getResources()); when(context.getPackageManager()).thenReturn(mPackageManager); when(context.getContentResolver()).thenReturn(mContentResolver); + when(context.getResources()).thenReturn(mResources); doReturn(PACKAGE_NAME).when(mPackageManager).getRotationResolverPackageName(); doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission( Manifest.permission.CAMERA, PACKAGE_NAME); @@ -91,6 +97,7 @@ public class SmartAutoRotateControllerTest { doReturn(context).when(context).getApplicationContext(); doReturn(mDeviceStateManager).when(context).getSystemService(DeviceStateManager.class); doReturn(getDeviceStateList()).when(mDeviceStateManager).getSupportedDeviceStates(); + setDeviceStateRotationLockEnabled(false, mResources); mDeviceStateAutoRotateSettingManager = DeviceStateAutoRotateSettingManagerProvider.getSingletonInstance(context); mController = Mockito.spy(new SmartAutoRotateController(context, "test_key")); @@ -144,7 +151,7 @@ public class SmartAutoRotateControllerTest { @Test @Config(shadows = { - ShadowDeviceStateRotationLockSettingsManager.class, + ShadowDeviceStateAutoRotateSettingManager.class, ShadowRotationPolicy.class }) public void getAvailabilityStatus_deviceStateRotationLocked_returnDisableDependentSetting() { @@ -158,7 +165,7 @@ public class SmartAutoRotateControllerTest { @Test @Config(shadows = { - ShadowDeviceStateRotationLockSettingsManager.class, + ShadowDeviceStateAutoRotateSettingManager.class, ShadowRotationPolicy.class }) public void getAvailabilityStatus_deviceStateRotationUnlocked_returnAvailable() { @@ -182,17 +189,17 @@ public class SmartAutoRotateControllerTest { private void enableDeviceStateRotation() { ShadowRotationPolicy.setRotationSupported(true); - ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(true); + setDeviceStateRotationLockEnabled(true, mResources); } private void lockDeviceStateRotation() { - ShadowDeviceStateRotationLockSettingsManager shadowManager = + ShadowDeviceStateAutoRotateSettingManager shadowManager = Shadow.extract(mDeviceStateAutoRotateSettingManager); shadowManager.setRotationLockedForAllStates(true); } private void unlockDeviceStateRotation() { - ShadowDeviceStateRotationLockSettingsManager shadowManager = + ShadowDeviceStateAutoRotateSettingManager shadowManager = Shadow.extract(mDeviceStateAutoRotateSettingManager); shadowManager.setRotationLockedForAllStates(false); } diff --git a/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceControllerTest.java index 9f1b5d4357f..1fb4703fc7c 100644 --- a/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceControllerTest.java @@ -18,6 +18,8 @@ package com.android.settings.display; import static android.provider.Settings.Secure.CAMERA_AUTOROTATE; +import static com.android.settings.testutils.DeviceStateAutoRotateSettingTestUtils.setDeviceStateRotationLockEnabled; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -40,7 +42,6 @@ import com.android.settings.R; import com.android.settings.core.BasePreferenceController; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.ResolveInfoBuilder; -import com.android.settings.testutils.shadow.ShadowDeviceStateRotationLockSettingsManager; import com.android.settings.testutils.shadow.ShadowSensorPrivacyManager; import com.android.settings.testutils.shadow.ShadowSystemSettings; @@ -57,8 +58,7 @@ import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) @Config(shadows = { ShadowSystemSettings.class, - ShadowSensorPrivacyManager.class, - ShadowDeviceStateRotationLockSettingsManager.class + ShadowSensorPrivacyManager.class }) public class SmartAutoRotatePreferenceControllerTest { @@ -104,7 +104,7 @@ public class SmartAutoRotatePreferenceControllerTest { new SmartAutoRotatePreferenceController(mContext, "smart_auto_rotate")); when(mController.isCameraLocked()).thenReturn(false); when(mController.isPowerSaveMode()).thenReturn(false); - ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(false); + setDeviceStateRotationLockEnabled(false, mResources); } @Test @@ -213,7 +213,7 @@ public class SmartAutoRotatePreferenceControllerTest { @Test public void getAvailabilityStatus_deviceStateRotationEnabled_returnsUnsupported() { enableAutoRotationPreference(); - ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(true); + setDeviceStateRotationLockEnabled(true, mResources); assertThat(mController.getAvailabilityStatus()).isEqualTo( BasePreferenceController.UNSUPPORTED_ON_DEVICE); diff --git a/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceFragmentTest.java index 4b95aedd3a9..731cffb8719 100644 --- a/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceFragmentTest.java @@ -27,6 +27,7 @@ import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED import static com.android.settings.display.SmartAutoRotatePreferenceFragment.AUTO_ROTATE_MAIN_SWITCH_PREFERENCE_KEY; import static com.android.settings.display.SmartAutoRotatePreferenceFragment.AUTO_ROTATE_SWITCH_PREFERENCE_KEY; +import static com.android.settings.testutils.DeviceStateAutoRotateSettingTestUtils.setDeviceStateRotationLockEnabled; import static com.google.common.truth.Truth.assertThat; @@ -55,7 +56,6 @@ import androidx.preference.Preference; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.testutils.ResolveInfoBuilder; -import com.android.settings.testutils.shadow.ShadowDeviceStateRotationLockSettingsManager; import com.android.settings.testutils.shadow.ShadowRotationPolicy; import com.android.settingslib.core.AbstractPreferenceController; @@ -74,7 +74,6 @@ import java.util.Set; @RunWith(RobolectricTestRunner.class) @Config(shadows = { com.android.settings.testutils.shadow.ShadowFragment.class, - ShadowDeviceStateRotationLockSettingsManager.class, ShadowRotationPolicy.class }) public class SmartAutoRotatePreferenceFragmentTest { @@ -173,7 +172,7 @@ public class SmartAutoRotatePreferenceFragmentTest { @Test public void createHeader_faceDetectionSupported_switchBarIsEnabled() { - ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(false); + setDeviceStateRotationLockEnabled(false, mResources); mFragment.createHeader(mActivity); verify(mRotateMainSwitchPreference, never()).setVisible(false); @@ -183,7 +182,7 @@ public class SmartAutoRotatePreferenceFragmentTest { @Test public void createHeader_deviceStateRotationSupported_switchBarIsDisabled() { ShadowRotationPolicy.setRotationSupported(true); - ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(true); + setDeviceStateRotationLockEnabled(true, mResources); mFragment.createHeader(mActivity); diff --git a/tests/robotests/src/com/android/settings/testutils/DeviceStateAutoRotateSettingTestUtils.java b/tests/robotests/src/com/android/settings/testutils/DeviceStateAutoRotateSettingTestUtils.java new file mode 100644 index 00000000000..3359b2f9dc5 --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/DeviceStateAutoRotateSettingTestUtils.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2025 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.settings.testutils; + +import static org.mockito.Mockito.when; + +import android.content.res.Resources; + +/** + * Helper for testing device state auto rotate setting + */ +public class DeviceStateAutoRotateSettingTestUtils { + + /** + * Mock {@link mockResources} to return device state auto rotate enabled or disabled based on + * value passed for {@link enable}. + */ + public static void setDeviceStateRotationLockEnabled(boolean enable, Resources mockResources) { + String[] perDeviceStateRotationLockDefaults = new String[0]; + if (enable) { + perDeviceStateRotationLockDefaults = new String[]{"test_value"}; + } + when(mockResources.getStringArray( + com.android.internal.R.array.config_perDeviceStateRotationLockDefaults)) + .thenReturn(perDeviceStateRotationLockDefaults); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDeviceStateRotationLockSettingsManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDeviceStateAutoRotateSettingManager.java similarity index 73% rename from tests/robotests/src/com/android/settings/testutils/shadow/ShadowDeviceStateRotationLockSettingsManager.java rename to tests/robotests/src/com/android/settings/testutils/shadow/ShadowDeviceStateAutoRotateSettingManager.java index ed266e3b23e..b44d79e3acc 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDeviceStateRotationLockSettingsManager.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDeviceStateAutoRotateSettingManager.java @@ -16,28 +16,16 @@ package com.android.settings.testutils.shadow; -import android.content.Context; - import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; @Implements(DeviceStateRotationLockSettingsManager.class) -public class ShadowDeviceStateRotationLockSettingsManager { +public class ShadowDeviceStateAutoRotateSettingManager { - private static boolean sDeviceStateRotationLockEnabled; private boolean mIsRotationLockedForAllStates; - @Implementation - public static boolean isDeviceStateRotationLockEnabled(Context context) { - return sDeviceStateRotationLockEnabled; - } - - public static void setDeviceStateRotationLockEnabled(boolean enabled) { - sDeviceStateRotationLockEnabled = enabled; - } - @Implementation public boolean isRotationLockedForAllStates() { return mIsRotationLockedForAllStates; From a333827d6dce2cfa1896f7914dbdad4a9e5208b9 Mon Sep 17 00:00:00 2001 From: Haijie Hong Date: Tue, 18 Mar 2025 11:25:33 +0800 Subject: [PATCH 11/11] Move enable/disable LE Audio to Utils Bug: 381353150 Test: atest UtilsTest Flag: EXEMPT minor refactor Change-Id: Id017f2b5a0f51d19878bf91b219334baeda97d6d --- .../BluetoothDetailsProfilesController.java | 97 +--------- src/com/android/settings/bluetooth/Utils.java | 122 ++++++++++++ .../android/settings/bluetooth/UtilsTest.java | 176 ++++++++++++++++-- .../shadow/ShadowBluetoothUtils.java | 24 +++ 4 files changed, 313 insertions(+), 106 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java index 58d278704b2..d7f53ca4cb5 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java @@ -419,32 +419,7 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll mContext, SettingsEnums.ACTION_BLUETOOTH_PROFILE_LE_AUDIO_OFF, isCurrentDeviceInOrByPassAllowList()); - - LocalBluetoothProfile asha = mProfileManager.getHearingAidProfile(); - LocalBluetoothProfile broadcastAssistant = - mProfileManager.getLeAudioBroadcastAssistantProfile(); - - for (CachedBluetoothDevice leAudioDevice : mProfileDeviceMap.get(profile.toString())) { - Log.d(TAG, - "device:" + leAudioDevice.getDevice().getAnonymizedAddress() - + " disable LE profile"); - profile.setEnabled(leAudioDevice.getDevice(), false); - if (asha != null) { - asha.setEnabled(leAudioDevice.getDevice(), true); - } - if (broadcastAssistant != null) { - Log.d(TAG, - "device:" + leAudioDevice.getDevice().getAnonymizedAddress() - + " disable LE broadcast assistant profile"); - broadcastAssistant.setEnabled(leAudioDevice.getDevice(), false); - } - } - - if (!SystemProperties.getBoolean(ENABLE_DUAL_MODE_AUDIO, false)) { - Log.i(TAG, "Enabling classic audio profiles because dual mode is disabled"); - enableProfileAfterUserDisablesLeAudio(mProfileManager.getA2dpProfile()); - enableProfileAfterUserDisablesLeAudio(mProfileManager.getHeadsetProfile()); - } + Utils.setLeAudioEnabled(mManager, List.copyOf(mCachedDeviceGroup), false); } /** @@ -462,75 +437,7 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll mContext, SettingsEnums.ACTION_BLUETOOTH_PROFILE_LE_AUDIO_ON, isCurrentDeviceInOrByPassAllowList()); - - if (!SystemProperties.getBoolean(ENABLE_DUAL_MODE_AUDIO, false)) { - Log.i(TAG, "Disabling classic audio profiles because dual mode is disabled"); - disableProfileBeforeUserEnablesLeAudio(mProfileManager.getA2dpProfile()); - disableProfileBeforeUserEnablesLeAudio(mProfileManager.getHeadsetProfile()); - } - LocalBluetoothProfile asha = mProfileManager.getHearingAidProfile(); - LocalBluetoothProfile broadcastAssistant = - mProfileManager.getLeAudioBroadcastAssistantProfile(); - - for (CachedBluetoothDevice leAudioDevice : mProfileDeviceMap.get(profile.toString())) { - Log.d(TAG, - "device:" + leAudioDevice.getDevice().getAnonymizedAddress() - + " enable LE profile"); - profile.setEnabled(leAudioDevice.getDevice(), true); - if (asha != null) { - asha.setEnabled(leAudioDevice.getDevice(), false); - } - if (broadcastAssistant != null) { - Log.d(TAG, - "device:" + leAudioDevice.getDevice().getAnonymizedAddress() - + " enable LE broadcast assistant profile"); - broadcastAssistant.setEnabled(leAudioDevice.getDevice(), true); - } - } - } - - private void disableProfileBeforeUserEnablesLeAudio(LocalBluetoothProfile profile) { - if (profile != null && mProfileDeviceMap.get(profile.toString()) != null) { - Log.d(TAG, "Disable " + profile.toString() + " before user enables LE"); - for (CachedBluetoothDevice profileDevice : mProfileDeviceMap.get(profile.toString())) { - if (profile.isEnabled(profileDevice.getDevice())) { - Log.d(TAG, "The " + profileDevice.getDevice().getAnonymizedAddress() + ":" - + profile.toString() + " set disable"); - profile.setEnabled(profileDevice.getDevice(), false); - } else { - Log.d(TAG, "The " + profileDevice.getDevice().getAnonymizedAddress() + ":" - + profile.toString() + " profile is disabled. Do nothing."); - } - } - } else { - if (profile == null) { - Log.w(TAG, "profile is null"); - } else { - Log.w(TAG, profile.toString() + " is not in " + mProfileDeviceMap); - } - } - } - - private void enableProfileAfterUserDisablesLeAudio(LocalBluetoothProfile profile) { - if (profile != null && mProfileDeviceMap.get(profile.toString()) != null) { - Log.d(TAG, "enable " + profile.toString() + "after user disables LE"); - for (CachedBluetoothDevice profileDevice : mProfileDeviceMap.get(profile.toString())) { - if (!profile.isEnabled(profileDevice.getDevice())) { - Log.d(TAG, "The " + profileDevice.getDevice().getAnonymizedAddress() + ":" - + profile.toString() + " set enable"); - profile.setEnabled(profileDevice.getDevice(), true); - } else { - Log.d(TAG, "The " + profileDevice.getDevice().getAnonymizedAddress() + ":" - + profile.toString() + " profile is enabled. Do nothing."); - } - } - } else { - if (profile == null) { - Log.w(TAG, "profile is null"); - } else { - Log.w(TAG, profile.toString() + " is not in " + mProfileDeviceMap); - } - } + Utils.setLeAudioEnabled(mManager, List.copyOf(mCachedDeviceGroup), true); } /** diff --git a/src/com/android/settings/bluetooth/Utils.java b/src/com/android/settings/bluetooth/Utils.java index 9f4bb132921..76c3ed0c73b 100644 --- a/src/com/android/settings/bluetooth/Utils.java +++ b/src/com/android/settings/bluetooth/Utils.java @@ -28,6 +28,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; @@ -45,15 +46,20 @@ import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.BluetoothUtils.ErrorListener; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; +import com.android.settingslib.bluetooth.HearingAidProfile; +import com.android.settingslib.bluetooth.LeAudioProfile; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager.BluetoothManagerCallback; +import com.android.settingslib.bluetooth.LocalBluetoothProfile; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.utils.ThreadUtils; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -70,6 +76,7 @@ import java.util.stream.Collectors; public final class Utils { private static final String TAG = "BluetoothUtils"; + private static final String ENABLE_DUAL_MODE_AUDIO = "persist.bluetooth.enable_dual_mode_audio"; static final boolean V = BluetoothUtils.V; // verbose logging static final boolean D = BluetoothUtils.D; // regular logging @@ -360,4 +367,119 @@ public final class Utils { dialog.show(); return dialog; } + + /** Enables/disables LE Audio profile for the device. */ + public static void setLeAudioEnabled( + @NonNull LocalBluetoothManager manager, + @NonNull CachedBluetoothDevice cachedDevice, + boolean enable) { + List devices = + List.copyOf(findAllCachedBluetoothDevicesByGroupId(manager, cachedDevice)); + setLeAudioEnabled(manager, devices, enable); + } + + /** Enables/disables LE Audio profile for the devices in the same csip group. */ + public static void setLeAudioEnabled( + @NonNull LocalBluetoothManager manager, + @NonNull List devicesWithSameGroupId, + boolean enable) { + LocalBluetoothProfileManager profileManager = manager.getProfileManager(); + LeAudioProfile leAudioProfile = profileManager.getLeAudioProfile(); + List leAudioDevices = + getDevicesWithProfile(devicesWithSameGroupId, leAudioProfile); + if (leAudioDevices.isEmpty()) { + Log.i(TAG, "Fail to setLeAudioEnabled, no LE Audio profile found."); + } + boolean dualModeEnabled = SystemProperties.getBoolean(ENABLE_DUAL_MODE_AUDIO, false); + + if (enable && !dualModeEnabled) { + Log.i(TAG, "Disabling classic audio profiles because dual mode is disabled"); + setProfileEnabledWhenChangingLeAudio( + devicesWithSameGroupId, profileManager.getA2dpProfile(), false); + setProfileEnabledWhenChangingLeAudio( + devicesWithSameGroupId, profileManager.getHeadsetProfile(), false); + } + + HearingAidProfile asha = profileManager.getHearingAidProfile(); + LocalBluetoothLeBroadcastAssistant broadcastAssistant = + profileManager.getLeAudioBroadcastAssistantProfile(); + + for (CachedBluetoothDevice leAudioDevice : leAudioDevices) { + Log.d( + TAG, + "device:" + + leAudioDevice.getDevice().getAnonymizedAddress() + + " set LE profile enabled: " + + enable); + leAudioProfile.setEnabled(leAudioDevice.getDevice(), enable); + if (asha != null) { + asha.setEnabled(leAudioDevice.getDevice(), !enable); + } + if (broadcastAssistant != null) { + Log.d( + TAG, + "device:" + + leAudioDevice.getDevice().getAnonymizedAddress() + + " enable LE broadcast assistant profile: " + + enable); + broadcastAssistant.setEnabled(leAudioDevice.getDevice(), enable); + } + } + + if (!enable && !dualModeEnabled) { + Log.i(TAG, "Enabling classic audio profiles because dual mode is disabled"); + setProfileEnabledWhenChangingLeAudio( + devicesWithSameGroupId, profileManager.getA2dpProfile(), true); + setProfileEnabledWhenChangingLeAudio( + devicesWithSameGroupId, profileManager.getHeadsetProfile(), true); + } + } + + private static List getDevicesWithProfile( + List devices, LocalBluetoothProfile profile) { + List devicesWithProfile = new ArrayList<>(); + for (CachedBluetoothDevice device : devices) { + for (LocalBluetoothProfile currentProfile : device.getProfiles()) { + if (currentProfile.toString().equals(profile.toString())) { + devicesWithProfile.add(device); + } + } + } + return devicesWithProfile; + } + + private static void setProfileEnabledWhenChangingLeAudio( + List devices, + @Nullable LocalBluetoothProfile profile, + boolean enable) { + if (profile == null) { + Log.i(TAG, "profile is null"); + return; + } + List deviceWithProfile = getDevicesWithProfile(devices, profile); + Log.d(TAG, "Set " + profile + " enabled:" + enable + " when switching LE Audio"); + for (CachedBluetoothDevice profileDevice : deviceWithProfile) { + if (profile.isEnabled(profileDevice.getDevice()) != enable) { + Log.d( + TAG, + "The " + + profileDevice.getDevice().getAnonymizedAddress() + + ":" + + profile + + " set to " + + enable); + profile.setEnabled(profileDevice.getDevice(), enable); + } else { + Log.d( + TAG, + "The " + + profileDevice.getDevice().getAnonymizedAddress() + + ":" + + profile + + " profile is already " + + enable + + ". Do nothing."); + } + } + } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java b/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java index 8859ebd97e5..176bfa8f573 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java @@ -18,22 +18,29 @@ package com.android.settings.bluetooth; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.content.Context; +import android.os.SystemProperties; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.testutils.FakeFeatureFactory; -import com.android.settings.testutils.shadow.ShadowBluetoothUtils; +import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; +import com.android.settingslib.bluetooth.HeadsetProfile; +import com.android.settingslib.bluetooth.HearingAidProfile; +import com.android.settingslib.bluetooth.LeAudioProfile; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -41,8 +48,8 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; -import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -52,10 +59,8 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowBluetoothUtils.class}) public class UtilsTest { private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25; private static final String TEMP_BOND_METADATA = @@ -73,6 +78,14 @@ public class UtilsTest { @Mock private LocalBluetoothLeBroadcastAssistant mAssistant; @Mock + private A2dpProfile mA2dpProfile; + @Mock + private HeadsetProfile mHeadsetProfile; + @Mock + private LeAudioProfile mLeAudioProfile; + @Mock + private HearingAidProfile mHearingAidProfile; + @Mock private CachedBluetoothDeviceManager mDeviceManager; private MetricsFeatureProvider mMetricsFeatureProvider; @@ -80,17 +93,14 @@ public class UtilsTest { @Before public void setUp() { mMetricsFeatureProvider = FakeFeatureFactory.setupForTest().getMetricsFeatureProvider(); - ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager; - mLocalBtManager = Utils.getLocalBtManager(mContext); when(mLocalBtManager.getProfileManager()).thenReturn(mProfileManager); when(mLocalBtManager.getCachedDeviceManager()).thenReturn(mDeviceManager); when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast); when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant); - } - - @After - public void tearDown() { - ShadowBluetoothUtils.reset(); + when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); + when(mProfileManager.getHeadsetProfile()).thenReturn(mHeadsetProfile); + when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile); + when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile); } @Test @@ -170,4 +180,148 @@ public class UtilsTest { when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(state)); assertThat(Utils.shouldBlockPairingInAudioSharing(mLocalBtManager)).isTrue(); } + + @Test + public void enableLeAudioProfile_multipleDeviceInGroup() { + CachedBluetoothDevice cachedDevice1 = mock(CachedBluetoothDevice.class); + CachedBluetoothDevice cachedDevice2 = mock(CachedBluetoothDevice.class); + CachedBluetoothDevice cachedDevice3 = mock(CachedBluetoothDevice.class); + BluetoothDevice device1 = mock(BluetoothDevice.class); + BluetoothDevice device2 = mock(BluetoothDevice.class); + BluetoothDevice device3 = mock(BluetoothDevice.class); + when(cachedDevice1.getDevice()).thenReturn(device1); + when(cachedDevice2.getDevice()).thenReturn(device2); + when(cachedDevice3.getDevice()).thenReturn(device3); + when(cachedDevice1.getMemberDevice()).thenReturn(ImmutableSet.of(cachedDevice2)); + when(mDeviceManager.getCachedDevicesCopy()) + .thenReturn(ImmutableList.of(cachedDevice1, cachedDevice3)); + when(cachedDevice1.getGroupId()).thenReturn(1); + when(cachedDevice2.getGroupId()).thenReturn(1); + when(cachedDevice3.getGroupId()).thenReturn(2); + when(cachedDevice1.getProfiles()) + .thenReturn(ImmutableList.of(mA2dpProfile, mHeadsetProfile, mLeAudioProfile)); + when(cachedDevice2.getProfiles()).thenReturn(ImmutableList.of(mLeAudioProfile)); + when(cachedDevice3.getProfiles()) + .thenReturn(ImmutableList.of(mA2dpProfile, mHeadsetProfile, mLeAudioProfile)); + + Utils.setLeAudioEnabled(mLocalBtManager, cachedDevice2, true); + + verify(mLeAudioProfile).setEnabled(device1, true); + verify(mLeAudioProfile).setEnabled(device2, true); + verify(mHearingAidProfile).setEnabled(device1, false); + verify(mAssistant).setEnabled(device1, true); + verify(mLeAudioProfile, never()).setEnabled(eq(device3), anyBoolean()); + verify(mA2dpProfile, never()).setEnabled(eq(device3), anyBoolean()); + verify(mHeadsetProfile, never()).setEnabled(eq(device3), anyBoolean()); + } + + @Test + public void enableLeAudioProfile_dualModeEnabled_a2dpAndHfpNotChanged() { + SystemProperties.set("persist.bluetooth.enable_dual_mode_audio", "true"); + CachedBluetoothDevice cachedDevice1 = mock(CachedBluetoothDevice.class); + BluetoothDevice device1 = mock(BluetoothDevice.class); + when(cachedDevice1.getDevice()).thenReturn(device1); + when(cachedDevice1.getGroupId()).thenReturn(BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + when(cachedDevice1.getProfiles()) + .thenReturn(ImmutableList.of(mA2dpProfile, mHeadsetProfile, mLeAudioProfile)); + when(mA2dpProfile.isEnabled(device1)).thenReturn(true); + when(mHeadsetProfile.isEnabled(device1)).thenReturn(true); + + Utils.setLeAudioEnabled(mLocalBtManager, cachedDevice1, true); + + verify(mLeAudioProfile).setEnabled(device1, true); + verify(mA2dpProfile, never()).setEnabled(device1, false); + verify(mHeadsetProfile, never()).setEnabled(device1, false); + } + + @Test + public void enableLeAudioProfile_dualModeDisabled_disableA2dpAndHfp() { + SystemProperties.set("persist.bluetooth.enable_dual_mode_audio", "false"); + CachedBluetoothDevice cachedDevice1 = mock(CachedBluetoothDevice.class); + BluetoothDevice device1 = mock(BluetoothDevice.class); + when(cachedDevice1.getDevice()).thenReturn(device1); + when(cachedDevice1.getGroupId()).thenReturn(BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + when(cachedDevice1.getProfiles()) + .thenReturn(ImmutableList.of(mA2dpProfile, mHeadsetProfile, mLeAudioProfile)); + when(mA2dpProfile.isEnabled(device1)).thenReturn(true); + when(mHeadsetProfile.isEnabled(device1)).thenReturn(true); + + Utils.setLeAudioEnabled(mLocalBtManager, cachedDevice1, true); + + verify(mLeAudioProfile).setEnabled(device1, true); + verify(mA2dpProfile).setEnabled(device1, false); + verify(mHeadsetProfile).setEnabled(device1, false); + } + + @Test + public void disableLeAudioProfile_multipleDeviceInGroup() { + CachedBluetoothDevice cachedDevice1 = mock(CachedBluetoothDevice.class); + CachedBluetoothDevice cachedDevice2 = mock(CachedBluetoothDevice.class); + CachedBluetoothDevice cachedDevice3 = mock(CachedBluetoothDevice.class); + BluetoothDevice device1 = mock(BluetoothDevice.class); + BluetoothDevice device2 = mock(BluetoothDevice.class); + BluetoothDevice device3 = mock(BluetoothDevice.class); + when(cachedDevice1.getDevice()).thenReturn(device1); + when(cachedDevice2.getDevice()).thenReturn(device2); + when(cachedDevice3.getDevice()).thenReturn(device3); + when(cachedDevice1.getMemberDevice()).thenReturn(ImmutableSet.of(cachedDevice2)); + when(mDeviceManager.getCachedDevicesCopy()) + .thenReturn(ImmutableList.of(cachedDevice1, cachedDevice3)); + when(cachedDevice1.getGroupId()).thenReturn(1); + when(cachedDevice2.getGroupId()).thenReturn(1); + when(cachedDevice3.getGroupId()).thenReturn(2); + when(cachedDevice1.getProfiles()) + .thenReturn(ImmutableList.of(mA2dpProfile, mHeadsetProfile, mLeAudioProfile)); + when(cachedDevice2.getProfiles()).thenReturn(ImmutableList.of(mLeAudioProfile)); + when(cachedDevice3.getProfiles()) + .thenReturn(ImmutableList.of(mA2dpProfile, mHeadsetProfile, mLeAudioProfile)); + + Utils.setLeAudioEnabled(mLocalBtManager, cachedDevice2, false); + + verify(mLeAudioProfile).setEnabled(device1, false); + verify(mLeAudioProfile).setEnabled(device2, false); + verify(mHearingAidProfile).setEnabled(device1, true); + verify(mAssistant).setEnabled(device1, false); + verify(mLeAudioProfile, never()).setEnabled(eq(device3), anyBoolean()); + verify(mA2dpProfile, never()).setEnabled(eq(device3), anyBoolean()); + verify(mHeadsetProfile, never()).setEnabled(eq(device3), anyBoolean()); + } + + @Test + public void disableLeAudioProfile_dualModeEnabled_a2dpAndHfpNotChanged() { + SystemProperties.set("persist.bluetooth.enable_dual_mode_audio", "true"); + CachedBluetoothDevice cachedDevice1 = mock(CachedBluetoothDevice.class); + BluetoothDevice device1 = mock(BluetoothDevice.class); + when(cachedDevice1.getDevice()).thenReturn(device1); + when(cachedDevice1.getGroupId()).thenReturn(BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + when(cachedDevice1.getProfiles()) + .thenReturn(ImmutableList.of(mA2dpProfile, mHeadsetProfile, mLeAudioProfile)); + when(mA2dpProfile.isEnabled(device1)).thenReturn(false); + when(mHeadsetProfile.isEnabled(device1)).thenReturn(false); + + Utils.setLeAudioEnabled(mLocalBtManager, cachedDevice1, false); + + verify(mLeAudioProfile).setEnabled(device1, false); + verify(mA2dpProfile, never()).setEnabled(device1, true); + verify(mHeadsetProfile, never()).setEnabled(device1, true); + } + + @Test + public void disableLeAudioProfile_dualModeDisabled_enableA2dpAndHfp() { + SystemProperties.set("persist.bluetooth.enable_dual_mode_audio", "false"); + CachedBluetoothDevice cachedDevice1 = mock(CachedBluetoothDevice.class); + BluetoothDevice device1 = mock(BluetoothDevice.class); + when(cachedDevice1.getDevice()).thenReturn(device1); + when(cachedDevice1.getGroupId()).thenReturn(BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + when(cachedDevice1.getProfiles()) + .thenReturn(ImmutableList.of(mA2dpProfile, mHeadsetProfile, mLeAudioProfile)); + when(mA2dpProfile.isEnabled(device1)).thenReturn(false); + when(mHeadsetProfile.isEnabled(device1)).thenReturn(false); + + Utils.setLeAudioEnabled(mLocalBtManager, cachedDevice1, false); + + verify(mLeAudioProfile).setEnabled(device1, false); + verify(mA2dpProfile).setEnabled(device1, true); + verify(mHeadsetProfile).setEnabled(device1, true); + } } diff --git a/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowBluetoothUtils.java b/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowBluetoothUtils.java index 4dca7490e1a..72de74672f6 100644 --- a/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowBluetoothUtils.java +++ b/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowBluetoothUtils.java @@ -18,24 +18,48 @@ package com.android.settings.testutils.shadow; import android.content.Context; +import androidx.annotation.NonNull; + import com.android.settings.bluetooth.Utils; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; +import java.util.HashMap; +import java.util.Map; + /** Robolectric shadow for the bluetooth utils. */ @Implements(Utils.class) public class ShadowBluetoothUtils { public static LocalBluetoothManager sLocalBluetoothManager; + private static final Map sLeAudioState = new HashMap<>(); @Implementation protected static LocalBluetoothManager getLocalBtManager(Context context) { return sLocalBluetoothManager; } + /** Sets le audio state for the device. */ + @Implementation + public static void setLeAudioEnabled( + @NonNull LocalBluetoothManager manager, + @NonNull CachedBluetoothDevice cachedDevice, + boolean enable) { + sLeAudioState.put(cachedDevice, enable); + } + + /** Checks whether le audio is enabled for the device. */ + public static boolean isLeAudioEnabled(@NonNull CachedBluetoothDevice cachedDevice) { + if (sLeAudioState.containsKey(cachedDevice)) { + return sLeAudioState.get(cachedDevice); + } + return false; + } + /** Resets the local bluetooth manager to null. */ @Resetter public static void reset() {