diff --git a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java index c40d2225b0..3bc6576239 100644 --- a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java +++ b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java @@ -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 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; } } } diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index 22a30e92f6..4aa69d1d1a 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -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) { diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java index 5afcdcb41c..5b69aeb8fa 100644 --- a/quickstep/src/com/android/quickstep/TaskViewUtils.java +++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java @@ -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); diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java index 69da97772b..c7a8382208 100644 --- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java +++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java @@ -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 callback) { - mSecondTask = task; - launchTasks(mInitialTask, mSecondTask, mStagePosition, callback, - false /* freezeTaskList */, DEFAULT_SPLIT_RATIO); + public void setSecondTaskId(int taskId, Consumer 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 callback, boolean freezeTaskList, float splitRatio) { + public void launchTasks(int taskId1, @Nullable PendingIntent taskPendingIntent, + int taskId2, @StagePosition int stagePosition, Consumer 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(); } - mSystemUiProxy.startTasksWithLegacyTransition(taskIds[0], mainOpts.toBundle(), - taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, - splitRatio, adapter); + 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 mSuccessCallback; - RemoteSplitLaunchAnimationRunner(Task initialTask, Task secondTask, - Consumer successCallback) { - mInitialTask = initialTask; - mSecondTask = secondTask; + RemoteSplitLaunchAnimationRunner(int initialTaskId, PendingIntent initialTaskPendingIntent, + int secondTaskId, Consumer 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; } } diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java index 18ab3bb2e9..9ae5d250b8 100644 --- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java +++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java @@ -7,8 +7,10 @@ import static com.android.systemui.shared.system.QuickStepContract.supportsRound import android.animation.ValueAnimator; import android.content.Context; +import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -18,7 +20,6 @@ import android.widget.ImageView; import androidx.annotation.Nullable; import com.android.launcher3.BaseActivity; -import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.InsettableFrameLayout; import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.R; @@ -30,7 +31,8 @@ import com.android.launcher3.views.BaseDragLayer; import com.android.quickstep.util.MultiValueUpdateListener; /** - * Create an instance via {@link #getFloatingTaskView(StatefulActivity, TaskView, RectF)} to + * Create an instance via + * {@link #getFloatingTaskView(StatefulActivity, View, Bitmap, Drawable, RectF)} to * which will have the thumbnail from the provided existing TaskView overlaying the taskview itself. * * Can then animate the taskview using @@ -44,7 +46,7 @@ public class FloatingTaskView extends FrameLayout { private SplitPlaceholderView mSplitPlaceholderView; private RectF mStartingPosition; - private final BaseDraggingActivity mActivity; + private final StatefulActivity mActivity; private final boolean mIsRtl; private final Rect mOutline = new Rect(); private PagedOrientationHandler mOrientationHandler; @@ -74,7 +76,8 @@ public class FloatingTaskView extends FrameLayout { mSplitPlaceholderView.setAlpha(0); } - private void init(StatefulActivity launcher, TaskView originalView, RectF positionOut) { + private void init(StatefulActivity launcher, View originalView, @Nullable Bitmap thumbnail, + Drawable icon, RectF positionOut) { mStartingPosition = positionOut; updateInitialPositionForView(originalView); final InsettableFrameLayout.LayoutParams lp = @@ -86,12 +89,12 @@ public class FloatingTaskView extends FrameLayout { setPivotY(0); // Copy bounds of exiting thumbnail into ImageView - TaskThumbnailView thumbnail = originalView.getThumbnail(); - mImageView.setImageBitmap(thumbnail.getThumbnail()); + mImageView.setImageBitmap(thumbnail); mImageView.setVisibility(VISIBLE); - mOrientationHandler = originalView.getRecentsView().getPagedOrientationHandler(); - mSplitPlaceholderView.setIconView(originalView.getIconView(), + RecentsView recentsView = launcher.getOverviewPanel(); + mOrientationHandler = recentsView.getPagedOrientationHandler(); + mSplitPlaceholderView.setIcon(icon, launcher.getDeviceProfile().overviewTaskIconDrawableSizePx); mSplitPlaceholderView.getIconView().setRotation(mOrientationHandler.getDegreesRotated()); } @@ -101,21 +104,20 @@ public class FloatingTaskView extends FrameLayout { * appearance of {@code originalView}. */ public static FloatingTaskView getFloatingTaskView(StatefulActivity launcher, - TaskView originalView, RectF positionOut) { + View originalView, @Nullable Bitmap thumbnail, Drawable icon, RectF positionOut) { final BaseDragLayer dragLayer = launcher.getDragLayer(); ViewGroup parent = (ViewGroup) dragLayer.getParent(); final FloatingTaskView floatingView = (FloatingTaskView) launcher.getLayoutInflater() .inflate(R.layout.floating_split_select_view, parent, false); - floatingView.init(launcher, originalView, positionOut); + floatingView.init(launcher, originalView, thumbnail, icon, positionOut); parent.addView(floatingView); return floatingView; } - public void updateInitialPositionForView(TaskView originalView) { - View thumbnail = originalView.getThumbnail(); - Rect viewBounds = new Rect(0, 0, thumbnail.getWidth(), thumbnail.getHeight()); - Utilities.getBoundsForViewInDragLayer(mActivity.getDragLayer(), thumbnail, viewBounds, + public void updateInitialPositionForView(View originalView) { + Rect viewBounds = new Rect(0, 0, originalView.getWidth(), originalView.getHeight()); + Utilities.getBoundsForViewInDragLayer(mActivity.getDragLayer(), originalView, viewBounds, true /* ignoreTransform */, null /* recycle */, mStartingPosition); mStartingPosition.offset(originalView.getTranslationX(), originalView.getTranslationY()); diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java index 718effe39f..3ddec26360 100644 --- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java +++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java @@ -180,8 +180,8 @@ public class GroupedTaskView extends TaskView { @Override public void launchTask(@NonNull Consumer callback, boolean freezeTaskList) { - getRecentsView().getSplitPlaceholder().launchTasks(mTask, mSecondaryTask, - STAGE_POSITION_TOP_OR_LEFT, callback, freezeTaskList, + getRecentsView().getSplitPlaceholder().launchTasks(mTask.key.id, null, + mSecondaryTask.key.id, STAGE_POSITION_TOP_OR_LEFT, callback, freezeTaskList, getSplitRatio()); } diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java index e0395ea5aa..e8f3324b5e 100644 --- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java @@ -34,6 +34,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.LauncherState; +import com.android.launcher3.popup.QuickstepSystemShortcut; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.StateManager.StateListener; import com.android.launcher3.util.SplitConfigurationOptions; @@ -168,4 +169,10 @@ public class LauncherRecentsView extends RecentsView { if (success) { mSplitToast.show(); @@ -3924,20 +3959,40 @@ public abstract class RecentsView - mSplitSelectStateController.setSecondTaskId(taskView.getTask(), + mSplitSelectStateController.setSecondTaskId(taskView.getTask().key.id, aBoolean1 -> RecentsView.this.resetFromSplitSelectionState())); mSecondSplitHiddenTaskView = taskView; taskView.setVisibility(INVISIBLE); @@ -3983,6 +4039,20 @@ public abstract class RecentsView