/* * Copyright (C) 2021 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; import static android.app.ActivityTaskManager.INVALID_STACK_ID; import static android.provider.Settings.Global.ANIMATOR_DURATION_SCALE; import static android.server.wm.CliIntentExtra.extraInt; import static android.server.wm.ComponentNameUtils.getWindowName; import static android.server.wm.app.Components.BACKGROUND_IMAGE_ACTIVITY; import static android.server.wm.app.Components.BAD_BLUR_ACTIVITY; import static android.server.wm.app.Components.BLUR_ACTIVITY; import static android.server.wm.app.Components.BLUR_ATTRIBUTES_ACTIVITY; import static android.server.wm.app.Components.BlurActivity.EXTRA_BACKGROUND_BLUR_RADIUS_PX; import static android.server.wm.app.Components.BlurActivity.EXTRA_BLUR_BEHIND_RADIUS_PX; import static android.server.wm.app.Components.BlurActivity.EXTRA_NO_BLUR_BACKGROUND_COLOR; import static android.view.Display.DEFAULT_DISPLAY; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse; import static org.junit.Assume.assumeTrue; import static org.mockito.Mockito.times; import static org.mockito.Mockito.spy; import android.content.ComponentName; import android.content.ContentResolver; 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.view.View; import android.view.WindowManager; import android.widget.LinearLayout; import androidx.test.filters.FlakyTest; import com.android.compatibility.common.util.ColorUtils; import com.android.compatibility.common.util.SystemUtil; import java.util.function.Consumer; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; @Presubmit @FlakyTest(detail = "Promote once confirmed non-flaky") 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 = Color.BLACK; private static final int BLUR_BEHIND_DYNAMIC_UPDATE_WAIT_TIME = 300; private static final int BACKGROUND_BLUR_DYNAMIC_UPDATE_WAIT_TIME = 100; private static final int DISABLE_BLUR_BROADCAST_WAIT_TIME = 100; private float mSavedAnimatorDurationScale; private boolean mSavedWindowBlurDisabledSetting; @Before public void setUp() { assumeTrue(supportsBlur()); mSavedWindowBlurDisabledSetting = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.DISABLE_WINDOW_BLURS, 0) == 1; setForceBlurDisabled(false); SystemUtil.runWithShellPermissionIdentity(() -> { final ContentResolver resolver = getInstrumentation().getContext().getContentResolver(); mSavedAnimatorDurationScale = Settings.Global.getFloat(resolver, ANIMATOR_DURATION_SCALE, 1f); Settings.Global.putFloat(resolver, ANIMATOR_DURATION_SCALE, 0); }); startTestActivity(BACKGROUND_IMAGE_ACTIVITY); verifyOnlyBackgroundImageVisible(); assertTrue(mContext.getSystemService(WindowManager.class).isCrossWindowBlurEnabled()); } @After public void tearDown() { if (!supportsBlur()) return; SystemUtil.runWithShellPermissionIdentity(() -> { Settings.Global.putFloat(getInstrumentation().getContext().getContentResolver(), ANIMATOR_DURATION_SCALE, mSavedAnimatorDurationScale); }); setForceBlurDisabled(mSavedWindowBlurDisabledSetting); } @Test public void testBackgroundBlurSimple() { startTestActivity(BLUR_ACTIVITY, extraInt(EXTRA_BACKGROUND_BLUR_RADIUS_PX, BACKGROUND_BLUR_PX)); final Rect windowFrame = getWindowFrame(BLUR_ACTIVITY); assertBackgroundBlur(takeScreenshot(), windowFrame); } @Test public void testBlurBehindSimple() { startTestActivity(BLUR_ACTIVITY, extraInt(EXTRA_BLUR_BEHIND_RADIUS_PX, BLUR_BEHIND_PX), extraInt(EXTRA_NO_BLUR_BACKGROUND_COLOR, NO_BLUR_BACKGROUND_COLOR)); final Bitmap screenshot = takeScreenshot(); final Rect windowFrame = getWindowFrame(BLUR_ACTIVITY); assertBlurBehind(screenshot, windowFrame); assertNoBackgroundBlur(screenshot, windowFrame); } @Test public void testNoBackgroundBlurWhenBlurDisabled() { setForceBlurDisabled(true); startTestActivity(BLUR_ACTIVITY, extraInt(EXTRA_BACKGROUND_BLUR_RADIUS_PX, BACKGROUND_BLUR_PX), extraInt(EXTRA_NO_BLUR_BACKGROUND_COLOR, Color.TRANSPARENT)); verifyOnlyBackgroundImageVisible(); } @Test public void testNoBackgroundBlurForNonTranslucentWindow() { startTestActivity(BAD_BLUR_ACTIVITY, extraInt(EXTRA_BACKGROUND_BLUR_RADIUS_PX, BACKGROUND_BLUR_PX), extraInt(EXTRA_NO_BLUR_BACKGROUND_COLOR, Color.TRANSPARENT)); verifyOnlyBackgroundImageVisible(); } @Test public void testNoBlurBehindWhenBlurDisabled() { setForceBlurDisabled(true); startTestActivity(BLUR_ACTIVITY, extraInt(EXTRA_BLUR_BEHIND_RADIUS_PX, BLUR_BEHIND_PX), extraInt(EXTRA_NO_BLUR_BACKGROUND_COLOR, Color.TRANSPARENT)); verifyOnlyBackgroundImageVisible(); } @Test public void testNoBlurBehindWhenFlagNotSet() { startTestActivity(BAD_BLUR_ACTIVITY, extraInt(EXTRA_BLUR_BEHIND_RADIUS_PX, BLUR_BEHIND_PX), extraInt(EXTRA_NO_BLUR_BACKGROUND_COLOR, Color.TRANSPARENT)); verifyOnlyBackgroundImageVisible(); } @Test public void testBackgroundBlurActivatesFallbackDynamically() throws Exception { startTestActivity(BLUR_ACTIVITY, extraInt(EXTRA_BACKGROUND_BLUR_RADIUS_PX, BACKGROUND_BLUR_PX), extraInt(EXTRA_NO_BLUR_BACKGROUND_COLOR, NO_BLUR_BACKGROUND_COLOR)); final Rect windowFrame = getWindowFrame(BLUR_ACTIVITY); Bitmap screenshot = takeScreenshot(); assertBackgroundBlur(takeScreenshot(), windowFrame); assertNoBlurBehind(screenshot, windowFrame); setForceBlurDisabled(true); Thread.sleep(BACKGROUND_BLUR_DYNAMIC_UPDATE_WAIT_TIME); screenshot = takeScreenshot(); assertNoBackgroundBlur(screenshot, windowFrame); assertNoBlurBehind(screenshot, windowFrame); setForceBlurDisabled(false); Thread.sleep(BACKGROUND_BLUR_DYNAMIC_UPDATE_WAIT_TIME); screenshot = takeScreenshot(); assertBackgroundBlur(takeScreenshot(), windowFrame); assertNoBlurBehind(screenshot, windowFrame); } @Test public void testBlurBehindDisabledDynamically() throws Exception { startTestActivity(BLUR_ACTIVITY, extraInt(EXTRA_BLUR_BEHIND_RADIUS_PX, BLUR_BEHIND_PX), extraInt(EXTRA_NO_BLUR_BACKGROUND_COLOR, NO_BLUR_BACKGROUND_COLOR)); final Rect windowFrame = getWindowFrame(BLUR_ACTIVITY); Bitmap screenshot = takeScreenshot(); assertBlurBehind(screenshot, windowFrame); assertNoBackgroundBlur(screenshot, windowFrame); setForceBlurDisabled(true); Thread.sleep(BLUR_BEHIND_DYNAMIC_UPDATE_WAIT_TIME); screenshot = takeScreenshot(); assertNoBackgroundBlur(screenshot, windowFrame); assertNoBlurBehind(screenshot, windowFrame); setForceBlurDisabled(false); Thread.sleep(BLUR_BEHIND_DYNAMIC_UPDATE_WAIT_TIME); screenshot = takeScreenshot(); assertBlurBehind(screenshot, windowFrame); assertNoBackgroundBlur(screenshot, windowFrame); } @Test public void testBlurBehindAndBackgroundBlur() throws Exception { startTestActivity(BLUR_ACTIVITY, extraInt(EXTRA_BLUR_BEHIND_RADIUS_PX, BLUR_BEHIND_PX), extraInt(EXTRA_NO_BLUR_BACKGROUND_COLOR, NO_BLUR_BACKGROUND_COLOR), extraInt(EXTRA_BACKGROUND_BLUR_RADIUS_PX, BACKGROUND_BLUR_PX)); final Rect windowFrame = getWindowFrame(BLUR_ACTIVITY); Bitmap screenshot = takeScreenshot(); assertBlurBehind(screenshot, windowFrame); assertBackgroundBlurOverBlurBehind(screenshot, windowFrame); setForceBlurDisabled(true); Thread.sleep(BLUR_BEHIND_DYNAMIC_UPDATE_WAIT_TIME); screenshot = takeScreenshot(); assertNoBackgroundBlur(screenshot, windowFrame); assertNoBlurBehind(screenshot, windowFrame); setForceBlurDisabled(false); Thread.sleep(BLUR_BEHIND_DYNAMIC_UPDATE_WAIT_TIME); screenshot = takeScreenshot(); assertBlurBehind(screenshot, windowFrame); assertBackgroundBlurOverBlurBehind(screenshot, windowFrame); } @Test public void testBlurBehindAndBackgroundBlurSetWithAttributes() { startTestActivity(BLUR_ATTRIBUTES_ACTIVITY); final Rect windowFrame = getWindowFrame(BLUR_ATTRIBUTES_ACTIVITY); final Bitmap screenshot = takeScreenshot(); assertBlurBehind(screenshot, windowFrame); assertBackgroundBlurOverBlurBehind(screenshot, windowFrame); } @Test public void testBlurDestroyedAfterActivityFinished() { startTestActivity(BLUR_ACTIVITY, extraInt(EXTRA_BLUR_BEHIND_RADIUS_PX, BLUR_BEHIND_PX), extraInt(EXTRA_NO_BLUR_BACKGROUND_COLOR, NO_BLUR_BACKGROUND_COLOR), extraInt(EXTRA_BACKGROUND_BLUR_RADIUS_PX, BACKGROUND_BLUR_PX)); final Rect windowFrame = getWindowFrame(BLUR_ACTIVITY); Bitmap screenshot = takeScreenshot(); assertBlurBehind(screenshot, windowFrame); assertBackgroundBlurOverBlurBehind(screenshot, windowFrame); mBroadcastActionTrigger.finishBroadcastReceiverActivity(); mWmState.waitAndAssertActivityRemoved(BLUR_ACTIVITY); verifyOnlyBackgroundImageVisible(); } @Test public void testIsCrossWindowBlurEnabledUpdatedCorrectly() throws Exception { setForceBlurDisabled(true); Thread.sleep(DISABLE_BLUR_BROADCAST_WAIT_TIME); assertFalse(mContext.getSystemService(WindowManager.class).isCrossWindowBlurEnabled()); setForceBlurDisabled(false); Thread.sleep(DISABLE_BLUR_BROADCAST_WAIT_TIME); assertTrue(mContext.getSystemService(WindowManager.class).isCrossWindowBlurEnabled()); } @Test public void testBlurListener() throws Exception { ListenerTestActivity activity = startActivity(ListenerTestActivity.class); Mockito.verify(activity.mBlurEnabledListener).accept(true); setForceBlurDisabled(true); Thread.sleep(DISABLE_BLUR_BROADCAST_WAIT_TIME); assertFalse(mContext.getSystemService(WindowManager.class).isCrossWindowBlurEnabled()); Mockito.verify(activity.mBlurEnabledListener).accept(false); setForceBlurDisabled(false); Thread.sleep(DISABLE_BLUR_BROADCAST_WAIT_TIME); assertTrue(mContext.getSystemService(WindowManager.class).isCrossWindowBlurEnabled()); Mockito.verify(activity.mBlurEnabledListener, times(2)).accept(true); } public static class BlurListener implements Consumer { @Override public void accept(Boolean enabled) {} } public static class ListenerTestActivity extends FocusableActivity { Consumer mBlurEnabledListener = spy(new BlurListener()); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); View v = new LinearLayout(this); v.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View view) { getWindowManager().addCrossWindowBlurEnabledListener(mBlurEnabledListener); } @Override public void onViewDetachedFromWindow(View view) { getWindowManager().removeCrossWindowBlurEnabledListener(mBlurEnabledListener); } }); setContentView(v); } } private void startTestActivity(ComponentName activityName, final CliIntentExtra... extras) { launchActivity(activityName, extras); assertNotEquals(mWmState.getRootTaskIdByActivity(activityName), INVALID_STACK_ID); waitAndAssertResumedActivity(activityName, activityName + " must be resumed"); mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); } private Rect getWindowFrame(ComponentName activityName) { String windowName = getWindowName(activityName); mWmState.computeState(activityName); return mWmState.getMatchingVisibleWindowState(windowName).get(0).getFrame(); } private void verifyOnlyBackgroundImageVisible() { final Bitmap screenshot = takeScreenshot(); final int height = screenshot.getHeight(); final int width = screenshot.getWidth(); final int blueWidth = width / 2; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if (x < blueWidth) { ColorUtils.verifyColor("failed for pixel (x, y) = (" + x + ", " + y + ")", Color.BLUE, screenshot.getPixel(x, y), 1); } else { ColorUtils.verifyColor("failed for pixel (x, y) = (" + x + ", " + y + ")", Color.RED, screenshot.getPixel(x, y), 1); } } } } private static void assertBlurBehind(Bitmap screenshot, Rect windowFrame) { assertBlur(screenshot, BLUR_BEHIND_PX, 0, windowFrame.top); assertBlur(screenshot, BLUR_BEHIND_PX, windowFrame.bottom, screenshot.getHeight()); } private static void assertBackgroundBlur(Bitmap screenshot, Rect windowFrame) { assertBlur(screenshot, BACKGROUND_BLUR_PX, windowFrame.top, windowFrame.bottom); } private static void assertBackgroundBlurOverBlurBehind(Bitmap screenshot, Rect windowFrame) { // We are assuming that the background blur will become bigger by roughly half of the blur // behind radius assertBlur(screenshot, BACKGROUND_BLUR_PX + ((int) (BLUR_BEHIND_PX*0.5f)), windowFrame.top, windowFrame.bottom); } private static void assertNoBlurBehind(Bitmap screenshot, Rect windowFrame) { for (int x = 0; x < screenshot.getWidth(); x++) { for (int y = 0; y < screenshot.getHeight(); y++) { if (x < windowFrame.left) { ColorUtils.verifyColor("failed for pixel (x, y) = (" + x + ", " + y + ")", Color.BLUE, screenshot.getPixel(x, y), 1); } else if (x < screenshot.getWidth() / 2) { if (y < windowFrame.top || y > windowFrame.bottom) { ColorUtils.verifyColor("failed for pixel (x, y) = (" + x + ", " + y + ")", Color.BLUE, screenshot.getPixel(x, y), 1); } } else if (x <= windowFrame.right) { if (y < windowFrame.top || y > windowFrame.bottom) { ColorUtils.verifyColor("failed for pixel (x, y) = (" + x + ", " + y + ")", Color.RED, screenshot.getPixel(x, y), 1); } } else if (x > windowFrame.right) { ColorUtils.verifyColor("failed for pixel (x, y) = (" + x + ", " + y + ")", Color.RED, screenshot.getPixel(x, y), 1); } } } } private static void assertNoBackgroundBlur(Bitmap screenshot, Rect windowFrame) { for (int y = windowFrame.top; y < windowFrame.bottom; y++) { for (int x = windowFrame.left; x < windowFrame.right; x++) { ColorUtils.verifyColor("failed for pixel (x, y) = (" + x + ", " + y + ")", NO_BLUR_BACKGROUND_COLOR, screenshot.getPixel(x, y), 1); } } } private static 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; Color previousColor; Color currentColor; final int unaffectedBluePixelX = width / 2 - blurRadius * 2 - 1; final int unaffectedRedPixelX = width / 2 + blurRadius * 2 + 1; for (int y = startHeight; y < endHeight; y++) { ColorUtils.verifyColor( "failed for pixel (x, y) = (" + unaffectedBluePixelX + ", " + y + ")", Color.BLUE, screenshot.getPixel(unaffectedBluePixelX, y), 1); previousColor = Color.valueOf(Color.BLUE); for (int x = blurAreaStartX; x < blurAreaEndX; x += stepSize) { currentColor = screenshot.getColor(x, y); assertTrue("assertBlur failed for blue for pixel (x, y) = (" + x + ", " + y + ");" + " previousColor blue: " + previousColor.blue() + ", currentColor blue: " + currentColor.blue() , previousColor.blue() > currentColor.blue()); 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; } ColorUtils.verifyColor( "failed for pixel (x, y) = (" + unaffectedRedPixelX + ", " + y + ")", Color.RED, screenshot.getPixel(unaffectedRedPixelX, y), 1); } } private void setForceBlurDisabled(boolean disable) { Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DISABLE_WINDOW_BLURS, disable ? 1 : 0); } }