diff --git a/packages/SystemUI/res/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml index ae16e5c919c42..7c755e5465898 100644 --- a/packages/SystemUI/res/layout/window_magnifier_view.xml +++ b/packages/SystemUI/res/layout/window_magnifier_view.xml @@ -77,8 +77,7 @@ android:layout_height="@dimen/magnification_drag_view_size" android:layout_margin="@dimen/magnification_inner_border_margin" android:layout_gravity="right|bottom" - android:paddingEnd="@dimen/magnifier_drag_handle_padding" - android:paddingBottom="@dimen/magnifier_drag_handle_padding" + android:padding="@dimen/magnifier_drag_handle_padding" android:scaleType="center" android:importantForAccessibility="no" android:src="@drawable/ic_move_magnification"/> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 207879c127d19..fa0d6769ca48f 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1036,6 +1036,9 @@ 3dp 300dp + + 32dp 48dp diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java index 6e14ddc671a32..d7fead16e2694 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java @@ -28,6 +28,7 @@ import android.hardware.display.DisplayManager; import android.os.Handler; import android.view.Display; import android.view.SurfaceControl; +import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.view.accessibility.IWindowMagnificationConnection; @@ -89,9 +90,14 @@ public class WindowMagnification extends CoreStartable implements WindowMagnifie TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null); return new WindowMagnificationController( windowContext, - mHandler, new WindowMagnificationAnimationController(windowContext), - new SfVsyncFrameCallbackProvider(), null, - new SurfaceControl.Transaction(), mWindowMagnifierCallback, mSysUiState); + mHandler, + new WindowMagnificationAnimationController(windowContext), + new SfVsyncFrameCallbackProvider(), + null, + new SurfaceControl.Transaction(), + mWindowMagnifierCallback, + mSysUiState, + WindowManagerGlobal::getWindowSession); } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index a1428f3120c8e..1eedae6f01b0d 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -57,11 +57,11 @@ import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.WindowManager; -import android.view.WindowManagerGlobal; import android.view.WindowMetrics; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.IRemoteMagnificationAnimationCallback; +import android.widget.FrameLayout; import androidx.core.math.MathUtils; @@ -75,6 +75,7 @@ import java.io.PrintWriter; import java.text.NumberFormat; import java.util.Collections; import java.util.Locale; +import java.util.function.Supplier; /** * Class to handle adding and removing a window magnification. @@ -92,6 +93,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private static final Range A11Y_ACTION_SCALE_RANGE = new Range<>(2.0f, 8.0f); private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f; private static final float ANIMATION_BOUNCE_EFFECT_SCALE = 1.05f; + private final Context mContext; private final Resources mResources; private final Handler mHandler; @@ -163,8 +165,13 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private SurfaceView mMirrorSurfaceView; private int mMirrorSurfaceMargin; private int mBorderDragSize; - private int mDragViewSize; private int mOuterBorderSize; + + /** + * How far from the right edge of the screen you need to drag the window before the button + * repositions to the other side. + */ + private int mButtonRepositionThresholdFromEdge; // The boundary of magnification frame. private final Rect mMagnificationFrameBoundary = new Rect(); // The top Y of the system gesture rect at the bottom. Set to -1 if it is invalid. @@ -172,6 +179,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private int mMinWindowSize; private final WindowMagnificationAnimationController mAnimationController; + private final Supplier mGlobalWindowSessionSupplier; private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; private final MagnificationGestureDetector mGestureDetector; private final int mBounceEffectDuration; @@ -182,18 +190,25 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private SysUiState mSysUiState; // Set it to true when the view is overlapped with the gesture insets at the bottom. private boolean mOverlapWithGestureInsets; + private boolean mIsDragging; @Nullable private MirrorWindowControl mMirrorWindowControl; - WindowMagnificationController(@UiContext Context context, @NonNull Handler handler, + WindowMagnificationController( + @UiContext Context context, + @NonNull Handler handler, @NonNull WindowMagnificationAnimationController animationController, SfVsyncFrameCallbackProvider sfVsyncFrameProvider, - MirrorWindowControl mirrorWindowControl, SurfaceControl.Transaction transaction, - @NonNull WindowMagnifierCallback callback, SysUiState sysUiState) { + MirrorWindowControl mirrorWindowControl, + SurfaceControl.Transaction transaction, + @NonNull WindowMagnifierCallback callback, + SysUiState sysUiState, + @NonNull Supplier globalWindowSessionSupplier) { mContext = context; mHandler = handler; mAnimationController = animationController; + mGlobalWindowSessionSupplier = globalWindowSessionSupplier; mAnimationController.setWindowMagnificationController(this); mSfVsyncFrameProvider = sfVsyncFrameProvider; mWindowMagnifierCallback = callback; @@ -249,8 +264,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold }; mMirrorSurfaceViewLayoutChangeListener = - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) - -> applyTapExcludeRegion(); + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> + mMirrorView.post(this::applyTapExcludeRegion); mMirrorViewGeometryVsyncCallback = l -> { @@ -283,10 +298,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold R.dimen.magnification_mirror_surface_margin); mBorderDragSize = mResources.getDimensionPixelSize( R.dimen.magnification_border_drag_size); - mDragViewSize = mResources.getDimensionPixelSize( - R.dimen.magnification_drag_view_size); mOuterBorderSize = mResources.getDimensionPixelSize( R.dimen.magnification_outer_border_margin); + mButtonRepositionThresholdFromEdge = + mResources.getDimensionPixelSize( + R.dimen.magnification_button_reposition_threshold_from_edge); mMinWindowSize = mResources.getDimensionPixelSize( com.android.internal.R.dimen.accessibility_window_magnifier_min_size); } @@ -551,10 +567,13 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold } private void applyTapExcludeRegion() { + // Sometimes this can get posted and run after deleteWindowMagnification() is called. + if (mMirrorView == null) return; + final Region tapExcludeRegion = calculateTapExclude(); final IWindow window = IWindow.Stub.asInterface(mMirrorView.getWindowToken()); try { - IWindowSession session = WindowManagerGlobal.getWindowSession(); + IWindowSession session = mGlobalWindowSessionSupplier.get(); session.updateTapExcludeRegion(window, tapExcludeRegion); } catch (RemoteException e) { } @@ -564,9 +583,9 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold Region regionInsideDragBorder = new Region(mBorderDragSize, mBorderDragSize, mMirrorView.getWidth() - mBorderDragSize, mMirrorView.getHeight() - mBorderDragSize); - Rect dragArea = new Rect(mMirrorView.getWidth() - mDragViewSize - mBorderDragSize, - mMirrorView.getHeight() - mDragViewSize - mBorderDragSize, - mMirrorView.getWidth(), mMirrorView.getHeight()); + Rect dragArea = new Rect(); + mDragView.getHitRect(dragArea); + regionInsideDragBorder.op(dragArea, Region.Op.DIFFERENCE); return regionInsideDragBorder; } @@ -713,6 +732,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mMirrorView.setTranslationX(translationX); mMirrorView.setTranslationY(translationY); mWm.updateViewLayout(mMirrorView, params); + + // If they are not dragging the handle, we can move the drag handle immediately without + // disruption. But if they are dragging it, we avoid moving until the end of the drag. + if (!mIsDragging) { + mMirrorView.post(this::maybeRepositionButton); + } } @Override @@ -1060,14 +1085,40 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold @Override public boolean onStart(float x, float y) { + mIsDragging = true; return true; } @Override public boolean onFinish(float x, float y) { + maybeRepositionButton(); + mIsDragging = false; return false; } + /** Moves the button to the opposite edge if the frame is against the edge of the screen. */ + private void maybeRepositionButton() { + if (mMirrorView == null) return; + + final float screenEdgeX = mWindowBounds.right - mButtonRepositionThresholdFromEdge; + final FrameLayout.LayoutParams layoutParams = + (FrameLayout.LayoutParams) mDragView.getLayoutParams(); + + mMirrorView.getBoundsOnScreen(mTmpRect); + + final int newGravity; + if (mTmpRect.right >= screenEdgeX) { + newGravity = Gravity.BOTTOM | Gravity.LEFT; + } else { + newGravity = Gravity.BOTTOM | Gravity.RIGHT; + } + if (newGravity != layoutParams.gravity) { + layoutParams.gravity = newGravity; + mDragView.setLayoutParams(layoutParams); + mDragView.post(this::applyTapExcludeRegion); + } + } + private void animateBounceEffect() { final ObjectAnimator scaleAnimator = ObjectAnimator.ofPropertyValuesHolder(mMirrorView, PropertyValuesHolder.ofFloat(View.SCALE_X, 1, mBounceEffectAnimationScale, 1), diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java index 21c3d6ea0660a..08503308209b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java @@ -38,6 +38,7 @@ import android.testing.AndroidTestingRunner; import android.view.SurfaceControl; import android.view.View; import android.view.WindowManager; +import android.view.WindowManagerGlobal; import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.view.animation.AccelerateInterpolator; @@ -761,8 +762,16 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { SfVsyncFrameCallbackProvider sfVsyncFrameProvider, MirrorWindowControl mirrorWindowControl, SurfaceControl.Transaction transaction, WindowMagnifierCallback callback, SysUiState sysUiState) { - super(context, handler, animationController, sfVsyncFrameProvider, mirrorWindowControl, - transaction, callback, sysUiState); + super( + context, + handler, + animationController, + sfVsyncFrameProvider, + mirrorWindowControl, + transaction, + callback, + sysUiState, + WindowManagerGlobal::getWindowSession); mSpyController = Mockito.mock(WindowMagnificationController.class); } 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 b7d345965d36a..3d77d64a7988f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -28,6 +28,7 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasItems; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -53,18 +54,23 @@ import android.content.res.Resources; import android.graphics.Insets; import android.graphics.PointF; import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.RegionIterator; import android.os.Handler; +import android.os.RemoteException; import android.os.SystemClock; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableResources; import android.text.TextUtils; import android.view.Display; +import android.view.IWindowSession; import android.view.Surface; import android.view.SurfaceControl; import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; +import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.IRemoteMagnificationAnimationCallback; @@ -120,9 +126,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { private WindowMagnificationController mWindowMagnificationController; private Instrumentation mInstrumentation; private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0); + private IWindowSession mWindowSessionSpy; @Before - public void setUp() { + public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); mContext = Mockito.spy(getContext()); mHandler = new FakeHandler(TestableLooper.get(this).getLooper()); @@ -130,6 +137,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final WindowManager wm = mContext.getSystemService(WindowManager.class); mWindowManager = spy(new TestableWindowManager(wm)); + mWindowSessionSpy = spy(WindowManagerGlobal.getWindowSession()); + mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); doAnswer(invocation -> { FrameCallback callback = invocation.getArgument(0); @@ -142,9 +151,17 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mResources = getContext().getOrCreateTestableResources().getResources(); mWindowMagnificationAnimationController = new WindowMagnificationAnimationController( mContext, mValueAnimator); - mWindowMagnificationController = new WindowMagnificationController(mContext, - mHandler, mWindowMagnificationAnimationController, mSfVsyncFrameProvider, - mMirrorWindowControl, mTransaction, mWindowMagnifierCallback, mSysUiState); + mWindowMagnificationController = + new WindowMagnificationController( + mContext, + mHandler, + mWindowMagnificationAnimationController, + mSfVsyncFrameProvider, + mMirrorWindowControl, + mTransaction, + mWindowMagnifierCallback, + mSysUiState, + () -> mWindowSessionSpy); verify(mMirrorWindowControl).setWindowDelegate( any(MirrorWindowControl.MirrorWindowDelegate.class)); @@ -715,6 +732,77 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { ReferenceTestUtils.waitForCondition(() -> hasMagnificationOverlapFlag()); } + @Test + public void moveWindowMagnificationToRightEdge_dragHandleMovesToLeftAndUpdatesTapExcludeRegion() + throws RemoteException { + final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); + setSystemGestureInsets(); + mInstrumentation.runOnMainSync( + () -> { + mWindowMagnificationController.enableWindowMagnificationInternal( + Float.NaN, Float.NaN, Float.NaN); + }); + + mInstrumentation.runOnMainSync( + () -> { + mWindowMagnificationController.moveWindowMagnifier(bounds.width(), 0); + }); + + // Wait for Region updated. + waitForIdleSync(); + + final ArgumentCaptor tapExcludeRegionCapturer = + ArgumentCaptor.forClass(Region.class); + verify(mWindowSessionSpy, times(2)) + .updateTapExcludeRegion(any(), tapExcludeRegionCapturer.capture()); + Region tapExcludeRegion = tapExcludeRegionCapturer.getValue(); + RegionIterator iterator = new RegionIterator(tapExcludeRegion); + + final Rect topRect = new Rect(); + final Rect bottomRect = new Rect(); + assertTrue(iterator.next(topRect)); + assertTrue(iterator.next(bottomRect)); + assertFalse(iterator.next(new Rect())); + + assertEquals(topRect.right, bottomRect.right); + assertNotEquals(topRect.left, bottomRect.left); + } + + @Test + public void moveWindowMagnificationToLeftEdge_dragHandleMovesToRightAndUpdatesTapExcludeRegion() + throws RemoteException { + final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); + setSystemGestureInsets(); + mInstrumentation.runOnMainSync( + () -> { + mWindowMagnificationController.enableWindowMagnificationInternal( + Float.NaN, Float.NaN, Float.NaN); + }); + + mInstrumentation.runOnMainSync( + () -> { + mWindowMagnificationController.moveWindowMagnifier(-bounds.width(), 0); + }); + + // Wait for Region updated. + waitForIdleSync(); + + final ArgumentCaptor tapExcludeRegionCapturer = + ArgumentCaptor.forClass(Region.class); + verify(mWindowSessionSpy).updateTapExcludeRegion(any(), tapExcludeRegionCapturer.capture()); + Region tapExcludeRegion = tapExcludeRegionCapturer.getValue(); + RegionIterator iterator = new RegionIterator(tapExcludeRegion); + + final Rect topRect = new Rect(); + final Rect bottomRect = new Rect(); + assertTrue(iterator.next(topRect)); + assertTrue(iterator.next(bottomRect)); + assertFalse(iterator.next(new Rect())); + + assertEquals(topRect.left, bottomRect.left); + assertNotEquals(topRect.right, bottomRect.right); + } + @Test public void setMinimumWindowSize_enabled_expectedWindowSize() { final int minimumWindowSize = mResources.getDimensionPixelSize(