1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.quickstep.util; 18 19 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_LEFT_TOP; 20 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_RIGHT_BOTTOM; 21 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 22 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 23 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; 24 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; 25 26 import android.animation.Animator; 27 import android.animation.AnimatorListenerAdapter; 28 import android.app.ActivityManager; 29 import android.app.ActivityOptions; 30 import android.graphics.Rect; 31 import android.graphics.RectF; 32 import android.window.TransitionInfo; 33 34 import androidx.annotation.BinderThread; 35 36 import com.android.launcher3.R; 37 import com.android.launcher3.anim.PendingAnimation; 38 import com.android.launcher3.taskbar.LauncherTaskbarUIController; 39 import com.android.launcher3.uioverrides.QuickstepLauncher; 40 import com.android.quickstep.OverviewComponentObserver; 41 import com.android.quickstep.RecentsAnimationCallbacks; 42 import com.android.quickstep.RecentsAnimationController; 43 import com.android.quickstep.RecentsAnimationTargets; 44 import com.android.quickstep.RecentsModel; 45 import com.android.quickstep.SystemUiProxy; 46 import com.android.quickstep.TopTaskTracker; 47 import com.android.quickstep.views.FloatingTaskView; 48 import com.android.quickstep.views.RecentsView; 49 import com.android.systemui.shared.recents.model.Task; 50 import com.android.systemui.shared.system.ActivityManagerWrapper; 51 52 /** Transitions app from fullscreen to stage split when triggered from keyboard shortcuts. */ 53 public class SplitWithKeyboardShortcutController { 54 55 private final QuickstepLauncher mLauncher; 56 private final SplitSelectStateController mController; 57 private final OverviewComponentObserver mOverviewComponentObserver; 58 59 private final int mSplitPlaceholderSize; 60 private final int mSplitPlaceholderInset; 61 SplitWithKeyboardShortcutController( QuickstepLauncher launcher, SplitSelectStateController controller)62 public SplitWithKeyboardShortcutController( 63 QuickstepLauncher launcher, SplitSelectStateController controller) { 64 mLauncher = launcher; 65 mController = controller; 66 mOverviewComponentObserver = OverviewComponentObserver.INSTANCE.get(launcher); 67 68 mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize( 69 R.dimen.split_placeholder_size); 70 mSplitPlaceholderInset = mLauncher.getResources().getDimensionPixelSize( 71 R.dimen.split_placeholder_inset); 72 } 73 74 @BinderThread enterStageSplit(boolean leftOrTop)75 public void enterStageSplit(boolean leftOrTop) { 76 if (TopTaskTracker.INSTANCE.get(mLauncher).getRunningSplitTaskIds().length == 2) { 77 // Do not enter stage split from keyboard shortcuts if the user is already in split 78 return; 79 } 80 RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks( 81 SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext())); 82 SplitWithKeyboardShortcutRecentsAnimationListener listener = 83 new SplitWithKeyboardShortcutRecentsAnimationListener(leftOrTop); 84 85 MAIN_EXECUTOR.execute(() -> { 86 callbacks.addListener(listener); 87 UI_HELPER_EXECUTOR.execute(() -> { 88 // Transition from fullscreen app to enter stage split in launcher with 89 // recents animation 90 final ActivityOptions options = ActivityOptions.makeBasic(); 91 options.setPendingIntentBackgroundActivityStartMode( 92 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); 93 options.setTransientLaunch(); 94 SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()) 95 .startRecentsActivity(mOverviewComponentObserver.getOverviewIntent(), 96 ActivityOptions.makeBasic(), callbacks, 97 false /* useSyntheticRecentsTransition */); 98 }); 99 }); 100 } 101 102 private class SplitWithKeyboardShortcutRecentsAnimationListener implements 103 RecentsAnimationCallbacks.RecentsAnimationListener { 104 105 private final boolean mLeftOrTop; 106 private final Rect mTempRect = new Rect(); 107 private final ActivityManager.RunningTaskInfo mRunningTaskInfo; 108 SplitWithKeyboardShortcutRecentsAnimationListener(boolean leftOrTop)109 private SplitWithKeyboardShortcutRecentsAnimationListener(boolean leftOrTop) { 110 mLeftOrTop = leftOrTop; 111 mRunningTaskInfo = ActivityManagerWrapper.getInstance().getRunningTask(); 112 } 113 114 @Override onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets, TransitionInfo transitionInfo)115 public void onRecentsAnimationStart(RecentsAnimationController controller, 116 RecentsAnimationTargets targets, TransitionInfo transitionInfo) { 117 mController.setInitialTaskSelect(mRunningTaskInfo, 118 mLeftOrTop ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT, 119 null /* itemInfo */, 120 mLeftOrTop ? LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_LEFT_TOP 121 : LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_RIGHT_BOTTOM); 122 123 RecentsView recentsView = mLauncher.getOverviewPanel(); 124 recentsView.getPagedOrientationHandler().getInitialSplitPlaceholderBounds( 125 mSplitPlaceholderSize, mSplitPlaceholderInset, mLauncher.getDeviceProfile(), 126 mController.getActiveSplitStagePosition(), mTempRect); 127 128 PendingAnimation anim = new PendingAnimation( 129 SplitAnimationTimings.TABLET_HOME_TO_SPLIT.getDuration()); 130 RectF startingTaskRect = new RectF(); 131 final FloatingTaskView floatingTaskView = FloatingTaskView.getFloatingTaskView( 132 mLauncher, mLauncher.getDragLayer(), 133 controller.screenshotTask(mRunningTaskInfo.taskId).getThumbnail(), 134 null /* icon */, startingTaskRect); 135 Task task = Task.from(new Task.TaskKey(mRunningTaskInfo), mRunningTaskInfo, 136 false /* isLocked */); 137 RecentsModel.INSTANCE.get(mLauncher.getApplicationContext()) 138 .getIconCache() 139 .getIconInBackground( 140 task, 141 (icon, contentDescription, title) -> floatingTaskView.setIcon(icon)); 142 floatingTaskView.setAlpha(1); 143 floatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect, 144 false /* fadeWithThumbnail */, true /* isStagedTask */); 145 mController.setFirstFloatingTaskView(floatingTaskView); 146 147 anim.addListener(new AnimatorListenerAdapter() { 148 @Override 149 public void onAnimationStart(Animator animation) { 150 controller.finish( 151 true /* toRecents */, 152 () -> { 153 LauncherTaskbarUIController controller = 154 mLauncher.getTaskbarUIController(); 155 if (controller != null) { 156 controller.updateTaskbarLauncherStateGoingHome(); 157 } 158 159 }, 160 false /* sendUserLeaveHint */); 161 } 162 163 @Override 164 public void onAnimationCancel(Animator animation) { 165 mLauncher.getDragLayer().removeView(floatingTaskView); 166 mController.getSplitAnimationController() 167 .removeSplitInstructionsView(mLauncher); 168 mController.resetState(); 169 } 170 }); 171 anim.add(mController.getSplitAnimationController() 172 .getShowSplitInstructionsAnim(mLauncher).buildAnim()); 173 anim.buildAnim().start(); 174 } 175 }; 176 } 177