diff options
9 files changed, 427 insertions, 439 deletions
diff --git a/res/xml/bundle_notifications_settings.xml b/res/xml/bundle_notifications_settings.xml index 3ff8c053273..0f153de81a6 100644 --- a/res/xml/bundle_notifications_settings.xml +++ b/res/xml/bundle_notifications_settings.xml @@ -32,30 +32,29 @@ settings:searchable="false" android:title="@string/notification_bundle_description"/> - <com.android.settingslib.widget.MainSwitchPreference - android:key="global_pref" - android:title="@string/notification_bundle_main_control_title" - settings:controller="com.android.settings.notification.BundleGlobalPreferenceController" /> + <PreferenceCategory + android:key="enabled_settings"> + + <com.android.settingslib.widget.MainSwitchPreference + android:key="global_pref" + android:title="@string/notification_bundle_main_control_title" /> - <CheckBoxPreference - android:key="promotions" - android:title="@*android:string/promotional_notification_channel_label" - settings:controller="com.android.settings.notification.BundleTypePreferenceController"/> + <CheckBoxPreference + android:key="promotions" + android:title="@*android:string/promotional_notification_channel_label"/> - <CheckBoxPreference - android:key="news" - android:title="@*android:string/news_notification_channel_label" - settings:controller="com.android.settings.notification.BundleTypePreferenceController"/> + <CheckBoxPreference + android:key="news" + android:title="@*android:string/news_notification_channel_label"/> - <CheckBoxPreference - android:key="social" - android:title="@*android:string/social_notification_channel_label" - settings:controller="com.android.settings.notification.BundleTypePreferenceController"/> + <CheckBoxPreference + android:key="social" + android:title="@*android:string/social_notification_channel_label"/> - <CheckBoxPreference - android:key="recs" - android:title="@*android:string/recs_notification_channel_label" - settings:controller="com.android.settings.notification.BundleTypePreferenceController"/> + <CheckBoxPreference + android:key="recs" + android:title="@*android:string/recs_notification_channel_label" /> + </PreferenceCategory> <PreferenceCategory android:key="notification_bundle_excluded_apps_list" diff --git a/src/com/android/settings/notification/BundleCombinedPreferenceController.java b/src/com/android/settings/notification/BundleCombinedPreferenceController.java new file mode 100644 index 00000000000..cf9d7b8be4a --- /dev/null +++ b/src/com/android/settings/notification/BundleCombinedPreferenceController.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import android.app.Flags; +import android.content.Context; +import android.service.notification.Adjustment; +import android.util.ArrayMap; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.TwoStatePreference; + +import com.android.settings.core.BasePreferenceController; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Preference controller governing both the global and individual type-based bundle preferences. + */ +public class BundleCombinedPreferenceController extends BasePreferenceController { + + static final String GLOBAL_KEY = "global_pref"; + static final String PROMO_KEY = "promotions"; + static final String NEWS_KEY = "news"; + static final String SOCIAL_KEY = "social"; + static final String RECS_KEY = "recs"; + + static final List<String> ALL_PREF_TYPES = List.of(PROMO_KEY, NEWS_KEY, SOCIAL_KEY, RECS_KEY); + + @NonNull NotificationBackend mBackend; + + private @Nullable TwoStatePreference mGlobalPref; + private Map<String, TwoStatePreference> mTypePrefs = new ArrayMap<>(); + + public BundleCombinedPreferenceController(@NonNull Context context, @NonNull String prefKey, + @NonNull NotificationBackend backend) { + super(context, prefKey); + mBackend = backend; + } + + @Override + @AvailabilityStatus + public int getAvailabilityStatus() { + if (Flags.notificationClassificationUi() && mBackend.isNotificationBundlingSupported()) { + return AVAILABLE; + } + return CONDITIONALLY_UNAVAILABLE; + } + + @Override + public void updateState(Preference preference) { + PreferenceCategory category = (PreferenceCategory) preference; + + // Find and cache relevant preferences for later updates, then set values + mGlobalPref = category.findPreference(GLOBAL_KEY); + if (mGlobalPref != null) { + mGlobalPref.setOnPreferenceChangeListener(mGlobalPrefListener); + } + for (String key : ALL_PREF_TYPES) { + TwoStatePreference typePref = category.findPreference(key); + if (typePref != null) { + mTypePrefs.put(key, typePref); + typePref.setOnPreferenceChangeListener(getListenerForType(key)); + } + } + + updatePrefValues(); + } + + void updatePrefValues() { + boolean isBundlingEnabled = mBackend.isNotificationBundlingEnabled(mContext); + Set<Integer> allowedTypes = mBackend.getAllowedBundleTypes(); + + // State check: if bundling is globally enabled, but there are no allowed bundle types, + // disable the global bundling state from here before proceeding. + if (isBundlingEnabled && allowedTypes.size() == 0) { + mBackend.setNotificationBundlingEnabled(false); + isBundlingEnabled = false; + } + + if (mGlobalPref != null) { + mGlobalPref.setChecked(isBundlingEnabled); + } + + for (String key : mTypePrefs.keySet()) { + TwoStatePreference typePref = mTypePrefs.get(key); + // checkboxes for individual types should only be active if the global switch is on + typePref.setVisible(isBundlingEnabled); + if (isBundlingEnabled) { + typePref.setChecked(allowedTypes.contains(getBundleTypeForKey(key))); + } + } + } + + private Preference.OnPreferenceChangeListener mGlobalPrefListener = (p, val) -> { + boolean checked = (boolean) val; + mBackend.setNotificationBundlingEnabled(checked); + // update state to hide or show preferences for individual types + updatePrefValues(); + return true; + }; + + // Returns a preference listener for the given pref key that: + // * sets the backend state for whether that type is enabled + // * if it is disabled, trigger a new update sync global switch if needed + private Preference.OnPreferenceChangeListener getListenerForType(String prefKey) { + return (p, val) -> { + boolean checked = (boolean) val; + mBackend.setBundleTypeState(getBundleTypeForKey(prefKey), checked); + if (!checked) { + // goes from checked to un-checked; update state in case this was the last enabled + // individual category + updatePrefValues(); + } + return true; + }; + } + + static @Adjustment.Types int getBundleTypeForKey(String preferenceKey) { + if (PROMO_KEY.equals(preferenceKey)) { + return Adjustment.TYPE_PROMOTION; + } else if (NEWS_KEY.equals(preferenceKey)) { + return Adjustment.TYPE_NEWS; + } else if (SOCIAL_KEY.equals(preferenceKey)) { + return Adjustment.TYPE_SOCIAL_MEDIA; + } else if (RECS_KEY.equals(preferenceKey)) { + return Adjustment.TYPE_CONTENT_RECOMMENDATION; + } + return Adjustment.TYPE_OTHER; + } + +} diff --git a/src/com/android/settings/notification/BundleGlobalPreferenceController.java b/src/com/android/settings/notification/BundleGlobalPreferenceController.java deleted file mode 100644 index 4df36ca716c..00000000000 --- a/src/com/android/settings/notification/BundleGlobalPreferenceController.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.core.TogglePreferenceController; - -public class BundleGlobalPreferenceController extends TogglePreferenceController { - - 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/BundlePreferenceFragment.java b/src/com/android/settings/notification/BundlePreferenceFragment.java index 4a61f9e54af..fe854a7f6bc 100644 --- a/src/com/android/settings/notification/BundlePreferenceFragment.java +++ b/src/com/android/settings/notification/BundlePreferenceFragment.java @@ -16,24 +16,23 @@ package com.android.settings.notification; -import static android.service.notification.Adjustment.KEY_SUMMARIZATION; import static android.service.notification.Adjustment.KEY_TYPE; import android.app.Activity; import android.app.Application; +import android.app.Flags; 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.applications.ApplicationsState; +import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.search.SearchIndexable; -import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.List; /** * Fragment for bundled notifications. @@ -41,6 +40,8 @@ import org.jetbrains.annotations.NotNull; @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) public class BundlePreferenceFragment extends DashboardFragment { + private static final String BUNDLE_CATEGORY_KEY = "enabled_settings"; + @Override public int getMetricsCategory() { return SettingsEnums.BUNDLED_NOTIFICATIONS; @@ -50,6 +51,15 @@ public class BundlePreferenceFragment extends DashboardFragment { protected int getPreferenceScreenResId() { return R.xml.bundle_notifications_settings; } + + @Override + protected List<AbstractPreferenceController> createPreferenceControllers(Context context) { + final List<AbstractPreferenceController> controllers = new ArrayList<>(); + controllers.add(new BundleCombinedPreferenceController(context, BUNDLE_CATEGORY_KEY, + new NotificationBackend())); + return controllers; + } + @Override protected String getLogTag() { return "BundlePreferenceFragment"; diff --git a/src/com/android/settings/notification/BundleTypePreferenceController.java b/src/com/android/settings/notification/BundleTypePreferenceController.java deleted file mode 100644 index d0786931ed7..00000000000 --- a/src/com/android/settings/notification/BundleTypePreferenceController.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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.core.TogglePreferenceController; - -public class BundleTypePreferenceController extends TogglePreferenceController { - - 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/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java index 31972edcc1f..45bf9e3e23a 100644 --- a/src/com/android/settings/notification/NotificationBackend.java +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -751,6 +751,19 @@ public class NotificationBackend { return false; } + public Set<Integer> getAllowedBundleTypes() { + try { + Set<Integer> allowed = new HashSet<>(); + for (int type : sINM.getAllowedAdjustmentKeyTypes()) { + allowed.add(type); + } + return allowed; + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return new HashSet<>(); + } + } + public void setBundleTypeState(@Adjustment.Types int type, boolean enabled) { try { sINM.setAssistantAdjustmentKeyTypeState(type, enabled); diff --git a/tests/robotests/src/com/android/settings/notification/BundleCombinedPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BundleCombinedPreferenceControllerTest.java new file mode 100644 index 00000000000..6d1c2c99a36 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/BundleCombinedPreferenceControllerTest.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Flags; +import android.content.Context; +import android.os.RemoteException; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.service.notification.Adjustment; + +import androidx.preference.CheckBoxPreference; +import androidx.preference.PreferenceCategory; + +import com.android.settingslib.widget.MainSwitchPreference; + +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.Set; + +@RunWith(RobolectricTestRunner.class) +@EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) +public class BundleCombinedPreferenceControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private static final String PREFERENCE_KEY = "preference_key"; + + private Context mContext; + private BundleCombinedPreferenceController mController; + + @Mock + private NotificationBackend mBackend; + + @Mock + private PreferenceCategory mPrefCategory; + + private MainSwitchPreference mGlobalSwitch; + private CheckBoxPreference mPromoCheckbox, mNewsCheckbox, mSocialCheckbox, mRecsCheckbox; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.getApplication(); + mController = new BundleCombinedPreferenceController(mContext, PREFERENCE_KEY, mBackend); + + // preference category/controller initiation + mGlobalSwitch = new MainSwitchPreference(mContext); + when(mPrefCategory.findPreference( + BundleCombinedPreferenceController.GLOBAL_KEY)).thenReturn(mGlobalSwitch); + mPromoCheckbox = new CheckBoxPreference(mContext); + when(mPrefCategory.findPreference(BundleCombinedPreferenceController.PROMO_KEY)).thenReturn( + mPromoCheckbox); + mNewsCheckbox = new CheckBoxPreference(mContext); + when(mPrefCategory.findPreference(BundleCombinedPreferenceController.NEWS_KEY)).thenReturn( + mNewsCheckbox); + mSocialCheckbox = new CheckBoxPreference(mContext); + when(mPrefCategory.findPreference( + BundleCombinedPreferenceController.SOCIAL_KEY)).thenReturn(mSocialCheckbox); + mRecsCheckbox = new CheckBoxPreference(mContext); + when(mPrefCategory.findPreference(BundleCombinedPreferenceController.RECS_KEY)).thenReturn( + mRecsCheckbox); + + mController.updateState(mPrefCategory); + } + + @Test + @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) + public void isAvailable_flagEnabledNasSupports_shouldReturnTrue() { + when(mBackend.isNotificationBundlingSupported()).thenReturn(true); + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) + public void isAvailable_flagEnabledNasDoesNotSupport_shouldReturnFalse() + throws RemoteException { + when(mBackend.isNotificationBundlingSupported()).thenReturn(false); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + @DisableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) + public void isAvailable_flagDisabledNasSupports_shouldReturnFalse() throws RemoteException { + when(mBackend.isNotificationBundlingSupported()).thenReturn(true); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) + public void updatePrefValues_reflectsSettings() { + // bundling is enabled globally + when(mBackend.isNotificationBundlingEnabled(any())).thenReturn(true); + + // allowed key types are promos & news + when(mBackend.getAllowedBundleTypes()).thenReturn(Set.of(Adjustment.TYPE_PROMOTION, + Adjustment.TYPE_NEWS)); + + mController.updatePrefValues(); + assertThat(mGlobalSwitch.isChecked()).isTrue(); + assertThat(mPromoCheckbox.isChecked()).isTrue(); + assertThat(mNewsCheckbox.isChecked()).isTrue(); + assertThat(mRecsCheckbox.isChecked()).isFalse(); + assertThat(mSocialCheckbox.isChecked()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) + public void updatePrefValues_typesGoneWhenGlobalOff() { + when(mBackend.isNotificationBundlingEnabled(any())).thenReturn(false); + when(mBackend.getAllowedBundleTypes()).thenReturn(Set.of(Adjustment.TYPE_PROMOTION, + Adjustment.TYPE_NEWS)); + + mController.updatePrefValues(); + assertThat(mGlobalSwitch.isChecked()).isFalse(); + assertThat(mPromoCheckbox.isVisible()).isFalse(); + assertThat(mNewsCheckbox.isVisible()).isFalse(); + assertThat(mRecsCheckbox.isVisible()).isFalse(); + assertThat(mSocialCheckbox.isVisible()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) + public void turnOffGlobalSwitch_updatesBackendAndTypeSwitches() { + // Initial state: global allowed + some types set + when(mBackend.isNotificationBundlingEnabled(any())).thenReturn(true); + when(mBackend.getAllowedBundleTypes()).thenReturn(Set.of(Adjustment.TYPE_PROMOTION, + Adjustment.TYPE_NEWS)); + mController.updatePrefValues(); + + // Simulate the global switch turning off. This also requires telling the mock backend to + // start returning false before the click listener updates pref values + when(mBackend.isNotificationBundlingEnabled(any())).thenReturn(false); + mGlobalSwitch.getOnPreferenceChangeListener().onPreferenceChange(mGlobalSwitch, false); + verify(mBackend, times(1)).setNotificationBundlingEnabled(false); + + // All individual type checkboxes should now not be visible. + assertThat(mPromoCheckbox.isVisible()).isFalse(); + assertThat(mNewsCheckbox.isVisible()).isFalse(); + assertThat(mRecsCheckbox.isVisible()).isFalse(); + assertThat(mSocialCheckbox.isVisible()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) + public void turnOnGlobalSwitch_updatesBackendAndTypeSwitches() { + when(mBackend.isNotificationBundlingEnabled(any())).thenReturn(false); + when(mBackend.getAllowedBundleTypes()).thenReturn(Set.of(Adjustment.TYPE_PROMOTION, + Adjustment.TYPE_NEWS)); + mController.updatePrefValues(); + + when(mBackend.isNotificationBundlingEnabled(any())).thenReturn(true); + mGlobalSwitch.getOnPreferenceChangeListener().onPreferenceChange(mGlobalSwitch, true); + verify(mBackend, times(1)).setNotificationBundlingEnabled(true); + + // type checkboxes should now exist & be checked accordingly to their state + assertThat(mPromoCheckbox.isChecked()).isTrue(); + assertThat(mNewsCheckbox.isChecked()).isTrue(); + assertThat(mRecsCheckbox.isChecked()).isFalse(); + assertThat(mSocialCheckbox.isChecked()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) + public void turnOnTypeBundle_updatesBackend_doesNotChangeGlobalSwitch() { + when(mBackend.isNotificationBundlingEnabled(any())).thenReturn(true); + when(mBackend.getAllowedBundleTypes()).thenReturn(Set.of(Adjustment.TYPE_SOCIAL_MEDIA)); + mController.updatePrefValues(); + + mRecsCheckbox.getOnPreferenceChangeListener().onPreferenceChange(mRecsCheckbox, true); + + // recs bundle setting should be updated in the backend, and global switch unchanged + verify(mBackend).setBundleTypeState(Adjustment.TYPE_CONTENT_RECOMMENDATION, true); + verify(mBackend, never()).setNotificationBundlingEnabled(anyBoolean()); + } + + @Test + @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) + public void turnOffTypeBundle_lastOneChangesGlobalSwitch() { + when(mBackend.isNotificationBundlingEnabled(any())).thenReturn(true); + when(mBackend.getAllowedBundleTypes()).thenReturn(Set.of(Adjustment.TYPE_SOCIAL_MEDIA, + Adjustment.TYPE_CONTENT_RECOMMENDATION)); + mController.updatePrefValues(); + + // Turning off one should update state, but not turn off the global setting + when(mBackend.getAllowedBundleTypes()).thenReturn(Set.of(Adjustment.TYPE_SOCIAL_MEDIA)); + mRecsCheckbox.getOnPreferenceChangeListener().onPreferenceChange(mRecsCheckbox, false); + verify(mBackend).setBundleTypeState(Adjustment.TYPE_CONTENT_RECOMMENDATION, false); + + // Now turn off the second + when(mBackend.getAllowedBundleTypes()).thenReturn(Set.of()); + mSocialCheckbox.getOnPreferenceChangeListener().onPreferenceChange(mSocialCheckbox, false); + verify(mBackend).setBundleTypeState(Adjustment.TYPE_SOCIAL_MEDIA, false); + + // This update should trigger a call to turn off the global switch + verify(mBackend).setNotificationBundlingEnabled(false); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/BundleGlobalPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BundleGlobalPreferenceControllerTest.java deleted file mode 100644 index c389247389f..00000000000 --- a/tests/robotests/src/com/android/settings/notification/BundleGlobalPreferenceControllerTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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/BundleTypePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BundleTypePreferenceControllerTest.java deleted file mode 100644 index 68bb1bb276e..00000000000 --- a/tests/robotests/src/com/android/settings/notification/BundleTypePreferenceControllerTest.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * 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); - } -} |