• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 package com.android.launcher3.taskbar;
17 
18 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
19 
20 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
21 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
22 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
23 
24 import android.content.Intent;
25 import android.graphics.drawable.BitmapDrawable;
26 import android.view.MotionEvent;
27 import android.view.View;
28 
29 import androidx.annotation.CallSuper;
30 import androidx.annotation.NonNull;
31 import androidx.annotation.Nullable;
32 
33 import com.android.launcher3.model.data.ItemInfo;
34 import com.android.launcher3.model.data.ItemInfoWithIcon;
35 import com.android.launcher3.util.ComponentKey;
36 import com.android.launcher3.util.DisplayController;
37 import com.android.launcher3.util.SplitConfigurationOptions;
38 import com.android.quickstep.util.GroupTask;
39 import com.android.quickstep.views.RecentsView;
40 import com.android.quickstep.views.TaskView;
41 import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
42 
43 import java.io.PrintWriter;
44 
45 /**
46  * Base class for providing different taskbar UI
47  */
48 public class TaskbarUIController {
49 
50     public static final TaskbarUIController DEFAULT = new TaskbarUIController();
51 
52     // Initialized in init.
53     protected TaskbarControllers mControllers;
54 
55     @CallSuper
init(TaskbarControllers taskbarControllers)56     protected void init(TaskbarControllers taskbarControllers) {
57         mControllers = taskbarControllers;
58     }
59 
60     @CallSuper
onDestroy()61     protected void onDestroy() {
62         mControllers = null;
63     }
64 
isTaskbarTouchable()65     protected boolean isTaskbarTouchable() {
66         return true;
67     }
68 
69     /**
70      * This should only be called by TaskbarStashController so that a TaskbarUIController can
71      * disable stashing. All other controllers should use
72      * {@link TaskbarStashController#supportsVisualStashing()} as the source of truth.
73      */
supportsVisualStashing()74     public boolean supportsVisualStashing() {
75         return true;
76     }
77 
onStashedInAppChanged()78     protected void onStashedInAppChanged() { }
79 
80     /**
81      * Called when taskbar icon layout bounds change.
82      */
onIconLayoutBoundsChanged()83     protected void onIconLayoutBoundsChanged() { }
84 
85     /** Called when an icon is launched. */
86     @CallSuper
onTaskbarIconLaunched(ItemInfo item)87     public void onTaskbarIconLaunched(ItemInfo item) {
88         // When launching from Taskbar, e.g. from Overview, set FLAG_IN_APP immediately instead of
89         // waiting for onPause, to reduce potential visual noise during the app open transition.
90         mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_APP, true);
91         mControllers.taskbarStashController.applyState();
92     }
93 
getRootView()94     public View getRootView() {
95         return mControllers.taskbarActivityContext.getDragLayer();
96     }
97 
98     /**
99      * Called when swiping from the bottom nav region in fully gestural mode.
100      * @param inProgress True if the animation started, false if we just settled on an end target.
101      */
setSystemGestureInProgress(boolean inProgress)102     public void setSystemGestureInProgress(boolean inProgress) {
103         mControllers.taskbarStashController.setSystemGestureInProgress(inProgress);
104     }
105 
106     /**
107      * Manually closes the overlay window.
108      */
hideOverlayWindow()109     public void hideOverlayWindow() {
110         if (!DisplayController.isTransientTaskbar(mControllers.taskbarActivityContext)
111                 || mControllers.taskbarAllAppsController.isOpen()) {
112             mControllers.taskbarOverlayController.hideWindow();
113         }
114     }
115 
116     /**
117      * User expands PiP to full-screen (or split-screen) mode, try to hide the Taskbar.
118      */
onExpandPip()119     public void onExpandPip() {
120         if (mControllers != null) {
121             final TaskbarStashController stashController = mControllers.taskbarStashController;
122             stashController.updateStateForFlag(FLAG_IN_APP, true);
123             stashController.applyState();
124         }
125     }
126 
127     /**
128      * SysUI flags updated, see QuickStepContract.SYSUI_STATE_* values.
129      */
updateStateForSysuiFlags(int sysuiFlags, boolean skipAnim)130     public void updateStateForSysuiFlags(int sysuiFlags, boolean skipAnim){
131     }
132 
133     /**
134      * Returns {@code true} iff taskbar is stashed.
135      */
isTaskbarStashed()136     public boolean isTaskbarStashed() {
137         return mControllers.taskbarStashController.isStashed();
138     }
139 
140     /**
141      * Returns {@code true} iff taskbar All Apps is open.
142      */
isTaskbarAllAppsOpen()143     public boolean isTaskbarAllAppsOpen() {
144         return mControllers.taskbarAllAppsController.isOpen();
145     }
146 
147     /**
148      * Called at the end of the swipe gesture on Transient taskbar.
149      */
startTranslationSpring()150     public void startTranslationSpring() {
151         mControllers.taskbarActivityContext.startTranslationSpring();
152     }
153 
154     /*
155      * @param ev MotionEvent in screen coordinates.
156      * @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
157      */
isEventOverAnyTaskbarItem(MotionEvent ev)158     public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
159         return mControllers.taskbarViewController.isEventOverAnyItem(ev)
160                 || mControllers.navbarButtonsViewController.isEventOverAnyItem(ev);
161     }
162 
163     /**
164      * Returns true if icons should be aligned to hotseat in the current transition.
165      */
isIconAlignedWithHotseat()166     public boolean isIconAlignedWithHotseat() {
167         return false;
168     }
169 
170     /**
171      * Returns true if hotseat icons are on top of view hierarchy when aligned in the current state.
172      */
isHotseatIconOnTopWhenAligned()173     public boolean isHotseatIconOnTopWhenAligned() {
174         return true;
175     }
176 
177     /** Returns {@code true} if Taskbar is currently within overview. */
isInOverview()178     protected boolean isInOverview() {
179         return false;
180     }
181 
182     @CallSuper
dumpLogs(String prefix, PrintWriter pw)183     protected void dumpLogs(String prefix, PrintWriter pw) {
184         pw.println(String.format(
185                 "%sTaskbarUIController: using an instance of %s",
186                 prefix,
187                 getClass().getSimpleName()));
188     }
189 
190     /**
191      * Returns RecentsView. Overwritten in LauncherTaskbarUIController and
192      * FallbackTaskbarUIController with Launcher-specific implementations. Returns null for other
193      * UI controllers (like DesktopTaskbarUIController) that don't have a RecentsView.
194      */
getRecentsView()195     public @Nullable RecentsView getRecentsView() {
196         return null;
197     }
198 
startSplitSelection(SplitConfigurationOptions.SplitSelectSource splitSelectSource)199     public void startSplitSelection(SplitConfigurationOptions.SplitSelectSource splitSelectSource) {
200         RecentsView recentsView = getRecentsView();
201         if (recentsView == null) {
202             return;
203         }
204 
205         ComponentKey componentToBeStaged = new ComponentKey(
206                 splitSelectSource.itemInfo.getTargetComponent(),
207                 splitSelectSource.itemInfo.user);
208         recentsView.getSplitSelectController().findLastActiveTaskAndRunCallback(
209                 componentToBeStaged,
210                 foundTask -> {
211                     splitSelectSource.alreadyRunningTaskId = foundTask == null
212                             ? INVALID_TASK_ID
213                             : foundTask.key.id;
214                     splitSelectSource.animateCurrentTaskDismissal = foundTask != null;
215                     recentsView.initiateSplitSelect(splitSelectSource);
216                 }
217         );
218     }
219 
220     /**
221      * Uses the clicked Taskbar icon to launch a second app for splitscreen.
222      */
triggerSecondAppForSplit(ItemInfoWithIcon info, Intent intent, View startingView)223     public void triggerSecondAppForSplit(ItemInfoWithIcon info, Intent intent, View startingView) {
224         RecentsView recents = getRecentsView();
225         ComponentKey secondAppComponent = new ComponentKey(info.getTargetComponent(), info.user);
226         recents.getSplitSelectController().findLastActiveTaskAndRunCallback(
227                 secondAppComponent,
228                 foundTask -> {
229                     if (foundTask != null) {
230                         TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
231                         // TODO (b/266482558): This additional null check is needed because there
232                         // are times when our Tasks list doesn't match our TaskViews list (like when
233                         // a tile is removed during {@link RecentsView#applyLoadPlan()}. A clearer
234                         // state management system is in the works so that we don't need to rely on
235                         // null checks as much. See comments at ag/21152798.
236                         if (foundTaskView != null) {
237                             // There is already a running app of this type, use that as second app.
238                             // Get index of task (0 or 1), in case it's a GroupedTaskView
239                             TaskIdAttributeContainer taskAttributes =
240                                     foundTaskView.getTaskAttributesById(foundTask.key.id);
241                             recents.confirmSplitSelect(
242                                     foundTaskView,
243                                     foundTask,
244                                     taskAttributes.getIconView().getDrawable(),
245                                     taskAttributes.getThumbnailView(),
246                                     taskAttributes.getThumbnailView().getThumbnail(),
247                                     null /* intent */,
248                                     null /* user */);
249                             return;
250                         }
251                     }
252 
253                     // No running app of that type, create a new instance as second app.
254                     recents.confirmSplitSelect(
255                             null /* containerTaskView */,
256                             null /* task */,
257                             new BitmapDrawable(info.bitmap.icon),
258                             startingView,
259                             null /* thumbnail */,
260                             intent,
261                             info.user);
262                 }
263         );
264     }
265 
266     /**
267      * Opens the Keyboard Quick Switch View.
268      *
269      * This will set the focus to the first task from the right (from the left in RTL)
270      */
openQuickSwitchView()271     public void openQuickSwitchView() {
272         mControllers.keyboardQuickSwitchController.openQuickSwitchView();
273     }
274 
275     /**
276      * Launches the focused task and closes the Keyboard Quick Switch View.
277      *
278      * If the overlay or view are closed, or the overview task is focused, then Overview is
279      * launched. If the overview task is launched, then the first hidden task is focused.
280      *
281      * @return the index of what task should be focused in ; -1 iff Overview shouldn't be launched
282      */
launchFocusedTask()283     public int launchFocusedTask() {
284         int focusedTaskIndex = mControllers.keyboardQuickSwitchController.launchFocusedTask();
285         mControllers.keyboardQuickSwitchController.closeQuickSwitchView();
286         return focusedTaskIndex;
287     }
288 
289     /**
290      * Launches the focused task in splitscreen.
291      *
292      * No-op if the view is not yet open.
293      */
launchSplitTasks(@onNull View taskview, @NonNull GroupTask groupTask)294     public void launchSplitTasks(@NonNull View taskview, @NonNull GroupTask groupTask) { }
295 
296     /**
297      * Returns the matching view (if any) in the taskbar.
298      * @param view The view to match.
299      */
findMatchingView(View view)300     public @Nullable View findMatchingView(View view) {
301         if (!(view.getTag() instanceof ItemInfo)) {
302             return null;
303         }
304         ItemInfo info = (ItemInfo) view.getTag();
305         if (info.container != CONTAINER_HOTSEAT && info.container != CONTAINER_HOTSEAT_PREDICTION) {
306             return null;
307         }
308 
309         // Taskbar has the same items as the hotseat and we can use screenId to find the match.
310         int screenId = info.screenId;
311         View[] views = mControllers.taskbarViewController.getIconViews();
312         for (int i = views.length - 1; i >= 0; --i) {
313             if (views[i] != null
314                     && views[i].getTag() instanceof ItemInfo
315                     && ((ItemInfo) views[i].getTag()).screenId == screenId) {
316                 return views[i];
317             }
318         }
319         return null;
320     }
321 }
322