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