1 /* 2 * Copyright (C) 2025 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.notification; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.mockito.ArgumentMatchers.any; 22 import static org.mockito.ArgumentMatchers.anyBoolean; 23 import static org.mockito.Mockito.never; 24 import static org.mockito.Mockito.times; 25 import static org.mockito.Mockito.verify; 26 import static org.mockito.Mockito.when; 27 28 import android.app.Flags; 29 import android.content.Context; 30 import android.os.RemoteException; 31 import android.platform.test.annotations.DisableFlags; 32 import android.platform.test.annotations.EnableFlags; 33 import android.platform.test.flag.junit.SetFlagsRule; 34 import android.service.notification.Adjustment; 35 36 import androidx.preference.CheckBoxPreference; 37 import androidx.preference.PreferenceCategory; 38 39 import com.android.settingslib.widget.MainSwitchPreference; 40 41 import org.junit.Before; 42 import org.junit.Rule; 43 import org.junit.Test; 44 import org.junit.runner.RunWith; 45 import org.mockito.Mock; 46 import org.mockito.MockitoAnnotations; 47 import org.robolectric.RobolectricTestRunner; 48 import org.robolectric.RuntimeEnvironment; 49 50 import java.util.Set; 51 52 @RunWith(RobolectricTestRunner.class) 53 @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) 54 public class BundleCombinedPreferenceControllerTest { 55 @Rule 56 public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); 57 58 private static final String PREFERENCE_KEY = "preference_key"; 59 60 private Context mContext; 61 private BundleCombinedPreferenceController mController; 62 63 @Mock 64 private NotificationBackend mBackend; 65 66 @Mock 67 private PreferenceCategory mPrefCategory; 68 69 private MainSwitchPreference mGlobalSwitch; 70 private CheckBoxPreference mPromoCheckbox, mNewsCheckbox, mSocialCheckbox, mRecsCheckbox; 71 72 @Before setUp()73 public void setUp() { 74 MockitoAnnotations.initMocks(this); 75 mContext = RuntimeEnvironment.getApplication(); 76 mController = new BundleCombinedPreferenceController(mContext, PREFERENCE_KEY, mBackend); 77 78 // preference category/controller initiation 79 mGlobalSwitch = new MainSwitchPreference(mContext); 80 when(mPrefCategory.findPreference( 81 BundleCombinedPreferenceController.GLOBAL_KEY)).thenReturn(mGlobalSwitch); 82 mPromoCheckbox = new CheckBoxPreference(mContext); 83 when(mPrefCategory.findPreference(BundleCombinedPreferenceController.PROMO_KEY)).thenReturn( 84 mPromoCheckbox); 85 mNewsCheckbox = new CheckBoxPreference(mContext); 86 when(mPrefCategory.findPreference(BundleCombinedPreferenceController.NEWS_KEY)).thenReturn( 87 mNewsCheckbox); 88 mSocialCheckbox = new CheckBoxPreference(mContext); 89 when(mPrefCategory.findPreference( 90 BundleCombinedPreferenceController.SOCIAL_KEY)).thenReturn(mSocialCheckbox); 91 mRecsCheckbox = new CheckBoxPreference(mContext); 92 when(mPrefCategory.findPreference(BundleCombinedPreferenceController.RECS_KEY)).thenReturn( 93 mRecsCheckbox); 94 95 mController.updateState(mPrefCategory); 96 } 97 98 @Test 99 @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) isAvailable_flagEnabledNasSupports_shouldReturnTrue()100 public void isAvailable_flagEnabledNasSupports_shouldReturnTrue() { 101 when(mBackend.isNotificationBundlingSupported()).thenReturn(true); 102 assertThat(mController.isAvailable()).isTrue(); 103 } 104 105 @Test 106 @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) isAvailable_flagEnabledNasDoesNotSupport_shouldReturnFalse()107 public void isAvailable_flagEnabledNasDoesNotSupport_shouldReturnFalse() 108 throws RemoteException { 109 when(mBackend.isNotificationBundlingSupported()).thenReturn(false); 110 assertThat(mController.isAvailable()).isFalse(); 111 } 112 113 @Test 114 @DisableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) isAvailable_flagDisabledNasSupports_shouldReturnFalse()115 public void isAvailable_flagDisabledNasSupports_shouldReturnFalse() throws RemoteException { 116 when(mBackend.isNotificationBundlingSupported()).thenReturn(true); 117 assertThat(mController.isAvailable()).isFalse(); 118 } 119 120 @Test 121 @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) updatePrefValues_reflectsSettings()122 public void updatePrefValues_reflectsSettings() { 123 // bundling is enabled globally 124 when(mBackend.isNotificationBundlingEnabled(any())).thenReturn(true); 125 126 // allowed key types are promos & news 127 when(mBackend.getAllowedBundleTypes()).thenReturn(Set.of(Adjustment.TYPE_PROMOTION, 128 Adjustment.TYPE_NEWS)); 129 130 mController.updatePrefValues(); 131 assertThat(mGlobalSwitch.isChecked()).isTrue(); 132 assertThat(mPromoCheckbox.isChecked()).isTrue(); 133 assertThat(mNewsCheckbox.isChecked()).isTrue(); 134 assertThat(mRecsCheckbox.isChecked()).isFalse(); 135 assertThat(mSocialCheckbox.isChecked()).isFalse(); 136 } 137 138 @Test 139 @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) updatePrefValues_typesGoneWhenGlobalOff()140 public void updatePrefValues_typesGoneWhenGlobalOff() { 141 when(mBackend.isNotificationBundlingEnabled(any())).thenReturn(false); 142 when(mBackend.getAllowedBundleTypes()).thenReturn(Set.of(Adjustment.TYPE_PROMOTION, 143 Adjustment.TYPE_NEWS)); 144 145 mController.updatePrefValues(); 146 assertThat(mGlobalSwitch.isChecked()).isFalse(); 147 assertThat(mPromoCheckbox.isVisible()).isFalse(); 148 assertThat(mNewsCheckbox.isVisible()).isFalse(); 149 assertThat(mRecsCheckbox.isVisible()).isFalse(); 150 assertThat(mSocialCheckbox.isVisible()).isFalse(); 151 } 152 153 @Test 154 @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) turnOffGlobalSwitch_updatesBackendAndTypeSwitches()155 public void turnOffGlobalSwitch_updatesBackendAndTypeSwitches() { 156 // Initial state: global allowed + some types set 157 when(mBackend.isNotificationBundlingEnabled(any())).thenReturn(true); 158 when(mBackend.getAllowedBundleTypes()).thenReturn(Set.of(Adjustment.TYPE_PROMOTION, 159 Adjustment.TYPE_NEWS)); 160 mController.updatePrefValues(); 161 162 // Simulate the global switch turning off. This also requires telling the mock backend to 163 // start returning false before the click listener updates pref values 164 when(mBackend.isNotificationBundlingEnabled(any())).thenReturn(false); 165 mGlobalSwitch.getOnPreferenceChangeListener().onPreferenceChange(mGlobalSwitch, false); 166 verify(mBackend, times(1)).setNotificationBundlingEnabled(false); 167 168 // All individual type checkboxes should now not be visible. 169 assertThat(mPromoCheckbox.isVisible()).isFalse(); 170 assertThat(mNewsCheckbox.isVisible()).isFalse(); 171 assertThat(mRecsCheckbox.isVisible()).isFalse(); 172 assertThat(mSocialCheckbox.isVisible()).isFalse(); 173 } 174 175 @Test 176 @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) turnOnGlobalSwitch_updatesBackendAndTypeSwitches()177 public void turnOnGlobalSwitch_updatesBackendAndTypeSwitches() { 178 when(mBackend.isNotificationBundlingEnabled(any())).thenReturn(false); 179 when(mBackend.getAllowedBundleTypes()).thenReturn(Set.of(Adjustment.TYPE_PROMOTION, 180 Adjustment.TYPE_NEWS)); 181 mController.updatePrefValues(); 182 183 when(mBackend.isNotificationBundlingEnabled(any())).thenReturn(true); 184 mGlobalSwitch.getOnPreferenceChangeListener().onPreferenceChange(mGlobalSwitch, true); 185 verify(mBackend, times(1)).setNotificationBundlingEnabled(true); 186 187 // type checkboxes should now exist & be checked accordingly to their state 188 assertThat(mPromoCheckbox.isChecked()).isTrue(); 189 assertThat(mNewsCheckbox.isChecked()).isTrue(); 190 assertThat(mRecsCheckbox.isChecked()).isFalse(); 191 assertThat(mSocialCheckbox.isChecked()).isFalse(); 192 } 193 194 @Test 195 @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) turnOnTypeBundle_updatesBackend_doesNotChangeGlobalSwitch()196 public void turnOnTypeBundle_updatesBackend_doesNotChangeGlobalSwitch() { 197 when(mBackend.isNotificationBundlingEnabled(any())).thenReturn(true); 198 when(mBackend.getAllowedBundleTypes()).thenReturn(Set.of(Adjustment.TYPE_SOCIAL_MEDIA)); 199 mController.updatePrefValues(); 200 201 mRecsCheckbox.getOnPreferenceChangeListener().onPreferenceChange(mRecsCheckbox, true); 202 203 // recs bundle setting should be updated in the backend, and global switch unchanged 204 verify(mBackend).setBundleTypeState(Adjustment.TYPE_CONTENT_RECOMMENDATION, true); 205 verify(mBackend, never()).setNotificationBundlingEnabled(anyBoolean()); 206 } 207 208 @Test 209 @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) turnOffTypeBundle_lastOneChangesGlobalSwitch()210 public void turnOffTypeBundle_lastOneChangesGlobalSwitch() { 211 when(mBackend.isNotificationBundlingEnabled(any())).thenReturn(true); 212 when(mBackend.getAllowedBundleTypes()).thenReturn(Set.of(Adjustment.TYPE_SOCIAL_MEDIA, 213 Adjustment.TYPE_CONTENT_RECOMMENDATION)); 214 mController.updatePrefValues(); 215 216 // Turning off one should update state, but not turn off the global setting 217 when(mBackend.getAllowedBundleTypes()).thenReturn(Set.of(Adjustment.TYPE_SOCIAL_MEDIA)); 218 mRecsCheckbox.getOnPreferenceChangeListener().onPreferenceChange(mRecsCheckbox, false); 219 verify(mBackend).setBundleTypeState(Adjustment.TYPE_CONTENT_RECOMMENDATION, false); 220 221 // Now turn off the second 222 when(mBackend.getAllowedBundleTypes()).thenReturn(Set.of()); 223 mSocialCheckbox.getOnPreferenceChangeListener().onPreferenceChange(mSocialCheckbox, false); 224 verify(mBackend).setBundleTypeState(Adjustment.TYPE_SOCIAL_MEDIA, false); 225 226 // This update should trigger a call to turn off the global switch 227 verify(mBackend).setNotificationBundlingEnabled(false); 228 } 229 } 230