1 /* 2 * Copyright (C) 2018 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; 18 19 import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL; 20 import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED; 21 22 import android.annotation.SuppressLint; 23 import android.content.Context; 24 import android.graphics.Insets; 25 import android.graphics.Matrix; 26 import android.graphics.Rect; 27 import android.os.Build; 28 import android.view.View; 29 30 import androidx.annotation.NonNull; 31 import androidx.annotation.RequiresApi; 32 33 import com.android.launcher3.BaseActivity; 34 import com.android.launcher3.R; 35 import com.android.launcher3.model.data.ItemInfo; 36 import com.android.launcher3.model.data.WorkspaceItemInfo; 37 import com.android.launcher3.popup.SystemShortcut; 38 import com.android.launcher3.util.ResourceBasedOverride; 39 import com.android.launcher3.views.ActivityContext; 40 import com.android.launcher3.views.Snackbar; 41 import com.android.quickstep.util.RecentsOrientedState; 42 import com.android.quickstep.views.DesktopTaskView; 43 import com.android.quickstep.views.GroupedTaskView; 44 import com.android.quickstep.views.OverviewActionsView; 45 import com.android.quickstep.views.RecentsView; 46 import com.android.quickstep.views.RecentsViewContainer; 47 import com.android.quickstep.views.TaskThumbnailViewDeprecated; 48 import com.android.quickstep.views.TaskView; 49 import com.android.quickstep.views.TaskView.TaskContainer; 50 import com.android.systemui.shared.recents.model.Task; 51 import com.android.systemui.shared.recents.model.ThumbnailData; 52 53 import java.util.ArrayList; 54 import java.util.List; 55 56 /** 57 * Factory class to create and add an overlays on the TaskView 58 */ 59 public class TaskOverlayFactory implements ResourceBasedOverride { 60 getEnabledShortcuts(TaskView taskView, TaskContainer taskContainer)61 public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView, 62 TaskContainer taskContainer) { 63 final ArrayList<SystemShortcut> shortcuts = new ArrayList<>(); 64 final RecentsViewContainer container = 65 RecentsViewContainer.containerFromContext(taskView.getContext()); 66 for (TaskShortcutFactory menuOption : MENU_OPTIONS) { 67 if (taskView instanceof GroupedTaskView && !menuOption.showForGroupedTask()) { 68 continue; 69 } 70 if (taskView instanceof DesktopTaskView && !menuOption.showForDesktopTask()) { 71 continue; 72 } 73 74 List<SystemShortcut> menuShortcuts = menuOption.getShortcuts(container, taskContainer); 75 if (menuShortcuts == null) { 76 continue; 77 } 78 shortcuts.addAll(menuShortcuts); 79 } 80 return shortcuts; 81 } 82 83 /** Creates a {@link TaskOverlay} associated with the provide {@link TaskContainer}. */ createOverlay(TaskContainer taskContainer)84 public TaskOverlay<?> createOverlay(TaskContainer taskContainer) { 85 return new TaskOverlay<>(taskContainer); 86 } 87 88 /** 89 * Subclasses can attach any system listeners in this method, must be paired with 90 * {@link #removeListeners()} 91 */ initListeners()92 public void initListeners() { 93 } 94 95 /** 96 * Subclasses should remove any system listeners in this method, must be paired with 97 * {@link #initListeners()} 98 */ removeListeners()99 public void removeListeners() { 100 } 101 102 /** 103 * Clears any active state outside of the TaskOverlay lifecycle which might have built 104 * up over time 105 */ clearAllActiveState()106 public void clearAllActiveState() { } 107 108 /** Note that these will be shown in order from top to bottom, if available for the task. */ 109 private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{ 110 TaskShortcutFactory.APP_INFO, 111 TaskShortcutFactory.SPLIT_SELECT, 112 TaskShortcutFactory.PIN, 113 TaskShortcutFactory.INSTALL, 114 TaskShortcutFactory.FREE_FORM, 115 DesktopSystemShortcut.Companion.createFactory(), 116 TaskShortcutFactory.WELLBEING, 117 TaskShortcutFactory.SAVE_APP_PAIR, 118 TaskShortcutFactory.SCREENSHOT, 119 TaskShortcutFactory.MODAL 120 }; 121 122 /** 123 * Overlay on each task handling Overview Action Buttons. 124 */ 125 public static class TaskOverlay<T extends OverviewActionsView> { 126 127 protected final Context mApplicationContext; 128 protected final TaskContainer mTaskContainer; 129 130 private T mActionsView; 131 protected ImageActionsApi mImageApi; 132 TaskOverlay(TaskContainer taskContainer)133 protected TaskOverlay(TaskContainer taskContainer) { 134 mApplicationContext = taskContainer.getTaskView().getContext().getApplicationContext(); 135 mTaskContainer = taskContainer; 136 mImageApi = new ImageActionsApi( 137 mApplicationContext, mTaskContainer.getThumbnailViewDeprecated()::getThumbnail); 138 } 139 getActionsView()140 protected T getActionsView() { 141 if (mActionsView == null) { 142 mActionsView = BaseActivity.fromContext( 143 mTaskContainer.getThumbnailViewDeprecated().getContext()).findViewById( 144 R.id.overview_actions_view); 145 } 146 return mActionsView; 147 } 148 getThumbnailView()149 public TaskThumbnailViewDeprecated getThumbnailView() { 150 return mTaskContainer.getThumbnailViewDeprecated(); 151 } 152 153 /** 154 * Called when the current task is interactive for the user 155 */ initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix, boolean rotated)156 public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix, 157 boolean rotated) { 158 getActionsView().updateDisabledFlags(DISABLED_NO_THUMBNAIL, thumbnail == null); 159 160 if (thumbnail != null) { 161 getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated); 162 boolean isAllowedByPolicy = 163 mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot(); 164 getActionsView().setCallbacks(new OverlayUICallbacksImpl(isAllowedByPolicy, task)); 165 } 166 } 167 168 /** 169 * End rendering live tile in Overview. 170 * 171 * @param callback callback to run, after switching to screenshot 172 */ endLiveTileMode(@onNull Runnable callback)173 public void endLiveTileMode(@NonNull Runnable callback) { 174 RecentsView recentsView = 175 mTaskContainer.getThumbnailViewDeprecated().getTaskView().getRecentsView(); 176 // Task has already been dismissed 177 if (recentsView == null) return; 178 recentsView.switchToScreenshot( 179 () -> recentsView.finishRecentsAnimation(true /* toRecents */, 180 false /* shouldPip */, callback)); 181 } 182 183 /** 184 * Called to save screenshot of the task thumbnail. 185 */ 186 @SuppressLint("NewApi") saveScreenshot(Task task)187 protected void saveScreenshot(Task task) { 188 if (mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot()) { 189 mImageApi.saveScreenshot(mTaskContainer.getThumbnailViewDeprecated().getThumbnail(), 190 getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key); 191 } else { 192 showBlockedByPolicyMessage(); 193 } 194 } 195 enterSplitSelect()196 protected void enterSplitSelect() { 197 RecentsView overviewPanel = 198 mTaskContainer.getThumbnailViewDeprecated().getTaskView().getRecentsView(); 199 // Task has already been dismissed 200 if (overviewPanel == null) return; 201 overviewPanel.initiateSplitSelect( 202 mTaskContainer.getThumbnailViewDeprecated().getTaskView()); 203 } 204 saveAppPair()205 protected void saveAppPair() { 206 GroupedTaskView taskView = 207 (GroupedTaskView) mTaskContainer.getThumbnailViewDeprecated().getTaskView(); 208 taskView.getRecentsView().getSplitSelectController().getAppPairsController() 209 .saveAppPair(taskView); 210 } 211 212 /** 213 * Called when the overlay is no longer used. 214 */ reset()215 public void reset() { 216 } 217 218 /** 219 * Called when the system wants to reset the modal visuals. 220 */ resetModalVisuals()221 public void resetModalVisuals() { 222 } 223 224 /** 225 * Gets the modal state system shortcut. 226 */ getModalStateSystemShortcut(WorkspaceItemInfo itemInfo, View original)227 public SystemShortcut getModalStateSystemShortcut(WorkspaceItemInfo itemInfo, 228 View original) { 229 return null; 230 } 231 232 /** 233 * Sets full screen progress to the task overlay. 234 */ setFullscreenProgress(float progress)235 public void setFullscreenProgress(float progress) { 236 } 237 238 /** 239 * Gets the system shortcut for the screenshot that will be added to the task menu. 240 */ getScreenshotShortcut(RecentsViewContainer container, ItemInfo iteminfo, View originalView)241 public SystemShortcut getScreenshotShortcut(RecentsViewContainer container, 242 ItemInfo iteminfo, View originalView) { 243 return new ScreenshotSystemShortcut(container, iteminfo, originalView); 244 } 245 246 /** 247 * Gets the task snapshot as it is displayed on the screen. 248 * 249 * @return the bounds of the snapshot in screen coordinates. 250 */ getTaskSnapshotBounds()251 public Rect getTaskSnapshotBounds() { 252 int[] location = new int[2]; 253 mTaskContainer.getThumbnailViewDeprecated().getLocationOnScreen(location); 254 255 return new Rect(location[0], location[1], 256 mTaskContainer.getThumbnailViewDeprecated().getWidth() + location[0], 257 mTaskContainer.getThumbnailViewDeprecated().getHeight() + location[1]); 258 } 259 260 /** 261 * Gets the insets that the snapshot is drawn with. 262 * 263 * @return the insets in screen coordinates. 264 */ 265 @RequiresApi(api = Build.VERSION_CODES.Q) getTaskSnapshotInsets()266 public Insets getTaskSnapshotInsets() { 267 return mTaskContainer.getThumbnailViewDeprecated().getScaledInsets(); 268 } 269 270 /** 271 * Called when the device rotated. 272 */ updateOrientationState(RecentsOrientedState state)273 public void updateOrientationState(RecentsOrientedState state) { 274 } 275 showBlockedByPolicyMessage()276 protected void showBlockedByPolicyMessage() { 277 ActivityContext activityContext = ActivityContext.lookupContext( 278 mTaskContainer.getThumbnailViewDeprecated().getContext()); 279 String message = activityContext.getStringCache() != null 280 ? activityContext.getStringCache().disabledByAdminMessage 281 : mTaskContainer.getThumbnailViewDeprecated().getContext().getString( 282 R.string.blocked_by_policy); 283 284 Snackbar.show(BaseActivity.fromContext( 285 mTaskContainer.getThumbnailViewDeprecated().getContext()), message, null); 286 } 287 288 /** Called when the snapshot has updated its full screen drawing parameters. */ setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams)289 public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {} 290 291 /** Sets visibility for the overlay associated elements. */ setVisibility(int visibility)292 public void setVisibility(int visibility) {} 293 294 private class ScreenshotSystemShortcut extends SystemShortcut { 295 296 private final RecentsViewContainer mContainer; 297 ScreenshotSystemShortcut(RecentsViewContainer container, ItemInfo itemInfo, View originalView)298 ScreenshotSystemShortcut(RecentsViewContainer container, ItemInfo itemInfo, 299 View originalView) { 300 super(R.drawable.ic_screenshot, R.string.action_screenshot, container, itemInfo, 301 originalView); 302 mContainer = container; 303 } 304 305 @Override onClick(View view)306 public void onClick(View view) { 307 saveScreenshot( 308 mTaskContainer.getThumbnailViewDeprecated().getTaskView().getFirstTask()); 309 dismissTaskMenuView(); 310 } 311 } 312 313 protected class OverlayUICallbacksImpl implements OverlayUICallbacks { 314 protected final boolean mIsAllowedByPolicy; 315 protected final Task mTask; 316 OverlayUICallbacksImpl(boolean isAllowedByPolicy, Task task)317 public OverlayUICallbacksImpl(boolean isAllowedByPolicy, Task task) { 318 mIsAllowedByPolicy = isAllowedByPolicy; 319 mTask = task; 320 } 321 322 @SuppressLint("NewApi") onScreenshot()323 public void onScreenshot() { 324 endLiveTileMode(() -> saveScreenshot(mTask)); 325 } 326 onSplit()327 public void onSplit() { 328 endLiveTileMode(TaskOverlay.this::enterSplitSelect); 329 } 330 onSaveAppPair()331 public void onSaveAppPair() { 332 endLiveTileMode(TaskOverlay.this::saveAppPair); 333 } 334 } 335 } 336 337 /** 338 * Callbacks the Ui can generate. This is the only way for a Ui to call methods on the 339 * controller. 340 */ 341 public interface OverlayUICallbacks { 342 /** User has indicated they want to screenshot the current task. */ onScreenshot()343 void onScreenshot(); 344 345 /** User wants to start split screen with current app. */ onSplit()346 void onSplit(); 347 348 /** User wants to save an app pair with current group of apps. */ onSaveAppPair()349 void onSaveAppPair(); 350 } 351 } 352