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.config.FeatureFlags.ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS; 20 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_LEFT_TOP; 21 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_RIGHT_BOTTOM; 22 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 23 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 24 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; 25 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; 26 27 import android.animation.Animator; 28 import android.animation.AnimatorListenerAdapter; 29 import android.app.ActivityManager; 30 import android.graphics.Rect; 31 import android.graphics.RectF; 32 import android.os.SystemClock; 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.uioverrides.QuickstepLauncher; 39 import com.android.quickstep.OverviewComponentObserver; 40 import com.android.quickstep.RecentsAnimationCallbacks; 41 import com.android.quickstep.RecentsAnimationController; 42 import com.android.quickstep.RecentsAnimationDeviceState; 43 import com.android.quickstep.RecentsAnimationTargets; 44 import com.android.quickstep.RecentsModel; 45 import com.android.quickstep.SystemUiProxy; 46 import com.android.quickstep.views.FloatingTaskView; 47 import com.android.quickstep.views.RecentsView; 48 import com.android.systemui.shared.recents.model.Task; 49 import com.android.systemui.shared.system.ActivityManagerWrapper; 50 51 /** Transitions app from fullscreen to stage split when triggered from keyboard shortcuts. */ 52 public class SplitWithKeyboardShortcutController { 53 54 private final QuickstepLauncher mLauncher; 55 private final SplitSelectStateController mController; 56 private final OverviewComponentObserver mOverviewComponentObserver; 57 58 private final int mSplitPlaceholderSize; 59 private final int mSplitPlaceholderInset; 60 SplitWithKeyboardShortcutController(QuickstepLauncher launcher, SplitSelectStateController controller)61 public SplitWithKeyboardShortcutController(QuickstepLauncher launcher, 62 SplitSelectStateController controller) { 63 mLauncher = launcher; 64 mController = controller; 65 RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState( 66 launcher.getApplicationContext()); 67 mOverviewComponentObserver = new OverviewComponentObserver(launcher.getApplicationContext(), 68 deviceState); 69 70 mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize( 71 R.dimen.split_placeholder_size); 72 mSplitPlaceholderInset = mLauncher.getResources().getDimensionPixelSize( 73 R.dimen.split_placeholder_inset); 74 } 75 76 @BinderThread enterStageSplit(boolean leftOrTop)77 public void enterStageSplit(boolean leftOrTop) { 78 if (!ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS.get()) { 79 return; 80 } 81 RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks( 82 SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()), 83 false /* allowMinimizeSplitScreen */); 84 SplitWithKeyboardShortcutRecentsAnimationListener listener = 85 new SplitWithKeyboardShortcutRecentsAnimationListener(leftOrTop); 86 87 MAIN_EXECUTOR.execute(() -> { 88 callbacks.addListener(listener); 89 UI_HELPER_EXECUTOR.execute( 90 // Transition from fullscreen app to enter stage split in launcher with 91 // recents animation. 92 () -> ActivityManagerWrapper.getInstance().startRecentsActivity( 93 mOverviewComponentObserver.getOverviewIntent(), 94 SystemClock.uptimeMillis(), callbacks, null, null)); 95 }); 96 } 97 onDestroy()98 public void onDestroy() { 99 mOverviewComponentObserver.onDestroy(); 100 } 101 102 private class SplitWithKeyboardShortcutRecentsAnimationListener implements 103 RecentsAnimationCallbacks.RecentsAnimationListener { 104 105 private final boolean mLeftOrTop; 106 private final Rect mTempRect = new Rect(); 107 SplitWithKeyboardShortcutRecentsAnimationListener(boolean leftOrTop)108 private SplitWithKeyboardShortcutRecentsAnimationListener(boolean leftOrTop) { 109 mLeftOrTop = leftOrTop; 110 } 111 112 @Override onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets)113 public void onRecentsAnimationStart(RecentsAnimationController controller, 114 RecentsAnimationTargets targets) { 115 ActivityManager.RunningTaskInfo runningTaskInfo = 116 ActivityManagerWrapper.getInstance().getRunningTask(); 117 mController.setInitialTaskSelect(runningTaskInfo, 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(runningTaskInfo.taskId).thumbnail, 134 null /* icon */, startingTaskRect); 135 RecentsModel.INSTANCE.get(mLauncher.getApplicationContext()) 136 .getIconCache() 137 .updateIconInBackground( 138 Task.from(new Task.TaskKey(runningTaskInfo), runningTaskInfo, 139 false /* isLocked */), 140 (task) -> floatingTaskView.setIcon(task.icon)); 141 floatingTaskView.setAlpha(1); 142 floatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect, 143 false /* fadeWithThumbnail */, true /* isStagedTask */); 144 mController.setFirstFloatingTaskView(floatingTaskView); 145 146 anim.addListener(new AnimatorListenerAdapter() { 147 @Override 148 public void onAnimationStart(Animator animation) { 149 controller.finish(true /* toRecents */, null /* onFinishComplete */, 150 false /* sendUserLeaveHint */); 151 } 152 }); 153 anim.buildAnim().start(); 154 } 155 }; 156 } 157