diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl index 7125232005c4e..6a2b723f905b7 100644 --- a/core/java/android/view/IRecentsAnimationController.aidl +++ b/core/java/android/view/IRecentsAnimationController.aidl @@ -18,6 +18,7 @@ package android.view; import android.app.ActivityManager; import android.view.IRemoteAnimationFinishedCallback; +import android.view.SurfaceControl; import android.graphics.GraphicBuffer; import android.window.PictureInPictureSurfaceTransaction; import android.window.TaskSnapshot; @@ -43,9 +44,10 @@ interface IRecentsAnimationController { * updated accordingly. This should be called before `finish` * @param taskId for which the leash should be updated * @param finishTransaction leash operations for the final transform. + * @param overlay the surface control for an overlay being shown above the pip (can be null) */ void setFinishTaskTransaction(int taskId, - in PictureInPictureSurfaceTransaction finishTransaction); + in PictureInPictureSurfaceTransaction finishTransaction, in SurfaceControl overlay); /** * Notifies to the system that the animation into Recents should end, and all leashes associated diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 26f98d8aeddb0..4c2863e4f5947 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -24,6 +24,9 @@ 425 + + 150 + true diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java index 6984ea458ccfd..006730d333eb3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java @@ -42,7 +42,7 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { private final SyncTransactionQueue mSyncQueue; - private final SparseArray mLeashByTaskId = new SparseArray<>(); + private final SparseArray mDataByTaskId = new SparseArray<>(); public FullscreenTaskListener(SyncTransactionQueue syncQueue) { mSyncQueue = syncQueue; @@ -50,14 +50,14 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { @Override public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { - if (mLeashByTaskId.get(taskInfo.taskId) != null) { + if (mDataByTaskId.get(taskInfo.taskId) != null) { throw new IllegalStateException("Task appeared more than once: #" + taskInfo.taskId); } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d", taskInfo.taskId); - mLeashByTaskId.put(taskInfo.taskId, leash); - if (Transitions.ENABLE_SHELL_TRANSITIONS) return; final Point positionInParent = taskInfo.positionInParent; + mDataByTaskId.put(taskInfo.taskId, new TaskData(leash, positionInParent)); + if (Transitions.ENABLE_SHELL_TRANSITIONS) return; mSyncQueue.runInSync(t -> { // Reset several properties back to fullscreen (PiP, for example, leaves all these // properties in a bad state). @@ -72,45 +72,57 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { @Override public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { if (Transitions.ENABLE_SHELL_TRANSITIONS) return; - final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId); + final TaskData data = mDataByTaskId.get(taskInfo.taskId); final Point positionInParent = taskInfo.positionInParent; - mSyncQueue.runInSync(t -> { - // Reset several properties back. For instance, when an Activity enters PiP with - // multiple activities in the same task, a new task will be created from that Activity - // and we want reset the leash of the original task. - t.setPosition(leash, positionInParent.x, positionInParent.y); - t.setWindowCrop(leash, null); - }); + if (!positionInParent.equals(data.positionInParent)) { + data.positionInParent.set(positionInParent.x, positionInParent.y); + mSyncQueue.runInSync(t -> { + t.setPosition(data.surface, positionInParent.x, positionInParent.y); + }); + } } @Override public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { - if (mLeashByTaskId.get(taskInfo.taskId) == null) { + if (mDataByTaskId.get(taskInfo.taskId) == null) { Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId); return; } - mLeashByTaskId.remove(taskInfo.taskId); + mDataByTaskId.remove(taskInfo.taskId); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d", taskInfo.taskId); } @Override public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { - if (!mLeashByTaskId.contains(taskId)) { + if (!mDataByTaskId.contains(taskId)) { throw new IllegalArgumentException("There is no surface for taskId=" + taskId); } - b.setParent(mLeashByTaskId.get(taskId)); + b.setParent(mDataByTaskId.get(taskId).surface); } @Override public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + this); - pw.println(innerPrefix + mLeashByTaskId.size() + " Tasks"); + pw.println(innerPrefix + mDataByTaskId.size() + " Tasks"); } @Override public String toString() { return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN); } + + /** + * Per-task data for each managed task. + */ + private static class TaskData { + public final SurfaceControl surface; + public final Point positionInParent; + + public TaskData(SurfaceControl surface, Point positionInParent) { + this.surface = surface; + this.positionInParent = positionInParent; + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl index a6ffa6e445846..ddc85f758916a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl @@ -17,6 +17,7 @@ package com.android.wm.shell.pip; import android.app.PictureInPictureParams; +import android.view.SurfaceControl; import android.content.ComponentName; import android.content.pm.ActivityInfo; import android.graphics.Rect; @@ -48,8 +49,10 @@ interface IPip { * * @param componentName ComponentName represents the Activity * @param destinationBounds the destination bounds the PiP window lands into + * @param overlay an optional overlay to fade out after entering PiP */ - oneway void stopSwipePipToHome(in ComponentName componentName, in Rect destinationBounds) = 2; + oneway void stopSwipePipToHome(in ComponentName componentName, in Rect destinationBounds, + in SurfaceControl overlay) = 2; /** * Sets listener to get pinned stack animation callbacks. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 0633330dc8303..b3528712db97e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -147,6 +147,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private final PipUiEventLogger mPipUiEventLoggerLogger; private final int mEnterAnimationDuration; private final int mExitAnimationDuration; + private final int mCrossFadeAnimationDuration; private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; private final Optional mSplitScreenOptional; protected final ShellTaskOrganizer mTaskOrganizer; @@ -257,6 +258,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, */ private boolean mInSwipePipToHomeTransition; + /** + * An optional overlay used to mask content changing between an app in/out of PiP, only set if + * {@link #mInSwipePipToHomeTransition} is true. + */ + private SurfaceControl mSwipePipToHomeOverlay; + public PipTaskOrganizer(Context context, @NonNull SyncTransactionQueue syncTransactionQueue, @NonNull PipBoundsState pipBoundsState, @@ -280,6 +287,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, .getInteger(R.integer.config_pipEnterAnimationDuration); mExitAnimationDuration = context.getResources() .getInteger(R.integer.config_pipExitAnimationDuration); + mCrossFadeAnimationDuration = context.getResources() + .getInteger(R.integer.config_pipCrossfadeAnimationDuration); mSurfaceTransactionHelper = surfaceTransactionHelper; mPipAnimationController = pipAnimationController; mPipUiEventLoggerLogger = pipUiEventLogger; @@ -350,10 +359,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, * Callback when launcher finishes swipe-pip-to-home operation. * Expect {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)} afterwards. */ - public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) { + public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds, + SurfaceControl overlay) { // do nothing if there is no startSwipePipToHome being called before if (mInSwipePipToHomeTransition) { mPipBoundsState.setBounds(destinationBounds); + mSwipePipToHomeOverlay = overlay; } } @@ -599,6 +610,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private void onEndOfSwipePipToHomeTransition() { final Rect destinationBounds = mPipBoundsState.getBounds(); + final SurfaceControl swipeToHomeOverlay = mSwipePipToHomeOverlay; final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); mSurfaceTransactionHelper.resetScale(tx, mLeash, destinationBounds); mSurfaceTransactionHelper.crop(tx, mLeash, destinationBounds); @@ -607,8 +619,14 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // Ensure menu's settled in its final bounds first. finishResizeForMenu(destinationBounds); sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); + + // Remove the swipe to home overlay + if (swipeToHomeOverlay != null) { + fadeOutAndRemoveOverlay(swipeToHomeOverlay); + } }, tx); mInSwipePipToHomeTransition = false; + mSwipePipToHomeOverlay = null; } private void applyEnterPipSyncTransaction(Rect destinationBounds, Runnable runnable, @@ -1139,25 +1157,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSurfaceTransactionHelper.scale(t, snapshotSurface, snapshotSrc, snapshotDest); // Start animation to fade out the snapshot. - final ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 0.0f); - animator.setDuration(mEnterAnimationDuration); - animator.addUpdateListener(animation -> { - final float alpha = (float) animation.getAnimatedValue(); - final SurfaceControl.Transaction transaction = - mSurfaceControlTransactionFactory.getTransaction(); - transaction.setAlpha(snapshotSurface, alpha); - transaction.apply(); - }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - final SurfaceControl.Transaction tx = - mSurfaceControlTransactionFactory.getTransaction(); - tx.remove(snapshotSurface); - tx.apply(); - } - }); - animator.start(); + fadeOutAndRemoveOverlay(snapshotSurface); }); } else { applyFinishBoundsResize(wct, direction); @@ -1300,6 +1300,35 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return true; } + /** + * Fades out and removes an overlay surface. + */ + private void fadeOutAndRemoveOverlay(SurfaceControl surface) { + if (surface == null) { + return; + } + + final ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 0.0f); + animator.setDuration(mCrossFadeAnimationDuration); + animator.addUpdateListener(animation -> { + final float alpha = (float) animation.getAnimatedValue(); + final SurfaceControl.Transaction transaction = + mSurfaceControlTransactionFactory.getTransaction(); + transaction.setAlpha(surface, alpha); + transaction.apply(); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + final SurfaceControl.Transaction tx = + mSurfaceControlTransactionFactory.getTransaction(); + tx.remove(surface); + tx.apply(); + } + }); + animator.start(); + } + /** * Dumps internal states. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 91e38872e3d0b..f80b161be42ac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -44,6 +44,7 @@ import android.util.Pair; import android.util.Size; import android.util.Slog; import android.view.DisplayInfo; +import android.view.SurfaceControl; import android.view.WindowManagerGlobal; import android.window.WindowContainerTransaction; @@ -557,8 +558,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb return entryBounds; } - private void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) { - mPipTaskOrganizer.stopSwipePipToHome(componentName, destinationBounds); + private void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds, + SurfaceControl overlay) { + mPipTaskOrganizer.stopSwipePipToHome(componentName, destinationBounds, overlay); } @Override @@ -850,10 +852,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override - public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) { + public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds, + SurfaceControl overlay) { executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome", (controller) -> { - controller.stopSwipePipToHome(componentName, destinationBounds); + controller.stopSwipePipToHome(componentName, destinationBounds, overlay); }); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java index 1cc488fc6df07..700ec49fc795c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java @@ -19,6 +19,7 @@ package com.android.systemui.shared.system; import android.os.RemoteException; import android.util.Log; import android.view.IRecentsAnimationController; +import android.view.SurfaceControl; import android.window.PictureInPictureSurfaceTransaction; import android.window.TaskSnapshot; @@ -76,11 +77,13 @@ public class RecentsAnimationControllerCompat { * updated accordingly. This should be called before `finish` * @param taskId Task id of the Activity in PiP mode. * @param finishTransaction leash operations for the final transform. + * @param overlay the surface control for an overlay being shown above the pip (can be null) */ public void setFinishTaskTransaction(int taskId, - PictureInPictureSurfaceTransaction finishTransaction) { + PictureInPictureSurfaceTransaction finishTransaction, + SurfaceControl overlay) { try { - mAnimationController.setFinishTaskTransaction(taskId, finishTransaction); + mAnimationController.setFinishTaskTransaction(taskId, finishTransaction, overlay); } catch (RemoteException e) { Log.d(TAG, "Failed to set finish task bounds", e); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java index 5708855b0a4f9..1729997ac8730 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java @@ -236,9 +236,9 @@ public class RemoteTransitionCompat implements Parcelable { } @Override public void setFinishTaskTransaction(int taskId, - PictureInPictureSurfaceTransaction finishTransaction) { + PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) { if (mWrapped != null) { - mWrapped.setFinishTaskTransaction(taskId, finishTransaction); + mWrapped.setFinishTaskTransaction(taskId, finishTransaction, overlay); } } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index b27278c92679e..53e39872af507 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -231,7 +231,8 @@ public class RecentsAnimationController implements DeathRecipient { @Override public void setFinishTaskTransaction(int taskId, - PictureInPictureSurfaceTransaction finishTransaction) { + PictureInPictureSurfaceTransaction finishTransaction, + SurfaceControl overlay) { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "setFinishTaskTransaction(%d): transaction=%s", taskId, finishTransaction); final long token = Binder.clearCallingIdentity(); @@ -241,6 +242,7 @@ public class RecentsAnimationController implements DeathRecipient { final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i); if (taskAdapter.mTask.mTaskId == taskId) { taskAdapter.mFinishTransaction = finishTransaction; + taskAdapter.mFinishOverlay = overlay; break; } } @@ -512,7 +514,6 @@ public class RecentsAnimationController implements DeathRecipient { void removeAnimation(TaskAnimationAdapter taskAdapter) { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "removeAnimation(%d)", taskAdapter.mTask.mTaskId); - taskAdapter.mTask.setCanAffectSystemUiFlags(true); taskAdapter.mCapturedFinishCallback.onAnimationFinished(taskAdapter.mLastAnimationType, taskAdapter); mPendingAnimations.remove(taskAdapter); @@ -1100,6 +1101,8 @@ public class RecentsAnimationController implements DeathRecipient { private final Rect mLocalBounds = new Rect(); // The final surface transaction when animation is finished. private PictureInPictureSurfaceTransaction mFinishTransaction; + // An overlay used to mask the content as an app goes into PIP + private SurfaceControl mFinishOverlay; TaskAnimationAdapter(Task task, boolean isRecentTaskInvisible) { mTask = task; @@ -1137,20 +1140,38 @@ public class RecentsAnimationController implements DeathRecipient { void onCleanup() { if (mFinishTransaction != null) { final Transaction pendingTransaction = mTask.getPendingTransaction(); + + // Reparent the overlay + if (mFinishOverlay != null) { + pendingTransaction.reparent(mFinishOverlay, mTask.mSurfaceControl); + } + + // Transfer the transform from the leash to the task PictureInPictureSurfaceTransaction.apply(mFinishTransaction, mTask.mSurfaceControl, pendingTransaction); - mTask.setLastRecentsAnimationTransaction(mFinishTransaction); + mTask.setLastRecentsAnimationTransaction(mFinishTransaction, mFinishOverlay); if (mDisplayContent.isFixedRotationLaunchingApp(mTargetActivityRecord)) { // The transaction is needed for position when rotating the display. mDisplayContent.mPinnedTaskController.setEnterPipTransaction( mFinishTransaction); } mFinishTransaction = null; + mFinishOverlay = null; pendingTransaction.apply(); + + // In the case where we are transferring the transform to the task in preparation + // for entering PIP, we disable the task being able to affect sysui flags otherwise + // it may cause a flash + if (mTask.getActivityType() != mTargetActivityType) { + mTask.setCanAffectSystemUiFlags(false); + } } else if (!mTask.isAttached()) { // Apply the task's pending transaction in case it is detached and its transaction // is not reachable. mTask.getPendingTransaction().apply(); + + // Reset whether this task can affect the sysui flags + mTask.setCanAffectSystemUiFlags(true); } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 0879ddd53a395..26dcf00cd6b5f 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2139,7 +2139,8 @@ class RootWindowContainer extends WindowContainer // Move the last recents animation transaction from original task to the new one. if (task.mLastRecentsAnimationTransaction != null) { rootTask.setLastRecentsAnimationTransaction( - task.mLastRecentsAnimationTransaction); + task.mLastRecentsAnimationTransaction, + task.mLastRecentsAnimationOverlay); task.clearLastRecentsAnimationTransaction(); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 20995656f6938..e120754498a71 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -482,6 +482,9 @@ class Task extends WindowContainer { // Do not forget to reset this after reparenting. // TODO: remove this once the recents animation is moved to the Shell PictureInPictureSurfaceTransaction mLastRecentsAnimationTransaction; + // The content overlay to be applied with mLastRecentsAnimationTransaction + // TODO: remove this once the recents animation is moved to the Shell + SurfaceControl mLastRecentsAnimationOverlay; static final int LAYER_RANK_INVISIBLE = -1; // Ranking (from top) of this task among all visible tasks. (-1 means it's not visible) @@ -5383,6 +5386,10 @@ class Task extends WindowContainer { : WINDOWING_MODE_FULLSCREEN; } if (currentMode == WINDOWING_MODE_PINNED) { + // In the case that we've disabled affecting the SysUI flags as a part of seamlessly + // transferring the transform on the leash to the task, reset this state once we've + // actually entered pip + setCanAffectSystemUiFlags(true); mRootWindowContainer.notifyActivityPipModeChanged(null); } if (likelyResolvedMode == WINDOWING_MODE_PINNED @@ -7616,22 +7623,28 @@ class Task extends WindowContainer { reparent(newParent, onTop ? POSITION_TOP : POSITION_BOTTOM); } - void setLastRecentsAnimationTransaction( - @NonNull PictureInPictureSurfaceTransaction transaction) { + void setLastRecentsAnimationTransaction(@NonNull PictureInPictureSurfaceTransaction transaction, + @Nullable SurfaceControl overlay) { mLastRecentsAnimationTransaction = new PictureInPictureSurfaceTransaction(transaction); + mLastRecentsAnimationOverlay = overlay; } void clearLastRecentsAnimationTransaction() { mLastRecentsAnimationTransaction = null; + mLastRecentsAnimationOverlay = null; // reset also the transform introduced by mLastRecentsAnimationTransaction getPendingTransaction().setMatrix(mSurfaceControl, Matrix.IDENTITY_MATRIX, new float[9]); } void maybeApplyLastRecentsAnimationTransaction() { if (mLastRecentsAnimationTransaction != null) { + if (mLastRecentsAnimationOverlay != null) { + getPendingTransaction().reparent(mLastRecentsAnimationOverlay, mSurfaceControl); + } PictureInPictureSurfaceTransaction.apply(mLastRecentsAnimationTransaction, mSurfaceControl, getPendingTransaction()); mLastRecentsAnimationTransaction = null; + mLastRecentsAnimationOverlay = null; } }