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