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 bfd76c5e29f9f..8b0bd429939d7 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1080,6 +1080,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(