• 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 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
21 
22 import android.app.Activity;
23 import android.app.ActivityOptions;
24 import android.content.ComponentName;
25 import android.content.Intent;
26 import android.graphics.Bitmap;
27 import android.graphics.Color;
28 import android.graphics.Rect;
29 import android.os.Handler;
30 import android.os.Looper;
31 import android.os.RemoteException;
32 import android.os.UserHandle;
33 import android.util.Log;
34 import android.view.View;
35 
36 import com.android.launcher3.BaseDraggingActivity;
37 import com.android.launcher3.DeviceProfile;
38 import com.android.launcher3.ItemInfo;
39 import com.android.launcher3.Launcher;
40 import com.android.launcher3.LauncherState;
41 import com.android.launcher3.R;
42 import com.android.launcher3.WorkspaceItemInfo;
43 import com.android.launcher3.popup.SystemShortcut;
44 import com.android.launcher3.userevent.nano.LauncherLogProto;
45 import com.android.launcher3.util.InstantAppResolver;
46 import com.android.quickstep.views.RecentsView;
47 import com.android.quickstep.views.TaskThumbnailView;
48 import com.android.quickstep.views.TaskView;
49 import com.android.systemui.shared.recents.ISystemUiProxy;
50 import com.android.systemui.shared.recents.model.Task;
51 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
52 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
53 import com.android.systemui.shared.recents.view.RecentsTransition;
54 import com.android.systemui.shared.system.ActivityCompat;
55 import com.android.systemui.shared.system.ActivityManagerWrapper;
56 import com.android.systemui.shared.system.ActivityOptionsCompat;
57 import com.android.systemui.shared.system.WindowManagerWrapper;
58 
59 import java.util.Collections;
60 import java.util.List;
61 import java.util.function.Consumer;
62 
63 /**
64  * Represents a system shortcut that can be shown for a recent task.
65  */
66 public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut {
67 
68     private static final String TAG = "TaskSystemShortcut";
69 
70     protected T mSystemShortcut;
71 
TaskSystemShortcut(T systemShortcut)72     public TaskSystemShortcut(T systemShortcut) {
73         super(systemShortcut);
74         mSystemShortcut = systemShortcut;
75     }
76 
TaskSystemShortcut(int iconResId, int labelResId)77     protected TaskSystemShortcut(int iconResId, int labelResId) {
78         super(iconResId, labelResId);
79     }
80 
81     @Override
getOnClickListener( BaseDraggingActivity activity, ItemInfo itemInfo)82     public View.OnClickListener getOnClickListener(
83             BaseDraggingActivity activity, ItemInfo itemInfo) {
84         return null;
85     }
86 
getOnClickListener(BaseDraggingActivity activity, TaskView view)87     public View.OnClickListener getOnClickListener(BaseDraggingActivity activity, TaskView view) {
88         Task task = view.getTask();
89 
90         WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
91         dummyInfo.intent = new Intent();
92         ComponentName component = task.getTopComponent();
93         dummyInfo.intent.setComponent(component);
94         dummyInfo.user = UserHandle.of(task.key.userId);
95         dummyInfo.title = TaskUtils.getTitle(activity, task);
96 
97         return getOnClickListenerForTask(activity, task, dummyInfo);
98     }
99 
getOnClickListenerForTask( BaseDraggingActivity activity, Task task, ItemInfo dummyInfo)100     protected View.OnClickListener getOnClickListenerForTask(
101             BaseDraggingActivity activity, Task task, ItemInfo dummyInfo) {
102         return mSystemShortcut.getOnClickListener(activity, dummyInfo);
103     }
104 
105     public static class AppInfo extends TaskSystemShortcut<SystemShortcut.AppInfo> {
AppInfo()106         public AppInfo() {
107             super(new SystemShortcut.AppInfo());
108         }
109     }
110 
111     public static abstract class MultiWindow extends TaskSystemShortcut {
112 
113         private Handler mHandler;
114 
MultiWindow(int iconRes, int textRes)115         public MultiWindow(int iconRes, int textRes) {
116             super(iconRes, textRes);
117             mHandler = new Handler(Looper.getMainLooper());
118         }
119 
isAvailable(BaseDraggingActivity activity, int displayId)120         protected abstract boolean isAvailable(BaseDraggingActivity activity, int displayId);
makeLaunchOptions(Activity activity)121         protected abstract ActivityOptions makeLaunchOptions(Activity activity);
onActivityStarted(BaseDraggingActivity activity)122         protected abstract boolean onActivityStarted(BaseDraggingActivity activity);
123 
124         @Override
getOnClickListener( BaseDraggingActivity activity, TaskView taskView)125         public View.OnClickListener getOnClickListener(
126                 BaseDraggingActivity activity, TaskView taskView) {
127             final Task task  = taskView.getTask();
128             final int taskId = task.key.id;
129             final int displayId = task.key.displayId;
130             if (!task.isDockable) {
131                 return null;
132             }
133             if (!isAvailable(activity, displayId)) {
134                 return null;
135             }
136             final RecentsView recentsView = activity.getOverviewPanel();
137 
138             final TaskThumbnailView thumbnailView = taskView.getThumbnail();
139             return (v -> {
140                 final View.OnLayoutChangeListener onLayoutChangeListener =
141                         new View.OnLayoutChangeListener() {
142                             @Override
143                             public void onLayoutChange(View v, int l, int t, int r, int b,
144                                     int oldL, int oldT, int oldR, int oldB) {
145                                 taskView.getRootView().removeOnLayoutChangeListener(this);
146                                 recentsView.clearIgnoreResetTask(taskId);
147 
148                                 // Start animating in the side pages once launcher has been resized
149                                 recentsView.dismissTask(taskView, false, false);
150                             }
151                         };
152 
153                 final DeviceProfile.OnDeviceProfileChangeListener onDeviceProfileChangeListener =
154                         new DeviceProfile.OnDeviceProfileChangeListener() {
155                             @Override
156                             public void onDeviceProfileChanged(DeviceProfile dp) {
157                                 activity.removeOnDeviceProfileChangeListener(this);
158                                 if (dp.isMultiWindowMode) {
159                                     taskView.getRootView().addOnLayoutChangeListener(
160                                             onLayoutChangeListener);
161                                 }
162                             }
163                         };
164 
165                 dismissTaskMenuView(activity);
166 
167                 ActivityOptions options = makeLaunchOptions(activity);
168                 if (options != null
169                         && ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
170                                 options)) {
171                     if (!onActivityStarted(activity)) {
172                         return;
173                     }
174                     // Add a device profile change listener to kick off animating the side tasks
175                     // once we enter multiwindow mode and relayout
176                     activity.addOnDeviceProfileChangeListener(onDeviceProfileChangeListener);
177 
178                     final Runnable animStartedListener = () -> {
179                         // Hide the task view and wait for the window to be resized
180                         // TODO: Consider animating in launcher and do an in-place start activity
181                         //       afterwards
182                         recentsView.setIgnoreResetTask(taskId);
183                         taskView.setAlpha(0f);
184                     };
185 
186                     final int[] position = new int[2];
187                     thumbnailView.getLocationOnScreen(position);
188                     final int width = (int) (thumbnailView.getWidth() * taskView.getScaleX());
189                     final int height = (int) (thumbnailView.getHeight() * taskView.getScaleY());
190                     final Rect taskBounds = new Rect(position[0], position[1],
191                             position[0] + width, position[1] + height);
192 
193                     // Take the thumbnail of the task without a scrim and apply it back after
194                     float alpha = thumbnailView.getDimAlpha();
195                     thumbnailView.setDimAlpha(0);
196                     Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
197                             taskBounds.width(), taskBounds.height(), thumbnailView, 1f,
198                             Color.BLACK);
199                     thumbnailView.setDimAlpha(alpha);
200 
201                     AppTransitionAnimationSpecsFuture future =
202                             new AppTransitionAnimationSpecsFuture(mHandler) {
203                         @Override
204                         public List<AppTransitionAnimationSpecCompat> composeSpecs() {
205                             return Collections.singletonList(new AppTransitionAnimationSpecCompat(
206                                     taskId, thumbnail, taskBounds));
207                         }
208                     };
209                     WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
210                             future, animStartedListener, mHandler, true /* scaleUp */,
211                             v.getDisplay().getDisplayId());
212                 }
213             });
214         }
215     }
216 
217     public static class SplitScreen extends MultiWindow {
218         public SplitScreen() {
219             super(R.drawable.ic_split_screen, R.string.recent_task_option_split_screen);
220         }
221 
222         @Override
223         protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
224             // Don't show menu-item if already in multi-window and the task is from
225             // the secondary display.
226             // TODO(b/118266305): Temporarily disable splitscreen for secondary display while new
227             // implementation is enabled
228             return !activity.getDeviceProfile().isMultiWindowMode
229                     && (displayId == -1 || displayId == DEFAULT_DISPLAY);
230         }
231 
232         @Override
233         protected ActivityOptions makeLaunchOptions(Activity activity) {
234             final ActivityCompat act = new ActivityCompat(activity);
235             final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition(
236                     act.getDisplayId());
237             if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) {
238                 return null;
239             }
240             boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT;
241             return ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft);
242         }
243 
244         @Override
245         protected boolean onActivityStarted(BaseDraggingActivity activity) {
246             ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
247             try {
248                 sysUiProxy.onSplitScreenInvoked();
249             } catch (RemoteException e) {
250                 Log.w(TAG, "Failed to notify SysUI of split screen: ", e);
251                 return false;
252             }
253             activity.getUserEventDispatcher().logActionOnControl(TAP,
254                     LauncherLogProto.ControlType.SPLIT_SCREEN_TARGET);
255             return true;
256         }
257     }
258 
259     public static class Freeform extends MultiWindow {
260         public Freeform() {
261             super(R.drawable.ic_split_screen, R.string.recent_task_option_freeform);
262         }
263 
264         @Override
265         protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
266             return ActivityManagerWrapper.getInstance().supportsFreeformMultiWindow(activity);
267         }
268 
269         @Override
270         protected ActivityOptions makeLaunchOptions(Activity activity) {
271             return ActivityOptionsCompat.makeFreeformOptions();
272         }
273 
274         @Override
275         protected boolean onActivityStarted(BaseDraggingActivity activity) {
276             Launcher.getLauncher(activity).getStateManager().goToState(LauncherState.NORMAL);
277             return true;
278         }
279     }
280 
281     public static class Pin extends TaskSystemShortcut {
282 
283         private static final String TAG = Pin.class.getSimpleName();
284 
285         private Handler mHandler;
286 
287         public Pin() {
288             super(R.drawable.ic_pin, R.string.recent_task_option_pin);
289             mHandler = new Handler(Looper.getMainLooper());
290         }
291 
292         @Override
293         public View.OnClickListener getOnClickListener(
294                 BaseDraggingActivity activity, TaskView taskView) {
295             ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
296             if (sysUiProxy == null) {
297                 return null;
298             }
299             if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
300                 return null;
301             }
302             if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {
303                 // We shouldn't be able to pin while an app is locked.
304                 return null;
305             }
306             return view -> {
307                 Consumer<Boolean> resultCallback = success -> {
308                     if (success) {
309                         try {
310                             sysUiProxy.startScreenPinning(taskView.getTask().key.id);
311                         } catch (RemoteException e) {
312                             Log.w(TAG, "Failed to start screen pinning: ", e);
313                         }
314                     } else {
315                         taskView.notifyTaskLaunchFailed(TAG);
316                     }
317                 };
318                 taskView.launchTask(true, resultCallback, mHandler);
319                 dismissTaskMenuView(activity);
320             };
321         }
322     }
323 
324     public static class Install extends TaskSystemShortcut<SystemShortcut.Install> {
325         public Install() {
326             super(new SystemShortcut.Install());
327         }
328 
329         @Override
330         protected View.OnClickListener getOnClickListenerForTask(
331                 BaseDraggingActivity activity, Task task, ItemInfo itemInfo) {
332             if (InstantAppResolver.newInstance(activity).isInstantApp(activity,
333                         task.getTopComponent().getPackageName())) {
334                 return mSystemShortcut.createOnClickListener(activity, itemInfo);
335             }
336             return null;
337         }
338     }
339 }
340