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