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 import android.widget.Toast; 32 33 import androidx.annotation.NonNull; 34 import androidx.annotation.RequiresApi; 35 36 import com.android.launcher3.BaseActivity; 37 import com.android.launcher3.BaseDraggingActivity; 38 import com.android.launcher3.R; 39 import com.android.launcher3.config.FeatureFlags; 40 import com.android.launcher3.model.data.ItemInfo; 41 import com.android.launcher3.model.data.WorkspaceItemInfo; 42 import com.android.launcher3.popup.SystemShortcut; 43 import com.android.launcher3.util.ResourceBasedOverride; 44 import com.android.launcher3.views.ActivityContext; 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 /** Note that these will be shown in order from top to bottom, if available for the task. */ 126 private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{ 127 TaskShortcutFactory.APP_INFO, 128 TaskShortcutFactory.SPLIT_SELECT, 129 TaskShortcutFactory.PIN, 130 TaskShortcutFactory.INSTALL, 131 TaskShortcutFactory.FREE_FORM, 132 TaskShortcutFactory.WELLBEING 133 }; 134 135 /** 136 * Overlay on each task handling Overview Action Buttons. 137 */ 138 public static class TaskOverlay<T extends OverviewActionsView> { 139 140 protected final Context mApplicationContext; 141 protected final TaskThumbnailView mThumbnailView; 142 143 private T mActionsView; 144 protected ImageActionsApi mImageApi; 145 TaskOverlay(TaskThumbnailView taskThumbnailView)146 protected TaskOverlay(TaskThumbnailView taskThumbnailView) { 147 mApplicationContext = taskThumbnailView.getContext().getApplicationContext(); 148 mThumbnailView = taskThumbnailView; 149 mImageApi = new ImageActionsApi( 150 mApplicationContext, mThumbnailView::getThumbnail); 151 } 152 getActionsView()153 protected T getActionsView() { 154 if (mActionsView == null) { 155 mActionsView = BaseActivity.fromContext(mThumbnailView.getContext()).findViewById( 156 R.id.overview_actions_view); 157 } 158 return mActionsView; 159 } 160 161 /** 162 * Called when the current task is interactive for the user 163 */ initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix, boolean rotated)164 public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix, 165 boolean rotated) { 166 getActionsView().updateDisabledFlags(DISABLED_NO_THUMBNAIL, thumbnail == null); 167 168 if (thumbnail != null) { 169 getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated); 170 boolean isAllowedByPolicy = mThumbnailView.isRealSnapshot(); 171 getActionsView().setCallbacks(new OverlayUICallbacksImpl(isAllowedByPolicy, task)); 172 } 173 } 174 175 /** 176 * End rendering live tile in Overview. 177 * 178 * @param callback callback to run, after switching to screenshot 179 */ endLiveTileMode(@onNull Runnable callback)180 public void endLiveTileMode(@NonNull Runnable callback) { 181 RecentsView recentsView = mThumbnailView.getTaskView().getRecentsView(); 182 recentsView.switchToScreenshot( 183 () -> recentsView.finishRecentsAnimation(true /* toRecents */, 184 false /* shouldPip */, callback)); 185 } 186 187 /** 188 * Called to save screenshot of the task thumbnail. 189 */ 190 @SuppressLint("NewApi") saveScreenshot(Task task)191 protected void saveScreenshot(Task task) { 192 if (mThumbnailView.isRealSnapshot()) { 193 mImageApi.saveScreenshot(mThumbnailView.getThumbnail(), 194 getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key); 195 } else { 196 showBlockedByPolicyMessage(); 197 } 198 } 199 enterSplitSelect()200 private void enterSplitSelect() { 201 RecentsView overviewPanel = mThumbnailView.getTaskView().getRecentsView(); 202 overviewPanel.initiateSplitSelect(mThumbnailView.getTaskView()); 203 } 204 205 /** 206 * Called when the overlay is no longer used. 207 */ reset()208 public void reset() { 209 } 210 211 /** 212 * Called when the system wants to reset the modal visuals. 213 */ resetModalVisuals()214 public void resetModalVisuals() { 215 } 216 217 /** 218 * Gets the modal state system shortcut. 219 */ getModalStateSystemShortcut(WorkspaceItemInfo itemInfo, View original)220 public SystemShortcut getModalStateSystemShortcut(WorkspaceItemInfo itemInfo, 221 View original) { 222 return null; 223 } 224 225 /** 226 * Sets full screen progress to the task overlay. 227 */ setFullscreenProgress(float progress)228 public void setFullscreenProgress(float progress) { 229 } 230 231 /** 232 * Gets the system shortcut for the screenshot that will be added to the task menu. 233 */ getScreenshotShortcut(BaseDraggingActivity activity, ItemInfo iteminfo, View originalView)234 public SystemShortcut getScreenshotShortcut(BaseDraggingActivity activity, 235 ItemInfo iteminfo, View originalView) { 236 return new ScreenshotSystemShortcut(activity, iteminfo, originalView); 237 } 238 239 /** 240 * Gets the task snapshot as it is displayed on the screen. 241 * 242 * @return the bounds of the snapshot in screen coordinates. 243 */ getTaskSnapshotBounds()244 public Rect getTaskSnapshotBounds() { 245 int[] location = new int[2]; 246 mThumbnailView.getLocationOnScreen(location); 247 248 return new Rect(location[0], location[1], mThumbnailView.getWidth() + location[0], 249 mThumbnailView.getHeight() + location[1]); 250 } 251 252 /** 253 * Gets the insets that the snapshot is drawn with. 254 * 255 * @return the insets in screen coordinates. 256 */ 257 @RequiresApi(api = Build.VERSION_CODES.Q) getTaskSnapshotInsets()258 public Insets getTaskSnapshotInsets() { 259 return mThumbnailView.getScaledInsets(); 260 } 261 262 /** 263 * Called when the device rotated. 264 */ updateOrientationState(RecentsOrientedState state)265 public void updateOrientationState(RecentsOrientedState state) { 266 } 267 showBlockedByPolicyMessage()268 protected void showBlockedByPolicyMessage() { 269 ActivityContext activityContext = ActivityContext.lookupContext( 270 mThumbnailView.getContext()); 271 String message = activityContext.getStringCache() != null 272 ? activityContext.getStringCache().disabledByAdminMessage 273 : mThumbnailView.getContext().getString(R.string.blocked_by_policy); 274 Toast.makeText( 275 mThumbnailView.getContext(), 276 message, 277 Toast.LENGTH_LONG).show(); 278 } 279 280 /** Called when the snapshot has updated its full screen drawing parameters. */ setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams)281 public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) { 282 } 283 284 private class ScreenshotSystemShortcut extends SystemShortcut { 285 286 private final BaseDraggingActivity mActivity; 287 ScreenshotSystemShortcut(BaseDraggingActivity activity, ItemInfo itemInfo, View originalView)288 ScreenshotSystemShortcut(BaseDraggingActivity activity, ItemInfo itemInfo, 289 View originalView) { 290 super(R.drawable.ic_screenshot, R.string.action_screenshot, activity, itemInfo, 291 originalView); 292 mActivity = activity; 293 } 294 295 @Override onClick(View view)296 public void onClick(View view) { 297 saveScreenshot(mThumbnailView.getTaskView().getTask()); 298 dismissTaskMenuView(mActivity); 299 } 300 } 301 302 protected class OverlayUICallbacksImpl implements OverlayUICallbacks { 303 protected final boolean mIsAllowedByPolicy; 304 protected final Task mTask; 305 OverlayUICallbacksImpl(boolean isAllowedByPolicy, Task task)306 public OverlayUICallbacksImpl(boolean isAllowedByPolicy, Task task) { 307 mIsAllowedByPolicy = isAllowedByPolicy; 308 mTask = task; 309 } 310 311 @SuppressLint("NewApi") onScreenshot()312 public void onScreenshot() { 313 endLiveTileMode(() -> saveScreenshot(mTask)); 314 } 315 onSplit()316 public void onSplit() { 317 endLiveTileMode(TaskOverlay.this::enterSplitSelect); 318 } 319 } 320 } 321 322 /** 323 * Callbacks the Ui can generate. This is the only way for a Ui to call methods on the 324 * controller. 325 */ 326 public interface OverlayUICallbacks { 327 /** User has indicated they want to screenshot the current task. */ onScreenshot()328 void onScreenshot(); 329 330 /** User wants to start split screen with current app. */ onSplit()331 void onSplit(); 332 } 333 } 334