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/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/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/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 389793e6fc94c..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; @@ -64,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; @@ -74,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(); @@ -87,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); @@ -190,11 +194,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 +203,18 @@ 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.resizePip(mLastResizeBounds); + break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: - //TODO: Finish resize operation here. - mMotionHelper.synchronizePinnedStackBounds(); + mPipTaskOrganizer.finishResize(mLastResizeBounds); + mLastResizeBounds.setEmpty(); mCtrlType = CTRL_NONE; mAllowGesture = false; break; @@ -220,7 +226,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..bd224800e858e 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 @@ -182,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())); @@ -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/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); } /** 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()); + } +}