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.launcher3.util.MainThreadInitializedObject.forOverride; 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.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.model.data.ItemInfo; 39 import com.android.launcher3.model.data.WorkspaceItemInfo; 40 import com.android.launcher3.popup.SystemShortcut; 41 import com.android.launcher3.util.MainThreadInitializedObject; 42 import com.android.launcher3.util.ResourceBasedOverride; 43 import com.android.quickstep.util.RecentsOrientedState; 44 import com.android.quickstep.views.OverviewActionsView; 45 import com.android.quickstep.views.TaskThumbnailView; 46 import com.android.quickstep.views.TaskView; 47 import com.android.systemui.plugins.OverscrollPlugin; 48 import com.android.systemui.shared.recents.model.Task; 49 import com.android.systemui.shared.recents.model.ThumbnailData; 50 51 import java.util.ArrayList; 52 import java.util.List; 53 54 /** 55 * Factory class to create and add an overlays on the TaskView 56 */ 57 public class TaskOverlayFactory implements ResourceBasedOverride { 58 getEnabledShortcuts(TaskView taskView)59 public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView) { 60 final ArrayList<SystemShortcut> shortcuts = new ArrayList<>(); 61 final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext()); 62 for (TaskShortcutFactory menuOption : MENU_OPTIONS) { 63 SystemShortcut shortcut = menuOption.getShortcut(activity, taskView); 64 if (shortcut != null) { 65 shortcuts.add(shortcut); 66 } 67 } 68 RecentsOrientedState orientedState = taskView.getRecentsView().getPagedViewOrientedState(); 69 boolean canLauncherRotate = orientedState.canRecentsActivityRotate(); 70 boolean isInLandscape = orientedState.getTouchRotation() != ROTATION_0; 71 72 // Add overview actions to the menu when in in-place rotate landscape mode. 73 if (!canLauncherRotate && isInLandscape) { 74 // Add screenshot action to task menu. 75 SystemShortcut screenshotShortcut = TaskShortcutFactory.SCREENSHOT 76 .getShortcut(activity, taskView); 77 if (screenshotShortcut != null) { 78 shortcuts.add(screenshotShortcut); 79 } 80 81 // Add modal action only if display orientation is the same as the device orientation. 82 if (orientedState.getDisplayRotation() == ROTATION_0) { 83 SystemShortcut modalShortcut = TaskShortcutFactory.MODAL 84 .getShortcut(activity, taskView); 85 if (modalShortcut != null) { 86 shortcuts.add(modalShortcut); 87 } 88 } 89 } 90 return shortcuts; 91 } 92 93 public static final MainThreadInitializedObject<TaskOverlayFactory> INSTANCE = 94 forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class); 95 96 /** 97 * @return a launcher-provided OverscrollPlugin if available, otherwise null 98 */ getLocalOverscrollPlugin()99 public OverscrollPlugin getLocalOverscrollPlugin() { 100 return null; 101 } 102 createOverlay(TaskThumbnailView thumbnailView)103 public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) { 104 return new TaskOverlay(thumbnailView); 105 } 106 107 /** Note that these will be shown in order from top to bottom, if available for the task. */ 108 private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{ 109 TaskShortcutFactory.APP_INFO, 110 TaskShortcutFactory.SPLIT_SCREEN, 111 TaskShortcutFactory.PIN, 112 TaskShortcutFactory.INSTALL, 113 TaskShortcutFactory.FREE_FORM, 114 TaskShortcutFactory.WELLBEING 115 }; 116 117 /** 118 * Overlay on each task handling Overview Action Buttons. 119 */ 120 public static class TaskOverlay<T extends OverviewActionsView> { 121 122 private final Context mApplicationContext; 123 protected final TaskThumbnailView mThumbnailView; 124 125 private T mActionsView; 126 private ImageActionsApi mImageApi; 127 private boolean mIsAllowedByPolicy; 128 TaskOverlay(TaskThumbnailView taskThumbnailView)129 protected TaskOverlay(TaskThumbnailView taskThumbnailView) { 130 mApplicationContext = taskThumbnailView.getContext().getApplicationContext(); 131 mThumbnailView = taskThumbnailView; 132 mImageApi = new ImageActionsApi( 133 mApplicationContext, mThumbnailView::getThumbnail); 134 } 135 getActionsView()136 protected T getActionsView() { 137 if (mActionsView == null) { 138 mActionsView = BaseActivity.fromContext(mThumbnailView.getContext()).findViewById( 139 R.id.overview_actions_view); 140 } 141 return mActionsView; 142 } 143 144 /** 145 * Called when the current task is interactive for the user 146 */ initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix, boolean rotated)147 public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix, 148 boolean rotated) { 149 final boolean isAllowedByPolicy = thumbnail.isRealSnapshot; 150 151 getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated); 152 153 getActionsView().setCallbacks(new OverlayUICallbacks() { 154 @Override 155 public void onShare() { 156 if (isAllowedByPolicy) { 157 mImageApi.startShareActivity(); 158 } else { 159 showBlockedByPolicyMessage(); 160 } 161 } 162 163 @SuppressLint("NewApi") 164 @Override 165 public void onScreenshot() { 166 saveScreenshot(task); 167 } 168 }); 169 } 170 171 /** 172 * Called to save screenshot of the task thumbnail. 173 */ 174 @SuppressLint("NewApi") saveScreenshot(Task task)175 private void saveScreenshot(Task task) { 176 if (mThumbnailView.isRealSnapshot()) { 177 mImageApi.saveScreenshot(mThumbnailView.getThumbnail(), 178 getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key); 179 } else { 180 showBlockedByPolicyMessage(); 181 } 182 } 183 184 /** 185 * Called when the overlay is no longer used. 186 */ reset()187 public void reset() { 188 } 189 190 /** 191 * Called when the system wants to reset the modal visuals. 192 */ resetModalVisuals()193 public void resetModalVisuals() { 194 } 195 196 /** 197 * Gets the modal state system shortcut. 198 */ getModalStateSystemShortcut(WorkspaceItemInfo itemInfo)199 public SystemShortcut getModalStateSystemShortcut(WorkspaceItemInfo itemInfo) { 200 return null; 201 } 202 203 /** 204 * Gets the system shortcut for the screenshot that will be added to the task menu. 205 */ getScreenshotShortcut(BaseDraggingActivity activity, ItemInfo iteminfo)206 public SystemShortcut getScreenshotShortcut(BaseDraggingActivity activity, 207 ItemInfo iteminfo) { 208 return new ScreenshotSystemShortcut(activity, iteminfo); 209 } 210 /** 211 * Gets the task snapshot as it is displayed on the screen. 212 * 213 * @return the bounds of the snapshot in screen coordinates. 214 */ getTaskSnapshotBounds()215 public Rect getTaskSnapshotBounds() { 216 int[] location = new int[2]; 217 mThumbnailView.getLocationOnScreen(location); 218 219 return new Rect(location[0], location[1], mThumbnailView.getWidth() + location[0], 220 mThumbnailView.getHeight() + location[1]); 221 } 222 223 /** 224 * Gets the insets that the snapshot is drawn with. 225 * 226 * @return the insets in screen coordinates. 227 */ 228 @RequiresApi(api = Build.VERSION_CODES.Q) getTaskSnapshotInsets()229 public Insets getTaskSnapshotInsets() { 230 return mThumbnailView.getScaledInsets(); 231 } 232 showBlockedByPolicyMessage()233 private void showBlockedByPolicyMessage() { 234 Toast.makeText( 235 mThumbnailView.getContext(), 236 R.string.blocked_by_policy, 237 Toast.LENGTH_LONG).show(); 238 } 239 240 private class ScreenshotSystemShortcut extends SystemShortcut { 241 242 private final BaseDraggingActivity mActivity; 243 ScreenshotSystemShortcut(BaseDraggingActivity activity, ItemInfo itemInfo)244 ScreenshotSystemShortcut(BaseDraggingActivity activity, ItemInfo itemInfo) { 245 super(R.drawable.ic_screenshot, R.string.action_screenshot, activity, itemInfo); 246 mActivity = activity; 247 } 248 249 @Override onClick(View view)250 public void onClick(View view) { 251 saveScreenshot(mThumbnailView.getTaskView().getTask()); 252 dismissTaskMenuView(mActivity); 253 } 254 } 255 } 256 257 /** 258 * Callbacks the Ui can generate. This is the only way for a Ui to call methods on the 259 * controller. 260 */ 261 public interface OverlayUICallbacks { 262 /** User has indicated they want to share the current task. */ onShare()263 void onShare(); 264 265 /** User has indicated they want to screenshot the current task. */ onScreenshot()266 void onScreenshot(); 267 } 268 } 269