Merge changes Id3953904,I2ef286d6 into rvc-dev

* changes:
  Implement PIP Leash resizing.
  Track current resized bounds for resized PIP.
This commit is contained in:
Ben Lin
2020-03-04 21:45:24 +00:00
committed by Android (Google) Code Review
8 changed files with 367 additions and 116 deletions

View File

@@ -225,8 +225,8 @@ public class PipBoundsHandler {
*/ */
Rect getDestinationBounds(float aspectRatio, Rect bounds) { Rect getDestinationBounds(float aspectRatio, Rect bounds) {
final Rect destinationBounds; final Rect destinationBounds;
final Rect defaultBounds = getDefaultBounds(mReentrySnapFraction, mReentrySize);
if (bounds == null) { if (bounds == null) {
final Rect defaultBounds = getDefaultBounds(mReentrySnapFraction, mReentrySize);
destinationBounds = new Rect(defaultBounds); destinationBounds = new Rect(defaultBounds);
} else { } else {
destinationBounds = new Rect(bounds); destinationBounds = new Rect(bounds);

View File

@@ -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_ALPHA;
import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_BOUNDS; 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.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
@@ -87,19 +87,7 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub {
} }
}); });
final Rect destinationBounds = animator.getDestinationBounds(); final Rect destinationBounds = animator.getDestinationBounds();
mLastReportedBounds.set(destinationBounds); finishResizeInternal(destinationBounds, wc, tx, animator.shouldScheduleFinishPip());
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);
}
} }
@Override @Override
@@ -124,15 +112,6 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub {
mPipAnimationController = new PipAnimationController(context); 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} * 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); final Rect destinationBounds = new Rect(originalBounds);
destinationBounds.offset(xOffset, yOffset); destinationBounds.offset(xOffset, yOffset);
resizePinnedStackInternal(mTaskInfo.token, false /* scheduleFinishPip*/, animateResizePipInternal(mTaskInfo.token, false /* scheduleFinishPip*/,
originalBounds, destinationBounds, durationMs); originalBounds, destinationBounds, durationMs);
} }
@@ -208,15 +187,14 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub {
mTaskInfo = info; mTaskInfo = info;
if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
resizePinnedStackInternal(mTaskInfo.token, true /* scheduleFinishPip */, animateResizePipInternal(mTaskInfo.token, true /* scheduleFinishPip */,
currentBounds, destinationBounds, currentBounds, destinationBounds, DURATION_DEFAULT_MS);
PipAnimationController.DURATION_DEFAULT_MS);
} else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
mMainHandler.post(() -> mPipAnimationController mMainHandler.post(() -> mPipAnimationController
.getAnimator(mTaskInfo.token, true /* scheduleFinishPip */, .getAnimator(mTaskInfo.token, true /* scheduleFinishPip */,
destinationBounds, 0f, 1f) destinationBounds, 0f, 1f)
.setPipAnimationCallback(mPipAnimationCallback) .setPipAnimationCallback(mPipAnimationCallback)
.setDuration(PipAnimationController.DURATION_DEFAULT_MS) .setDuration(DURATION_DEFAULT_MS)
.start()); .start());
mOneShotAnimationType = ANIM_TYPE_BOUNDS; mOneShotAnimationType = ANIM_TYPE_BOUNDS;
} else { } else {
@@ -231,9 +209,8 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub {
Log.wtf(TAG, "Unrecognized token: " + token); Log.wtf(TAG, "Unrecognized token: " + token);
return; return;
} }
resizePinnedStackInternal(token, false /* scheduleFinishPip */, animateResizePipInternal(token, false /* scheduleFinishPip */,
mLastReportedBounds, mDisplayBounds, mLastReportedBounds, mDisplayBounds, DURATION_DEFAULT_MS);
PipAnimationController.DURATION_DEFAULT_MS);
} }
@Override @Override
@@ -245,11 +222,22 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub {
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
getAspectRatioOrDefault(info.pictureInPictureParams), null /* bounds */); getAspectRatioOrDefault(info.pictureInPictureParams), null /* bounds */);
Objects.requireNonNull(destinationBounds, "Missing destination 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 { try {
// Could happen when dismissPip // Could happen when dismissPip
if (wc == null || wc.getLeash() == null) { if (wc == null || wc.getLeash() == null) {
@@ -257,20 +245,10 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub {
return; return;
} }
final SurfaceControl leash = wc.getLeash(); final SurfaceControl leash = wc.getLeash();
if (animationDurationMs == DURATION_NONE) { new SurfaceControl.Transaction()
// Directly resize if no animation duration is set. When fling, wait for final .setPosition(leash, destinationBounds.left, destinationBounds.top)
// callback to issue the proper WindowContainerTransaction with destination bounds. .setWindowCrop(leash, destinationBounds.width(), destinationBounds.height())
new SurfaceControl.Transaction() .apply();
.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());
}
} catch (RemoteException e) { } catch (RemoteException e) {
Log.w(TAG, "Abort animation, invalid window container", e); Log.w(TAG, "Abort animation, invalid window container", e);
} catch (Exception 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) { private float getAspectRatioOrDefault(@Nullable PictureInPictureParams params) {
return params == null return params == null
? mPipBoundsHandler.getDefaultAspectRatio() ? mPipBoundsHandler.getDefaultAspectRatio()

View File

@@ -168,7 +168,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
// bounds. We want to restore to the unexpanded bounds when re-entering pip, // 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 // so we save the bounds before expansion (normal) instead of the current
// bounds. // bounds.
mReentryBounds.set(mTouchHandler.getNormalBounds()); mReentryBounds.set(mTouchHandler.getMinBounds());
// Apply the snap fraction of the current bounds to the normal bounds. // Apply the snap fraction of the current bounds to the normal bounds.
float snapFraction = mPipBoundsHandler.getSnapFraction(bounds); float snapFraction = mPipBoundsHandler.getSnapFraction(bounds);
mPipBoundsHandler.applySnapFraction(mReentryBounds, snapFraction); mPipBoundsHandler.applySnapFraction(mReentryBounds, snapFraction);

View File

@@ -38,7 +38,6 @@ import androidx.dynamicanimation.animation.SpringForce;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.internal.os.SomeArgs; import com.android.internal.os.SomeArgs;
import com.android.systemui.pip.PipAnimationController;
import com.android.systemui.pip.PipSnapAlgorithm; import com.android.systemui.pip.PipSnapAlgorithm;
import com.android.systemui.pip.PipTaskOrganizer; import com.android.systemui.pip.PipTaskOrganizer;
import com.android.systemui.shared.system.WindowManagerWrapper; import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -546,7 +545,7 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call
case MSG_RESIZE_IMMEDIATE: { case MSG_RESIZE_IMMEDIATE: {
SomeArgs args = (SomeArgs) msg.obj; SomeArgs args = (SomeArgs) msg.obj;
Rect toBounds = (Rect) args.arg1; Rect toBounds = (Rect) args.arg1;
mPipTaskOrganizer.resizePinnedStack(toBounds, PipAnimationController.DURATION_NONE); mPipTaskOrganizer.resizePip(toBounds);
mBounds.set(toBounds); mBounds.set(toBounds);
return true; return true;
} }
@@ -564,7 +563,7 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call
return true; return true;
} }
mPipTaskOrganizer.resizePinnedStack(toBounds, duration); mPipTaskOrganizer.animateResizePip(toBounds, duration);
mBounds.set(toBounds); mBounds.set(toBounds);
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "Could not animate resize pinned stack to bounds: " + toBounds, e); Log.e(TAG, "Could not animate resize pinned stack to bounds: " + toBounds, e);

