Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9555ceb3 authored by ryanlwlin's avatar ryanlwlin
Browse files

Support set window size API

It provides resize api which constraint the minimum and
maximum window size.

Since the magnification window could be an arbitary rectangle,
We need to change the window size and the center based on
the transformation result.

Bug: 188136191
Test: atest WindowMagnificationControllerTest
Change-Id: I63abe89d087988460efc7d0586c5a309e6142768
parent f5cdd6f9
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -606,6 +606,9 @@
    <!-- The padding ratio of the Accessibility icon foreground drawable -->
    <item name="accessibility_icon_foreground_padding_ratio" type="dimen">21.88%</item>

    <!-- The minimum window size of the accessibility window magnifier -->
    <dimen name="accessibility_window_magnifier_min_size">122dp</dimen>

    <!-- Margin around the various security views -->
    <dimen name="keyguard_muliuser_selector_margin">8dp</dimen>

+1 −0
Original line number Diff line number Diff line
@@ -4379,6 +4379,7 @@
  <java-symbol type="color" name="accessibility_focus_highlight_color" />
  <!-- Width of the outline stroke used by the accessibility focus rectangle -->
  <java-symbol type="dimen" name="accessibility_focus_highlight_stroke_width" />
  <java-symbol type="dimen" name="accessibility_window_magnifier_min_size" />

  <java-symbol type="bool" name="config_attachNavBarToAppDuringTransition" />

+86 −29
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
import android.util.Range;
import android.util.Size;
import android.view.Choreographer;
import android.view.Display;
import android.view.Gravity;
@@ -62,6 +63,8 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.IRemoteMagnificationAnimationCallback;

import androidx.core.math.MathUtils;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.R;
@@ -166,6 +169,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
    private final Rect mMagnificationFrameBoundary = new Rect();
    // The top Y of the system gesture rect at the bottom. Set to -1 if it is invalid.
    private int mSystemGestureTop = -1;
    private int mMinWindowSize;

    private final WindowMagnificationAnimationController mAnimationController;
    private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
@@ -208,8 +212,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
        mBounceEffectDuration = mResources.getInteger(
                com.android.internal.R.integer.config_shortAnimTime);
        updateDimensions();
        setMagnificationFrameWith(mWindowBounds, mWindowBounds.width() / 2,
                mWindowBounds.height() / 2);

        final Size windowSize = getDefaultWindowSizeWithWindowBounds(mWindowBounds);
        setMagnificationFrame(windowSize.getWidth(), windowSize.getHeight(),
                mWindowBounds.width() / 2, mWindowBounds.height() / 2);
        computeBounceAnimationScale();

        mMirrorWindowControl = mirrorWindowControl;
@@ -281,6 +287,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
                R.dimen.magnification_drag_view_size);
        mOuterBorderSize = mResources.getDimensionPixelSize(
                R.dimen.magnification_outer_border_margin);
        mMinWindowSize = mResources.getDimensionPixelSize(
                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
    }

    private void computeBounceAnimationScale() {
@@ -414,9 +422,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
            return false;
        }
        mWindowBounds.set(currentWindowBounds);
        final Size windowSize = getDefaultWindowSizeWithWindowBounds(mWindowBounds);
        final float newCenterX = (getCenterX()) * mWindowBounds.width() / oldWindowBounds.width();
        final float newCenterY = (getCenterY()) * mWindowBounds.height() / oldWindowBounds.height();
        setMagnificationFrameWith(mWindowBounds, (int) newCenterX, (int) newCenterY);

        setMagnificationFrame(windowSize.getWidth(), windowSize.getHeight(), (int) newCenterX,
                (int) newCenterY);
        calculateMagnificationFrameBoundary();
        return true;
    }
@@ -454,11 +465,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold

        mWindowBounds.set(currentWindowBounds);

        calculateMagnificationFrameBoundary();

        if (!isWindowVisible()) {
            return;
        }
        // Keep MirrorWindow position on the screen unchanged when device rotates 90°
        // clockwise or anti-clockwise.

