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

Commit 4acb718a authored by Tracy Zhou's avatar Tracy Zhou Committed by Android (Google) Code Review
Browse files

Merge "Initial implementation of split from workspace and all apps"

parents 47a39329 a0d47208
Loading
Loading
Loading
Loading
+44 −0
Original line number Diff line number Diff line
@@ -15,14 +15,23 @@
 */
package com.android.launcher3.popup;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.View;

import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.quickstep.views.RecentsView;

public interface QuickstepSystemShortcut {

    String TAG = QuickstepSystemShortcut.class.getSimpleName();

    static SystemShortcut.Factory<BaseQuickstepLauncher> getSplitSelectShortcutByPosition(
            SplitPositionOption position) {
        return (activity, itemInfo) -> new QuickstepSystemShortcut.SplitSelectSystemShortcut(
@@ -46,6 +55,41 @@ public interface QuickstepSystemShortcut {

        @Override
        public void onClick(View view) {
            Bitmap bitmap;
            Intent intent;
            if (mItemInfo instanceof WorkspaceItemInfo) {
                final WorkspaceItemInfo workspaceItemInfo = (WorkspaceItemInfo) mItemInfo;
                bitmap = workspaceItemInfo.bitmap.icon;
                intent = workspaceItemInfo.intent;
            } else if (mItemInfo instanceof com.android.launcher3.model.data.AppInfo) {
                final com.android.launcher3.model.data.AppInfo appInfo =
                        (com.android.launcher3.model.data.AppInfo) mItemInfo;
                bitmap = appInfo.bitmap.icon;
                intent = appInfo.intent;
            } else {
                Log.e(TAG, "unknown item type");
                return;
            }

            RecentsView recentsView = mLauncher.getOverviewPanel();
            recentsView.initiateSplitSelect(
                    new SplitSelectSource(view, new BitmapDrawable(bitmap), intent, mPosition));
        }
    }

    class SplitSelectSource {

        public final View view;
        public final Drawable drawable;
        public final Intent intent;
        public final SplitPositionOption position;

        public SplitSelectSource(View view, Drawable drawable, Intent intent,
                SplitPositionOption position) {
            this.view = view;
            this.drawable = drawable;
            this.intent = intent;
            this.position = position;
        }
    }
}
+15 −0
Original line number Diff line number Diff line
@@ -608,6 +608,21 @@ public class SystemUiProxy implements ISystemUiProxy,
        }
    }

    public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
            Intent fillInIntent, int taskId, boolean intentFirst, Bundle mainOptions,
            Bundle sideOptions, @SplitConfigurationOptions.StagePosition int sidePosition,
            float splitRatio, RemoteAnimationAdapter adapter) {
        if (mSystemUiProxy != null) {
            try {
                mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
                        taskId, intentFirst, mainOptions, sideOptions, sidePosition, splitRatio,
                        adapter);
            } catch (RemoteException e) {
                Log.w(TAG, "Failed call startTasksWithLegacyTransition");
            }
        }
    }

    public void startShortcut(String packageName, String shortcutId, int position,
            Bundle options, UserHandle user) {
        if (mSplitScreen != null) {
+30 −16
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
package com.android.quickstep;

import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -46,6 +47,7 @@ import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Matrix;
@@ -389,18 +391,20 @@ public final class TaskViewUtils {
     * device is considered in multiWindowMode and things like insets and stuff change
     * and calculations have to be adjusted in the animations for that
     */
    public static void composeRecentsSplitLaunchAnimator(@NonNull Task initalTask,
            @NonNull Task secondTask, @NonNull TransitionInfo transitionInfo,
            SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {

        final TransitionInfo.Change[] splitRoots = new TransitionInfo.Change[2];
    public static void composeRecentsSplitLaunchAnimator(int initialTaskId,
            @Nullable PendingIntent initialTaskPendingIntent, int secondTaskId,
            @NonNull TransitionInfo transitionInfo, SurfaceControl.Transaction t,
            @NonNull Runnable finishCallback) {
        // TODO: consider initialTaskPendingIntent
        TransitionInfo.Change splitRoot1 = null;
        TransitionInfo.Change splitRoot2 = null;
        for (int i = 0; i < transitionInfo.getChanges().size(); ++i) {
            final TransitionInfo.Change change = transitionInfo.getChanges().get(i);
            final int taskId = change.getTaskInfo() != null ? change.getTaskInfo().taskId : -1;
            final int mode = change.getMode();
            // Find the target tasks' root tasks since those are the split stages that need to
            // be animated (the tasks themselves are children and thus inherit animation).
            if (taskId == initalTask.key.id || taskId == secondTask.key.id) {
            if (taskId == initialTaskId || taskId == secondTaskId) {
                if (!(mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
                    throw new IllegalStateException(
                            "Expected task to be showing, but it is " + mode);
@@ -409,16 +413,18 @@ public final class TaskViewUtils {
                    throw new IllegalStateException("Initiating multi-split launch but the split"
                            + "root of " + taskId + " is already visible or has broken hierarchy.");
                }
                splitRoots[taskId == initalTask.key.id ? 0 : 1] =
                        transitionInfo.getChange(change.getParent());
            }
            if (taskId == initialTaskId && initialTaskId != INVALID_TASK_ID) {
                splitRoot1 = transitionInfo.getChange(change.getParent());
            }
            if (taskId == secondTaskId) {
                splitRoot2 = transitionInfo.getChange(change.getParent());
            }
        }

        // This is where we should animate the split roots. For now, though, just make them visible.
        for (int i = 0; i < 2; ++i) {
            t.show(splitRoots[i].getLeash());
            t.setAlpha(splitRoots[i].getLeash(), 1.f);
        }
        animateSplitRoot(t, splitRoot1);
        animateSplitRoot(t, splitRoot2);

        // This contains the initial state (before animation), so apply this at the beginning of
        // the animation.
@@ -428,6 +434,14 @@ public final class TaskViewUtils {
        finishCallback.run();
    }

    private static void animateSplitRoot(SurfaceControl.Transaction t,
            TransitionInfo.Change splitRoot) {
        if (splitRoot != null) {
            t.show(splitRoot.getLeash());
            t.setAlpha(splitRoot.getLeash(), 1.f);
        }
    }

    /**
     * Legacy version (until shell transitions are enabled)
     *
@@ -440,9 +454,9 @@ public final class TaskViewUtils {
     * If it is null, then it will simply fade in the starting apps and fade out launcher (for the
     * case where launcher handles animating starting split tasks from app icon) */
    public static void composeRecentsSplitLaunchAnimatorLegacy(
            @Nullable GroupedTaskView launchingTaskView,
            @NonNull Task initialTask,
            @NonNull Task secondTask, @NonNull RemoteAnimationTargetCompat[] appTargets,
            @Nullable GroupedTaskView launchingTaskView, int initialTaskId,
            @Nullable PendingIntent initialTaskPendingIntent, int secondTaskId,
            @NonNull RemoteAnimationTargetCompat[] appTargets,
            @NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
            @NonNull RemoteAnimationTargetCompat[] nonAppTargets,
            @NonNull StateManager stateManager,
@@ -478,7 +492,7 @@ public final class TaskViewUtils {

            if (mode == MODE_OPENING) {
                openingTargets.add(leash);
            } else if (taskId == initialTask.key.id || taskId == secondTask.key.id) {
            } else if (taskId == initialTaskId || taskId == secondTaskId) {
                throw new IllegalStateException("Expected task to be opening, but it is " + mode);
            } else if (mode == MODE_CLOSING) {
                closingTargets.add(leash);
+7 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import androidx.annotation.Nullable;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.popup.QuickstepSystemShortcut;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.FallbackActivityInterface;
@@ -254,4 +255,10 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity, RecentsSta
        // Do not let touch escape to siblings below this view.
        return result || mActivity.getStateManager().getState().overviewUi();
    }

    @Override
    public void initiateSplitSelect(QuickstepSystemShortcut.SplitSelectSource splitSelectSource) {
        super.initiateSplitSelect(splitSelectSource);
        mActivity.getStateManager().goToState(OVERVIEW_SPLIT_SELECT);
    }
}
+69 −41
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.quickstep.util;

import static android.app.ActivityTaskManager.INVALID_TASK_ID;

import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
@@ -24,7 +26,8 @@ import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITIO

import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.graphics.Rect;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.view.RemoteAnimationAdapter;
@@ -42,7 +45,6 @@ import com.android.quickstep.TaskAnimationManager;
import com.android.quickstep.TaskViewUtils;
import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -62,16 +64,16 @@ public class SplitSelectStateController {
    private final StateManager mStateManager;
    private final DepthController mDepthController;
    private @StagePosition int mStagePosition;
    private Task mInitialTask;
    private Task mSecondTask;
    private PendingIntent mInitialTaskPendingIntent;
    private int mInitialTaskId = INVALID_TASK_ID;
    private int mSecondTaskId = INVALID_TASK_ID;
    private boolean mRecentsAnimationRunning;
    /** If not null, this is the TaskView we want to launch from */
    @Nullable
    private GroupedTaskView mLaunchingTaskView;

    public SplitSelectStateController(Handler handler, SystemUiProxy systemUiProxy,
            StateManager stateManager,
            DepthController depthController) {
            StateManager stateManager, DepthController depthController) {
        mHandler = handler;
        mSystemUiProxy = systemUiProxy;
        mStateManager = stateManager;
@@ -81,19 +83,26 @@ public class SplitSelectStateController {
    /**
     * To be called after first task selected
     */
    public void setInitialTaskSelect(Task task, @StagePosition int stagePosition,
            Rect initialBounds) {
        mInitialTask = task;
    public void setInitialTaskSelect(int taskId, @StagePosition int stagePosition) {
        mInitialTaskId = taskId;
        mStagePosition = stagePosition;
        mInitialTaskPendingIntent = null;
    }

    public void setInitialTaskSelect(PendingIntent pendingIntent,
            @StagePosition int stagePosition) {
        mInitialTaskPendingIntent = pendingIntent;
        mStagePosition = stagePosition;
        mInitialTaskId = INVALID_TASK_ID;
    }

    /**
     * To be called after second task selected
     */
    public void setSecondTaskId(Task task, Consumer<Boolean> callback) {
        mSecondTask = task;
        launchTasks(mInitialTask, mSecondTask, mStagePosition, callback,
                false /* freezeTaskList */, DEFAULT_SPLIT_RATIO);
    public void setSecondTaskId(int taskId, Consumer<Boolean> callback) {
        mSecondTaskId = taskId;
        launchTasks(mInitialTaskId, mInitialTaskPendingIntent, mSecondTaskId, mStagePosition,
                callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO);
    }

    /**
@@ -104,7 +113,8 @@ public class SplitSelectStateController {
        mLaunchingTaskView = groupedTaskView;
        TaskView.TaskIdAttributeContainer[] taskIdAttributeContainers =
                groupedTaskView.getTaskIdAttributeContainers();
        launchTasks(taskIdAttributeContainers[0].getTask(), taskIdAttributeContainers[1].getTask(),
        launchTasks(taskIdAttributeContainers[0].getTask().key.id, null,
                taskIdAttributeContainers[1].getTask().key.id,
                taskIdAttributeContainers[0].getStagePosition(), callback, freezeTaskList,
                groupedTaskView.getSplitRatio());
    }
@@ -112,22 +122,25 @@ public class SplitSelectStateController {
    /**
     * @param stagePosition representing location of task1
     */
    public void launchTasks(Task task1, Task task2, @StagePosition int stagePosition,
            Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
    public void launchTasks(int taskId1, @Nullable PendingIntent taskPendingIntent,
            int taskId2, @StagePosition int stagePosition, Consumer<Boolean> callback,
            boolean freezeTaskList, float splitRatio) {
        // Assume initial task is for top/left part of screen
        final int[] taskIds = stagePosition == STAGE_POSITION_TOP_OR_LEFT
                ? new int[]{task1.key.id, task2.key.id}
                : new int[]{task2.key.id, task1.key.id};
                ? new int[]{taskId1, taskId2}
                : new int[]{taskId2, taskId1};
        if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
            RemoteSplitLaunchTransitionRunner animationRunner =
                    new RemoteSplitLaunchTransitionRunner(task1, task2);
                    new RemoteSplitLaunchTransitionRunner(taskId1, taskPendingIntent, taskId2);
            mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1],
                    null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, splitRatio,
                    new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR,
                            ActivityThread.currentActivityThread().getApplicationThread()));
            // TODO: handle intent + task with shell transition
        } else {
            RemoteSplitLaunchAnimationRunner animationRunner =
                    new RemoteSplitLaunchAnimationRunner(task1, task2, callback);
                    new RemoteSplitLaunchAnimationRunner(taskId1, taskPendingIntent, taskId2,
                            callback);
            final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
                    RemoteAnimationAdapterCompat.wrapRemoteAnimationRunner(animationRunner),
                    300, 150,
@@ -137,9 +150,16 @@ public class SplitSelectStateController {
            if (freezeTaskList) {
                mainOpts.setFreezeRecentTasksReordering();
            }
            if (taskPendingIntent == null) {
                mSystemUiProxy.startTasksWithLegacyTransition(taskIds[0], mainOpts.toBundle(),
                        taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
                        splitRatio, adapter);
            } else {
                mSystemUiProxy.startIntentAndTaskWithLegacyTransition(taskPendingIntent,
                        new Intent(), taskId2, stagePosition == STAGE_POSITION_TOP_OR_LEFT,
                        mainOpts.toBundle(), null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
                        splitRatio, adapter);
            }
        }
    }

@@ -156,19 +176,22 @@ public class SplitSelectStateController {
     */
    private class RemoteSplitLaunchTransitionRunner implements RemoteTransitionRunner {

        private final Task mInitialTask;
        private final Task mSecondTask;
        private final int mInitialTaskId;
        private final PendingIntent mInitialTaskPendingIntent;
        private final int mSecondTaskId;

        RemoteSplitLaunchTransitionRunner(Task initialTask, Task secondTask) {
            mInitialTask = initialTask;
            mSecondTask = secondTask;
        RemoteSplitLaunchTransitionRunner(int initialTaskId, PendingIntent initialTaskPendingIntent,
                int secondTaskId) {
            mInitialTaskId = initialTaskId;
            mInitialTaskPendingIntent = initialTaskPendingIntent;
            mSecondTaskId = secondTaskId;
        }

        @Override
        public void startAnimation(IBinder transition, TransitionInfo info,
                SurfaceControl.Transaction t, Runnable finishCallback) {
            TaskViewUtils.composeRecentsSplitLaunchAnimator(mInitialTask,
                    mSecondTask, info, t, finishCallback);
            TaskViewUtils.composeRecentsSplitLaunchAnimator(mInitialTaskId,
                    mInitialTaskPendingIntent, mSecondTaskId, info, t, finishCallback);
            // After successful launch, call resetState
            resetState();
        }
@@ -180,14 +203,16 @@ public class SplitSelectStateController {
     */
    private class RemoteSplitLaunchAnimationRunner implements RemoteAnimationRunnerCompat {

        private final Task mInitialTask;
        private final Task mSecondTask;
        private final int mInitialTaskId;
        private final PendingIntent mInitialTaskPendingIntent;
        private final int mSecondTaskId;
        private final Consumer<Boolean> mSuccessCallback;

        RemoteSplitLaunchAnimationRunner(Task initialTask, Task secondTask,
                Consumer<Boolean> successCallback) {
            mInitialTask = initialTask;
            mSecondTask = secondTask;
        RemoteSplitLaunchAnimationRunner(int initialTaskId, PendingIntent initialTaskPendingIntent,
                int secondTaskId, Consumer<Boolean> successCallback) {
            mInitialTaskId = initialTaskId;
            mInitialTaskPendingIntent = initialTaskPendingIntent;
            mSecondTaskId = secondTaskId;
            mSuccessCallback = successCallback;
        }

@@ -197,8 +222,9 @@ public class SplitSelectStateController {
                Runnable finishedCallback) {
            postAsyncCallback(mHandler,
                    () -> TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy(
                            mLaunchingTaskView, mInitialTask, mSecondTask, apps, wallpapers,
                            nonApps, mStateManager, mDepthController, () -> {
                            mLaunchingTaskView, mInitialTaskId, mInitialTaskPendingIntent,
                            mSecondTaskId, apps, wallpapers, nonApps, mStateManager,
                            mDepthController, () -> {
                                finishedCallback.run();
                                if (mSuccessCallback != null) {
                                    mSuccessCallback.accept(true);
@@ -224,8 +250,9 @@ public class SplitSelectStateController {
     * To be called if split select was cancelled
     */
    public void resetState() {
        mInitialTask = null;
        mSecondTask = null;
        mInitialTaskId = INVALID_TASK_ID;
        mInitialTaskPendingIntent = null;
        mSecondTaskId = INVALID_TASK_ID;
        mStagePosition = SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
        mRecentsAnimationRunning = false;
        mLaunchingTaskView = null;
@@ -236,6 +263,7 @@ public class SplitSelectStateController {
     *         chosen
     */
    public boolean isSplitSelectActive() {
        return mInitialTask != null && mSecondTask == null;
        return (mInitialTaskId != INVALID_TASK_ID || mInitialTaskPendingIntent != null)
                && mSecondTaskId == INVALID_TASK_ID;
    }
}
Loading