Ensure that PiP mode changed callback if animation is interrupted

- If a PiP enter animation callback is interrupted, the activity is never
  actually put into PiP mode, and will never receive
  onPictureInPictureModeChanged(false) even if enterPictureInPictureMode()
  returns true due to the change being deduped (it was never in that mode).
  In this specific case, force a callback to be made to the app so that it
  has a signal that it is no longer in PiP mode.

Bug: 63749396
Test: bit FrameworksServicesTests:com.android.server.wm.BoundsAnimationControllerTests
Test: android.server.cts.ActivityManagerPinnedStackTests
Test: #testEnterPipInterruptedCallbacks

Change-Id: I301c70e4fb0f2175dd6d7b5feae065b41df2878d
This commit is contained in:
Winson Chung
2017-08-14 13:33:51 -07:00
parent 02584a23b3
commit ab76bbc211
9 changed files with 90 additions and 63 deletions

View File

@@ -659,14 +659,14 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
}
}
void updatePictureInPictureMode(Rect targetStackBounds) {
void updatePictureInPictureMode(Rect targetStackBounds, boolean forceUpdate) {
if (task == null || task.getStack() == null || app == null || app.thread == null) {
return;
}
final boolean inPictureInPictureMode = (task.getStackId() == PINNED_STACK_ID) &&
(targetStackBounds != null);
if (inPictureInPictureMode != mLastReportedPictureInPictureMode) {
if (inPictureInPictureMode != mLastReportedPictureInPictureMode || forceUpdate) {
// Picture-in-picture mode changes also trigger a multi-window mode change as well, so
// update that here in order
mLastReportedPictureInPictureMode = inPictureInPictureMode;
@@ -681,8 +681,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
private void schedulePictureInPictureModeChanged(Configuration overrideConfig) {
try {
app.thread.schedulePictureInPictureModeChanged(appToken,
mLastReportedPictureInPictureMode,
overrideConfig);
mLastReportedPictureInPictureMode, overrideConfig);
} catch (Exception e) {
// If process died, no one cares.
}

View File

@@ -4411,31 +4411,29 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return;
}
scheduleUpdatePictureInPictureModeIfNeeded(task, stack.mBounds, false /* immediate */);
scheduleUpdatePictureInPictureModeIfNeeded(task, stack.mBounds);
}
void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, Rect targetStackBounds,
boolean immediate) {
if (immediate) {
mHandler.removeMessages(REPORT_PIP_MODE_CHANGED_MSG);
for (int i = task.mActivities.size() - 1; i >= 0; i--) {
final ActivityRecord r = task.mActivities.get(i);
if (r.app != null && r.app.thread != null) {
r.updatePictureInPictureMode(targetStackBounds);
}
void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, Rect targetStackBounds) {
for (int i = task.mActivities.size() - 1; i >= 0; i--) {
final ActivityRecord r = task.mActivities.get(i);
if (r.app != null && r.app.thread != null) {
mPipModeChangedActivities.add(r);
}
} else {
for (int i = task.mActivities.size() - 1; i >= 0; i--) {
final ActivityRecord r = task.mActivities.get(i);
if (r.app != null && r.app.thread != null) {
mPipModeChangedActivities.add(r);
}
}
mPipModeChangedTargetStackBounds = targetStackBounds;
}
mPipModeChangedTargetStackBounds = targetStackBounds;
if (!mHandler.hasMessages(REPORT_PIP_MODE_CHANGED_MSG)) {
mHandler.sendEmptyMessage(REPORT_PIP_MODE_CHANGED_MSG);
if (!mHandler.hasMessages(REPORT_PIP_MODE_CHANGED_MSG)) {
mHandler.sendEmptyMessage(REPORT_PIP_MODE_CHANGED_MSG);
}
}
void updatePictureInPictureMode(TaskRecord task, Rect targetStackBounds, boolean forceUpdate) {
mHandler.removeMessages(REPORT_PIP_MODE_CHANGED_MSG);
for (int i = task.mActivities.size() - 1; i >= 0; i--) {
final ActivityRecord r = task.mActivities.get(i);
if (r.app != null && r.app.thread != null) {
r.updatePictureInPictureMode(targetStackBounds, forceUpdate);
}
}
}
@@ -4497,7 +4495,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
synchronized (mService) {
for (int i = mPipModeChangedActivities.size() - 1; i >= 0; i--) {
final ActivityRecord r = mPipModeChangedActivities.remove(i);
r.updatePictureInPictureMode(mPipModeChangedTargetStackBounds);
r.updatePictureInPictureMode(mPipModeChangedTargetStackBounds,
false /* forceUpdate */);
}
}
} break;

View File

@@ -91,15 +91,16 @@ class PinnedActivityStack extends ActivityStack<PinnedStackWindowController>
return mWindowContainerController.deferScheduleMultiWindowModeChanged();
}
public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) {
public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds,
boolean forceUpdate) {
// It is guaranteed that the activities requiring the update will be in the pinned stack at
// this point (either reparented before the animation into PiP, or before reparenting after
// the animation out of PiP)
synchronized(this) {
ArrayList<TaskRecord> tasks = getAllTasks();
for (int i = 0; i < tasks.size(); i++ ) {
mStackSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(tasks.get(i),
targetStackBounds, true /* immediate */);
mStackSupervisor.updatePictureInPictureMode(tasks.get(i), targetStackBounds,
forceUpdate);
}
}
}

View File

@@ -21,7 +21,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.animation.AnimationHandler;
import android.animation.AnimationHandler.AnimationFrameCallbackProvider;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
@@ -32,13 +31,11 @@ import android.os.IBinder;
import android.os.Debug;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.Choreographer;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.WindowManagerInternal;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -142,9 +139,6 @@ public class BoundsAnimationController {
// True if this this animation was canceled and will be replaced the another animation from
// the same {@link #BoundsAnimationTarget} target.
private boolean mSkipFinalResize;
// True if this animation replaced a previous animation of the same
// {@link #BoundsAnimationTarget} target.
private final boolean mSkipAnimationStart;
// True if this animation was canceled by the user, not as a part of a replacing animation
private boolean mSkipAnimationEnd;
@@ -159,6 +153,7 @@ public class BoundsAnimationController {
// Whether to schedule PiP mode changes on animation start/end
private @SchedulePipModeChangedState int mSchedulePipModeChangedState;
private @SchedulePipModeChangedState int mPrevSchedulePipModeChangedState;
// Depending on whether we are animating from
// a smaller to a larger size
@@ -171,14 +166,14 @@ public class BoundsAnimationController {
BoundsAnimator(BoundsAnimationTarget target, Rect from, Rect to,
@SchedulePipModeChangedState int schedulePipModeChangedState,
boolean moveFromFullscreen, boolean moveToFullscreen,
boolean replacingExistingAnimation) {
@SchedulePipModeChangedState int prevShedulePipModeChangedState,
boolean moveFromFullscreen, boolean moveToFullscreen) {
super();
mTarget = target;
mFrom.set(from);
mTo.set(to);
mSkipAnimationStart = replacingExistingAnimation;
mSchedulePipModeChangedState = schedulePipModeChangedState;
mPrevSchedulePipModeChangedState = prevShedulePipModeChangedState;
mMoveFromFullscreen = moveFromFullscreen;
mMoveToFullscreen = moveToFullscreen;
addUpdateListener(this);
@@ -200,7 +195,7 @@ public class BoundsAnimationController {
@Override
public void onAnimationStart(Animator animation) {
if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget
+ " mSkipAnimationStart=" + mSkipAnimationStart
+ " mPrevSchedulePipModeChangedState=" + mPrevSchedulePipModeChangedState
+ " mSchedulePipModeChangedState=" + mSchedulePipModeChangedState);
mFinishAnimationAfterTransition = false;
mTmpRect.set(mFrom.left, mFrom.top, mFrom.left + mFrozenTaskWidth,
@@ -210,18 +205,26 @@ public class BoundsAnimationController {
// running
updateBooster();
// Ensure that we have prepared the target for animation before
// we trigger any size changes, so it can swap surfaces
// in to appropriate modes, or do as it wishes otherwise.
if (!mSkipAnimationStart) {
// Ensure that we have prepared the target for animation before we trigger any size
// changes, so it can swap surfaces in to appropriate modes, or do as it wishes
// otherwise.
if (mPrevSchedulePipModeChangedState == NO_PIP_MODE_CHANGED_CALLBACKS) {
mTarget.onAnimationStart(mSchedulePipModeChangedState ==
SCHEDULE_PIP_MODE_CHANGED_ON_START);
SCHEDULE_PIP_MODE_CHANGED_ON_START, false /* forceUpdate */);
// When starting an animation from fullscreen, pause here and wait for the
// windows-drawn signal before we start the rest of the transition down into PiP.
if (mMoveFromFullscreen) {
pause();
}
} else if (mPrevSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END &&
mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
// We are replacing a running animation into PiP, but since it hasn't completed, the
// client will not currently receive any picture-in-picture mode change callbacks.
// However, we still need to report to them that they are leaving PiP, so this will
// force an update via a mode changed callback.
mTarget.onAnimationStart(true /* schedulePipModeChangedCallback */,
true /* forceUpdate */);
}
// Immediately update the task bounds if they have to become larger, but preserve
@@ -388,6 +391,8 @@ public class BoundsAnimationController {
boolean moveFromFullscreen, boolean moveToFullscreen) {
final BoundsAnimator existing = mRunningAnimations.get(target);
final boolean replacing = existing != null;
@SchedulePipModeChangedState int prevSchedulePipModeChangedState =
NO_PIP_MODE_CHANGED_CALLBACKS;
if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to
+ " schedulePipModeChangedState=" + schedulePipModeChangedState
@@ -403,6 +408,9 @@ public class BoundsAnimationController {
return existing;
}
// Save the previous state
prevSchedulePipModeChangedState = existing.mSchedulePipModeChangedState;
// Update the PiP callback states if we are replacing the animation
if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
@@ -428,7 +436,8 @@ public class BoundsAnimationController {
existing.cancel();
}
final BoundsAnimator animator = new BoundsAnimator(target, from, to,
schedulePipModeChangedState, moveFromFullscreen, moveToFullscreen, replacing);
schedulePipModeChangedState, prevSchedulePipModeChangedState,
moveFromFullscreen, moveToFullscreen);
mRunningAnimations.put(target, animator);
animator.setFloatValues(0f, 1f);
animator.setDuration((animationDuration != -1 ? animationDuration

View File

@@ -31,7 +31,7 @@ interface BoundsAnimationTarget {
* @param schedulePipModeChangedCallback whether or not to schedule the PiP mode changed
* callbacks
*/
void onAnimationStart(boolean schedulePipModeChangedCallback);
void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate);
/**
* Sets the size of the target (without any intermediate steps, like scheduling animation)

View File

@@ -202,10 +202,12 @@ public class PinnedStackWindowController extends StackWindowController {
*/
/** Calls directly into activity manager so window manager lock shouldn't held. */
public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) {
public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds,
boolean forceUpdate) {
if (mListener != null) {
PinnedStackWindowListener listener = (PinnedStackWindowListener) mListener;
listener.updatePictureInPictureModeForPinnedStackAnimation(targetStackBounds);
listener.updatePictureInPictureModeForPinnedStackAnimation(targetStackBounds,
forceUpdate);
}
}
}

View File

@@ -28,5 +28,6 @@ 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) {}
default void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds,
boolean forceUpdate) {}
}

View File

@@ -1498,7 +1498,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
@Override // AnimatesBounds
public void onAnimationStart(boolean schedulePipModeChangedCallback) {
public void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate) {
// Hold the lock since this is called from the BoundsAnimator running on the UiThread
synchronized (mService.mWindowMap) {
mBoundsAnimatingRequested = false;
@@ -1523,9 +1523,11 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
final PinnedStackWindowController controller =
(PinnedStackWindowController) getController();
if (schedulePipModeChangedCallback && controller != null) {
// We need to schedule the PiP mode change after the animation down, so use the
// final bounds
controller.updatePictureInPictureModeForPinnedStackAnimation(null);
// We need to schedule the PiP mode change before the animation up. It is possible
// in this case for the animation down to not have been completed, so always
// force-schedule and update to the client to ensure that it is notified that it
// is no longer in picture-in-picture mode
controller.updatePictureInPictureModeForPinnedStackAnimation(null, forceUpdate);
}
}
}
@@ -1553,7 +1555,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
// We need to schedule the PiP mode change after the animation down, so use the
// final bounds
controller.updatePictureInPictureModeForPinnedStackAnimation(
mBoundsAnimationTarget);
mBoundsAnimationTarget, false /* forceUpdate */);
}
if (finalStackSize != null) {

View File

@@ -126,6 +126,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
boolean mMovedToFullscreen;
boolean mAnimationStarted;
boolean mSchedulePipModeChangedOnStart;
boolean mForcePipModeChangedCallback;
boolean mAnimationEnded;
Rect mAnimationEndFinalStackBounds;
boolean mSchedulePipModeChangedOnEnd;
@@ -140,6 +141,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
mAnimationStarted = false;
mAnimationEnded = false;
mAnimationEndFinalStackBounds = null;
mForcePipModeChangedCallback = false;
mSchedulePipModeChangedOnStart = false;
mSchedulePipModeChangedOnEnd = false;
mStackBounds = from;
@@ -148,10 +150,11 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
}
@Override
public void onAnimationStart(boolean schedulePipModeChangedCallback) {
public void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate) {
mAwaitingAnimationStart = false;
mAnimationStarted = true;
mSchedulePipModeChangedOnStart = schedulePipModeChangedCallback;
mForcePipModeChangedCallback = forceUpdate;
}
@Override
@@ -232,7 +235,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
return this;
}
BoundsAnimationDriver restart(Rect to) {
BoundsAnimationDriver restart(Rect to, boolean expectStartedAndPipModeChangedCallback) {
if (mAnimator == null) {
throw new IllegalArgumentException("Call start() to start a new animation");
}
@@ -251,8 +254,15 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
assertSame(oldAnimator, mAnimator);
}
// No animation start for replacing animation
assertTrue(!mTarget.mAnimationStarted);
if (expectStartedAndPipModeChangedCallback) {
// Replacing animation with pending pip mode changed callback, ensure we update
assertTrue(mTarget.mAnimationStarted);
assertTrue(mTarget.mSchedulePipModeChangedOnStart);
assertTrue(mTarget.mForcePipModeChangedCallback);
} else {
// No animation start for replacing animation
assertTrue(!mTarget.mAnimationStarted);
}
mTarget.mAnimationStarted = true;
return this;
}
@@ -467,7 +477,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
.expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
.update(0.25f)
.restart(BOUNDS_FLOATING)
.restart(BOUNDS_FLOATING, false /* expectStartedAndPipModeChangedCallback */)
.end()
.expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
}
@@ -478,7 +488,8 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
.expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
.update(0.25f)
.restart(BOUNDS_SMALLER_FLOATING)
.restart(BOUNDS_SMALLER_FLOATING,
false /* expectStartedAndPipModeChangedCallback */)
.end()
.expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
}
@@ -486,10 +497,12 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
@UiThreadTest
@Test
public void testFullscreenToFloatingCancelFromAnimationToFullscreenBounds() throws Exception {
// When animating from fullscreen and the animation is interruped, we expect the animation
// start callback to be made, with a forced pip mode change callback
mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
.expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
.update(0.25f)
.restart(BOUNDS_FULL)
.restart(BOUNDS_FULL, true /* expectStartedAndPipModeChangedCallback */)
.end()
.expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
}
@@ -512,7 +525,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
.expectStarted(SCHEDULE_PIP_MODE_CHANGED)
.update(0.25f)
.restart(BOUNDS_FULL)
.restart(BOUNDS_FULL, false /* expectStartedAndPipModeChangedCallback */)
.end()
.expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
}
@@ -523,7 +536,8 @@ public class BoundsAnimationControllerTests extends WindowTestsBase {
mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
.expectStarted(SCHEDULE_PIP_MODE_CHANGED)
.update(0.25f)
.restart(BOUNDS_SMALLER_FLOATING)
.restart(BOUNDS_SMALLER_FLOATING,
false /* expectStartedAndPipModeChangedCallback */)
.end()
.expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
}