• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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