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.enableSplitContextually;
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.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.RecentsAnimationDeviceState;
44 import com.android.quickstep.RecentsAnimationTargets;
45 import com.android.quickstep.RecentsModel;
46 import com.android.quickstep.SystemUiProxy;
47 import com.android.quickstep.TopTaskTracker;
48 import com.android.quickstep.views.FloatingTaskView;
49 import com.android.quickstep.views.RecentsView;
50 import com.android.systemui.shared.recents.model.Task;
51 import com.android.systemui.shared.system.ActivityManagerWrapper;
52 
53 /** Transitions app from fullscreen to stage split when triggered from keyboard shortcuts. */
54 public class SplitWithKeyboardShortcutController {
55 
56     private final QuickstepLauncher mLauncher;
57     private final SplitSelectStateController mController;
58     private final RecentsAnimationDeviceState mDeviceState;
59     private final OverviewComponentObserver mOverviewComponentObserver;
60 
61     private final int mSplitPlaceholderSize;
62     private final int mSplitPlaceholderInset;
63 
SplitWithKeyboardShortcutController(QuickstepLauncher launcher, SplitSelectStateController controller, OverviewComponentObserver overviewComponentObserver, RecentsAnimationDeviceState deviceState)64     public SplitWithKeyboardShortcutController(QuickstepLauncher launcher,
65             SplitSelectStateController controller,
66             OverviewComponentObserver overviewComponentObserver,
67             RecentsAnimationDeviceState deviceState) {
68         mLauncher = launcher;
69         mController = controller;
70         mDeviceState = deviceState;
71         mOverviewComponentObserver = overviewComponentObserver;
72 
73         mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize(
74                 R.dimen.split_placeholder_size);
75         mSplitPlaceholderInset = mLauncher.getResources().getDimensionPixelSize(
76                 R.dimen.split_placeholder_inset);
77     }
78 
79     @BinderThread
enterStageSplit(boolean leftOrTop)80     public void enterStageSplit(boolean leftOrTop) {
81         if (!enableSplitContextually() ||
82                 // Do not enter stage split from keyboard shortcuts if the user is already in split
83                 TopTaskTracker.INSTANCE.get(mLauncher).getRunningSplitTaskIds().length == 2) {
84             return;
85         }
86         RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks(
87                 SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()),
88                 false /* allowMinimizeSplitScreen */);
89         SplitWithKeyboardShortcutRecentsAnimationListener listener =
90                 new SplitWithKeyboardShortcutRecentsAnimationListener(leftOrTop);
91 
92         MAIN_EXECUTOR.execute(() -> {
93             callbacks.addListener(listener);
94             UI_HELPER_EXECUTOR.execute(
95                     // Transition from fullscreen app to enter stage split in launcher with
96                     // recents animation.
97                     () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
98                             mOverviewComponentObserver.getOverviewIntent(),
99                             SystemClock.uptimeMillis(), callbacks, null, null));
100         });
101     }
102 
onDestroy()103     public void onDestroy() {
104         mOverviewComponentObserver.onDestroy();
105         mDeviceState.destroy();
106     }
107 
108     private class SplitWithKeyboardShortcutRecentsAnimationListener implements
109             RecentsAnimationCallbacks.RecentsAnimationListener {
110 
111         private final boolean mLeftOrTop;
112         private final Rect mTempRect = new Rect();
113 
SplitWithKeyboardShortcutRecentsAnimationListener(boolean leftOrTop)114         private SplitWithKeyboardShortcutRecentsAnimationListener(boolean leftOrTop) {
115             mLeftOrTop = leftOrTop;
116         }
117 
118         @Override
onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets)119         public void onRecentsAnimationStart(RecentsAnimationController controller,
120                 RecentsAnimationTargets targets) {
121             ActivityManager.RunningTaskInfo runningTaskInfo =
122                     ActivityManagerWrapper.getInstance().getRunningTask();
123             mController.setInitialTaskSelect(runningTaskInfo,
124                     mLeftOrTop ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT,
125                     null /* itemInfo */,
126                     mLeftOrTop ? LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_LEFT_TOP
127                             : LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_RIGHT_BOTTOM);
128 
129             RecentsView recentsView = mLauncher.getOverviewPanel();
130             recentsView.getPagedOrientationHandler().getInitialSplitPlaceholderBounds(
131                     mSplitPlaceholderSize, mSplitPlaceholderInset, mLauncher.getDeviceProfile(),
132                     mController.getActiveSplitStagePosition(), mTempRect);
133 
134             PendingAnimation anim = new PendingAnimation(
135                     SplitAnimationTimings.TABLET_HOME_TO_SPLIT.getDuration());
136             RectF startingTaskRect = new RectF();
137             final FloatingTaskView floatingTaskView = FloatingTaskView.getFloatingTaskView(
138                     mLauncher, mLauncher.getDragLayer(),
139                     controller.screenshotTask(runningTaskInfo.taskId).getThumbnail(),
140                     null /* icon */, startingTaskRect);
141             RecentsModel.INSTANCE.get(mLauncher.getApplicationContext())
142                     .getIconCache()
143                     .updateIconInBackground(
144                             Task.from(new Task.TaskKey(runningTaskInfo), runningTaskInfo,
145                                     false /* isLocked */),
146                             (task) -> floatingTaskView.setIcon(task.icon));
147             floatingTaskView.setAlpha(1);
148             floatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
149                     false /* fadeWithThumbnail */, true /* isStagedTask */);
150             mController.setFirstFloatingTaskView(floatingTaskView);
151 
152             anim.addListener(new AnimatorListenerAdapter() {
153                 @Override
154                 public void onAnimationStart(Animator animation) {
155                     controller.finish(
156                             true /* toRecents */,
157                             () -> {
158                                 LauncherTaskbarUIController controller =
159                                         mLauncher.getTaskbarUIController();
160                                 if (controller != null) {
161                                     controller.updateTaskbarLauncherStateGoingHome();
162                                 }
163 
164                             },
165                             false /* sendUserLeaveHint */);
166                 }
167 
168                 @Override
169                 public void onAnimationCancel(Animator animation) {
170                     mLauncher.getDragLayer().removeView(floatingTaskView);
171                     mController.getSplitAnimationController()
172                             .removeSplitInstructionsView(mLauncher);
173                     mController.resetState();
174                 }
175             });
176             anim.add(mController.getSplitAnimationController()
177                     .getShowSplitInstructionsAnim(mLauncher).buildAnim());
178             anim.buildAnim().start();
179         }
180     };
181 }
182