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

Commit b027fca2 authored by Ikram Gabiyev's avatar Ikram Gabiyev
Browse files

Add PipTransitionState into pip2

Create a new PipTransitionState class
to dispatch state updates for
PiP specifically guided by transitions.

Also connect scheduling expand-pip to onActivityRestartAttempt().
But remove the usages of onActivityPinned/Unpinned() since we can now
rely on PipTraansitionStateChanged listener updates instead which
are in sync with diffferent stages of running Transitions.

The plan is to use these state changes to enable and disable touches
in pip2.PipTouchHandler in a follow up CL (a substitute for onAct(Un)Pinned).

Bug: 336870419
Test: swipe-up to enter PiP then tap icon to expand
Change-Id: Ica297920fbf4ace1c18b17e92341e179dcce22d3
parent 54019790
Loading
Loading
Loading
Loading
+18 −5
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
@@ -43,6 +44,7 @@ import com.android.wm.shell.pip2.phone.PipMotionHelper;
import com.android.wm.shell.pip2.phone.PipScheduler;
import com.android.wm.shell.pip2.phone.PipTouchHandler;
import com.android.wm.shell.pip2.phone.PipTransition;
import com.android.wm.shell.pip2.phone.PipTransitionState;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -69,9 +71,11 @@ public abstract class Pip2Module {
            PipBoundsAlgorithm pipBoundsAlgorithm,
            Optional<PipController> pipController,
            PipTouchHandler pipTouchHandler,
            @NonNull PipScheduler pipScheduler) {
            @NonNull PipScheduler pipScheduler,
            @NonNull PipTransitionState pipStackListenerController) {
        return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
                pipBoundsState, null, pipBoundsAlgorithm, pipScheduler);
                pipBoundsState, null, pipBoundsAlgorithm, pipScheduler,
                pipStackListenerController);
    }

    @WMSingleton
@@ -85,6 +89,9 @@ public abstract class Pip2Module {
            PipBoundsAlgorithm pipBoundsAlgorithm,
            PipDisplayLayoutState pipDisplayLayoutState,
            PipScheduler pipScheduler,
            TaskStackListenerImpl taskStackListener,
            ShellTaskOrganizer shellTaskOrganizer,
            PipTransitionState pipTransitionState,
            @ShellMainThread ShellExecutor mainExecutor) {
        if (!PipUtils.isPip2ExperimentEnabled()) {
            return Optional.empty();
@@ -92,7 +99,7 @@ public abstract class Pip2Module {
            return Optional.ofNullable(PipController.create(
                    context, shellInit, shellController, displayController, displayInsetsController,
                    pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, pipScheduler,
                    mainExecutor));
                    taskStackListener, shellTaskOrganizer, pipTransitionState, mainExecutor));
        }
    }

@@ -101,8 +108,8 @@ public abstract class Pip2Module {
    static PipScheduler providePipScheduler(Context context,
            PipBoundsState pipBoundsState,
            @ShellMainThread ShellExecutor mainExecutor,
            ShellTaskOrganizer shellTaskOrganizer) {
        return new PipScheduler(context, pipBoundsState, mainExecutor, shellTaskOrganizer);
            PipTransitionState pipTransitionState) {
        return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState);
    }

    @WMSingleton
@@ -146,4 +153,10 @@ public abstract class Pip2Module {
        return new PipMotionHelper(context, pipBoundsState, menuController, pipSnapAlgorithm,
                floatingContentCoordinator, pipPerfHintControllerOptional);
    }

    @WMSingleton
    @Provides
    static PipTransitionState providePipStackListenerController() {
        return new PipTransitionState();
    }
}
+1 −6
Original line number Diff line number Diff line
@@ -54,7 +54,6 @@ import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

