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