Merge "feat(magnification): move mag button when window reaches the screen edge"
This commit is contained in:
committed by
Android (Google) Code Review
commit
6738d00a76
@@ -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"/>
|
||||
|
||||
@@ -1080,6 +1080,9 @@
|
||||
<!-- The extra padding to show the whole outer border -->
|
||||
<dimen name="magnifier_drag_handle_padding">3dp</dimen>
|
||||
<dimen name="magnification_max_frame_size">300dp</dimen>
|
||||
<!-- How far from the right edge of the screen you need to drag the window before the button
|
||||
repositions to the other side. -->
|
||||
<dimen name="magnification_button_reposition_threshold_from_edge">32dp</dimen>
|
||||
|
||||
<!-- Home Controls -->
|
||||
<dimen name="controls_header_menu_size">48dp</dimen>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Float> 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<IWindowSession> 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<IWindowSession> 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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Region> 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<Region> 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(
|
||||
|
||||
Reference in New Issue
Block a user