• 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 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