From 39c9570d459da54aac59a615b9cadd0397383144 Mon Sep 17 00:00:00 2001 From: Ben Lin Date: Wed, 19 Feb 2020 16:12:48 -0800 Subject: [PATCH 1/2] 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. Also to make it less confusing, I've renamed: - normalBounds -> minBounds - expandedBounds -> maxBounds 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: I2ef286d6fa03914a88ac46e2854548548093e79e --- .../systemui/pip/PipBoundsHandler.java | 2 +- .../systemui/pip/phone/PipManager.java | 2 +- .../pip/phone/PipResizeGestureHandler.java | 16 +- .../systemui/pip/phone/PipTouchHandler.java | 133 ++++++++------ .../pip/phone/PipTouchHandlerTest.java | 168 ++++++++++++++++++ 5 files changed, 262 insertions(+), 59 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 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()); + } +} From 7d6b8e746b4aaecd69847fadcc66c2bc61a13343 Mon Sep 17 00:00:00 2001 From: Ben Lin Date: Thu, 27 Feb 2020 17:48:16 -0800 Subject: [PATCH 2/2] Implement PIP Leash resizing. This CL does a couple of things: - Break apart resizePinnedStack into animateResizeStack and resizeStackIntermediate, in which the latter is expected to only do maniuplation on the leash itself but don't send any WindowContainerTransaction until #finishResize is called. - Break apart the coupling where the duration signifies whether we only do manipulation on the leash, or send over a WCT. Now we can choose to either resize leash only, or animate correctly an entire resize with animation = 0. - Have PipResizeGestureHandler call on #finishResize and resizePipStackIntermediate during MOVE and UP events, respectively Bug: 147361175 Test: Manually. Resize PIP. Change-Id: Id3953904a7d626880855eab043abfb8d8a8d9b11 --- .../systemui/pip/PipTaskOrganizer.java | 141 ++++++++++++------ .../systemui/pip/phone/PipMotionHelper.java | 5 +- .../pip/phone/PipResizeGestureHandler.java | 10 +- .../systemui/pip/phone/PipTouchHandler.java | 2 +- .../android/systemui/pip/tv/PipManager.java | 4 +- 5 files changed, 105 insertions(+), 57 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index 665146e6ba0d0..1566877c7204b 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -18,7 +18,7 @@ package com.android.systemui.pip; import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_ALPHA; import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_BOUNDS; -import static com.android.systemui.pip.PipAnimationController.DURATION_NONE; +import static com.android.systemui.pip.PipAnimationController.DURATION_DEFAULT_MS; import android.annotation.NonNull; import android.annotation.Nullable; @@ -87,19 +87,7 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { } }); final Rect destinationBounds = animator.getDestinationBounds(); - mLastReportedBounds.set(destinationBounds); - try { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - if (animator.shouldScheduleFinishPip()) { - wct.scheduleFinishEnterPip(wc, destinationBounds); - } else { - wct.setBounds(wc, destinationBounds); - } - wct.setBoundsChangeTransaction(wc, tx); - mTaskOrganizerController.applyContainerTransaction(wct, null /* ITaskOrganizer */); - } catch (RemoteException e) { - Log.e(TAG, "Failed to apply container transaction", e); - } + finishResizeInternal(destinationBounds, wc, tx, animator.shouldScheduleFinishPip()); } @Override @@ -124,15 +112,6 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { mPipAnimationController = new PipAnimationController(context); } - /** - * Resize the PiP window, animate if the given duration is not {@link #DURATION_NONE} - */ - public void resizePinnedStack(Rect destinationBounds, int durationMs) { - Objects.requireNonNull(mTaskInfo, "Requires valid IWindowContainer"); - resizePinnedStackInternal(mTaskInfo.token, false /* scheduleFinishPip */, - mLastReportedBounds, destinationBounds, durationMs); - } - /** * Offset the PiP window, animate if the given duration is not {@link #DURATION_NONE} */ @@ -143,7 +122,7 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { } final Rect destinationBounds = new Rect(originalBounds); destinationBounds.offset(xOffset, yOffset); - resizePinnedStackInternal(mTaskInfo.token, false /* scheduleFinishPip*/, + animateResizePipInternal(mTaskInfo.token, false /* scheduleFinishPip*/, originalBounds, destinationBounds, durationMs); } @@ -208,15 +187,14 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { mTaskInfo = info; if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); - resizePinnedStackInternal(mTaskInfo.token, true /* scheduleFinishPip */, - currentBounds, destinationBounds, - PipAnimationController.DURATION_DEFAULT_MS); + animateResizePipInternal(mTaskInfo.token, true /* scheduleFinishPip */, + currentBounds, destinationBounds, DURATION_DEFAULT_MS); } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { mMainHandler.post(() -> mPipAnimationController .getAnimator(mTaskInfo.token, true /* scheduleFinishPip */, destinationBounds, 0f, 1f) .setPipAnimationCallback(mPipAnimationCallback) - .setDuration(PipAnimationController.DURATION_DEFAULT_MS) + .setDuration(DURATION_DEFAULT_MS) .start()); mOneShotAnimationType = ANIM_TYPE_BOUNDS; } else { @@ -231,9 +209,8 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { Log.wtf(TAG, "Unrecognized token: " + token); return; } - resizePinnedStackInternal(token, false /* scheduleFinishPip */, - mLastReportedBounds, mDisplayBounds, - PipAnimationController.DURATION_DEFAULT_MS); + animateResizePipInternal(token, false /* scheduleFinishPip */, + mLastReportedBounds, mDisplayBounds, DURATION_DEFAULT_MS); } @Override @@ -245,11 +222,22 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( getAspectRatioOrDefault(info.pictureInPictureParams), null /* bounds */); Objects.requireNonNull(destinationBounds, "Missing destination bounds"); - resizePinnedStack(destinationBounds, PipAnimationController.DURATION_DEFAULT_MS); + animateResizePip(destinationBounds, DURATION_DEFAULT_MS); } - private void resizePinnedStackInternal(IWindowContainer wc, boolean scheduleFinishPip, - Rect currentBounds, Rect destinationBounds, int animationDurationMs) { + + /** + * Directly perform manipulation/resize on the leash. This will not perform any + * {@link WindowContainerTransaction} until {@link #finishResize} is called. + */ + public void resizePip(Rect destinationBounds) { + Objects.requireNonNull(mTaskInfo, "Requires valid IWindowContainer"); + resizePipInternal(mTaskInfo.token, destinationBounds); + } + + private void resizePipInternal(IWindowContainer wc, + Rect destinationBounds) { + Objects.requireNonNull(mTaskInfo, "Requires valid IWindowContainer"); try { // Could happen when dismissPip if (wc == null || wc.getLeash() == null) { @@ -257,20 +245,10 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { return; } final SurfaceControl leash = wc.getLeash(); - if (animationDurationMs == DURATION_NONE) { - // Directly resize if no animation duration is set. When fling, wait for final - // callback to issue the proper WindowContainerTransaction with destination bounds. - new SurfaceControl.Transaction() - .setPosition(leash, destinationBounds.left, destinationBounds.top) - .setWindowCrop(leash, destinationBounds.width(), destinationBounds.height()) - .apply(); - } else { - mMainHandler.post(() -> mPipAnimationController - .getAnimator(wc, scheduleFinishPip, currentBounds, destinationBounds) - .setPipAnimationCallback(mPipAnimationCallback) - .setDuration(animationDurationMs) - .start()); - } + new SurfaceControl.Transaction() + .setPosition(leash, destinationBounds.left, destinationBounds.top) + .setWindowCrop(leash, destinationBounds.width(), destinationBounds.height()) + .apply(); } catch (RemoteException e) { Log.w(TAG, "Abort animation, invalid window container", e); } catch (Exception e) { @@ -278,6 +256,73 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { } } + /** + * Finish a intermediate resize operation. This is expected to be called after + * {@link #resizePip}. + */ + public void finishResize(Rect destinationBounds) { + try { + final IWindowContainer wc = mTaskInfo.token; + SurfaceControl.Transaction tx = new SurfaceControl.Transaction() + .setPosition(wc.getLeash(), destinationBounds.left, + destinationBounds.top) + .setWindowCrop(wc.getLeash(), destinationBounds.width(), + destinationBounds.height()); + finishResizeInternal(destinationBounds, wc, tx, false); + } catch (RemoteException e) { + Log.e(TAG, "Failed to obtain leash"); + } + } + + private void finishResizeInternal(Rect destinationBounds, IWindowContainer wc, + SurfaceControl.Transaction tx, boolean shouldScheduleFinishPip) { + mLastReportedBounds.set(destinationBounds); + try { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + if (shouldScheduleFinishPip) { + wct.scheduleFinishEnterPip(wc, destinationBounds); + } else { + wct.setBounds(wc, destinationBounds); + } + wct.setBoundsChangeTransaction(mTaskInfo.token, tx); + mTaskOrganizerController.applyContainerTransaction(wct, null /* ITaskOrganizer */); + } catch (RemoteException e) { + Log.e(TAG, "Failed to apply container transaction", e); + } + } + + /** + * Animates resizing of the pinned stack given the duration. + */ + public void animateResizePip(Rect destinationBounds, int durationMs) { + Objects.requireNonNull(mTaskInfo, "Requires valid IWindowContainer"); + animateResizePipInternal(mTaskInfo.token, false, mLastReportedBounds, + destinationBounds, durationMs); + } + + private void animateResizePipInternal(IWindowContainer wc, boolean scheduleFinishPip, + Rect currentBounds, Rect destinationBounds, int durationMs) { + try { + // Could happen when dismissPip + if (wc == null || wc.getLeash() == null) { + Log.w(TAG, "Abort animation, invalid leash"); + return; + } + final SurfaceControl leash = wc.getLeash(); + + mMainHandler.post(() -> mPipAnimationController + .getAnimator(wc, scheduleFinishPip, currentBounds, destinationBounds) + .setPipAnimationCallback(mPipAnimationCallback) + .setDuration(durationMs) + .start()); + } catch (RemoteException e) { + Log.w(TAG, "Abort animation, invalid window container", e); + } catch (Exception e) { + Log.e(TAG, "Should not reach here, terrible thing happened", e); + } + } + + private float getAspectRatioOrDefault(@Nullable PictureInPictureParams params) { return params == null ? mPipBoundsHandler.getDefaultAspectRatio() diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index 980d18b6a7e0f..dece8503cc3ad 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -38,7 +38,6 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.internal.os.SomeArgs; -import com.android.systemui.pip.PipAnimationController; import com.android.systemui.pip.PipSnapAlgorithm; import com.android.systemui.pip.PipTaskOrganizer; import com.android.systemui.shared.system.WindowManagerWrapper; @@ -546,7 +545,7 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call case MSG_RESIZE_IMMEDIATE: { SomeArgs args = (SomeArgs) msg.obj; Rect toBounds = (Rect) args.arg1; - mPipTaskOrganizer.resizePinnedStack(toBounds, PipAnimationController.DURATION_NONE); + mPipTaskOrganizer.resizePip(toBounds); mBounds.set(toBounds); return true; } @@ -564,7 +563,7 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call return true; } - mPipTaskOrganizer.resizePinnedStack(toBounds, duration); + mPipTaskOrganizer.animateResizePip(toBounds, duration); mBounds.set(toBounds); } catch (RemoteException e) { Log.e(TAG, "Could not animate resize pinned stack to bounds: " + toBounds, e); 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 e3d3df6358b18..5926b89221733 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java @@ -41,6 +41,7 @@ import android.view.MotionEvent; import com.android.internal.policy.TaskResizingAlgorithm; import com.android.systemui.R; import com.android.systemui.pip.PipBoundsHandler; +import com.android.systemui.pip.PipTaskOrganizer; import com.android.systemui.util.DeviceConfigProxy; import java.util.concurrent.Executor; @@ -75,12 +76,13 @@ public class PipResizeGestureHandler { private InputMonitor mInputMonitor; private InputEventReceiver mInputEventReceiver; + private PipTaskOrganizer mPipTaskOrganizer; private int mCtrlType; public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler, PipTouchHandler pipTouchHandler, PipMotionHelper motionHelper, - DeviceConfigProxy deviceConfig) { + DeviceConfigProxy deviceConfig, PipTaskOrganizer pipTaskOrganizer) { final Resources res = context.getResources(); context.getDisplay().getMetrics(mDisplayMetrics); mDisplayId = context.getDisplayId(); @@ -88,6 +90,7 @@ public class PipResizeGestureHandler { mPipBoundsHandler = pipBoundsHandler; mPipTouchHandler = pipTouchHandler; mMotionHelper = motionHelper; + mPipTaskOrganizer = pipTaskOrganizer; context.getDisplay().getRealSize(mMaxSize); mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size); @@ -205,12 +208,13 @@ public class PipResizeGestureHandler { mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x, mMinSize.y, mMaxSize, true, true)); mPipBoundsHandler.transformBoundsToAspectRatio(mLastResizeBounds); - //TODO: Actually do resize here. + mPipTaskOrganizer.resizePip(mLastResizeBounds); + break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: + mPipTaskOrganizer.finishResize(mLastResizeBounds); mLastResizeBounds.setEmpty(); - //TODO: Finish resize operation here. mCtrlType = CTRL_NONE; mAllowGesture = false; break; 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 558b3c696df8a..bd224800e858e 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -191,7 +191,7 @@ public class PipTouchHandler { mMenuController, mSnapAlgorithm, mFlingAnimationUtils, floatingContentCoordinator); mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsHandler, this, mMotionHelper, - deviceConfig); + deviceConfig, pipTaskOrganizer); mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler, () -> mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(), mMovementBounds, true /* allowMenuTimeout */, willResizeMenu())); diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java index cb1a218af954f..f28c3f6e71ec2 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java @@ -433,8 +433,8 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio mCurrentPipBounds = mPipBounds; break; } - mPipTaskOrganizer.resizePinnedStack( - mCurrentPipBounds, PipAnimationController.DURATION_DEFAULT_MS); + mPipTaskOrganizer.animateResizePip(mCurrentPipBounds, + PipAnimationController.DURATION_DEFAULT_MS); } /**