/**
 * Responsible supplying PiP Transitions.
@@ -125,12 +124,8 @@ public abstract class PipTransitionController implements Transitions.TransitionH

    /**
     * Called when the Shell wants to start resizing Pip transition/animation.
     *
     * @param onFinishResizeCallback callback guaranteed to execute when animation ends and
     *                               client completes any potential draws upon WM state updates.
     */
    public void startResizeTransition(WindowContainerTransaction wct,
            Consumer<Rect> onFinishResizeCallback) {
    public void startResizeTransition(WindowContainerTransaction wct) {
        // Default implementation does nothing.
    }

+74 −3
Original line number Diff line number Diff line
@@ -16,25 +16,31 @@

package com.android.wm.shell.pip2.phone;

import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
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.ActivityManager;
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.os.Bundle;
import android.view.InsetsState;
import android.view.SurfaceControl;

import androidx.annotation.BinderThread;
import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.Preconditions;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
@@ -42,6 +48,8 @@ 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.SingleInstanceRemoteListener;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.pip.IPip;
import com.android.wm.shell.common.pip.IPipAnimationListener;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -57,8 +65,11 @@ import com.android.wm.shell.sysui.ShellInit;
 * Manages the picture-in-picture (PIP) UI and states for Phones.
 */
public class PipController implements ConfigurationChangeListener,
        PipTransitionState.PipTransitionStateChangedListener,
        DisplayController.OnDisplaysChangedListener, RemoteCallable<PipController> {
    private static final String TAG = PipController.class.getSimpleName();
    private static final String SWIPE_TO_PIP_APP_BOUNDS = "pip_app_bounds";
    private static final String SWIPE_TO_PIP_OVERLAY = "swipe_to_pip_overlay";

    private final Context mContext;
    private final ShellController mShellController;
@@ -68,6 +79,9 @@ public class PipController implements ConfigurationChangeListener,
    private final PipBoundsAlgorithm mPipBoundsAlgorithm;
    private final PipDisplayLayoutState mPipDisplayLayoutState;
    private final PipScheduler mPipScheduler;
    private final TaskStackListenerImpl mTaskStackListener;
    private final ShellTaskOrganizer mShellTaskOrganizer;
    private final PipTransitionState mPipTransitionState;
    private final ShellExecutor mMainExecutor;

    // Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents.
@@ -104,6 +118,9 @@ public class PipController implements ConfigurationChangeListener,
            PipBoundsAlgorithm pipBoundsAlgorithm,
            PipDisplayLayoutState pipDisplayLayoutState,
            PipScheduler pipScheduler,
            TaskStackListenerImpl taskStackListener,
            ShellTaskOrganizer shellTaskOrganizer,
            PipTransitionState pipTransitionState,
            ShellExecutor mainExecutor) {
        mContext = context;
        mShellController = shellController;
@@ -113,6 +130,10 @@ public class PipController implements ConfigurationChangeListener,
        mPipBoundsAlgorithm = pipBoundsAlgorithm;
        mPipDisplayLayoutState = pipDisplayLayoutState;
        mPipScheduler = pipScheduler;
        mTaskStackListener = taskStackListener;
        mShellTaskOrganizer = shellTaskOrganizer;
        mPipTransitionState = pipTransitionState;
        mPipTransitionState.addPipTransitionStateChangedListener(this);
        mMainExecutor = mainExecutor;

        if (PipUtils.isPip2ExperimentEnabled()) {
@@ -132,6 +153,9 @@ public class PipController implements ConfigurationChangeListener,
            PipBoundsAlgorithm pipBoundsAlgorithm,
            PipDisplayLayoutState pipDisplayLayoutState,
            PipScheduler pipScheduler,
            TaskStackListenerImpl taskStackListener,
            ShellTaskOrganizer shellTaskOrganizer,
            PipTransitionState pipTransitionState,
            ShellExecutor mainExecutor) {
        if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
            ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -140,7 +164,8 @@ public class PipController implements ConfigurationChangeListener,
        }
        return new PipController(context, shellInit, shellController, displayController,
                displayInsetsController, pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState,
                pipScheduler, mainExecutor);
                pipScheduler, taskStackListener, shellTaskOrganizer, pipTransitionState,
                mainExecutor);
    }

    private void onInit() {
@@ -164,6 +189,17 @@ public class PipController implements ConfigurationChangeListener,
        mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
                this::createExternalInterface, this);
        mShellController.addConfigurationChangeListener(this);

        mTaskStackListener.addListener(new TaskStackListenerCallback() {
            @Override
            public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
                    boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
                if (task.getWindowingMode() != WINDOWING_MODE_PINNED) {
                    return;
                }
                mPipScheduler.scheduleExitPipViaExpand();
            }
        });
    }

    private ExternalInterfaceBinder createExternalInterface() {
@@ -245,11 +281,46 @@ public class PipController implements ConfigurationChangeListener,
            Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                "onSwipePipToHomeAnimationStart: %s", componentName);
        mPipScheduler.onSwipePipToHomeAnimationStart(taskId, componentName, destinationBounds,
                overlay, appBounds);
        Bundle extra = new Bundle();
        extra.putParcelable(SWIPE_TO_PIP_OVERLAY, overlay);
        extra.putParcelable(SWIPE_TO_PIP_APP_BOUNDS, appBounds);
        mPipTransitionState.setState(PipTransitionState.SWIPING_TO_PIP, extra);
        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();
        }
        mPipRecentsAnimationListener.onPipAnimationStarted();
    }

    @Override
    public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
            @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
        if (newState == PipTransitionState.SWIPING_TO_PIP) {
            Preconditions.checkState(extra != null,
                    "No extra bundle for " + mPipTransitionState);

            SurfaceControl overlay = extra.getParcelable(
                    SWIPE_TO_PIP_OVERLAY, SurfaceControl.class);
            Rect appBounds = extra.getParcelable(
                    SWIPE_TO_PIP_APP_BOUNDS, Rect.class);

            Preconditions.checkState(appBounds != null,
                    "App bounds can't be null for " + mPipTransitionState);
            mPipTransitionState.setSwipePipToHomeState(overlay, appBounds);
        } else if (newState == PipTransitionState.ENTERED_PIP) {
            if (mPipTransitionState.isInSwipePipToHomeTransition()) {
                mPipTransitionState.resetSwipePipToHomeState();
            }
        }
    }

    //
    // IPipAnimationListener Binder proxy helpers
    //