View File

@@ -41,6 +41,7 @@ import android.view.MotionEvent;
import com.android.internal.policy.TaskResizingAlgorithm; import com.android.internal.policy.TaskResizingAlgorithm;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.pip.PipBoundsHandler; import com.android.systemui.pip.PipBoundsHandler;
import com.android.systemui.pip.PipTaskOrganizer;
import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.DeviceConfigProxy;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -64,6 +65,7 @@ public class PipResizeGestureHandler {
private final PointF mDownPoint = new PointF(); private final PointF mDownPoint = new PointF();
private final Point mMaxSize = new Point(); private final Point mMaxSize = new Point();
private final Point mMinSize = new Point(); private final Point mMinSize = new Point();
private final Rect mLastResizeBounds = new Rect();
private final Rect mTmpBounds = new Rect(); private final Rect mTmpBounds = new Rect();
private final int mDelta; private final int mDelta;
@@ -74,12 +76,13 @@ public class PipResizeGestureHandler {
private InputMonitor mInputMonitor; private InputMonitor mInputMonitor;
private InputEventReceiver mInputEventReceiver; private InputEventReceiver mInputEventReceiver;
private PipTaskOrganizer mPipTaskOrganizer;
private int mCtrlType; private int mCtrlType;
public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler, public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler,
PipTouchHandler pipTouchHandler, PipMotionHelper motionHelper, PipTouchHandler pipTouchHandler, PipMotionHelper motionHelper,
DeviceConfigProxy deviceConfig) { DeviceConfigProxy deviceConfig, PipTaskOrganizer pipTaskOrganizer) {
final Resources res = context.getResources(); final Resources res = context.getResources();
context.getDisplay().getMetrics(mDisplayMetrics); context.getDisplay().getMetrics(mDisplayMetrics);
mDisplayId = context.getDisplayId(); mDisplayId = context.getDisplayId();
@@ -87,6 +90,7 @@ public class PipResizeGestureHandler {
mPipBoundsHandler = pipBoundsHandler; mPipBoundsHandler = pipBoundsHandler;
mPipTouchHandler = pipTouchHandler; mPipTouchHandler = pipTouchHandler;
mMotionHelper = motionHelper; mMotionHelper = motionHelper;
mPipTaskOrganizer = pipTaskOrganizer;
context.getDisplay().getRealSize(mMaxSize); context.getDisplay().getRealSize(mMaxSize);
mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size); mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size);
@@ -190,11 +194,7 @@ public class PipResizeGestureHandler {
} }
} else if (mAllowGesture) { } 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) { switch (action) {
case MotionEvent.ACTION_POINTER_DOWN: case MotionEvent.ACTION_POINTER_DOWN:
// We do not support multi touch for resizing via drag // We do not support multi touch for resizing via drag
@@ -203,12 +203,18 @@ public class PipResizeGestureHandler {
case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_MOVE:
// Capture inputs // Capture inputs
mInputMonitor.pilferPointers(); 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; break;
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_CANCEL:
//TODO: Finish resize operation here. mPipTaskOrganizer.finishResize(mLastResizeBounds);
mMotionHelper.synchronizePinnedStackBounds(); mLastResizeBounds.setEmpty();
mCtrlType = CTRL_NONE; mCtrlType = CTRL_NONE;
mAllowGesture = false; mAllowGesture = false;
break; break;
@@ -220,7 +226,7 @@ public class PipResizeGestureHandler {
mMaxSize.set(maxX, maxY); mMaxSize.set(maxX, maxY);
} }
void updateMiniSize(int minX, int minY) { void updateMinSize(int minX, int minY) {
mMinSize.set(minX, minY); mMinSize.set(minX, minY);
} }

View File

@@ -41,6 +41,7 @@ import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.AccessibilityWindowInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.logging.MetricsLoggerWrapper; import com.android.internal.os.logging.MetricsLoggerWrapper;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.pip.PipBoundsHandler; import com.android.systemui.pip.PipBoundsHandler;
@@ -73,7 +74,7 @@ public class PipTouchHandler {
private final Context mContext; private final Context mContext;
private final IActivityManager mActivityManager; private final IActivityManager mActivityManager;
private final PipBoundsHandler mPipBoundsHandler; private final PipBoundsHandler mPipBoundsHandler;
private final PipResizeGestureHandler mPipResizeGestureHandler; private PipResizeGestureHandler mPipResizeGestureHandler;
private IPinnedStackController mPinnedStackController; private IPinnedStackController mPinnedStackController;
private final PipMenuActivityController mMenuController; private final PipMenuActivityController mMenuController;
@@ -84,14 +85,22 @@ public class PipTouchHandler {
// The current movement bounds // The current movement bounds
private Rect mMovementBounds = new Rect(); 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 // The reference inset bounds, used to determine the dismiss fraction
private Rect mInsetBounds = new Rect(); private Rect mInsetBounds = new Rect();
// The reference bounds used to calculate the normal/expanded target bounds
private Rect mNormalBounds = new Rect(); // The reference bounds used to calculate the minimum/maximum target bounds
private Rect mNormalMovementBounds = new Rect(); // The bound in which PIP enters is the starting/minimum bound, while the expanded/auto-resized
private Rect mExpandedBounds = new Rect(); // bound is the maximum bound.
private Rect mExpandedMovementBounds = new Rect(); private Rect mMinBounds = new Rect();
@VisibleForTesting Rect mMinMovementBounds = new Rect();
private Rect mMaxBounds = new Rect();
@VisibleForTesting Rect mMaxMovementBounds = new Rect();
private int mExpandedShortestEdgeSize; private int mExpandedShortestEdgeSize;
// Used to workaround an issue where the WM rotation happens before we are notified, allowing // 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 PipTouchState mTouchState;
private final FlingAnimationUtils mFlingAnimationUtils; private final FlingAnimationUtils mFlingAnimationUtils;
private final FloatingContentCoordinator mFloatingContentCoordinator; private final FloatingContentCoordinator mFloatingContentCoordinator;
private final PipMotionHelper mMotionHelper; private PipMotionHelper mMotionHelper;
private PipTouchGesture mGesture; private PipTouchGesture mGesture;
// Temp vars // Temp vars
@@ -182,7 +191,7 @@ public class PipTouchHandler {
mMenuController, mSnapAlgorithm, mFlingAnimationUtils, floatingContentCoordinator); mMenuController, mSnapAlgorithm, mFlingAnimationUtils, floatingContentCoordinator);
mPipResizeGestureHandler = mPipResizeGestureHandler =
new PipResizeGestureHandler(context, pipBoundsHandler, this, mMotionHelper, new PipResizeGestureHandler(context, pipBoundsHandler, this, mMotionHelper,
deviceConfig); deviceConfig, pipTaskOrganizer);
mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler, mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler,
() -> mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(), () -> mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
mMovementBounds, true /* allowMenuTimeout */, willResizeMenu())); mMovementBounds, true /* allowMenuTimeout */, willResizeMenu()));
@@ -235,14 +244,16 @@ public class PipTouchHandler {
mFloatingContentCoordinator.onContentRemoved(mMotionHelper); mFloatingContentCoordinator.onContentRemoved(mMotionHelper);
} }
mResizedBounds.setEmpty();
mPipResizeGestureHandler.onActivityUnpinned(); mPipResizeGestureHandler.onActivityUnpinned();
} }
public void onPinnedStackAnimationEnded() { public void onPinnedStackAnimationEnded() {
// Always synchronize the motion helper bounds once PiP animations finish // Always synchronize the motion helper bounds once PiP animations finish
mMotionHelper.synchronizePinnedStackBounds(); mMotionHelper.synchronizePinnedStackBounds();
mPipResizeGestureHandler.updateMiniSize(mMotionHelper.getBounds().width(),
mMotionHelper.getBounds().height()); updateMovementBounds();
mResizedBounds.set(mMinBounds);
if (mShowPipMenuOnAnimationEnd) { if (mShowPipMenuOnAnimationEnd) {
mMenuController.showMenu(MENU_STATE_CLOSE, mMotionHelper.getBounds(), mMenuController.showMenu(MENU_STATE_CLOSE, mMotionHelper.getBounds(),
@@ -266,7 +277,10 @@ public class PipTouchHandler {
mShelfHeight = shelfHeight; 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) { boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation) {
final int bottomOffset = mIsImeShowing ? mImeHeight : 0; final int bottomOffset = mIsImeShowing ? mImeHeight : 0;
final boolean fromDisplayRotationChanged = (mDisplayRotation != displayRotation); final boolean fromDisplayRotationChanged = (mDisplayRotation != displayRotation);
@@ -275,23 +289,25 @@ public class PipTouchHandler {
} }
// Re-calculate the expanded bounds // Re-calculate the expanded bounds
mNormalBounds = normalBounds; mMinBounds.set(minBounds);
Rect normalMovementBounds = new Rect(); Rect minMovementBounds = new Rect();
mSnapAlgorithm.getMovementBounds(mNormalBounds, insetBounds, normalMovementBounds, mSnapAlgorithm.getMovementBounds(mMinBounds, insetBounds, minMovementBounds,
bottomOffset); bottomOffset);
// Calculate the expanded size // Calculate the expanded size
float aspectRatio = (float) normalBounds.width() / normalBounds.height(); float aspectRatio = (float) minBounds.width() / minBounds.height();
Point displaySize = new Point(); Point displaySize = new Point();
mContext.getDisplay().getRealSize(displaySize); mContext.getDisplay().getRealSize(displaySize);
Size expandedSize = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, Size maxSize = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio,
mExpandedShortestEdgeSize, displaySize.x, displaySize.y); mExpandedShortestEdgeSize, displaySize.x, displaySize.y);
mExpandedBounds.set(0, 0, expandedSize.getWidth(), expandedSize.getHeight()); mMaxBounds.set(0, 0, maxSize.getWidth(), maxSize.getHeight());
mPipResizeGestureHandler.updateMaxSize(expandedSize.getWidth(), expandedSize.getHeight()); Rect maxMovementBounds = new Rect();
Rect expandedMovementBounds = new Rect(); mSnapAlgorithm.getMovementBounds(mMaxBounds, insetBounds, maxMovementBounds,
mSnapAlgorithm.getMovementBounds(mExpandedBounds, insetBounds, expandedMovementBounds,
bottomOffset); 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 // 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 // current state (ime showing, or shelf offset) when we need to actually shift
int extraOffset = Math.max( int extraOffset = Math.max(
@@ -308,8 +324,8 @@ public class PipTouchHandler {
final float offsetBufferPx = BOTTOM_OFFSET_BUFFER_DP final float offsetBufferPx = BOTTOM_OFFSET_BUFFER_DP
* mContext.getResources().getDisplayMetrics().density; * mContext.getResources().getDisplayMetrics().density;
final Rect toMovementBounds = mMenuState == MENU_STATE_FULL && willResizeMenu() final Rect toMovementBounds = mMenuState == MENU_STATE_FULL && willResizeMenu()
? new Rect(expandedMovementBounds) ? new Rect(maxMovementBounds)
: new Rect(normalMovementBounds); : new Rect(minMovementBounds);
final int prevBottom = mMovementBounds.bottom - mMovementBoundsExtraOffsets; final int prevBottom = mMovementBounds.bottom - mMovementBoundsExtraOffsets;
final int toBottom = toMovementBounds.bottom < toMovementBounds.top final int toBottom = toMovementBounds.bottom < toMovementBounds.top
? toMovementBounds.bottom ? toMovementBounds.bottom
@@ -323,17 +339,17 @@ public class PipTouchHandler {
// Update the movement bounds after doing the calculations based on the old movement bounds // Update the movement bounds after doing the calculations based on the old movement bounds
// above // above
mNormalMovementBounds = normalMovementBounds; mMinMovementBounds = minMovementBounds;
mExpandedMovementBounds = expandedMovementBounds; mMaxMovementBounds = maxMovementBounds;
mDisplayRotation = displayRotation; mDisplayRotation = displayRotation;
mInsetBounds.set(insetBounds); mInsetBounds.set(insetBounds);
updateMovementBounds(mMenuState); updateMovementBounds();
mMovementBoundsExtraOffsets = extraOffset; mMovementBoundsExtraOffsets = extraOffset;
// If we have a deferred resize, apply it now // If we have a deferred resize, apply it now
if (mDeferResizeToNormalBoundsUntilRotation == displayRotation) { if (mDeferResizeToNormalBoundsUntilRotation == displayRotation) {
mMotionHelper.animateToUnexpandedState(normalBounds, mSavedSnapFraction, mMotionHelper.animateToUnexpandedState(minBounds, mSavedSnapFraction,
mNormalMovementBounds, mMovementBounds, true /* immediate */); mMinMovementBounds, mMovementBounds, true /* immediate */);
mSavedSnapFraction = -1f; mSavedSnapFraction = -1f;
mDeferResizeToNormalBoundsUntilRotation = -1; mDeferResizeToNormalBoundsUntilRotation = -1;
} }
@@ -387,7 +403,7 @@ public class PipTouchHandler {
case MotionEvent.ACTION_UP: { case MotionEvent.ACTION_UP: {
// Update the movement bounds again if the state has changed since the user started // Update the movement bounds again if the state has changed since the user started
// dragging (ie. when the IME shows) // dragging (ie. when the IME shows)
updateMovementBounds(mMenuState); updateMovementBounds();
if (mGesture.onUp(mTouchState)) { if (mGesture.onUp(mTouchState)) {
break; break;
@@ -485,11 +501,13 @@ public class PipTouchHandler {
if (menuState == MENU_STATE_FULL && mMenuState != MENU_STATE_FULL) { 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 // 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 // we store back to this snap fraction. Otherwise, we'll reset the snap
// fraction and snap to the closest edge // fraction and snap to the closest edge.
Rect expandedBounds = new Rect(mExpandedBounds); // Also save the current resized bounds so when the menu disappears, we can restore it.
if (resize) { if (resize) {
mResizedBounds.set(mMotionHelper.getBounds());
Rect expandedBounds = new Rect(mMaxBounds);
mSavedSnapFraction = mMotionHelper.animateToExpandedState(expandedBounds, mSavedSnapFraction = mMotionHelper.animateToExpandedState(expandedBounds,
mMovementBounds, mExpandedMovementBounds); mMovementBounds, mMaxMovementBounds);
} }
} else if (menuState == MENU_STATE_NONE && mMenuState == MENU_STATE_FULL) { } else if (menuState == MENU_STATE_NONE && mMenuState == MENU_STATE_FULL) {
// Try and restore the PiP to the closest edge, using the saved snap fraction // Try and restore the PiP to the closest edge, using the saved snap fraction
@@ -515,9 +533,9 @@ public class PipTouchHandler {
} }
if (mDeferResizeToNormalBoundsUntilRotation == -1) { if (mDeferResizeToNormalBoundsUntilRotation == -1) {
Rect normalBounds = new Rect(mNormalBounds); Rect normalBounds = new Rect(mResizedBounds);
mMotionHelper.animateToUnexpandedState(normalBounds, mSavedSnapFraction, mMotionHelper.animateToUnexpandedState(normalBounds, mSavedSnapFraction,
mNormalMovementBounds, mMovementBounds, false /* immediate */); mMinMovementBounds, mMovementBounds, false /* immediate */);
mSavedSnapFraction = -1f; mSavedSnapFraction = -1f;
} }
} else { } else {
@@ -528,7 +546,7 @@ public class PipTouchHandler {
} }
} }
mMenuState = menuState; mMenuState = menuState;
updateMovementBounds(menuState); updateMovementBounds();
// If pip menu has dismissed, we should register the A11y ActionReplacingConnection for pip // 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. // as well, or it can't handle a11y focus and pip menu can't perform any action.
onRegistrationChanged(menuState == MENU_STATE_NONE); onRegistrationChanged(menuState == MENU_STATE_NONE);
@@ -544,11 +562,26 @@ public class PipTouchHandler {
return mMotionHelper; 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. * @return the unexpanded bounds.
*/ */
public Rect getNormalBounds() { public Rect getMinBounds() {
return mNormalBounds; return mMinBounds;
} }
/** /**
@@ -701,17 +734,17 @@ public class PipTouchHandler {
}; };
/** /**
* Updates the current movement bounds based on whether the menu is currently visible and * Updates the current movement bounds based on the current PIP size.
* resized.
*/ */
private void updateMovementBounds(int menuState) { private void updateMovementBounds() {
boolean isMenuExpanded = menuState == MENU_STATE_FULL; Rect movementBounds = new Rect();
mMovementBounds = isMenuExpanded && willResizeMenu() mSnapAlgorithm.getMovementBounds(mMotionHelper.getBounds(), mInsetBounds,
? mExpandedMovementBounds movementBounds, mIsImeShowing ? mImeHeight : 0);
: mNormalMovementBounds; mMotionHelper.setCurrentMovementBounds(movementBounds);
boolean isMenuExpanded = mMenuState == MENU_STATE_FULL;
mPipBoundsHandler.setMinEdgeSize( mPipBoundsHandler.setMinEdgeSize(
isMenuExpanded ? mExpandedShortestEdgeSize : 0); isMenuExpanded && willResizeMenu() ? mExpandedShortestEdgeSize : 0);
mMotionHelper.setCurrentMovementBounds(mMovementBounds);
} }
/** /**
@@ -729,18 +762,18 @@ public class PipTouchHandler {
if (!mEnableResize) { if (!mEnableResize) {
return false; return false;
} }
return mExpandedBounds.width() != mNormalBounds.width() return mMaxBounds.width() != mMinBounds.width()
|| mExpandedBounds.height() != mNormalBounds.height(); || mMaxBounds.height() != mMinBounds.height();
} }
public void dump(PrintWriter pw, String prefix) { public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " "; final String innerPrefix = prefix + " ";
pw.println(prefix + TAG); pw.println(prefix + TAG);
pw.println(innerPrefix + "mMovementBounds=" + mMovementBounds); pw.println(innerPrefix + "mMovementBounds=" + mMovementBounds);
pw.println(innerPrefix + "mNormalBounds=" + mNormalBounds); pw.println(innerPrefix + "mMinBounds=" + mMinBounds);
pw.println(innerPrefix + "mNormalMovementBounds=" + mNormalMovementBounds); pw.println(innerPrefix + "mMinMovementBounds=" + mMinMovementBounds);
pw.println(innerPrefix + "mExpandedBounds=" + mExpandedBounds); pw.println(innerPrefix + "mMaxBounds=" + mMaxBounds);
pw.println(innerPrefix + "mExpandedMovementBounds=" + mExpandedMovementBounds); pw.println(innerPrefix + "mMaxMovementBounds=" + mMaxMovementBounds);
pw.println(innerPrefix + "mMenuState=" + mMenuState); pw.println(innerPrefix + "mMenuState=" + mMenuState);
pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing); pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
pw.println(innerPrefix + "mImeHeight=" + mImeHeight); pw.println(innerPrefix + "mImeHeight=" + mImeHeight);

View File

@@ -433,8 +433,8 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
mCurrentPipBounds = mPipBounds; mCurrentPipBounds = mPipBounds;
break; break;
} }
mPipTaskOrganizer.resizePinnedStack( mPipTaskOrganizer.animateResizePip(mCurrentPipBounds,
mCurrentPipBounds, PipAnimationController.DURATION_DEFAULT_MS); PipAnimationController.DURATION_DEFAULT_MS);
} }
/** /**

View File

@@ -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());
}
}