From 5e2adc2683212d60930d7f0442b4ad3129995129 Mon Sep 17 00:00:00 2001 From: Joshua McCloskey Date: Wed, 23 Oct 2024 17:34:16 +0000 Subject: [PATCH 01/13] Added interupt to accessibility interactor Test: Verified manually Bug: 366219635 Flag: EXEMPT bugfix Change-Id: If1dea7b3efd23e40c6c2668867b175ba84f11550 --- .../interactor/AccessibilityInteractor.kt | 35 ++++++++++++++----- .../biometrics/fingerprint/Injector.kt | 1 + ...gerprintEnrollFindSensorViewModelV2Test.kt | 2 ++ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/AccessibilityInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/AccessibilityInteractor.kt index 9f62ed03572..bf0084d14a8 100644 --- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/AccessibilityInteractor.kt +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/AccessibilityInteractor.kt @@ -16,6 +16,7 @@ package com.android.settings.biometrics.fingerprint2.domain.interactor +import android.util.Log import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent.TYPE_ANNOUNCEMENT import android.view.accessibility.AccessibilityManager @@ -30,23 +31,26 @@ import kotlinx.coroutines.flow.stateIn interface AccessibilityInteractor { /** A flow that contains whether or not accessibility is enabled */ fun isEnabledFlow(scope: CoroutineScope): Flow + val isEnabled: Boolean + fun announce(clazz: Class<*>, announcement: CharSequence?) + + fun interrupt() } -class AccessibilityInteractorImpl( - private val accessibilityManager: AccessibilityManager, -) : AccessibilityInteractor { +class AccessibilityInteractorImpl(private val accessibilityManager: AccessibilityManager) : + AccessibilityInteractor { /** A flow that contains whether or not accessibility is enabled */ override fun isEnabledFlow(scope: CoroutineScope): Flow = callbackFlow { - val listener = - AccessibilityManager.AccessibilityStateChangeListener { enabled -> trySend(enabled) } - accessibilityManager.addAccessibilityStateChangeListener(listener) + val listener = + AccessibilityManager.AccessibilityStateChangeListener { enabled -> trySend(enabled) } + accessibilityManager.addAccessibilityStateChangeListener(listener) - // This clause will be called when no one is listening to the flow - awaitClose { accessibilityManager.removeAccessibilityStateChangeListener(listener) } - } + // This clause will be called when no one is listening to the flow + awaitClose { accessibilityManager.removeAccessibilityStateChangeListener(listener) } + } .stateIn( scope, SharingStarted.WhileSubscribed(), // When no longer subscribed, we removeTheListener @@ -63,4 +67,17 @@ class AccessibilityInteractorImpl( event.text.add(announcement) accessibilityManager.sendAccessibilityEvent(event) } + + /** Interrupts the current accessibility manager from announcing a phrase. */ + override fun interrupt() { + try { + accessibilityManager.interrupt() + } catch (e: IllegalStateException) { + Log.e(TAG, "Error trying to interrupt when accessibility isn't enabled $e") + } + } + + companion object { + const val TAG = "AccessibilityInteractor" + } } diff --git a/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt b/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt index a5d0461e4a3..84e3b8533b2 100644 --- a/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt +++ b/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt @@ -78,6 +78,7 @@ class Injector(step: FingerprintNavigationStep.UiStep) { override val isEnabled: Boolean get() = true override fun announce(clazz: Class<*>, announcement: CharSequence?) {} + override fun interrupt() {} } var foldStateInteractor = diff --git a/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt b/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt index a8c5e684d33..ca802087582 100644 --- a/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt +++ b/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt @@ -111,6 +111,8 @@ class FingerprintEnrollFindSensorViewModelV2Test { override val isEnabled: Boolean get() = true override fun announce(clazz: Class<*>, announcement: CharSequence?) {} + override fun interrupt() { + } } foldStateInteractor = object : FoldStateInteractor { From 547dd4ebd84045d385b5e5b8d5f61fd58be28754 Mon Sep 17 00:00:00 2001 From: Joshua McCloskey Date: Thu, 31 Oct 2024 21:26:57 +0000 Subject: [PATCH 02/13] Updated Orientation interactor Orientation interactor should now immediately give the result of the orientation. Test: Verified manually that the orientation is populated when the flow is collected. Flag: EXEMPT bugfix Fixes: 376558183 Change-Id: I3726df57f50ad835f4b0f0768664cab2e7e64326 --- .../interactor/OrientationInteractor.kt | 31 +++++++++++++++++- .../fingerprint2/lib/model/Orientation.kt | 32 +++++++++++++++++++ .../biometrics/fingerprint/Injector.kt | 2 ++ ...gerprintEnrollFindSensorViewModelV2Test.kt | 2 ++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 src/com/android/settings/biometrics/fingerprint2/lib/model/Orientation.kt diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt index e55d6b899ca..e273bb76c8f 100644 --- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt @@ -19,10 +19,13 @@ package com.android.settings.biometrics.fingerprint2.domain.interactor import android.content.Context import android.view.OrientationEventListener import com.android.internal.R +import com.android.settings.biometrics.fingerprint2.lib.model.Orientation import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.transform /** Interactor which provides information about orientation */ @@ -45,6 +48,9 @@ interface OrientationInteractor { * [R.bool.config_reverseDefaultConfigRotation] */ fun getRotationFromDefault(rotation: Int): Int + + /** Indicates an orientation changed event has occurred */ + val orientationChanged: Flow } class OrientationInteractorImpl(private val context: Context) : OrientationInteractor { @@ -60,7 +66,10 @@ class OrientationInteractorImpl(private val context: Context) : OrientationInter awaitClose { orientationEventListener.disable() } } - override val rotation: Flow = orientation.transform { emit(context.display.rotation) } + override val rotation: Flow = + orientation + .transform { emit(context.display.rotation) } + .onStart { emit(context.display.rotation) } override val rotationFromDefault: Flow = rotation.map { getRotationFromDefault(it) } @@ -73,4 +82,24 @@ class OrientationInteractorImpl(private val context: Context) : OrientationInter rotation } } + + override val orientationChanged: Flow = + rotationFromDefault + .map { + when (it) { + 1 -> { + Orientation.Portrait + } + 2 -> { + Orientation.ReverseLandscape + } + 3 -> { + Orientation.UpsideDownPortrait + } + else -> { + Orientation.Landscape + } + } + } + .distinctUntilChanged() } diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/model/Orientation.kt b/src/com/android/settings/biometrics/fingerprint2/lib/model/Orientation.kt new file mode 100644 index 00000000000..c88067772b8 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/lib/model/Orientation.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.biometrics.fingerprint2.lib.model + +/** The orientation events correspond to androids internal orientation events. */ +sealed class Orientation { + /** Indicates the device is in landscape orientation */ + data object Landscape : Orientation() + + /** Indicates the device is in reverse landscape orientation */ + data object ReverseLandscape : Orientation() + + /** Indicates the device is in portrait orientation */ + data object Portrait : Orientation() + + /** Indicates the device is in the upside down portrait orientation */ + data object UpsideDownPortrait : Orientation() +} diff --git a/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt b/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt index 84e3b8533b2..2f68521f3a1 100644 --- a/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt +++ b/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt @@ -29,6 +29,7 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.Accessibil import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor import com.android.settings.biometrics.fingerprint2.lib.model.Default +import com.android.settings.biometrics.fingerprint2.lib.model.Orientation import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSIconTouchViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel @@ -98,6 +99,7 @@ class Injector(step: FingerprintNavigationStep.UiStep) { override val rotationFromDefault: Flow = rotation override fun getRotationFromDefault(rotation: Int): Int = rotation + override val orientationChanged: Flow = flowOf(Orientation.Portrait) } var gatekeeperViewModel = FingerprintGatekeeperViewModel(fingerprintManagerInteractor) diff --git a/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt b/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt index ca802087582..70e6de10a07 100644 --- a/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt +++ b/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt @@ -30,6 +30,7 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.Accessibil import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor import com.android.settings.biometrics.fingerprint2.lib.model.Default +import com.android.settings.biometrics.fingerprint2.lib.model.Orientation import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel @@ -130,6 +131,7 @@ class FingerprintEnrollFindSensorViewModelV2Test { override val rotationFromDefault: Flow = flowOf(Surface.ROTATION_0) override fun getRotationFromDefault(rotation: Int): Int = rotation + override val orientationChanged: Flow = flowOf(Orientation.Portrait) } underTest = FingerprintEnrollFindSensorViewModel( From 7b73897b2621036e00c3c52a3cad386809b3e5ac Mon Sep 17 00:00:00 2001 From: Xiaohui Chen Date: Fri, 1 Nov 2024 16:15:55 -0700 Subject: [PATCH 03/13] settings: fix roboletric test in MainClearTest MainClearTest#testShowWipeEuicc_developerMode_unprovisioned Bug: 313566998 Test: run atest SettingsRoboTests:com.android.settings.MainClearTest#testShowWipeEuicc_developerMode_unprovisioned Flag: TEST_ONLY Change-Id: Icdd3b251f03b10b8ba11338a955216ba35de41b5 --- tests/robotests/src/com/android/settings/MainClearTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/robotests/src/com/android/settings/MainClearTest.java b/tests/robotests/src/com/android/settings/MainClearTest.java index 4c2b2669dcf..c938e5f17e1 100644 --- a/tests/robotests/src/com/android/settings/MainClearTest.java +++ b/tests/robotests/src/com/android/settings/MainClearTest.java @@ -66,7 +66,6 @@ import com.android.settingslib.development.DevelopmentSettingsEnabler; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -263,7 +262,6 @@ public class MainClearTest { assertThat(mMainClear.showWipeEuicc()).isTrue(); } - @Ignore("b/313566998") @Test public void testShowWipeEuicc_developerMode_unprovisioned() { prepareEuiccState( From 976850dbdd82345408d614c87e5d11f3e7e85ec6 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Fri, 1 Nov 2024 16:30:45 -0400 Subject: [PATCH 04/13] Add skeleton page for bundling Test: BundleTypePreferenceControllerTest Test: BundleGlobalPreferenceControllerTest Test: BundlePreferenceControllerTest Fixes: 376476949 Flag: android.app.notification_classification_ui Change-Id: I6fa7ddfeb5ee6d2033dee4b57b0cc0e76bb347f6 --- res/values/strings.xml | 7 + res/xml/bundle_notifications_settings.xml | 59 ++++++ res/xml/configure_notification_settings.xml | 8 + .../BundleGlobalPreferenceController.java | 61 +++++++ .../BundlePreferenceController.java | 49 +++++ .../BundlePreferenceFragment.java | 60 ++++++ .../BundleTypePreferenceController.java | 83 +++++++++ .../notification/NotificationBackend.java | 56 ++++++ .../BundleGlobalPreferenceControllerTest.java | 101 +++++++++++ .../BundlePreferenceControllerTest.java | 91 ++++++++++ .../BundleTypePreferenceControllerTest.java | 171 ++++++++++++++++++ 11 files changed, 746 insertions(+) create mode 100644 res/xml/bundle_notifications_settings.xml create mode 100644 src/com/android/settings/notification/BundleGlobalPreferenceController.java create mode 100644 src/com/android/settings/notification/BundlePreferenceController.java create mode 100644 src/com/android/settings/notification/BundlePreferenceFragment.java create mode 100644 src/com/android/settings/notification/BundleTypePreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/notification/BundleGlobalPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/notification/BundlePreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/notification/BundleTypePreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index a58702a0a2b..6c5e7dee292 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -9029,6 +9029,13 @@ Apply to work profiles Apply to work profile apps + + Bundled notifications + On + Off + Use notification bundling + Notifications with similar themes will be silenced and grouped together for a quieter experience. Bundling will override an app\'s own notification settings. + VR helper services diff --git a/res/xml/bundle_notifications_settings.xml b/res/xml/bundle_notifications_settings.xml new file mode 100644 index 00000000000..34efd7dd49b --- /dev/null +++ b/res/xml/bundle_notifications_settings.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/configure_notification_settings.xml b/res/xml/configure_notification_settings.xml index c88b7bace8a..aedf0b413aa 100644 --- a/res/xml/configure_notification_settings.xml +++ b/res/xml/configure_notification_settings.xml @@ -43,6 +43,14 @@ android:targetPackage="com.android.settings" android:targetClass="com.android.settings.notification.history.NotificationHistoryActivity" /> + + Date: Tue, 5 Nov 2024 23:25:02 +0000 Subject: [PATCH 05/13] Few update on contacts storage settings. 1. Add account preference category to contacts storage settings page 2. Preload account icon in settings constructor 3. Re-fetch account in controller to refresh the default account after user sets a different default account. Test: atest SettingsRoboTests:com.android.settings.applications.contacts.ContactsStorageSettingsTest atest SettingsRoboTests:com.android.settings.applications.contacts.ContactsStoragePreferenceControllerTest Bug: 368641291 Flag: android.provider.new_default_account_api_enabled Change-Id: I3ca2a7a0905118c7a3a47fe4573781ae86c246bd --- res/values/strings.xml | 5 +- res/xml/contacts_storage_settings.xml | 6 +++ .../ContactsStoragePreferenceController.java | 3 ++ .../contacts/ContactsStorageSettings.java | 30 ++++++++---- .../contacts/ContactsStorageSettingsTest.java | 46 +++++++++++-------- 5 files changed, 61 insertions(+), 29 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index e986ce08663..6b409057883 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -13779,9 +13779,12 @@ No default set Device only + + Device and %1$s Add an account to get started - + + Where to save contacts diff --git a/res/xml/contacts_storage_settings.xml b/res/xml/contacts_storage_settings.xml index 7cbabe725ce..691a1cc16fd 100644 --- a/res/xml/contacts_storage_settings.xml +++ b/res/xml/contacts_storage_settings.xml @@ -22,6 +22,12 @@ + + + mAccountMap = new HashMap<>(); private AuthenticatorHelper mAuthenticatorHelper; @@ -71,6 +73,12 @@ public class ContactsStorageSettings extends DashboardFragment super.onAttach(context); mAuthenticatorHelper = new AuthenticatorHelper(context, new UserHandle(UserHandle.myUserId()), null); + String[] accountTypes = getEligibleAccountTypes(); + for (String accountType : accountTypes) { + // Preload the drawable for the account type to avoid the latency when rendering the + // account preference. + mAuthenticatorHelper.preloadDrawableForType(context, accountType); + } } @UiThread @@ -130,23 +138,24 @@ public class ContactsStorageSettings extends DashboardFragment // Clear all the accounts stored in the map and later on re-fetch the eligible accounts // when creating eligible account preferences. mAccountMap.clear(); - final PreferenceScreen screen = getPreferenceScreen(); + final PreferenceGroup preferenceGroup = findPreference(PREF_KEY_ACCOUNT_CATEGORY); // If the default account is SIM, we should show in the page, otherwise don't show. SelectorWithWidgetPreference simAccountPreference = buildSimAccountPreference(); if (simAccountPreference != null) { - getPreferenceScreen().addPreference(simAccountPreference); + preferenceGroup.addPreference(simAccountPreference); } List accounts = DefaultAccount.getEligibleCloudAccounts(getContentResolver()); for (int i = 0; i < accounts.size(); i++) { - screen.addPreference(buildCloudAccountPreference(accounts.get(i), /*order=*/i)); + preferenceGroup.addPreference( + buildCloudAccountPreference(accounts.get(i), /*order=*/i)); } // If there's no eligible account types, the "Add Account" preference should // not be shown to the users. if (getEligibleAccountTypes().length > 0) { - screen.addPreference(buildAddAccountPreference(accounts.isEmpty())); + getPreferenceScreen().addPreference(buildAddAccountPreference(accounts.isEmpty())); } setupDeviceOnlyPreference(); - setDefaultAccountPreference(); + setDefaultAccountPreference(preferenceGroup); } private void setupDeviceOnlyPreference() { @@ -157,7 +166,7 @@ public class ContactsStorageSettings extends DashboardFragment } } - private void setDefaultAccountPreference() { + private void setDefaultAccountPreference(PreferenceGroup preferenceGroup) { DefaultAccountAndState currentDefaultAccountAndState = DefaultAccount.getDefaultAccountForNewContacts(getContentResolver()); String preferenceKey = getAccountHashCode(currentDefaultAccountAndState); @@ -170,20 +179,21 @@ public class ContactsStorageSettings extends DashboardFragment preference = getPreferenceScreen().findPreference(preferenceKey); } else if (preferenceKey != null && currentDefaultAccount != null) { preference = buildCloudAccountPreference(currentDefaultAccount, mAccountMap.size()); - getPreferenceScreen().addPreference(preference); + preferenceGroup.addPreference(preference); } if (preference != null) { preference.setChecked(true); } } - //TODO: Add preference category on account preferences. private SelectorWithWidgetPreference buildCloudAccountPreference(Account account, int order) { SelectorWithWidgetPreference preference = new SelectorWithWidgetPreference( getPrefContext()); DefaultAccountAndState accountAndState = DefaultAccountAndState.ofCloud(account); String preferenceKey = getAccountHashCode(accountAndState); - preference.setTitle(mAuthenticatorHelper.getLabelForType(getPrefContext(), account.type)); + String accountPreferenceTitle = getString(R.string.contacts_storage_account_title, + mAuthenticatorHelper.getLabelForType(getPrefContext(), account.type)); + preference.setTitle(accountPreferenceTitle); preference.setIcon(mAuthenticatorHelper.getDrawableForType(getPrefContext(), account.type)); preference.setSummary(account.name); preference.setKey(preferenceKey); diff --git a/tests/robotests/src/com/android/settings/applications/contacts/ContactsStorageSettingsTest.java b/tests/robotests/src/com/android/settings/applications/contacts/ContactsStorageSettingsTest.java index 4e873fb20ee..a2debd24ee2 100644 --- a/tests/robotests/src/com/android/settings/applications/contacts/ContactsStorageSettingsTest.java +++ b/tests/robotests/src/com/android/settings/applications/contacts/ContactsStorageSettingsTest.java @@ -44,6 +44,7 @@ import android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccou import android.provider.SearchIndexableResource; import androidx.preference.Preference; +import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; import androidx.test.core.app.ApplicationProvider; @@ -73,7 +74,7 @@ import java.util.List; @Config(shadows = ShadowAuthenticationHelper.class) public class ContactsStorageSettingsTest { private static final String PREF_KEY_DEVICE_ONLY = "device_only_account_preference"; - + private static final String PREF_KEY_ACCOUNT_CATEGORY = "account_category"; private static final String PREF_KEY_ADD_ACCOUNT = "add_account"; private static final Account TEST_ACCOUNT1 = new Account("test@gmail.com", "type1"); @@ -95,6 +96,7 @@ public class ContactsStorageSettingsTest { private PreferenceManager mPreferenceManager; private TestContactsStorageSettings mContactsStorageSettings; private PreferenceScreen mScreen; + private PreferenceGroup accountCategory; @Before public void setUp() throws Exception { @@ -103,8 +105,16 @@ public class ContactsStorageSettingsTest { eq(ContactsContract.AUTHORITY_URI))).thenReturn(mContentProviderClient); mPreferenceManager = new PreferenceManager(mContext); when(mContactsStorageSettings.getPreferenceManager()).thenReturn(mPreferenceManager); - mScreen = spy(new PreferenceScreen(mContext, /* attrs= */ null)); + mScreen = spy(mPreferenceManager.inflateFromResource(mContext, + R.xml.contacts_storage_settings, mScreen)); when(mScreen.getPreferenceManager()).thenReturn(mPreferenceManager); + accountCategory = mScreen.findPreference(PREF_KEY_ACCOUNT_CATEGORY); + SelectorWithWidgetPreference deviceOnlyPreference = mScreen.findPreference( + PREF_KEY_DEVICE_ONLY); + when(mContactsStorageSettings.findPreference(eq(PREF_KEY_DEVICE_ONLY))).thenReturn( + deviceOnlyPreference); + when(mContactsStorageSettings.findPreference(eq(PREF_KEY_ACCOUNT_CATEGORY))).thenReturn( + accountCategory); when(mContactsStorageSettings.getPreferenceScreen()).thenReturn(mScreen); mContactsStorageSettings.onAttach(mContext); } @@ -134,17 +144,15 @@ public class ContactsStorageSettingsTest { when(mContentProviderClient.call(eq(QUERY_ELIGIBLE_DEFAULT_ACCOUNTS_METHOD), any(), any())).thenReturn(eligibleAccountBundle); - PreferenceScreen settingScreen = mPreferenceManager.inflateFromResource(mContext, - R.xml.contacts_storage_settings, mScreen); - SelectorWithWidgetPreference deviceOnlyPreference = settingScreen.findPreference( + SelectorWithWidgetPreference deviceOnlyPreference = mContactsStorageSettings.findPreference( PREF_KEY_DEVICE_ONLY); - when(mContactsStorageSettings.findPreference(eq(PREF_KEY_DEVICE_ONLY))).thenReturn( - deviceOnlyPreference); assertThat(deviceOnlyPreference.getTitle()).isEqualTo("Device only"); assertThat(deviceOnlyPreference.getSummary()).isEqualTo( "New contacts won't be synced with an account"); assertThat(deviceOnlyPreference.getOrder()).isEqualTo(999); + assertThat(mContactsStorageSettings.findPreference( + PREF_KEY_ACCOUNT_CATEGORY).getTitle()).isEqualTo("Where to save contacts"); mContactsStorageSettings.refreshUI(); mContactsStorageSettings.onRadioButtonClicked(deviceOnlyPreference); @@ -175,6 +183,8 @@ public class ContactsStorageSettingsTest { mContactsStorageSettings.refreshUI(); + assertThat(mContactsStorageSettings.findPreference( + PREF_KEY_ACCOUNT_CATEGORY).getTitle()).isEqualTo("Where to save contacts"); assertThat(mScreen.findPreference(PREF_KEY_ADD_ACCOUNT).getTitle()).isEqualTo( "Add an account to get started"); assertThat(mScreen.findPreference(PREF_KEY_ADD_ACCOUNT).getOrder()).isEqualTo(998); @@ -232,15 +242,15 @@ public class ContactsStorageSettingsTest { mContactsStorageSettings.refreshUI(); - SelectorWithWidgetPreference account1Preference = mScreen.findPreference( + SelectorWithWidgetPreference account1Preference = accountCategory.findPreference( String.valueOf(TEST_ACCOUNT1.hashCode())); - assertThat(account1Preference.getTitle()).isEqualTo("LABEL1"); + assertThat(account1Preference.getTitle()).isEqualTo("Device and LABEL1"); assertThat(account1Preference.getSummary()).isEqualTo("test@gmail.com"); assertThat(account1Preference.getIcon()).isNotNull(); - SelectorWithWidgetPreference account2Preference = mScreen.findPreference( + SelectorWithWidgetPreference account2Preference = accountCategory.findPreference( String.valueOf(TEST_ACCOUNT2.hashCode())); - assertThat(account2Preference.getTitle()).isEqualTo("LABEL2"); + assertThat(account2Preference.getTitle()).isEqualTo("Device and LABEL2"); assertThat(account2Preference.getSummary()).isEqualTo("test@samsung.com"); assertThat(account2Preference.getIcon()).isNotNull(); @@ -286,21 +296,21 @@ public class ContactsStorageSettingsTest { mContactsStorageSettings.refreshUI(); - SelectorWithWidgetPreference account1Preference = mScreen.findPreference( + SelectorWithWidgetPreference account1Preference = accountCategory.findPreference( String.valueOf(TEST_ACCOUNT1.hashCode())); - assertThat(account1Preference.getTitle()).isEqualTo("LABEL1"); + assertThat(account1Preference.getTitle()).isEqualTo("Device and LABEL1"); assertThat(account1Preference.getSummary()).isEqualTo("test@gmail.com"); assertThat(account1Preference.getIcon()).isNotNull(); - SelectorWithWidgetPreference account2Preference = mScreen.findPreference( + SelectorWithWidgetPreference account2Preference = accountCategory.findPreference( String.valueOf(TEST_ACCOUNT2.hashCode())); - assertThat(account2Preference.getTitle()).isEqualTo("LABEL2"); + assertThat(account2Preference.getTitle()).isEqualTo("Device and LABEL2"); assertThat(account2Preference.getSummary()).isEqualTo("test@samsung.com"); assertThat(account2Preference.getIcon()).isNotNull(); - SelectorWithWidgetPreference account3Preference = mScreen.findPreference( + SelectorWithWidgetPreference account3Preference = accountCategory.findPreference( String.valueOf(TEST_ACCOUNT3.hashCode())); - assertThat(account3Preference.getTitle()).isEqualTo("LABEL3"); + assertThat(account3Preference.getTitle()).isEqualTo("Device and LABEL3"); assertThat(account3Preference.getSummary()).isEqualTo("test@outlook.com"); assertThat(account3Preference.getIcon()).isNotNull(); @@ -327,7 +337,7 @@ public class ContactsStorageSettingsTest { mContactsStorageSettings.refreshUI(); - SelectorWithWidgetPreference simPreference = mScreen.findPreference( + SelectorWithWidgetPreference simPreference = accountCategory.findPreference( String.valueOf(SIM_ACCOUNT.hashCode())); assertThat(simPreference.getTitle()).isEqualTo("SIM"); assertThat(simPreference.getSummary()).isEqualTo("SIM"); From 3551614bafae5e9a2401dd2e4ee31df0aac3eb81 Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Wed, 6 Nov 2024 11:43:42 +0800 Subject: [PATCH 06/13] Implement RestrictedPreferenceHelperProvider for restricted preference Bug: 377600992 Flag: EXEMPT library Test: Manual Change-Id: I73e60ea7a392aac96e701b9d7f0ff617e449f3b2 --- .../android/settings/RestrictedListPreference.java | 11 ++++++++++- .../datausage/UnrestrictedDataAccessPreference.java | 8 +++++++- .../settings/notification/app/BubblePreference.java | 9 ++++++++- .../settings/widget/RestrictedAppPreference.java | 9 ++++++++- .../settings/widget/SettingsMainSwitchPreference.java | 9 ++++++++- 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/com/android/settings/RestrictedListPreference.java b/src/com/android/settings/RestrictedListPreference.java index d75f1b8fae9..d5bc3418627 100644 --- a/src/com/android/settings/RestrictedListPreference.java +++ b/src/com/android/settings/RestrictedListPreference.java @@ -33,6 +33,7 @@ import android.widget.CheckedTextView; import android.widget.ListAdapter; import android.widget.ListView; +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog.Builder; import androidx.preference.ListPreferenceDialogFragmentCompat; @@ -40,11 +41,14 @@ import androidx.preference.PreferenceViewHolder; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedPreferenceHelper; +import com.android.settingslib.RestrictedPreferenceHelperProvider; import java.util.ArrayList; import java.util.List; -public class RestrictedListPreference extends CustomListPreference { +public class RestrictedListPreference extends CustomListPreference implements + RestrictedPreferenceHelperProvider { + private final RestrictedPreferenceHelper mHelper; private final List mRestrictedItems = new ArrayList<>(); private boolean mRequiresActiveUnlockedProfile = false; @@ -61,6 +65,11 @@ public class RestrictedListPreference extends CustomListPreference { mHelper = new RestrictedPreferenceHelper(context, this, attrs); } + @Override + public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() { + return mHelper; + } + @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); diff --git a/src/com/android/settings/datausage/UnrestrictedDataAccessPreference.java b/src/com/android/settings/datausage/UnrestrictedDataAccessPreference.java index b3e66a9ce06..bb84c1804e9 100644 --- a/src/com/android/settings/datausage/UnrestrictedDataAccessPreference.java +++ b/src/com/android/settings/datausage/UnrestrictedDataAccessPreference.java @@ -30,6 +30,7 @@ import com.android.settings.applications.appinfo.AppInfoDashboardFragment; import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedPreferenceHelper; +import com.android.settingslib.RestrictedPreferenceHelperProvider; import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState.AppEntry; @@ -37,7 +38,7 @@ import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.widget.AppSwitchPreference; public class UnrestrictedDataAccessPreference extends AppSwitchPreference implements - DataSaverBackend.Listener { + DataSaverBackend.Listener, RestrictedPreferenceHelperProvider { private static final String ECM_SETTING_IDENTIFIER = "android:unrestricted_data_access"; private final ApplicationsState mApplicationsState; @@ -78,6 +79,11 @@ public class UnrestrictedDataAccessPreference extends AppSwitchPreference implem return entry.info.packageName + "|" + entry.info.uid; } + @Override + public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() { + return mHelper; + } + @Override public void onAttached() { super.onAttached(); diff --git a/src/com/android/settings/notification/app/BubblePreference.java b/src/com/android/settings/notification/app/BubblePreference.java index 17deef9e6f0..73f4582034b 100644 --- a/src/com/android/settings/notification/app/BubblePreference.java +++ b/src/com/android/settings/notification/app/BubblePreference.java @@ -33,11 +33,13 @@ import androidx.preference.PreferenceViewHolder; import com.android.settings.R; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedPreferenceHelper; +import com.android.settingslib.RestrictedPreferenceHelperProvider; /** * A tri-state preference allowing a user to specify what gets to bubble. */ -public class BubblePreference extends Preference implements RadioGroup.OnCheckedChangeListener { +public class BubblePreference extends Preference implements RadioGroup.OnCheckedChangeListener, + RestrictedPreferenceHelperProvider { RestrictedPreferenceHelper mHelper; private int mSelectedPreference; @@ -64,6 +66,11 @@ public class BubblePreference extends Preference implements RadioGroup.OnChecked setLayoutResource(R.layout.bubble_preference); } + @Override + public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() { + return mHelper; + } + public void setSelectedPreference(int preference) { mSelectedPreference = preference; notifyChanged(); diff --git a/src/com/android/settings/widget/RestrictedAppPreference.java b/src/com/android/settings/widget/RestrictedAppPreference.java index c76a5de4535..86552422467 100644 --- a/src/com/android/settings/widget/RestrictedAppPreference.java +++ b/src/com/android/settings/widget/RestrictedAppPreference.java @@ -27,6 +27,7 @@ import androidx.preference.PreferenceViewHolder; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedPreferenceHelper; +import com.android.settingslib.RestrictedPreferenceHelperProvider; import com.android.settingslib.widget.AppPreference; /** @@ -34,7 +35,8 @@ import com.android.settingslib.widget.AppPreference; * {@link com.android.settingslib.RestrictedPreferenceHelper}. * Used to show policy transparency on {@link AppPreference}. */ -public class RestrictedAppPreference extends AppPreference { +public class RestrictedAppPreference extends AppPreference implements + RestrictedPreferenceHelperProvider { private RestrictedPreferenceHelper mHelper; private String userRestriction; @@ -58,6 +60,11 @@ public class RestrictedAppPreference extends AppPreference { this.userRestriction = userRestriction; } + @Override + public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() { + return mHelper; + } + @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); diff --git a/src/com/android/settings/widget/SettingsMainSwitchPreference.java b/src/com/android/settings/widget/SettingsMainSwitchPreference.java index 9f6d787e841..17d5fc88915 100644 --- a/src/com/android/settings/widget/SettingsMainSwitchPreference.java +++ b/src/com/android/settings/widget/SettingsMainSwitchPreference.java @@ -23,12 +23,14 @@ import android.util.AttributeSet; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; +import androidx.annotation.NonNull; import androidx.preference.PreferenceViewHolder; import androidx.preference.TwoStatePreference; import com.android.settings.R; import com.android.settings.widget.SettingsMainSwitchBar.OnBeforeCheckedChangeListener; import com.android.settingslib.RestrictedPreferenceHelper; +import com.android.settingslib.RestrictedPreferenceHelperProvider; import com.android.settingslib.core.instrumentation.SettingsJankMonitor; import java.util.ArrayList; @@ -40,7 +42,7 @@ import java.util.List; * to enable or disable the preferences on the page. */ public class SettingsMainSwitchPreference extends TwoStatePreference implements - OnCheckedChangeListener { + OnCheckedChangeListener, RestrictedPreferenceHelperProvider { private final List mBeforeCheckedChangeListeners = new ArrayList<>(); @@ -71,6 +73,11 @@ public class SettingsMainSwitchPreference extends TwoStatePreference implements init(context, attrs); } + @Override + public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() { + return mRestrictedHelper; + } + @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); From f18e3bafe61f8f0dde8b8d6e2cbb3f32f69fe108 Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Wed, 6 Nov 2024 11:47:29 +0800 Subject: [PATCH 07/13] Add SettingsPreferenceBindingFactory and support restriction Bug: 377600992 Flag: com.android.settings.flags.catalyst Test: testdpc Change-Id: I14c37a3cfb1d69108ad4f5dabd4f35e8ec8899bd --- .../settings/PreferenceRestrictionMixin.kt | 44 +++++++++++++++++ .../android/settings/SettingsApplication.java | 2 + .../SettingsPreferenceBindingFactory.kt | 49 +++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 src/com/android/settings/PreferenceRestrictionMixin.kt create mode 100644 src/com/android/settings/SettingsPreferenceBindingFactory.kt diff --git a/src/com/android/settings/PreferenceRestrictionMixin.kt b/src/com/android/settings/PreferenceRestrictionMixin.kt new file mode 100644 index 00000000000..c70b94bf500 --- /dev/null +++ b/src/com/android/settings/PreferenceRestrictionMixin.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 + +import android.content.Context +import android.os.UserHandle +import androidx.annotation.CallSuper +import com.android.settingslib.RestrictedLockUtilsInternal +import com.android.settingslib.metadata.PreferenceRestrictionProvider + +/** Mixin to support restriction. */ +interface PreferenceRestrictionMixin : PreferenceRestrictionProvider { + + val restrictionKey: String + + val useAdminDisabledSummary: Boolean + get() = false + + @CallSuper fun isEnabled(context: Context) = !context.hasBaseUserRestriction(restrictionKey) + + override fun isRestricted(context: Context) = + RestrictedLockUtilsInternal.checkIfRestrictionEnforced( + context, + restrictionKey, + UserHandle.myUserId(), + ) != null +} + +fun Context.hasBaseUserRestriction(restrictionKey: String) = + RestrictedLockUtilsInternal.hasBaseUserRestriction(this, restrictionKey, UserHandle.myUserId()) diff --git a/src/com/android/settings/SettingsApplication.java b/src/com/android/settings/SettingsApplication.java index c908855ee49..99d3d922a0c 100644 --- a/src/com/android/settings/SettingsApplication.java +++ b/src/com/android/settings/SettingsApplication.java @@ -46,6 +46,7 @@ import com.android.settingslib.datastore.BackupRestoreStorageManager; import com.android.settingslib.metadata.PreferenceScreenMetadata; import com.android.settingslib.metadata.PreferenceScreenRegistry; import com.android.settingslib.metadata.ProvidePreferenceScreenOptions; +import com.android.settingslib.preference.PreferenceBindingFactory; import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory; import com.google.android.setupcompat.util.WizardManagerHelper; @@ -76,6 +77,7 @@ public class SettingsApplication extends Application { if (Flags.catalyst()) { PreferenceScreenRegistry.INSTANCE.setPreferenceScreensSupplier( this::getPreferenceScreens); + PreferenceBindingFactory.setDefaultFactory(new SettingsPreferenceBindingFactory()); } BackupRestoreStorageManager.getInstance(this) diff --git a/src/com/android/settings/SettingsPreferenceBindingFactory.kt b/src/com/android/settings/SettingsPreferenceBindingFactory.kt new file mode 100644 index 00000000000..53e5d0f45b5 --- /dev/null +++ b/src/com/android/settings/SettingsPreferenceBindingFactory.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 + +import android.os.UserHandle +import androidx.preference.Preference +import com.android.settingslib.RestrictedPreferenceHelperProvider +import com.android.settingslib.metadata.PreferenceHierarchyNode +import com.android.settingslib.preference.DefaultPreferenceBindingFactory +import com.android.settingslib.preference.PreferenceBinding + +/** Preference binding factory for settings app. */ +class SettingsPreferenceBindingFactory : DefaultPreferenceBindingFactory() { + override fun bind( + preference: Preference, + node: PreferenceHierarchyNode, + preferenceBinding: PreferenceBinding?, + ) { + super.bind(preference, node, preferenceBinding) + + // handle restriction consistently + val metadata = node.metadata + if (metadata is PreferenceRestrictionMixin) { + if (preference is RestrictedPreferenceHelperProvider) { + preference.getRestrictedPreferenceHelper().apply { + val restrictionKey = metadata.restrictionKey + if (!preference.context.hasBaseUserRestriction(restrictionKey)) { + useAdminDisabledSummary(metadata.useAdminDisabledSummary) + checkRestrictionAndSetDisabled(restrictionKey, UserHandle.myUserId()) + } + } + } + } + } +} From d33b0a5114c4b672e9296652ef5947c12f9709a5 Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Wed, 6 Nov 2024 11:58:10 +0800 Subject: [PATCH 08/13] [Catalyst] Support restriction for Sound settings NO_IFTTT=Catalyst only Bug: 377600992 Flag: com.android.settings.flags.catalyst_sound_screen Test: testdpc Change-Id: I2d768155f450f415c2279bbff69f94e8934b4383 --- .../notification/CallVolumePreference.kt | 24 +++++-------- .../notification/MediaVolumePreference.kt | 25 +++++--------- .../SeparateRingVolumePreference.kt | 34 +++++++------------ 3 files changed, 28 insertions(+), 55 deletions(-) diff --git a/src/com/android/settings/notification/CallVolumePreference.kt b/src/com/android/settings/notification/CallVolumePreference.kt index 3c14ae4fafb..df6c4275c94 100644 --- a/src/com/android/settings/notification/CallVolumePreference.kt +++ b/src/com/android/settings/notification/CallVolumePreference.kt @@ -20,18 +20,16 @@ import android.content.Context import android.media.AudioManager import android.media.AudioManager.STREAM_BLUETOOTH_SCO import android.media.AudioManager.STREAM_VOICE_CALL -import android.os.UserHandle -import android.os.UserManager.DISALLOW_ADJUST_VOLUME +import android.os.UserManager import androidx.preference.Preference +import com.android.settings.PreferenceRestrictionMixin import com.android.settings.R -import com.android.settingslib.RestrictedLockUtilsInternal import com.android.settingslib.datastore.KeyValueStore import com.android.settingslib.datastore.NoOpKeyedObservable import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.PreferenceAvailabilityProvider import com.android.settingslib.metadata.PreferenceIconProvider import com.android.settingslib.metadata.PreferenceMetadata -import com.android.settingslib.metadata.PreferenceRestrictionProvider import com.android.settingslib.metadata.RangeValue import com.android.settingslib.metadata.ReadWritePermit import com.android.settingslib.preference.PreferenceBinding @@ -44,7 +42,7 @@ open class CallVolumePreference : RangeValue, PreferenceAvailabilityProvider, PreferenceIconProvider, - PreferenceRestrictionProvider { + PreferenceRestrictionMixin { override val key: String get() = KEY @@ -55,18 +53,12 @@ open class CallVolumePreference : override fun isAvailable(context: Context) = context.resources.getBoolean(R.bool.config_show_call_volume) && - !createAudioHelper(context).isSingleVolume() + !createAudioHelper(context).isSingleVolume - override fun isRestricted(context: Context) = - RestrictedLockUtilsInternal.hasBaseUserRestriction( - context, - DISALLOW_ADJUST_VOLUME, - UserHandle.myUserId() - ) || RestrictedLockUtilsInternal.checkIfRestrictionEnforced( - context, - DISALLOW_ADJUST_VOLUME, - UserHandle.myUserId() - ) != null + override fun isEnabled(context: Context) = super.isEnabled(context) + + override val restrictionKey: String + get() = UserManager.DISALLOW_ADJUST_VOLUME override fun storage(context: Context): KeyValueStore { val helper = createAudioHelper(context) diff --git a/src/com/android/settings/notification/MediaVolumePreference.kt b/src/com/android/settings/notification/MediaVolumePreference.kt index acb8f8d3af1..a39023361f4 100644 --- a/src/com/android/settings/notification/MediaVolumePreference.kt +++ b/src/com/android/settings/notification/MediaVolumePreference.kt @@ -18,18 +18,16 @@ package com.android.settings.notification import android.content.Context import android.media.AudioManager.STREAM_MUSIC -import android.os.UserHandle import android.os.UserManager import androidx.preference.Preference +import com.android.settings.PreferenceRestrictionMixin import com.android.settings.R -import com.android.settingslib.RestrictedLockUtilsInternal import com.android.settingslib.datastore.KeyValueStore import com.android.settingslib.datastore.NoOpKeyedObservable import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.PreferenceAvailabilityProvider import com.android.settingslib.metadata.PreferenceIconProvider import com.android.settingslib.metadata.PreferenceMetadata -import com.android.settingslib.metadata.PreferenceRestrictionProvider import com.android.settingslib.metadata.RangeValue import com.android.settingslib.metadata.ReadWritePermit import com.android.settingslib.preference.PreferenceBinding @@ -42,7 +40,7 @@ open class MediaVolumePreference : RangeValue, PreferenceAvailabilityProvider, PreferenceIconProvider, - PreferenceRestrictionProvider { + PreferenceRestrictionMixin { override val key: String get() = KEY @@ -58,17 +56,10 @@ open class MediaVolumePreference : override fun isAvailable(context: Context) = context.resources.getBoolean(R.bool.config_show_media_volume) - override fun isRestricted(context: Context) = - RestrictedLockUtilsInternal.hasBaseUserRestriction( - context, - UserManager.DISALLOW_ADJUST_VOLUME, - UserHandle.myUserId(), - ) || - RestrictedLockUtilsInternal.checkIfRestrictionEnforced( - context, - UserManager.DISALLOW_ADJUST_VOLUME, - UserHandle.myUserId(), - ) != null + override fun isEnabled(context: Context) = super.isEnabled(context) + + override val restrictionKey: String + get() = UserManager.DISALLOW_ADJUST_VOLUME override fun storage(context: Context): KeyValueStore { val helper = createAudioHelper(context) @@ -107,9 +98,9 @@ open class MediaVolumePreference : open fun createAudioHelper(context: Context) = AudioHelper(context) - fun updateContentDescription(preference: VolumeSeekBarPreference) { + private fun updateContentDescription(preference: VolumeSeekBarPreference) { when { - preference.isMuted() -> + preference.isMuted -> preference.updateContentDescription( preference.context.getString( R.string.volume_content_description_silent_mode, diff --git a/src/com/android/settings/notification/SeparateRingVolumePreference.kt b/src/com/android/settings/notification/SeparateRingVolumePreference.kt index 6831daad683..c6485686f52 100644 --- a/src/com/android/settings/notification/SeparateRingVolumePreference.kt +++ b/src/com/android/settings/notification/SeparateRingVolumePreference.kt @@ -24,21 +24,19 @@ import android.media.AudioManager.RINGER_MODE_SILENT import android.media.AudioManager.RINGER_MODE_VIBRATE import android.media.AudioManager.STREAM_RING import android.os.ServiceManager -import android.os.UserHandle -import android.os.UserManager.DISALLOW_ADJUST_VOLUME +import android.os.UserManager import android.os.Vibrator import android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS import android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS import androidx.preference.Preference +import com.android.settings.PreferenceRestrictionMixin import com.android.settings.R -import com.android.settingslib.RestrictedLockUtilsInternal import com.android.settingslib.datastore.KeyValueStore import com.android.settingslib.datastore.NoOpKeyedObservable import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.PreferenceAvailabilityProvider import com.android.settingslib.metadata.PreferenceIconProvider import com.android.settingslib.metadata.PreferenceMetadata -import com.android.settingslib.metadata.PreferenceRestrictionProvider import com.android.settingslib.metadata.RangeValue import com.android.settingslib.metadata.ReadWritePermit import com.android.settingslib.preference.PreferenceBinding @@ -51,7 +49,8 @@ open class SeparateRingVolumePreference : RangeValue, PreferenceAvailabilityProvider, PreferenceIconProvider, - PreferenceRestrictionProvider { + PreferenceRestrictionMixin { + override val key: String get() = KEY @@ -64,21 +63,12 @@ open class SeparateRingVolumePreference : else -> R.drawable.ic_ring_volume } - override fun isAvailable(context: Context) = !createAudioHelper(context).isSingleVolume() + override fun isAvailable(context: Context) = !createAudioHelper(context).isSingleVolume - override fun isEnabled(context: Context) = - !RestrictedLockUtilsInternal.hasBaseUserRestriction( - context, - DISALLOW_ADJUST_VOLUME, - UserHandle.myUserId(), - ) + override fun isEnabled(context: Context) = super.isEnabled(context) - override fun isRestricted(context: Context) = - RestrictedLockUtilsInternal.checkIfRestrictionEnforced( - context, - DISALLOW_ADJUST_VOLUME, - UserHandle.myUserId(), - ) != null + override val restrictionKey: String + get() = UserManager.DISALLOW_ADJUST_VOLUME override fun storage(context: Context): KeyValueStore { val helper = createAudioHelper(context) @@ -118,7 +108,7 @@ open class SeparateRingVolumePreference : open fun createAudioHelper(context: Context) = AudioHelper(context) - fun updateContentDescription(preference: VolumeSeekBarPreference) { + private fun updateContentDescription(preference: VolumeSeekBarPreference) { val context = preference.context val ringerMode = getEffectiveRingerMode(context) when (ringerMode) { @@ -152,13 +142,13 @@ open class SeparateRingVolumePreference : } } - fun getSuppressionText(context: Context): String? { + private fun getSuppressionText(context: Context): String? { val suppressor = NotificationManager.from(context).getEffectsSuppressor() val notificationManager = INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE) ) - val hints = notificationManager.getHintsFromListenerNoToken() + val hints = notificationManager.hintsFromListenerNoToken return when { hintsMatch(hints) -> SuppressorHelper.getSuppressionText(context, suppressor) else -> null @@ -167,7 +157,7 @@ open class SeparateRingVolumePreference : private fun hintsMatch(hints: Int) = (hints and HINT_HOST_DISABLE_CALL_EFFECTS) != 0 || - (hints and HINT_HOST_DISABLE_EFFECTS) != 0 + (hints and HINT_HOST_DISABLE_EFFECTS) != 0 companion object { const val KEY = "separate_ring_volume" From ffa4fba144c9e40759db1f888bfc9a33f22af16a Mon Sep 17 00:00:00 2001 From: Haijie Hong Date: Wed, 6 Nov 2024 13:37:35 +0800 Subject: [PATCH 09/13] Also disable main content if switch is disabled BUG: 377530027 Test: local test Flag: com.android.settings.flags.enable_bluetooth_device_details_polish Change-Id: I883618919523fea2305858c370390c88511d5a0f --- .../settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt index 1c8aafdbce1..e3ed7f597b5 100644 --- a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt +++ b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt @@ -294,6 +294,7 @@ class DeviceDetailsFragmentFormatterImpl( TwoTargetSwitchPreference( switchPrefModel, primaryOnClick = { triggerAction(model.action) }, + primaryEnabled = { !model.disabled } ) } else { SwitchPreference(switchPrefModel) From 33c580ec458282ecde721741d5f80bb9902fe026 Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Wed, 6 Nov 2024 16:49:27 +0800 Subject: [PATCH 10/13] [Catalyst] Support restriction for Display settings NO_IFTTT=Catalyst only Bug: 377600992 Flag: com.android.settings.flags.catalyst_display_settings_screen Test: testdpc Change-Id: I0a9a125a24614ea8d93e870891c7db6f73f4ac9e --- .../settings/display/AutoBrightnessScreen.kt | 23 ++++----- .../BrightnessLevelRestrictedPreference.kt | 48 ++++++++----------- .../display/AutoBrightnessScreenTest.kt | 11 ++--- 3 files changed, 32 insertions(+), 50 deletions(-) diff --git a/src/com/android/settings/display/AutoBrightnessScreen.kt b/src/com/android/settings/display/AutoBrightnessScreen.kt index 0e682ff92df..5d08b1dfed1 100644 --- a/src/com/android/settings/display/AutoBrightnessScreen.kt +++ b/src/com/android/settings/display/AutoBrightnessScreen.kt @@ -16,17 +16,15 @@ package com.android.settings.display import android.content.Context -import android.os.Process -import android.os.UserHandle import android.os.UserManager import android.provider.Settings import android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC import android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL import androidx.preference.Preference +import com.android.settings.PreferenceRestrictionMixin import com.android.settings.R import com.android.settings.flags.Flags import com.android.settingslib.PrimarySwitchPreference -import com.android.settingslib.RestrictedLockUtilsInternal import com.android.settingslib.datastore.KeyValueStore import com.android.settingslib.datastore.KeyedObservableDelegate import com.android.settingslib.datastore.SettingsStore @@ -35,7 +33,6 @@ import com.android.settingslib.metadata.BooleanValue import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.PreferenceAvailabilityProvider import com.android.settingslib.metadata.PreferenceMetadata -import com.android.settingslib.metadata.PreferenceRestrictionProvider import com.android.settingslib.metadata.ProvidePreferenceScreen import com.android.settingslib.metadata.ReadWritePermit import com.android.settingslib.metadata.preferenceHierarchy @@ -47,7 +44,7 @@ class AutoBrightnessScreen : PreferenceScreenCreator, PreferenceScreenBinding, PreferenceAvailabilityProvider, - PreferenceRestrictionProvider, + PreferenceRestrictionMixin, PersistentPreference, BooleanValue { override val key: String @@ -75,23 +72,19 @@ class AutoBrightnessScreen : com.android.internal.R.bool.config_automatic_brightness_available ) - override fun isEnabled(context: Context) = - !UserManager.get(context) - .hasBaseUserRestriction(UserManager.DISALLOW_CONFIG_BRIGHTNESS, Process.myUserHandle()) + override fun isEnabled(context: Context) = super.isEnabled(context) - override fun isRestricted(context: Context) = - RestrictedLockUtilsInternal.checkIfRestrictionEnforced( - context, - UserManager.DISALLOW_CONFIG_BRIGHTNESS, - UserHandle.myUserId(), - ) != null + override val restrictionKey: String + get() = UserManager.DISALLOW_CONFIG_BRIGHTNESS + + override val useAdminDisabledSummary: Boolean + get() = true override fun createWidget(context: Context) = PrimarySwitchPreference(context) override fun bind(preference: Preference, metadata: PreferenceMetadata) { super.bind(preference, metadata) (preference as PrimarySwitchPreference).apply { - useAdminDisabledSummary(true) isSwitchEnabled = isEnabled // "true" is not the real default value (it is provided by AutoBrightnessDataStore) isChecked = preferenceDataStore!!.getBoolean(key, true) diff --git a/src/com/android/settings/display/BrightnessLevelRestrictedPreference.kt b/src/com/android/settings/display/BrightnessLevelRestrictedPreference.kt index a412b8cb310..09d92ebeee8 100644 --- a/src/com/android/settings/display/BrightnessLevelRestrictedPreference.kt +++ b/src/com/android/settings/display/BrightnessLevelRestrictedPreference.kt @@ -23,15 +23,13 @@ import android.content.Intent.EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH import android.hardware.display.BrightnessInfo import android.hardware.display.DisplayManager import android.hardware.display.DisplayManager.DisplayListener -import android.os.Process -import android.os.UserHandle import android.os.UserManager import android.provider.Settings.System import androidx.preference.Preference +import com.android.settings.PreferenceRestrictionMixin import com.android.settings.R import com.android.settings.Utils import com.android.settings.core.SettingsBaseActivity -import com.android.settingslib.RestrictedLockUtilsInternal import com.android.settingslib.RestrictedPreference import com.android.settingslib.datastore.HandlerExecutor import com.android.settingslib.datastore.KeyedObserver @@ -42,7 +40,6 @@ import com.android.settingslib.display.BrightnessUtils.convertLinearToGammaFloat import com.android.settingslib.metadata.PreferenceLifecycleContext import com.android.settingslib.metadata.PreferenceLifecycleProvider import com.android.settingslib.metadata.PreferenceMetadata -import com.android.settingslib.metadata.PreferenceRestrictionProvider import com.android.settingslib.metadata.PreferenceSummaryProvider import com.android.settingslib.preference.PreferenceBinding import com.android.settingslib.transition.SettingsTransitionHelper @@ -52,7 +49,7 @@ import java.text.NumberFormat class BrightnessLevelRestrictedPreference : PreferenceMetadata, PreferenceBinding, - PreferenceRestrictionProvider, + PreferenceRestrictionMixin, PreferenceSummaryProvider, PreferenceLifecycleProvider, Preference.OnPreferenceClickListener { @@ -69,34 +66,28 @@ class BrightnessLevelRestrictedPreference : override val keywords: Int get() = R.string.keywords_display_brightness_level - override fun getSummary(context: Context) = + override fun getSummary(context: Context): CharSequence? = NumberFormat.getPercentInstance().format(getCurrentBrightness(context)) - override fun isEnabled(context: Context) = - !UserManager.get(context) - .hasBaseUserRestriction(UserManager.DISALLOW_CONFIG_BRIGHTNESS, Process.myUserHandle()) + override fun isEnabled(context: Context) = super.isEnabled(context) - override fun isRestricted(context: Context) = - RestrictedLockUtilsInternal.checkIfRestrictionEnforced( - context, - UserManager.DISALLOW_CONFIG_BRIGHTNESS, - UserHandle.myUserId(), - ) != null + override val restrictionKey: String + get() = UserManager.DISALLOW_CONFIG_BRIGHTNESS + + override val useAdminDisabledSummary: Boolean + get() = true override fun createWidget(context: Context) = RestrictedPreference(context) override fun bind(preference: Preference, metadata: PreferenceMetadata) { super.bind(preference, metadata) - if (preference is RestrictedPreference) preference.useAdminDisabledSummary(true) preference.onPreferenceClickListener = this } override fun onStart(context: PreferenceLifecycleContext) { val observer = - object : KeyedObserver { - override fun onKeyChanged(key: String, reason: Int) { - context.notifyPreferenceChange(this@BrightnessLevelRestrictedPreference) - } + KeyedObserver { _, _ -> + context.notifyPreferenceChange(this@BrightnessLevelRestrictedPreference) } brightnessObserver = observer SettingsSystemStore.get(context) @@ -113,13 +104,11 @@ class BrightnessLevelRestrictedPreference : } } displayListener = listener - context - .getSystemService(DisplayManager::class.java) - .registerDisplayListener( - listener, - HandlerExecutor.main, - DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS, - ) + context.displayManager.registerDisplayListener( + listener, + HandlerExecutor.main, + DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS, + ) } override fun onStop(context: PreferenceLifecycleContext) { @@ -129,11 +118,14 @@ class BrightnessLevelRestrictedPreference : } displayListener?.let { - context.getSystemService(DisplayManager::class.java).unregisterDisplayListener(it) + context.displayManager.unregisterDisplayListener(it) displayListener = null } } + private val Context.displayManager: DisplayManager + get() = getSystemService(DisplayManager::class.java)!! + override fun onPreferenceClick(preference: Preference): Boolean { val context = preference.context val intent = diff --git a/tests/robotests/src/com/android/settings/display/AutoBrightnessScreenTest.kt b/tests/robotests/src/com/android/settings/display/AutoBrightnessScreenTest.kt index d05b258574d..ab2d9bd29d9 100644 --- a/tests/robotests/src/com/android/settings/display/AutoBrightnessScreenTest.kt +++ b/tests/robotests/src/com/android/settings/display/AutoBrightnessScreenTest.kt @@ -25,7 +25,7 @@ import androidx.preference.PreferenceViewHolder import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.PrimarySwitchPreference -import com.android.settingslib.preference.PreferenceDataStoreAdapter +import com.android.settingslib.preference.createAndBindWidget import com.android.settingslib.widget.SettingsThemeHelper.isExpressiveTheme import com.android.settingslib.widget.theme.R import com.google.common.truth.Truth.assertThat @@ -117,17 +117,14 @@ class AutoBrightnessScreenTest { assertThat(preferenceScreenCreator.isAvailable(context)).isFalse() } - private fun getPrimarySwitchPreference(): PrimarySwitchPreference = - preferenceScreenCreator.run { - val preference = createWidget(context) - preference.preferenceDataStore = PreferenceDataStoreAdapter(storage(context)) - bind(preference, this) + private fun getPrimarySwitchPreference() = + preferenceScreenCreator.createAndBindWidget(context).also { val holder = PreferenceViewHolder.createInstanceForTests( LayoutInflater.from(context).inflate(getResId(), /* root= */ null) ) .apply { findViewById(androidx.preference.R.id.switchWidget) } - preference.apply { onBindViewHolder(holder) } + it.onBindViewHolder(holder) } private fun setScreenBrightnessMode(value: Int) = From 3e96c9459bb368754076f63e935c7f464f2cf7db Mon Sep 17 00:00:00 2001 From: Candice Date: Tue, 5 Nov 2024 15:39:19 +0000 Subject: [PATCH 11/13] Explicitly mark the TopIntroPreference not searchable Bug: 353847080 Test: Manually. Add screen recording to the bug Test: atest AccessibilityHearingAidsFragmentTest Flag: com.android.settings.accessibility.fix_a11y_settings_search Change-Id: I7bdeb07d78ed717abf0aee53bc63952edb183354 --- res/xml/accessibility_hearing_aids.xml | 3 ++- .../accessibility/AccessibilityHearingAidsFragmentTest.java | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/res/xml/accessibility_hearing_aids.xml b/res/xml/accessibility_hearing_aids.xml index 9c6e661235a..16128281182 100644 --- a/res/xml/accessibility_hearing_aids.xml +++ b/res/xml/accessibility_hearing_aids.xml @@ -20,7 +20,8 @@ android:title="@string/accessibility_hearingaid_title"> + android:title="@string/accessibility_hearingaid_intro" + settings:searchable="false" /> niks = AccessibilityHearingAidsFragment.SEARCH_INDEX_DATA_PROVIDER - .getNonIndexableKeys(mContext); + .getNonIndexableKeys(mContext).stream() + .filter(Objects::nonNull) + .toList(); final List keys = XmlTestUtils.getKeysFromPreferenceXml(mContext, R.xml.accessibility_hearing_aids); From 13c6cb4c222b1706554357e50c03936fdc791626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Wed, 6 Nov 2024 17:35:45 +0100 Subject: [PATCH 12/13] Use the DND icon for modes of type UNKNOWN Fixes: 376404078 Test: manual Flag: android.app.modes_ui Change-Id: I0b0959a468b2dde495b301fa723c7bef265fb3fe --- res/values/arrays.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/arrays.xml b/res/values/arrays.xml index ed0bce40041..febdb0412a4 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -1567,7 +1567,7 @@ @*android:drawable/ic_zen_mode_icon_child @*android:drawable/ic_zen_mode_icon_animal_paw - @*android:drawable/ic_zen_mode_type_unknown + @*android:drawable/ic_zen_mode_icon_star_badge @*android:drawable/ic_zen_mode_type_managed @*android:drawable/ic_zen_mode_type_other @*android:drawable/ic_zen_mode_icon_heart From 776af69b32a9628aded7cb26cdc0afdbf579209d Mon Sep 17 00:00:00 2001 From: Prince Date: Wed, 6 Nov 2024 16:54:49 +0000 Subject: [PATCH 13/13] Move When to start tab below grid Test: Device Tested Fixes: 376237263 Flag: com.android.systemui.dream_overlay_updated_font Change-Id: Ic974a18ba0fcaf879ade7965f3372af8ca17966a --- res/xml/dream_fragment_overview.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/res/xml/dream_fragment_overview.xml b/res/xml/dream_fragment_overview.xml index fff6bfe0333..bfc9855dd28 100644 --- a/res/xml/dream_fragment_overview.xml +++ b/res/xml/dream_fragment_overview.xml @@ -27,11 +27,6 @@ settings:controller="com.android.settings.dream.DreamMainSwitchPreferenceController" settings:searchable="false"/> - - + +