From 094c77536b5d8baa8d7fecba2c48d1032b28e16d Mon Sep 17 00:00:00 2001 From: Ben Lin Date: Wed, 11 Mar 2020 12:10:51 -0700 Subject: [PATCH] Reland: Track current resized bounds for resized PIP. Previously, there were only mNormalMovementBounds and mExpandedMovementBounds since these were the only two sizes PIP window can be in (on a per-onMovementBoundsChanged call, that is). With the ability to resize PIP on the horizon, we will need to update the movement bound as it resizes, and also calculcate this movement with the new bound on the fly. Bug: 147361175 Test: Manually. Resize PIP, and then drag around and off-screen, let go - see PIP window snaps back. Resize PIP, tap window (see it expands), and wait for timeout. Restores to previously resized PIP bound. Change-Id: Iaec103d3964e6141fe4b769879c91159e57f7546 --- .../systemui/pip/PipBoundsHandler.java | 2 +- .../pip/phone/PipResizeGestureHandler.java | 18 +- .../systemui/pip/phone/PipTouchHandler.java | 71 +++++--- .../pip/phone/PipTouchHandlerTest.java | 170 ++++++++++++++++++ 4 files changed, 230 insertions(+), 31 deletions(-) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java index fb348f483c46f..32cdedfa6f4d3 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java @@ -228,8 +228,8 @@ public class PipBoundsHandler { */ Rect getDestinationBounds(float aspectRatio, Rect bounds) { final Rect destinationBounds; - final Rect defaultBounds = getDefaultBounds(mReentrySnapFraction, mReentrySize); if (bounds == null) { + final Rect defaultBounds = getDefaultBounds(mReentrySnapFraction, mReentrySize); destinationBounds = new Rect(defaultBounds); } else { destinationBounds = new Rect(bounds); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java index 8fff419af5a1a..25acce678594c 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java @@ -65,6 +65,7 @@ public class PipResizeGestureHandler { private final PointF mDownPoint = new PointF(); private final Point mMaxSize = new Point(); private final Point mMinSize = new Point(); + private final Rect mLastResizeBounds = new Rect(); private final Rect mTmpBounds = new Rect(); private final int mDelta; @@ -187,17 +188,13 @@ public class PipResizeGestureHandler { private void onMotionEvent(MotionEvent ev) { int action = ev.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { + mLastResizeBounds.setEmpty(); mAllowGesture = isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); if (mAllowGesture) { mDownPoint.set(ev.getX(), ev.getY()); } } else if (mAllowGesture) { - final Rect currentPipBounds = mMotionHelper.getBounds(); - Rect newSize = TaskResizingAlgorithm.resizeDrag(ev.getX(), ev.getY(), mDownPoint.x, - mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x, mMinSize.y, mMaxSize, - true, true); - mPipBoundsHandler.transformBoundsToAspectRatio(newSize); switch (action) { case MotionEvent.ACTION_POINTER_DOWN: // We do not support multi touch for resizing via drag @@ -206,11 +203,16 @@ public class PipResizeGestureHandler { case MotionEvent.ACTION_MOVE: // Capture inputs mInputMonitor.pilferPointers(); - //TODO: Actually do resize here. + final Rect currentPipBounds = mMotionHelper.getBounds(); + mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(ev.getX(), ev.getY(), + mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x, + mMinSize.y, mMaxSize, true, true)); + mPipBoundsHandler.transformBoundsToAspectRatio(mLastResizeBounds); + mPipTaskOrganizer.scheduleResizePip(mLastResizeBounds, null); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: - //TODO: Finish resize operation here. + mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds); mMotionHelper.synchronizePinnedStackBounds(); mCtrlType = CTRL_NONE; mAllowGesture = false; @@ -223,7 +225,7 @@ public class PipResizeGestureHandler { mMaxSize.set(maxX, maxY); } - void updateMiniSize(int minX, int minY) { + void updateMinSize(int minX, int minY) { mMinSize.set(minX, minY); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index b5fb1a9dba9a9..9b67d8020c320 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -42,6 +42,7 @@ import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.logging.MetricsLoggerWrapper; import com.android.systemui.R; import com.android.systemui.pip.PipBoundsHandler; @@ -74,7 +75,7 @@ public class PipTouchHandler { private final Context mContext; private final IActivityManager mActivityManager; private final PipBoundsHandler mPipBoundsHandler; - private final PipResizeGestureHandler mPipResizeGestureHandler; + private PipResizeGestureHandler mPipResizeGestureHandler; private IPinnedStackController mPinnedStackController; private final PipMenuActivityController mMenuController; @@ -85,14 +86,17 @@ public class PipTouchHandler { // The current movement bounds private Rect mMovementBounds = new Rect(); + // The current resized bounds, changed by user resize. + // This is used during expand/un-expand to save/restore the user's resized size. + @VisibleForTesting Rect mResizedBounds = new Rect(); // The reference inset bounds, used to determine the dismiss fraction private Rect mInsetBounds = new Rect(); // The reference bounds used to calculate the normal/expanded target bounds private Rect mNormalBounds = new Rect(); - private Rect mNormalMovementBounds = new Rect(); + @VisibleForTesting Rect mNormalMovementBounds = new Rect(); private Rect mExpandedBounds = new Rect(); - private Rect mExpandedMovementBounds = new Rect(); + @VisibleForTesting Rect mExpandedMovementBounds = new Rect(); private int mExpandedShortestEdgeSize; // Used to workaround an issue where the WM rotation happens before we are notified, allowing @@ -127,7 +131,7 @@ public class PipTouchHandler { private final PipTouchState mTouchState; private final FlingAnimationUtils mFlingAnimationUtils; private final FloatingContentCoordinator mFloatingContentCoordinator; - private final PipMotionHelper mMotionHelper; + private PipMotionHelper mMotionHelper; private PipTouchGesture mGesture; // Temp vars @@ -240,14 +244,15 @@ public class PipTouchHandler { mFloatingContentCoordinator.onContentRemoved(mMotionHelper); } + mResizedBounds.setEmpty(); mPipResizeGestureHandler.onActivityUnpinned(); } public void onPinnedStackAnimationEnded() { // Always synchronize the motion helper bounds once PiP animations finish mMotionHelper.synchronizePinnedStackBounds(); - mPipResizeGestureHandler.updateMiniSize(mMotionHelper.getBounds().width(), - mMotionHelper.getBounds().height()); + updateMovementBounds(); + mResizedBounds.set(mMotionHelper.getBounds()); if (mShowPipMenuOnAnimationEnd) { mMenuController.showMenu(MENU_STATE_CLOSE, mMotionHelper.getBounds(), @@ -292,11 +297,13 @@ public class PipTouchHandler { Size expandedSize = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, mExpandedShortestEdgeSize, displaySize.x, displaySize.y); mExpandedBounds.set(0, 0, expandedSize.getWidth(), expandedSize.getHeight()); - mPipResizeGestureHandler.updateMaxSize(expandedSize.getWidth(), expandedSize.getHeight()); Rect expandedMovementBounds = new Rect(); mSnapAlgorithm.getMovementBounds(mExpandedBounds, insetBounds, expandedMovementBounds, bottomOffset); + mPipResizeGestureHandler.updateMinSize(mNormalBounds.width(), mNormalBounds.height()); + mPipResizeGestureHandler.updateMaxSize(mExpandedBounds.width(), mExpandedBounds.height()); + // The extra offset does not really affect the movement bounds, but are applied based on the // current state (ime showing, or shelf offset) when we need to actually shift int extraOffset = Math.max( @@ -332,7 +339,7 @@ public class PipTouchHandler { mExpandedMovementBounds = expandedMovementBounds; mDisplayRotation = displayRotation; mInsetBounds.set(insetBounds); - updateMovementBounds(mMenuState); + updateMovementBounds(); mMovementBoundsExtraOffsets = extraOffset; // If we have a deferred resize, apply it now @@ -392,7 +399,7 @@ public class PipTouchHandler { case MotionEvent.ACTION_UP: { // Update the movement bounds again if the state has changed since the user started // dragging (ie. when the IME shows) - updateMovementBounds(mMenuState); + updateMovementBounds(); if (mGesture.onUp(mTouchState)) { break; @@ -490,9 +497,11 @@ public class PipTouchHandler { if (menuState == MENU_STATE_FULL && mMenuState != MENU_STATE_FULL) { // Save the current snap fraction and if we do not drag or move the PiP, then // we store back to this snap fraction. Otherwise, we'll reset the snap - // fraction and snap to the closest edge - Rect expandedBounds = new Rect(mExpandedBounds); + // fraction and snap to the closest edge. + // Also save the current resized bounds so when the menu disappears, we can restore it. if (resize) { + mResizedBounds.set(mMotionHelper.getBounds()); + Rect expandedBounds = new Rect(mExpandedBounds); mSavedSnapFraction = mMotionHelper.animateToExpandedState(expandedBounds, mMovementBounds, mExpandedMovementBounds); } @@ -520,9 +529,12 @@ public class PipTouchHandler { } if (mDeferResizeToNormalBoundsUntilRotation == -1) { - Rect normalBounds = new Rect(mNormalBounds); - mMotionHelper.animateToUnexpandedState(normalBounds, mSavedSnapFraction, - mNormalMovementBounds, mMovementBounds, false /* immediate */); + Rect restoreBounds = new Rect(mResizedBounds); + Rect restoredMovementBounds = new Rect(); + mSnapAlgorithm.getMovementBounds(restoreBounds, mInsetBounds, + restoredMovementBounds, mIsImeShowing ? mImeHeight : 0); + mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction, + restoredMovementBounds, mMovementBounds, false /* immediate */); mSavedSnapFraction = -1f; } } else { @@ -533,7 +545,7 @@ public class PipTouchHandler { } } mMenuState = menuState; - updateMovementBounds(menuState); + updateMovementBounds(); // If pip menu has dismissed, we should register the A11y ActionReplacingConnection for pip // as well, or it can't handle a11y focus and pip menu can't perform any action. onRegistrationChanged(menuState == MENU_STATE_NONE); @@ -549,6 +561,21 @@ public class PipTouchHandler { return mMotionHelper; } + @VisibleForTesting + PipResizeGestureHandler getPipResizeGestureHandler() { + return mPipResizeGestureHandler; + } + + @VisibleForTesting + void setPipResizeGestureHandler(PipResizeGestureHandler pipResizeGestureHandler) { + mPipResizeGestureHandler = pipResizeGestureHandler; + } + + @VisibleForTesting + void setPipMotionHelper(PipMotionHelper pipMotionHelper) { + mMotionHelper = pipMotionHelper; + } + /** * @return the unexpanded bounds. */ @@ -709,14 +736,14 @@ public class PipTouchHandler { * Updates the current movement bounds based on whether the menu is currently visible and * resized. */ - private void updateMovementBounds(int menuState) { - boolean isMenuExpanded = menuState == MENU_STATE_FULL; - mMovementBounds = isMenuExpanded && willResizeMenu() - ? mExpandedMovementBounds - : mNormalMovementBounds; - mPipBoundsHandler.setMinEdgeSize( - isMenuExpanded ? mExpandedShortestEdgeSize : 0); + private void updateMovementBounds() { + mSnapAlgorithm.getMovementBounds(mMotionHelper.getBounds(), mInsetBounds, + mMovementBounds, mIsImeShowing ? mImeHeight : 0); mMotionHelper.setCurrentMovementBounds(mMovementBounds); + + boolean isMenuExpanded = mMenuState == MENU_STATE_FULL; + mPipBoundsHandler.setMinEdgeSize( + isMenuExpanded && willResizeMenu() ? mExpandedShortestEdgeSize : 0); } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java new file mode 100644 index 0000000000000..4d7e6aec8aa9d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.pip.phone; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.IActivityManager; +import android.app.IActivityTaskManager; +import android.graphics.Point; +import android.graphics.Rect; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.Size; +import android.view.DisplayInfo; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.pip.PipBoundsHandler; +import com.android.systemui.pip.PipSnapAlgorithm; +import com.android.systemui.pip.PipTaskOrganizer; +import com.android.systemui.shared.system.InputConsumerController; +import com.android.systemui.util.DeviceConfigProxy; +import com.android.systemui.util.FloatingContentCoordinator; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests against {@link PipTouchHandler}, including but not limited to: + * - Update movement bounds based on new bounds + * - Update movement bounds based on IME/shelf + * - Update movement bounds to PipResizeHandler + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class PipTouchHandlerTest extends SysuiTestCase { + private static final int ROUNDING_ERROR_MARGIN = 10; + private static final float DEFAULT_ASPECT_RATIO = 1f; + private static final Rect EMPTY_CURRENT_BOUNDS = null; + + private PipTouchHandler mPipTouchHandler; + private DisplayInfo mDefaultDisplayInfo; + + @Mock + private IActivityManager mActivityManager; + + @Mock + private IActivityTaskManager mIActivityTaskManager; + + @Mock + private PipMenuActivityController mPipMenuActivityController; + + @Mock + private InputConsumerController mInputConsumerController; + + @Mock + private PipBoundsHandler mPipBoundsHandler; + + @Mock + private PipTaskOrganizer mPipTaskOrganizer; + + @Mock + private FloatingContentCoordinator mFloatingContentCoordinator; + + @Mock + private DeviceConfigProxy mDeviceConfigProxy; + + + private PipSnapAlgorithm mPipSnapAlgorithm; + private PipMotionHelper mMotionHelper; + private PipResizeGestureHandler mPipResizeGestureHandler; + + Rect mInsetBounds; + Rect mMinBounds; + Rect mCurBounds; + boolean mFromImeAdjustment; + boolean mFromShelfAdjustment; + int mDisplayRotation; + + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mPipSnapAlgorithm = new PipSnapAlgorithm(mContext); + mPipTouchHandler = new PipTouchHandler(mContext, mActivityManager, mIActivityTaskManager, + mPipMenuActivityController, mInputConsumerController, mPipBoundsHandler, + mPipTaskOrganizer, mFloatingContentCoordinator, mDeviceConfigProxy, + mPipSnapAlgorithm); + mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper()); + mPipResizeGestureHandler = Mockito.spy(mPipTouchHandler.getPipResizeGestureHandler()); + mPipTouchHandler.setPipMotionHelper(mMotionHelper); + mPipTouchHandler.setPipResizeGestureHandler(mPipResizeGestureHandler); + + // Assume a display of 1000 x 1000 + // inset of 10 + mInsetBounds = new Rect(10, 10, 990, 990); + // minBounds of 100x100 bottom right corner + mMinBounds = new Rect(890, 890, 990, 990); + mCurBounds = new Rect(); + mFromImeAdjustment = false; + mFromShelfAdjustment = false; + mDisplayRotation = 0; + } + + @Test + public void updateMovementBounds_minBounds() { + Rect expectedMinMovementBounds = new Rect(); + mPipSnapAlgorithm.getMovementBounds(mMinBounds, mInsetBounds, expectedMinMovementBounds, 0); + + mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds, + mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation); + + assertEquals(expectedMinMovementBounds, mPipTouchHandler.mNormalMovementBounds); + verify(mPipResizeGestureHandler, times(1)) + .updateMinSize(mMinBounds.width(), mMinBounds.height()); + } + + @Test + public void updateMovementBounds_maxBounds() { + Point displaySize = new Point(); + mContext.getDisplay().getRealSize(displaySize); + Size maxSize = mPipSnapAlgorithm.getSizeForAspectRatio(1, + mContext.getResources().getDimensionPixelSize( + R.dimen.pip_expanded_shortest_edge_size), displaySize.x, displaySize.y); + Rect maxBounds = new Rect(0, 0, maxSize.getWidth(), maxSize.getHeight()); + Rect expectedMaxMovementBounds = new Rect(); + mPipSnapAlgorithm.getMovementBounds(maxBounds, mInsetBounds, expectedMaxMovementBounds, 0); + + mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds, + mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation); + + assertEquals(expectedMaxMovementBounds, mPipTouchHandler.mExpandedMovementBounds); + verify(mPipResizeGestureHandler, times(1)) + .updateMaxSize(maxBounds.width(), maxBounds.height()); + } + + @Test + public void updateMovementBounds_withImeAdjustment_movesPip() { + mFromImeAdjustment = true; + mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds, + mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation); + + verify(mMotionHelper, times(1)).animateToOffset(any(), anyInt()); + } +}