From 430b258b6a56e1b2b557aed3295fe95429ba9e17 Mon Sep 17 00:00:00 2001 From: ryanlwlin Date: Thu, 27 May 2021 10:55:19 +0800 Subject: [PATCH] Fix incorrect boundary/appearance after folding the device When the device is folded, the screen size is changed. We need to compute the magnification frame and its boundary based on current window metrics. Besides, to keep the magnified center, we rescale the former center and apply it to the center of the new magnification frame. Folding the device also triggers orietation changes, which is the unexpected case. To avoid handling this case, we also check whethe it is the window hieght/width exchanging case. Bug: 185728667 Bug: 185536537 Bug: 180054792 Test: atest WindowMagnificationControllerTest manually test with follow cases 1. change display size or font size 2. orientate the device Change-Id: If2a93ad14bf8e07cf149180e639b35fc0bedfd10 --- .../WindowMagnificationController.java | 108 ++++++++++++++---- .../accessibility/TestableWindowManager.java | 22 ++-- .../WindowMagnificationControllerTest.java | 55 ++++++++- 3 files changed, 147 insertions(+), 38 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index 717c7156f9174..a51e3fcfd50b7 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -28,6 +28,7 @@ import android.annotation.Nullable; import android.annotation.UiContext; import android.content.Context; import android.content.pm.ActivityInfo; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.Matrix; @@ -35,6 +36,7 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; @@ -76,6 +78,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold MirrorWindowControl.MirrorWindowDelegate, MagnificationGestureDetector.OnGestureListener { private static final String TAG = "WindowMagnificationController"; + @SuppressWarnings("isloggabletaglength") + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Build.IS_DEBUGGABLE; // Delay to avoid updating state description too frequently. private static final int UPDATE_STATE_DESCRIPTION_DELAY_MS = 100; // It should be consistent with the value defined in WindowMagnificationGestureHandler. @@ -158,14 +162,15 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mRotation = display.getRotation(); mWm = context.getSystemService(WindowManager.class); - mWindowBounds = mWm.getCurrentWindowMetrics().getBounds(); + mWindowBounds = new Rect(mWm.getCurrentWindowMetrics().getBounds()); mResources = mContext.getResources(); mScale = mResources.getInteger(R.integer.magnification_default_scale); mBounceEffectDuration = mResources.getInteger( com.android.internal.R.integer.config_shortAnimTime); updateDimensions(); - setInitialStartBounds(); + setMagnificationFrameWith(mWindowBounds, mWindowBounds.width() / 2, + mWindowBounds.height() / 2); computeBounceAnimationScale(); mMirrorWindowControl = mirrorWindowControl; @@ -286,18 +291,59 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold * @param configDiff a bit mask of the differences between the configurations */ void onConfigurationChanged(int configDiff) { + if (DEBUG) { + Log.d(TAG, "onConfigurationChanged = " + Configuration.configurationDiffToString( + configDiff)); + } + if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) { + onRotate(); + } + + if ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0) { + updateAccessibilityWindowTitleIfNeeded(); + } + + boolean reCreateWindow = false; if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0) { updateDimensions(); computeBounceAnimationScale(); - if (isWindowVisible()) { - deleteWindowMagnification(); - enableWindowMagnification(Float.NaN, Float.NaN, Float.NaN); - } - } else if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) { - onRotate(); - } else if ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0) { - updateAccessibilityWindowTitleIfNeeded(); + reCreateWindow = true; } + + if ((configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0) { + reCreateWindow |= handleScreenSizeChanged(); + } + + // Recreate the window again to correct the window appearance due to density or + // window size changed not caused by rotation. + if (isWindowVisible() && reCreateWindow) { + deleteWindowMagnification(); + enableWindowMagnification(Float.NaN, Float.NaN, Float.NaN); + } + } + + /** + * Calculates the magnification frame if the window bounds is changed. + * Note that the orientation also changes the wind bounds, so it should be handled first. + * + * @return {@code true} if the magnification frame is changed with the new window bounds. + */ + private boolean handleScreenSizeChanged() { + final Rect oldWindowBounds = new Rect(mWindowBounds); + final Rect currentWindowBounds = mWm.getCurrentWindowMetrics().getBounds(); + + if (currentWindowBounds.equals(oldWindowBounds)) { + if (DEBUG) { + Log.d(TAG, "updateMagnificationFrame -- window bounds is not changed"); + } + return false; + } + mWindowBounds.set(currentWindowBounds); + final float newCenterX = (getCenterX()) * mWindowBounds.width() / oldWindowBounds.width(); + final float newCenterY = (getCenterY()) * mWindowBounds.height() / oldWindowBounds.height(); + setMagnificationFrameWith(mWindowBounds, (int) newCenterX, (int) newCenterY); + calculateMagnificationFrameBoundary(); + return true; } private void updateSystemUIStateIfNeeded() { @@ -311,30 +357,42 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mWm.updateViewLayout(mMirrorView, params); } - /** Handles MirrorWindow position when the device rotation changed. */ + /** + * Keep MirrorWindow position on the screen unchanged when device rotates 90° clockwise or + * anti-clockwise. + */ private void onRotate() { final Display display = mContext.getDisplay(); final int oldRotation = mRotation; - mWindowBounds = mWm.getCurrentWindowMetrics().getBounds(); - - setMagnificationFrameBoundary(); mRotation = display.getRotation(); + final int rotationDegree = getDegreeFromRotation(mRotation, oldRotation); + if (rotationDegree == 0 || rotationDegree == 180) { + Log.w(TAG, "onRotate -- rotate with the device. skip it"); + return; + } + final Rect currentWindowBounds = new Rect(mWm.getCurrentWindowMetrics().getBounds()); + if (currentWindowBounds.width() != mWindowBounds.height() + || currentWindowBounds.height() != mWindowBounds.width()) { + Log.w(TAG, "onRotate -- unexpected window height/width"); + return; + } + + mWindowBounds.set(currentWindowBounds); + + calculateMagnificationFrameBoundary(); if (!isWindowVisible()) { return; } // Keep MirrorWindow position on the screen unchanged when device rotates 90° // clockwise or anti-clockwise. - final int rotationDegree = getDegreeFromRotation(mRotation, oldRotation); + final Matrix matrix = new Matrix(); matrix.setRotate(rotationDegree); if (rotationDegree == 90) { matrix.postTranslate(mWindowBounds.width(), 0); } else if (rotationDegree == 270) { matrix.postTranslate(0, mWindowBounds.height()); - } else { - Log.w(TAG, "Invalid rotation change. " + rotationDegree); - return; } // The rect of MirrorView is going to be transformed. LayoutParams params = @@ -440,12 +498,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold } } - private void setInitialStartBounds() { + private void setMagnificationFrameWith(Rect windowBounds, int centerX, int centerY) { // Sets the initial frame area for the mirror and places it in the center of the display. - final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 2 + final int initSize = Math.min(windowBounds.width(), windowBounds.height()) / 2 + 2 * mMirrorSurfaceMargin; - final int initX = mWindowBounds.width() / 2 - initSize / 2; - final int initY = mWindowBounds.height() / 2 - initSize / 2; + final int initX = centerX - initSize / 2; + final int initY = centerY - initSize / 2; mMagnificationFrame.set(initX, initY, initX + initSize, initY + initSize); } @@ -553,7 +611,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mSourceBounds.set(left, top, right, bottom); } - private void setMagnificationFrameBoundary() { + private void calculateMagnificationFrameBoundary() { // Calculates width and height for magnification frame could exceed out the screen. // TODO : re-calculating again when scale is changed. // The half width of magnification frame. @@ -644,7 +702,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold : centerY - mMagnificationFrame.exactCenterY(); mScale = Float.isNaN(scale) ? mScale : scale; - setMagnificationFrameBoundary(); + calculateMagnificationFrameBoundary(); updateMagnificationFramePosition((int) offsetX, (int) offsetY); if (!isWindowVisible()) { createMirrorWindow(); @@ -764,6 +822,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold pw.println(" mOverlapWithGestureInsets:" + mOverlapWithGestureInsets); pw.println(" mScale:" + mScale); pw.println(" mMirrorViewBounds:" + (isWindowVisible() ? mMirrorViewBounds : "empty")); + pw.println(" mSourceBounds:" + + (isWindowVisible() ? mSourceBounds : "empty")); pw.println(" mSystemGestureTop:" + mSystemGestureTop); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java index 9621bedd92100..8bb9d423fa921 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java @@ -16,9 +16,6 @@ package com.android.systemui.accessibility; -import static android.view.WindowInsets.Type.systemGestures; - -import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Region; import android.view.Display; @@ -79,14 +76,11 @@ public class TestableWindowManager implements WindowManager { @Override public WindowMetrics getCurrentWindowMetrics() { - final Insets systemGesturesInsets = Insets.of(0, 0, 0, 10); - final WindowInsets insets = new WindowInsets.Builder() - .setInsets(systemGestures(), systemGesturesInsets) - .build(); + final WindowMetrics realMetrics = mWindowManager.getCurrentWindowMetrics(); final WindowMetrics windowMetrics = new WindowMetrics( - mWindowBounds == null ? mWindowManager.getCurrentWindowMetrics().getBounds() + mWindowBounds == null ? realMetrics.getBounds() : mWindowBounds, - mWindowInsets == null ? insets : mWindowInsets); + mWindowInsets == null ? realMetrics.getWindowInsets() : mWindowInsets); return windowMetrics; } @@ -106,10 +100,20 @@ public class TestableWindowManager implements WindowManager { return (WindowManager.LayoutParams) mView.getLayoutParams(); } + /** + * Sets the given window bounds to current window metrics. + * + * @param bounds the window bounds + */ public void setWindowBounds(Rect bounds) { mWindowBounds = bounds; } + /** + * Sets the given window insets to the current window metics. + * + * @param insets the window insets. + */ public void setWindowInsets(WindowInsets insets) { mWindowInsets = insets; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index f62069d35d350..76cc7a0eda133 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.accessibility; import static android.view.Choreographer.FrameCallback; +import static android.view.WindowInsets.Type.systemGestures; import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP; @@ -45,6 +46,8 @@ import android.app.Instrumentation; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Resources; +import android.graphics.Insets; +import android.graphics.PointF; import android.graphics.Rect; import android.os.Handler; import android.os.SystemClock; @@ -55,6 +58,7 @@ import android.view.Display; import android.view.Surface; import android.view.SurfaceControl; import android.view.View; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -239,11 +243,13 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY); mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION); + mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE); + mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE); }); } @Test - public void onOrientationChanged_enabled_updateDisplayRotationAndLayout() { + public void onOrientationChanged_enabled_updateDisplayRotationAndCenterStayAtSamePosition() { final Display display = Mockito.spy(mContext.getDisplay()); when(display.getRotation()).thenReturn(Surface.ROTATION_90); when(mContext.getDisplay()).thenReturn(display); @@ -251,13 +257,22 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, Float.NaN); }); + final PointF expectedCenter = new PointF(mWindowMagnificationController.getCenterY(), + mWindowMagnificationController.getCenterX()); + final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds()); + // Rotate the window 90 degrees. + windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom, + windowBounds.right); + mWindowManager.setWindowBounds(windowBounds); mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION); }); assertEquals(Surface.ROTATION_90, mWindowMagnificationController.mRotation); - // The first invocation is called when the surface is created. + final PointF actualCenter = new PointF(mWindowMagnificationController.getCenterX(), + mWindowMagnificationController.getCenterY()); + assertEquals(expectedCenter, actualCenter); verify(mWindowManager, times(2)).updateViewLayout(any(), any()); } @@ -274,6 +289,33 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { assertEquals(Surface.ROTATION_90, mWindowMagnificationController.mRotation); } + @Test + public void onScreenSizeChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() { + // The default position is at the center of the screen. + final float expectedRatio = 0.5f; + final Rect testWindowBounds = new Rect( + mWindowManager.getCurrentWindowMetrics().getBounds()); + testWindowBounds.set(testWindowBounds.left, testWindowBounds.top, + testWindowBounds.right + 100, testWindowBounds.bottom + 100); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + Float.NaN); + }); + mWindowManager.setWindowBounds(testWindowBounds); + + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE); + }); + + // The ratio of center to window size should be the same. + assertEquals(expectedRatio, + mWindowMagnificationController.getCenterX() / testWindowBounds.width(), + 0); + assertEquals(expectedRatio, + mWindowMagnificationController.getCenterY() / testWindowBounds.height(), + 0); + } + @Test public void onDensityChanged_enabled_updateDimensionsAndResetWindowMagnification() { mInstrumentation.runOnMainSync(() -> { @@ -419,9 +461,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { } @Test - public void moveWindowMagnificationToTheBottom_enabled_overlapFlagIsTrue() { - final WindowManager wm = mContext.getSystemService(WindowManager.class); - final Rect bounds = wm.getCurrentWindowMetrics().getBounds(); + public void moveWindowMagnificationToTheBottom_enabledWithGestureInset_overlapFlagIsTrue() { + final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); + final WindowInsets testInsets = new WindowInsets.Builder() + .setInsets(systemGestures(), Insets.of(0, 0, 0, 10)) + .build(); + mWindowManager.setWindowInsets(testInsets); mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, Float.NaN);