• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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