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
This commit is contained in:
ryanlwlin
2021-05-27 10:55:19 +08:00
parent ba2d2a3d5d
commit 430b258b6a
3 changed files with 147 additions and 38 deletions

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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);