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.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; 20 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; 21 import static android.view.Surface.ROTATION_0; 22 23 import static com.android.launcher3.Flags.enableGridOnlyOverview; 24 import static com.android.launcher3.Flags.enableRefactorTaskThumbnail; 25 import static com.android.launcher3.Flags.enableShowEnabledShortcutsInAccessibilityMenu; 26 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_CLOSE_APP_TAP; 27 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP; 28 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; 29 30 import android.app.ActivityOptions; 31 import android.graphics.Bitmap; 32 import android.graphics.Color; 33 import android.graphics.Rect; 34 import android.os.Handler; 35 import android.os.Looper; 36 import android.os.RemoteException; 37 import android.provider.Settings; 38 import android.util.Log; 39 import android.view.View; 40 import android.view.WindowInsets; 41 import android.view.WindowManagerGlobal; 42 import android.window.SplashScreen; 43 44 import androidx.annotation.Nullable; 45 46 import com.android.launcher3.DeviceProfile; 47 import com.android.launcher3.R; 48 import com.android.launcher3.logging.StatsLogManager.LauncherEvent; 49 import com.android.launcher3.model.WellbeingModel; 50 import com.android.launcher3.popup.SystemShortcut; 51 import com.android.launcher3.popup.SystemShortcut.AppInfo; 52 import com.android.launcher3.util.InstantAppResolver; 53 import com.android.launcher3.util.SplitConfigurationOptions; 54 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; 55 import com.android.launcher3.views.ActivityContext; 56 import com.android.quickstep.orientation.RecentsPagedOrientationHandler; 57 import com.android.quickstep.util.RecentsOrientedState; 58 import com.android.quickstep.views.GroupedTaskView; 59 import com.android.quickstep.views.RecentsView; 60 import com.android.quickstep.views.RecentsViewContainer; 61 import com.android.quickstep.views.TaskContainer; 62 import com.android.quickstep.views.TaskView; 63 import com.android.systemui.shared.recents.model.Task; 64 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat; 65 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture; 66 import com.android.systemui.shared.recents.view.RecentsTransition; 67 import com.android.systemui.shared.system.ActivityManagerWrapper; 68 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; 69 70 import java.util.Collections; 71 import java.util.List; 72 import java.util.function.Function; 73 import java.util.stream.Collectors; 74 75 /** 76 * Represents a system shortcut that can be shown for a recent task. Appears as a single entry in 77 * the dropdown menu that shows up when you tap an app icon in Overview. 78 */ 79 public interface TaskShortcutFactory { 80 @Nullable getShortcuts(RecentsViewContainer container, TaskContainer taskContainer)81 default List<SystemShortcut> getShortcuts(RecentsViewContainer container, 82 TaskContainer taskContainer) { 83 return null; 84 } 85 86 /** 87 * Returns {@code true} if it should be shown for grouped task; {@code false} otherwise. 88 */ showForGroupedTask()89 default boolean showForGroupedTask() { 90 return false; 91 } 92 93 /** 94 * Returns {@code true} if it should be shown for desktop task; {@code false} otherwise. 95 */ showForDesktopTask()96 default boolean showForDesktopTask() { 97 return false; 98 } 99 100 /** @return a singleton list if the provided shortcut is non-null, null otherwise */ 101 @Nullable createSingletonShortcutList(@ullable SystemShortcut shortcut)102 default List<SystemShortcut> createSingletonShortcutList(@Nullable SystemShortcut shortcut) { 103 if (shortcut != null) { 104 return Collections.singletonList(shortcut); 105 } 106 return null; 107 } 108 109 TaskShortcutFactory APP_INFO = new TaskShortcutFactory() { 110 @Override 111 public List<SystemShortcut> getShortcuts(RecentsViewContainer container, 112 TaskContainer taskContainer) { 113 TaskView taskView = taskContainer.getTaskView(); 114 int actionId = taskContainer.getStagePosition() == STAGE_POSITION_BOTTOM_OR_RIGHT 115 ? R.id.action_app_info_bottom_right 116 : R.id.action_app_info_top_left; 117 118 AppInfo.SplitAccessibilityInfo accessibilityInfo = 119 new AppInfo.SplitAccessibilityInfo(taskView.containsMultipleTasks(), 120 TaskUtils.getTitle(taskView.getContext(), taskContainer.getTask()), 121 actionId 122 ); 123 return Collections.singletonList(new AppInfo(container, taskContainer.getItemInfo(), 124 taskView, accessibilityInfo)); 125 } 126 127 @Override 128 public boolean showForGroupedTask() { 129 return true; 130 } 131 }; 132 133 class SplitSelectSystemShortcut extends SystemShortcut { 134 private final TaskContainer mTaskContainer; 135 private final SplitPositionOption mSplitPositionOption; 136 SplitSelectSystemShortcut(RecentsViewContainer container, TaskContainer taskContainer, TaskView taskView, SplitPositionOption option)137 public SplitSelectSystemShortcut(RecentsViewContainer container, 138 TaskContainer taskContainer, TaskView taskView, 139 SplitPositionOption option) { 140 super(option.iconResId, option.textResId, container, taskContainer.getItemInfo(), 141 taskView); 142 mTaskContainer = taskContainer; 143 mSplitPositionOption = option; 144 } 145 146 @Override onClick(View view)147 public void onClick(View view) { 148 RecentsView recentsView = mTaskContainer.getTaskView().getRecentsView(); 149 if (recentsView != null) { 150 recentsView.initiateSplitSelect( 151 mTaskContainer, 152 mSplitPositionOption.stagePosition, 153 SplitConfigurationOptions.getLogEventForPosition( 154 mSplitPositionOption.stagePosition)); 155 } 156 } 157 } 158 159 /** 160 * A menu item, "Save app pair", that allows the user to preserve the current app combination as 161 * one persistent icon on the Home screen, allowing for quick split screen launching. 162 */ 163 class SaveAppPairSystemShortcut extends SystemShortcut<RecentsViewContainer> { 164 private final GroupedTaskView mTaskView; 165 SaveAppPairSystemShortcut(RecentsViewContainer container, GroupedTaskView taskView, int iconResId)166 public SaveAppPairSystemShortcut(RecentsViewContainer container, GroupedTaskView taskView, 167 int iconResId) { 168 super(iconResId, R.string.save_app_pair, container, taskView.getItemInfo(), taskView); 169 mTaskView = taskView; 170 } 171 172 @Override onClick(View view)173 public void onClick(View view) { 174 dismissTaskMenuView(); 175 ((RecentsView) mTarget.getOverviewPanel()) 176 .getSplitSelectController().getAppPairsController().saveAppPair(mTaskView); 177 } 178 } 179 180 class FreeformSystemShortcut extends SystemShortcut<RecentsViewContainer> { 181 private static final String TAG = "FreeformSystemShortcut"; 182 183 private Handler mHandler; 184 185 private final RecentsView mRecentsView; 186 private final TaskContainer mTaskContainer; 187 private final TaskView mTaskView; 188 private final LauncherEvent mLauncherEvent; 189 FreeformSystemShortcut(int iconRes, int textRes, RecentsViewContainer container, TaskContainer taskContainer, LauncherEvent launcherEvent)190 public FreeformSystemShortcut(int iconRes, int textRes, RecentsViewContainer container, 191 TaskContainer taskContainer, LauncherEvent launcherEvent) { 192 super(iconRes, textRes, container, taskContainer.getItemInfo(), 193 taskContainer.getTaskView()); 194 mLauncherEvent = launcherEvent; 195 mHandler = new Handler(Looper.getMainLooper()); 196 mTaskView = taskContainer.getTaskView(); 197 mRecentsView = container.getOverviewPanel(); 198 mTaskContainer = taskContainer; 199 } 200 201 @Override onClick(View view)202 public void onClick(View view) { 203 dismissTaskMenuView(); 204 RecentsView rv = mTarget.getOverviewPanel(); 205 rv.switchToScreenshot(() -> { 206 rv.finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, () -> { 207 mTarget.returnToHomescreen(); 208 rv.getHandler().post(this::startActivity); 209 }); 210 }); 211 } 212 startActivity()213 private void startActivity() { 214 final ActivityOptions options = makeLaunchOptions(mTarget); 215 if (options == null) { 216 return; 217 } 218 final Task.TaskKey taskKey = mTaskContainer.getTask().key; 219 final int taskId = taskKey.id; 220 options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON); 221 if (ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId, 222 options)) { 223 final Runnable animStartedListener = () -> { 224 // Hide the task view and wait for the window to be resized 225 // TODO: Consider animating in launcher and do an in-place start activity 226 // afterwards 227 mRecentsView.setIgnoreResetTask(taskId); 228 mTaskView.setAlpha(0f); 229 }; 230 231 final int[] position = new int[2]; 232 View snapShotView = mTaskContainer.getSnapshotView(); 233 snapShotView.getLocationOnScreen(position); 234 final int width = (int) (snapShotView.getWidth() * mTaskView.getScaleX()); 235 final int height = (int) (snapShotView.getHeight() * mTaskView.getScaleY()); 236 final Rect taskBounds = new Rect(position[0], position[1], 237 position[0] + width, position[1] + height); 238 239 // Take the thumbnail of the task without a scrim and apply it back after 240 Bitmap thumbnail; 241 if (enableRefactorTaskThumbnail()) { 242 thumbnail = mTaskContainer.getThumbnail(); 243 } else { 244 float alpha = mTaskContainer.getThumbnailViewDeprecated().getDimAlpha(); 245 mTaskContainer.getThumbnailViewDeprecated().setDimAlpha(0); 246 thumbnail = RecentsTransition.drawViewIntoHardwareBitmap( 247 taskBounds.width(), taskBounds.height(), snapShotView, 1f, Color.BLACK); 248 mTaskContainer.getThumbnailViewDeprecated().setDimAlpha(alpha); 249 } 250 251 AppTransitionAnimationSpecsFuture future = 252 new AppTransitionAnimationSpecsFuture(mHandler) { 253 @Override 254 public List<AppTransitionAnimationSpecCompat> composeSpecs() { 255 return Collections.singletonList( 256 new AppTransitionAnimationSpecCompat( 257 taskId, thumbnail, taskBounds)); 258 } 259 }; 260 overridePendingAppTransitionMultiThumbFuture( 261 future, animStartedListener, mHandler, true /* scaleUp */, 262 taskKey.displayId); 263 mTarget.getStatsLogManager().logger().withItemInfo(mTaskContainer.getItemInfo()) 264 .log(mLauncherEvent); 265 } 266 } 267 268 /** 269 * Overrides a pending app transition. 270 */ overridePendingAppTransitionMultiThumbFuture( AppTransitionAnimationSpecsFuture animationSpecFuture, Runnable animStartedCallback, Handler animStartedCallbackHandler, boolean scaleUp, int displayId)271 private void overridePendingAppTransitionMultiThumbFuture( 272 AppTransitionAnimationSpecsFuture animationSpecFuture, Runnable animStartedCallback, 273 Handler animStartedCallbackHandler, boolean scaleUp, int displayId) { 274 try { 275 WindowManagerGlobal.getWindowManagerService() 276 .overridePendingAppTransitionMultiThumbFuture( 277 animationSpecFuture.getFuture(), 278 RecentsTransition.wrapStartedListener(animStartedCallbackHandler, 279 animStartedCallback), scaleUp, displayId); 280 } catch (RemoteException e) { 281 Log.w(TAG, "Failed to override pending app transition (multi-thumbnail future): ", 282 e); 283 } 284 } 285 makeLaunchOptions(RecentsViewContainer container)286 private ActivityOptions makeLaunchOptions(RecentsViewContainer container) { 287 ActivityOptions activityOptions = ActivityOptions.makeBasic(); 288 activityOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM); 289 // Arbitrary bounds only because freeform is in dev mode right now 290 final View decorView = container.getWindow().getDecorView(); 291 final WindowInsets insets = decorView.getRootWindowInsets(); 292 final Rect r = new Rect(0, 0, decorView.getWidth() / 2, decorView.getHeight() / 2); 293 r.offsetTo(insets.getSystemWindowInsetLeft() + 50, 294 insets.getSystemWindowInsetTop() + 50); 295 activityOptions.setLaunchBounds(r); 296 return activityOptions; 297 } 298 } 299 300 class CloseSystemShortcut extends SystemShortcut { 301 private final TaskContainer mTaskContainer; 302 CloseSystemShortcut(int iconResId, int textResId, RecentsViewContainer container, TaskContainer taskContainer)303 public CloseSystemShortcut(int iconResId, int textResId, RecentsViewContainer container, 304 TaskContainer taskContainer) { 305 super(iconResId, textResId, container, taskContainer.getTaskView().getFirstItemInfo(), 306 taskContainer.getTaskView()); 307 mTaskContainer = taskContainer; 308 } 309 310 @Override onClick(View view)311 public void onClick(View view) { 312 TaskView taskView = mTaskContainer.getTaskView(); 313 RecentsView<?, ?> recentsView = taskView.getRecentsView(); 314 if (recentsView != null) { 315 dismissTaskMenuView(); 316 recentsView.dismissTaskView(taskView, true, true); 317 mTarget.getStatsLogManager().logger().withItemInfo(mTaskContainer.getItemInfo()) 318 .log(LAUNCHER_SYSTEM_SHORTCUT_CLOSE_APP_TAP); 319 } 320 } 321 } 322 323 /** 324 * Does NOT add split options in the following scenarios: 325 * * 1. Taskbar is not present AND aren't at least 2 tasks in overview to show split options for 326 * * 2. Split isn't supported by the task itself (non resizable activity) 327 * * 3. We aren't currently in multi-window 328 * * 4. The taskView to show split options for is the focused task AND we haven't started 329 * * scrolling in overview (if we haven't scrolled, there's a split overview action button so 330 * * we don't need this menu option) 331 */ 332 TaskShortcutFactory SPLIT_SELECT = new TaskShortcutFactory() { 333 @Override 334 public List<SystemShortcut> getShortcuts(RecentsViewContainer container, 335 TaskContainer taskContainer) { 336 DeviceProfile deviceProfile = container.getDeviceProfile(); 337 final Task task = taskContainer.getTask(); 338 final int intentFlags = task.key.baseIntent.getFlags(); 339 final TaskView taskView = taskContainer.getTaskView(); 340 final RecentsView recentsView = taskView.getRecentsView(); 341 final RecentsPagedOrientationHandler orientationHandler = 342 recentsView.getPagedOrientationHandler(); 343 344 boolean notEnoughTasksToSplit = 345 !deviceProfile.isTaskbarPresent && recentsView.getTaskViewCount() < 2; 346 boolean isTaskSplitNotSupported = !task.isDockable || 347 (intentFlags & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0; 348 boolean hideForExistingMultiWindow = container.getDeviceProfile().isMultiWindowMode; 349 350 if (notEnoughTasksToSplit || isTaskSplitNotSupported || hideForExistingMultiWindow) { 351 return null; 352 } 353 354 if (!enableShowEnabledShortcutsInAccessibilityMenu()) { 355 boolean isLargeTile = deviceProfile.isTablet && taskView.isLargeTile(); 356 boolean isTaskInExpectedScrollPosition = 357 recentsView.isTaskInExpectedScrollPosition(taskView); 358 if (isLargeTile && isTaskInExpectedScrollPosition) { 359 return null; 360 } 361 } 362 363 return orientationHandler.getSplitPositionOptions(deviceProfile) 364 .stream() 365 .map((Function<SplitPositionOption, SystemShortcut>) option -> 366 new SplitSelectSystemShortcut(container, taskContainer, taskView, 367 option)) 368 .collect(Collectors.toList()); 369 } 370 }; 371 372 TaskShortcutFactory SAVE_APP_PAIR = new TaskShortcutFactory() { 373 @Nullable 374 @Override 375 public List<SystemShortcut> getShortcuts(RecentsViewContainer container, 376 TaskContainer taskContainer) { 377 DeviceProfile deviceProfile = container.getDeviceProfile(); 378 final TaskView taskView = taskContainer.getTaskView(); 379 final RecentsView recentsView = taskView.getRecentsView(); 380 boolean isLargeTile = deviceProfile.isTablet && taskView.isLargeTile(); 381 boolean isInExpectedScrollPosition = 382 recentsView.isTaskInExpectedScrollPosition(taskView); 383 boolean shouldShowActionsButtonInstead = 384 isLargeTile && isInExpectedScrollPosition; 385 386 // No "save app pair" menu item if: 387 // - we are in 3p launcher 388 // - the Overview Actions Button should be visible 389 // - the task view is not a valid save-able split pair 390 if (!recentsView.supportsAppPairs() 391 || shouldShowActionsButtonInstead 392 || !recentsView.getSplitSelectController().getAppPairsController() 393 .canSaveAppPair(taskView)) { 394 return null; 395 } 396 397 int iconResId = deviceProfile.isLeftRightSplit 398 ? R.drawable.ic_save_app_pair_left_right 399 : R.drawable.ic_save_app_pair_up_down; 400 401 return Collections.singletonList( 402 new SaveAppPairSystemShortcut(container, 403 (GroupedTaskView) taskView, iconResId)); 404 } 405 406 @Override 407 public boolean showForGroupedTask() { 408 return true; 409 } 410 }; 411 412 TaskShortcutFactory FREE_FORM = new TaskShortcutFactory() { 413 @Override 414 public List<SystemShortcut> getShortcuts(RecentsViewContainer container, 415 TaskContainer taskContainer) { 416 final Task task = taskContainer.getTask(); 417 if (!task.isDockable) { 418 return null; 419 } 420 if (!isAvailable(container)) { 421 return null; 422 } 423 424 return Collections.singletonList(new FreeformSystemShortcut( 425 R.drawable.ic_caption_desktop_button_foreground, 426 R.string.recent_task_option_freeform, container, taskContainer, 427 LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP)); 428 } 429 430 private boolean isAvailable(RecentsViewContainer container) { 431 return Settings.Global.getInt( 432 container.asContext().getContentResolver(), 433 Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0 434 && !DesktopModeStatus.canEnterDesktopMode(container.asContext()); 435 } 436 }; 437 438 TaskShortcutFactory PIN = new TaskShortcutFactory() { 439 @Override 440 public List<SystemShortcut> getShortcuts(RecentsViewContainer container, 441 TaskContainer taskContainer) { 442 if (!SystemUiProxy.INSTANCE.get(container.asContext()).isActive()) { 443 return null; 444 } 445 if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) { 446 return null; 447 } 448 if (ActivityManagerWrapper.getInstance().isLockToAppActive()) { 449 // We shouldn't be able to pin while an app is locked. 450 return null; 451 } 452 return Collections.singletonList(new PinSystemShortcut(container, taskContainer)); 453 } 454 }; 455 456 class PinSystemShortcut extends SystemShortcut<RecentsViewContainer> { 457 458 private static final String TAG = "PinSystemShortcut"; 459 460 private final TaskContainer mTaskContainer; 461 PinSystemShortcut(RecentsViewContainer target, TaskContainer taskContainer)462 public PinSystemShortcut(RecentsViewContainer target, 463 TaskContainer taskContainer) { 464 super(R.drawable.ic_pin, R.string.recent_task_option_pin, target, 465 taskContainer.getItemInfo(), taskContainer.getTaskView()); 466 mTaskContainer = taskContainer; 467 } 468 469 @Override onClick(View view)470 public void onClick(View view) { 471 if (mTaskContainer.getTaskView().launchAsStaticTile() != null) { 472 SystemUiProxy.INSTANCE.get(mTarget.asContext()).startScreenPinning( 473 mTaskContainer.getTask().key.id); 474 } 475 dismissTaskMenuView(); 476 mTarget.getStatsLogManager().logger().withItemInfo(mTaskContainer.getItemInfo()) 477 .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP); 478 } 479 } 480 481 TaskShortcutFactory INSTALL = new TaskShortcutFactory() { 482 @Override 483 public List<SystemShortcut> getShortcuts(RecentsViewContainer container, 484 TaskContainer taskContainer) { 485 Task t = taskContainer.getTask(); 486 return InstantAppResolver.newInstance(container.asContext()).isInstantApp( 487 t.getTopComponent().getPackageName(), t.getKey().userId) 488 ? Collections.singletonList(new SystemShortcut.Install(container, 489 taskContainer.getItemInfo(), taskContainer.getTaskView())) 490 : null; 491 } 492 }; 493 494 TaskShortcutFactory WELLBEING = new TaskShortcutFactory() { 495 @Override 496 public List<SystemShortcut> getShortcuts(RecentsViewContainer container, 497 TaskContainer taskContainer) { 498 SystemShortcut<ActivityContext> wellbeingShortcut = 499 WellbeingModel.SHORTCUT_FACTORY.getShortcut(container, 500 taskContainer.getItemInfo(), taskContainer.getTaskView()); 501 return createSingletonShortcutList(wellbeingShortcut); 502 } 503 }; 504 505 TaskShortcutFactory SCREENSHOT = new TaskShortcutFactory() { 506 @Override 507 public List<SystemShortcut> getShortcuts(RecentsViewContainer container, 508 TaskContainer taskContainer) { 509 if (enableShowEnabledShortcutsInAccessibilityMenu()) { 510 if (!taskContainer.getOverlay().isRealSnapshot()) { 511 return null; 512 } 513 } else { 514 boolean isTablet = container.getDeviceProfile().isTablet; 515 boolean isGridOnlyOverview = isTablet && enableGridOnlyOverview(); 516 // Extra conditions if it's not grid-only overview 517 if (!isGridOnlyOverview) { 518 RecentsOrientedState orientedState = taskContainer.getTaskView() 519 .getOrientedState(); 520 boolean isFakeLandscape = !orientedState.isRecentsActivityRotationAllowed() 521 && orientedState.getTouchRotation() != ROTATION_0; 522 if (!isFakeLandscape) { 523 return null; 524 } 525 } 526 } 527 528 SystemShortcut screenshotShortcut = taskContainer.getOverlay().getScreenshotShortcut( 529 container, taskContainer.getItemInfo(), taskContainer.getTaskView()); 530 return createSingletonShortcutList(screenshotShortcut); 531 } 532 533 @Override 534 public boolean showForDesktopTask() { 535 return true; 536 } 537 }; 538 539 TaskShortcutFactory MODAL = new TaskShortcutFactory() { 540 @Override 541 public List<SystemShortcut> getShortcuts(RecentsViewContainer container, 542 TaskContainer taskContainer) { 543 if (enableShowEnabledShortcutsInAccessibilityMenu()) { 544 if (!taskContainer.getOverlay().isRealSnapshot()) { 545 return null; 546 } 547 548 // Modal only works with grid size tiles with enableGridOnlyOverview enabled on 549 // tablets / foldables. With enableGridOnlyOverview off, for large tiles it works, 550 // but the tile needs to be in the center of Recents / Overview. 551 boolean isTablet = container.getDeviceProfile().isTablet; 552 RecentsView recentsView = container.getOverviewPanel(); 553 boolean isLargeTileInCenterOfOverview = taskContainer.getTaskView().isLargeTile() 554 && recentsView.isFocusedTaskInExpectedScrollPosition(); 555 if (isTablet 556 && !isLargeTileInCenterOfOverview 557 && !enableGridOnlyOverview()) { 558 return null; 559 } 560 561 boolean isFakeLandscape = !taskContainer.getTaskView().getPagedOrientationHandler() 562 .isLayoutNaturalToLauncher(); 563 if (isFakeLandscape) { 564 return null; 565 } 566 567 if (taskContainer.getOverlay().isThumbnailRotationDifferentFromTask()) { 568 return null; 569 } 570 } else { 571 boolean isTablet = container.getDeviceProfile().isTablet; 572 boolean isGridOnlyOverview = isTablet && enableGridOnlyOverview(); 573 if (!isGridOnlyOverview) { 574 return null; 575 } 576 } 577 578 SystemShortcut modalStateSystemShortcut = 579 taskContainer.getOverlay().getModalStateSystemShortcut( 580 taskContainer.getItemInfo(), taskContainer.getTaskView()); 581 return createSingletonShortcutList(modalStateSystemShortcut); 582 } 583 }; 584 585 TaskShortcutFactory CLOSE = new TaskShortcutFactory() { 586 @Override 587 public List<SystemShortcut> getShortcuts(RecentsViewContainer container, 588 TaskContainer taskContainer) { 589 return Collections.singletonList(new CloseSystemShortcut( 590 R.drawable.ic_close_option, 591 R.string.recent_task_option_close, container, taskContainer)); 592 } 593 594 @Override 595 public boolean showForGroupedTask() { 596 return true; 597 } 598 599 @Override 600 public boolean showForDesktopTask() { 601 return true; 602 } 603 }; 604 } 605