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