+10 −76
Original line number Diff line number Diff line
@@ -21,21 +21,16 @@ 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;
import android.graphics.Rect;
import android.view.SurfaceControl;
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;
@@ -43,7 +38,6 @@ import com.android.wm.shell.pip.PipTransitionController;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.function.Consumer;

/**
 * Scheduler for Shell initiated PiP transitions and animations.
@@ -55,31 +49,10 @@ public class PipScheduler {
    private final Context mContext;
    private final PipBoundsState mPipBoundsState;
    private final ShellExecutor mMainExecutor;
    private final ShellTaskOrganizer mShellTaskOrganizer;
    private final PipTransitionState mPipTransitionState;
    private PipSchedulerReceiver mSchedulerReceiver;
    private PipTransitionController mPipTransitionController;

    // pinned PiP task's WC token
    @Nullable
    private WindowContainerToken mPipTaskToken;

    // pinned PiP task's leash
    @Nullable
    private SurfaceControl mPinnedTaskLeash;

    // 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
@@ -118,11 +91,11 @@ public class PipScheduler {
    public PipScheduler(Context context,
            PipBoundsState pipBoundsState,
            ShellExecutor mainExecutor,
            ShellTaskOrganizer shellTaskOrganizer) {
            PipTransitionState pipTransitionState) {
        mContext = context;
        mPipBoundsState = pipBoundsState;
        mMainExecutor = mainExecutor;
        mShellTaskOrganizer = shellTaskOrganizer;
        mPipTransitionState = pipTransitionState;

        if (PipUtils.isPip2ExperimentEnabled()) {
            // temporary broadcast receiver to initiate exit PiP via expand
@@ -140,25 +113,17 @@ public class PipScheduler {
        mPipTransitionController = pipTransitionController;
    }

    void setPinnedTaskLeash(SurfaceControl pinnedTaskLeash) {
        mPinnedTaskLeash = pinnedTaskLeash;
    }

    void setPipTaskToken(@Nullable WindowContainerToken pipTaskToken) {
        mPipTaskToken = pipTaskToken;
    }

    @Nullable
    private WindowContainerTransaction getExitPipViaExpandTransaction() {
        if (mPipTaskToken == null) {
        if (mPipTransitionState.mPipTaskToken == null) {
            return null;
        }
        WindowContainerTransaction wct = new WindowContainerTransaction();
        // final expanded bounds to be inherited from the parent
        wct.setBounds(mPipTaskToken, null);
        wct.setBounds(mPipTransitionState.mPipTaskToken, null);
        // if we are hitting a multi-activity case
        // windowing mode change will reparent to original host task
        wct.setWindowingMode(mPipTaskToken, WINDOWING_MODE_UNDEFINED);
        wct.setWindowingMode(mPipTransitionState.mPipTaskToken, WINDOWING_MODE_UNDEFINED);
        return wct;
    }

@@ -183,43 +148,12 @@ public class PipScheduler {
    /**
     * Animates resizing of the pinned stack given the duration.
     */
    public void scheduleAnimateResizePip(Rect toBounds, Consumer<Rect> onFinishResizeCallback) {
        if (mPipTaskToken == null) {
    public void scheduleAnimateResizePip(Rect toBounds) {
        if (mPipTransitionState.mPipTaskToken == null || !mPipTransitionState.isInPip()) {
            return;
        }
        WindowContainerTransaction wct = new WindowContainerTransaction();
        wct.setBounds(mPipTaskToken, toBounds);
        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;
    }

    boolean isInSwipePipToHomeTransition() {
        return mInSwipePipToHomeTransition;
    }

    void onExitPip() {
        mPipTaskToken = null;
        mPinnedTaskLeash = null;
        wct.setBounds(mPipTransitionState.mPipTaskToken, toBounds);
        mPipTransitionController.startResizeTransition(wct);
    }
}
+120 −51

File changed.

Preview size limit exceeded, changes collapsed.

Loading