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