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