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