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