• 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.animation.Animator;
25 import android.content.Intent;
26 import android.graphics.drawable.BitmapDrawable;
27 import android.view.MotionEvent;
28 import android.view.View;
29 import android.window.RemoteTransition;
30 
31 import androidx.annotation.CallSuper;
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 
35 import com.android.launcher3.LauncherState;
36 import com.android.launcher3.Utilities;
37 import com.android.launcher3.model.data.ItemInfo;
38 import com.android.launcher3.model.data.ItemInfoWithIcon;
39 import com.android.launcher3.popup.SystemShortcut;
40 import com.android.launcher3.taskbar.bubbles.BubbleBarController;
41 import com.android.launcher3.util.SplitConfigurationOptions;
42 import com.android.quickstep.GestureState;
43 import com.android.quickstep.RecentsAnimationCallbacks;
44 import com.android.quickstep.util.SplitTask;
45 import com.android.quickstep.views.RecentsView;
46 import com.android.quickstep.views.TaskContainer;
47 import com.android.quickstep.views.TaskView;
48 import com.android.systemui.shared.recents.model.Task;
49 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
50 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
51 
52 import java.io.PrintWriter;
53 import java.util.Collections;
54 import java.util.stream.Stream;
55 
56 /**
57  * Base class for providing different taskbar UI
58  */
59 public class TaskbarUIController implements BubbleBarController.BubbleBarLocationListener {
60     public static final TaskbarUIController DEFAULT = new TaskbarUIController();
61 
62     // Initialized in init.
63     protected TaskbarControllers mControllers;
64 
65     protected boolean mSkipLauncherVisibilityChange;
66 
67     @CallSuper
init(TaskbarControllers taskbarControllers)68     protected void init(TaskbarControllers taskbarControllers) {
69         mControllers = taskbarControllers;
70     }
71 
72     @CallSuper
onDestroy()73     protected void onDestroy() {
74         mControllers = null;
75     }
76 
isTaskbarTouchable()77     protected boolean isTaskbarTouchable() {
78         return true;
79     }
80 
81     /**
82      * This should only be called by TaskbarStashController so that a TaskbarUIController can
83      * disable stashing. All other controllers should use
84      * {@link TaskbarStashController#supportsVisualStashing()} as the source of truth.
85      */
supportsVisualStashing()86     public boolean supportsVisualStashing() {
87         return true;
88     }
89 
onStashedInAppChanged()90     protected void onStashedInAppChanged() { }
91 
92     /**
93      * Called when taskbar icon layout bounds change.
94      */
onIconLayoutBoundsChanged()95     protected void onIconLayoutBoundsChanged() { }
96 
getTaskbarUIControllerName()97     protected String getTaskbarUIControllerName() {
98         return "TaskbarUIController";
99     }
100 
101     /** Called when an icon is launched. */
onTaskbarIconLaunched(ItemInfo item)102     public void onTaskbarIconLaunched(ItemInfo item) { }
103 
getRootView()104     public View getRootView() {
105         return mControllers.taskbarActivityContext.getDragLayer();
106     }
107 
108     /**
109      * Called when swiping from the bottom nav region in fully gestural mode.
110      * @param inProgress True if the animation started, false if we just settled on an end target.
111      */
setSystemGestureInProgress(boolean inProgress)112     public void setSystemGestureInProgress(boolean inProgress) {
113         mControllers.taskbarStashController.setSystemGestureInProgress(inProgress);
114     }
115 
116     /**
117      * Manually closes the overlay window.
118      */
hideOverlayWindow()119     public void hideOverlayWindow() {
120         mControllers.keyboardQuickSwitchController.closeQuickSwitchView();
121         boolean isTransientTaskbar = mControllers.taskbarActivityContext.isTransientTaskbar();
122         if (!isTransientTaskbar || mControllers.taskbarAllAppsController.isOpen()) {
123             mControllers.taskbarOverlayController.hideWindow();
124         }
125     }
126 
127     /**
128      * User expands PiP to full-screen (or split-screen) mode, try to hide the Taskbar.
129      */
onExpandPip()130     public void onExpandPip() {
131         if (mControllers != null) {
132             final TaskbarStashController stashController = mControllers.taskbarStashController;
133             stashController.updateStateForFlag(FLAG_IN_APP, true);
134             stashController.applyState();
135         }
136     }
137 
138     /**
139      * SysUI flags updated, see QuickStepContract.SYSUI_STATE_* values.
140      */
updateStateForSysuiFlags(@ystemUiStateFlags long sysuiFlags)141     public void updateStateForSysuiFlags(@SystemUiStateFlags long sysuiFlags) {
142     }
143 
144     /**
145      * Returns {@code true} iff taskbar is stashed.
146      */
isTaskbarStashed()147     public boolean isTaskbarStashed() {
148         return mControllers.taskbarStashController.isStashed();
149     }
150 
151     /**
152      * Returns {@code true} iff taskbar All Apps is open.
153      */
isTaskbarAllAppsOpen()154     public boolean isTaskbarAllAppsOpen() {
155         return mControllers.taskbarAllAppsController.isOpen();
156     }
157 
158     /**
159      * Called at the end of the swipe gesture on Transient taskbar.
160      */
startTranslationSpring()161     public void startTranslationSpring() {
162         mControllers.taskbarActivityContext.startTranslationSpring();
163     }
164 
165     /**
166      * @param ev MotionEvent in screen coordinates.
167      * @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
168      */
isEventOverAnyTaskbarItem(MotionEvent ev)169     public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
170         return mControllers.taskbarViewController.isEventOverAnyItem(ev)
171                 || mControllers.navbarButtonsViewController.isEventOverAnyItem(ev);
172     }
173 
174     /** Checks if the given {@link MotionEvent} is over the bubble bar views. */
isEventOverBubbleBarViews(MotionEvent ev)175     public boolean isEventOverBubbleBarViews(MotionEvent ev) {
176         return mControllers.bubbleControllers.map(
177                 bubbleControllers ->
178                         bubbleControllers.bubbleStashController.isEventOverBubbleBarViews(ev))
179                 .orElse(false);
180     }
181 
182     /**
183      * Returns true if icons should be aligned to hotseat in the current transition.
184      */
isIconAlignedWithHotseat()185     public boolean isIconAlignedWithHotseat() {
186         return false;
187     }
188 
189     /**
190      * Returns true if hotseat icons are on top of view hierarchy when aligned in the current state.
191      */
isHotseatIconOnTopWhenAligned()192     public boolean isHotseatIconOnTopWhenAligned() {
193         return true;
194     }
195 
isAnimatingToHotseat()196     public boolean isAnimatingToHotseat() {
197         return false;
198     }
199 
200     /**
201      * Skips to the end of the animation to Hotseat - should only be used if
202      * {@link #isAnimatingToHotseat()} returns true.
203      */
endAnimationToHotseat()204     public void endAnimationToHotseat() {}
205 
206     /** Returns {@code true} if Taskbar is currently within overview. */
isInOverviewUi()207     protected boolean isInOverviewUi() {
208         return false;
209     }
210 
211 
212     /**
213      * Toggles all apps UI. Default implementation opens Taskbar All Apps, but may be overridden to
214      * open different Alls Apps variant depending on the context.
215      * @param focusSearch indicates whether All Apps should be opened with search input focused.
216      */
toggleAllApps(boolean focusSearch)217     protected void toggleAllApps(boolean focusSearch) {
218         if (focusSearch) {
219             mControllers.taskbarAllAppsController.toggleSearch();
220         } else {
221             mControllers.taskbarAllAppsController.toggle();
222         }
223     }
224 
225     @CallSuper
dumpLogs(String prefix, PrintWriter pw)226     protected void dumpLogs(String prefix, PrintWriter pw) {
227         pw.println(String.format(
228                 "%sTaskbarUIController: using an instance of %s",
229                 prefix,
230                 getTaskbarUIControllerName()));
231     }
232 
233     /**
234      * Returns RecentsView. Overwritten in LauncherTaskbarUIController and
235      * FallbackTaskbarUIController with Launcher-specific implementations. Returns null for other
236      * UI controllers (like DesktopTaskbarUIController) that don't have a RecentsView.
237      */
getRecentsView()238     public @Nullable RecentsView getRecentsView() {
239         return null;
240     }
241 
startSplitSelection(SplitConfigurationOptions.SplitSelectSource splitSelectSource)242     public void startSplitSelection(SplitConfigurationOptions.SplitSelectSource splitSelectSource) {
243         RecentsView recentsView = getRecentsView();
244         if (recentsView == null) {
245             return;
246         }
247 
248         recentsView.getSplitSelectController().findLastActiveTasksAndRunCallback(
249                 Collections.singletonList(splitSelectSource.getItemInfo().getComponentKey()),
250                 false /* findExactPairMatch */,
251                 foundTasks -> {
252                     @Nullable Task foundTask = foundTasks[0];
253                     splitSelectSource.alreadyRunningTaskId = foundTask == null
254                             ? INVALID_TASK_ID
255                             : foundTask.key.id;
256                     splitSelectSource.animateCurrentTaskDismissal = foundTask != null;
257                     recentsView.initiateSplitSelect(splitSelectSource);
258                 }
259         );
260     }
261 
262     /**
263      * Uses the clicked Taskbar icon to launch a second app for splitscreen.
264      */
triggerSecondAppForSplit(ItemInfoWithIcon info, Intent intent, View startingView)265     public void triggerSecondAppForSplit(ItemInfoWithIcon info, Intent intent, View startingView) {
266         // When launching from Taskbar, e.g. from Overview, set FLAG_IN_APP immediately
267         // to reduce potential visual noise during the app open transition.
268         if (mControllers.taskbarStashController != null) {
269             mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_APP, true);
270             mControllers.taskbarStashController.applyState();
271         }
272 
273         RecentsView recents = getRecentsView();
274         recents.getSplitSelectController().findLastActiveTasksAndRunCallback(
275                 Collections.singletonList(info.getComponentKey()),
276                 false /* findExactPairMatch */,
277                 foundTasks -> {
278                     @Nullable Task foundTask = foundTasks[0];
279                     if (foundTask != null) {
280                         TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
281                         // TODO (b/266482558): This additional null check is needed because there
282                         // are times when our Tasks list doesn't match our TaskViews list (like when
283                         // a tile is removed during {@link RecentsView#applyLoadPlan()}. A clearer
284                         // state management system is in the works so that we don't need to rely on
285                         // null checks as much. See comments at ag/21152798.
286                         if (foundTaskView != null) {
287                             // There is already a running app of this type, use that as second app.
288                             // Get index of task (0 or 1), in case it's a GroupedTaskView
289                             TaskContainer taskContainer =
290                                     foundTaskView.getTaskContainerById(foundTask.key.id);
291                             recents.confirmSplitSelect(
292                                     foundTaskView,
293                                     foundTask,
294                                     taskContainer.getIconView().getDrawable(),
295                                     taskContainer.getSnapshotView(),
296                                     taskContainer.getThumbnail(),
297                                     null /* intent */,
298                                     null /* user */,
299                                     info);
300                             return;
301                         }
302                     }
303 
304                     // No running app of that type, create a new instance as second app.
305                     recents.confirmSplitSelect(
306                             null /* containerTaskView */,
307                             null /* task */,
308                             new BitmapDrawable(info.bitmap.icon),
309                             startingView,
310                             null /* thumbnail */,
311                             intent,
312                             info.user,
313                             info);
314                 }
315         );
316     }
317 
318     /**
319      * Opens the Keyboard Quick Switch View.
320      *
321      * This will set the focus to the first task from the right (from the left in RTL)
322      */
openQuickSwitchView()323     public void openQuickSwitchView() {
324         mControllers.keyboardQuickSwitchController.openQuickSwitchView();
325     }
326 
327     /**
328      * Launches the focused task and closes the Keyboard Quick Switch View.
329      *
330      * If the overlay or view are closed, or the overview task is focused, then Overview is
331      * launched. If the overview task is launched, then the first hidden task is focused.
332      *
333      * @return the index of what task should be focused in ; -1 iff Overview shouldn't be launched
334      */
launchFocusedTask()335     public int launchFocusedTask() {
336         int focusedTaskIndex = mControllers.keyboardQuickSwitchController.launchFocusedTask();
337         mControllers.keyboardQuickSwitchController.closeQuickSwitchView();
338         return focusedTaskIndex;
339     }
340 
341     /**
342      * Launches the given task in split-screen.
343      */
launchSplitTasks( @onNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition)344     public void launchSplitTasks(
345             @NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) { }
346 
347     /**
348      * Returns the matching view (if any) in the taskbar.
349      * @param view The view to match.
350      */
findMatchingView(View view)351     public @Nullable View findMatchingView(View view) {
352         if (!(view.getTag() instanceof ItemInfo)) {
353             return null;
354         }
355         ItemInfo info = (ItemInfo) view.getTag();
356         if (info.container != CONTAINER_HOTSEAT && info.container != CONTAINER_HOTSEAT_PREDICTION) {
357             return null;
358         }
359 
360         // Taskbar has the same items as the hotseat and we can use screenId to find the match.
361         int screenId = info.screenId;
362         View[] views = mControllers.taskbarViewController.getIconViews();
363         for (int i = views.length - 1; i >= 0; --i) {
364             if (views[i] != null
365                     && views[i].getTag() instanceof ItemInfo
366                     && ((ItemInfo) views[i].getTag()).screenId == screenId) {
367                 return views[i];
368             }
369         }
370         return null;
371     }
372 
373     /**
374      * Callback for when launcher state transition completes after user swipes to home.
375      * @param finalState The final state of the transition.
376      */
onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState)377     public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) {
378         // Overridden
379     }
380 
381     /**
382      * Refreshes the resumed state of this ui controller.
383      */
refreshResumedState()384     public void refreshResumedState() {}
385 
386     /**
387      * Returns a stream of split screen menu options appropriate to the device.
388      */
getSplitMenuOptions()389     Stream<SystemShortcut.Factory<BaseTaskbarContext>> getSplitMenuOptions() {
390         return Utilities
391                 .getSplitPositionOptions(mControllers.taskbarActivityContext.getDeviceProfile())
392                 .stream()
393                 .map(mControllers.taskbarPopupController::createSplitShortcutFactory);
394     }
395 
396     /** Adjusts the hotseat for the bubble bar. */
adjustHotseatForBubbleBar(boolean isBubbleBarVisible)397     public void adjustHotseatForBubbleBar(boolean isBubbleBarVisible) {}
398 
399     /**
400      * Launches the focused task in the Keyboard Quick Switch view through the OverviewCommandHelper
401      * <p>
402      * Use this helper method when the focused task may be the overview task.
403      */
launchKeyboardFocusedTask()404     public void launchKeyboardFocusedTask() {
405         mControllers.navButtonController.hideOverview();
406     }
407 
408     /**
409      * Adjusts the taskbar based on the visibility of the launcher.
410      * @param isVisible True if launcher is visible, false otherwise.
411      */
onLauncherVisibilityChanged(boolean isVisible)412     public void onLauncherVisibilityChanged(boolean isVisible) {
413         mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_APP, !isVisible);
414         mControllers.taskbarStashController.applyState();
415     }
416 
417     /**
418      * Request for UI controller to ignore animations for the next callback for the end of recents
419      * animation
420      */
setSkipNextRecentsAnimEnd()421     public void setSkipNextRecentsAnimEnd() {
422         // Overridden
423     }
424 
425     /**
426      * Sets whether the user is going home based on the current gesture.
427      */
setUserIsNotGoingHome(boolean isNotGoingHome)428     public void setUserIsNotGoingHome(boolean isNotGoingHome) {
429         mControllers.taskbarStashController.setUserIsNotGoingHome(isNotGoingHome);
430     }
431 
432     /**
433      * Sets whether to prevent taskbar from reacting to launcher visibility during the recents
434      * transition animation.
435      */
setSkipLauncherVisibilityChange(boolean skip)436     public void setSkipLauncherVisibilityChange(boolean skip) {
437         mSkipLauncherVisibilityChange = skip;
438     }
439 
440     /** Sets whether the hotseat is stashed */
stashHotseat(boolean stash)441     public void stashHotseat(boolean stash) {
442     }
443 
444     @Override
onBubbleBarLocationAnimated(BubbleBarLocation location)445     public void onBubbleBarLocationAnimated(BubbleBarLocation location) {
446     }
447 
448     @Override
onBubbleBarLocationUpdated(BubbleBarLocation location)449     public void onBubbleBarLocationUpdated(BubbleBarLocation location) {
450     }
451 
452     /** Un-stash the hotseat instantly */
unStashHotseatInstantly()453     public void unStashHotseatInstantly() {
454     }
455 
456     /**
457      * Called when we want to unstash taskbar when user performs swipes up gesture.
458      */
onSwipeToUnstashTaskbar()459     public void onSwipeToUnstashTaskbar() {
460     }
461 
462     /**
463      * Called at the end of a gesture (see {@link GestureState.GestureEndTarget}).
464      * @param endTarget Where the gesture animation is going to.
465      * @param callbacks callbacks to track the recents animation lifecycle. The state change is
466      *                 automatically reset once the recents animation finishes
467      * @return An optional Animator to play in parallel with the default gesture end animation.
468      */
getParallelAnimationToGestureEndTarget( GestureState.GestureEndTarget endTarget, long duration, RecentsAnimationCallbacks callbacks)469     public @Nullable Animator getParallelAnimationToGestureEndTarget(
470             GestureState.GestureEndTarget endTarget,
471             long duration,
472             RecentsAnimationCallbacks callbacks) {
473         return null;
474     }
475 }
476