diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java index 1ae3d4f482f40..60c1a940d2c60 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java @@ -225,8 +225,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/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index 8ada3c393222b..6b3b7481b9c9f 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -168,7 +168,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio // bounds. We want to restore to the unexpanded bounds when re-entering pip, // so we save the bounds before expansion (normal) instead of the current // bounds. - mReentryBounds.set(mTouchHandler.getNormalBounds()); + mReentryBounds.set(mTouchHandler.getMinBounds()); // Apply the snap fraction of the current bounds to the normal bounds. float snapFraction = mPipBoundsHandler.getSnapFraction(bounds); mPipBoundsHandler.applySnapFraction(mReentryBounds, snapFraction); 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 389793e6fc94c..e3d3df6358b18 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java @@ -64,6 +64,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; @@ -190,11 +191,7 @@ public class PipResizeGestureHandler { } } 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 @@ -203,12 +200,17 @@ public class PipResizeGestureHandler { case MotionEvent.ACTION_MOVE: // Capture inputs mInputMonitor.pilferPointers(); + 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); //TODO: Actually do resize here. break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: + mLastResizeBounds.setEmpty(); //TODO: Finish resize operation here. - mMotionHelper.synchronizePinnedStackBounds(); mCtrlType = CTRL_NONE; mAllowGesture = false; break; @@ -220,7 +222,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 3f73d0194cd6b..558b3c696df8a 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -41,6 +41,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; @@ -73,7 +74,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; @@ -84,14 +85,22 @@ public class PipTouchHandler { // The current movement bounds private Rect mMovementBounds = new Rect(); + // The current resized bounds, changed by user resize. + // Note that this is not necessarily the same as PipMotionHelper#getBounds, since it's possible + // that PIP is currently is in a expanded state (max size) but we still need mResizeBounds to + // know what size to restore to once expand animation times out. + @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(); - private Rect mExpandedBounds = new Rect(); - private Rect mExpandedMovementBounds = new Rect(); + + // The reference bounds used to calculate the minimum/maximum target bounds + // The bound in which PIP enters is the starting/minimum bound, while the expanded/auto-resized + // bound is the maximum bound. + private Rect mMinBounds = new Rect(); + @VisibleForTesting Rect mMinMovementBounds = new Rect(); + private Rect mMaxBounds = new Rect(); + @VisibleForTesting Rect mMaxMovementBounds = new Rect(); private int mExpandedShortestEdgeSize; // Used to workaround an issue where the WM rotation happens before we are notified, allowing @@ -126,7 +135,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 @@ -235,14 +244,16 @@ 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(mMinBounds); if (mShowPipMenuOnAnimationEnd) { mMenuController.showMenu(MENU_STATE_CLOSE, mMotionHelper.getBounds(), @@ -266,7 +277,10 @@ public class PipTouchHandler { mShelfHeight = shelfHeight; } - public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect curBounds, + /** + * Update all the cached bounds (movement, min, max, etc.) + */ + public void onMovementBoundsChanged(Rect insetBounds, Rect minBounds, Rect curBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation) { final int bottomOffset = mIsImeShowing ? mImeHeight : 0; final boolean fromDisplayRotationChanged = (mDisplayRotation != displayRotation); @@ -275,23 +289,25 @@ public class PipTouchHandler { } // Re-calculate the expanded bounds - mNormalBounds = normalBounds; - Rect normalMovementBounds = new Rect(); - mSnapAlgorithm.getMovementBounds(mNormalBounds, insetBounds, normalMovementBounds, + mMinBounds.set(minBounds); + Rect minMovementBounds = new Rect(); + mSnapAlgorithm.getMovementBounds(mMinBounds, insetBounds, minMovementBounds, bottomOffset); // Calculate the expanded size - float aspectRatio = (float) normalBounds.width() / normalBounds.height(); + float aspectRatio = (float) minBounds.width() / minBounds.height(); Point displaySize = new Point(); mContext.getDisplay().getRealSize(displaySize); - Size expandedSize = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, + Size maxSize = 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, + mMaxBounds.set(0, 0, maxSize.getWidth(), maxSize.getHeight()); + Rect maxMovementBounds = new Rect(); + mSnapAlgorithm.getMovementBounds(mMaxBounds, insetBounds, maxMovementBounds, bottomOffset); + mPipResizeGestureHandler.updateMinSize(minBounds.width(), minBounds.height()); + mPipResizeGestureHandler.updateMaxSize(mMaxBounds.width(), mMaxBounds.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( @@ -308,8 +324,8 @@ public class PipTouchHandler { final float offsetBufferPx = BOTTOM_OFFSET_BUFFER_DP * mContext.getResources().getDisplayMetrics().density; final Rect toMovementBounds = mMenuState == MENU_STATE_FULL && willResizeMenu() - ? new Rect(expandedMovementBounds) - : new Rect(normalMovementBounds); + ? new Rect(maxMovementBounds) + : new Rect(minMovementBounds); final int prevBottom = mMovementBounds.bottom - mMovementBoundsExtraOffsets; final int toBottom = toMovementBounds.bottom < toMovementBounds.top ? toMovementBounds.bottom @@ -323,17 +339,17 @@ public class PipTouchHandler { // Update the movement bounds after doing the calculations based on the old movement bounds // above - mNormalMovementBounds = normalMovementBounds; - mExpandedMovementBounds = expandedMovementBounds; + mMinMovementBounds = minMovementBounds; + mMaxMovementBounds = maxMovementBounds; mDisplayRotation = displayRotation; mInsetBounds.set(insetBounds); - updateMovementBounds(mMenuState); + updateMovementBounds(); mMovementBoundsExtraOffsets = extraOffset; // If we have a deferred resize, apply it now if (mDeferResizeToNormalBoundsUntilRotation == displayRotation) { - mMotionHelper.animateToUnexpandedState(normalBounds, mSavedSnapFraction, - mNormalMovementBounds, mMovementBounds, true /* immediate */); + mMotionHelper.animateToUnexpandedState(minBounds, mSavedSnapFraction, + mMinMovementBounds, mMovementBounds, true /* immediate */); mSavedSnapFraction = -1f; mDeferResizeToNormalBoundsUntilRotation = -1; } @@ -387,7 +403,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; @@ -485,11 +501,13 @@ 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(mMaxBounds); mSavedSnapFraction = mMotionHelper.animateToExpandedState(expandedBounds, - mMovementBounds, mExpandedMovementBounds); + mMovementBounds, mMaxMovementBounds); } } else if (menuState == MENU_STATE_NONE && mMenuState == MENU_STATE_FULL) { // Try and restore the PiP to the closest edge, using the saved snap fraction @@ -515,9 +533,9 @@ public class PipTouchHandler { } if (mDeferResizeToNormalBoundsUntilRotation == -1) { - Rect normalBounds = new Rect(mNormalBounds); + Rect normalBounds = new Rect(mResizedBounds); mMotionHelper.animateToUnexpandedState(normalBounds, mSavedSnapFraction, - mNormalMovementBounds, mMovementBounds, false /* immediate */); + mMinMovementBounds, mMovementBounds, false /* immediate */); mSavedSnapFraction = -1f; } } else { @@ -528,7 +546,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); @@ -544,11 +562,26 @@ 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. */ - public Rect getNormalBounds() { - return mNormalBounds; + public Rect getMinBounds() { + return mMinBounds; } /** @@ -701,17 +734,17 @@ public class PipTouchHandler { }; /** - * Updates the current movement bounds based on whether the menu is currently visible and - * resized. + * Updates the current movement bounds based on the current PIP size. */ - private void updateMovementBounds(int menuState) { - boolean isMenuExpanded = menuState == MENU_STATE_FULL; - mMovementBounds = isMenuExpanded && willResizeMenu() - ? mExpandedMovementBounds - : mNormalMovementBounds; + private void updateMovementBounds() { + Rect movementBounds = new Rect(); + mSnapAlgorithm.getMovementBounds(mMotionHelper.getBounds(), mInsetBounds, + movementBounds, mIsImeShowing ? mImeHeight : 0); + mMotionHelper.setCurrentMovementBounds(movementBounds); + + boolean isMenuExpanded = mMenuState == MENU_STATE_FULL; mPipBoundsHandler.setMinEdgeSize( - isMenuExpanded ? mExpandedShortestEdgeSize : 0); - mMotionHelper.setCurrentMovementBounds(mMovementBounds); + isMenuExpanded && willResizeMenu() ? mExpandedShortestEdgeSize : 0); } /** @@ -729,18 +762,18 @@ public class PipTouchHandler { if (!mEnableResize) { return false; } - return mExpandedBounds.width() != mNormalBounds.width() - || mExpandedBounds.height() != mNormalBounds.height(); + return mMaxBounds.width() != mMinBounds.width() + || mMaxBounds.height() != mMinBounds.height(); } public void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); pw.println(innerPrefix + "mMovementBounds=" + mMovementBounds); - pw.println(innerPrefix + "mNormalBounds=" + mNormalBounds); - pw.println(innerPrefix + "mNormalMovementBounds=" + mNormalMovementBounds); - pw.println(innerPrefix + "mExpandedBounds=" + mExpandedBounds); - pw.println(innerPrefix + "mExpandedMovementBounds=" + mExpandedMovementBounds); + pw.println(innerPrefix + "mMinBounds=" + mMinBounds); + pw.println(innerPrefix + "mMinMovementBounds=" + mMinMovementBounds); + pw.println(innerPrefix + "mMaxBounds=" + mMaxBounds); + pw.println(innerPrefix + "mMaxMovementBounds=" + mMaxMovementBounds); pw.println(innerPrefix + "mMenuState=" + mMenuState); pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing); pw.println(innerPrefix + "mImeHeight=" + mImeHeight); 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..35abb14070bf4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java @@ -0,0 +1,168 @@ +/* + * 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); + mPipTouchHandler = new PipTouchHandler(mContext, mActivityManager, mIActivityTaskManager, + mPipMenuActivityController, mInputConsumerController, mPipBoundsHandler, + mPipTaskOrganizer, mFloatingContentCoordinator, mDeviceConfigProxy); + mPipSnapAlgorithm = new PipSnapAlgorithm(mContext); + 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.mMinMovementBounds); + 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.mMaxMovementBounds); + 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()); + } +}