@@ -469,14 +475,13 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
        } else if (rotationDegree == 270) {
            matrix.postTranslate(0, mWindowBounds.height());
        }
        // The rect of MirrorView is going to be transformed.
        LayoutParams params =
                (LayoutParams) mMirrorView.getLayoutParams();
        mTmpRect.set(params.x, params.y, params.x + params.width, params.y + params.height);
        final RectF transformedRect = new RectF(mTmpRect);

        final RectF transformedRect = new RectF(mMagnificationFrame);
        // The window frame is going to be transformed by the rotation matrix.
        transformedRect.inset(-mMirrorSurfaceMargin, -mMirrorSurfaceMargin);
        matrix.mapRect(transformedRect);
        moveWindowMagnifier(transformedRect.left - mTmpRect.left,
                transformedRect.top - mTmpRect.top);
        setWindowSizeAndCenter((int) transformedRect.width(), (int) transformedRect.height(),
                (int) transformedRect.centerX(), (int) transformedRect.centerY());
    }

    /** Returns the rotation degree change of two {@link Surface.Rotation} */
@@ -573,16 +578,52 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
        }
    }

    private void setMagnificationFrameWith(Rect windowBounds, int centerX, int centerY) {
    /**
     * Sets the window size with given width and height in pixels without changing the
     * window center. The width or the height will be clamped in the range
     * [{@link #mMinWindowSize}, screen width or height].
     *
     * @param width the window width in pixels
     * @param height the window height in pixels.
     */
    public void setWindowSize(int width, int height) {
        setWindowSizeAndCenter(width, height, Float.NaN, Float.NaN);
    }

    void setWindowSizeAndCenter(int width, int height, float centerX, float centerY) {
        width = MathUtils.clamp(width, mMinWindowSize, mWindowBounds.width());
        height = MathUtils.clamp(height, mMinWindowSize, mWindowBounds.height());

        if (Float.isNaN(centerX)) {
            centerX = mMagnificationFrame.centerX();
        }
        if (Float.isNaN(centerX)) {
            centerY = mMagnificationFrame.centerY();
        }

        final int frameWidth = width - 2 * mMirrorSurfaceMargin;
        final int frameHeight = height - 2 * mMirrorSurfaceMargin;
        setMagnificationFrame(frameWidth, frameHeight, (int) centerX, (int) centerY);
        calculateMagnificationFrameBoundary();
        // Correct the frame position to ensure it is inside the boundary.
        updateMagnificationFramePosition(0, 0);
        modifyWindowMagnification(true);
    }

    private void setMagnificationFrame(int width, int height, int centerX, int centerY) {
        // Sets the initial frame area for the mirror and place it to the given center on the
        // display.
        final int initX = centerX - width / 2;
        final int initY = centerY - height / 2;
        mMagnificationFrame.set(initX, initY, initX + width, initY + height);
    }

    private Size getDefaultWindowSizeWithWindowBounds(Rect windowBounds) {
        int initSize = Math.min(windowBounds.width(), windowBounds.height()) / 2;
        initSize = Math.min(mResources.getDimensionPixelSize(R.dimen.magnification_max_frame_size),
                initSize);
        initSize += 2 * mMirrorSurfaceMargin;
        final int initX = centerX - initSize / 2;
        final int initY = centerY - initSize / 2;
        mMagnificationFrame.set(initX, initY, initX + initSize, initY + initSize);
        return new Size(initSize, initSize);
    }

    /**
@@ -596,8 +637,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
        }
        mTransaction.show(mMirrorSurface)
                .reparent(mMirrorSurface, mMirrorSurfaceView.getSurfaceControl());

        modifyWindowMagnification(mTransaction);
        modifyWindowMagnification(false);
    }

    private void addDragTouchListeners() {
@@ -615,18 +655,25 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
    }

    /**
     * Modifies the placement of the mirrored content when the position of mMirrorView is updated.
     * Modifies the placement of the mirrored content when the position or size of mMirrorView is
     * updated.
     *
     * @param computeWindowSize set to {@code true} to compute window size with
     * {@link #mMagnificationFrame}.
     */
    private void modifyWindowMagnification(SurfaceControl.Transaction t) {
    private void modifyWindowMagnification(boolean computeWindowSize) {
        mSfVsyncFrameProvider.postFrameCallback(mMirrorViewGeometryVsyncCallback);
        updateMirrorViewLayout();
        updateMirrorViewLayout(computeWindowSize);
    }

    /**
     * Updates the layout params of MirrorView and translates MirrorView position when the view is
     * moved close to the screen edges.
     * Updates the layout params of MirrorView based on the size of {@link #mMagnificationFrame}
     * and translates MirrorView position when the view is moved close to the screen edges;
     *
     * @param computeWindowSize set to {@code true} to compute window size with
     * {@link #mMagnificationFrame}.
     */
    private void updateMirrorViewLayout() {
    private void updateMirrorViewLayout(boolean computeWindowSize) {
        if (!isWindowVisible()) {
            return;
        }
@@ -637,6 +684,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
                (LayoutParams) mMirrorView.getLayoutParams();
        params.x = mMagnificationFrame.left - mMirrorSurfaceMargin;
        params.y = mMagnificationFrame.top - mMirrorSurfaceMargin;
        if (computeWindowSize) {
            params.width = mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin;
            params.height = mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin;
        }

        // Translates MirrorView position to make MirrorSurfaceView that is inside MirrorView
        // able to move close to the screen edges.
@@ -899,7 +950,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
            createMirrorWindow();
            showControls();
        } else {
            modifyWindowMagnification(mTransaction);
            modifyWindowMagnification(false);
        }
    }

@@ -930,7 +981,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
            return;
        }
        if (updateMagnificationFramePosition((int) offsetX, (int) offsetY)) {
            modifyWindowMagnification(mTransaction);
            modifyWindowMagnification(false);
        }
    }

