diff options
| author | 2024-02-20 11:20:06 -0800 | |
|---|---|---|
| committer | 2024-02-20 11:37:57 -0800 | |
| commit | 1895b133b9505a0b9075a2c316c2048e1df4d773 (patch) | |
| tree | 6bef03897d27352df16197f595b497e33d741e02 | |
| parent | 2183a65d5602af7537b1ec01966def4ed0a7cf55 (diff) | |
Implement auto-enter PiP2 in gesture nav [1/N]
Implement auto-enter PiP in PiP2 while in gesture
navigation mode.
Launcher handles the animation as a part of the first
TO_FRONT transition (no changes there).
For now only startSwipePipToHome() and stopSwipePipToHome()
IPC calls are set up to let launcher start PiP animation.
The plan is to add the pip animation listener to properly
update Launcher state in [2/N] CL of this series (similar to pip1).
This is followed by a call to requestStartTransition()
-> PipTransition#handleRequest() for TRANSIT_PIP type of a transition.
The flow here overlaps with other enter PiP2 flows, as we use
WindowContainerTransaction#movePipActivityToPinnedRootTask()
to update the WM state with the new bounds (unlike in PiP1).
Then, we recognize that this is a swipe PiP to Home animation
in PipTransition#startAnimation() and we carry out a no-op animation
essentially finishing TRANSIT_PIP. The plan is to continue overlay
animation here as well in later CLs of this series. So overlay is
not animating properly for now - hence testing would require non-null
srcRechHint in PictureInPictureParams.
Bug: 325481148
Test: swipe auto-enter activity (srcRectHint != null) to PiP
Change-Id: If171678181b8b9e0006c74fe41cd441ec25a10b4
4 files changed, 180 insertions, 6 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 8eecf1c58db0..458ea05e620d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -74,13 +74,18 @@ public abstract class Pip2Module { ShellController shellController, DisplayController displayController, DisplayInsetsController displayInsetsController, - PipDisplayLayoutState pipDisplayLayoutState) { + PipBoundsState pipBoundsState, + PipBoundsAlgorithm pipBoundsAlgorithm, + PipDisplayLayoutState pipDisplayLayoutState, + PipScheduler pipScheduler, + @ShellMainThread ShellExecutor mainExecutor) { if (!PipUtils.isPip2ExperimentEnabled()) { return Optional.empty(); } else { return Optional.ofNullable(PipController.create( context, shellInit, shellController, displayController, displayInsetsController, - pipDisplayLayoutState)); + pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, pipScheduler, + mainExecutor)); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index 186cb615f4ec..e73a85003881 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -18,14 +18,31 @@ package com.android.wm.shell.pip2.phone; import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; +import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; +import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP; + +import android.app.PictureInPictureParams; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.graphics.Rect; import android.view.InsetsState; +import android.view.SurfaceControl; + +import androidx.annotation.BinderThread; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.ExternalInterfaceBinder; +import com.android.wm.shell.common.RemoteCallable; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.IPip; +import com.android.wm.shell.common.pip.IPipAnimationListener; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -37,32 +54,54 @@ import com.android.wm.shell.sysui.ShellInit; * Manages the picture-in-picture (PIP) UI and states for Phones. */ public class PipController implements ConfigurationChangeListener, - DisplayController.OnDisplaysChangedListener { + DisplayController.OnDisplaysChangedListener, RemoteCallable<PipController> { private static final String TAG = PipController.class.getSimpleName(); private Context mContext; private ShellController mShellController; private DisplayController mDisplayController; private DisplayInsetsController mDisplayInsetsController; + private PipBoundsState mPipBoundsState; + private PipBoundsAlgorithm mPipBoundsAlgorithm; private PipDisplayLayoutState mPipDisplayLayoutState; + private PipScheduler mPipScheduler; + private ShellExecutor mMainExecutor; private PipController(Context context, ShellInit shellInit, ShellController shellController, DisplayController displayController, DisplayInsetsController displayInsetsController, - PipDisplayLayoutState pipDisplayLayoutState) { + PipBoundsState pipBoundsState, + PipBoundsAlgorithm pipBoundsAlgorithm, + PipDisplayLayoutState pipDisplayLayoutState, + PipScheduler pipScheduler, + ShellExecutor mainExecutor) { mContext = context; mShellController = shellController; mDisplayController = displayController; mDisplayInsetsController = displayInsetsController; + mPipBoundsState = pipBoundsState; + mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipDisplayLayoutState = pipDisplayLayoutState; + mPipScheduler = pipScheduler; + mMainExecutor = mainExecutor; if (PipUtils.isPip2ExperimentEnabled()) { shellInit.addInitCallback(this::onInit, this); } } + @Override + public Context getContext() { + return mContext; + } + + @Override + public ShellExecutor getRemoteCallExecutor() { + return mMainExecutor; + } + private void onInit() { // Ensure that we have the display info in case we get calls to update the bounds before the // listener calls back @@ -80,6 +119,10 @@ public class PipController implements ConfigurationChangeListener, .getDisplayLayout(mPipDisplayLayoutState.getDisplayId())); } }); + + // Allow other outside processes to bind to PiP controller using the key below. + mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP, + this::createExternalInterface, this); } /** @@ -90,16 +133,24 @@ public class PipController implements ConfigurationChangeListener, ShellController shellController, DisplayController displayController, DisplayInsetsController displayInsetsController, - PipDisplayLayoutState pipDisplayLayoutState) { + PipBoundsState pipBoundsState, + PipBoundsAlgorithm pipBoundsAlgorithm, + PipDisplayLayoutState pipDisplayLayoutState, + PipScheduler pipScheduler, + ShellExecutor mainExecutor) { if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Device doesn't support Pip feature", TAG); return null; } return new PipController(context, shellInit, shellController, displayController, - displayInsetsController, pipDisplayLayoutState); + displayInsetsController, pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, + pipScheduler, mainExecutor); } + private ExternalInterfaceBinder createExternalInterface() { + return new IPipImpl(this); + } @Override public void onConfigurationChanged(Configuration newConfiguration) { @@ -130,4 +181,86 @@ public class PipController implements ConfigurationChangeListener, private void onDisplayChanged(DisplayLayout layout) { mPipDisplayLayoutState.setDisplayLayout(layout); } + + private Rect getSwipePipToHomeBounds(ComponentName componentName, ActivityInfo activityInfo, + PictureInPictureParams pictureInPictureParams, + int launcherRotation, Rect hotseatKeepClearArea) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "getSwipePipToHomeBounds: %s", componentName); + mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, pictureInPictureParams, + mPipBoundsAlgorithm); + return mPipBoundsAlgorithm.getEntryDestinationBounds(); + } + + private void onSwipePipToHomeAnimationStart(int taskId, ComponentName componentName, + Rect destinationBounds, SurfaceControl overlay, Rect appBounds) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "onSwipePipToHomeAnimationStart: %s", componentName); + mPipScheduler.setInSwipePipToHomeTransition(true); + // TODO: cache the overlay if provided for reparenting later. + } + + /** + * The interface for calls from outside the host process. + */ + @BinderThread + private static class IPipImpl extends IPip.Stub implements ExternalInterfaceBinder { + private PipController mController; + + IPipImpl(PipController controller) { + mController = controller; + } + + /** + * Invalidates this instance, preventing future calls from updating the controller. + */ + @Override + public void invalidate() { + mController = null; + } + + @Override + public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, + PictureInPictureParams pictureInPictureParams, int launcherRotation, + Rect keepClearArea) { + Rect[] result = new Rect[1]; + executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome", + (controller) -> { + result[0] = controller.getSwipePipToHomeBounds(componentName, activityInfo, + pictureInPictureParams, launcherRotation, keepClearArea); + }, true /* blocking */); + return result[0]; + } + + @Override + public void stopSwipePipToHome(int taskId, ComponentName componentName, + Rect destinationBounds, SurfaceControl overlay, Rect appBounds) { + if (overlay != null) { + overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome"); + } + executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome", + (controller) -> controller.onSwipePipToHomeAnimationStart( + taskId, componentName, destinationBounds, overlay, appBounds)); + } + + @Override + public void abortSwipePipToHome(int taskId, ComponentName componentName) {} + + @Override + public void setShelfHeight(boolean visible, int height) {} + + @Override + public void setLauncherKeepClearAreaHeight(boolean visible, int height) {} + + @Override + public void setLauncherAppIconSize(int iconSizePx) {} + + @Override + public void setPipAnimationListener(IPipAnimationListener listener) { + // TODO: set a proper animation listener to update the Launcher state as needed. + } + + @Override + public void setPipAnimationTypeToAlpha() {} + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index 57b73b3019f4..895c793007a5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -63,6 +63,9 @@ public class PipScheduler { @Nullable private SurfaceControl mPinnedTaskLeash; + // true if Launcher has started swipe PiP to home animation + private boolean mInSwipePipToHomeTransition; + /** * Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell. * This is used for a broadcast receiver to resolve intents. This should be removed once @@ -168,6 +171,14 @@ public class PipScheduler { mPipTransitionController.startResizeTransition(wct, onFinishResizeCallback); } + void setInSwipePipToHomeTransition(boolean inSwipePipToHome) { + mInSwipePipToHomeTransition = true; + } + + boolean isInSwipePipToHomeTransition() { + return mInSwipePipToHomeTransition; + } + void onExitPip() { mPipTaskToken = null; mPinnedTaskLeash = null; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index fbf4d13a0c19..dfb04758c851 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -152,6 +152,12 @@ public class PipTransition extends PipTransitionController { @NonNull Transitions.TransitionFinishCallback finishCallback) { if (transition == mEnterTransition) { mEnterTransition = null; + if (mPipScheduler.isInSwipePipToHomeTransition()) { + // If this is the second transition as a part of swipe PiP to home cuj, + // handle this transition as a special case with no-op animation. + return handleSwipePipToHomeTransition(info, startTransaction, finishTransaction, + finishCallback); + } if (isLegacyEnter(info)) { // If this is a legacy-enter-pip (auto-enter is off and PiP activity went to pause), // then we should run an ALPHA type (cross-fade) animation. @@ -207,6 +213,25 @@ public class PipTransition extends PipTransitionController { return true; } + private boolean handleSwipePipToHomeTransition(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + TransitionInfo.Change pipChange = getPipChange(info); + if (pipChange == null) { + return false; + } + mPipScheduler.setInSwipePipToHomeTransition(false); + mPipTaskToken = pipChange.getContainer(); + + // cache the PiP task token and leash + mPipScheduler.setPipTaskToken(mPipTaskToken); + + startTransaction.apply(); + finishCallback.onTransitionFinished(null); + return true; + } + private boolean startBoundsTypeEnterAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, |