Merge changes I789945c1,I532b0928,I62c6df8b,Ib2bd81ad into oc-dev

* changes:
  Fixing missing movement bounds notification to SystemUI.
  Fixing animating bounds regression.
  Tightening up rotation behavior for PIP (2/3)
  Tightening up rotation behavior for PIP (1/3)
This commit is contained in:
TreeHugger Robot
2017-04-14 04:38:55 +00:00
committed by Android (Google) Code Review
17 changed files with 591 additions and 120 deletions

View File

@@ -86,6 +86,7 @@ public class PipManager implements BasePipManager {
ComponentName topPipActivity = PipUtils.getTopPinnedActivity(mContext,
mActivityManager);
mMenuController.hideMenu();
mNotificationController.onActivityUnpinned(topPipActivity);
SystemServicesProxy.getInstance(mContext).setPipVisibility(topPipActivity != null);

View File

@@ -7864,9 +7864,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final float aspectRatio = r.pictureInPictureArgs.getAspectRatio();
final List<RemoteAction> actions = r.pictureInPictureArgs.getActions();
final Rect sourceBounds = r.pictureInPictureArgs.getSourceRectHint();
final Rect destBounds = mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY,
aspectRatio);
mStackSupervisor.moveActivityToPinnedStackLocked(r, sourceBounds, destBounds,
mStackSupervisor.moveActivityToPinnedStackLocked(r, sourceBounds, aspectRatio,
true /* moveHomeStackToFront */, "enterPictureInPictureMode");
final PinnedActivityStack stack = mStackSupervisor.getStack(PINNED_STACK_ID);
stack.setPictureInPictureAspectRatio(aspectRatio);
@@ -7927,7 +7925,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// if it is not already expanding to fullscreen. Otherwise, the arguments will
// be used the next time the activity enters PiP
final PinnedActivityStack stack = r.getStack();
if (!stack.isBoundsAnimatingToFullscreen()) {
if (!stack.isAnimatingBoundsToFullscreen()) {
stack.setPictureInPictureAspectRatio(
r.pictureInPictureArgs.getAspectRatio());
stack.setPictureInPictureActions(r.pictureInPictureArgs.getActions());

View File

@@ -174,6 +174,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
import com.android.server.am.ActivityStack.ActivityState;
import com.android.server.wm.PinnedStackWindowController;
import com.android.server.wm.WindowManagerService;
import java.io.FileDescriptor;
@@ -2492,11 +2493,21 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
void resizePinnedStackLocked(Rect pinnedBounds, Rect tempPinnedTaskBounds) {
final ActivityStack stack = getStack(PINNED_STACK_ID);
final PinnedActivityStack stack = getStack(PINNED_STACK_ID);
if (stack == null) {
Slog.w(TAG, "resizePinnedStackLocked: pinned stack not found");
return;
}
// It is possible for the bounds animation from the WM to call this but be delayed by
// another AM call that is holding the AMS lock. In such a case, the pinnedBounds may be
// incorrect if AMS.resizeStackWithBoundsFromWindowManager() is already called while waiting
// for the AMS lock to be freed. So check and make sure these bounds are still good.
final PinnedStackWindowController stackController = stack.getWindowContainerController();
if (stackController.pinnedStackResizeAllowed()) {
return;
}
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizePinnedStack");
mWindowManager.deferSurfaceLayout();
try {
@@ -2857,12 +2868,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return false;
}
moveActivityToPinnedStackLocked(r, null /* sourceBounds */, destBounds,
moveActivityToPinnedStackLocked(r, null /* sourceBounds */, 0f /* aspectRatio */,
true /* moveHomeStackToFront */, "moveTopActivityToPinnedStack");
return true;
}
void moveActivityToPinnedStackLocked(ActivityRecord r, Rect sourceBounds, Rect destBounds,
void moveActivityToPinnedStackLocked(ActivityRecord r, Rect sourceBounds, float aspectRatio,
boolean moveHomeStackToFront, String reason) {
mWindowManager.deferSurfaceLayout();
@@ -2932,6 +2943,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
resumeFocusedStackTopActivityLocked();
// Calculate the default bounds (don't use existing stack bounds as we may have just created
// the stack
final Rect destBounds = mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY,
aspectRatio, false /* useExistingStackBounds */);
// TODO(b/36099777): Schedule the PiP mode change here immediately until we can defer all
// callbacks until after the bounds animation
scheduleUpdatePictureInPictureModeIfNeeded(r.getTask(), destBounds, true /* immediate */);

View File

@@ -21,7 +21,7 @@ import android.graphics.Rect;
import com.android.server.am.ActivityStackSupervisor.ActivityContainer;
import com.android.server.wm.PinnedStackWindowController;
import com.android.server.wm.StackWindowController;
import com.android.server.wm.PinnedStackWindowListener;
import java.util.ArrayList;
import java.util.List;
@@ -29,7 +29,8 @@ import java.util.List;
/**
* State and management of the pinned stack of activities.
*/
class PinnedActivityStack extends ActivityStack<PinnedStackWindowController> {
class PinnedActivityStack extends ActivityStack<PinnedStackWindowController>
implements PinnedStackWindowListener {
PinnedActivityStack(ActivityContainer activityContainer,
RecentTasks recentTasks, boolean onTop) {
@@ -55,8 +56,8 @@ class PinnedActivityStack extends ActivityStack<PinnedStackWindowController> {
getWindowContainerController().setPictureInPictureActions(actions);
}
boolean isBoundsAnimatingToFullscreen() {
return getWindowContainerController().isBoundsAnimatingToFullscreen();
boolean isAnimatingBoundsToFullscreen() {
return getWindowContainerController().isAnimatingBoundsToFullscreen();
}
@Override

View File

@@ -33,6 +33,8 @@ import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.WindowManagerInternal;
import com.android.internal.annotations.VisibleForTesting;
/**
* Enables animating bounds of objects.
*
@@ -103,7 +105,8 @@ public class BoundsAnimationController {
com.android.internal.R.interpolator.fast_out_slow_in);
}
private final class BoundsAnimator extends ValueAnimator
@VisibleForTesting
final class BoundsAnimator extends ValueAnimator
implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
private final AnimateBoundsUser mTarget;
private final Rect mFrom = new Rect();
@@ -113,10 +116,12 @@ public class BoundsAnimationController {
private final boolean mMoveToFullScreen;
// True if this this animation was cancelled and will be replaced the another animation from
// the same {@link #AnimateBoundsUser} target.
private boolean mSkipAnimationEnd;
private boolean mSkipFinalResize;
// True if this animation replaced a previous animation of the same
// {@link #AnimateBoundsUser} target.
private final boolean mSkipAnimationStart;
// True if this animation was cancelled by the user, not as a part of a replacing animation
private boolean mSkipAnimationEnd;
// True if this animation is not replacing a previous animation, or if the previous
// animation is animating to a different fullscreen state than the current animation.
// We use this to ensure that we always provide a consistent set/order of callbacks when we
@@ -200,7 +205,11 @@ public class BoundsAnimationController {
// Whoops, the target doesn't feel like animating anymore. Let's immediately finish
// any further animation.
if (DEBUG) Slog.d(TAG, "animateUpdate: cancelled");
animation.cancel();
// Since we are cancelling immediately without a replacement animation, send the
// animation end to maintain callback parity, but also skip any further resizes
prepareCancel(false /* skipAnimationEnd */, true /* skipFinalResize */);
cancel();
}
}
@@ -208,7 +217,7 @@ public class BoundsAnimationController {
public void onAnimationEnd(Animator animation) {
if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
+ " mMoveToFullScreen=" + mMoveToFullScreen
+ " mSkipAnimationEnd=" + mSkipAnimationEnd
+ " mSkipFinalResize=" + mSkipFinalResize
+ " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition
+ " mAppTransitionIsRunning=" + mAppTransition.isRunning());
@@ -222,10 +231,15 @@ public class BoundsAnimationController {
return;
}
if (!mSkipFinalResize) {
// If not cancelled, resize the pinned stack to the final size. All calls to
// setPinnedStackSize() must be done between onAnimationStart() and onAnimationEnd()
mTarget.setPinnedStackSize(mTo, null);
}
finishAnimation();
mTarget.setPinnedStackSize(mTo, null);
if (mMoveToFullScreen && !mSkipAnimationEnd) {
if (mMoveToFullScreen && !mSkipFinalResize) {
mTarget.moveToFullscreen();
}
}
@@ -235,10 +249,16 @@ public class BoundsAnimationController {
finishAnimation();
}
public void prepareCancel(boolean skipAnimationEnd, boolean skipFinalResize) {
if (DEBUG) Slog.d(TAG, "prepareCancel: skipAnimationEnd=" + skipAnimationEnd
+ " skipFinalResize=" + skipFinalResize);
mSkipAnimationEnd = skipAnimationEnd;
mSkipFinalResize = skipFinalResize;
}
@Override
public void cancel() {
mSkipAnimationEnd = true;
if (DEBUG) Slog.d(TAG, "cancel: willReplace mTarget=" + mTarget);
if (DEBUG) Slog.d(TAG, "cancel: mTarget=" + mTarget);
super.cancel();
}
@@ -273,19 +293,15 @@ public class BoundsAnimationController {
public interface AnimateBoundsUser {
/**
* Asks the target to directly (without any intermediate steps, like scheduling animation)
* resize its bounds.
*
* @return Whether the target still wants to be animated and successfully finished the
* operation. If it returns false, the animation will immediately be cancelled. The target
* should return false when something abnormal happened, e.g. it was completely removed
* from the hierarchy and is not valid anymore.
*/
boolean setSize(Rect bounds);
/**
* Behaves as setSize, but freezes the bounds of any tasks in the target at taskBounds,
* Sets the size of the target (without any intermediate steps, like scheduling animation)
* but freezes the bounds of any tasks in the target at taskBounds,
* to allow for more flexibility during resizing. Only works for the pinned stack at the
* moment.
*
* @return Whether the target should continue to be animated and this call was successful.
* If false, the animation will be cancelled because the user has determined that the
* animation is now invalid and not required. In such a case, the cancel will trigger the
* animation end callback as well, but will not send any further size changes.
*/
boolean setPinnedStackSize(Rect bounds, Rect taskBounds);
@@ -310,11 +326,20 @@ public class BoundsAnimationController {
*/
void onAnimationEnd();
/**
* Callback for the target to inform it to reparent to the fullscreen stack.
*/
void moveToFullscreen();
}
void animateBounds(final AnimateBoundsUser target, Rect from, Rect to, int animationDuration,
boolean moveToFullscreen) {
public void animateBounds(final AnimateBoundsUser target, Rect from, Rect to,
int animationDuration, boolean moveToFullscreen) {
animateBoundsImpl(target, from, to, animationDuration, moveToFullscreen);
}
@VisibleForTesting
BoundsAnimator animateBoundsImpl(final AnimateBoundsUser target, Rect from, Rect to,
int animationDuration, boolean moveToFullscreen) {
final BoundsAnimator existing = mRunningAnimations.get(target);
final boolean replacing = existing != null;
final boolean animatingToNewFullscreenState = (existing == null) ||
@@ -326,12 +351,15 @@ public class BoundsAnimationController {
if (replacing) {
if (existing.isAnimatingTo(to)) {
// Just les the current animation complete if it has the same destination as the
// Just let the current animation complete if it has the same destination as the
// one we are trying to start.
if (DEBUG) Slog.d(TAG, "animateBounds: same destination as existing=" + existing
+ " ignoring...");
return;
return existing;
}
// Since we are replacing, we skip both animation start and end callbacks, and don't
// animate to the final bounds when cancelling
existing.prepareCancel(true /* skipAnimationEnd */, true /* skipFinalResize */);
existing.cancel();
}
final BoundsAnimator animator = new BoundsAnimator(target, from, to, moveToFullscreen,
@@ -342,5 +370,6 @@ public class BoundsAnimationController {
: DEFAULT_TRANSITION_DURATION) * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
animator.setInterpolator(mFastOutSlowInInterpolator);
animator.start();
return animator;
}
}

View File

@@ -1420,6 +1420,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
changedStackList.add(stack.mStackId);
}
}
// If there was no pinned stack, we still need to notify the controller of the display info
// update as a result of the config change. We do this here to consolidate the flow between
// changes when there is and is not a stack.
if (getStackById(PINNED_STACK_ID) == null) {
mPinnedStackControllerLocked.onDisplayInfoChanged();
}
}
@Override

View File

@@ -147,7 +147,6 @@ class PinnedStackController {
void onConfigurationChanged() {
reloadResources();
notifyMovementBoundsChanged(false /* fromImeAdjustment */);
}
/**
@@ -240,14 +239,32 @@ class PinnedStackController {
return defaultBounds;
}
/**
* In the case where the display rotation is changed but there is no stack, we can't depend on
* onTaskStackBoundsChanged() to be called. But we still should update our known display info
* with the new state so that we can update SystemUI.
*/
synchronized void onDisplayInfoChanged() {
mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
notifyMovementBoundsChanged(false /* fromImeAdjustment */);
}
/**
* Updates the display info, calculating and returning the new stack and movement bounds in the
* new orientation of the device if necessary.
*/
void onTaskStackBoundsChanged(Rect targetBounds, Rect outBounds) {
boolean onTaskStackBoundsChanged(Rect targetBounds, Rect outBounds) {
final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
if (mDisplayInfo.equals(displayInfo)) {
return;
// We are already in the right orientation, ignore
outBounds.setEmpty();
return false;
} else if (targetBounds.isEmpty()) {
// The stack is null, we are just initializing the stack, so just store the display info
// and ignore
mDisplayInfo.copyFrom(displayInfo);
outBounds.setEmpty();
return false;
}
mTmpRect.set(targetBounds);
@@ -272,6 +289,7 @@ class PinnedStackController {
notifyMovementBoundsChanged(false /* fromImeAdjustment */);
outBounds.set(postChangeStackBounds);
return true;
}
/**
@@ -371,7 +389,7 @@ class PinnedStackController {
final Rect animatingBounds = mTmpAnimatingBoundsRect;
final TaskStack pinnedStack = mDisplayContent.getStackById(PINNED_STACK_ID);
if (pinnedStack != null) {
pinnedStack.getAnimatingBounds(animatingBounds);
pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
} else {
animatingBounds.set(normalBounds);
}

View File

@@ -32,8 +32,8 @@ public class PinnedStackWindowController extends StackWindowController {
private Rect mTmpBoundsRect = new Rect();
public PinnedStackWindowController(int stackId, StackWindowListener listener, int displayId,
boolean onTop, Rect outBounds) {
public PinnedStackWindowController(int stackId, PinnedStackWindowListener listener,
int displayId, boolean onTop, Rect outBounds) {
super(stackId, listener, displayId, onTop, outBounds, WindowManagerService.getInstance());
}
@@ -63,7 +63,7 @@ public class PinnedStackWindowController extends StackWindowController {
final Rect originalBounds = new Rect();
mContainer.getBounds(originalBounds);
mContainer.setAnimatingBounds(sourceBounds, toBounds);
mContainer.setAnimationFinalBounds(sourceBounds, toBounds);
UiThread.getHandler().post(() -> {
if (mContainer == null) {
return;
@@ -84,9 +84,10 @@ public class PinnedStackWindowController extends StackWindowController {
}
final int displayId = mContainer.getDisplayContent().getDisplayId();
final Rect toBounds = mService.getPictureInPictureBounds(displayId, aspectRatio);
final Rect toBounds = mService.getPictureInPictureBounds(displayId, aspectRatio,
true /* useExistingStackBounds */);
final Rect targetBounds = new Rect();
mContainer.getAnimatingBounds(targetBounds);
mContainer.getAnimationOrCurrentBounds(targetBounds);
final PinnedStackController pinnedStackController =
mContainer.getDisplayContent().getPinnedStackController();
@@ -117,8 +118,12 @@ public class PinnedStackWindowController extends StackWindowController {
/**
* @return whether the bounds are currently animating to fullscreen.
*/
public boolean isBoundsAnimatingToFullscreen() {
return mContainer.isBoundsAnimatingToFullscreen();
public boolean isAnimatingBoundsToFullscreen() {
return mContainer.isAnimatingBoundsToFullscreen();
}
public boolean pinnedStackResizeAllowed() {
return mContainer.pinnedStackResizeAllowed();
}
/**
@@ -132,4 +137,16 @@ public class PinnedStackWindowController extends StackWindowController {
}
return bounds;
}
/**
* The following calls are made from WM to AM.
*/
/** Calls directly into activity manager so window manager lock shouldn't held. */
public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) {
if (mListener != null) {
PinnedStackWindowListener listener = (PinnedStackWindowListener) mListener;
listener.updatePictureInPictureModeForPinnedStackAnimation(targetStackBounds);
}
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2017 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.server.wm;
import android.graphics.Rect;
/**
* Interface used by the creator of {@link PinnedStackWindowController} to listen to changes with
* the stack container.
*/
public interface PinnedStackWindowListener extends StackWindowListener {
/**
* Called when the stack container pinned stack animation will change the picture-in-picture
* mode. This is a direct call into ActivityManager.
*/
default void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) {}
}

View File

@@ -368,13 +368,6 @@ public class StackWindowController
}
}
/** Calls directly into activity manager so window manager lock shouldn't held. */
void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) {
if (mListener != null) {
mListener.updatePictureInPictureModeForPinnedStackAnimation(targetStackBounds);
}
}
void requestResize(Rect bounds) {
mHandler.obtainMessage(H.REQUEST_RESIZE, bounds).sendToTarget();
}

View File

@@ -26,10 +26,4 @@ public interface StackWindowListener extends WindowContainerListener {
/** Called when the stack container would like its controller to resize. */
void requestResize(Rect bounds);
/**
* Called when the stack container pinned stack animation will change the picture-in-picture
* mode. This is a direct call into ActivityManager.
*/
default void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) {}
}

View File

@@ -595,8 +595,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
* we will have a jump at the end.
*/
boolean isFloating() {
return StackId.tasksAreFloating(mStack.mStackId)
&& !mStack.isBoundsAnimatingToFullscreen();
return StackId.tasksAreFloating(mStack.mStackId) && !mStack.isAnimatingBoundsToFullscreen();
}
WindowState getTopVisibleAppMainWindow() {

View File

@@ -77,6 +77,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
/** For comparison with DisplayContent bounds. */
private Rect mTmpRect = new Rect();
private Rect mTmpRect2 = new Rect();
private Rect mTmpRect3 = new Rect();
/** Content limits relative to the DisplayContent this sits in. */
private Rect mBounds = new Rect();
@@ -125,7 +126,11 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
// perfectly fit the region it would have been cropped to. We may also avoid certain logic we
// would otherwise apply while resizing, while resizing in the bounds animating mode.
private boolean mBoundsAnimating = false;
// Set when an animation has been requested but has not yet started from the UI thread. This is
// cleared when the animation actually starts.
private boolean mBoundsAnimatingRequested = false;
private boolean mBoundsAnimatingToFullscreen = false;
private boolean mCancelCurrentBoundsAnimation = false;
private Rect mBoundsAnimationTarget = new Rect();
private Rect mBoundsAnimationSourceBounds = new Rect();
@@ -262,12 +267,6 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
if (mDisplayContent != null) {
mDisplayContent.mDimLayerController.updateDimLayer(this);
if (mStackId == PINNED_STACK_ID) {
// Update the bounds based on any changes to the display info
getAnimatingBounds(mTmpRect2);
mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(mTmpRect2,
bounds);
}
mAnimationBackgroundSurface.setBounds(bounds);
}
@@ -320,10 +319,11 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
/**
* Sets the bounds animation target bounds. This can't currently be done in onAnimationStart()
* since that is started on the UiThread.
* Sets the bounds animation target bounds ahead of an animation. This can't currently be done
* in onAnimationStart() since that is started on the UiThread.
*/
void setAnimatingBounds(Rect sourceBounds, Rect destBounds) {
void setAnimationFinalBounds(Rect sourceBounds, Rect destBounds) {
mBoundsAnimatingRequested = true;
if (sourceBounds != null) {
mBoundsAnimationSourceBounds.set(sourceBounds);
} else {
@@ -337,23 +337,26 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
/**
* @return the source bounds for the bounds animation.
* @return the final bounds for the bounds animation.
*/
void getAnimatingSourceBounds(Rect outBounds) {
if (!mBoundsAnimationSourceBounds.isEmpty()) {
outBounds.set(mBoundsAnimationSourceBounds);
return;
}
outBounds.setEmpty();
void getFinalAnimationBounds(Rect outBounds) {
outBounds.set(mBoundsAnimationTarget);
}
/**
* @return the bounds that the task stack is currently being animated towards, or the current
* stack bounds if there is no animation in progress.
* @return the final source bounds for the bounds animation.
*/
void getAnimatingBounds(Rect outBounds) {
if (!mBoundsAnimationTarget.isEmpty()) {
outBounds.set(mBoundsAnimationTarget);
void getFinalAnimationSourceBounds(Rect outBounds) {
outBounds.set(mBoundsAnimationSourceBounds);
}
/**
* @return the final animation bounds if the task stack is currently being animated, or the
* current stack bounds otherwise.
*/
void getAnimationOrCurrentBounds(Rect outBounds) {
if ((mBoundsAnimatingRequested || mBoundsAnimating) && !mBoundsAnimationTarget.isEmpty()) {
getFinalAnimationBounds(outBounds);
return;
}
getBounds(outBounds);
@@ -398,6 +401,24 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
// as it's going away soon anyway.
return false;
}
if (mStackId == PINNED_STACK_ID) {
getAnimationOrCurrentBounds(mTmpRect2);
boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
mTmpRect2, mTmpRect3);
if (updated) {
mBoundsAfterRotation.set(mTmpRect3);
// Once we've set the bounds based on the rotation of the old bounds in the new
// orientation, clear the animation target bounds since they are obsolete, and
// cancel any currently running animations
mBoundsAnimationTarget.setEmpty();
mBoundsAnimationSourceBounds.setEmpty();
mCancelCurrentBoundsAnimation = true;
return true;
}
}
final int newRotation = getDisplayInfo().rotation;
final int newDensity = getDisplayInfo().logicalDensityDpi;
@@ -413,20 +434,6 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
return false;
}
if (StackId.tasksAreFloating(mStackId)) {
// Update stack bounds again since the display info has changed since updateDisplayInfo,
// however, for floating tasks, we don't need to apply the new rotation to the bounds,
// we can just update and return them here
setBounds(mBounds);
mBoundsAfterRotation.set(mBounds);
// Once we've set the bounds based on the rotation of the old bounds in the new
// orientation, clear the animation target bounds since they are obsolete
mBoundsAnimationTarget.setEmpty();
mBoundsAnimationSourceBounds.setEmpty();
return true;
}
mTmpRect2.set(mBounds);
mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
switch (mStackId) {
@@ -692,6 +699,14 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
getStackDockedModeBounds(mTmpRect, bounds, mStackId, mTmpRect2,
mDisplayContent.mDividerControllerLocked.getContentWidth(),
dockedOnTopOrLeft);
} else if (mStackId == PINNED_STACK_ID) {
// Update the bounds based on any changes to the display info
getAnimationOrCurrentBounds(mTmpRect2);
boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
mTmpRect2, mTmpRect3);
if (updated) {
bounds = new Rect(mTmpRect3);
}
}
updateDisplayInfo(bounds);
@@ -1443,21 +1458,11 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
}
@Override // AnimatesBounds
public boolean setSize(Rect bounds) {
synchronized (mService.mWindowMap) {
if (mDisplayContent == null) {
return false;
}
}
try {
mService.mActivityManager.resizeStack(mStackId, bounds, false, true, false, -1);
} catch (RemoteException e) {
}
return true;
}
public boolean setPinnedStackSize(Rect bounds, Rect tempTaskBounds) {
if (mCancelCurrentBoundsAnimation) {
return false;
}
try {
mService.mActivityManager.resizePinnedStack(bounds, tempTaskBounds);
} catch (RemoteException e) {
@@ -1469,8 +1474,10 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
@Override // AnimatesBounds
public void onAnimationStart(boolean toFullscreen) {
synchronized (mService.mWindowMap) {
mBoundsAnimatingRequested = false;
mBoundsAnimating = true;
mBoundsAnimatingToFullscreen = toFullscreen;
mCancelCurrentBoundsAnimation = false;
}
if (mStackId == PINNED_STACK_ID) {
@@ -1484,7 +1491,8 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
@Override // AnimatesBounds
public void updatePictureInPictureMode(Rect targetStackBounds) {
final StackWindowController controller = getController();
final PinnedStackWindowController controller =
(PinnedStackWindowController) getController();
if (controller != null) {
controller.updatePictureInPictureModeForPinnedStackAnimation(targetStackBounds);
}
@@ -1523,14 +1531,21 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
return mBoundsAnimating;
}
public boolean getBoundsAnimating() {
public boolean isAnimatingBounds() {
return mBoundsAnimating;
}
public boolean isBoundsAnimatingToFullscreen() {
public boolean isAnimatingBoundsToFullscreen() {
return mBoundsAnimating && mBoundsAnimatingToFullscreen;
}
public boolean pinnedStackResizeAllowed() {
if (mBoundsAnimating && mCancelCurrentBoundsAnimation) {
return true;
}
return false;
}
/** Returns true if a removal action is still being deferred. */
boolean checkCompleteDeferredRemoval() {
if (isAnimating()) {

View File

@@ -31,7 +31,6 @@ import static android.os.Process.SHELL_UID;
import static android.os.Process.SYSTEM_UID;
import static android.os.Process.THREAD_PRIORITY_DISPLAY;
import static android.os.Process.myPid;
import static android.os.Process.myTid;
import static android.os.UserHandle.USER_NULL;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.DOCKED_INVALID;
@@ -148,7 +147,6 @@ import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
@@ -2758,7 +2756,12 @@ public class WindowManagerService extends IWindowManager.Stub
mDockedStackCreateBounds = bounds;
}
public Rect getPictureInPictureBounds(int displayId, float aspectRatio) {
/**
* @param useExistingStackBounds Apply {@param aspectRatio} to the existing target stack bounds
* if possible
*/
public Rect getPictureInPictureBounds(int displayId, float aspectRatio,
boolean useExistingStackBounds) {
synchronized (mWindowMap) {
if (!mSupportsPictureInPicture) {
return null;
@@ -2773,11 +2776,11 @@ public class WindowManagerService extends IWindowManager.Stub
final PinnedStackController pinnedStackController =
displayContent.getPinnedStackController();
final TaskStack stack = displayContent.getStackById(PINNED_STACK_ID);
if (stack != null) {
if (stack != null && useExistingStackBounds) {
// If the stack exists, then use its final bounds to calculate the new aspect ratio
// bounds.
stackBounds = new Rect();
stack.getAnimatingBounds(stackBounds);
stack.getAnimationOrCurrentBounds(stackBounds);
} else {
// Otherwise, just calculate the aspect ratio bounds from the default bounds
stackBounds = pinnedStackController.getDefaultBounds();

View File

@@ -1090,7 +1090,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// notify the client of frame changes in this case. Not only is it a lot of churn, but
// the frame may not correspond to the surface size or the onscreen area at various
// phases in the animation, and the client will become sad and confused.
if (task != null && task.mStack.getBoundsAnimating()) {
if (task != null && task.mStack.isAnimatingBounds()) {
return;
}

View File

@@ -1360,11 +1360,11 @@ class WindowStateAnimator {
int posX = mTmpSize.left;
int posY = mTmpSize.top;
task.mStack.getDimBounds(mTmpStackBounds);
task.mStack.getAnimatingSourceBounds(mTmpSourceBounds);
task.mStack.getFinalAnimationSourceBounds(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);
task.mStack.getFinalAnimationBounds(mTmpAnimatingBounds);
// Calculate the current progress and interpolate the difference between the target
// and source bounds

View File

@@ -0,0 +1,348 @@
/*
* Copyright (C) 2017 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.server.wm;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.platform.test.annotations.Presubmit;
import android.support.test.InstrumentationRegistry;
import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.WindowManagerInternal.AppTransitionListener;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.server.wm.BoundsAnimationController.BoundsAnimator;
/**
* Test class for {@link BoundsAnimationController} to ensure that it sends the right callbacks
* depending on the various interactions.
*
* Build/Install/Run:
* bit FrameworksServicesTests:com.android.server.wm.BoundsAnimationControllerTests
*/
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
public class BoundsAnimationControllerTests extends WindowTestsBase {
/**
* Mock value animator to simulate updates with.
*/
private class MockValueAnimator extends ValueAnimator {
private float mFraction;
public MockValueAnimator getWithValue(float fraction) {
mFraction = fraction;
return this;
}
@Override
public Object getAnimatedValue() {
return mFraction;
}
}
/**
* Mock app transition to fire notifications to the bounds animator.
*/
private class MockAppTransition extends AppTransition {
private AppTransitionListener mListener;
MockAppTransition(Context context) {
super(context, null);
}
@Override
void registerListenerLocked(AppTransitionListener listener) {
mListener = listener;
}
public void notifyTransitionPending() {
mListener.onAppTransitionPendingLocked();
}
public void notifyTransitionCancelled(int transit) {
mListener.onAppTransitionCancelledLocked(transit);
}
public void notifyTransitionStarting(int transit) {
mListener.onAppTransitionStartingLocked(transit, null, null, null, null);
}
public void notifyTransitionFinished() {
mListener.onAppTransitionFinishedLocked(null);
}
}
/**
* A test animate bounds user to track callbacks from the bounds animation.
*/
private class TestAnimateBoundsUser implements BoundsAnimationController.AnimateBoundsUser {
boolean mMovedToFullscreen;
boolean mAnimationStarted;
boolean mAnimationStartedToFullscreen;
boolean mAnimationEnded;
boolean mUpdatedPictureInPictureModeWithBounds;
boolean mBoundsUpdated;
Rect mStackBounds;
Rect mTaskBounds;
boolean mRequestCancelAnimation = false;
void reinitialize(Rect stackBounds, Rect taskBounds) {
mMovedToFullscreen = false;
mAnimationStarted = false;
mAnimationStartedToFullscreen = false;
mAnimationEnded = false;
mUpdatedPictureInPictureModeWithBounds = false;
mStackBounds = stackBounds;
mTaskBounds = taskBounds;
mBoundsUpdated = false;
mRequestCancelAnimation = false;
}
@Override
public void onAnimationStart(boolean toFullscreen) {
mAnimationStarted = true;
mAnimationStartedToFullscreen = toFullscreen;
}
@Override
public void updatePictureInPictureMode(Rect targetStackBounds) {
mUpdatedPictureInPictureModeWithBounds = true;
}
@Override
public boolean setPinnedStackSize(Rect stackBounds, Rect taskBounds) {
// TODO: Once we break the runs apart, we should fail() here if this is called outside
// of onAnimationStart() and onAnimationEnd()
if (mRequestCancelAnimation) {
return false;
} else {
mBoundsUpdated = true;
mStackBounds = stackBounds;
mTaskBounds = taskBounds;
return true;
}
}
@Override
public void onAnimationEnd() {
mAnimationEnded = true;
}
@Override
public void moveToFullscreen() {
mMovedToFullscreen = true;
}
}
// Constants
private static final boolean MOVE_TO_FULLSCREEN = true;
// Some dummy bounds to represent fullscreen and floating bounds
private static final Rect BOUNDS_FULL = new Rect(0, 0, 100, 100);
private static final Rect BOUNDS_FLOATING = new Rect(80, 80, 95, 95);
private static final Rect BOUNDS_ALT_FLOATING = new Rect(60, 60, 95, 95);
// Some dummy duration
private static final int DURATION = 100;
// Common
private MockAppTransition mAppTransition;
private MockValueAnimator mAnimator;
private TestAnimateBoundsUser mTarget;
private BoundsAnimationController mController;
// Temp
private Rect mTmpRect = new Rect();
@Override
public void setUp() throws Exception {
super.setUp();
final Context context = InstrumentationRegistry.getTargetContext();
final Handler handler = new Handler(Looper.getMainLooper());
mAppTransition = new MockAppTransition(context);
mAnimator = new MockValueAnimator();
mTarget = new TestAnimateBoundsUser();
mController = new BoundsAnimationController(context, mAppTransition, handler);
}
@UiThreadTest
@Test
public void testFullscreenToFloatingTransition() throws Exception {
// Create and start the animation
mTarget.reinitialize(BOUNDS_FULL, null);
final BoundsAnimator boundsAnimator = mController.animateBoundsImpl(mTarget, BOUNDS_FULL,
BOUNDS_FLOATING, DURATION, !MOVE_TO_FULLSCREEN);
// Assert that when we are started, and that we are not going to fullscreen
assertTrue(mTarget.mAnimationStarted);
assertFalse(mTarget.mAnimationStartedToFullscreen);
// Ensure we are not triggering a PiP mode change
assertFalse(mTarget.mUpdatedPictureInPictureModeWithBounds);
// Ensure that the task stack bounds are already frozen to the larger source stack bounds
assertEquals(BOUNDS_FULL, mTarget.mStackBounds);
assertEquals(BOUNDS_FULL, offsetToZero(mTarget.mTaskBounds));
// Drive some animation updates, ensure that only the stack bounds change and the task
// bounds are frozen to the original stack bounds (adjusted for the offset)
boundsAnimator.onAnimationUpdate(mAnimator.getWithValue(0.5f));
assertNotEquals(BOUNDS_FULL, mTarget.mStackBounds);
assertEquals(BOUNDS_FULL, offsetToZero(mTarget.mTaskBounds));
boundsAnimator.onAnimationUpdate(mAnimator.getWithValue(1f));
assertNotEquals(BOUNDS_FULL, mTarget.mStackBounds);
assertEquals(BOUNDS_FULL, offsetToZero(mTarget.mTaskBounds));
// Finish the animation, ensure that it reaches the final bounds with the given state
boundsAnimator.end();
assertTrue(mTarget.mAnimationEnded);
assertEquals(BOUNDS_FLOATING, mTarget.mStackBounds);
assertNull(mTarget.mTaskBounds);
}
@UiThreadTest
@Test
public void testFloatingToFullscreenTransition() throws Exception {
// Create and start the animation
mTarget.reinitialize(BOUNDS_FULL, null);
final BoundsAnimator boundsAnimator = mController.animateBoundsImpl(mTarget, BOUNDS_FLOATING,
BOUNDS_FULL, DURATION, MOVE_TO_FULLSCREEN);
// Assert that when we are started, and that we are going to fullscreen
assertTrue(mTarget.mAnimationStarted);
assertTrue(mTarget.mAnimationStartedToFullscreen);
// Ensure that we update the PiP mode change with the new fullscreen bounds
assertTrue(mTarget.mUpdatedPictureInPictureModeWithBounds);
// Ensure that the task stack bounds are already frozen to the larger target stack bounds
assertEquals(BOUNDS_FLOATING, mTarget.mStackBounds);
assertEquals(BOUNDS_FULL, offsetToZero(mTarget.mTaskBounds));
// Drive some animation updates, ensure that only the stack bounds change and the task
// bounds are frozen to the original stack bounds (adjusted for the offset)
boundsAnimator.onAnimationUpdate(mAnimator.getWithValue(0.5f));
assertNotEquals(BOUNDS_FLOATING, mTarget.mStackBounds);
assertEquals(BOUNDS_FULL, offsetToZero(mTarget.mTaskBounds));
boundsAnimator.onAnimationUpdate(mAnimator.getWithValue(1f));
assertNotEquals(BOUNDS_FLOATING, mTarget.mStackBounds);
assertEquals(BOUNDS_FULL, offsetToZero(mTarget.mTaskBounds));
// Finish the animation, ensure that it reaches the final bounds with the given state
boundsAnimator.end();
assertTrue(mTarget.mAnimationEnded);
assertEquals(BOUNDS_FULL, mTarget.mStackBounds);
assertNull(mTarget.mTaskBounds);
}
@UiThreadTest
@Test
public void testInterruptAnimationFromUser() throws Exception {
// Create and start the animation
mTarget.reinitialize(BOUNDS_FULL, null);
final BoundsAnimator boundsAnimator = mController.animateBoundsImpl(mTarget, BOUNDS_FULL,
BOUNDS_FLOATING, DURATION, !MOVE_TO_FULLSCREEN);
// Cancel the animation on the next update from the user
mTarget.mRequestCancelAnimation = true;
mTarget.mBoundsUpdated = false;
boundsAnimator.onAnimationUpdate(mAnimator.getWithValue(0.5f));
// Ensure that we got no more updates after returning false and the bounds are not updated
// to the end value
assertFalse(mTarget.mBoundsUpdated);
assertNotEquals(BOUNDS_FLOATING, mTarget.mStackBounds);
assertNotEquals(BOUNDS_FLOATING, mTarget.mTaskBounds);
// Ensure that we received the animation end call
assertTrue(mTarget.mAnimationEnded);
}
@UiThreadTest
@Test
public void testCancelAnimationFromNewAnimationToExistingBounds() throws Exception {
// Create and start the animation
mTarget.reinitialize(BOUNDS_FULL, null);
final BoundsAnimator boundsAnimator = mController.animateBoundsImpl(mTarget, BOUNDS_FULL,
BOUNDS_FLOATING, DURATION, !MOVE_TO_FULLSCREEN);
// Drive some animation updates
boundsAnimator.onAnimationUpdate(mAnimator.getWithValue(0.5f));
// Cancel the animation as a restart to the same bounds
mTarget.reinitialize(null, null);
final BoundsAnimator altBoundsAnimator = mController.animateBoundsImpl(mTarget, BOUNDS_FULL,
BOUNDS_FLOATING, DURATION, !MOVE_TO_FULLSCREEN);
// Ensure the animator is the same
assertSame(boundsAnimator, altBoundsAnimator);
// Ensure we haven't restarted or finished the animation
assertFalse(mTarget.mAnimationStarted);
assertFalse(mTarget.mAnimationEnded);
// Ensure that we haven't tried to update the PiP mode
assertFalse(mTarget.mUpdatedPictureInPictureModeWithBounds);
}
@UiThreadTest
@Test
public void testCancelAnimationFromNewAnimationToNewBounds() throws Exception {
// Create and start the animation
mTarget.reinitialize(BOUNDS_FULL, null);
final BoundsAnimator boundsAnimator = mController.animateBoundsImpl(mTarget, BOUNDS_FULL,
BOUNDS_FLOATING, DURATION, !MOVE_TO_FULLSCREEN);
// Drive some animation updates
boundsAnimator.onAnimationUpdate(mAnimator.getWithValue(0.5f));
// Cancel the animation as a restart to new bounds
mTarget.reinitialize(null, null);
final BoundsAnimator altBoundsAnimator = mController.animateBoundsImpl(mTarget, BOUNDS_FULL,
BOUNDS_ALT_FLOATING, DURATION, !MOVE_TO_FULLSCREEN);
// Ensure the animator is not the same
assertNotSame(boundsAnimator, altBoundsAnimator);
// Ensure that we did not get an animation start/end callback
assertFalse(mTarget.mAnimationStarted);
assertFalse(mTarget.mAnimationEnded);
// Ensure that we haven't tried to update the PiP mode
assertFalse(mTarget.mUpdatedPictureInPictureModeWithBounds);
}
/**
* @return the bounds offset to zero/zero.
*/
private Rect offsetToZero(Rect bounds) {
mTmpRect.set(bounds);
mTmpRect.offsetTo(0, 0);
return mTmpRect;
}
}