• 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.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