@@ -1014,9 +1065,15 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
        pw.println("      mOverlapWithGestureInsets:" + mOverlapWithGestureInsets);
        pw.println("      mScale:" + mScale);
        pw.println("      mMirrorViewBounds:" + (isWindowVisible() ? mMirrorViewBounds : "empty"));
        pw.println("      mMagnificationFrameBoundary:"
                + (isWindowVisible() ? mMagnificationFrameBoundary : "empty"));
        pw.println("      mMagnificationFrame:"
                + (isWindowVisible() ? mMagnificationFrame : "empty"));
        pw.println("      mSourceBounds:"
                 + (isWindowVisible() ? mSourceBounds : "empty"));
        pw.println("      mSystemGestureTop:" + mSystemGestureTop);
        pw.println("      mMagnificationFrameOffsetX:" + mMagnificationFrameOffsetX);
        pw.println("      mMagnificationFrameOffsetY:" + mMagnificationFrameOffsetY);
    }

    private class MirrorWindowA11yDelegate extends View.AccessibilityDelegate {
+117 −7
Original line number Diff line number Diff line
@@ -88,6 +88,7 @@ import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@LargeTest
@TestableLooper.RunWithLooper
@@ -345,15 +346,17 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {

    @Test
    public void onOrientationChanged_disabled_updateDisplayRotation() {
        final Display display = Mockito.spy(mContext.getDisplay());
        when(display.getRotation()).thenReturn(Surface.ROTATION_90);
        when(mContext.getDisplay()).thenReturn(display);
        final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
        // Rotate the window clockwise 90 degree.
        windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom,
                windowBounds.right);
        mWindowManager.setWindowBounds(windowBounds);
        final int newRotation = simulateRotateTheDevice();

        mInstrumentation.runOnMainSync(() -> {
            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
        });
        mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
                ActivityInfo.CONFIG_ORIENTATION));

        assertEquals(Surface.ROTATION_90, mWindowMagnificationController.mRotation);
        assertEquals(newRotation, mWindowMagnificationController.mRotation);
    }

    @Test
