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