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 diff --git a/res/values/strings.xml b/res/values/strings.xml index b4092f63fbe..208073d05fb 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -9041,6 +9041,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 @@ -13781,9 +13788,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/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" /> + + + + + + + + + + + + + + + + + + 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" /> + + + + + - - + + 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/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()) + } + } + } + } + } +} diff --git a/src/com/android/settings/applications/contacts/ContactsStoragePreferenceController.java b/src/com/android/settings/applications/contacts/ContactsStoragePreferenceController.java index e4343e542bc..340a666d88a 100644 --- a/src/com/android/settings/applications/contacts/ContactsStoragePreferenceController.java +++ b/src/com/android/settings/applications/contacts/ContactsStoragePreferenceController.java @@ -61,6 +61,9 @@ public class ContactsStoragePreferenceController extends BasePreferenceControlle @Override public CharSequence getSummary() { if (mCurrentDefaultAccountAndState != null) { + // Re-fetch account in controller to refresh the latest set default account. + mCurrentDefaultAccountAndState = + DefaultAccount.getDefaultAccountForNewContacts(mContext.getContentResolver()); int currentDefaultAccountState = mCurrentDefaultAccountAndState.getState(); Account currentDefaultAccount = mCurrentDefaultAccountAndState.getAccount(); if (currentDefaultAccountState diff --git a/src/com/android/settings/applications/contacts/ContactsStorageSettings.java b/src/com/android/settings/applications/contacts/ContactsStorageSettings.java index 8e71d08f1a3..5e48dd98ec6 100644 --- a/src/com/android/settings/applications/contacts/ContactsStorageSettings.java +++ b/src/com/android/settings/applications/contacts/ContactsStorageSettings.java @@ -25,6 +25,7 @@ import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; import android.content.res.Resources; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.UserHandle; import android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState; @@ -36,7 +37,7 @@ import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceClickListener; -import androidx.preference.PreferenceScreen; +import androidx.preference.PreferenceGroup; import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; @@ -63,6 +64,7 @@ public class ContactsStorageSettings extends DashboardFragment private static final String TAG = "ContactsStorageSettings"; private static final String PREF_KEY_ADD_ACCOUNT = "add_account"; private static final String PREF_KEY_DEVICE_ONLY = "device_only_account_preference"; + private static final String PREF_KEY_ACCOUNT_CATEGORY = "account_category"; private final Map 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/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/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/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) 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/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/src/com/android/settings/notification/BundleGlobalPreferenceController.java b/src/com/android/settings/notification/BundleGlobalPreferenceController.java new file mode 100644 index 00000000000..e41e7dd7c63 --- /dev/null +++ b/src/com/android/settings/notification/BundleGlobalPreferenceController.java @@ -0,0 +1,61 @@ +/* + * 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.notification; + +import android.app.Flags; +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.android.settings.widget.SettingsMainSwitchPreferenceController; + +public class BundleGlobalPreferenceController extends + SettingsMainSwitchPreferenceController { + + NotificationBackend mBackend; + + public BundleGlobalPreferenceController(@NonNull Context context, + @NonNull String preferenceKey) { + super(context, preferenceKey); + mBackend = new NotificationBackend(); + } + + @Override + public int getAvailabilityStatus() { + if (Flags.notificationClassificationUi() && mBackend.isNotificationBundlingSupported()) { + return AVAILABLE; + } + return CONDITIONALLY_UNAVAILABLE; + } + + @Override + public boolean isChecked() { + return mBackend.isNotificationBundlingEnabled(mContext); + } + + @Override + public boolean setChecked(boolean isChecked) { + mBackend.setNotificationBundlingEnabled(isChecked); + return true; + } + + @Override + public int getSliceHighlightMenuRes() { + // not needed since it's not sliceable + return NO_RES; + } +} diff --git a/src/com/android/settings/notification/BundlePreferenceController.java b/src/com/android/settings/notification/BundlePreferenceController.java new file mode 100644 index 00000000000..39d28ceee07 --- /dev/null +++ b/src/com/android/settings/notification/BundlePreferenceController.java @@ -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.notification; + +import android.app.Flags; +import android.content.Context; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +/** + * Controller for the bundled notifications settings page. + */ +public class BundlePreferenceController extends BasePreferenceController { + + NotificationBackend mBackend; + + public BundlePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mBackend = new NotificationBackend(); + } + + @Override + public int getAvailabilityStatus() { + return Flags.notificationClassificationUi() && mBackend.isNotificationBundlingSupported() + ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } + + @Override + public CharSequence getSummary() { + return mBackend.isNotificationBundlingEnabled(mContext) + ? mContext.getString(R.string.notification_bundle_on) + : mContext.getString(R.string.notification_bundle_off); + } +} diff --git a/src/com/android/settings/notification/BundlePreferenceFragment.java b/src/com/android/settings/notification/BundlePreferenceFragment.java new file mode 100644 index 00000000000..14de2c26d1c --- /dev/null +++ b/src/com/android/settings/notification/BundlePreferenceFragment.java @@ -0,0 +1,60 @@ +/* + * 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.notification; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.app.Flags; + +import androidx.lifecycle.Lifecycle; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +import org.jetbrains.annotations.NotNull; + +/** + * Fragment for bundled notifications. + */ +@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) +public class BundlePreferenceFragment extends DashboardFragment { + + @Override + public int getMetricsCategory() { + return SettingsEnums.BUNDLED_NOTIFICATIONS; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.bundle_notifications_settings; + } + @Override + protected String getLogTag() { + return "BundlePreferenceFragment"; + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.bundle_notifications_settings) { + + @Override + protected boolean isPageSearchEnabled(Context context) { + return Flags.notificationClassificationUi(); + } + }; +} diff --git a/src/com/android/settings/notification/BundleTypePreferenceController.java b/src/com/android/settings/notification/BundleTypePreferenceController.java new file mode 100644 index 00000000000..b9fb2b2d78f --- /dev/null +++ b/src/com/android/settings/notification/BundleTypePreferenceController.java @@ -0,0 +1,83 @@ +/* + * 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.notification; + +import android.app.Flags; +import android.content.Context; +import android.service.notification.Adjustment; + +import androidx.annotation.NonNull; + +import com.android.settings.widget.SettingsMainSwitchPreferenceController; + +public class BundleTypePreferenceController extends + SettingsMainSwitchPreferenceController { + + static final String PROMO_KEY = "promotions"; + static final String NEWS_KEY = "news"; + static final String SOCIAL_KEY = "social"; + static final String RECS_KEY = "recs"; + + NotificationBackend mBackend; + int mType; + + public BundleTypePreferenceController(@NonNull Context context, + @NonNull String preferenceKey) { + super(context, preferenceKey); + mBackend = new NotificationBackend(); + mType = getBundleTypeForKey(); + } + + @Override + public int getAvailabilityStatus() { + if (Flags.notificationClassificationUi() && mBackend.isNotificationBundlingSupported() + && mBackend.isNotificationBundlingEnabled(mContext)) { + return AVAILABLE; + } + return CONDITIONALLY_UNAVAILABLE; + } + + @Override + public boolean isChecked() { + return mBackend.isBundleTypeApproved(mType); + } + + @Override + public boolean setChecked(boolean isChecked) { + mBackend.setBundleTypeState(mType, isChecked); + return true; + } + + @Override + public int getSliceHighlightMenuRes() { + // not needed since it's not sliceable + return NO_RES; + } + + private @Adjustment.Types int getBundleTypeForKey() { + if (PROMO_KEY.equals(mPreferenceKey)) { + return Adjustment.TYPE_PROMOTION; + } else if (NEWS_KEY.equals(mPreferenceKey)) { + return Adjustment.TYPE_NEWS; + } else if (SOCIAL_KEY.equals(mPreferenceKey)) { + return Adjustment.TYPE_SOCIAL_MEDIA; + } else if (RECS_KEY.equals(mPreferenceKey)) { + return Adjustment.TYPE_CONTENT_RECOMMENDATION; + } + return Adjustment.TYPE_OTHER; + } +} 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/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java index d6f810c99d1..388e0d7ffaf 100644 --- a/src/com/android/settings/notification/NotificationBackend.java +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -46,6 +46,7 @@ import android.os.Build; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.service.notification.Adjustment; import android.service.notification.ConversationChannelWrapper; import android.service.notification.NotificationListenerFilter; import android.text.format.DateUtils; @@ -65,9 +66,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; public class NotificationBackend { private static final String TAG = "NotificationBackend"; @@ -651,6 +654,59 @@ public class NotificationBackend { return false; } + public boolean isNotificationBundlingSupported() { + try { + return !sINM.getUnsupportedAdjustmentTypes().contains(Adjustment.KEY_TYPE); + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + } + return false; + } + + public boolean isNotificationBundlingEnabled(Context context) { + try { + return sINM.getAllowedAssistantAdjustments(context.getPackageName()) + .contains(Adjustment.KEY_TYPE); + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + } + return false; + } + + public void setNotificationBundlingEnabled(boolean enabled) { + try { + if (enabled) { + sINM.allowAssistantAdjustment(Adjustment.KEY_TYPE); + } else { + sINM.disallowAssistantAdjustment(Adjustment.KEY_TYPE); + } + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + } + } + + public boolean isBundleTypeApproved(@Adjustment.Types int type) { + try { + int[] approved = sINM.getAllowedAdjustmentKeyTypes(); + for (int approvedType : approved) { + if (type == approvedType) { + return true; + } + } + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + } + return false; + } + + public void setBundleTypeState(@Adjustment.Types int type, boolean enabled) { + try { + sINM.setAssistantAdjustmentKeyTypeState(type, enabled); + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + } + } + @VisibleForTesting void setNm(INotificationManager inm) { sINM = inm; 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" 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); 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( diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidsFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidsFragmentTest.java index 25a2cc16b4e..80cc5719e38 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidsFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidsFragmentTest.java @@ -51,6 +51,7 @@ import org.robolectric.annotation.Config; import org.robolectric.shadow.api.Shadow; import java.util.List; +import java.util.Objects; /** Tests for {@link AccessibilityHearingAidsFragment}. */ @RunWith(RobolectricTestRunner.class) @@ -88,7 +89,9 @@ public class AccessibilityHearingAidsFragmentTest { mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID); final List 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); 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"); 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) = diff --git a/tests/robotests/src/com/android/settings/notification/BundleGlobalPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BundleGlobalPreferenceControllerTest.java new file mode 100644 index 00000000000..c389247389f --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/BundleGlobalPreferenceControllerTest.java @@ -0,0 +1,101 @@ +/* + * 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.notification; + +import static android.service.notification.Adjustment.KEY_IMPORTANCE; +import static android.service.notification.Adjustment.KEY_TYPE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Flags; +import android.app.INotificationManager; +import android.content.Context; +import android.platform.test.flag.junit.SetFlagsRule; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +public class BundleGlobalPreferenceControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private static final String PREFERENCE_KEY = "preference_key"; + + private Context mContext; + BundleGlobalPreferenceController mController; + @Mock + INotificationManager mInm; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mSetFlagsRule.enableFlags( + android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION, + Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI); + mController = new BundleGlobalPreferenceController(mContext, PREFERENCE_KEY); + mController.mBackend.setNm(mInm); + } + + @Test + public void isAvailable_flagEnabledNasSupports_shouldReturnTrue() { + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_flagEnabledNasDoesNotSupport_shouldReturnFalse() throws Exception { + when(mInm.getUnsupportedAdjustmentTypes()).thenReturn(List.of(KEY_TYPE)); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_flagDisabledNasSupports_shouldReturnFalse() { + mSetFlagsRule.disableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isChecked() throws Exception { + when(mInm.getAllowedAssistantAdjustments(any())).thenReturn(List.of(KEY_TYPE)); + assertThat(mController.isChecked()).isTrue(); + + when(mInm.getAllowedAssistantAdjustments(any())).thenReturn(List.of(KEY_IMPORTANCE)); + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void setChecked() throws Exception { + mController.setChecked(false); + verify(mInm).disallowAssistantAdjustment(KEY_TYPE); + + mController.setChecked(true); + verify(mInm).allowAssistantAdjustment(KEY_TYPE); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/BundlePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BundlePreferenceControllerTest.java new file mode 100644 index 00000000000..75da895c88c --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/BundlePreferenceControllerTest.java @@ -0,0 +1,91 @@ +/* + * 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.notification; + +import static android.service.notification.Adjustment.KEY_IMPORTANCE; +import static android.service.notification.Adjustment.KEY_TYPE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import android.app.Flags; +import android.app.INotificationManager; +import android.content.Context; +import android.platform.test.flag.junit.SetFlagsRule; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +public class BundlePreferenceControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private static final String PREFERENCE_KEY = "preference_key"; + + private Context mContext; + BundlePreferenceController mController; + @Mock + INotificationManager mInm; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mSetFlagsRule.enableFlags( + android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION, + Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI); + mController = new BundlePreferenceController(mContext, PREFERENCE_KEY); + mController.mBackend.setNm(mInm); + } + + @Test + public void isAvailable_flagEnabledNasSupports_shouldReturnTrue() { + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_flagEnabledNasDoesNotSupport_shouldReturnFalse() throws Exception { + when(mInm.getUnsupportedAdjustmentTypes()).thenReturn(List.of(KEY_TYPE)); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_flagDisabledNasSupports_shouldReturnFalse() { + mSetFlagsRule.disableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void getSummary() throws Exception { + when(mInm.getAllowedAssistantAdjustments(any())).thenReturn(List.of(KEY_TYPE)); + assertThat(mController.getSummary()).isEqualTo("On"); + + when(mInm.getAllowedAssistantAdjustments(any())).thenReturn(List.of(KEY_IMPORTANCE)); + assertThat(mController.getSummary()).isEqualTo("Off"); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/BundleTypePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BundleTypePreferenceControllerTest.java new file mode 100644 index 00000000000..68bb1bb276e --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/BundleTypePreferenceControllerTest.java @@ -0,0 +1,171 @@ +/* + * 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.notification; + +import static android.service.notification.Adjustment.KEY_TYPE; +import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION; +import static android.service.notification.Adjustment.TYPE_NEWS; +import static android.service.notification.Adjustment.TYPE_PROMOTION; +import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Flags; +import android.app.INotificationManager; +import android.content.Context; +import android.os.RemoteException; +import android.platform.test.flag.junit.SetFlagsRule; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +public class BundleTypePreferenceControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private static final String PREFERENCE_KEY = "preference_key"; + + private Context mContext; + BundleTypePreferenceController mController; + @Mock + INotificationManager mInm; + + @Before + public void setUp() throws RemoteException { + MockitoAnnotations.initMocks(this); + when(mInm.getAllowedAssistantAdjustments(any())).thenReturn(List.of(KEY_TYPE)); + when(mInm.getUnsupportedAdjustmentTypes()).thenReturn(List.of()); + mSetFlagsRule.enableFlags( + android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION, + Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI); + mContext = RuntimeEnvironment.application; + mController = new BundleTypePreferenceController(mContext, PREFERENCE_KEY); + mController.mBackend.setNm(mInm); + } + + @Test + public void isAvailable() throws RemoteException { + when(mInm.getAllowedAssistantAdjustments(any())).thenReturn(List.of(KEY_TYPE)); + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_flagEnabledNasDoesNotSupport_shouldReturnFalse() + throws RemoteException { + when(mInm.getUnsupportedAdjustmentTypes()).thenReturn(List.of(KEY_TYPE)); + when(mInm.getAllowedAssistantAdjustments(any())).thenReturn(List.of(KEY_TYPE)); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_flagDisabledNasSupports_shouldReturnFalse() throws RemoteException { + mSetFlagsRule.disableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI); + when(mInm.getUnsupportedAdjustmentTypes()).thenReturn(List.of()); + when(mInm.getAllowedAssistantAdjustments(any())).thenReturn(List.of(KEY_TYPE)); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_flagEnabledNasDisabled_shouldReturnFalse() throws RemoteException { + when(mInm.getUnsupportedAdjustmentTypes()).thenReturn(List.of()); + when(mInm.getAllowedAssistantAdjustments(any())).thenReturn(List.of()); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isChecked_promotions() throws RemoteException { + mController = new BundleTypePreferenceController(mContext, + BundleTypePreferenceController.PROMO_KEY); + + when(mInm.getAllowedAdjustmentKeyTypes()).thenReturn(new int[]{TYPE_PROMOTION}); + assertThat(mController.isChecked()).isTrue(); + + when(mInm.getAllowedAdjustmentKeyTypes()).thenReturn(new int[]{}); + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void isChecked_news() throws RemoteException { + mController = new BundleTypePreferenceController(mContext, + BundleTypePreferenceController.NEWS_KEY); + + when(mInm.getAllowedAdjustmentKeyTypes()).thenReturn(new int[]{TYPE_NEWS}); + assertThat(mController.isChecked()).isTrue(); + + when(mInm.getAllowedAdjustmentKeyTypes()).thenReturn(new int[]{}); + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void isChecked_social() throws RemoteException { + mController = new BundleTypePreferenceController(mContext, + BundleTypePreferenceController.SOCIAL_KEY); + + when(mInm.getAllowedAdjustmentKeyTypes()).thenReturn(new int[]{TYPE_SOCIAL_MEDIA}); + assertThat(mController.isChecked()).isTrue(); + + when(mInm.getAllowedAdjustmentKeyTypes()).thenReturn(new int[]{}); + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void isChecked_recs() throws RemoteException { + mController = new BundleTypePreferenceController(mContext, + BundleTypePreferenceController.RECS_KEY); + + when(mInm.getAllowedAdjustmentKeyTypes()).thenReturn( + new int[]{TYPE_CONTENT_RECOMMENDATION}); + assertThat(mController.isChecked()).isTrue(); + + when(mInm.getAllowedAdjustmentKeyTypes()).thenReturn(new int[]{}); + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void isChecked_mixed() throws RemoteException { + mController = new BundleTypePreferenceController(mContext, + BundleTypePreferenceController.RECS_KEY); + + when(mInm.getAllowedAdjustmentKeyTypes()).thenReturn( + new int[]{TYPE_PROMOTION, TYPE_CONTENT_RECOMMENDATION}); + assertThat(mController.isChecked()).isTrue(); + } + + @Test + public void setChecked() throws RemoteException { + mController = new BundleTypePreferenceController(mContext, + BundleTypePreferenceController.PROMO_KEY); + mController.setChecked(false); + verify(mInm).setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false); + + mController.setChecked(true); + verify(mInm).setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, true); + } +} 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..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 @@ -78,6 +79,7 @@ class Injector(step: FingerprintNavigationStep.UiStep) { override val isEnabled: Boolean get() = true override fun announce(clazz: Class<*>, announcement: CharSequence?) {} + override fun interrupt() {} } var foldStateInteractor = @@ -97,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 a8c5e684d33..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 @@ -111,6 +112,8 @@ class FingerprintEnrollFindSensorViewModelV2Test { override val isEnabled: Boolean get() = true override fun announce(clazz: Class<*>, announcement: CharSequence?) {} + override fun interrupt() { + } } foldStateInteractor = object : FoldStateInteractor { @@ -128,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(