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.app.ActivityTaskManager.INVALID_STACK_ID;
20 import static android.provider.Settings.Global.ANIMATOR_DURATION_SCALE;
21 import static android.server.wm.CliIntentExtra.extraInt;
22 import static android.server.wm.ComponentNameUtils.getWindowName;
23 import static android.server.wm.app.Components.BACKGROUND_IMAGE_ACTIVITY;
24 import static android.server.wm.app.Components.BAD_BLUR_ACTIVITY;
25 import static android.server.wm.app.Components.BLUR_ACTIVITY;
26 import static android.server.wm.app.Components.BLUR_ATTRIBUTES_ACTIVITY;
27 import static android.server.wm.app.Components.BlurActivity.EXTRA_BACKGROUND_BLUR_RADIUS_PX;
28 import static android.server.wm.app.Components.BlurActivity.EXTRA_BLUR_BEHIND_RADIUS_PX;
29 import static android.server.wm.app.Components.BlurActivity.EXTRA_NO_BLUR_BACKGROUND_COLOR;
30 import static android.view.Display.DEFAULT_DISPLAY;
31 
32 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
33 
34 import static org.junit.Assert.assertNotEquals;
35 import static org.junit.Assert.assertTrue;
36 import static org.junit.Assert.assertFalse;
37 import static org.junit.Assume.assumeTrue;
38 import static org.mockito.Mockito.times;
39 import static org.mockito.Mockito.spy;
40 
41 import android.content.ComponentName;
42 import android.content.ContentResolver;
43 import android.graphics.Bitmap;
44 import android.graphics.Color;
45 import android.graphics.Rect;
46 import android.os.Bundle;
47 import android.platform.test.annotations.Presubmit;
48 import android.provider.Settings;
49 import android.view.View;
50 import android.view.WindowManager;
51 import android.widget.LinearLayout;
52 
53 import androidx.test.filters.FlakyTest;
54 
55 import com.android.compatibility.common.util.ColorUtils;
56 import com.android.compatibility.common.util.SystemUtil;
57 
58 import java.util.function.Consumer;
59 
60 import org.junit.After;
61 import org.junit.Before;
62 import org.junit.Test;
63 import org.mockito.Mockito;
64 
65 @Presubmit
66 @FlakyTest(detail = "Promote once confirmed non-flaky")
67 public class BlurTests extends WindowManagerTestBase {
68     private static final int BACKGROUND_BLUR_PX = 80;
69     private static final int BLUR_BEHIND_PX = 40;
70     private static final int NO_BLUR_BACKGROUND_COLOR = Color.BLACK;
71     private static final int BLUR_BEHIND_DYNAMIC_UPDATE_WAIT_TIME = 300;
72     private static final int BACKGROUND_BLUR_DYNAMIC_UPDATE_WAIT_TIME = 100;
73     private static final int DISABLE_BLUR_BROADCAST_WAIT_TIME = 100;
74     private float mSavedAnimatorDurationScale;
75     private boolean mSavedWindowBlurDisabledSetting;
76 
77     @Before
setUp()78     public void setUp() {
79         assumeTrue(supportsBlur());
80 
81         mSavedWindowBlurDisabledSetting = Settings.Global.getInt(mContext.getContentResolver(),
82                 Settings.Global.DISABLE_WINDOW_BLURS, 0) == 1;
83         setForceBlurDisabled(false);
84         SystemUtil.runWithShellPermissionIdentity(() -> {
85             final ContentResolver resolver = getInstrumentation().getContext().getContentResolver();
86             mSavedAnimatorDurationScale =
87                     Settings.Global.getFloat(resolver, ANIMATOR_DURATION_SCALE, 1f);
88             Settings.Global.putFloat(resolver, ANIMATOR_DURATION_SCALE, 0);
89         });
90         startTestActivity(BACKGROUND_IMAGE_ACTIVITY);
91         verifyOnlyBackgroundImageVisible();
92         assertTrue(mContext.getSystemService(WindowManager.class).isCrossWindowBlurEnabled());
93     }
94 
95     @After
tearDown()96     public void tearDown() {
97         if (!supportsBlur()) return;
98 
99         SystemUtil.runWithShellPermissionIdentity(() -> {
100             Settings.Global.putFloat(getInstrumentation().getContext().getContentResolver(),
101                     ANIMATOR_DURATION_SCALE, mSavedAnimatorDurationScale);
102         });
103         setForceBlurDisabled(mSavedWindowBlurDisabledSetting);
104     }
105 
106     @Test
testBackgroundBlurSimple()107     public void testBackgroundBlurSimple() {
108         startTestActivity(BLUR_ACTIVITY,
109                           extraInt(EXTRA_BACKGROUND_BLUR_RADIUS_PX, BACKGROUND_BLUR_PX));
110 
111         final Rect windowFrame = getWindowFrame(BLUR_ACTIVITY);
112         assertBackgroundBlur(takeScreenshot(), windowFrame);
113     }
114 
115     @Test
testBlurBehindSimple()116     public void testBlurBehindSimple() {
117         startTestActivity(BLUR_ACTIVITY,
118                           extraInt(EXTRA_BLUR_BEHIND_RADIUS_PX, BLUR_BEHIND_PX),
119                           extraInt(EXTRA_NO_BLUR_BACKGROUND_COLOR, NO_BLUR_BACKGROUND_COLOR));
120 
121         final Bitmap screenshot = takeScreenshot();
122         final Rect windowFrame = getWindowFrame(BLUR_ACTIVITY);
123         assertBlurBehind(screenshot, windowFrame);
124         assertNoBackgroundBlur(screenshot, windowFrame);
125     }
126 
127     @Test
testNoBackgroundBlurWhenBlurDisabled()128     public void testNoBackgroundBlurWhenBlurDisabled() {
129         setForceBlurDisabled(true);
130         startTestActivity(BLUR_ACTIVITY,
131                           extraInt(EXTRA_BACKGROUND_BLUR_RADIUS_PX, BACKGROUND_BLUR_PX),
132                           extraInt(EXTRA_NO_BLUR_BACKGROUND_COLOR, Color.TRANSPARENT));
133         verifyOnlyBackgroundImageVisible();
134     }
135 
136     @Test
testNoBackgroundBlurForNonTranslucentWindow()137     public void testNoBackgroundBlurForNonTranslucentWindow() {
138         startTestActivity(BAD_BLUR_ACTIVITY,
139                           extraInt(EXTRA_BACKGROUND_BLUR_RADIUS_PX, BACKGROUND_BLUR_PX),
140                           extraInt(EXTRA_NO_BLUR_BACKGROUND_COLOR, Color.TRANSPARENT));
141         verifyOnlyBackgroundImageVisible();
142     }
143 
144     @Test
testNoBlurBehindWhenBlurDisabled()145     public void testNoBlurBehindWhenBlurDisabled() {
146         setForceBlurDisabled(true);
147         startTestActivity(BLUR_ACTIVITY,
148                           extraInt(EXTRA_BLUR_BEHIND_RADIUS_PX, BLUR_BEHIND_PX),
149                           extraInt(EXTRA_NO_BLUR_BACKGROUND_COLOR, Color.TRANSPARENT));
150         verifyOnlyBackgroundImageVisible();
151     }
152 
153     @Test
testNoBlurBehindWhenFlagNotSet()154     public void testNoBlurBehindWhenFlagNotSet() {
155         startTestActivity(BAD_BLUR_ACTIVITY,
156                           extraInt(EXTRA_BLUR_BEHIND_RADIUS_PX, BLUR_BEHIND_PX),
157                           extraInt(EXTRA_NO_BLUR_BACKGROUND_COLOR, Color.TRANSPARENT));
158         verifyOnlyBackgroundImageVisible();
159     }
160 
161     @Test
testBackgroundBlurActivatesFallbackDynamically()162     public void testBackgroundBlurActivatesFallbackDynamically() throws Exception {
163         startTestActivity(BLUR_ACTIVITY,
164                           extraInt(EXTRA_BACKGROUND_BLUR_RADIUS_PX, BACKGROUND_BLUR_PX),
165                           extraInt(EXTRA_NO_BLUR_BACKGROUND_COLOR, NO_BLUR_BACKGROUND_COLOR));
166         final Rect windowFrame = getWindowFrame(BLUR_ACTIVITY);
167 
168         Bitmap screenshot = takeScreenshot();
169         assertBackgroundBlur(takeScreenshot(), windowFrame);
170         assertNoBlurBehind(screenshot, windowFrame);
171 
172         setForceBlurDisabled(true);
173         Thread.sleep(BACKGROUND_BLUR_DYNAMIC_UPDATE_WAIT_TIME);
174 
175         screenshot = takeScreenshot();
176         assertNoBackgroundBlur(screenshot, windowFrame);
177         assertNoBlurBehind(screenshot, windowFrame);
178 
179         setForceBlurDisabled(false);
180         Thread.sleep(BACKGROUND_BLUR_DYNAMIC_UPDATE_WAIT_TIME);
181 
182         screenshot = takeScreenshot();
183         assertBackgroundBlur(takeScreenshot(), windowFrame);
184         assertNoBlurBehind(screenshot, windowFrame);
185     }
186 
187     @Test
testBlurBehindDisabledDynamically()188     public void testBlurBehindDisabledDynamically() throws Exception {
189         startTestActivity(BLUR_ACTIVITY,
190                           extraInt(EXTRA_BLUR_BEHIND_RADIUS_PX, BLUR_BEHIND_PX),
191                           extraInt(EXTRA_NO_BLUR_BACKGROUND_COLOR, NO_BLUR_BACKGROUND_COLOR));
192         final Rect windowFrame = getWindowFrame(BLUR_ACTIVITY);
193 
194         Bitmap screenshot = takeScreenshot();
195         assertBlurBehind(screenshot, windowFrame);
196         assertNoBackgroundBlur(screenshot, windowFrame);
197 
198         setForceBlurDisabled(true);
199         Thread.sleep(BLUR_BEHIND_DYNAMIC_UPDATE_WAIT_TIME);
200 
201         screenshot = takeScreenshot();
202         assertNoBackgroundBlur(screenshot, windowFrame);
203         assertNoBlurBehind(screenshot, windowFrame);
204 
205         setForceBlurDisabled(false);
206         Thread.sleep(BLUR_BEHIND_DYNAMIC_UPDATE_WAIT_TIME);
207 
208         screenshot = takeScreenshot();
209         assertBlurBehind(screenshot,  windowFrame);
210         assertNoBackgroundBlur(screenshot, windowFrame);
211     }
212 
213     @Test
testBlurBehindAndBackgroundBlur()214     public void testBlurBehindAndBackgroundBlur() throws Exception {
215         startTestActivity(BLUR_ACTIVITY,
216                           extraInt(EXTRA_BLUR_BEHIND_RADIUS_PX, BLUR_BEHIND_PX),
217                           extraInt(EXTRA_NO_BLUR_BACKGROUND_COLOR, NO_BLUR_BACKGROUND_COLOR),
218                           extraInt(EXTRA_BACKGROUND_BLUR_RADIUS_PX, BACKGROUND_BLUR_PX));
219         final Rect windowFrame = getWindowFrame(BLUR_ACTIVITY);
220 
221         Bitmap screenshot = takeScreenshot();
222         assertBlurBehind(screenshot, windowFrame);
223         assertBackgroundBlurOverBlurBehind(screenshot, windowFrame);
224 
225         setForceBlurDisabled(true);
226         Thread.sleep(BLUR_BEHIND_DYNAMIC_UPDATE_WAIT_TIME);
227 
228         screenshot = takeScreenshot();
229         assertNoBackgroundBlur(screenshot, windowFrame);
230         assertNoBlurBehind(screenshot, windowFrame);
231 
232         setForceBlurDisabled(false);
233         Thread.sleep(BLUR_BEHIND_DYNAMIC_UPDATE_WAIT_TIME);
234 
235         screenshot = takeScreenshot();
236         assertBlurBehind(screenshot, windowFrame);
237         assertBackgroundBlurOverBlurBehind(screenshot, windowFrame);
238     }
239 
240     @Test
testBlurBehindAndBackgroundBlurSetWithAttributes()241     public void testBlurBehindAndBackgroundBlurSetWithAttributes() {
242         startTestActivity(BLUR_ATTRIBUTES_ACTIVITY);
243         final Rect windowFrame = getWindowFrame(BLUR_ATTRIBUTES_ACTIVITY);
244         final Bitmap screenshot = takeScreenshot();
245 
246         assertBlurBehind(screenshot, windowFrame);
247         assertBackgroundBlurOverBlurBehind(screenshot, windowFrame);
248     }
249 
250     @Test
testBlurDestroyedAfterActivityFinished()251     public void testBlurDestroyedAfterActivityFinished() {
252         startTestActivity(BLUR_ACTIVITY,
253                           extraInt(EXTRA_BLUR_BEHIND_RADIUS_PX, BLUR_BEHIND_PX),
254                           extraInt(EXTRA_NO_BLUR_BACKGROUND_COLOR, NO_BLUR_BACKGROUND_COLOR),
255                           extraInt(EXTRA_BACKGROUND_BLUR_RADIUS_PX, BACKGROUND_BLUR_PX));
256         final Rect windowFrame = getWindowFrame(BLUR_ACTIVITY);
257         Bitmap screenshot = takeScreenshot();
258 
259         assertBlurBehind(screenshot, windowFrame);
260         assertBackgroundBlurOverBlurBehind(screenshot, windowFrame);
261 
262         mBroadcastActionTrigger.finishBroadcastReceiverActivity();
263         mWmState.waitAndAssertActivityRemoved(BLUR_ACTIVITY);
264 
265         verifyOnlyBackgroundImageVisible();
266     }
267 
268     @Test
testIsCrossWindowBlurEnabledUpdatedCorrectly()269     public void testIsCrossWindowBlurEnabledUpdatedCorrectly() throws Exception {
270         setForceBlurDisabled(true);
271         Thread.sleep(DISABLE_BLUR_BROADCAST_WAIT_TIME);
272         assertFalse(mContext.getSystemService(WindowManager.class).isCrossWindowBlurEnabled());
273 
274         setForceBlurDisabled(false);
275         Thread.sleep(DISABLE_BLUR_BROADCAST_WAIT_TIME);
276         assertTrue(mContext.getSystemService(WindowManager.class).isCrossWindowBlurEnabled());
277     }
278 
279     @Test
testBlurListener()280     public void testBlurListener() throws Exception {
281         ListenerTestActivity activity = startActivity(ListenerTestActivity.class);
282         Mockito.verify(activity.mBlurEnabledListener).accept(true);
283 
284         setForceBlurDisabled(true);
285         Thread.sleep(DISABLE_BLUR_BROADCAST_WAIT_TIME);
286         assertFalse(mContext.getSystemService(WindowManager.class).isCrossWindowBlurEnabled());
287         Mockito.verify(activity.mBlurEnabledListener).accept(false);
288 
289         setForceBlurDisabled(false);
290         Thread.sleep(DISABLE_BLUR_BROADCAST_WAIT_TIME);
291         assertTrue(mContext.getSystemService(WindowManager.class).isCrossWindowBlurEnabled());
292         Mockito.verify(activity.mBlurEnabledListener, times(2)).accept(true);
293     }
294 
295     public static class BlurListener implements Consumer<Boolean> {
296         @Override
accept(Boolean enabled)297         public void accept(Boolean enabled) {}
298     }
299 
300     public static class ListenerTestActivity extends FocusableActivity {
301         Consumer<Boolean> mBlurEnabledListener = spy(new BlurListener());
302 
303         @Override
onCreate(Bundle savedInstanceState)304         protected void onCreate(Bundle savedInstanceState) {
305             super.onCreate(savedInstanceState);
306             View v = new LinearLayout(this);
307             v.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
308                 @Override
309                 public void onViewAttachedToWindow(View view) {
310                     getWindowManager().addCrossWindowBlurEnabledListener(mBlurEnabledListener);
311                 }
312 
313                 @Override
314                 public void onViewDetachedFromWindow(View view) {
315                     getWindowManager().removeCrossWindowBlurEnabledListener(mBlurEnabledListener);
316                 }
317             });
318             setContentView(v);
319         }
320     }
321 
startTestActivity(ComponentName activityName, final CliIntentExtra... extras)322     private void startTestActivity(ComponentName activityName, final CliIntentExtra... extras) {
323         launchActivity(activityName, extras);
324         assertNotEquals(mWmState.getRootTaskIdByActivity(activityName), INVALID_STACK_ID);
325         waitAndAssertResumedActivity(activityName, activityName + " must be resumed");
326         mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
327     }
328 
329 
getWindowFrame(ComponentName activityName)330     private Rect getWindowFrame(ComponentName activityName) {
331         String windowName = getWindowName(activityName);
332         mWmState.computeState(activityName);
333         return mWmState.getMatchingVisibleWindowState(windowName).get(0).getFrame();
334     }
335 
verifyOnlyBackgroundImageVisible()336     private void verifyOnlyBackgroundImageVisible() {
337         final Bitmap screenshot = takeScreenshot();
338         final int height = screenshot.getHeight();
339         final int width = screenshot.getWidth();
340 
341         final int blueWidth = width / 2;
342 
343         for (int x = 0; x < width; x++) {
344             for (int y = 0; y < height; y++) {
345                 if (x < blueWidth) {
346                     ColorUtils.verifyColor("failed for pixel (x, y) = (" + x + ", " + y + ")",
347                             Color.BLUE, screenshot.getPixel(x, y), 0);
348                 } else {
349                     ColorUtils.verifyColor("failed for pixel (x, y) = (" + x + ", " + y + ")",
350                             Color.RED, screenshot.getPixel(x, y), 0);
351                 }
352             }
353         }
354     }
355 
assertBlurBehind(Bitmap screenshot, Rect windowFrame)356     private static void assertBlurBehind(Bitmap screenshot, Rect windowFrame) {
357         assertBlur(screenshot, BLUR_BEHIND_PX, 0, windowFrame.top);
358         assertBlur(screenshot, BLUR_BEHIND_PX, windowFrame.bottom, screenshot.getHeight());
359     }
360 
assertBackgroundBlur(Bitmap screenshot, Rect windowFrame)361     private static void assertBackgroundBlur(Bitmap screenshot, Rect windowFrame) {
362         assertBlur(screenshot, BACKGROUND_BLUR_PX, windowFrame.top, windowFrame.bottom);
363     }
364 
assertBackgroundBlurOverBlurBehind(Bitmap screenshot, Rect windowFrame)365     private static void assertBackgroundBlurOverBlurBehind(Bitmap screenshot, Rect windowFrame) {
366         // We are assuming that the background blur will become bigger by roughly half of the blur
367         // behind radius
368         assertBlur(screenshot, BACKGROUND_BLUR_PX + ((int) (BLUR_BEHIND_PX*0.5f)),
369                 windowFrame.top, windowFrame.bottom);
370     }
371 
assertNoBlurBehind(Bitmap screenshot, Rect windowFrame)372     private static void assertNoBlurBehind(Bitmap screenshot, Rect windowFrame) {
373         for (int x = 0; x < screenshot.getWidth(); x++) {
374             for (int y = 0; y < screenshot.getHeight(); y++) {
375                 if (x < windowFrame.left) {
376                     ColorUtils.verifyColor("failed for pixel (x, y) = (" + x + ", " + y + ")",
377                             Color.BLUE, screenshot.getPixel(x, y), 0);
378                 } else if (x < screenshot.getWidth() / 2) {
379                     if (y < windowFrame.top || y > windowFrame.bottom) {
380                         ColorUtils.verifyColor("failed for pixel (x, y) = (" + x + ", " + y + ")",
381                                 Color.BLUE, screenshot.getPixel(x, y), 0);
382                     }
383                 } else if (x <= windowFrame.right) {
384                     if (y < windowFrame.top || y > windowFrame.bottom) {
385                         ColorUtils.verifyColor("failed for pixel (x, y) = (" + x + ", " + y + ")",
386                                 Color.RED, screenshot.getPixel(x, y), 0);
387                     }
388                 } else if (x > windowFrame.right) {
389                     ColorUtils.verifyColor("failed for pixel (x, y) = (" + x + ", " + y + ")",
390                             Color.RED, screenshot.getPixel(x, y), 0);
391                 }
392 
393             }
394         }
395     }
396 
assertNoBackgroundBlur(Bitmap screenshot, Rect windowFrame)397     private static void assertNoBackgroundBlur(Bitmap screenshot, Rect windowFrame) {
398         for (int y = windowFrame.top; y < windowFrame.bottom; y++) {
399             for (int x = windowFrame.left; x < windowFrame.right; x++) {
400                 ColorUtils.verifyColor("failed for pixel (x, y) = (" + x + ", " + y + ")",
401                         NO_BLUR_BACKGROUND_COLOR, screenshot.getPixel(x, y), 0);
402             }
403         }
404     }
405 
assertBlur(Bitmap screenshot, int blurRadius, int startHeight, int endHeight)406     private static void assertBlur(Bitmap screenshot, int blurRadius, int startHeight,
407                                    int endHeight) {
408         final int width = screenshot.getWidth();
409 
410         // Adjust the test to check a smaller part of the blurred area in order to accept various
411         // blur algorithm approximations used in RenderEngine
412         final int stepSize = blurRadius / 4;
413         final int blurAreaStartX = width / 2 - blurRadius + stepSize;
414         final int blurAreaEndX = width / 2 + blurRadius;
415 
416         Color previousColor;
417         Color currentColor;
418         final int unaffectedBluePixelX = width / 2 - blurRadius - 1;
419         final int unaffectedRedPixelX = width / 2 + blurRadius + 1;
420         for (int y = startHeight; y < endHeight; y++) {
421             ColorUtils.verifyColor(
422                     "failed for pixel (x, y) = (" + unaffectedBluePixelX + ", " + y + ")",
423                     Color.BLUE, screenshot.getPixel(unaffectedBluePixelX, y), 0);
424             previousColor = Color.valueOf(Color.BLUE);
425             for (int x = blurAreaStartX; x <= blurAreaEndX; x += stepSize) {
426                 currentColor = screenshot.getColor(x, y);
427                 assertTrue("assertBlur failed for blue for pixel (x, y) = (" + x + ", " + y + ");"
428                         + " previousColor blue: " + previousColor.blue()
429                         + ", currentColor blue: " + currentColor.blue()
430                         , previousColor.blue() > currentColor.blue());
431                 assertTrue("assertBlur failed for red for pixel (x, y) = (" + x + ", " + y + ");"
432                        + " previousColor red: " + previousColor.red()
433                        + ", currentColor red: " + currentColor.red(),
434                        previousColor.red() < currentColor.red());
435 
436                 previousColor = currentColor;
437             }
438             ColorUtils.verifyColor(
439                     "failed for pixel (x, y) = (" + unaffectedRedPixelX + ", " + y + ")",
440                     Color.RED, screenshot.getPixel(unaffectedRedPixelX, y), 0);
441         }
442     }
443 
setForceBlurDisabled(boolean disable)444     private void setForceBlurDisabled(boolean disable) {
445         Settings.Global.putInt(mContext.getContentResolver(),
446                 Settings.Global.DISABLE_WINDOW_BLURS, disable ? 1 : 0);
447     }
448 }
449