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 com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL;
20 import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
21 
22 import android.annotation.SuppressLint;
23 import android.content.Context;
24 import android.graphics.Insets;
25 import android.graphics.Matrix;
26 import android.graphics.Rect;
27 import android.os.Build;
28 import android.view.View;
29 
30 import androidx.annotation.NonNull;
31 import androidx.annotation.RequiresApi;
32 
33 import com.android.launcher3.BaseActivity;
34 import com.android.launcher3.R;
35 import com.android.launcher3.model.data.ItemInfo;
36 import com.android.launcher3.model.data.WorkspaceItemInfo;
37 import com.android.launcher3.popup.SystemShortcut;
38 import com.android.launcher3.util.ResourceBasedOverride;
39 import com.android.launcher3.views.ActivityContext;
40 import com.android.launcher3.views.Snackbar;
41 import com.android.quickstep.util.RecentsOrientedState;
42 import com.android.quickstep.views.DesktopTaskView;
43 import com.android.quickstep.views.GroupedTaskView;
44 import com.android.quickstep.views.OverviewActionsView;
45 import com.android.quickstep.views.RecentsView;
46 import com.android.quickstep.views.RecentsViewContainer;
47 import com.android.quickstep.views.TaskThumbnailViewDeprecated;
48 import com.android.quickstep.views.TaskView;
49 import com.android.quickstep.views.TaskView.TaskContainer;
50 import com.android.systemui.shared.recents.model.Task;
51 import com.android.systemui.shared.recents.model.ThumbnailData;
52 
53 import java.util.ArrayList;
54 import java.util.List;
55 
56 /**
57  * Factory class to create and add an overlays on the TaskView
58  */
59 public class TaskOverlayFactory implements ResourceBasedOverride {
60 
getEnabledShortcuts(TaskView taskView, TaskContainer taskContainer)61     public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView,
62             TaskContainer taskContainer) {
63         final ArrayList<SystemShortcut> shortcuts = new ArrayList<>();
64         final RecentsViewContainer container =
65                 RecentsViewContainer.containerFromContext(taskView.getContext());
66         for (TaskShortcutFactory menuOption : MENU_OPTIONS) {
67             if (taskView instanceof GroupedTaskView && !menuOption.showForGroupedTask()) {
68                 continue;
69             }
70             if (taskView instanceof DesktopTaskView && !menuOption.showForDesktopTask()) {
71                 continue;
72             }
73 
74             List<SystemShortcut> menuShortcuts = menuOption.getShortcuts(container, taskContainer);
75             if (menuShortcuts == null) {
76                 continue;
77             }
78             shortcuts.addAll(menuShortcuts);
79         }
80         return shortcuts;
81     }
82 
83     /** Creates a {@link TaskOverlay} associated with the provide {@link TaskContainer}. */
createOverlay(TaskContainer taskContainer)84     public TaskOverlay<?> createOverlay(TaskContainer taskContainer) {
85         return new TaskOverlay<>(taskContainer);
86     }
87 
88     /**
89      * Subclasses can attach any system listeners in this method, must be paired with
90      * {@link #removeListeners()}
91      */
initListeners()92     public void initListeners() {
93     }
94 
95     /**
96      * Subclasses should remove any system listeners in this method, must be paired with
97      * {@link #initListeners()}
98      */
removeListeners()99     public void removeListeners() {
100     }
101 
102     /**
103      * Clears any active state outside of the TaskOverlay lifecycle which might have built
104      * up over time
105      */
clearAllActiveState()106     public void clearAllActiveState() { }
107 
108     /** Note that these will be shown in order from top to bottom, if available for the task. */
109     private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{
110             TaskShortcutFactory.APP_INFO,
111             TaskShortcutFactory.SPLIT_SELECT,
112             TaskShortcutFactory.PIN,
113             TaskShortcutFactory.INSTALL,
114             TaskShortcutFactory.FREE_FORM,
115             DesktopSystemShortcut.Companion.createFactory(),
116             TaskShortcutFactory.WELLBEING,
117             TaskShortcutFactory.SAVE_APP_PAIR,
118             TaskShortcutFactory.SCREENSHOT,
119             TaskShortcutFactory.MODAL
120     };
121 
122     /**
123      * Overlay on each task handling Overview Action Buttons.
124      */
125     public static class TaskOverlay<T extends OverviewActionsView> {
126 
127         protected final Context mApplicationContext;
128         protected final TaskContainer mTaskContainer;
129 
130         private T mActionsView;
131         protected ImageActionsApi mImageApi;
132 
TaskOverlay(TaskContainer taskContainer)133         protected TaskOverlay(TaskContainer taskContainer) {
134             mApplicationContext = taskContainer.getTaskView().getContext().getApplicationContext();
135             mTaskContainer = taskContainer;
136             mImageApi = new ImageActionsApi(
137                     mApplicationContext, mTaskContainer.getThumbnailViewDeprecated()::getThumbnail);
138         }
139 
getActionsView()140         protected T getActionsView() {
141             if (mActionsView == null) {
142                 mActionsView = BaseActivity.fromContext(
143                         mTaskContainer.getThumbnailViewDeprecated().getContext()).findViewById(
144                         R.id.overview_actions_view);
145             }
146             return mActionsView;
147         }
148 
getThumbnailView()149         public TaskThumbnailViewDeprecated getThumbnailView() {
150             return mTaskContainer.getThumbnailViewDeprecated();
151         }
152 
153         /**
154          * Called when the current task is interactive for the user
155          */
initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix, boolean rotated)156         public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix,
157                 boolean rotated) {
158             getActionsView().updateDisabledFlags(DISABLED_NO_THUMBNAIL, thumbnail == null);
159 
160             if (thumbnail != null) {
161                 getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
162                 boolean isAllowedByPolicy =
163                         mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot();
164                 getActionsView().setCallbacks(new OverlayUICallbacksImpl(isAllowedByPolicy, task));
165             }
166         }
167 
168         /**
169          * End rendering live tile in Overview.
170          *
171          * @param callback callback to run, after switching to screenshot
172          */
endLiveTileMode(@onNull Runnable callback)173         public void endLiveTileMode(@NonNull Runnable callback) {
174             RecentsView recentsView =
175                     mTaskContainer.getThumbnailViewDeprecated().getTaskView().getRecentsView();
176             // Task has already been dismissed
177             if (recentsView == null) return;
178             recentsView.switchToScreenshot(
179                     () -> recentsView.finishRecentsAnimation(true /* toRecents */,
180                             false /* shouldPip */, callback));
181         }
182 
183         /**
184          * Called to save screenshot of the task thumbnail.
185          */
186         @SuppressLint("NewApi")
saveScreenshot(Task task)187         protected void saveScreenshot(Task task) {
188             if (mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot()) {
189                 mImageApi.saveScreenshot(mTaskContainer.getThumbnailViewDeprecated().getThumbnail(),
190                         getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key);
191             } else {
192                 showBlockedByPolicyMessage();
193             }
194         }
195 
enterSplitSelect()196         protected void enterSplitSelect() {
197             RecentsView overviewPanel =
198                     mTaskContainer.getThumbnailViewDeprecated().getTaskView().getRecentsView();
199             // Task has already been dismissed
200             if (overviewPanel == null) return;
201             overviewPanel.initiateSplitSelect(
202                     mTaskContainer.getThumbnailViewDeprecated().getTaskView());
203         }
204 
saveAppPair()205         protected void saveAppPair() {
206             GroupedTaskView taskView =
207                     (GroupedTaskView) mTaskContainer.getThumbnailViewDeprecated().getTaskView();
208             taskView.getRecentsView().getSplitSelectController().getAppPairsController()
209                     .saveAppPair(taskView);
210         }
211 
212         /**
213          * Called when the overlay is no longer used.
214          */
reset()215         public void reset() {
216         }
217 
218         /**
219          * Called when the system wants to reset the modal visuals.
220          */
resetModalVisuals()221         public void resetModalVisuals() {
222         }
223 
224         /**
225          * Gets the modal state system shortcut.
226          */
getModalStateSystemShortcut(WorkspaceItemInfo itemInfo, View original)227         public SystemShortcut getModalStateSystemShortcut(WorkspaceItemInfo itemInfo,
228                 View original) {
229             return null;
230         }
231 
232         /**
233          * Sets full screen progress to the task overlay.
234          */
setFullscreenProgress(float progress)235         public void setFullscreenProgress(float progress) {
236         }
237 
238         /**
239          * Gets the system shortcut for the screenshot that will be added to the task menu.
240          */
getScreenshotShortcut(RecentsViewContainer container, ItemInfo iteminfo, View originalView)241         public SystemShortcut getScreenshotShortcut(RecentsViewContainer container,
242                 ItemInfo iteminfo, View originalView) {
243             return new ScreenshotSystemShortcut(container, iteminfo, originalView);
244         }
245 
246         /**
247          * Gets the task snapshot as it is displayed on the screen.
248          *
249          * @return the bounds of the snapshot in screen coordinates.
250          */
getTaskSnapshotBounds()251         public Rect getTaskSnapshotBounds() {
252             int[] location = new int[2];
253             mTaskContainer.getThumbnailViewDeprecated().getLocationOnScreen(location);
254 
255             return new Rect(location[0], location[1],
256                     mTaskContainer.getThumbnailViewDeprecated().getWidth() + location[0],
257                     mTaskContainer.getThumbnailViewDeprecated().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 mTaskContainer.getThumbnailViewDeprecated().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             ActivityContext activityContext = ActivityContext.lookupContext(
278                     mTaskContainer.getThumbnailViewDeprecated().getContext());
279             String message = activityContext.getStringCache() != null
280                     ? activityContext.getStringCache().disabledByAdminMessage
281                     : mTaskContainer.getThumbnailViewDeprecated().getContext().getString(
282                             R.string.blocked_by_policy);
283 
284             Snackbar.show(BaseActivity.fromContext(
285                     mTaskContainer.getThumbnailViewDeprecated().getContext()), message, null);
286         }
287 
288         /** Called when the snapshot has updated its full screen drawing parameters. */
setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams)289         public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {}
290 
291         /** Sets visibility for the overlay associated elements. */
setVisibility(int visibility)292         public void setVisibility(int visibility) {}
293 
294         private class ScreenshotSystemShortcut extends SystemShortcut {
295 
296             private final RecentsViewContainer mContainer;
297 
ScreenshotSystemShortcut(RecentsViewContainer container, ItemInfo itemInfo, View originalView)298             ScreenshotSystemShortcut(RecentsViewContainer container, ItemInfo itemInfo,
299                     View originalView) {
300                 super(R.drawable.ic_screenshot, R.string.action_screenshot, container, itemInfo,
301                         originalView);
302                 mContainer = container;
303             }
304 
305             @Override
onClick(View view)306             public void onClick(View view) {
307                 saveScreenshot(
308                         mTaskContainer.getThumbnailViewDeprecated().getTaskView().getFirstTask());
309                 dismissTaskMenuView();
310             }
311         }
312 
313         protected class OverlayUICallbacksImpl implements OverlayUICallbacks {
314             protected final boolean mIsAllowedByPolicy;
315             protected final Task mTask;
316 
OverlayUICallbacksImpl(boolean isAllowedByPolicy, Task task)317             public OverlayUICallbacksImpl(boolean isAllowedByPolicy, Task task) {
318                 mIsAllowedByPolicy = isAllowedByPolicy;
319                 mTask = task;
320             }
321 
322             @SuppressLint("NewApi")
onScreenshot()323             public void onScreenshot() {
324                 endLiveTileMode(() -> saveScreenshot(mTask));
325             }
326 
onSplit()327             public void onSplit() {
328                 endLiveTileMode(TaskOverlay.this::enterSplitSelect);
329             }
330 
onSaveAppPair()331             public void onSaveAppPair() {
332                 endLiveTileMode(TaskOverlay.this::saveAppPair);
333             }
334         }
335     }
336 
337     /**
338      * Callbacks the Ui can generate. This is the only way for a Ui to call methods on the
339      * controller.
340      */
341     public interface OverlayUICallbacks {
342         /** User has indicated they want to screenshot the current task. */
onScreenshot()343         void onScreenshot();
344 
345         /** User wants to start split screen with current app. */
onSplit()346         void onSplit();
347 
348         /** User wants to save an app pair with current group of apps. */
onSaveAppPair()349         void onSaveAppPair();
350     }
351 }
352