Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1895b133 authored by Ikram Gabiyev's avatar Ikram Gabiyev
Browse files

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
parent 2183a65d
Loading
Loading
Loading
Loading
+7 −2
Original line number Diff line number Diff line
@@ -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));
        }
    }

+137 −4
Original line number Diff line number Diff line
@@ -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() {}
    }
}
+11 −0
Original line number Diff line number Diff line
@@ -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;
+25 −0
Original line number Diff line number Diff line
@@ -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,