Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +3 −2 Original line number Diff line number Diff line Loading @@ -93,8 +93,9 @@ public abstract class Pip2Module { @Provides static PipScheduler providePipScheduler(Context context, PipBoundsState pipBoundsState, @ShellMainThread ShellExecutor mainExecutor) { return new PipScheduler(context, pipBoundsState, mainExecutor); @ShellMainThread ShellExecutor mainExecutor, ShellTaskOrganizer shellTaskOrganizer) { return new PipScheduler(context, pipBoundsState, mainExecutor, shellTaskOrganizer); } @WMSingleton Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +2 −2 Original line number Diff line number Diff line Loading @@ -245,9 +245,9 @@ public class PipController implements ConfigurationChangeListener, Rect destinationBounds, SurfaceControl overlay, Rect appBounds) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "onSwipePipToHomeAnimationStart: %s", componentName); mPipScheduler.setInSwipePipToHomeTransition(true); mPipScheduler.onSwipePipToHomeAnimationStart(taskId, componentName, destinationBounds, overlay, appBounds); mPipRecentsAnimationListener.onPipAnimationStarted(); // TODO: cache the overlay if provided for reparenting later. } // Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +41 −2 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; Loading @@ -30,9 +31,11 @@ import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipUtils; Loading @@ -52,6 +55,7 @@ public class PipScheduler { private final Context mContext; private final PipBoundsState mPipBoundsState; private final ShellExecutor mMainExecutor; private final ShellTaskOrganizer mShellTaskOrganizer; private PipSchedulerReceiver mSchedulerReceiver; private PipTransitionController mPipTransitionController; Loading @@ -66,6 +70,16 @@ public class PipScheduler { // true if Launcher has started swipe PiP to home animation private boolean mInSwipePipToHomeTransition; // Overlay leash potentially used during swipe PiP to home transition; // if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid. @Nullable SurfaceControl mSwipePipToHomeOverlay; // App bounds used when as a starting point to swipe PiP to home animation in Launcher; // these are also used to calculate the app icon overlay buffer size. @NonNull final Rect mSwipePipToHomeAppBounds = new Rect(); /** * 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 Loading Loading @@ -101,11 +115,14 @@ public class PipScheduler { } } public PipScheduler(Context context, PipBoundsState pipBoundsState, ShellExecutor mainExecutor) { public PipScheduler(Context context, PipBoundsState pipBoundsState, ShellExecutor mainExecutor, ShellTaskOrganizer shellTaskOrganizer) { mContext = context; mPipBoundsState = pipBoundsState; mMainExecutor = mainExecutor; mShellTaskOrganizer = shellTaskOrganizer; if (PipUtils.isPip2ExperimentEnabled()) { // temporary broadcast receiver to initiate exit PiP via expand Loading @@ -115,6 +132,10 @@ public class PipScheduler { } } ShellExecutor getMainExecutor() { return mMainExecutor; } void setPipTransitionController(PipTransitionController pipTransitionController) { mPipTransitionController = pipTransitionController; } Loading Loading @@ -171,6 +192,24 @@ public class PipScheduler { mPipTransitionController.startResizeTransition(wct, onFinishResizeCallback); } void onSwipePipToHomeAnimationStart(int taskId, ComponentName componentName, Rect destinationBounds, SurfaceControl overlay, Rect appBounds) { mInSwipePipToHomeTransition = true; mSwipePipToHomeOverlay = overlay; mSwipePipToHomeAppBounds.set(appBounds); if (overlay != null) { // Shell transitions might use a root animation leash, which will be removed when // the Recents transition is finished. Launcher attaches the overlay leash to this // animation target leash; thus, we need to reparent it to the actual Task surface now. // PipTransition is responsible to fade it out and cleanup when finishing the enter PIP // transition. SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); mShellTaskOrganizer.reparentChildSurfaceToTask(taskId, overlay, tx); tx.setLayer(overlay, Integer.MAX_VALUE); tx.apply(); } } void setInSwipePipToHomeTransition(boolean inSwipePipToHome) { mInSwipePipToHomeTransition = inSwipePipToHome; } Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +64 −3 Original line number Diff line number Diff line Loading @@ -24,6 +24,9 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.PictureInPictureParams; Loading @@ -44,6 +47,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipMenuController; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.PipContentOverlay; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; Loading @@ -55,6 +59,11 @@ import java.util.function.Consumer; */ public class PipTransition extends PipTransitionController { private static final String TAG = PipTransition.class.getSimpleName(); /** * The fixed start delay in ms when fading out the content overlay from bounds animation. * The fadeout animation is guaranteed to start after the client has drawn under the new config. */ private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 400; private final Context mContext; private final PipScheduler mPipScheduler; Loading Loading @@ -230,10 +239,13 @@ public class PipTransition extends PipTransitionController { PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams; Rect srcRectHint = params.getSourceRectHint(); Rect startBounds = pipChange.getStartAbsBounds(); Rect destinationBounds = pipChange.getEndAbsBounds(); WindowContainerTransaction finishWct = new WindowContainerTransaction(); if (PipBoundsAlgorithm.isSourceRectHintValidForEnterPip(srcRectHint, destinationBounds)) { float scale = (float) destinationBounds.width() / srcRectHint.width(); final float scale = (float) destinationBounds.width() / srcRectHint.width(); startTransaction.setWindowCrop(pipLeash, srcRectHint); startTransaction.setPosition(pipLeash, destinationBounds.left - srcRectHint.left * scale, Loading @@ -244,13 +256,62 @@ public class PipTransition extends PipTransitionController { // in multi-activity case, reparenting yields new reset scales coming from pinned task. startTransaction.setScale(pipLeash, scale, scale); } else { // TODO(b/325481148): handle the case with invalid srcRectHint (using overlay). final float scaleX = (float) destinationBounds.width() / startBounds.width(); final float scaleY = (float) destinationBounds.height() / startBounds.height(); final int overlaySize = PipContentOverlay.PipAppIconOverlay .getOverlaySize(mPipScheduler.mSwipePipToHomeAppBounds, destinationBounds); SurfaceControl overlayLeash = mPipScheduler.mSwipePipToHomeOverlay; startTransaction.setPosition(pipLeash, destinationBounds.left, destinationBounds.top) .setScale(pipLeash, scaleX, scaleY) .setWindowCrop(pipLeash, startBounds) .reparent(overlayLeash, pipLeash) .setLayer(overlayLeash, Integer.MAX_VALUE); if (mPipTaskToken != null) { SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(), this::onClientDrawAtTransitionEnd) .setScale(overlayLeash, 1f, 1f) .setPosition(overlayLeash, (destinationBounds.width() - overlaySize) / 2f, (destinationBounds.height() - overlaySize) / 2f); finishWct.setBoundsChangeTransaction(mPipTaskToken, tx); } } startTransaction.apply(); finishCallback.onTransitionFinished(null); // Note that finishWct should be free of any actual WM state changes; we are using // it for syncing with the client draw after delayed configuration changes are dispatched. finishCallback.onTransitionFinished(finishWct.isEmpty() ? null : finishWct); return true; } private void onClientDrawAtTransitionEnd() { startOverlayFadeoutAnimation(); } private void startOverlayFadeoutAnimation() { ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f); animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); tx.remove(mPipScheduler.mSwipePipToHomeOverlay); tx.apply(); mPipScheduler.mSwipePipToHomeOverlay = null; } }); animator.addUpdateListener(animation -> { float alpha = (float) animation.getAnimatedValue(); SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); tx.setAlpha(mPipScheduler.mSwipePipToHomeOverlay, alpha).apply(); }); animator.start(); } private boolean startBoundsTypeEnterAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +3 −2 Original line number Diff line number Diff line Loading @@ -93,8 +93,9 @@ public abstract class Pip2Module { @Provides static PipScheduler providePipScheduler(Context context, PipBoundsState pipBoundsState, @ShellMainThread ShellExecutor mainExecutor) { return new PipScheduler(context, pipBoundsState, mainExecutor); @ShellMainThread ShellExecutor mainExecutor, ShellTaskOrganizer shellTaskOrganizer) { return new PipScheduler(context, pipBoundsState, mainExecutor, shellTaskOrganizer); } @WMSingleton Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +2 −2 Original line number Diff line number Diff line Loading @@ -245,9 +245,9 @@ public class PipController implements ConfigurationChangeListener, Rect destinationBounds, SurfaceControl overlay, Rect appBounds) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "onSwipePipToHomeAnimationStart: %s", componentName); mPipScheduler.setInSwipePipToHomeTransition(true); mPipScheduler.onSwipePipToHomeAnimationStart(taskId, componentName, destinationBounds, overlay, appBounds); mPipRecentsAnimationListener.onPipAnimationStarted(); // TODO: cache the overlay if provided for reparenting later. } // Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +41 −2 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; Loading @@ -30,9 +31,11 @@ import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipUtils; Loading @@ -52,6 +55,7 @@ public class PipScheduler { private final Context mContext; private final PipBoundsState mPipBoundsState; private final ShellExecutor mMainExecutor; private final ShellTaskOrganizer mShellTaskOrganizer; private PipSchedulerReceiver mSchedulerReceiver; private PipTransitionController mPipTransitionController; Loading @@ -66,6 +70,16 @@ public class PipScheduler { // true if Launcher has started swipe PiP to home animation private boolean mInSwipePipToHomeTransition; // Overlay leash potentially used during swipe PiP to home transition; // if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid. @Nullable SurfaceControl mSwipePipToHomeOverlay; // App bounds used when as a starting point to swipe PiP to home animation in Launcher; // these are also used to calculate the app icon overlay buffer size. @NonNull final Rect mSwipePipToHomeAppBounds = new Rect(); /** * 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 Loading Loading @@ -101,11 +115,14 @@ public class PipScheduler { } } public PipScheduler(Context context, PipBoundsState pipBoundsState, ShellExecutor mainExecutor) { public PipScheduler(Context context, PipBoundsState pipBoundsState, ShellExecutor mainExecutor, ShellTaskOrganizer shellTaskOrganizer) { mContext = context; mPipBoundsState = pipBoundsState; mMainExecutor = mainExecutor; mShellTaskOrganizer = shellTaskOrganizer; if (PipUtils.isPip2ExperimentEnabled()) { // temporary broadcast receiver to initiate exit PiP via expand Loading @@ -115,6 +132,10 @@ public class PipScheduler { } } ShellExecutor getMainExecutor() { return mMainExecutor; } void setPipTransitionController(PipTransitionController pipTransitionController) { mPipTransitionController = pipTransitionController; } Loading Loading @@ -171,6 +192,24 @@ public class PipScheduler { mPipTransitionController.startResizeTransition(wct, onFinishResizeCallback); } void onSwipePipToHomeAnimationStart(int taskId, ComponentName componentName, Rect destinationBounds, SurfaceControl overlay, Rect appBounds) { mInSwipePipToHomeTransition = true; mSwipePipToHomeOverlay = overlay; mSwipePipToHomeAppBounds.set(appBounds); if (overlay != null) { // Shell transitions might use a root animation leash, which will be removed when // the Recents transition is finished. Launcher attaches the overlay leash to this // animation target leash; thus, we need to reparent it to the actual Task surface now. // PipTransition is responsible to fade it out and cleanup when finishing the enter PIP // transition. SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); mShellTaskOrganizer.reparentChildSurfaceToTask(taskId, overlay, tx); tx.setLayer(overlay, Integer.MAX_VALUE); tx.apply(); } } void setInSwipePipToHomeTransition(boolean inSwipePipToHome) { mInSwipePipToHomeTransition = inSwipePipToHome; } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +64 −3 Original line number Diff line number Diff line Loading @@ -24,6 +24,9 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.PictureInPictureParams; Loading @@ -44,6 +47,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipMenuController; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.PipContentOverlay; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; Loading @@ -55,6 +59,11 @@ import java.util.function.Consumer; */ public class PipTransition extends PipTransitionController { private static final String TAG = PipTransition.class.getSimpleName(); /** * The fixed start delay in ms when fading out the content overlay from bounds animation. * The fadeout animation is guaranteed to start after the client has drawn under the new config. */ private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 400; private final Context mContext; private final PipScheduler mPipScheduler; Loading Loading @@ -230,10 +239,13 @@ public class PipTransition extends PipTransitionController { PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams; Rect srcRectHint = params.getSourceRectHint(); Rect startBounds = pipChange.getStartAbsBounds(); Rect destinationBounds = pipChange.getEndAbsBounds(); WindowContainerTransaction finishWct = new WindowContainerTransaction(); if (PipBoundsAlgorithm.isSourceRectHintValidForEnterPip(srcRectHint, destinationBounds)) { float scale = (float) destinationBounds.width() / srcRectHint.width(); final float scale = (float) destinationBounds.width() / srcRectHint.width(); startTransaction.setWindowCrop(pipLeash, srcRectHint); startTransaction.setPosition(pipLeash, destinationBounds.left - srcRectHint.left * scale, Loading @@ -244,13 +256,62 @@ public class PipTransition extends PipTransitionController { // in multi-activity case, reparenting yields new reset scales coming from pinned task. startTransaction.setScale(pipLeash, scale, scale); } else { // TODO(b/325481148): handle the case with invalid srcRectHint (using overlay). final float scaleX = (float) destinationBounds.width() / startBounds.width(); final float scaleY = (float) destinationBounds.height() / startBounds.height(); final int overlaySize = PipContentOverlay.PipAppIconOverlay .getOverlaySize(mPipScheduler.mSwipePipToHomeAppBounds, destinationBounds); SurfaceControl overlayLeash = mPipScheduler.mSwipePipToHomeOverlay; startTransaction.setPosition(pipLeash, destinationBounds.left, destinationBounds.top) .setScale(pipLeash, scaleX, scaleY) .setWindowCrop(pipLeash, startBounds) .reparent(overlayLeash, pipLeash) .setLayer(overlayLeash, Integer.MAX_VALUE); if (mPipTaskToken != null) { SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(), this::onClientDrawAtTransitionEnd) .setScale(overlayLeash, 1f, 1f) .setPosition(overlayLeash, (destinationBounds.width() - overlaySize) / 2f, (destinationBounds.height() - overlaySize) / 2f); finishWct.setBoundsChangeTransaction(mPipTaskToken, tx); } } startTransaction.apply(); finishCallback.onTransitionFinished(null); // Note that finishWct should be free of any actual WM state changes; we are using // it for syncing with the client draw after delayed configuration changes are dispatched. finishCallback.onTransitionFinished(finishWct.isEmpty() ? null : finishWct); return true; } private void onClientDrawAtTransitionEnd() { startOverlayFadeoutAnimation(); } private void startOverlayFadeoutAnimation() { ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f); animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); tx.remove(mPipScheduler.mSwipePipToHomeOverlay); tx.apply(); mPipScheduler.mSwipePipToHomeOverlay = null; } }); animator.addUpdateListener(animation -> { float alpha = (float) animation.getAnimatedValue(); SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); tx.setAlpha(mPipScheduler.mSwipePipToHomeOverlay, alpha).apply(); }); animator.start(); } private boolean startBoundsTypeEnterAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, Loading