1 /* 2 * Copyright (C) 2023 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.server.notification; 18 19 import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT; 20 import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF; 21 import static android.service.notification.ZenModeConfig.ORIGIN_APP; 22 import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI; 23 24 import static com.google.common.truth.Truth.assertThat; 25 26 import static org.mockito.ArgumentMatchers.any; 27 import static org.mockito.ArgumentMatchers.anyBoolean; 28 import static org.mockito.ArgumentMatchers.anyFloat; 29 import static org.mockito.ArgumentMatchers.anyInt; 30 import static org.mockito.ArgumentMatchers.anyString; 31 import static org.mockito.ArgumentMatchers.argThat; 32 import static org.mockito.ArgumentMatchers.eq; 33 import static org.mockito.Mockito.doThrow; 34 import static org.mockito.Mockito.mock; 35 import static org.mockito.Mockito.never; 36 import static org.mockito.Mockito.spy; 37 import static org.mockito.Mockito.verify; 38 import static org.mockito.Mockito.verifyNoMoreInteractions; 39 import static org.mockito.Mockito.when; 40 41 import android.app.KeyguardManager; 42 import android.app.UiModeManager; 43 import android.app.WallpaperManager; 44 import android.content.BroadcastReceiver; 45 import android.content.Intent; 46 import android.content.IntentFilter; 47 import android.hardware.display.ColorDisplayManager; 48 import android.os.PowerManager; 49 import android.platform.test.flag.junit.SetFlagsRule; 50 import android.service.notification.ZenDeviceEffects; 51 import android.testing.TestableContext; 52 53 import androidx.test.InstrumentationRegistry; 54 55 import com.google.testing.junit.testparameterinjector.TestParameter; 56 import com.google.testing.junit.testparameterinjector.TestParameterInjector; 57 import com.google.testing.junit.testparameterinjector.TestParameters; 58 59 import org.junit.Before; 60 import org.junit.Rule; 61 import org.junit.Test; 62 import org.junit.runner.RunWith; 63 import org.mockito.ArgumentCaptor; 64 import org.mockito.Mock; 65 import org.mockito.MockitoAnnotations; 66 67 import java.io.PrintWriter; 68 import java.io.StringWriter; 69 70 @RunWith(TestParameterInjector.class) 71 public class DefaultDeviceEffectsApplierTest { 72 73 @Rule 74 public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); 75 76 private TestableContext mContext; 77 private DefaultDeviceEffectsApplier mApplier; 78 @Mock PowerManager mPowerManager; 79 @Mock ColorDisplayManager mColorDisplayManager; 80 @Mock KeyguardManager mKeyguardManager; 81 @Mock UiModeManager mUiModeManager; 82 @Mock WallpaperManager mWallpaperManager; 83 84 @Before setUp()85 public void setUp() { 86 MockitoAnnotations.initMocks(this); 87 mContext = spy(new TestableContext(InstrumentationRegistry.getContext(), null)); 88 mContext.addMockSystemService(PowerManager.class, mPowerManager); 89 mContext.addMockSystemService(ColorDisplayManager.class, mColorDisplayManager); 90 mContext.addMockSystemService(KeyguardManager.class, mKeyguardManager); 91 mContext.addMockSystemService(UiModeManager.class, mUiModeManager); 92 mContext.addMockSystemService(WallpaperManager.class, mWallpaperManager); 93 when(mWallpaperManager.isWallpaperSupported()).thenReturn(true); 94 95 mApplier = new DefaultDeviceEffectsApplier(mContext); 96 verify(mWallpaperManager).isWallpaperSupported(); 97 98 ZenLog.clear(); 99 } 100 101 @Test apply_appliesEffects()102 public void apply_appliesEffects() { 103 ZenDeviceEffects effects = new ZenDeviceEffects.Builder() 104 .setShouldSuppressAmbientDisplay(true) 105 .setShouldDimWallpaper(true) 106 .setShouldDisplayGrayscale(true) 107 .setShouldUseNightMode(true) 108 .build(); 109 mApplier.apply(effects, ORIGIN_USER_IN_SYSTEMUI); 110 111 verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true)); 112 verify(mColorDisplayManager).setSaturationLevel(eq(0)); 113 verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f)); 114 verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT)); 115 } 116 117 @Test apply_logsToZenLog()118 public void apply_logsToZenLog() { 119 when(mPowerManager.isInteractive()).thenReturn(true); 120 ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = 121 ArgumentCaptor.forClass(BroadcastReceiver.class); 122 ArgumentCaptor<IntentFilter> intentFilterCaptor = 123 ArgumentCaptor.forClass(IntentFilter.class); 124 125 ZenDeviceEffects effects = new ZenDeviceEffects.Builder() 126 .setShouldDisplayGrayscale(true) 127 .setShouldUseNightMode(true) 128 .build(); 129 mApplier.apply(effects, ORIGIN_APP); 130 131 String zenLog = getZenLog(); 132 assertThat(zenLog).contains("apply_device_effect: displayGrayscale -> true"); 133 assertThat(zenLog).contains("schedule_device_effect: nightMode -> true"); 134 assertThat(zenLog).doesNotContain("apply_device_effect: nightMode"); 135 136 verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(), 137 intentFilterCaptor.capture(), anyInt()); 138 BroadcastReceiver screenOffReceiver = broadcastReceiverCaptor.getValue(); 139 screenOffReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); 140 141 zenLog = getZenLog(); 142 assertThat(zenLog).contains("apply_device_effect: nightMode -> true"); 143 } 144 getZenLog()145 private static String getZenLog() { 146 StringWriter zenLogWriter = new StringWriter(); 147 ZenLog.dump(new PrintWriter(zenLogWriter), ""); 148 return zenLogWriter.toString(); 149 } 150 151 @Test apply_removesEffects()152 public void apply_removesEffects() { 153 ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder() 154 .setShouldSuppressAmbientDisplay(true) 155 .setShouldDimWallpaper(true) 156 .setShouldDisplayGrayscale(true) 157 .setShouldUseNightMode(true) 158 .build(); 159 mApplier.apply(previousEffects, ORIGIN_USER_IN_SYSTEMUI); 160 verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true)); 161 verify(mColorDisplayManager).setSaturationLevel(eq(0)); 162 verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f)); 163 verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT)); 164 165 ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build(); 166 mApplier.apply(noEffects, ORIGIN_USER_IN_SYSTEMUI); 167 168 verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false)); 169 verify(mColorDisplayManager).setSaturationLevel(eq(100)); 170 verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f)); 171 verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_OFF)); 172 } 173 174 @Test apply_removesOnlyPreviouslyAppliedEffects()175 public void apply_removesOnlyPreviouslyAppliedEffects() { 176 ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder() 177 .setShouldSuppressAmbientDisplay(true) 178 .build(); 179 mApplier.apply(previousEffects, ORIGIN_USER_IN_SYSTEMUI); 180 verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true)); 181 182 ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build(); 183 mApplier.apply(noEffects, ORIGIN_USER_IN_SYSTEMUI); 184 185 verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false)); 186 verifyNoMoreInteractions(mColorDisplayManager, mWallpaperManager, mUiModeManager); 187 } 188 189 @Test apply_missingSomeServices_okay()190 public void apply_missingSomeServices_okay() { 191 mContext.addMockSystemService(ColorDisplayManager.class, null); 192 mContext.addMockSystemService(WallpaperManager.class, null); 193 mApplier = new DefaultDeviceEffectsApplier(mContext); 194 195 ZenDeviceEffects effects = new ZenDeviceEffects.Builder() 196 .setShouldSuppressAmbientDisplay(true) 197 .setShouldDimWallpaper(true) 198 .setShouldDisplayGrayscale(true) 199 .setShouldUseNightMode(true) 200 .build(); 201 mApplier.apply(effects, ORIGIN_USER_IN_SYSTEMUI); 202 203 verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true)); 204 // (And no crash from missing services). 205 } 206 207 @Test apply_disabledWallpaperService_dimWallpaperNotApplied()208 public void apply_disabledWallpaperService_dimWallpaperNotApplied() { 209 WallpaperManager disabledWallpaperService = mock(WallpaperManager.class); 210 when(mWallpaperManager.isWallpaperSupported()).thenReturn(false); 211 mContext.addMockSystemService(WallpaperManager.class, disabledWallpaperService); 212 mApplier = new DefaultDeviceEffectsApplier(mContext); 213 verify(mWallpaperManager).isWallpaperSupported(); 214 215 ZenDeviceEffects effects = new ZenDeviceEffects.Builder() 216 .setShouldSuppressAmbientDisplay(true) 217 .setShouldDimWallpaper(true) 218 .setShouldDisplayGrayscale(true) 219 .setShouldUseNightMode(true) 220 .build(); 221 mApplier.apply(effects, ORIGIN_USER_IN_SYSTEMUI); 222 223 verifyNoMoreInteractions(mWallpaperManager); 224 } 225 226 @Test apply_someEffects_onlyThoseEffectsApplied()227 public void apply_someEffects_onlyThoseEffectsApplied() { 228 ZenDeviceEffects effects = new ZenDeviceEffects.Builder() 229 .setShouldDimWallpaper(true) 230 .setShouldDisplayGrayscale(true) 231 .build(); 232 mApplier.apply(effects, ORIGIN_USER_IN_SYSTEMUI); 233 234 verify(mColorDisplayManager).setSaturationLevel(eq(0)); 235 verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f)); 236 237 verify(mPowerManager, never()).suppressAmbientDisplay(anyString(), anyBoolean()); 238 verify(mUiModeManager, never()).setAttentionModeThemeOverlay(anyInt()); 239 } 240 241 @Test apply_onlyEffectDeltaApplied()242 public void apply_onlyEffectDeltaApplied() { 243 mApplier.apply(new ZenDeviceEffects.Builder().setShouldDimWallpaper(true).build(), 244 ORIGIN_USER_IN_SYSTEMUI); 245 verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f)); 246 247 // Apply a second effect and remove the first one. 248 mApplier.apply(new ZenDeviceEffects.Builder().setShouldDisplayGrayscale(true).build(), 249 ORIGIN_USER_IN_SYSTEMUI); 250 251 // Wallpaper dimming was undone, Grayscale was applied, nothing else was touched. 252 verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f)); 253 verify(mColorDisplayManager).setSaturationLevel(eq(0)); 254 verifyNoMoreInteractions(mPowerManager); 255 verifyNoMoreInteractions(mUiModeManager); 256 } 257 258 @Test apply_nightModeFromApp_appliedOnScreenOff()259 public void apply_nightModeFromApp_appliedOnScreenOff() { 260 ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = 261 ArgumentCaptor.forClass(BroadcastReceiver.class); 262 ArgumentCaptor<IntentFilter> intentFilterCaptor = 263 ArgumentCaptor.forClass(IntentFilter.class); 264 265 when(mPowerManager.isInteractive()).thenReturn(true); 266 267 mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(), 268 ORIGIN_APP); 269 270 // Effect was not yet applied, but a broadcast receiver was registered. 271 verifyNoMoreInteractions(mUiModeManager); 272 verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(), 273 intentFilterCaptor.capture(), anyInt()); 274 assertThat(intentFilterCaptor.getValue().getAction(0)).isEqualTo(Intent.ACTION_SCREEN_OFF); 275 BroadcastReceiver screenOffReceiver = broadcastReceiverCaptor.getValue(); 276 277 // Now the "screen off" event comes. 278 screenOffReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); 279 280 // So the effect is applied, and we stopped listening for this event. 281 verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT)); 282 verify(mContext).unregisterReceiver(eq(screenOffReceiver)); 283 } 284 285 @Test apply_nightModeWithScreenOff_appliedImmediately( @estParameter ZenChangeOrigin origin)286 public void apply_nightModeWithScreenOff_appliedImmediately( 287 @TestParameter ZenChangeOrigin origin) { 288 when(mPowerManager.isInteractive()).thenReturn(false); 289 290 mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(), 291 origin.value()); 292 293 // Effect was applied, and no broadcast receiver was registered. 294 verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT)); 295 verify(mContext, never()).registerReceiver(any(), any(), anyInt()); 296 } 297 298 @Test apply_nightModeWithScreenOnAndKeyguardShowing_appliedImmediately( @estParameter ZenChangeOrigin origin)299 public void apply_nightModeWithScreenOnAndKeyguardShowing_appliedImmediately( 300 @TestParameter ZenChangeOrigin origin) { 301 302 when(mPowerManager.isInteractive()).thenReturn(true); 303 when(mKeyguardManager.isKeyguardLocked()).thenReturn(true); 304 305 mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(), 306 origin.value()); 307 308 // Effect was applied, and no broadcast receiver was registered. 309 verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT)); 310 verify(mContext, never()).registerReceiver(any(), any(), anyInt()); 311 } 312 313 @Test 314 @TestParameters({"{origin: ORIGIN_USER_IN_SYSTEMUI}", "{origin: ORIGIN_USER_IN_APP}", 315 "{origin: ORIGIN_INIT}", "{origin: ORIGIN_INIT_USER}"}) apply_nightModeWithScreenOn_appliedImmediatelyBasedOnOrigin( ZenChangeOrigin origin)316 public void apply_nightModeWithScreenOn_appliedImmediatelyBasedOnOrigin( 317 ZenChangeOrigin origin) { 318 when(mPowerManager.isInteractive()).thenReturn(true); 319 320 mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(), 321 origin.value()); 322 323 // Effect was applied, and no broadcast receiver was registered. 324 verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT)); 325 verify(mContext, never()).registerReceiver(any(), any(), anyInt()); 326 } 327 328 @Test 329 @TestParameters({"{origin: ORIGIN_APP}", "{origin: ORIGIN_RESTORE_BACKUP}", 330 "{origin: ORIGIN_SYSTEM}", "{origin: ORIGIN_UNKNOWN}"}) apply_nightModeWithScreenOn_willBeAppliedLaterBasedOnOrigin( ZenChangeOrigin origin)331 public void apply_nightModeWithScreenOn_willBeAppliedLaterBasedOnOrigin( 332 ZenChangeOrigin origin) { 333 when(mPowerManager.isInteractive()).thenReturn(true); 334 335 mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(), 336 origin.value()); 337 338 // Effect was not applied, will be on next screen-off. 339 verifyNoMoreInteractions(mUiModeManager); 340 verify(mContext).registerReceiver(any(), 341 argThat(filter -> Intent.ACTION_SCREEN_OFF.equals(filter.getAction(0))), 342 anyInt()); 343 } 344 345 @Test apply_servicesThrow_noCrash()346 public void apply_servicesThrow_noCrash() { 347 doThrow(new RuntimeException()).when(mPowerManager) 348 .suppressAmbientDisplay(anyString(), anyBoolean()); 349 doThrow(new RuntimeException()).when(mColorDisplayManager).setSaturationLevel(anyInt()); 350 doThrow(new RuntimeException()).when(mWallpaperManager).setWallpaperDimAmount(anyFloat()); 351 doThrow(new RuntimeException()).when(mUiModeManager).setAttentionModeThemeOverlay(anyInt()); 352 mApplier = new DefaultDeviceEffectsApplier(mContext); 353 354 ZenDeviceEffects effects = new ZenDeviceEffects.Builder() 355 .setShouldSuppressAmbientDisplay(true) 356 .setShouldDimWallpaper(true) 357 .setShouldDisplayGrayscale(true) 358 .setShouldUseNightMode(true) 359 .build(); 360 mApplier.apply(effects, ORIGIN_USER_IN_SYSTEMUI); 361 362 // No crashes 363 } 364 } 365