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

Commit 467839fa authored by Ikram Gabiyev's avatar Ikram Gabiyev
Browse files

implement no animtion expand pip (1/2)

Implement no animation exit pip via expand temporarily
triggered by a broadcast into a new PipScheduler class.
Broadcast receiver is used to initiate expand CUJ since
pip menu is not yet added into pip2 implementation.

The second part of this overall change (i.e. 2/2 CL)
will target the synchronization of the app draw
via wct.setBoundsChangeTransaction().

The current implementation doesn't take fixed rotation into account.
For more info see go/pip2-transitions.

Test: adb shell am broadcast -a \
	com.android.wm.shell.pip2.phone.PipScheduler
Bug: 307784067

Change-Id: Id0263162a001e4db73cb0483b2c5960f727e6653
parent 26a541f8
Loading
Loading
Loading
Loading
+14 −2
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@ import android.content.Context;
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.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
@@ -29,6 +31,7 @@ import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip2.phone.PipController;
import com.android.wm.shell.pip2.phone.PipScheduler;
import com.android.wm.shell.pip2.phone.PipTransition;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -52,9 +55,10 @@ public abstract class Pip2Module {
            @NonNull Transitions transitions,
            PipBoundsState pipBoundsState,
            PipBoundsAlgorithm pipBoundsAlgorithm,
            Optional<PipController> pipController) {
            Optional<PipController> pipController,
            @NonNull PipScheduler pipScheduler) {
        return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
                pipBoundsAlgorithm);
                pipBoundsAlgorithm, pipScheduler);
    }

    @WMSingleton
@@ -73,4 +77,12 @@ public abstract class Pip2Module {
                    pipDisplayLayoutState));
        }
    }

    @WMSingleton
    @Provides
    static PipScheduler providePipScheduler(Context context,
            PipBoundsState pipBoundsState,
            @ShellMainThread ShellExecutor mainExecutor) {
        return new PipScheduler(context, pipBoundsState, mainExecutor);
    }
}
+138 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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

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.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.view.SurfaceControl;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;

import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;

import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.PipTransitionController;

/**
 * Scheduler for Shell initiated PiP transitions and animations.
 */
public class PipScheduler {
    private static final String TAG = PipScheduler.class.getSimpleName();
    private static final String BROADCAST_FILTER = PipScheduler.class.getCanonicalName();

    private final Context mContext;
    private final PipBoundsState mPipBoundsState;
    private final ShellExecutor mMainExecutor;
    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;

    // the leash of the original task of the PiP activity;
    // used to synchronize app drawings in the multi-activity case
    @Nullable
    private SurfaceControl mOriginalTaskLeash;

    /**
     * A temporary broadcast receiver to initiate exit PiP via expand.
     * This will later be modified to be triggered by the PiP menu.
     */
    private class PipSchedulerReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            scheduleExitPipViaExpand();
        }
    }

    public PipScheduler(Context context, PipBoundsState pipBoundsState,
            ShellExecutor mainExecutor) {
        mContext = context;
        mPipBoundsState = pipBoundsState;
        mMainExecutor = mainExecutor;

        if (PipUtils.isPip2ExperimentEnabled()) {
            // temporary broadcast receiver to initiate exit PiP via expand
            mSchedulerReceiver = new PipSchedulerReceiver();
            ContextCompat.registerReceiver(mContext, mSchedulerReceiver,
                    new IntentFilter(BROADCAST_FILTER), ContextCompat.RECEIVER_EXPORTED);
        }
    }

    void setPipTransitionController(PipTransitionController pipTransitionController) {
        mPipTransitionController = pipTransitionController;
    }

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

    void setOriginalTaskLeash(SurfaceControl originalTaskLeash) {
        mOriginalTaskLeash = originalTaskLeash;
    }

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

    @Nullable
    private WindowContainerTransaction getExitPipViaExpandTransaction() {
        if (mPipTaskToken == null || mPinnedTaskLeash == null) {
            return null;
        }
        WindowContainerTransaction wct = new WindowContainerTransaction();
        // final expanded bounds to be inherited from the parent
        wct.setBounds(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);
        return wct;
    }

    /**
     * Schedules exit PiP via expand transition.
     */
    public void scheduleExitPipViaExpand() {
        WindowContainerTransaction wct = getExitPipViaExpandTransaction();
        if (wct != null) {
            mMainExecutor.execute(() -> {
                mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct,
                        null /* destinationBounds */);
            });
        }
    }

    void onExitPip() {
        mPipTaskToken = null;
        mPinnedTaskLeash = null;
        mOriginalTaskLeash = null;
    }
}
+88 −8
Original line number Diff line number Diff line
@@ -16,8 +16,12 @@

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

import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.WindowManager.TRANSIT_OPEN;

import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;

import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
@@ -26,6 +30,7 @@ import android.os.IBinder;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;

import androidx.annotation.Nullable;
@@ -43,8 +48,15 @@ import com.android.wm.shell.transition.Transitions;
 * Implementation of transitions for PiP on phone.
 */
public class PipTransition extends PipTransitionController {
    private static final String TAG = PipTransition.class.getSimpleName();

    private PipScheduler mPipScheduler;
    @Nullable
    private WindowContainerToken mPipTaskToken;
    @Nullable
    private IBinder mAutoEnterButtonNavTransition;
    @Nullable
    private IBinder mExitViaExpandTransition;

