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