@@ -603,6 +606,113 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
        ReferenceTestUtils.waitForCondition(() -> hasMagnificationOverlapFlag());
    }

    @Test
    public void setMinimumWindowSize_enabled_expectedWindowSize() {
        final int minimumWindowSize = mResources.getDimensionPixelSize(
                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
        final int  expectedWindowHeight = minimumWindowSize;
        final int  expectedWindowWidth = minimumWindowSize;
        mInstrumentation.runOnMainSync(
                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
                        Float.NaN, Float.NaN));

        final AtomicInteger actualWindowHeight = new AtomicInteger();
        final AtomicInteger actualWindowWidth = new AtomicInteger();
        mInstrumentation.runOnMainSync(() -> {
            mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
            actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
            actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);

        });

        assertEquals(expectedWindowHeight, actualWindowHeight.get());
        assertEquals(expectedWindowWidth, actualWindowWidth.get());
    }

    @Test
    public void setMinimumWindowSizeThenEnable_expectedWindowSize() {
        final int minimumWindowSize = mResources.getDimensionPixelSize(
                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
        final int  expectedWindowHeight = minimumWindowSize;
        final int  expectedWindowWidth = minimumWindowSize;

        final AtomicInteger actualWindowHeight = new AtomicInteger();
        final AtomicInteger actualWindowWidth = new AtomicInteger();
        mInstrumentation.runOnMainSync(() -> {
            mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
                    Float.NaN, Float.NaN);
            actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
            actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
        });

        assertEquals(expectedWindowHeight, actualWindowHeight.get());
        assertEquals(expectedWindowWidth, actualWindowWidth.get());
    }

    @Test
    public void setWindowSizeLessThanMin_enabled_minimumWindowSize() {
        final int minimumWindowSize = mResources.getDimensionPixelSize(
                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
        mInstrumentation.runOnMainSync(
                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
                        Float.NaN, Float.NaN));

        final AtomicInteger actualWindowHeight = new AtomicInteger();
        final AtomicInteger actualWindowWidth = new AtomicInteger();
        mInstrumentation.runOnMainSync(() -> {
            mWindowMagnificationController.setWindowSize(minimumWindowSize - 10,
                    minimumWindowSize - 10);
            actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
            actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
        });

        assertEquals(minimumWindowSize, actualWindowHeight.get());
        assertEquals(minimumWindowSize, actualWindowWidth.get());
    }

    @Test
    public void setWindowSizeLargerThanScreenSize_enabled_windowSizeIsScreenSize() {
        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
        mInstrumentation.runOnMainSync(
                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
                        Float.NaN, Float.NaN));

        final AtomicInteger actualWindowHeight = new AtomicInteger();
        final AtomicInteger actualWindowWidth = new AtomicInteger();
        mInstrumentation.runOnMainSync(() -> {
            mWindowMagnificationController.setWindowSize(bounds.width() + 10, bounds.height() + 10);
            actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
            actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
        });

        assertEquals(bounds.height(), actualWindowHeight.get());
        assertEquals(bounds.width(), actualWindowWidth.get());
    }

    @Test
    public void setWindowCenterOutOfScreen_enabled_magnificationCenterIsInsideTheScreen() {

        final int minimumWindowSize = mResources.getDimensionPixelSize(
                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
        mInstrumentation.runOnMainSync(
                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
                        Float.NaN, Float.NaN));

        final AtomicInteger magnificationCenterX = new AtomicInteger();
        final AtomicInteger magnificationCenterY = new AtomicInteger();
        mInstrumentation.runOnMainSync(() -> {
            mWindowMagnificationController.setWindowSizeAndCenter(minimumWindowSize,
                    minimumWindowSize, bounds.right, bounds.bottom);
            magnificationCenterX.set((int) mWindowMagnificationController.getCenterX());
            magnificationCenterY.set((int) mWindowMagnificationController.getCenterY());
        });

        assertTrue(magnificationCenterX.get() < bounds.right);
        assertTrue(magnificationCenterY.get() < bounds.bottom);
    }

    private CharSequence getAccessibilityWindowTitle() {
        final View mirrorView = mWindowManager.getAttachedView();
        if (mirrorView == null) {