    public PipTransition(
            @NonNull ShellInit shellInit,
@@ -52,9 +64,13 @@ public class PipTransition extends PipTransitionController {
            @NonNull Transitions transitions,
            PipBoundsState pipBoundsState,
            PipMenuController pipMenuController,
            PipBoundsAlgorithm pipBoundsAlgorithm) {
            PipBoundsAlgorithm pipBoundsAlgorithm,
            PipScheduler pipScheduler) {
        super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
                pipBoundsAlgorithm);

        mPipScheduler = pipScheduler;
        mPipScheduler.setPipTransitionController(this);
    }

    @Override
@@ -64,6 +80,18 @@ public class PipTransition extends PipTransitionController {
        }
    }

    @Override
    public void startExitTransition(int type, WindowContainerTransaction out,
            @android.annotation.Nullable Rect destinationBounds) {
        if (out == null) {
            return;
        }
        IBinder transition = mTransitions.startTransition(type, out, this);
        if (type == TRANSIT_EXIT_PIP) {
            mExitViaExpandTransition = transition;
        }
    }

    @Nullable
    @Override
    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@@ -84,8 +112,18 @@ public class PipTransition extends PipTransitionController {
        }
    }

    @Override
    public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
            @NonNull Transitions.TransitionFinishCallback finishCallback) {}

    @Override
    public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
            @Nullable SurfaceControl.Transaction finishT) {}

    private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
            @NonNull TransitionRequestInfo request) {
        // cache the original task token to check for multi-activity case later
        final ActivityManager.RunningTaskInfo pipTask = request.getPipTask();
        PictureInPictureParams pipParams = pipTask.pictureInPictureParams;
        mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
@@ -93,6 +131,8 @@ public class PipTransition extends PipTransitionController {

        // calculate the entry bounds and notify core to move task to pinned with final bounds
        final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
        mPipBoundsState.setBounds(entryBounds);

        WindowContainerTransaction wct = new WindowContainerTransaction();
        wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds);
        return wct;
@@ -121,19 +161,59 @@ public class PipTransition extends PipTransitionController {
            @NonNull SurfaceControl.Transaction finishTransaction,
            @NonNull Transitions.TransitionFinishCallback finishCallback) {
        if (transition == mAutoEnterButtonNavTransition) {
            mAutoEnterButtonNavTransition = null;
            TransitionInfo.Change pipChange = getPipChange(info);
            if (pipChange == null) {
                return false;
            }
            mPipTaskToken = pipChange.getContainer();

            // cache the PiP task token and the relevant leashes
            mPipScheduler.setPipTaskToken(mPipTaskToken);
            mPipScheduler.setPinnedTaskLeash(pipChange.getLeash());
            // check if we entered PiP from a multi-activity task and set the original task leash
            final int lastParentTaskId = pipChange.getTaskInfo().lastParentTaskIdBeforePip;
            final boolean isSingleActivity = lastParentTaskId == INVALID_TASK_ID;
            mPipScheduler.setOriginalTaskLeash(isSingleActivity ? null :
                    findChangeByTaskId(info, lastParentTaskId).getLeash());

            startTransaction.apply();
            finishCallback.onTransitionFinished(null);
            return true;
        } else if (transition == mExitViaExpandTransition) {
            mExitViaExpandTransition = null;
            startTransaction.apply();
            finishCallback.onTransitionFinished(null);
            onExitPip();
            return true;
        }
        return false;
    }

    @Override
    public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
            @NonNull Transitions.TransitionFinishCallback finishCallback) {}
    @Nullable
    private TransitionInfo.Change getPipChange(TransitionInfo info) {
        for (TransitionInfo.Change change : info.getChanges()) {
            if (change.getTaskInfo() != null
                    && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
                return change;
            }
        }
        return null;
    }

    @Override
    public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
            @Nullable SurfaceControl.Transaction finishT) {}
    @Nullable
    private TransitionInfo.Change findChangeByTaskId(TransitionInfo info, int taskId) {
        for (TransitionInfo.Change change : info.getChanges()) {
            if (change.getTaskInfo() != null
                    && change.getTaskInfo().taskId == taskId) {
                return change;
            }
        }
        return null;
    }

    private void onExitPip() {
        mPipTaskToken = null;
        mPipScheduler.onExitPip();
    }
}
+3 −7
Original line number Diff line number Diff line
@@ -1108,16 +1108,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
                if (pipTask == null) {
                    break;
                }
                ActivityRecord[] pipActivity = new ActivityRecord[1];
                pipTask.forAllActivities((activity) -> {
                    if (activity.pictureInPictureArgs != null) {
                        pipActivity[0] = activity;
                    }
                });
                ActivityRecord pipActivity = pipTask.getActivity(
                        (activity) -> activity.pictureInPictureArgs != null);

                Rect entryBounds = hop.getBounds();
                mService.mRootWindowContainer.moveActivityToPinnedRootTask(
                        pipActivity[0], null /* launchIntoPipHostActivity */,
                        pipActivity, null /* launchIntoPipHostActivity */,
                        "moveActivityToPinnedRootTask", null /* transition */, entryBounds);
                effects |= TRANSACT_EFFECTS_LIFECYCLE;
                break;