blob: 099d72225187345ffb2349e72262cf073a1f78e7 [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.server.wm.other;
import static android.server.wm.ComponentNameUtils.getWindowName;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowInsets.Type.systemBars;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import android.app.Activity;
import android.content.ComponentName;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.server.wm.DumpOnFailure;
import android.server.wm.WindowManagerState;
import android.server.wm.WindowManagerTestBase;
import android.server.wm.cts.R;
import android.server.wm.settings.SettingsSession;
import android.view.WindowInsets;
import android.view.WindowManager;
import androidx.test.rule.ActivityTestRule;
import androidx.test.uiautomator.UiDevice;
import com.android.compatibility.common.util.ApiTest;
import com.android.compatibility.common.util.ColorUtils;
import com.android.compatibility.common.util.PollingCheck;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.mockito.Mockito;
import java.util.function.Consumer;
@Presubmit
public class BlurTests extends WindowManagerTestBase {
private static final int BACKGROUND_BLUR_PX = 80;
private static final int BLUR_BEHIND_PX = 40;
private static final int NO_BLUR_BACKGROUND_COLOR = 0xFF550055;
private static final int BROADCAST_WAIT_TIMEOUT = 300;
private Rect mBackgroundActivityBounds;
private final DumpOnFailure mDumpOnFailure = new DumpOnFailure();
private final TestRule mEnableBlurRule = SettingsSession.overrideForTest(
Settings.Global.getUriFor(Settings.Global.DISABLE_WINDOW_BLURS),
Settings.Global::getInt,
Settings.Global::putInt,
0);
private final TestRule mDisableTransitionAnimationRule = SettingsSession.overrideForTest(
Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE),
Settings.Global::getFloat,
Settings.Global::putFloat,
0f);
private final ActivityTestRule<BackgroundActivity> mBackgroundActivity =
new ActivityTestRule<>(BackgroundActivity.class);
@Rule
public final TestRule methodRules = RuleChain.outerRule(mDumpOnFailure)
.around(mEnableBlurRule)
.around(mDisableTransitionAnimationRule)
.around(mBackgroundActivity);
@Before
public void setUp() {
assumeTrue(supportsBlur());
ComponentName cn = mBackgroundActivity.getActivity().getComponentName();
waitAndAssertResumedActivity(cn, cn + " must be resumed");
mBackgroundActivity.getActivity().waitAndAssertWindowFocusState(true);
// Use the background activity's bounds when taking the device screenshot.
// This is needed for multi-screen devices (foldables) where
// the launched activity covers just one screen
WindowManagerState.WindowState windowState = mWmState.getWindowState(cn);
WindowManagerState.Activity act = mWmState.getActivity(cn);
mBackgroundActivityBounds = act.getBounds();
insetGivenFrame(windowState,
insetsSource -> (insetsSource.is(WindowInsets.Type.captionBar())),
mBackgroundActivityBounds);
// Wait for the first frame *after* the splash screen is removed to take screenshots.
// We don't currently have a definite event / callback for this.
mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
waitForActivityIdle(mBackgroundActivity.getActivity());
// Basic checks common to all tests
verifyOnlyBackgroundImageVisible();
assertTrue(mContext.getSystemService(WindowManager.class).isCrossWindowBlurEnabled());
}
@Test
@ApiTest(apis = {"android.view.Window#setBackgroundBlurRadius(int)"})
public void testBackgroundBlurSimple() {
final BlurActivity blurActivity = startTestActivity(BlurActivity.class);
getInstrumentation().runOnMainSync(() -> {
blurActivity.setBackgroundBlurRadius(BACKGROUND_BLUR_PX);
});
waitForActivityIdle(blurActivity);
final Rect windowFrame = getFloatingWindowFrame(blurActivity);
assertBackgroundBlur(takeScreenshotForBounds(mBackgroundActivityBounds), windowFrame);
}
@Test
@ApiTest(apis = {"android.view.WindowManager.LayoutParams#setBlurBehindRadius",
"android.R.styleable#Window_windowBlurBehindEnabled"})
public void testBlurBehindSimple() throws Exception {
final BlurActivity blurActivity = startTestActivity(BlurActivity.class);
getInstrumentation().runOnMainSync(() -> {
blurActivity.setBlurBehindRadius(BLUR_BEHIND_PX);
});
waitForActivityIdle(blurActivity);
final Rect windowFrame = getFloatingWindowFrame(blurActivity);
Bitmap screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
assertBlurBehind(screenshot, windowFrame);
assertNoBackgroundBlur(screenshot, windowFrame);
getInstrumentation().runOnMainSync(() -> {
blurActivity.setBlurBehindRadius(0);
});
waitForActivityIdle(blurActivity);
screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
assertNoBlurBehind(screenshot, windowFrame);
assertNoBackgroundBlur(screenshot, windowFrame);
}
@Test
@ApiTest(apis = {"android.view.Window#setBackgroundBlurRadius"})
public void testNoBackgroundBlurWhenBlurDisabled() {
setAndAssertForceBlurDisabled(true);
final BlurActivity blurActivity = startTestActivity(BlurActivity.class);
getInstrumentation().runOnMainSync(() -> {
blurActivity.setBackgroundBlurRadius(BACKGROUND_BLUR_PX);
blurActivity.setBackgroundColor(Color.TRANSPARENT);
});
waitForActivityIdle(blurActivity);
verifyOnlyBackgroundImageVisible();
setAndAssertForceBlurDisabled(false, blurActivity.mBlurEnabledListener);
waitForActivityIdle(blurActivity);
final Rect windowFrame = getFloatingWindowFrame(blurActivity);
assertBackgroundBlur(takeScreenshotForBounds(mBackgroundActivityBounds), windowFrame);
}
@Test
@ApiTest(apis = {"android.view.Window#setBackgroundBlurRadius"})
public void testNoBackgroundBlurForNonTranslucentWindow() {
final BlurActivity blurActivity = startTestActivity(BadBlurActivity.class);
getInstrumentation().runOnMainSync(() -> {
blurActivity.setBackgroundBlurRadius(BACKGROUND_BLUR_PX);
blurActivity.setBackgroundColor(Color.TRANSPARENT);
});
waitForActivityIdle(blurActivity);
verifyOnlyBackgroundImageVisible();
}
@Test
@ApiTest(apis = {"android.view.WindowManager.LayoutParams#setBlurBehindRadius",
"android.R.styleable#Window_windowBlurBehindEnabled"})
public void testNoBlurBehindWhenBlurDisabled() {
setAndAssertForceBlurDisabled(true);
final BlurActivity blurActivity = startTestActivity(BlurActivity.class);
getInstrumentation().runOnMainSync(() -> {
blurActivity.setBlurBehindRadius(BLUR_BEHIND_PX);
blurActivity.setBackgroundColor(Color.TRANSPARENT);
});
waitForActivityIdle(blurActivity);
verifyOnlyBackgroundImageVisible();
setAndAssertForceBlurDisabled(false, blurActivity.mBlurEnabledListener);
waitForActivityIdle(blurActivity);
final Rect windowFrame = getFloatingWindowFrame(blurActivity);
final Bitmap screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
assertBlurBehind(screenshot, windowFrame);
assertNoBackgroundBlur(screenshot, windowFrame);
}
@Test
@ApiTest(apis = {"android.view.WindowManager.LayoutParams#setBlurBehindRadius",
"android.R.styleable#Window_windowBlurBehindEnabled"})
public void testNoBlurBehindWhenFlagNotSet() {
final BlurActivity blurActivity = startTestActivity(BadBlurActivity.class);
getInstrumentation().runOnMainSync(() -> {
blurActivity.setBlurBehindRadius(BLUR_BEHIND_PX);
blurActivity.setBackgroundColor(Color.TRANSPARENT);
});
waitForActivityIdle(blurActivity);
verifyOnlyBackgroundImageVisible();
}
@Test
@ApiTest(apis = {"android.view.Window#setBackgroundBlurRadius"})
public void testBackgroundBlurActivatesFallbackDynamically() {
final BlurActivity blurActivity = startTestActivity(BlurActivity.class);
getInstrumentation().runOnMainSync(() -> {
blurActivity.setBackgroundBlurRadius(BACKGROUND_BLUR_PX);
});
waitForActivityIdle(blurActivity);
final Rect windowFrame = getFloatingWindowFrame(blurActivity);
Bitmap screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
assertBackgroundBlur(screenshot, windowFrame);
assertNoBlurBehind(screenshot, windowFrame);
setAndAssertForceBlurDisabled(true, blurActivity.mBlurEnabledListener);
waitForActivityIdle(blurActivity);
screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
assertNoBackgroundBlur(screenshot, windowFrame);
assertNoBlurBehind(screenshot, windowFrame);
setAndAssertForceBlurDisabled(false, blurActivity.mBlurEnabledListener);
waitForActivityIdle(blurActivity);
screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
assertBackgroundBlur(screenshot, windowFrame);
assertNoBlurBehind(screenshot, windowFrame);
}
@Test
@ApiTest(apis = {"android.view.WindowManager.LayoutParams#setBlurBehindRadius",
"android.R.styleable#Window_windowBlurBehindEnabled"})
public void testBlurBehindDisabledDynamically() {
final BlurActivity blurActivity = startTestActivity(BlurActivity.class);
getInstrumentation().runOnMainSync(() -> {
blurActivity.setBlurBehindRadius(BLUR_BEHIND_PX);
});
waitForActivityIdle(blurActivity);
final Rect windowFrame = getFloatingWindowFrame(blurActivity);
Bitmap screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
assertBlurBehind(screenshot, windowFrame);
assertNoBackgroundBlur(screenshot, windowFrame);
getInstrumentation().runOnMainSync(() -> {
blurActivity.setBlurBehindRadius(0);
});
waitForActivityIdle(blurActivity);
screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
assertNoBackgroundBlur(screenshot, windowFrame);
assertNoBlurBehind(screenshot, windowFrame);
getInstrumentation().runOnMainSync(() -> {
blurActivity.setBlurBehindRadius(BLUR_BEHIND_PX);
});
waitForActivityIdle(blurActivity);
screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
assertBlurBehind(screenshot, windowFrame);
assertNoBackgroundBlur(screenshot, windowFrame);
}
@Test
@ApiTest(apis = {"android.view.WindowManager.LayoutParams#setBlurBehindRadius",
"android.R.styleable#Window_windowBlurBehindEnabled",
"android.view.Window#setBackgroundBlurRadius"})
public void testBlurBehindAndBackgroundBlur() {
final BlurActivity blurActivity = startTestActivity(BlurActivity.class);
getInstrumentation().runOnMainSync(() -> {
blurActivity.setBlurBehindRadius(BLUR_BEHIND_PX);
blurActivity.setBackgroundBlurRadius(BACKGROUND_BLUR_PX);
});
waitForActivityIdle(blurActivity);
final Rect windowFrame = getFloatingWindowFrame(blurActivity);
Bitmap screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
assertBlurBehind(screenshot, windowFrame);
assertBackgroundBlurOverBlurBehind(screenshot, windowFrame);
getInstrumentation().runOnMainSync(() -> {
blurActivity.setBlurBehindRadius(0);
blurActivity.setBackgroundBlurRadius(0);
});
waitForActivityIdle(blurActivity);
screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
assertNoBackgroundBlur(screenshot, windowFrame);
assertNoBlurBehind(screenshot, windowFrame);
getInstrumentation().runOnMainSync(() -> {
blurActivity.setBlurBehindRadius(BLUR_BEHIND_PX);
blurActivity.setBackgroundBlurRadius(BACKGROUND_BLUR_PX);
});
waitForActivityIdle(blurActivity);
screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
assertBlurBehind(screenshot, windowFrame);
assertBackgroundBlurOverBlurBehind(screenshot, windowFrame);
}
@Test
@ApiTest(apis = {"android.R.styleable#Window_windowBackgroundBlurRadius",
"android.R.styleable#Window_windowBlurBehindRadius",
"android.R.styleable#Window_windowBlurBehindEnabled"})
public void testBlurBehindAndBackgroundBlurSetWithAttributes() {
final Activity blurAttrActivity = startTestActivity(BlurAttributesActivity.class);
final Rect windowFrame = getFloatingWindowFrame(blurAttrActivity);
final Bitmap screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
assertBlurBehind(screenshot, windowFrame);
assertBackgroundBlurOverBlurBehind(screenshot, windowFrame);
}
@Test
@ApiTest(apis = {"android.view.WindowManager.LayoutParams#setBlurBehindRadius",
"android.R.styleable#Window_windowBlurBehindEnabled",
"android.view.Window#setBackgroundBlurRadius"})
public void testAllBlurRemovedAndRestoredWhenToggleBlurDisabled() {
final BlurActivity blurActivity = startTestActivity(BlurActivity.class);
getInstrumentation().runOnMainSync(() -> {
blurActivity.setBlurBehindRadius(BLUR_BEHIND_PX);
blurActivity.setBackgroundBlurRadius(BACKGROUND_BLUR_PX);
});
waitForActivityIdle(blurActivity);
final Rect windowFrame = getFloatingWindowFrame(blurActivity);
Bitmap screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
assertBlurBehind(screenshot, windowFrame);
assertBackgroundBlurOverBlurBehind(screenshot, windowFrame);
setAndAssertForceBlurDisabled(true, blurActivity.mBlurEnabledListener);
waitForActivityIdle(blurActivity);
screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
assertNoBackgroundBlur(screenshot, windowFrame);
assertNoBlurBehind(screenshot, windowFrame);
getInstrumentation().runOnMainSync(() -> {
blurActivity.setBackgroundColor(Color.TRANSPARENT);
});
waitForActivityIdle(blurActivity);
verifyOnlyBackgroundImageVisible();
setAndAssertForceBlurDisabled(false, blurActivity.mBlurEnabledListener);
waitForActivityIdle(blurActivity);
screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
assertBlurBehind(screenshot, windowFrame);
assertBackgroundBlurOverBlurBehind(screenshot, windowFrame);
}
@Test
@ApiTest(apis = {"android.view.WindowManager.LayoutParams#setBlurBehindRadius",
"android.R.styleable#Window_windowBlurBehindEnabled",
"android.view.Window#setBackgroundBlurRadius"})
public void testBlurDestroyedAfterActivityFinished() {
final BlurActivity blurActivity = startTestActivity(BlurActivity.class);
getInstrumentation().runOnMainSync(() -> {
blurActivity.setBlurBehindRadius(BLUR_BEHIND_PX);
blurActivity.setBackgroundBlurRadius(BACKGROUND_BLUR_PX);
});
waitForActivityIdle(blurActivity);
final Rect windowFrame = getFloatingWindowFrame(blurActivity);
Bitmap screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
assertBlurBehind(screenshot, windowFrame);
assertBackgroundBlurOverBlurBehind(screenshot, windowFrame);
blurActivity.finish();
mWmState.waitAndAssertActivityRemoved(blurActivity.getComponentName());
waitForActivityIdle(blurActivity);
verifyOnlyBackgroundImageVisible();
}
@Test
@ApiTest(apis = {"android.view.WindowManager#isCrossWindowBlurEnabled"})
public void testIsCrossWindowBlurEnabledUpdatedCorrectly() {
setAndAssertForceBlurDisabled(true);
setAndAssertForceBlurDisabled(false);
}
@Test
@ApiTest(apis = {"android.view.WindowManager#addCrossWindowBlurEnabledListener",
"android.view.WindowManager#removeCrossWindowBlurEnabledListener"})
public void testBlurListener() {
final BlurActivity activity = startTestActivity(BlurActivity.class);
Mockito.verify(activity.mBlurEnabledListener).accept(true);
setAndAssertForceBlurDisabled(true, activity.mBlurEnabledListener);
setAndAssertForceBlurDisabled(false, activity.mBlurEnabledListener);
activity.finishAndRemoveTask();
mWmState.waitAndAssertActivityRemoved(activity.getComponentName());
Mockito.clearInvocations(activity.mBlurEnabledListener);
setAndAssertForceBlurDisabled(true);
Mockito.verifyNoMoreInteractions(activity.mBlurEnabledListener);
}
public static class BackgroundActivity extends FocusableActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSplashScreen().setOnExitAnimationListener(view -> view.remove());
setContentView(R.layout.background_image);
getWindow().setDecorFitsSystemWindows(false);
getWindow().getInsetsController().hide(systemBars());
}
}
public static class BlurActivity extends FocusableActivity {
public final Consumer<Boolean> mBlurEnabledListener = spy(new BlurListener());
private int mBackgroundBlurRadius = 0;
private int mBlurBehindRadius = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.blur_activity);
getWindow().setDecorFitsSystemWindows(false);
getWindow().getInsetsController().hide(systemBars());
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
getWindowManager().addCrossWindowBlurEnabledListener(getMainExecutor(),
mBlurEnabledListener);
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
getWindowManager().removeCrossWindowBlurEnabledListener(mBlurEnabledListener);
}
void setBackgroundBlurRadius(int backgroundBlurRadius) {
mBackgroundBlurRadius = backgroundBlurRadius;
getWindow().setBackgroundBlurRadius(mBackgroundBlurRadius);
setBackgroundColor(
mBackgroundBlurRadius > 0 && getWindowManager().isCrossWindowBlurEnabled()
? Color.TRANSPARENT : NO_BLUR_BACKGROUND_COLOR);
}
void setBlurBehindRadius(int blurBehindRadius) {
mBlurBehindRadius = blurBehindRadius;
getWindow().getAttributes().setBlurBehindRadius(mBlurBehindRadius);
getWindow().setAttributes(getWindow().getAttributes());
getWindowManager().updateViewLayout(getWindow().getDecorView(),
getWindow().getAttributes());
}
void setBackgroundColor(int color) {
getWindow().getDecorView().setBackgroundColor(color);
getWindowManager().updateViewLayout(getWindow().getDecorView(),
getWindow().getAttributes());
}
public class BlurListener implements Consumer<Boolean> {
@Override
public void accept(Boolean enabled) {
setBackgroundBlurRadius(mBackgroundBlurRadius);
setBlurBehindRadius(mBlurBehindRadius);
}
}
}
/**
* This activity is used to test 2 things:
* 1. Blur behind does not work if WindowManager.LayoutParams.FLAG_BLUR_BEHIND is not set,
* respectively if windowBlurBehindEnabled is not set.
* 2. Background blur does not work for opaque activities (where windowIsTranslucent is false)
*
* In the style of this activity windowBlurBehindEnabled is false and windowIsTranslucent is
* false. As a result, we expect that neither blur behind, nor background blur is rendered,
* even though they are requested with setBlurBehindRadius and setBackgroundBlurRadius.
*/
public static class BadBlurActivity extends BlurActivity {
}
public static class BlurAttributesActivity extends FocusableActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.blur_activity);
getWindow().setDecorFitsSystemWindows(false);
getWindow().getInsetsController().hide(systemBars());
}
}
private <T extends FocusableActivity> T startTestActivity(Class<T> activityClass) {
T activity = startActivity(activityClass);
ComponentName activityName = activity.getComponentName();
waitAndAssertResumedActivity(activityName, activityName + " must be resumed");
waitForActivityIdle(activity);
return activity;
}
private Rect getFloatingWindowFrame(Activity activity) {
mWmState.computeState(activity.getComponentName());
String windowName = getWindowName(activity.getComponentName());
Rect windowFrame =
new Rect(mWmState.getMatchingVisibleWindowState(windowName).get(0).getFrame());
// Offset the frame of the BlurActivity to the coordinates of
// mBackgroundActivityBounds, because we only take the screenshot in that area.
windowFrame.offset(-mBackgroundActivityBounds.left, -mBackgroundActivityBounds.top);
return windowFrame;
}
private void waitForActivityIdle(Activity activity) {
// This helps with the test flakiness
getInstrumentation().runOnMainSync(() -> {});
UiDevice.getInstance(getInstrumentation()).waitForIdle();
getInstrumentation().getUiAutomation().syncInputTransactions();
mWmState.computeState(activity.getComponentName());
}
private void setAndAssertForceBlurDisabled(boolean disable) {
setAndAssertForceBlurDisabled(disable, null);
}
private void setAndAssertForceBlurDisabled(boolean disable,
Consumer<Boolean> blurEnabledListener) {
if (blurEnabledListener != null) {
Mockito.clearInvocations(blurEnabledListener);
}
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.DISABLE_WINDOW_BLURS, disable ? 1 : 0);
if (blurEnabledListener != null) {
Mockito.verify(blurEnabledListener, timeout(BROADCAST_WAIT_TIMEOUT))
.accept(!disable);
} else {
PollingCheck.waitFor(BROADCAST_WAIT_TIMEOUT, () -> {
return disable != mContext.getSystemService(WindowManager.class)
.isCrossWindowBlurEnabled();
});
assertTrue(!disable == mContext.getSystemService(WindowManager.class)
.isCrossWindowBlurEnabled());
}
}
private void verifyOnlyBackgroundImageVisible() {
final Bitmap screenshot = takeScreenshotForBounds(mBackgroundActivityBounds);
mDumpOnFailure.dumpOnFailure("verifyOnlyBackgroundImageVisible", screenshot);
final int height = screenshot.getHeight();
final int width = screenshot.getWidth();
final int blueWidth = width / 2;
final int[] row = new int[width];
for (int y = 0; y < height; y++) {
screenshot.getPixels(row, 0, width, 0, y, row.length, 1);
for (int x = 0; x < width; x++) {
final int actual = row[x];
final int expected = (x < blueWidth ? Color.BLUE : Color.RED);
if (actual != expected) {
ColorUtils.verifyColor("failed for pixel (x, y) = (" + x + ", " + y + ")",
expected, actual, 1);
}
}
}
}
private void assertBlurBehind(Bitmap screenshot, Rect windowFrame) {
mDumpOnFailure.dumpOnFailure("assertBlurBehind", screenshot);
assertBlur(screenshot, BLUR_BEHIND_PX, 0, windowFrame.top);
assertBlur(screenshot, BLUR_BEHIND_PX, windowFrame.bottom, screenshot.getHeight());
}
private void assertBackgroundBlur(Bitmap screenshot, Rect windowFrame) {
mDumpOnFailure.dumpOnFailure("assertBackgroundBlur", screenshot);
assertBlur(screenshot, BACKGROUND_BLUR_PX, windowFrame.top, windowFrame.bottom);
}
private void assertBackgroundBlurOverBlurBehind(Bitmap screenshot, Rect windowFrame) {
mDumpOnFailure.dumpOnFailure("assertBackgroundBlurOverBlurBehind", screenshot);
assertBlur(
screenshot,
(int) Math.hypot(BACKGROUND_BLUR_PX, BLUR_BEHIND_PX),
windowFrame.top,
windowFrame.bottom);
}
private void assertNoBlurBehind(Bitmap screenshot, Rect windowFrame) {
mDumpOnFailure.dumpOnFailure("assertNoBlurBehind", screenshot);
// Batch fetch pixels from each row of the bitmap to speed up the test.
final int[] row = new int[screenshot.getWidth()];
for (int y = 0; y < screenshot.getHeight(); y++) {
screenshot.getPixels(row, 0, screenshot.getWidth(), 0, y, row.length, 1);
for (int x = 0; x < screenshot.getWidth(); x++) {
if (!windowFrame.contains(x, y)) {
final int actual = row[x];
final int expected = (x < screenshot.getWidth() / 2 ? Color.BLUE : Color.RED);
if (actual != expected) {
ColorUtils.verifyColor(
"failed for pixel (x, y) = (" + x + ", " + y + ")",
expected, actual, 1);
}
}
}
}
}
private void assertNoBackgroundBlur(Bitmap screenshot, Rect windowFrame) {
mDumpOnFailure.dumpOnFailure("assertNoBackgroundBlur", screenshot);
// Batch fetch pixels from each row of the bitmap to speed up the test.
final int[] row = new int[windowFrame.width()];
for (int y = windowFrame.top; y < windowFrame.bottom; y++) {
screenshot.getPixels(
row, 0, screenshot.getWidth(), windowFrame.left, y, row.length, 1);
for (int x = windowFrame.left; x < windowFrame.right; x++) {
final int actual = row[x - windowFrame.left];
final int expected = NO_BLUR_BACKGROUND_COLOR;
if (actual != expected) {
ColorUtils.verifyColor("failed for pixel (x, y) = (" + x + ", " + y + ")",
expected, actual, 1);
}
}
}
}
private void assertBlur(Bitmap screenshot, int blurRadius, int startHeight,
int endHeight) {
final int width = screenshot.getWidth();
// Adjust the test to check a smaller part of the blurred area in order to accept
// various blur algorithm approximations used in RenderEngine
final int stepSize = blurRadius / 4;
final int blurAreaStartX = width / 2 - blurRadius + stepSize;
final int blurAreaEndX = width / 2 + blurRadius;
// At 2 * radius there should be no visible blur effects.
final int unaffectedBluePixelX = width / 2 - blurRadius * 2 - 1;
final int unaffectedRedPixelX = width / 2 + blurRadius * 2 + 1;
for (int y = startHeight; y < endHeight; y++) {
Color previousColor = Color.valueOf(Color.BLUE);
for (int x = blurAreaStartX; x < blurAreaEndX; x += stepSize) {
Color currentColor = screenshot.getColor(x, y);
if (previousColor.blue() <= currentColor.blue()) {
assertTrue("assertBlur failed for blue for pixel (x, y) = ("
+ x + ", " + y + ");"
+ " previousColor blue: " + previousColor.blue()
+ ", currentColor blue: " + currentColor.blue()
, previousColor.blue() > currentColor.blue());
}
if (previousColor.red() >= currentColor.red()) {
assertTrue("assertBlur failed for red for pixel (x, y) = ("
+ x + ", " + y + ");"
+ " previousColor red: " + previousColor.red()
+ ", currentColor red: " + currentColor.red(),
previousColor.red() < currentColor.red());
}
previousColor = currentColor;
}
}
for (int y = startHeight; y < endHeight; y++) {
final int unaffectedBluePixel = screenshot.getPixel(unaffectedBluePixelX, y);
if (unaffectedBluePixel != Color.BLUE) {
ColorUtils.verifyColor(
"failed for pixel (x, y) = (" + unaffectedBluePixelX + ", " + y + ")",
Color.BLUE, unaffectedBluePixel, 1);
}
final int unaffectedRedPixel = screenshot.getPixel(unaffectedRedPixelX, y);
if (unaffectedRedPixel != Color.RED) {
ColorUtils.verifyColor(
"failed for pixel (x, y) = (" + unaffectedRedPixelX + ", " + y + ")",
Color.RED, unaffectedRedPixel, 1);
}
}
}
}