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.Display.DEFAULT_DISPLAY;
20 
21 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SELECTIONS;
22 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
23 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP;
24 
25 import android.app.Activity;
26 import android.app.ActivityOptions;
27 import android.graphics.Bitmap;
28 import android.graphics.Color;
29 import android.graphics.Rect;
30 import android.os.Handler;
31 import android.os.Looper;
32 import android.view.View;
33 import android.window.SplashScreen;
34 
35 import com.android.launcher3.BaseDraggingActivity;
36 import com.android.launcher3.DeviceProfile;
37 import com.android.launcher3.R;
38 import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
39 import com.android.launcher3.model.WellbeingModel;
40 import com.android.launcher3.popup.SystemShortcut;
41 import com.android.launcher3.popup.SystemShortcut.AppInfo;
42 import com.android.launcher3.util.InstantAppResolver;
43 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
44 import com.android.quickstep.views.RecentsView;
45 import com.android.quickstep.views.TaskThumbnailView;
46 import com.android.quickstep.views.TaskView;
47 import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
48 import com.android.systemui.shared.recents.model.Task;
49 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
50 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
51 import com.android.systemui.shared.recents.view.RecentsTransition;
52 import com.android.systemui.shared.system.ActivityCompat;
53 import com.android.systemui.shared.system.ActivityManagerWrapper;
54 import com.android.systemui.shared.system.ActivityOptionsCompat;
55 import com.android.systemui.shared.system.WindowManagerWrapper;
56 
57 import java.util.Collections;
58 import java.util.List;
59 
60 /**
61  * Represents a system shortcut that can be shown for a recent task.
62  */
63 public interface TaskShortcutFactory {
getShortcut(BaseDraggingActivity activity, TaskIdAttributeContainer taskContainer)64     SystemShortcut getShortcut(BaseDraggingActivity activity,
65             TaskIdAttributeContainer taskContainer);
66 
showForSplitscreen()67     default boolean showForSplitscreen() {
68         return false;
69     }
70 
71     TaskShortcutFactory APP_INFO = new TaskShortcutFactory() {
72         @Override
73         public SystemShortcut getShortcut(BaseDraggingActivity activity,
74                 TaskIdAttributeContainer taskContainer) {
75             TaskView taskView = taskContainer.getTaskView();
76             AppInfo.SplitAccessibilityInfo accessibilityInfo =
77                     new AppInfo.SplitAccessibilityInfo(taskView.containsMultipleTasks(),
78                             TaskUtils.getTitle(taskView.getContext(), taskContainer.getTask()),
79                             taskContainer.getA11yNodeId()
80                     );
81             return new AppInfo(activity, taskContainer.getItemInfo(), accessibilityInfo);
82         }
83 
84         @Override
85         public boolean showForSplitscreen() {
86             return true;
87         }
88     };
89 
90     abstract class MultiWindowFactory implements TaskShortcutFactory {
91 
92         private final int mIconRes;
93         private final int mTextRes;
94         private final LauncherEvent mLauncherEvent;
95 
MultiWindowFactory(int iconRes, int textRes, LauncherEvent launcherEvent)96         MultiWindowFactory(int iconRes, int textRes, LauncherEvent launcherEvent) {
97             mIconRes = iconRes;
98             mTextRes = textRes;
99             mLauncherEvent = launcherEvent;
100         }
101 
isAvailable(BaseDraggingActivity activity, int displayId)102         protected abstract boolean isAvailable(BaseDraggingActivity activity, int displayId);
makeLaunchOptions(Activity activity)103         protected abstract ActivityOptions makeLaunchOptions(Activity activity);
onActivityStarted(BaseDraggingActivity activity)104         protected abstract boolean onActivityStarted(BaseDraggingActivity activity);
105 
106         @Override
getShortcut(BaseDraggingActivity activity, TaskIdAttributeContainer taskContainer)107         public SystemShortcut getShortcut(BaseDraggingActivity activity,
108                 TaskIdAttributeContainer taskContainer) {
109             final Task task  = taskContainer.getTask();
110             if (!task.isDockable) {
111                 return null;
112             }
113             if (!isAvailable(activity, task.key.displayId)) {
114                 return null;
115             }
116             return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskContainer, this,
117                     mLauncherEvent);
118         }
119     }
120 
121     class SplitSelectSystemShortcut extends SystemShortcut {
122         private final TaskView mTaskView;
123         private final SplitPositionOption mSplitPositionOption;
SplitSelectSystemShortcut(BaseDraggingActivity target, TaskView taskView, SplitPositionOption option)124         public SplitSelectSystemShortcut(BaseDraggingActivity target, TaskView taskView,
125                 SplitPositionOption option) {
126             super(option.iconResId, option.textResId, target, taskView.getItemInfo());
127             mTaskView = taskView;
128             mSplitPositionOption = option;
129         }
130 
131         @Override
onClick(View view)132         public void onClick(View view) {
133             mTaskView.initiateSplitSelect(mSplitPositionOption);
134         }
135     }
136 
137     class MultiWindowSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
138 
139         private Handler mHandler;
140 
141         private final RecentsView mRecentsView;
142         private final TaskThumbnailView mThumbnailView;
143         private final TaskView mTaskView;
144         private final MultiWindowFactory mFactory;
145         private final LauncherEvent mLauncherEvent;
146 
MultiWindowSystemShortcut(int iconRes, int textRes, BaseDraggingActivity activity, TaskIdAttributeContainer taskContainer, MultiWindowFactory factory, LauncherEvent launcherEvent)147         public MultiWindowSystemShortcut(int iconRes, int textRes, BaseDraggingActivity activity,
148                 TaskIdAttributeContainer taskContainer, MultiWindowFactory factory,
149                 LauncherEvent launcherEvent) {
150             super(iconRes, textRes, activity, taskContainer.getItemInfo());
151             mLauncherEvent = launcherEvent;
152             mHandler = new Handler(Looper.getMainLooper());
153             mTaskView = taskContainer.getTaskView();
154             mRecentsView = activity.getOverviewPanel();
155             mThumbnailView = taskContainer.getThumbnailView();
156             mFactory = factory;
157         }
158 
159         @Override
onClick(View view)160         public void onClick(View view) {
161             Task.TaskKey taskKey = mTaskView.getTask().key;
162             final int taskId = taskKey.id;
163 
164             final View.OnLayoutChangeListener onLayoutChangeListener =
165                     new View.OnLayoutChangeListener() {
166                         @Override
167                         public void onLayoutChange(View v, int l, int t, int r, int b,
168                                 int oldL, int oldT, int oldR, int oldB) {
169                             mTaskView.getRootView().removeOnLayoutChangeListener(this);
170                             mRecentsView.clearIgnoreResetTask(taskId);
171 
172                             // Start animating in the side pages once launcher has been resized
173                             mRecentsView.dismissTask(mTaskView, false, false);
174                         }
175                     };
176 
177             final DeviceProfile.OnDeviceProfileChangeListener onDeviceProfileChangeListener =
178                     new DeviceProfile.OnDeviceProfileChangeListener() {
179                         @Override
180                         public void onDeviceProfileChanged(DeviceProfile dp) {
181                             mTarget.removeOnDeviceProfileChangeListener(this);
182                             if (dp.isMultiWindowMode) {
183                                 mTaskView.getRootView().addOnLayoutChangeListener(
184                                         onLayoutChangeListener);
185                             }
186                         }
187                     };
188 
189             dismissTaskMenuView(mTarget);
190 
191             ActivityOptions options = mFactory.makeLaunchOptions(mTarget);
192             if (options != null) {
193                 options.setSplashscreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
194             }
195             if (options != null
196                     && ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
197                             options)) {
198                 if (!mFactory.onActivityStarted(mTarget)) {
199                     return;
200                 }
201                 // Add a device profile change listener to kick off animating the side tasks
202                 // once we enter multiwindow mode and relayout
203                 mTarget.addOnDeviceProfileChangeListener(onDeviceProfileChangeListener);
204 
205                 final Runnable animStartedListener = () -> {
206                     // Hide the task view and wait for the window to be resized
207                     // TODO: Consider animating in launcher and do an in-place start activity
208                     //       afterwards
209                     mRecentsView.setIgnoreResetTask(taskId);
210                     mTaskView.setAlpha(0f);
211                 };
212 
213                 final int[] position = new int[2];
214                 mThumbnailView.getLocationOnScreen(position);
215                 final int width = (int) (mThumbnailView.getWidth() * mTaskView.getScaleX());
216                 final int height = (int) (mThumbnailView.getHeight() * mTaskView.getScaleY());
217                 final Rect taskBounds = new Rect(position[0], position[1],
218                         position[0] + width, position[1] + height);
219 
220                 // Take the thumbnail of the task without a scrim and apply it back after
221                 float alpha = mThumbnailView.getDimAlpha();
222                 mThumbnailView.setDimAlpha(0);
223                 Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
224                         taskBounds.width(), taskBounds.height(), mThumbnailView, 1f,
225                         Color.BLACK);
226                 mThumbnailView.setDimAlpha(alpha);
227 
228                 AppTransitionAnimationSpecsFuture future =
229                         new AppTransitionAnimationSpecsFuture(mHandler) {
230                     @Override
231                     public List<AppTransitionAnimationSpecCompat> composeSpecs() {
232                         return Collections.singletonList(new AppTransitionAnimationSpecCompat(
233                                 taskId, thumbnail, taskBounds));
234                     }
235                 };
236                 WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
237                         future, animStartedListener, mHandler, true /* scaleUp */,
238                         taskKey.displayId);
239                 mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
240                         .log(mLauncherEvent);
241             }
242         }
243     }
244 
245     /** @Deprecated */
246     TaskShortcutFactory SPLIT_SCREEN = new MultiWindowFactory(R.drawable.ic_split_screen,
247             R.string.recent_task_option_split_screen, LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP) {
248 
249         @Override
250         protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
251             // Don't show menu-item if already in multi-window and the task is from
252             // the secondary display.
253             // TODO(b/118266305): Temporarily disable splitscreen for secondary display while new
254             // implementation is enabled
255             return !activity.getDeviceProfile().isMultiWindowMode
256                     && (displayId == -1 || displayId == DEFAULT_DISPLAY);
257         }
258 
259         @Override
260         protected ActivityOptions makeLaunchOptions(Activity activity) {
261             final ActivityCompat act = new ActivityCompat(activity);
262             final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition(
263                     act.getDisplayId());
264             if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) {
265                 return null;
266             }
267             boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT;
268             return ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft);
269         }
270 
271         @Override
272         protected boolean onActivityStarted(BaseDraggingActivity activity) {
273             return true;
274         }
275     };
276 
277     TaskShortcutFactory FREE_FORM = new MultiWindowFactory(R.drawable.ic_split_screen,
278             R.string.recent_task_option_freeform, LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP) {
279 
280         @Override
281         protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
282             return ActivityManagerWrapper.getInstance().supportsFreeformMultiWindow(activity);
283         }
284 
285         @Override
286         protected ActivityOptions makeLaunchOptions(Activity activity) {
287             ActivityOptions activityOptions = ActivityOptionsCompat.makeFreeformOptions();
288             // Arbitrary bounds only because freeform is in dev mode right now
289             Rect r = new Rect(50, 50, 200, 200);
290             activityOptions.setLaunchBounds(r);
291             return activityOptions;
292         }
293 
294         @Override
295         protected boolean onActivityStarted(BaseDraggingActivity activity) {
296             activity.returnToHomescreen();
297             return true;
298         }
299     };
300 
301     TaskShortcutFactory PIN = (activity, taskContainer) -> {
302         if (!SystemUiProxy.INSTANCE.get(activity).isActive()) {
303             return null;
304         }
305         if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
306             return null;
307         }
308         if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {
309             // We shouldn't be able to pin while an app is locked.
310             return null;
311         }
312         return new PinSystemShortcut(activity, taskContainer);
313     };
314 
315     class PinSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
316 
317         private static final String TAG = "PinSystemShortcut";
318 
319         private final TaskView mTaskView;
320 
PinSystemShortcut(BaseDraggingActivity target, TaskIdAttributeContainer taskContainer)321         public PinSystemShortcut(BaseDraggingActivity target,
322                 TaskIdAttributeContainer taskContainer) {
323             super(R.drawable.ic_pin, R.string.recent_task_option_pin, target,
324                     taskContainer.getItemInfo());
325             mTaskView = taskContainer.getTaskView();
326         }
327 
328         @Override
onClick(View view)329         public void onClick(View view) {
330             if (mTaskView.launchTaskAnimated() != null) {
331                 SystemUiProxy.INSTANCE.get(mTarget).startScreenPinning(mTaskView.getTask().key.id);
332             }
333             dismissTaskMenuView(mTarget);
334             mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
335                     .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
336         }
337     }
338 
339     TaskShortcutFactory INSTALL = (activity, taskContainer) ->
340             InstantAppResolver.newInstance(activity).isInstantApp(activity,
341                  taskContainer.getTask().getTopComponent().getPackageName())
342                     ? new SystemShortcut.Install(activity, taskContainer.getItemInfo()) : null;
343 
344     TaskShortcutFactory WELLBEING = (activity, taskContainer) ->
345             WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, taskContainer.getItemInfo());
346 
347     TaskShortcutFactory SCREENSHOT = (activity, taskContainer) ->
348             taskContainer.getThumbnailView().getTaskOverlay()
349                     .getScreenshotShortcut(activity, taskContainer.getItemInfo());
350 
351     TaskShortcutFactory MODAL = (activity, taskContainer) -> {
352         if (ENABLE_OVERVIEW_SELECTIONS.get()) {
353             return taskContainer.getThumbnailView()
354                     .getTaskOverlay().getModalStateSystemShortcut(taskContainer.getItemInfo());
355         }
356         return null;
357     };
358 }
359