• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 android.server.wm;
18 
19 import static android.server.wm.ComponentNameUtils.getWindowName;
20 import static android.view.Display.DEFAULT_DISPLAY;
21 import static android.view.WindowInsets.Type.systemBars;
22 
23 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
24 
25 import static org.junit.Assert.assertTrue;
26 import static org.junit.Assume.assumeTrue;
27 import static org.mockito.Mockito.spy;
28 import static org.mockito.Mockito.timeout;
29 
30 import android.app.Activity;
31 import android.content.ComponentName;
32 import android.graphics.Bitmap;
33 import android.graphics.Color;
34 import android.graphics.Rect;
35 import android.os.Bundle;
36 import android.platform.test.annotations.Presubmit;
37 import android.provider.Settings;
38 import android.server.wm.cts.R;
39 import android.server.wm.settings.SettingsSession;
40 import android.view.View;
41 import android.view.WindowManager;
42 
43 import androidx.test.rule.ActivityTestRule;
44 import androidx.test.uiautomator.UiDevice;
45 
46 import com.android.compatibility.common.util.ApiTest;
47 import com.android.compatibility.common.util.ColorUtils;
48 import com.android.compatibility.common.util.PollingCheck;
49 
50 import org.junit.Before;
51 import org.junit.Rule;
52 import org.junit.Test;
53 import org.junit.rules.RuleChain;
54 import org.junit.rules.TestRule;
55 import org.mockito.Mockito;
56 
57 import java.util.function.Consumer;
58 
59 @Presubmit
60 public class BlurTests extends WindowManagerTestBase {
61     private static final int BACKGROUND_BLUR_PX = 80;
62     private static final int BLUR_BEHIND_PX = 40;
63     private static final int NO_BLUR_BACKGROUND_COLOR = 0xFF550055;
64     private static final int BROADCAST_WAIT_TIMEOUT = 300;
65 
66     private Rect mBackgroundActivityBounds;
67 
68     private final DumpOnFailure mDumpOnFailure = new DumpOnFailure();
69 
70     private final TestRule mEnableBlurRule = SettingsSession.overrideForTest(
71             Settings.Global.getUriFor(Settings.Global.DISABLE_WINDOW_BLURS),
72             Settings.Global::getInt,
73             Settings.Global::putInt,
74             0);
75 
76     private final ActivityTestRule<BackgroundActivity> mBackgroundActivity =
77             new ActivityTestRule<>(BackgroundActivity.class);
78 
79     @Rule
80     public final TestRule methodRules = RuleChain.outerRule(mDumpOnFailure)
81             .around(mEnableBlurRule)
82             .around(mBackgroundActivity);
83 
84     @Before
setUp()85     public void setUp() {
86         assumeTrue(supportsBlur());
87         ComponentName cn = mBackgroundActivity.getActivity().getComponentName();
88         waitAndAssertResumedActivity(cn, cn + " must be resumed");
89         mBackgroundActivity.getActivity().waitAndAssertWindowFocusState(true);
90 
91         // Use the background activity's bounds when taking the device screenshot.
92         // This is needed for multi-screen devices (foldables) where
93         // the launched activity covers just one screen
94         WindowManagerState.WindowState windowState = mWmState.getWindowState(cn);
95         WindowManagerState.Activity act = mWmState.getActivity(cn);
96         mBackgroundActivityBounds = act.getBounds();
97         insetGivenFrame(windowState, WindowManagerState.InsetsSource::isCaptionBar,
98                 mBackgroundActivityBounds);
99 
100         // Wait for the first frame *after* the splash screen is removed to take screenshots.
101         // We don't currently have a definite event / callback for this.
102         mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
103         waitForActivityIdle(mBackgroundActivity.getActivity());
104 
105         // Basic checks common to all tests
106         verifyOnlyBackgroundImageVisible();
107         assertTrue(mContext.getSystemService(WindowManager.class).isCrossWindowBlurEnabled());
108     }
109 
110     @Test
111     @ApiTest(apis = {"android.view.Window#setBackgroundBlurRadius(int)"})
testBackgroundBlurSimple()112     public void testBackgroundBlurSimple() {
113         final BlurActivity blurActivity = startTestActivity(BlurActivity.class);
114         getInstrumentation().runOnMainSync(() -> {
115             blurActivity.setBackgroundBlurRadius(BACKGROUND_BLUR_PX);
116         });
117 
118         waitForActivityIdle(blurActivity);
119 
120         final Rect windowFrame = getFloatingWindowFrame(blurActivity);
121         assertBackgroundBlur(takeScreenshotForBounds(mBackgroundActivityBounds), windowFrame);
122     }
123 
124     @Test
125     @ApiTest(apis = {"android.view.WindowManager.LayoutParams#setBlurBehindRadius",
126                      "android.R.styleable#Window_windowBlurBehindEnabled"})
testBlurBehindSimple()127     public void testBlurBehindSimple() throws Exception {
128         final BlurActivity blurActivity = startTestActivity(BlurActivity.class);
129         getInstrumentation().runOnMainSync(() -> {
130             blurActivity.setBlurBehindRadius(BLUR_BEHIND_PX);
131         });
132         waitForActivityIdle(blurActivity);
133         final Rect windowFrame = getFloatingWindowFrame(blurActivity);
134 
135         Bitmap screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
136         assertBlurBehind(screenshot, windowFrame);
137         assertNoBackgroundBlur(screenshot, windowFrame);
138 
139         getInstrumentation().runOnMainSync(() -> {
140             blurActivity.setBlurBehindRadius(0);
141         });
142         waitForActivityIdle(blurActivity);
143 
144         screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
145         assertNoBlurBehind(screenshot, windowFrame);
146         assertNoBackgroundBlur(screenshot, windowFrame);
147     }
148 
149     @Test
150     @ApiTest(apis = {"android.view.Window#setBackgroundBlurRadius"})
testNoBackgroundBlurWhenBlurDisabled()151     public void testNoBackgroundBlurWhenBlurDisabled() {
152         setAndAssertForceBlurDisabled(true);
153         final BlurActivity blurActivity = startTestActivity(BlurActivity.class);
154         getInstrumentation().runOnMainSync(() -> {
155             blurActivity.setBackgroundBlurRadius(BACKGROUND_BLUR_PX);
156             blurActivity.setBackgroundColor(Color.TRANSPARENT);
157         });
158         waitForActivityIdle(blurActivity);
159 
160         verifyOnlyBackgroundImageVisible();
161 
162         setAndAssertForceBlurDisabled(false, blurActivity.mBlurEnabledListener);
163         waitForActivityIdle(blurActivity);
164 
165         final Rect windowFrame = getFloatingWindowFrame(blurActivity);
166         assertBackgroundBlur(takeScreenshotForBounds(mBackgroundActivityBounds), windowFrame);
167     }
168 
169     @Test
170     @ApiTest(apis = {"android.view.Window#setBackgroundBlurRadius"})
testNoBackgroundBlurForNonTranslucentWindow()171     public void testNoBackgroundBlurForNonTranslucentWindow() {
172         final BlurActivity blurActivity = startTestActivity(BadBlurActivity.class);
173         getInstrumentation().runOnMainSync(() -> {
174             blurActivity.setBackgroundBlurRadius(BACKGROUND_BLUR_PX);
175             blurActivity.setBackgroundColor(Color.TRANSPARENT);
176         });
177         waitForActivityIdle(blurActivity);
178 
179         verifyOnlyBackgroundImageVisible();
180     }
181 
182     @Test
183     @ApiTest(apis = {"android.view.WindowManager.LayoutParams#setBlurBehindRadius",
184                      "android.R.styleable#Window_windowBlurBehindEnabled"})
testNoBlurBehindWhenBlurDisabled()185     public void testNoBlurBehindWhenBlurDisabled() {
186         setAndAssertForceBlurDisabled(true);
187         final BlurActivity blurActivity = startTestActivity(BlurActivity.class);
188         getInstrumentation().runOnMainSync(() -> {
189             blurActivity.setBlurBehindRadius(BLUR_BEHIND_PX);
190             blurActivity.setBackgroundColor(Color.TRANSPARENT);
191         });
192         waitForActivityIdle(blurActivity);
193 
194         verifyOnlyBackgroundImageVisible();
195 
196         setAndAssertForceBlurDisabled(false, blurActivity.mBlurEnabledListener);
197         waitForActivityIdle(blurActivity);
198 
199         final Rect windowFrame = getFloatingWindowFrame(blurActivity);
200         final Bitmap screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
201         assertBlurBehind(screenshot, windowFrame);
202         assertNoBackgroundBlur(screenshot, windowFrame);
203     }
204 
205     @Test
206     @ApiTest(apis = {"android.view.WindowManager.LayoutParams#setBlurBehindRadius",
207                      "android.R.styleable#Window_windowBlurBehindEnabled"})
testNoBlurBehindWhenFlagNotSet()208     public void testNoBlurBehindWhenFlagNotSet() {
209         final BlurActivity blurActivity = startTestActivity(BadBlurActivity.class);
210         getInstrumentation().runOnMainSync(() -> {
211             blurActivity.setBlurBehindRadius(BLUR_BEHIND_PX);
212             blurActivity.setBackgroundColor(Color.TRANSPARENT);
213         });
214         waitForActivityIdle(blurActivity);
215 
216         verifyOnlyBackgroundImageVisible();
217     }
218 
219     @Test
220     @ApiTest(apis = {"android.view.Window#setBackgroundBlurRadius"})
testBackgroundBlurActivatesFallbackDynamically()221     public void testBackgroundBlurActivatesFallbackDynamically() {
222         final BlurActivity blurActivity = startTestActivity(BlurActivity.class);
223         getInstrumentation().runOnMainSync(() -> {
224             blurActivity.setBackgroundBlurRadius(BACKGROUND_BLUR_PX);
225         });
226         waitForActivityIdle(blurActivity);
227         final Rect windowFrame = getFloatingWindowFrame(blurActivity);
228 
229         Bitmap screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
230         assertBackgroundBlur(screenshot, windowFrame);
231         assertNoBlurBehind(screenshot, windowFrame);
232 
233         setAndAssertForceBlurDisabled(true, blurActivity.mBlurEnabledListener);
234         waitForActivityIdle(blurActivity);
235 
236         screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
237         assertNoBackgroundBlur(screenshot, windowFrame);
238         assertNoBlurBehind(screenshot, windowFrame);
239 
240         setAndAssertForceBlurDisabled(false, blurActivity.mBlurEnabledListener);
241         waitForActivityIdle(blurActivity);
242 
243         screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
244         assertBackgroundBlur(screenshot, windowFrame);
245         assertNoBlurBehind(screenshot, windowFrame);
246     }
247 
248     @Test
249     @ApiTest(apis = {"android.view.WindowManager.LayoutParams#setBlurBehindRadius",
250                      "android.R.styleable#Window_windowBlurBehindEnabled"})
testBlurBehindDisabledDynamically()251     public void testBlurBehindDisabledDynamically() {
252         final BlurActivity blurActivity = startTestActivity(BlurActivity.class);
253         getInstrumentation().runOnMainSync(() -> {
254             blurActivity.setBlurBehindRadius(BLUR_BEHIND_PX);
255         });
256         waitForActivityIdle(blurActivity);
257         final Rect windowFrame = getFloatingWindowFrame(blurActivity);
258 
259         Bitmap screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
260         assertBlurBehind(screenshot, windowFrame);
261         assertNoBackgroundBlur(screenshot, windowFrame);
262 
263         getInstrumentation().runOnMainSync(() -> {
264             blurActivity.setBlurBehindRadius(0);
265         });
266         waitForActivityIdle(blurActivity);
267 
268         screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
269         assertNoBackgroundBlur(screenshot, windowFrame);
270         assertNoBlurBehind(screenshot, windowFrame);
271 
272         getInstrumentation().runOnMainSync(() -> {
273             blurActivity.setBlurBehindRadius(BLUR_BEHIND_PX);
274         });
275         waitForActivityIdle(blurActivity);
276 
277         screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
278         assertBlurBehind(screenshot,  windowFrame);
279         assertNoBackgroundBlur(screenshot, windowFrame);
280     }
281 
282     @Test
283     @ApiTest(apis = {"android.view.WindowManager.LayoutParams#setBlurBehindRadius",
284                      "android.R.styleable#Window_windowBlurBehindEnabled",
285                      "android.view.Window#setBackgroundBlurRadius"})
testBlurBehindAndBackgroundBlur()286     public void testBlurBehindAndBackgroundBlur() {
287         final BlurActivity blurActivity = startTestActivity(BlurActivity.class);
288         getInstrumentation().runOnMainSync(() -> {
289             blurActivity.setBlurBehindRadius(BLUR_BEHIND_PX);
290             blurActivity.setBackgroundBlurRadius(BACKGROUND_BLUR_PX);
291         });
292         waitForActivityIdle(blurActivity);
293         final Rect windowFrame = getFloatingWindowFrame(blurActivity);
294 
295         Bitmap screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
296         assertBlurBehind(screenshot, windowFrame);
297         assertBackgroundBlurOverBlurBehind(screenshot, windowFrame);
298 
299         getInstrumentation().runOnMainSync(() -> {
300             blurActivity.setBlurBehindRadius(0);
301             blurActivity.setBackgroundBlurRadius(0);
302         });
303         waitForActivityIdle(blurActivity);
304 
305         screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
306         assertNoBackgroundBlur(screenshot, windowFrame);
307         assertNoBlurBehind(screenshot, windowFrame);
308 
309         getInstrumentation().runOnMainSync(() -> {
310             blurActivity.setBlurBehindRadius(BLUR_BEHIND_PX);
311             blurActivity.setBackgroundBlurRadius(BACKGROUND_BLUR_PX);
312         });
313         waitForActivityIdle(blurActivity);
314 
315         screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
316         assertBlurBehind(screenshot, windowFrame);
317         assertBackgroundBlurOverBlurBehind(screenshot, windowFrame);
318     }
319 
320     @Test
321     @ApiTest(apis = {"android.R.styleable#Window_windowBackgroundBlurRadius",
322                      "android.R.styleable#Window_windowBlurBehindRadius",
323                      "android.R.styleable#Window_windowBlurBehindEnabled"})
testBlurBehindAndBackgroundBlurSetWithAttributes()324     public void testBlurBehindAndBackgroundBlurSetWithAttributes() {
325         final Activity blurAttrActivity = startTestActivity(BlurAttributesActivity.class);
326         final Rect windowFrame = getFloatingWindowFrame(blurAttrActivity);
327         final Bitmap screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
328 
329         assertBlurBehind(screenshot, windowFrame);
330         assertBackgroundBlurOverBlurBehind(screenshot, windowFrame);
331     }
332 
333     @Test
334     @ApiTest(apis = {"android.view.WindowManager.LayoutParams#setBlurBehindRadius",
335                      "android.R.styleable#Window_windowBlurBehindEnabled",
336                      "android.view.Window#setBackgroundBlurRadius"})
testAllBlurRemovedAndRestoredWhenToggleBlurDisabled()337     public void testAllBlurRemovedAndRestoredWhenToggleBlurDisabled() {
338         final BlurActivity blurActivity = startTestActivity(BlurActivity.class);
339         getInstrumentation().runOnMainSync(() -> {
340             blurActivity.setBlurBehindRadius(BLUR_BEHIND_PX);
341             blurActivity.setBackgroundBlurRadius(BACKGROUND_BLUR_PX);
342         });
343         waitForActivityIdle(blurActivity);
344         final Rect windowFrame = getFloatingWindowFrame(blurActivity);
345 
346         Bitmap screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
347         assertBlurBehind(screenshot, windowFrame);
348         assertBackgroundBlurOverBlurBehind(screenshot, windowFrame);
349 
350         setAndAssertForceBlurDisabled(true, blurActivity.mBlurEnabledListener);
351         waitForActivityIdle(blurActivity);
352 
353         screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
354         assertNoBackgroundBlur(screenshot, windowFrame);
355         assertNoBlurBehind(screenshot, windowFrame);
356 
357         getInstrumentation().runOnMainSync(() -> {
358             blurActivity.setBackgroundColor(Color.TRANSPARENT);
359         });
360         waitForActivityIdle(blurActivity);
361         verifyOnlyBackgroundImageVisible();
362 
363         setAndAssertForceBlurDisabled(false, blurActivity.mBlurEnabledListener);
364         waitForActivityIdle(blurActivity);
365 
366         screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
367         assertBlurBehind(screenshot, windowFrame);
368         assertBackgroundBlurOverBlurBehind(screenshot, windowFrame);
369     }
370 
371     @Test
372     @ApiTest(apis = {"android.view.WindowManager.LayoutParams#setBlurBehindRadius",
373                      "android.R.styleable#Window_windowBlurBehindEnabled",
374                      "android.view.Window#setBackgroundBlurRadius"})
testBlurDestroyedAfterActivityFinished()375     public void testBlurDestroyedAfterActivityFinished() {
376         final BlurActivity blurActivity = startTestActivity(BlurActivity.class);
377         getInstrumentation().runOnMainSync(() -> {
378             blurActivity.setBlurBehindRadius(BLUR_BEHIND_PX);
379             blurActivity.setBackgroundBlurRadius(BACKGROUND_BLUR_PX);
380         });
381         waitForActivityIdle(blurActivity);
382 
383         final Rect windowFrame = getFloatingWindowFrame(blurActivity);
384         Bitmap screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
385 
386         assertBlurBehind(screenshot, windowFrame);
387         assertBackgroundBlurOverBlurBehind(screenshot, windowFrame);
388 
389         blurActivity.finish();
390         mWmState.waitAndAssertActivityRemoved(blurActivity.getComponentName());
391         waitForActivityIdle(blurActivity);
392 
393         verifyOnlyBackgroundImageVisible();
394     }
395 
396     @Test
397     @ApiTest(apis = {"android.view.WindowManager#isCrossWindowBlurEnabled"})
testIsCrossWindowBlurEnabledUpdatedCorrectly()398     public void testIsCrossWindowBlurEnabledUpdatedCorrectly() {
399         setAndAssertForceBlurDisabled(true);
400         setAndAssertForceBlurDisabled(false);
401     }
402 
403     @Test
404     @ApiTest(apis = {"android.view.WindowManager#addCrossWindowBlurEnabledListener",
405                      "android.view.WindowManager#removeCrossWindowBlurEnabledListener"})
testBlurListener()406     public void testBlurListener() {
407         final BlurActivity activity = startTestActivity(BlurActivity.class);
408         Mockito.verify(activity.mBlurEnabledListener).accept(true);
409 
410         setAndAssertForceBlurDisabled(true, activity.mBlurEnabledListener);
411         setAndAssertForceBlurDisabled(false, activity.mBlurEnabledListener);
412 
413         activity.finishAndRemoveTask();
414         mWmState.waitAndAssertActivityRemoved(activity.getComponentName());
415 
416         Mockito.clearInvocations(activity.mBlurEnabledListener);
417         setAndAssertForceBlurDisabled(true);
418         Mockito.verifyNoMoreInteractions(activity.mBlurEnabledListener);
419     }
420 
421     public static class BackgroundActivity extends FocusableActivity {
422         @Override
onCreate(Bundle savedInstanceState)423         protected void onCreate(Bundle savedInstanceState) {
424             super.onCreate(savedInstanceState);
425             getSplashScreen().setOnExitAnimationListener(view -> view.remove());
426 
427             setContentView(new View(this));
428 
429             getWindow().setDecorFitsSystemWindows(false);
430             getWindow().getInsetsController().hide(systemBars());
431         }
432     }
433 
434     public static class BlurActivity extends FocusableActivity {
435         public final Consumer<Boolean> mBlurEnabledListener = spy(new BlurListener());
436 
437         private int mBackgroundBlurRadius = 0;
438         private int mBlurBehindRadius = 0;
439 
440         @Override
onCreate(Bundle savedInstanceState)441         protected void onCreate(Bundle savedInstanceState) {
442             super.onCreate(savedInstanceState);
443             setContentView(R.layout.blur_activity);
444             getWindow().setDecorFitsSystemWindows(false);
445             getWindow().getInsetsController().hide(systemBars());
446         }
447 
448         @Override
onAttachedToWindow()449         public void onAttachedToWindow() {
450             super.onAttachedToWindow();
451             getWindowManager().addCrossWindowBlurEnabledListener(getMainExecutor(),
452                     mBlurEnabledListener);
453         }
454 
455         @Override
onDetachedFromWindow()456         public void onDetachedFromWindow() {
457             super.onDetachedFromWindow();
458             getWindowManager().removeCrossWindowBlurEnabledListener(mBlurEnabledListener);
459         }
460 
setBackgroundBlurRadius(int backgroundBlurRadius)461         void setBackgroundBlurRadius(int backgroundBlurRadius) {
462             mBackgroundBlurRadius = backgroundBlurRadius;
463             getWindow().setBackgroundBlurRadius(mBackgroundBlurRadius);
464             setBackgroundColor(
465                         mBackgroundBlurRadius > 0 && getWindowManager().isCrossWindowBlurEnabled()
466                         ? Color.TRANSPARENT : NO_BLUR_BACKGROUND_COLOR);
467         }
468 
setBlurBehindRadius(int blurBehindRadius)469         void setBlurBehindRadius(int blurBehindRadius) {
470             mBlurBehindRadius = blurBehindRadius;
471             getWindow().getAttributes().setBlurBehindRadius(mBlurBehindRadius);
472             getWindow().setAttributes(getWindow().getAttributes());
473             getWindowManager().updateViewLayout(getWindow().getDecorView(),
474                     getWindow().getAttributes());
475         }
476 
setBackgroundColor(int color)477         void setBackgroundColor(int color) {
478             getWindow().getDecorView().setBackgroundColor(color);
479             getWindowManager().updateViewLayout(getWindow().getDecorView(),
480                     getWindow().getAttributes());
481         }
482 
483         public class BlurListener implements Consumer<Boolean> {
484             @Override
accept(Boolean enabled)485             public void accept(Boolean enabled) {
486                 setBackgroundBlurRadius(mBackgroundBlurRadius);
487                 setBlurBehindRadius(mBlurBehindRadius);
488             }
489         }
490     }
491 
492     /**
493      * This activity is used to test 2 things:
494      * 1. Blur behind does not work if WindowManager.LayoutParams.FLAG_BLUR_BEHIND is not set,
495      *    respectively if windowBlurBehindEnabled is not set.
496      * 2. Background blur does not work for opaque activities (where windowIsTranslucent is false)
497      *
498      * In the style of this activity windowBlurBehindEnabled is false and windowIsTranslucent is
499      * false. As a result, we expect that neither blur behind, nor background blur is rendered,
500      * even though they are requested with setBlurBehindRadius and setBackgroundBlurRadius.
501      */
502     public static class BadBlurActivity extends BlurActivity {
503     }
504 
505     public static class BlurAttributesActivity extends FocusableActivity {
506         @Override
onCreate(Bundle savedInstanceState)507         protected void onCreate(Bundle savedInstanceState) {
508             super.onCreate(savedInstanceState);
509             setContentView(R.layout.blur_activity);
510             getWindow().setDecorFitsSystemWindows(false);
511             getWindow().getInsetsController().hide(systemBars());
512         }
513     }
514 
startTestActivity(Class<T> activityClass)515     private <T extends FocusableActivity> T startTestActivity(Class<T> activityClass) {
516         T activity = startActivity(activityClass);
517         ComponentName activityName = activity.getComponentName();
518         waitAndAssertResumedActivity(activityName, activityName + " must be resumed");
519         waitForActivityIdle(activity);
520         return activity;
521     }
522 
getFloatingWindowFrame(Activity activity)523     private Rect getFloatingWindowFrame(Activity activity) {
524         mWmState.computeState(activity.getComponentName());
525         String windowName = getWindowName(activity.getComponentName());
526         Rect windowFrame =
527                 new Rect(mWmState.getMatchingVisibleWindowState(windowName).get(0).getFrame());
528         // Offset the frame of the BlurActivity to the coordinates of
529         // mBackgroundActivityBounds, because we only take the screenshot in that area.
530         windowFrame.offset(-mBackgroundActivityBounds.left, -mBackgroundActivityBounds.top);
531         return windowFrame;
532     }
533 
waitForActivityIdle(Activity activity)534     private void waitForActivityIdle(Activity activity) {
535         // This helps with the test flakiness
536         getInstrumentation().runOnMainSync(() -> {});
537         UiDevice.getInstance(getInstrumentation()).waitForIdle();
538         getInstrumentation().getUiAutomation().syncInputTransactions();
539         mWmState.computeState(activity.getComponentName());
540     }
541 
setAndAssertForceBlurDisabled(boolean disable)542     private void setAndAssertForceBlurDisabled(boolean disable) {
543         setAndAssertForceBlurDisabled(disable, null);
544     }
545 
setAndAssertForceBlurDisabled(boolean disable, Consumer<Boolean> blurEnabledListener)546     private void setAndAssertForceBlurDisabled(boolean disable,
547                 Consumer<Boolean> blurEnabledListener) {
548         if (blurEnabledListener != null) {
549             Mockito.clearInvocations(blurEnabledListener);
550         }
551         Settings.Global.putInt(mContext.getContentResolver(),
552                 Settings.Global.DISABLE_WINDOW_BLURS, disable ? 1 : 0);
553         if (blurEnabledListener != null) {
554             Mockito.verify(blurEnabledListener, timeout(BROADCAST_WAIT_TIMEOUT))
555                 .accept(!disable);
556         } else {
557             PollingCheck.waitFor(BROADCAST_WAIT_TIMEOUT, () -> {
558                 return disable != mContext.getSystemService(WindowManager.class)
559                         .isCrossWindowBlurEnabled();
560             });
561             assertTrue(!disable == mContext.getSystemService(WindowManager.class)
562                         .isCrossWindowBlurEnabled());
563         }
564     }
565 
verifyOnlyBackgroundImageVisible()566     private void verifyOnlyBackgroundImageVisible() {
567         final Bitmap screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
568         mDumpOnFailure.dumpOnFailure("verifyOnlyBackgroundImageVisible", screenshot);
569         final int height = screenshot.getHeight();
570         final int width = screenshot.getWidth();
571 
572         final int blueWidth = width / 2;
573 
574         final int[] row = new int[width];
575 
576         for (int y = 0; y < height; y++) {
577             screenshot.getPixels(row, 0, width, 0, y, row.length, 1);
578             for (int x = 0; x < width; x++) {
579                 final int actual = row[x];
580                 final int expected = (x < blueWidth ? Color.BLUE : Color.RED);
581 
582                 if (actual != expected) {
583                     ColorUtils.verifyColor("failed for pixel (x, y) = (" + x + ", " + y + ")",
584                             expected, actual, 1);
585                 }
586             }
587         }
588     }
589 
assertBlurBehind(Bitmap screenshot, Rect windowFrame)590     private void assertBlurBehind(Bitmap screenshot, Rect windowFrame) {
591         mDumpOnFailure.dumpOnFailure("assertBlurBehind", screenshot);
592         assertBlur(screenshot, BLUR_BEHIND_PX, 0, windowFrame.top);
593         assertBlur(screenshot, BLUR_BEHIND_PX, windowFrame.bottom, screenshot.getHeight());
594     }
595 
assertBackgroundBlur(Bitmap screenshot, Rect windowFrame)596     private void assertBackgroundBlur(Bitmap screenshot, Rect windowFrame) {
597         mDumpOnFailure.dumpOnFailure("assertBackgroundBlur", screenshot);
598         assertBlur(screenshot, BACKGROUND_BLUR_PX, windowFrame.top, windowFrame.bottom);
599     }
600 
assertBackgroundBlurOverBlurBehind(Bitmap screenshot, Rect windowFrame)601     private void assertBackgroundBlurOverBlurBehind(Bitmap screenshot, Rect windowFrame) {
602         mDumpOnFailure.dumpOnFailure("assertBackgroundBlurOverBlurBehind", screenshot);
603         assertBlur(
604                 screenshot,
605                 (int) Math.hypot(BACKGROUND_BLUR_PX, BLUR_BEHIND_PX),
606                 windowFrame.top,
607                 windowFrame.bottom);
608     }
609 
assertNoBlurBehind(Bitmap screenshot, Rect windowFrame)610     private void assertNoBlurBehind(Bitmap screenshot, Rect windowFrame) {
611         mDumpOnFailure.dumpOnFailure("assertNoBlurBehind", screenshot);
612 
613         // Batch fetch pixels from each row of the bitmap to speed up the test.
614         final int[] row = new int[screenshot.getWidth()];
615 
616         for (int y = 0; y < screenshot.getHeight(); y++) {
617             screenshot.getPixels(row, 0, screenshot.getWidth(), 0, y, row.length, 1);
618             for (int x = 0; x < screenshot.getWidth(); x++) {
619                 if (!windowFrame.contains(x, y)) {
620                     final int actual = row[x];
621                     final int expected = (x < screenshot.getWidth() / 2 ? Color.BLUE : Color.RED);
622 
623                     if (actual != expected) {
624                         ColorUtils.verifyColor(
625                                "failed for pixel (x, y) = (" + x + ", " + y + ")",
626                                 expected, actual, 1);
627                     }
628                 }
629             }
630         }
631     }
632 
assertNoBackgroundBlur(Bitmap screenshot, Rect windowFrame)633     private void assertNoBackgroundBlur(Bitmap screenshot, Rect windowFrame) {
634         mDumpOnFailure.dumpOnFailure("assertNoBackgroundBlur", screenshot);
635 
636         // Batch fetch pixels from each row of the bitmap to speed up the test.
637         final int[] row = new int[windowFrame.width()];
638 
639         for (int y = windowFrame.top; y < windowFrame.bottom; y++) {
640             screenshot.getPixels(
641                     row, 0, screenshot.getWidth(), windowFrame.left, y, row.length, 1);
642             for (int x = windowFrame.left; x < windowFrame.right; x++) {
643                 final int actual = row[x - windowFrame.left];
644                 final int expected = NO_BLUR_BACKGROUND_COLOR;
645 
646                 if (actual != expected) {
647                     ColorUtils.verifyColor("failed for pixel (x, y) = (" + x + ", " + y + ")",
648                             expected, actual, 1);
649                 }
650             }
651         }
652     }
653 
assertBlur(Bitmap screenshot, int blurRadius, int startHeight, int endHeight)654     private void assertBlur(Bitmap screenshot, int blurRadius, int startHeight,
655             int endHeight) {
656         final int width = screenshot.getWidth();
657 
658         // Adjust the test to check a smaller part of the blurred area in order to accept
659         // various blur algorithm approximations used in RenderEngine
660         final int stepSize = blurRadius / 4;
661         final int blurAreaStartX = width / 2 - blurRadius + stepSize;
662         final int blurAreaEndX = width / 2 + blurRadius;
663 
664         // At 2 * radius there should be no visible blur effects.
665         final int unaffectedBluePixelX = width / 2 - blurRadius * 2 - 1;
666         final int unaffectedRedPixelX = width / 2 + blurRadius * 2 + 1;
667 
668         for (int y = startHeight; y < endHeight; y++) {
669             Color previousColor = Color.valueOf(Color.BLUE);
670             for (int x = blurAreaStartX; x < blurAreaEndX; x += stepSize) {
671                 Color currentColor = screenshot.getColor(x, y);
672 
673                 if (previousColor.blue() <= currentColor.blue()) {
674                     assertTrue("assertBlur failed for blue for pixel (x, y) = ("
675                             + x + ", " + y + ");"
676                             + " previousColor blue: " + previousColor.blue()
677                             + ", currentColor blue: " + currentColor.blue()
678                             , previousColor.blue() > currentColor.blue());
679                 }
680                 if (previousColor.red() >= currentColor.red()) {
681                     assertTrue("assertBlur failed for red for pixel (x, y) = ("
682                            + x + ", " + y + ");"
683                            + " previousColor red: " + previousColor.red()
684                            + ", currentColor red: " + currentColor.red(),
685                            previousColor.red() < currentColor.red());
686                 }
687                 previousColor = currentColor;
688             }
689         }
690 
691         for (int y = startHeight; y < endHeight; y++) {
692             final int unaffectedBluePixel = screenshot.getPixel(unaffectedBluePixelX, y);
693             if (unaffectedBluePixel != Color.BLUE) {
694                 ColorUtils.verifyColor(
695                         "failed for pixel (x, y) = (" + unaffectedBluePixelX + ", " + y + ")",
696                         Color.BLUE, unaffectedBluePixel, 1);
697             }
698             final int unaffectedRedPixel = screenshot.getPixel(unaffectedRedPixelX, y);
699             if (unaffectedRedPixel != Color.RED) {
700                 ColorUtils.verifyColor(
701                         "failed for pixel (x, y) = (" + unaffectedRedPixelX + ", " + y + ")",
702                         Color.RED, unaffectedRedPixel, 1);
703             }
704         }
705     }
706 }
707