From 08f81890bc70a0c8f4d25817a1dd0c93f8927e78 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Thu, 2 Mar 2017 15:40:51 -0800 Subject: [PATCH] Adding source bounds hint to support better PiP transition. Bug: 35396882 Test: Start a transition with source bounds hint. Change-Id: I4897242af84744bc05a093111a15ea52e49815e8 --- api/current.txt | 1 + api/system-current.txt | 1 + api/test-current.txt | 1 + .../android/app/PictureInPictureArgs.java | 59 ++++++++++++++- .../server/am/ActivityManagerService.java | 14 ++-- .../server/am/ActivityStackSupervisor.java | 13 ++-- .../server/am/PinnedActivityStack.java | 5 +- .../server/wm/BoundsAnimationController.java | 4 +- .../wm/PinnedStackWindowController.java | 23 +++--- .../java/com/android/server/wm/TaskStack.java | 25 +++++-- .../server/wm/WindowStateAnimator.java | 73 +++++++++++++------ 11 files changed, 166 insertions(+), 53 deletions(-) diff --git a/api/current.txt b/api/current.txt index 33eb0748a00e2..24586f0669f8e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5667,6 +5667,7 @@ package android.app { method public int describeContents(); method public void setActions(java.util.List); method public void setAspectRatio(float); + method public void setSourceRectHint(android.graphics.Rect); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } diff --git a/api/system-current.txt b/api/system-current.txt index 60d7f2f864392..212eea183c85b 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5859,6 +5859,7 @@ package android.app { method public int describeContents(); method public void setActions(java.util.List); method public void setAspectRatio(float); + method public void setSourceRectHint(android.graphics.Rect); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } diff --git a/api/test-current.txt b/api/test-current.txt index 39663f2e89f01..d33c1fa574806 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5678,6 +5678,7 @@ package android.app { method public int describeContents(); method public void setActions(java.util.List); method public void setAspectRatio(float); + method public void setSourceRectHint(android.graphics.Rect); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } diff --git a/core/java/android/app/PictureInPictureArgs.java b/core/java/android/app/PictureInPictureArgs.java index fbdcbf4e6b11d..0ce5eebed2877 100644 --- a/core/java/android/app/PictureInPictureArgs.java +++ b/core/java/android/app/PictureInPictureArgs.java @@ -17,6 +17,7 @@ package android.app; import android.annotation.Nullable; +import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; @@ -35,11 +36,19 @@ public final class PictureInPictureArgs implements Parcelable { private Float mAspectRatio; /** - * The set of actions that are associated with this activity when in picture in picture. + * The set of actions that are associated with this activity when in picture-in-picture. */ @Nullable private List mUserActions; + /** + * The source bounds hint used when entering picture-in-picture, relative to the window bounds. + * We can use this internally for the transition into picture-in-picture to ensure that a + * particular source rect is visible throughout the whole transition. + */ + @Nullable + private Rect mSourceRectHint; + PictureInPictureArgs(Parcel in) { if (in.readInt() != 0) { mAspectRatio = in.readFloat(); @@ -48,6 +57,9 @@ public final class PictureInPictureArgs implements Parcelable { mUserActions = new ArrayList<>(); in.readParcelableList(mUserActions, RemoteAction.class.getClassLoader()); } + if (in.readInt() != 0) { + mSourceRectHint = Rect.CREATOR.createFromParcel(in); + } } /** @@ -79,6 +91,9 @@ public final class PictureInPictureArgs implements Parcelable { if (otherArgs.hasSetActions()) { mUserActions = otherArgs.mUserActions; } + if (otherArgs.hasSourceBoundsHint()) { + mSourceRectHint = new Rect(otherArgs.getSourceRectHint()); + } } /** @@ -137,9 +152,43 @@ public final class PictureInPictureArgs implements Parcelable { return mUserActions != null; } + /** + * Sets the source bounds hint. These bounds are only used when an activity first enters + * picture-in-picture, and describe the bounds in window coordinates of activity entering + * picture-in-picture that will be visible following the transition. For the best effect, these + * bounds should also match the aspect ratio in the arguments. + */ + public void setSourceRectHint(Rect launchBounds) { + if (launchBounds == null) { + mSourceRectHint = null; + } else { + mSourceRectHint = new Rect(launchBounds); + } + } + + /** + * @return the launch bounds + * @hide + */ + public Rect getSourceRectHint() { + return mSourceRectHint; + } + + /** + * @return whether there are launch bounds set + * @hide + */ + public boolean hasSourceBoundsHint() { + return mSourceRectHint != null && !mSourceRectHint.isEmpty(); + } + @Override public PictureInPictureArgs clone() { - return new PictureInPictureArgs(mAspectRatio, mUserActions); + PictureInPictureArgs args = new PictureInPictureArgs(mAspectRatio, mUserActions); + if (mSourceRectHint != null) { + args.setSourceRectHint(mSourceRectHint); + } + return args; } @Override @@ -161,6 +210,12 @@ public final class PictureInPictureArgs implements Parcelable { } else { out.writeInt(0); } + if (mSourceRectHint != null) { + out.writeInt(1); + mSourceRectHint.writeToParcel(out, 0); + } else { + out.writeInt(0); + } } public static final Creator CREATOR = diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 2be5e77d967e2..92a4b786f39ff 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -7870,10 +7870,11 @@ public class ActivityManagerService extends IActivityManager.Stub r.pictureInPictureArgs.copyOnlySet(args); final float aspectRatio = r.pictureInPictureArgs.getAspectRatio(); final List actions = r.pictureInPictureArgs.getActions(); - final Rect bounds = mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY, + final Rect sourceBounds = r.pictureInPictureArgs.getSourceRectHint(); + final Rect destBounds = mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY, aspectRatio); - mStackSupervisor.moveActivityToPinnedStackLocked(r, "enterPictureInPictureMode", - bounds, true /* moveHomeStackToFront */); + mStackSupervisor.moveActivityToPinnedStackLocked(r, sourceBounds, destBounds, + true /* moveHomeStackToFront */, "enterPictureInPictureMode"); final PinnedActivityStack stack = mStackSupervisor.getStack(PINNED_STACK_ID); stack.setPictureInPictureAspectRatio(aspectRatio); stack.setPictureInPictureActions(actions); @@ -10525,7 +10526,7 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void resizeStack(int stackId, Rect bounds, boolean allowResizeInDockedMode, + public void resizeStack(int stackId, Rect destBounds, boolean allowResizeInDockedMode, boolean preserveWindows, boolean animate, int animationDuration) { enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "resizeStack()"); long ident = Binder.clearCallingIdentity(); @@ -10535,13 +10536,14 @@ public class ActivityManagerService extends IActivityManager.Stub if (stackId == PINNED_STACK_ID) { final PinnedActivityStack pinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID); - pinnedStack.animateResizePinnedStack(bounds, animationDuration); + pinnedStack.animateResizePinnedStack(null /* sourceBounds */, destBounds, + animationDuration); } else { throw new IllegalArgumentException("Stack: " + stackId + " doesn't support animated resize."); } } else { - mStackSupervisor.resizeStackLocked(stackId, bounds, null /* tempTaskBounds */, + mStackSupervisor.resizeStackLocked(stackId, destBounds, null /* tempTaskBounds */, null /* tempTaskInsetBounds */, preserveWindows, allowResizeInDockedMode, !DEFER_RESUME); } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 5c49dfd7052b1..9c19c2b91bf78 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -2794,7 +2794,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } - boolean moveTopStackActivityToPinnedStackLocked(int stackId, Rect bounds) { + boolean moveTopStackActivityToPinnedStackLocked(int stackId, Rect destBounds) { final ActivityStack stack = getStack(stackId, !CREATE_IF_NEEDED, !ON_TOP); if (stack == null) { throw new IllegalArgumentException( @@ -2815,13 +2815,14 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return false; } - moveActivityToPinnedStackLocked(r, "moveTopActivityToPinnedStack", bounds, - true /* moveHomeStackToFront */); + moveActivityToPinnedStackLocked(r, null /* sourceBounds */, destBounds, + true /* moveHomeStackToFront */, "moveTopActivityToPinnedStack"); return true; } - void moveActivityToPinnedStackLocked(ActivityRecord r, String reason, Rect bounds, - boolean moveHomeStackToFront) { + void moveActivityToPinnedStackLocked(ActivityRecord r, Rect sourceBounds, Rect destBounds, + boolean moveHomeStackToFront, String reason) { + mWindowManager.deferSurfaceLayout(); // Need to make sure the pinned stack exist so we can resize it below... @@ -2889,7 +2890,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); resumeFocusedStackTopActivityLocked(); - stack.animateResizePinnedStack(bounds, -1 /* animationDuration */); + stack.animateResizePinnedStack(sourceBounds, destBounds, -1 /* animationDuration */); mService.mTaskChangeNotificationController.notifyActivityPinned(r.packageName); } diff --git a/services/core/java/com/android/server/am/PinnedActivityStack.java b/services/core/java/com/android/server/am/PinnedActivityStack.java index 1708fe503b646..32d3082357a21 100644 --- a/services/core/java/com/android/server/am/PinnedActivityStack.java +++ b/services/core/java/com/android/server/am/PinnedActivityStack.java @@ -41,8 +41,9 @@ class PinnedActivityStack extends ActivityStack { return new PinnedStackWindowController(mStackId, this, displayId, onTop, outBounds); } - void animateResizePinnedStack(Rect bounds, int animationDuration) { - getWindowContainerController().animateResizePinnedStack(bounds, animationDuration); + void animateResizePinnedStack(Rect sourceBounds, Rect destBounds, int animationDuration) { + getWindowContainerController().animateResizePinnedStack(sourceBounds, destBounds, + animationDuration); } void setPictureInPictureAspectRatio(float aspectRatio) { diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java index cd0e6ccb2eb45..62414e50d0c6e 100644 --- a/services/core/java/com/android/server/wm/BoundsAnimationController.java +++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java @@ -121,8 +121,8 @@ public class BoundsAnimationController { private final int mFrozenTaskWidth; private final int mFrozenTaskHeight; - BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to, - boolean moveToFullScreen, boolean replacement) { + BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to, boolean moveToFullScreen, + boolean replacement) { super(); mTarget = target; mFrom.set(from); diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowController.java b/services/core/java/com/android/server/wm/PinnedStackWindowController.java index 34ccf87852de5..0145454d5ce1f 100644 --- a/services/core/java/com/android/server/wm/PinnedStackWindowController.java +++ b/services/core/java/com/android/server/wm/PinnedStackWindowController.java @@ -40,29 +40,30 @@ public class PinnedStackWindowController extends StackWindowController { /** * Animates the pinned stack. */ - public void animateResizePinnedStack(Rect bounds, int animationDuration) { + public void animateResizePinnedStack(Rect sourceBounds, Rect destBounds, + int animationDuration) { synchronized (mWindowMap) { if (mContainer == null) { throw new IllegalArgumentException("Pinned stack container not found :("); } // Get non-null fullscreen bounds if the bounds are null - final boolean moveToFullscreen = bounds == null; - bounds = getPinnedStackAnimationBounds(bounds); + final boolean moveToFullscreen = destBounds == null; + destBounds = getPinnedStackAnimationBounds(destBounds); // If the bounds are truly null, then there was no fullscreen stack at this time, so // animate this to the full display bounds final Rect toBounds; - if (bounds == null) { + if (destBounds == null) { toBounds = new Rect(); mContainer.getDisplayContent().getLogicalDisplayRect(toBounds); } else { - toBounds = bounds; + toBounds = destBounds; } final Rect originalBounds = new Rect(); mContainer.getBounds(originalBounds); - mContainer.setAnimatingBounds(toBounds); + mContainer.setAnimatingBounds(sourceBounds, toBounds); UiThread.getHandler().post(() -> { if (mContainer == null) { return; @@ -82,13 +83,17 @@ public class PinnedStackWindowController extends StackWindowController { return; } + final int displayId = mContainer.getDisplayContent().getDisplayId(); + final Rect toBounds = mService.getPictureInPictureBounds(displayId, aspectRatio); + final Rect targetBounds = new Rect(); + mContainer.getAnimatingBounds(targetBounds); final PinnedStackController pinnedStackController = mContainer.getDisplayContent().getPinnedStackController(); if (Float.compare(aspectRatio, pinnedStackController.getAspectRatio()) != 0) { - final int displayId = mContainer.getDisplayContent().getDisplayId(); - final Rect toBounds = mService.getPictureInPictureBounds(displayId, aspectRatio); - animateResizePinnedStack(toBounds, -1 /* duration */); + if (!toBounds.equals(targetBounds)) { + animateResizePinnedStack(null /* sourceBounds */, toBounds, -1 /* duration */); + } pinnedStackController.setAspectRatio( pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio) ? aspectRatio : -1f); diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 442cd54a2773a..dc437ea723159 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -19,7 +19,6 @@ package com.android.server.wm; import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; -import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.HOME_STACK_ID; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; @@ -128,6 +127,7 @@ public class TaskStack extends WindowContainer implements DimLayer.DimLaye private boolean mBoundsAnimating = false; private boolean mBoundsAnimatingToFullscreen = false; private Rect mBoundsAnimationTarget = new Rect(); + private Rect mBoundsAnimationSourceBounds = new Rect(); // Temporary storage for the new bounds that should be used after the configuration change. // Will be cleared once the client retrieves the new bounds via getBoundsForNewConfiguration(). @@ -323,14 +323,30 @@ public class TaskStack extends WindowContainer implements DimLayer.DimLaye * Sets the bounds animation target bounds. This can't currently be done in onAnimationStart() * since that is started on the UiThread. */ - void setAnimatingBounds(Rect bounds) { - if (bounds != null) { - mBoundsAnimationTarget.set(bounds); + void setAnimatingBounds(Rect sourceBounds, Rect destBounds) { + if (sourceBounds != null) { + mBoundsAnimationSourceBounds.set(sourceBounds); + } else { + mBoundsAnimationSourceBounds.setEmpty(); + } + if (destBounds != null) { + mBoundsAnimationTarget.set(destBounds); } else { mBoundsAnimationTarget.setEmpty(); } } + /** + * @return the source bounds for the bounds animation. + */ + void getAnimatingSourceBounds(Rect outBounds) { + if (mBoundsAnimationSourceBounds != null) { + outBounds.set(mBoundsAnimationSourceBounds); + return; + } + outBounds.setEmpty(); + } + /** * @return the bounds that the task stack is currently being animated towards, or the current * stack bounds if there is no animation in progress. @@ -1465,7 +1481,6 @@ public class TaskStack extends WindowContainer implements DimLayer.DimLaye public void onAnimationEnd() { synchronized (mService.mWindowMap) { mBoundsAnimating = false; - mBoundsAnimationTarget.setEmpty(); mService.requestTraversal(); } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 48de7e401925e..826fb455b6e20 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -67,8 +67,6 @@ import android.view.animation.AnimationSet; import android.view.animation.AnimationUtils; import android.view.animation.Transformation; -import com.android.server.wm.WindowManagerService.H; - import java.io.PrintWriter; import java.io.FileDescriptor; @@ -156,6 +154,8 @@ class WindowStateAnimator { Rect mLastClipRect = new Rect(); Rect mLastFinalClipRect = new Rect(); Rect mTmpStackBounds = new Rect(); + private Rect mTmpAnimatingBounds = new Rect(); + private Rect mTmpSourceBounds = new Rect(); /** * This is rectangle of the window's surface that is not covered by @@ -1282,6 +1282,7 @@ class WindowStateAnimator { void setSurfaceBoundariesLocked(final boolean recoveringMemory) { final WindowState w = mWin; + final LayoutParams attrs = mWin.getAttrs(); final Task task = w.getTask(); // We got resized, so block all updates until we got the new surface. @@ -1290,7 +1291,7 @@ class WindowStateAnimator { } mTmpSize.set(w.mShownPosition.x, w.mShownPosition.y, 0, 0); - calculateSurfaceBounds(w, w.getAttrs()); + calculateSurfaceBounds(w, attrs); mExtraHScale = (float) 1.0; mExtraVScale = (float) 1.0; @@ -1329,23 +1330,59 @@ class WindowStateAnimator { float surfaceHeight = mSurfaceController.getHeight(); if (isForceScaled()) { - int hInsets = w.getAttrs().surfaceInsets.left + w.getAttrs().surfaceInsets.right; - int vInsets = w.getAttrs().surfaceInsets.top + w.getAttrs().surfaceInsets.bottom; + int hInsets = attrs.surfaceInsets.left + attrs.surfaceInsets.right; + int vInsets = attrs.surfaceInsets.top + attrs.surfaceInsets.bottom; + float surfaceContentWidth = surfaceWidth - hInsets; + float surfaceContentHeight = surfaceHeight - vInsets; if (!mForceScaleUntilResize) { mSurfaceController.forceScaleableInTransaction(true); } + int posX = mTmpSize.left; + int posY = mTmpSize.top; task.mStack.getDimBounds(mTmpStackBounds); - // We want to calculate the scaling based on the content area, not based on - // the entire surface, so that we scale in sync with windows that don't have insets. - mExtraHScale = mTmpStackBounds.width() / (float)(surfaceWidth - hInsets); - mExtraVScale = mTmpStackBounds.height() / (float)(surfaceHeight - vInsets); + task.mStack.getAnimatingSourceBounds(mTmpSourceBounds); + if (!mTmpSourceBounds.isEmpty()) { + // Get the final target stack bounds, if we are not animating, this is just the + // current stack bounds + task.mStack.getAnimatingBounds(mTmpAnimatingBounds); + + // Calculate the current progress and interpolate the difference between the target + // and source bounds + float finalWidth = mTmpAnimatingBounds.width(); + float initialWidth = mTmpSourceBounds.width(); + float t = (surfaceContentWidth - mTmpStackBounds.width()) + / (surfaceContentWidth - mTmpAnimatingBounds.width()); + mExtraHScale = (initialWidth + t * (finalWidth - initialWidth)) / initialWidth; + mExtraVScale = mExtraHScale; + + // Adjust the position to account for the inset bounds + posX -= (int) (t * mExtraHScale * mTmpSourceBounds.left); + posY -= (int) (t * mExtraVScale * mTmpSourceBounds.top); + + // Always clip to the stack bounds since the surface can be larger with the current + // scale + clipRect = null; + finalClipRect = mTmpStackBounds; + } else { + // We want to calculate the scaling based on the content area, not based on + // the entire surface, so that we scale in sync with windows that don't have insets. + mExtraHScale = mTmpStackBounds.width() / surfaceContentWidth; + mExtraVScale = mTmpStackBounds.height() / surfaceContentHeight; + + // Since we are scaled to fit in our previously desired crop, we can now + // expose the whole window in buffer space, and not risk extending + // past where the system would have cropped us + clipRect = null; + finalClipRect = null; + } // In the case of ForceScaleToStack we scale entire tasks together, // and so we need to scale our offsets relative to the task bounds // or parent and child windows would fall out of alignment. - int posX = (int) (mTmpSize.left - w.mAttrs.x * (1 - mExtraHScale)); - int posY = (int) (mTmpSize.top - w.mAttrs.y * (1 - mExtraVScale)); + posX -= (int) (attrs.x * (1 - mExtraHScale)); + posY -= (int) (attrs.y * (1 - mExtraVScale)); + // Imagine we are scaling down. As we scale the buffer down, we decrease the // distance between the surface top left, and the start of the surface contents // (previously it was surfaceInsets.left pixels in screen space but now it @@ -1353,17 +1390,11 @@ class WindowStateAnimator { // non inset content at the same position, we have to shift the whole window // forward. Likewise for scaling up, we've increased this distance, and we need // to shift by a negative number to compensate. - posX += w.getAttrs().surfaceInsets.left * (1 - mExtraHScale); - posY += w.getAttrs().surfaceInsets.top * (1 - mExtraVScale); + posX += attrs.surfaceInsets.left * (1 - mExtraHScale); + posY += attrs.surfaceInsets.top * (1 - mExtraVScale); - mSurfaceController.setPositionInTransaction((float)Math.floor(posX), - (float)Math.floor(posY), recoveringMemory); - - // Since we are scaled to fit in our previously desired crop, we can now - // expose the whole window in buffer space, and not risk extending - // past where the system would have cropped us - clipRect = null; - finalClipRect = null; + mSurfaceController.setPositionInTransaction((float) Math.floor(posX), + (float) Math.floor(posY), recoveringMemory); // Various surfaces in the scaled stack may resize at different times. // We need to ensure for each surface, that we disable transformation matrix