1 /*
2  * Copyright (C) 2022 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 package com.android.launcher3.uioverrides;
17 
18 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
19 import static android.os.Trace.TRACE_TAG_APP;
20 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
21 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
22 
23 import static com.android.app.animation.Interpolators.EMPHASIZED;
24 import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE;
25 import static com.android.launcher3.Flags.enablePredictiveBackGesture;
26 import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
27 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
28 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
29 import static com.android.launcher3.LauncherSettings.Animation.DEFAULT_NO_ICON;
30 import static com.android.launcher3.LauncherSettings.Animation.VIEW_BACKGROUND;
31 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
32 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
33 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
34 import static com.android.launcher3.LauncherState.ALL_APPS;
35 import static com.android.launcher3.LauncherState.NORMAL;
36 import static com.android.launcher3.LauncherState.NO_OFFSET;
37 import static com.android.launcher3.LauncherState.OVERVIEW;
38 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
39 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
40 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
41 import static com.android.launcher3.config.FeatureFlags.enableSplitContextually;
42 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
43 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
44 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED;
45 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
46 import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
47 import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
48 import static com.android.launcher3.popup.SystemShortcut.DONT_SUGGEST_APP;
49 import static com.android.launcher3.popup.SystemShortcut.INSTALL;
50 import static com.android.launcher3.popup.SystemShortcut.PRIVATE_PROFILE_INSTALL;
51 import static com.android.launcher3.popup.SystemShortcut.UNINSTALL_APP;
52 import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
53 import static com.android.launcher3.taskbar.LauncherTaskbarUIController.ALL_APPS_PAGE_PROGRESS_INDEX;
54 import static com.android.launcher3.taskbar.LauncherTaskbarUIController.MINUS_ONE_PAGE_PROGRESS_INDEX;
55 import static com.android.launcher3.taskbar.LauncherTaskbarUIController.WIDGETS_PAGE_PROGRESS_INDEX;
56 import static com.android.launcher3.testing.shared.TestProtocol.HINT_STATE_ORDINAL;
57 import static com.android.launcher3.testing.shared.TestProtocol.HINT_STATE_TWO_BUTTON_ORDINAL;
58 import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
59 import static com.android.launcher3.testing.shared.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
60 import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
61 import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
62 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
63 import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
64 import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
65 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
66 import static com.android.window.flags.Flags.enableDesktopWindowingMode;
67 import static com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity;
68 import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
69 
70 import android.animation.Animator;
71 import android.animation.AnimatorListenerAdapter;
72 import android.animation.AnimatorSet;
73 import android.app.ActivityOptions;
74 import android.content.Context;
75 import android.content.Intent;
76 import android.content.IntentSender;
77 import android.content.res.Configuration;
78 import android.graphics.Rect;
79 import android.graphics.RectF;
80 import android.hardware.display.DisplayManager;
81 import android.media.permission.SafeCloseable;
82 import android.os.Build;
83 import android.os.Bundle;
84 import android.os.IBinder;
85 import android.os.IRemoteCallback;
86 import android.os.SystemProperties;
87 import android.os.Trace;
88 import android.util.AttributeSet;
89 import android.view.Display;
90 import android.view.HapticFeedbackConstants;
91 import android.view.KeyEvent;
92 import android.view.View;
93 import android.widget.AnalogClock;
94 import android.widget.TextClock;
95 import android.window.BackEvent;
96 import android.window.OnBackAnimationCallback;
97 import android.window.OnBackInvokedDispatcher;
98 import android.window.RemoteTransition;
99 import android.window.SplashScreen;
100 
101 import androidx.annotation.BinderThread;
102 import androidx.annotation.NonNull;
103 import androidx.annotation.Nullable;
104 import androidx.annotation.RequiresApi;
105 
106 import com.android.app.viewcapture.ViewCaptureFactory;
107 import com.android.launcher3.AbstractFloatingView;
108 import com.android.launcher3.DeviceProfile;
109 import com.android.launcher3.Flags;
110 import com.android.launcher3.InvariantDeviceProfile;
111 import com.android.launcher3.Launcher;
112 import com.android.launcher3.LauncherSettings.Favorites;
113 import com.android.launcher3.LauncherState;
114 import com.android.launcher3.QuickstepAccessibilityDelegate;
115 import com.android.launcher3.QuickstepTransitionManager;
116 import com.android.launcher3.R;
117 import com.android.launcher3.Utilities;
118 import com.android.launcher3.Workspace;
119 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
120 import com.android.launcher3.anim.AnimatorPlaybackController;
121 import com.android.launcher3.anim.PendingAnimation;
122 import com.android.launcher3.apppairs.AppPairIcon;
123 import com.android.launcher3.appprediction.PredictionRowView;
124 import com.android.launcher3.config.FeatureFlags;
125 import com.android.launcher3.desktop.DesktopRecentsTransitionController;
126 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
127 import com.android.launcher3.logging.InstanceId;
128 import com.android.launcher3.logging.StatsLogManager;
129 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
130 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
131 import com.android.launcher3.model.WellbeingModel;
132 import com.android.launcher3.model.data.ItemInfo;
133 import com.android.launcher3.popup.SystemShortcut;
134 import com.android.launcher3.proxy.ProxyActivityStarter;
135 import com.android.launcher3.statehandlers.DepthController;
136 import com.android.launcher3.statehandlers.DesktopVisibilityController;
137 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
138 import com.android.launcher3.statemanager.StateManager.StateHandler;
139 import com.android.launcher3.taskbar.LauncherTaskbarUIController;
140 import com.android.launcher3.taskbar.TaskbarManager;
141 import com.android.launcher3.testing.TestLogging;
142 import com.android.launcher3.testing.shared.TestProtocol;
143 import com.android.launcher3.uioverrides.QuickstepWidgetHolder.QuickstepHolderFactory;
144 import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
145 import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
146 import com.android.launcher3.uioverrides.touchcontrollers.NoButtonNavbarToOverviewTouchController;
147 import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTouchController;
148 import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
149 import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
150 import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
151 import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
152 import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
153 import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
154 import com.android.launcher3.util.ActivityOptionsWrapper;
155 import com.android.launcher3.util.DisplayController;
156 import com.android.launcher3.util.IntSet;
157 import com.android.launcher3.util.NavigationMode;
158 import com.android.launcher3.util.ObjectWrapper;
159 import com.android.launcher3.util.PendingRequestArgs;
160 import com.android.launcher3.util.PendingSplitSelectInfo;
161 import com.android.launcher3.util.RunnableList;
162 import com.android.launcher3.util.SplitConfigurationOptions;
163 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
164 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
165 import com.android.launcher3.util.StartActivityParams;
166 import com.android.launcher3.util.TouchController;
167 import com.android.launcher3.widget.LauncherWidgetHolder;
168 import com.android.quickstep.OverviewCommandHelper;
169 import com.android.quickstep.OverviewComponentObserver;
170 import com.android.quickstep.RecentsAnimationDeviceState;
171 import com.android.quickstep.RecentsModel;
172 import com.android.quickstep.SystemUiProxy;
173 import com.android.quickstep.TaskUtils;
174 import com.android.quickstep.TouchInteractionService.TISBinder;
175 import com.android.quickstep.util.AsyncClockEventDelegate;
176 import com.android.quickstep.util.GroupTask;
177 import com.android.quickstep.util.LauncherUnfoldAnimationController;
178 import com.android.quickstep.util.QuickstepOnboardingPrefs;
179 import com.android.quickstep.util.SplitSelectStateController;
180 import com.android.quickstep.util.SplitToWorkspaceController;
181 import com.android.quickstep.util.SplitWithKeyboardShortcutController;
182 import com.android.quickstep.util.TISBindHelper;
183 import com.android.quickstep.util.unfold.LauncherUnfoldTransitionController;
184 import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider;
185 import com.android.quickstep.views.FloatingTaskView;
186 import com.android.quickstep.views.OverviewActionsView;
187 import com.android.quickstep.views.RecentsView;
188 import com.android.quickstep.views.RecentsViewContainer;
189 import com.android.quickstep.views.TaskView;
190 import com.android.systemui.shared.recents.model.Task;
191 import com.android.systemui.shared.system.ActivityManagerWrapper;
192 import com.android.systemui.unfold.RemoteUnfoldSharedComponent;
193 import com.android.systemui.unfold.UnfoldTransitionFactory;
194 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
195 import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig;
196 import com.android.systemui.unfold.config.UnfoldTransitionConfig;
197 import com.android.systemui.unfold.dagger.UnfoldMain;
198 import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver;
199 import com.android.systemui.unfold.updates.RotationChangeProvider;
200 
201 import kotlin.Unit;
202 
203 import java.io.FileDescriptor;
204 import java.io.PrintWriter;
205 import java.util.ArrayList;
206 import java.util.Arrays;
207 import java.util.Collections;
208 import java.util.List;
209 import java.util.Objects;
210 import java.util.Optional;
211 import java.util.function.BiConsumer;
212 import java.util.function.Predicate;
213 import java.util.stream.Stream;
214 
215 public class QuickstepLauncher extends Launcher implements RecentsViewContainer {
216     private static final boolean TRACE_LAYOUTS =
217             SystemProperties.getBoolean("persist.debug.trace_layouts", false);
218     private static final String TRACE_RELAYOUT_CLASS =
219             SystemProperties.get("persist.debug.trace_request_layout_class", null);
220     public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
221 
222     protected static final String RING_APPEAR_ANIMATION_PREFIX = "RingAppearAnimation\t";
223 
224     private FixedContainerItems mAllAppsPredictions;
225     private HotseatPredictionController mHotseatPredictionController;
226     private DepthController mDepthController;
227     private @Nullable DesktopVisibilityController mDesktopVisibilityController;
228     private QuickstepTransitionManager mAppTransitionManager;
229 
230     private OverviewActionsView<?> mActionsView;
231     private TISBindHelper mTISBindHelper;
232     private @Nullable LauncherTaskbarUIController mTaskbarUIController;
233     // Will be updated when dragging from taskbar.
234     private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
235     private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
236 
237     private SplitSelectStateController mSplitSelectStateController;
238     private SplitWithKeyboardShortcutController mSplitWithKeyboardShortcutController;
239     private SplitToWorkspaceController mSplitToWorkspaceController;
240 
241     /**
242      * If Launcher restarted while in the middle of an Overview split select, it needs this data to
243      * recover. In all other cases this will remain null.
244      */
245     private PendingSplitSelectInfo mPendingSplitSelectInfo = null;
246 
247     @Nullable
248     private DesktopRecentsTransitionController mDesktopRecentsTransitionController;
249 
250     private SafeCloseable mViewCapture;
251 
252     private boolean mEnableWidgetDepth;
253 
254     private boolean mIsPredictiveBackToHomeInProgress;
255 
getLauncher(Context context)256     public static QuickstepLauncher getLauncher(Context context) {
257         return fromContext(context);
258     }
259 
260     @Override
setupViews()261     protected void setupViews() {
262         super.setupViews();
263 
264         mActionsView = findViewById(R.id.overview_actions_view);
265         RecentsView<?,?> overviewPanel = getOverviewPanel();
266         SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this);
267         mSplitSelectStateController =
268                 new SplitSelectStateController(this, mHandler, getStateManager(),
269                         getDepthController(), getStatsLogManager(),
270                         systemUiProxy, RecentsModel.INSTANCE.get(this),
271                         () -> onStateBack());
272         RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(asContext());
273         // TODO(b/337863494): Explore use of the same OverviewComponentObserver across launcher
274         OverviewComponentObserver overviewComponentObserver = new OverviewComponentObserver(
275                 asContext(), deviceState);
276         if (enableDesktopWindowingMode()) {
277             mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
278                     getStateManager(), systemUiProxy, getIApplicationThread(),
279                     getDepthController());
280         }
281         overviewPanel.init(mActionsView, mSplitSelectStateController,
282                 mDesktopRecentsTransitionController);
283         mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(this,
284                 mSplitSelectStateController, overviewComponentObserver, deviceState);
285         mSplitToWorkspaceController = new SplitToWorkspaceController(this,
286                 mSplitSelectStateController);
287         mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize());
288         mActionsView.updateVerticalMargin(DisplayController.getNavigationMode(this));
289 
290         mAppTransitionManager = buildAppTransitionManager();
291         mAppTransitionManager.registerRemoteAnimations();
292         mAppTransitionManager.registerRemoteTransitions();
293 
294         mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
295         mDepthController = new DepthController(this);
296         if (enableDesktopWindowingMode()) {
297             mDesktopVisibilityController = new DesktopVisibilityController(this);
298             mDesktopVisibilityController.registerSystemUiListener();
299             mSplitSelectStateController.initSplitFromDesktopController(this,
300                     overviewComponentObserver);
301         }
302         mHotseatPredictionController = new HotseatPredictionController(this);
303 
304         mEnableWidgetDepth = SystemProperties.getBoolean("ro.launcher.depth.widget", true);
305         getWorkspace().addOverlayCallback(progress ->
306                 onTaskbarInAppDisplayProgressUpdate(progress, MINUS_ONE_PAGE_PROGRESS_INDEX));
307         addBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler());
308     }
309 
310     @Override
logAppLaunch(StatsLogManager statsLogManager, ItemInfo info, InstanceId instanceId)311     public void logAppLaunch(StatsLogManager statsLogManager, ItemInfo info,
312             InstanceId instanceId) {
313         // If the app launch is from any of the surfaces in AllApps then add the InstanceId from
314         // LiveSearchManager to recreate the AllApps session on the server side.
315         if (mAllAppsSessionLogId != null && ALL_APPS.equals(
316                 getStateManager().getCurrentStableState())) {
317             instanceId = mAllAppsSessionLogId;
318         }
319 
320         StatsLogger logger = statsLogManager.logger().withItemInfo(info).withInstanceId(instanceId);
321 
322         if (mAllAppsPredictions != null
323                 && (info.itemType == ITEM_TYPE_APPLICATION
324                 || info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) {
325             int count = mAllAppsPredictions.items.size();
326             for (int i = 0; i < count; i++) {
327                 ItemInfo targetInfo = mAllAppsPredictions.items.get(i);
328                 if (targetInfo.itemType == info.itemType
329                         && targetInfo.user.equals(info.user)
330                         && Objects.equals(targetInfo.getIntent(), info.getIntent())) {
331                     logger.withRank(i);
332                     break;
333                 }
334 
335             }
336         }
337         logger.log(LAUNCHER_APP_LAUNCH_TAP);
338 
339         mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId);
340     }
341 
342     @Override
completeAddShortcut(Intent data, int container, int screenId, int cellX, int cellY, PendingRequestArgs args)343     protected void completeAddShortcut(Intent data, int container, int screenId, int cellX,
344             int cellY, PendingRequestArgs args) {
345         if (container == CONTAINER_HOTSEAT) {
346             mHotseatPredictionController.onDeferredDrop(cellX, cellY);
347         }
348         super.completeAddShortcut(data, container, screenId, cellX, cellY, args);
349     }
350 
351     @Override
createAccessibilityDelegate()352     protected LauncherAccessibilityDelegate createAccessibilityDelegate() {
353         return new QuickstepAccessibilityDelegate(this);
354     }
355 
356     /**
357      * Returns Prediction controller for hybrid hotseat
358      */
getHotseatPredictionController()359     public HotseatPredictionController getHotseatPredictionController() {
360         return mHotseatPredictionController;
361     }
362 
363     @Override
enableHotseatEdu(boolean enable)364     public void enableHotseatEdu(boolean enable) {
365         super.enableHotseatEdu(enable);
366         mHotseatPredictionController.enableHotseatEdu(enable);
367     }
368 
369     /**
370      * Builds the {@link QuickstepTransitionManager} instance to use for managing transitions.
371      */
buildAppTransitionManager()372     protected QuickstepTransitionManager buildAppTransitionManager() {
373         return new QuickstepTransitionManager(this);
374     }
375 
376     @Override
onConfigurationChanged(Configuration newConfig)377     public void onConfigurationChanged(Configuration newConfig) {
378         super.onConfigurationChanged(newConfig);
379         onStateOrResumeChanging(false /* inTransition */);
380     }
381 
382     @Override
startActivitySafely(View v, Intent intent, ItemInfo item)383     public RunnableList startActivitySafely(View v, Intent intent, ItemInfo item) {
384         // Only pause is taskbar controller is not present until the transition (if it exists) ends
385         mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null);
386         PredictionRowView<?> predictionRowView =
387                 getAppsView().getFloatingHeaderView().findFixedRowByType(PredictionRowView.class);
388         // Pause the prediction row updates until the transition (if it exists) ends.
389         predictionRowView.setPredictionUiUpdatePaused(true);
390         RunnableList result = super.startActivitySafely(v, intent, item);
391         if (result == null) {
392             mHotseatPredictionController.setPauseUIUpdate(false);
393             predictionRowView.setPredictionUiUpdatePaused(false);
394         } else {
395             result.add(() -> {
396                 mHotseatPredictionController.setPauseUIUpdate(false);
397                 predictionRowView.setPredictionUiUpdatePaused(false);
398             });
399         }
400         return result;
401     }
402 
403     @Override
startBinding()404     public void startBinding() {
405         super.startBinding();
406         mHotseatPredictionController.verifyUIUpdateNotPaused();
407     }
408 
409     @Override
onActivityFlagsChanged(int changeBits)410     protected void onActivityFlagsChanged(int changeBits) {
411         if ((changeBits & ACTIVITY_STATE_STARTED) != 0) {
412             mDepthController.setActivityStarted(isStarted());
413         }
414 
415         if ((changeBits & ACTIVITY_STATE_RESUMED) != 0) {
416             if (!FeatureFlags.enableHomeTransitionListener() && mTaskbarUIController != null) {
417                 mTaskbarUIController.onLauncherVisibilityChanged(hasBeenResumed());
418             }
419         }
420 
421         super.onActivityFlagsChanged(changeBits);
422         if ((changeBits & (ACTIVITY_STATE_DEFERRED_RESUMED | ACTIVITY_STATE_STARTED
423                 | ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) {
424             onStateOrResumeChanging((getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0);
425         }
426     }
427 
428     @Override
showAllAppsFromIntent(boolean alreadyOnHome)429     protected void showAllAppsFromIntent(boolean alreadyOnHome) {
430         TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
431         super.showAllAppsFromIntent(alreadyOnHome);
432     }
433 
onItemClicked(View view)434     protected void onItemClicked(View view) {
435         if (!mSplitToWorkspaceController.handleSecondAppSelectionForSplit(view)) {
436             QuickstepLauncher.super.getItemOnClickListener().onClick(view);
437         }
438     }
439 
440     @Override
getItemOnClickListener()441     public View.OnClickListener getItemOnClickListener() {
442         return this::onItemClicked;
443     }
444 
445     @Override
getSupportedShortcuts()446     public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
447         // Order matters as it affects order of appearance in popup container
448         List<SystemShortcut.Factory> shortcuts = new ArrayList(Arrays.asList(
449                 APP_INFO, WellbeingModel.SHORTCUT_FACTORY, mHotseatPredictionController));
450         shortcuts.addAll(getSplitShortcuts());
451         shortcuts.add(WIDGETS);
452         shortcuts.add(INSTALL);
453         if (Flags.enablePrivateSpaceInstallShortcut()) {
454             shortcuts.add(PRIVATE_PROFILE_INSTALL);
455         }
456         if (Flags.enableShortcutDontSuggestApp()) {
457             shortcuts.add(DONT_SUGGEST_APP);
458         }
459         if (Flags.enablePrivateSpace()) {
460             shortcuts.add(UNINSTALL_APP);
461         }
462         return shortcuts.stream();
463     }
464 
getSplitShortcuts()465     private List<SystemShortcut.Factory<QuickstepLauncher>> getSplitShortcuts() {
466         if (!mDeviceProfile.isTablet || mSplitSelectStateController.isSplitSelectActive()) {
467             return Collections.emptyList();
468         }
469         RecentsView recentsView = getOverviewPanel();
470         // TODO(b/266482558): Pull it out of PagedOrentationHandler for split from workspace.
471         List<SplitPositionOption> positions =
472                 recentsView.getPagedOrientationHandler().getSplitPositionOptions(
473                         mDeviceProfile);
474         List<SystemShortcut.Factory<QuickstepLauncher>> splitShortcuts = new ArrayList<>();
475         for (SplitPositionOption position : positions) {
476             splitShortcuts.add(getSplitSelectShortcutByPosition(position));
477         }
478         return splitShortcuts;
479     }
480 
481     /**
482      * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
483      */
onStateOrResumeChanging(boolean inTransition)484     private void onStateOrResumeChanging(boolean inTransition) {
485         LauncherState state = getStateManager().getState();
486         boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0;
487         if (started) {
488             DeviceProfile profile = getDeviceProfile();
489             boolean willUserBeActive =
490                     (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
491             boolean visible = (state == NORMAL || state == OVERVIEW)
492                     && (willUserBeActive || isUserActive())
493                     && !profile.isVerticalBarLayout();
494             SystemUiProxy.INSTANCE.get(this)
495                     .setLauncherKeepClearAreaHeight(visible, profile.hotseatBarSizePx);
496         }
497         if (state == NORMAL && !inTransition) {
498             ((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false);
499         }
500     }
501 
502     @Override
bindExtraContainerItems(FixedContainerItems item)503     public void bindExtraContainerItems(FixedContainerItems item) {
504         if (item.containerId == Favorites.CONTAINER_PREDICTION) {
505             mAllAppsPredictions = item;
506             PredictionRowView<?> predictionRowView =
507                     getAppsView().getFloatingHeaderView().findFixedRowByType(
508                             PredictionRowView.class);
509             predictionRowView.setPredictedApps(item.items);
510         } else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
511             mHotseatPredictionController.setPredictedItems(item);
512         } else if (item.containerId == Favorites.CONTAINER_WIDGETS_PREDICTION) {
513             getPopupDataProvider().setRecommendedWidgets(item.items);
514         }
515     }
516 
517     @Override
bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher)518     public void bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher) {
519         super.bindWorkspaceComponentsRemoved(matcher);
520         mHotseatPredictionController.onModelItemsRemoved(matcher);
521     }
522 
523     @Override
onDestroy()524     public void onDestroy() {
525         if (mAppTransitionManager != null) {
526             mAppTransitionManager.onActivityDestroyed();
527         }
528         mAppTransitionManager = null;
529         mIsPredictiveBackToHomeInProgress = false;
530 
531         if (mUnfoldTransitionProgressProvider != null) {
532             SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(null);
533             mUnfoldTransitionProgressProvider.destroy();
534         }
535 
536         mTISBindHelper.onDestroy();
537 
538         if (mLauncherUnfoldAnimationController != null) {
539             mLauncherUnfoldAnimationController.onDestroy();
540         }
541 
542         if (mDesktopVisibilityController != null) {
543             mDesktopVisibilityController.unregisterSystemUiListener();
544         }
545 
546         if (mSplitSelectStateController != null) {
547             mSplitSelectStateController.onDestroy();
548         }
549 
550         super.onDestroy();
551         mHotseatPredictionController.destroy();
552         mSplitWithKeyboardShortcutController.onDestroy();
553         if (mViewCapture != null) mViewCapture.close();
554         removeBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler());
555     }
556 
557     @Override
onStateSetEnd(LauncherState state)558     public void onStateSetEnd(LauncherState state) {
559         super.onStateSetEnd(state);
560         handlePendingActivityRequest();
561 
562         switch (state.ordinal) {
563             case HINT_STATE_ORDINAL: {
564                 Workspace<?> workspace = getWorkspace();
565                 getStateManager().goToState(NORMAL);
566                 if (workspace.getNextPage() != Workspace.DEFAULT_PAGE) {
567                     workspace.post(workspace::moveToDefaultScreen);
568                 }
569                 break;
570             }
571             case HINT_STATE_TWO_BUTTON_ORDINAL: {
572                 getStateManager().goToState(OVERVIEW);
573                 getDragLayer().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
574                 break;
575             }
576             case OVERVIEW_STATE_ORDINAL: {
577                 RecentsView rv = getOverviewPanel();
578                 sendCustomAccessibilityEvent(
579                         rv.getPageAt(rv.getCurrentPage()), TYPE_VIEW_FOCUSED, null);
580                 break;
581             }
582             case QUICK_SWITCH_STATE_ORDINAL: {
583                 RecentsView rv = getOverviewPanel();
584                 TaskView tasktolaunch = rv.getCurrentPageTaskView();
585                 if (tasktolaunch != null) {
586                     tasktolaunch.launchTask(success -> {
587                         if (!success) {
588                             getStateManager().goToState(OVERVIEW);
589                         } else {
590                             getStateManager().moveToRestState();
591                         }
592                         return Unit.INSTANCE;
593                     });
594                 } else {
595                     getStateManager().goToState(NORMAL);
596                 }
597                 break;
598             }
599 
600         }
601     }
602 
603     @Override
createTouchControllers()604     public TouchController[] createTouchControllers() {
605         NavigationMode mode = DisplayController.getNavigationMode(this);
606 
607         ArrayList<TouchController> list = new ArrayList<>();
608         list.add(getDragController());
609         BiConsumer<AnimatorSet, Long> splitAnimator = (animatorSet, duration) ->
610                 animatorSet.play(mSplitSelectStateController.getSplitAnimationController()
611                         .createPlaceholderDismissAnim(this, LAUNCHER_SPLIT_SELECTION_EXIT_HOME,
612                                 duration));
613         switch (mode) {
614             case NO_BUTTON:
615                 list.add(new NoButtonQuickSwitchTouchController(this));
616                 list.add(new NavBarToHomeTouchController(this, splitAnimator));
617                 list.add(new NoButtonNavbarToOverviewTouchController(this, splitAnimator));
618                 break;
619             case TWO_BUTTONS:
620                 list.add(new TwoButtonNavbarTouchController(this));
621                 list.add(getDeviceProfile().isVerticalBarLayout()
622                         ? new TransposedQuickSwitchTouchController(this)
623                         : new QuickSwitchTouchController(this));
624                 list.add(new PortraitStatesTouchController(this));
625                 break;
626             case THREE_BUTTONS:
627                 list.add(new NoButtonQuickSwitchTouchController(this));
628                 list.add(new NavBarToHomeTouchController(this, splitAnimator));
629                 list.add(new NoButtonNavbarToOverviewTouchController(this, splitAnimator));
630                 list.add(new PortraitStatesTouchController(this));
631                 break;
632             default:
633                 list.add(new PortraitStatesTouchController(this));
634                 break;
635         }
636 
637         if (!getDeviceProfile().isMultiWindowMode) {
638             list.add(new StatusBarTouchController(this));
639         }
640 
641         list.add(new LauncherTaskViewController(this));
642         return list.toArray(new TouchController[list.size()]);
643     }
644 
645     @Override
createAtomicAnimationFactory()646     public AtomicAnimationFactory createAtomicAnimationFactory() {
647         return new QuickstepAtomicAnimationFactory(this);
648     }
649 
650     @Override
createAppWidgetHolder()651     protected LauncherWidgetHolder createAppWidgetHolder() {
652         final QuickstepHolderFactory factory =
653                 (QuickstepHolderFactory) LauncherWidgetHolder.HolderFactory.newFactory(this);
654         return factory.newInstance(this,
655                 appWidgetId -> getWorkspace().removeWidget(appWidgetId),
656                 new QuickstepInteractionHandler(this));
657     }
658 
659     @Override
onCreate(Bundle savedInstanceState)660     protected void onCreate(Bundle savedInstanceState) {
661         // Back dispatcher is registered in {@link BaseActivity#onCreate}. For predictive back to
662         // work, we must opt-in BEFORE registering back dispatcher. So we need to call
663         // setEnableOnBackInvokedCallback() before super.onCreate()
664         if (Utilities.ATLEAST_U && enablePredictiveBackGesture()) {
665             getApplicationInfo().setEnableOnBackInvokedCallback(true);
666         }
667         super.onCreate(savedInstanceState);
668         if (savedInstanceState != null) {
669             mPendingSplitSelectInfo = ObjectWrapper.unwrap(
670                     savedInstanceState.getIBinder(PENDING_SPLIT_SELECT_INFO));
671         }
672         addMultiWindowModeChangedListener(mDepthController);
673         initUnfoldTransitionProgressProvider();
674         if (FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
675             mViewCapture = ViewCaptureFactory.getInstance(this).startCapture(getWindow());
676         }
677         getWindow().addPrivateFlags(PRIVATE_FLAG_OPTIMIZE_MEASURE);
678         QuickstepOnboardingPrefs.setup(this);
679         View.setTraceLayoutSteps(TRACE_LAYOUTS);
680         View.setTracedRequestLayoutClassClass(TRACE_RELAYOUT_CLASS);
681     }
682 
683     @Override
initDeviceProfile(InvariantDeviceProfile idp)684     protected boolean initDeviceProfile(InvariantDeviceProfile idp) {
685         final boolean ret = super.initDeviceProfile(idp);
686         mDeviceProfile.isPredictiveBackSwipe =
687                 getApplicationInfo().isOnBackInvokedCallbackEnabled();
688         return ret;
689     }
690 
691     @Override
startSplitSelection(SplitSelectSource splitSelectSource)692     public void startSplitSelection(SplitSelectSource splitSelectSource) {
693         RecentsView recentsView = getOverviewPanel();
694         // Check if there is already an instance of this app running, if so, initiate the split
695         // using that.
696         mSplitSelectStateController.findLastActiveTasksAndRunCallback(
697                 Collections.singletonList(splitSelectSource.itemInfo.getComponentKey()),
698                 false /* findExactPairMatch */,
699                 foundTasks -> {
700                     @Nullable Task foundTask = foundTasks[0];
701                     boolean taskWasFound = foundTask != null;
702                     splitSelectSource.alreadyRunningTaskId = taskWasFound
703                             ? foundTask.key.id
704                             : INVALID_TASK_ID;
705                     if (enableSplitContextually()) {
706                         startSplitToHome(splitSelectSource);
707                     } else {
708                         recentsView.initiateSplitSelect(splitSelectSource);
709                     }
710                 }
711         );
712     }
713 
714     /** TODO(b/266482558) Migrate into SplitSelectStateController or someplace split specific. */
startSplitToHome(SplitSelectSource source)715     private void startSplitToHome(SplitSelectSource source) {
716         AbstractFloatingView.closeAllOpenViews(this);
717         int splitPlaceholderSize = getResources().getDimensionPixelSize(
718                 R.dimen.split_placeholder_size);
719         int splitPlaceholderInset = getResources().getDimensionPixelSize(
720                 R.dimen.split_placeholder_inset);
721         Rect tempRect = new Rect();
722 
723         mSplitSelectStateController.setInitialTaskSelect(source.intent,
724                 source.position.stagePosition, source.itemInfo, source.splitEvent,
725                 source.alreadyRunningTaskId);
726 
727         RecentsView recentsView = getOverviewPanel();
728         recentsView.getPagedOrientationHandler().getInitialSplitPlaceholderBounds(
729                 splitPlaceholderSize, splitPlaceholderInset, getDeviceProfile(),
730                 mSplitSelectStateController.getActiveSplitStagePosition(), tempRect);
731 
732         PendingAnimation anim = new PendingAnimation(TABLET_HOME_TO_SPLIT.getDuration());
733         RectF startingTaskRect = new RectF();
734         final FloatingTaskView floatingTaskView = FloatingTaskView.getFloatingTaskView(this,
735                 source.getView(), null /* thumbnail */, source.getDrawable(), startingTaskRect);
736         floatingTaskView.setAlpha(1);
737         floatingTaskView.addStagingAnimation(anim, startingTaskRect, tempRect,
738                 false /* fadeWithThumbnail */, true /* isStagedTask */);
739         floatingTaskView.setOnClickListener(view ->
740                 mSplitSelectStateController.getSplitAnimationController().
741                         playAnimPlaceholderToFullscreen(this, view, Optional.empty()));
742         mSplitSelectStateController.setFirstFloatingTaskView(floatingTaskView);
743         anim.addListener(new AnimatorListenerAdapter() {
744             @Override
745             public void onAnimationCancel(Animator animation) {
746                 getDragLayer().removeView(floatingTaskView);
747                 mSplitSelectStateController.getSplitAnimationController()
748                         .removeSplitInstructionsView(QuickstepLauncher.this);
749                 mSplitSelectStateController.resetState();
750             }
751         });
752         anim.add(mSplitSelectStateController.getSplitAnimationController()
753                 .getShowSplitInstructionsAnim(this).buildAnim());
754         anim.buildAnim().start();
755     }
756 
757     @Override
isSplitSelectionActive()758     public boolean isSplitSelectionActive() {
759         if (mSplitSelectStateController == null) {
760             return false;
761         }
762         return mSplitSelectStateController.isSplitSelectActive();
763     }
764 
areBothSplitAppsConfirmed()765     public boolean areBothSplitAppsConfirmed() {
766         return mSplitSelectStateController.isBothSplitAppsConfirmed();
767     }
768 
769     @Override
onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState)770     public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) {
771         if (mTaskbarUIController != null) {
772             mTaskbarUIController.onStateTransitionCompletedAfterSwipeToHome(finalState);
773         }
774     }
775 
776     @Override
onResume()777     protected void onResume() {
778         super.onResume();
779 
780         if (mLauncherUnfoldAnimationController != null) {
781             mLauncherUnfoldAnimationController.onResume();
782         }
783     }
784 
785     @Override
onPause()786     protected void onPause() {
787         if (mLauncherUnfoldAnimationController != null) {
788             mLauncherUnfoldAnimationController.onPause();
789         }
790 
791         super.onPause();
792 
793         if (enableSplitContextually()) {
794             // If Launcher pauses before both split apps are selected, exit split screen.
795             if (!mSplitSelectStateController.isBothSplitAppsConfirmed() &&
796                     !mSplitSelectStateController.isLaunchingFirstAppFullscreen()) {
797                 mSplitSelectStateController
798                         .logExitReason(LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED);
799                 mSplitSelectStateController.getSplitAnimationController()
800                         .playPlaceholderDismissAnim(this, LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED);
801             }
802         }
803     }
804 
805     @Override
onNewIntent(Intent intent)806     protected void onNewIntent(Intent intent) {
807         super.onNewIntent(intent);
808         OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper();
809         if (overviewCommandHelper != null) {
810             overviewCommandHelper.clearPendingCommands();
811         }
812     }
813 
getAppTransitionManager()814     public QuickstepTransitionManager getAppTransitionManager() {
815         return mAppTransitionManager;
816     }
817 
818     @Override
onEnterAnimationComplete()819     public void onEnterAnimationComplete() {
820         super.onEnterAnimationComplete();
821         // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
822         // as a part of quickstep, so that high-res thumbnails can load the next time we enter
823         // overview
824         RecentsModel.INSTANCE.get(this).getThumbnailCache()
825                 .getHighResLoadingState().setVisible(true);
826     }
827 
828     @Override
handleGestureContract(Intent intent)829     protected void handleGestureContract(Intent intent) {
830         if (FeatureFlags.SEPARATE_RECENTS_ACTIVITY.get()) {
831             super.handleGestureContract(intent);
832         }
833     }
834 
835     @Override
onTrimMemory(int level)836     public void onTrimMemory(int level) {
837         super.onTrimMemory(level);
838         RecentsModel.INSTANCE.get(this).onTrimMemory(level);
839     }
840 
841     @Override
onUiChangedWhileSleeping()842     public void onUiChangedWhileSleeping() {
843         // Remove the snapshot because the content view may have obvious changes.
844         UI_HELPER_EXECUTOR.execute(
845                 () -> ActivityManagerWrapper.getInstance().invalidateHomeTaskSnapshot(this));
846     }
847 
848     @Override
onAllAppsTransition(float progress)849     public void onAllAppsTransition(float progress) {
850         super.onAllAppsTransition(progress);
851         onTaskbarInAppDisplayProgressUpdate(progress, ALL_APPS_PAGE_PROGRESS_INDEX);
852     }
853 
854     @Override
onWidgetsTransition(float progress)855     public void onWidgetsTransition(float progress) {
856         super.onWidgetsTransition(progress);
857         onTaskbarInAppDisplayProgressUpdate(progress, WIDGETS_PAGE_PROGRESS_INDEX);
858         if (mEnableWidgetDepth) {
859             getDepthController().widgetDepth.setValue(Utilities.mapToRange(
860                     progress, 0f, 1f, 0f, getDeviceProfile().bottomSheetDepth, EMPHASIZED));
861         }
862     }
863 
864     @Override
dispatchKeyEvent(KeyEvent event)865     public boolean dispatchKeyEvent(KeyEvent event) {
866         return tryHandleBackKey(event) || super.dispatchKeyEvent(event);
867     }
868 
869     // TODO (b/267248420) Once the recents input consumer has been removed, there is no need to
870     //  handle the back key specially.
tryHandleBackKey(KeyEvent event)871     private boolean tryHandleBackKey(KeyEvent event) {
872         // Unlike normal activity, recents can receive input event from InputConsumer, so the input
873         // event won't go through ViewRootImpl#InputStage#onProcess.
874         // So when receive back key, try to do the same check thing in
875         // ViewRootImpl#NativePreImeInputStage#onProcess
876         if (!Utilities.ATLEAST_U || !enablePredictiveBackGesture()
877                 || event.getKeyCode() != KeyEvent.KEYCODE_BACK
878                 || event.getAction() != KeyEvent.ACTION_UP || event.isCanceled()) {
879             return false;
880         }
881 
882         getOnBackAnimationCallback().onBackInvoked();
883         return true;
884     }
885 
886     @Override
registerBackDispatcher()887     protected void registerBackDispatcher() {
888         if (!enablePredictiveBackGesture()) {
889             super.registerBackDispatcher();
890             return;
891         }
892         getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
893                 OnBackInvokedDispatcher.PRIORITY_DEFAULT,
894                 new OnBackAnimationCallback() {
895 
896                     @Nullable OnBackAnimationCallback mActiveOnBackAnimationCallback;
897 
898                     @Override
899                     public void onBackStarted(@NonNull BackEvent backEvent) {
900                         if (mActiveOnBackAnimationCallback != null) {
901                             mActiveOnBackAnimationCallback.onBackCancelled();
902                         }
903                         mActiveOnBackAnimationCallback = getOnBackAnimationCallback();
904                         mActiveOnBackAnimationCallback.onBackStarted(backEvent);
905                     }
906 
907                     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
908                     @Override
909                     public void onBackInvoked() {
910                         // Recreate mActiveOnBackAnimationCallback if necessary to avoid NPE
911                         // because:
912                         // 1. b/260636433: In 3-button-navigation mode, onBackStarted() is not
913                         // called on ACTION_DOWN before onBackInvoked() is called in ACTION_UP.
914                         // 2. Launcher#onBackPressed() will call onBackInvoked() without calling
915                         // onBackInvoked() beforehand.
916                         if (mActiveOnBackAnimationCallback == null) {
917                             mActiveOnBackAnimationCallback = getOnBackAnimationCallback();
918                         }
919                         mActiveOnBackAnimationCallback.onBackInvoked();
920                         mActiveOnBackAnimationCallback = null;
921                         TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked");
922                     }
923 
924                     @Override
925                     public void onBackProgressed(@NonNull BackEvent backEvent) {
926                         if (!FeatureFlags.IS_STUDIO_BUILD
927                                 && mActiveOnBackAnimationCallback == null) {
928                             return;
929                         }
930                         mActiveOnBackAnimationCallback.onBackProgressed(backEvent);
931                     }
932 
933                     @Override
934                     public void onBackCancelled() {
935                         if (!FeatureFlags.IS_STUDIO_BUILD
936                                 && mActiveOnBackAnimationCallback == null) {
937                             return;
938                         }
939                         mActiveOnBackAnimationCallback.onBackCancelled();
940                         mActiveOnBackAnimationCallback = null;
941                     }
942                 });
943     }
944 
onTaskbarInAppDisplayProgressUpdate(float progress, int flag)945     private void onTaskbarInAppDisplayProgressUpdate(float progress, int flag) {
946         TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
947         if (taskbarManager == null
948                 || taskbarManager.getCurrentActivityContext() == null
949                 || mTaskbarUIController == null) {
950             return;
951         }
952         mTaskbarUIController.onTaskbarInAppDisplayProgressUpdate(progress, flag);
953     }
954 
955     @Override
startIntentSenderForResult(IntentSender intent, int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options)956     public void startIntentSenderForResult(IntentSender intent, int requestCode,
957             Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
958         if (requestCode != -1) {
959             mPendingActivityRequestCode = requestCode;
960             StartActivityParams params = new StartActivityParams(this, requestCode);
961             params.intentSender = intent;
962             params.fillInIntent = fillInIntent;
963             params.flagsMask = flagsMask;
964             params.flagsValues = flagsValues;
965             params.extraFlags = extraFlags;
966             params.options = options;
967             startActivity(ProxyActivityStarter.getLaunchIntent(this, params));
968         } else {
969             super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask,
970                     flagsValues, extraFlags, options);
971         }
972     }
973 
974     @Override
startActivityForResult(Intent intent, int requestCode, Bundle options)975     public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
976         if (requestCode != -1) {
977             mPendingActivityRequestCode = requestCode;
978             StartActivityParams params = new StartActivityParams(this, requestCode);
979             params.intent = intent;
980             params.options = options;
981             startActivity(ProxyActivityStarter.getLaunchIntent(this, params));
982         } else {
983             super.startActivityForResult(intent, requestCode, options);
984         }
985     }
986 
987     @Override
setResumed()988     public void setResumed() {
989         if (!enableDesktopWindowingWallpaperActivity()
990                 && mDesktopVisibilityController != null
991                 && mDesktopVisibilityController.areDesktopTasksVisible()
992                 && !mDesktopVisibilityController.isRecentsGestureInProgress()) {
993             // Return early to skip setting activity to appear as resumed
994             // TODO: b/333533253 - Remove after flag rollout
995             return;
996         }
997         super.setResumed();
998     }
999 
1000     @Override
onDeferredResumed()1001     protected void onDeferredResumed() {
1002         super.onDeferredResumed();
1003         handlePendingActivityRequest();
1004     }
1005 
handlePendingActivityRequest()1006     private void handlePendingActivityRequest() {
1007         if (mPendingActivityRequestCode != -1 && isInState(NORMAL)
1008                 && ((getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) {
1009             // Remove any active ProxyActivityStarter task and send RESULT_CANCELED to Launcher.
1010             onActivityResult(mPendingActivityRequestCode, RESULT_CANCELED, null);
1011             // ProxyActivityStarter is started with clear task to reset the task after which it
1012             // removes the task itself.
1013             startActivity(ProxyActivityStarter.getLaunchIntent(this, null));
1014         }
1015     }
1016 
onTISConnected(TISBinder binder)1017     private void onTISConnected(TISBinder binder) {
1018         TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
1019         if (taskbarManager != null) {
1020             taskbarManager.setActivity(this);
1021         }
1022         mTISBindHelper.setPredictiveBackToHomeInProgress(mIsPredictiveBackToHomeInProgress);
1023     }
1024 
1025     @Override
runOnBindToTouchInteractionService(Runnable r)1026     public void runOnBindToTouchInteractionService(Runnable r) {
1027         mTISBindHelper.runOnBindToTouchInteractionService(r);
1028     }
1029 
initUnfoldTransitionProgressProvider()1030     private void initUnfoldTransitionProgressProvider() {
1031         if (!enableUnfoldStateAnimation()) {
1032             final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
1033             if (config.isEnabled()) {
1034                 initRemotelyCalculatedUnfoldAnimation(config);
1035             }
1036         } else {
1037             ProxyUnfoldTransitionProvider provider =
1038                     SystemUiProxy.INSTANCE.get(this).getUnfoldTransitionProvider();
1039             if (provider != null) {
1040                 new LauncherUnfoldTransitionController(this, provider);
1041             }
1042         }
1043     }
1044 
1045     /** Receives animation progress from sysui process. */
initRemotelyCalculatedUnfoldAnimation(UnfoldTransitionConfig config)1046     private void initRemotelyCalculatedUnfoldAnimation(UnfoldTransitionConfig config) {
1047         RemoteUnfoldSharedComponent unfoldComponent =
1048                 UnfoldTransitionFactory.createRemoteUnfoldSharedComponent(
1049                         /* context= */ this,
1050                         config,
1051                         getMainExecutor(),
1052                         getMainThreadHandler(),
1053                         /* backgroundExecutor= */ UI_HELPER_EXECUTOR,
1054                         /* bgHandler= */ UI_HELPER_EXECUTOR.getHandler(),
1055                         /* tracingTagPrefix= */ "launcher",
1056                         getSystemService(DisplayManager.class)
1057                 );
1058 
1059         final RemoteUnfoldTransitionReceiver remoteUnfoldTransitionProgressProvider =
1060                 unfoldComponent.getRemoteTransitionProgress().orElseThrow(
1061                         () -> new IllegalStateException(
1062                                 "Trying to create getRemoteTransitionProgress when the transition "
1063                                         + "is disabled"));
1064         mUnfoldTransitionProgressProvider = remoteUnfoldTransitionProgressProvider;
1065 
1066         SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(
1067                 remoteUnfoldTransitionProgressProvider);
1068 
1069         initUnfoldAnimationController(mUnfoldTransitionProgressProvider,
1070                 unfoldComponent.getRotationChangeProvider());
1071     }
1072 
initUnfoldAnimationController(UnfoldTransitionProgressProvider progressProvider, @UnfoldMain RotationChangeProvider rotationChangeProvider)1073     private void initUnfoldAnimationController(UnfoldTransitionProgressProvider progressProvider,
1074             @UnfoldMain RotationChangeProvider rotationChangeProvider) {
1075         mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
1076                 /* launcher= */ this,
1077                 getWindowManager(),
1078                 progressProvider,
1079                 rotationChangeProvider
1080         );
1081     }
1082 
setTaskbarUIController(LauncherTaskbarUIController taskbarUIController)1083     public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) {
1084         mTaskbarUIController = taskbarUIController;
1085     }
1086 
getTaskbarUIController()1087     public @Nullable LauncherTaskbarUIController getTaskbarUIController() {
1088         return mTaskbarUIController;
1089     }
1090 
getSplitToWorkspaceController()1091     public SplitToWorkspaceController getSplitToWorkspaceController() {
1092         return mSplitToWorkspaceController;
1093     }
1094 
1095     @Override
handleSplitAnimationGoingToHome(StatsLogManager.EventEnum splitDismissReason)1096     protected void handleSplitAnimationGoingToHome(StatsLogManager.EventEnum splitDismissReason) {
1097         super.handleSplitAnimationGoingToHome(splitDismissReason);
1098         mSplitSelectStateController.getSplitAnimationController()
1099                 .playPlaceholderDismissAnim(this, splitDismissReason);
1100     }
1101 
1102     @Override
dismissSplitSelection(StatsLogManager.LauncherEvent splitDismissEvent)1103     public void dismissSplitSelection(StatsLogManager.LauncherEvent splitDismissEvent) {
1104         super.dismissSplitSelection(splitDismissEvent);
1105         mSplitSelectStateController.getSplitAnimationController()
1106                 .playPlaceholderDismissAnim(this, splitDismissEvent);
1107     }
1108 
1109     @Override
getActionsView()1110     public OverviewActionsView<?> getActionsView() {
1111         return mActionsView;
1112     }
1113 
1114     @Override
closeOpenViews(boolean animate)1115     protected void closeOpenViews(boolean animate) {
1116         super.closeOpenViews(animate);
1117         TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
1118     }
1119 
1120     @Override
collectStateHandlers(List<StateHandler<LauncherState>> out)1121     public void collectStateHandlers(List<StateHandler<LauncherState>> out) {
1122         super.collectStateHandlers(out);
1123         out.add(getDepthController());
1124         out.add(new RecentsViewStateController(this));
1125     }
1126 
getDepthController()1127     public DepthController getDepthController() {
1128         return mDepthController;
1129     }
1130 
1131     @Nullable
getDesktopVisibilityController()1132     public DesktopVisibilityController getDesktopVisibilityController() {
1133         return mDesktopVisibilityController;
1134     }
1135 
1136     @Nullable
getUnfoldTransitionProgressProvider()1137     public UnfoldTransitionProgressProvider getUnfoldTransitionProgressProvider() {
1138         return mUnfoldTransitionProgressProvider;
1139     }
1140 
1141     @Override
supportsAdaptiveIconAnimation(View clickedView)1142     public boolean supportsAdaptiveIconAnimation(View clickedView) {
1143         return true;
1144     }
1145 
1146     @Override
getNormalOverviewScaleAndOffset()1147     public float[] getNormalOverviewScaleAndOffset() {
1148         return DisplayController.getNavigationMode(this).hasGestures
1149                 ? new float[] {1, 1} : new float[] {1.1f, NO_OFFSET};
1150     }
1151 
1152     @Override
finishBindingItems(IntSet pagesBoundFirst)1153     public void finishBindingItems(IntSet pagesBoundFirst) {
1154         super.finishBindingItems(pagesBoundFirst);
1155         // Instantiate and initialize WellbeingModel now that its loading won't interfere with
1156         // populating workspace.
1157         // TODO: Find a better place for this
1158         WellbeingModel.INSTANCE.get(this);
1159 
1160         if (mLauncherUnfoldAnimationController != null) {
1161             // This is needed in case items are rebound while the unfold animation is in progress.
1162             mLauncherUnfoldAnimationController.updateRegisteredViewsIfNeeded();
1163         }
1164     }
1165 
1166     @Override
getActivityLaunchOptions(View v, @Nullable ItemInfo item)1167     public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
1168         ActivityOptionsWrapper activityOptions = mAppTransitionManager.getActivityLaunchOptions(v);
1169         if (mLastTouchUpTime > 0) {
1170             activityOptions.options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER,
1171                     mLastTouchUpTime);
1172         }
1173         if (item != null && (item.animationType == DEFAULT_NO_ICON
1174                 || item.animationType == VIEW_BACKGROUND)) {
1175             activityOptions.options.setSplashScreenStyle(
1176                     SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR);
1177         } else {
1178             activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
1179         }
1180         activityOptions.options.setLaunchDisplayId(
1181                 (v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId()
1182                         : Display.DEFAULT_DISPLAY);
1183         activityOptions.options.setPendingIntentBackgroundActivityStartMode(
1184                 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
1185         addLaunchCookie(item, activityOptions.options);
1186         return activityOptions;
1187     }
1188 
1189     @Override
makeDefaultActivityOptions(int splashScreenStyle)1190     public ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) {
1191         RunnableList callbacks = new RunnableList();
1192         ActivityOptions options = ActivityOptions.makeCustomAnimation(this, 0, 0);
1193         options.setSplashScreenStyle(splashScreenStyle);
1194         options.setPendingIntentBackgroundActivityStartMode(
1195                 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
1196 
1197         IRemoteCallback endCallback = completeRunnableListCallback(callbacks);
1198         options.setOnAnimationAbortListener(endCallback);
1199         options.setOnAnimationFinishedListener(endCallback);
1200         return new ActivityOptionsWrapper(options, callbacks);
1201     }
1202 
1203     @Override
1204     @BinderThread
enterStageSplitFromRunningApp(boolean leftOrTop)1205     public void enterStageSplitFromRunningApp(boolean leftOrTop) {
1206         mSplitWithKeyboardShortcutController.enterStageSplit(leftOrTop);
1207     }
1208 
1209     /**
1210      * Adds a new launch cookie for the activity launch if supported.
1211      *
1212      * @param info the item info for the launch
1213      * @param opts the options to set the launchCookie on.
1214      */
addLaunchCookie(ItemInfo info, ActivityOptions opts)1215     public void addLaunchCookie(ItemInfo info, ActivityOptions opts) {
1216         IBinder launchCookie = getLaunchCookie(info);
1217         if (launchCookie != null) {
1218             opts.setLaunchCookie(launchCookie);
1219         }
1220     }
1221 
1222     /**
1223      * Return a new launch cookie for the activity launch if supported.
1224      *
1225      * @param info the item info for the launch
1226      */
getLaunchCookie(ItemInfo info)1227     public IBinder getLaunchCookie(ItemInfo info) {
1228         if (info == null) {
1229             return null;
1230         }
1231         switch (info.container) {
1232             case Favorites.CONTAINER_DESKTOP:
1233             case Favorites.CONTAINER_HOTSEAT:
1234             case Favorites.CONTAINER_PRIVATESPACE:
1235                 // Fall through and continue it's on the workspace (we don't support swiping back
1236                 // to other containers like all apps or the hotseat predictions (which can change)
1237                 break;
1238             default:
1239                 if (info.container >= 0) {
1240                     // Also allow swiping to folders
1241                     break;
1242                 }
1243                 // Reset any existing launch cookies associated with the cookie
1244                 return ObjectWrapper.wrap(NO_MATCHING_ID);
1245         }
1246         switch (info.itemType) {
1247             case Favorites.ITEM_TYPE_APPLICATION:
1248             case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
1249             case Favorites.ITEM_TYPE_APPWIDGET:
1250                 // Fall through and continue if it's an app, shortcut, or widget
1251                 break;
1252             default:
1253                 // Reset any existing launch cookies associated with the cookie
1254                 return ObjectWrapper.wrap(NO_MATCHING_ID);
1255         }
1256         return ObjectWrapper.wrap(new Integer(info.id));
1257     }
1258 
setHintUserWillBeActive()1259     public void setHintUserWillBeActive() {
1260         addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
1261     }
1262 
1263     @Override
onDisplayInfoChanged(Context context, DisplayController.Info info, int flags)1264     public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
1265         super.onDisplayInfoChanged(context, info, flags);
1266         // When changing screens, force moving to rest state similar to StatefulActivity.onStop, as
1267         // StatefulActivity isn't called consistently.
1268         if ((flags & CHANGE_ACTIVE_SCREEN) != 0) {
1269             // Do not animate moving to rest state, as it can clash with Launcher#onIdpChanged
1270             // where reapplyUi calls StateManager's reapplyState during the state change animation,
1271             // and cancel the state change unexpectedly. The screen will be off during screen
1272             // transition, hiding the unanimated transition.
1273             getStateManager().moveToRestState(/* isAnimated = */false);
1274         }
1275 
1276         if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
1277             getDragLayer().recreateControllers();
1278             if (mActionsView != null) {
1279                 mActionsView.updateVerticalMargin(info.getNavigationMode());
1280             }
1281         }
1282     }
1283 
1284     @Override
onSaveInstanceState(Bundle outState)1285     protected void onSaveInstanceState(Bundle outState) {
1286         super.onSaveInstanceState(outState);
1287 
1288         // If Launcher shuts downs during split select, we save some extra data in the recovery
1289         // bundle to allow graceful recovery. The normal LauncherState restore mechanism doesn't
1290         // work in this case because restoring straight to OverviewSplitSelect without staging data,
1291         // or before the tasks themselves have loaded into Overview, causes a crash. So we tell
1292         // Launcher to first restore into Overview state, wait for the relevant tasks and icons to
1293         // load in, and then proceed to OverviewSplitSelect.
1294         if (isInState(OVERVIEW_SPLIT_SELECT)) {
1295             // Launcher will restart in Overview and then transition to OverviewSplitSelect.
1296             outState.putIBinder(PENDING_SPLIT_SELECT_INFO, ObjectWrapper.wrap(
1297                     new PendingSplitSelectInfo(
1298                             mSplitSelectStateController.getInitialTaskId(),
1299                             mSplitSelectStateController.getActiveSplitStagePosition(),
1300                             mSplitSelectStateController.getSplitEvent())
1301             ));
1302             outState.putInt(RUNTIME_STATE, OVERVIEW.ordinal);
1303         }
1304     }
1305 
1306     /**
1307      * When Launcher restarts, it sometimes needs to recover to a split selection state.
1308      * This function checks if such a recovery is needed.
1309      * @return a boolean representing whether the launcher is waiting to recover to
1310      * OverviewSplitSelect state.
1311      */
hasPendingSplitSelectInfo()1312     public boolean hasPendingSplitSelectInfo() {
1313         return mPendingSplitSelectInfo != null;
1314     }
1315 
1316     /**
1317      * See {@link #hasPendingSplitSelectInfo()}
1318      */
getPendingSplitSelectInfo()1319     public @Nullable PendingSplitSelectInfo getPendingSplitSelectInfo() {
1320         return mPendingSplitSelectInfo;
1321     }
1322 
1323     /**
1324      * When the launcher has successfully recovered to OverviewSplitSelect state, this function
1325      * deletes the recovery data, returning it to a null state.
1326      */
finishSplitSelectRecovery()1327     public void finishSplitSelectRecovery() {
1328         mPendingSplitSelectInfo = null;
1329     }
1330 
1331     /**
1332      * Sets flag whether a predictive back-to-home animation is in progress
1333      */
setPredictiveBackToHomeInProgress(boolean isInProgress)1334     public void setPredictiveBackToHomeInProgress(boolean isInProgress) {
1335         mIsPredictiveBackToHomeInProgress = isInProgress;
1336         mTISBindHelper.setPredictiveBackToHomeInProgress(isInProgress);
1337     }
1338 
1339     @Override
areDesktopTasksVisible()1340     public boolean areDesktopTasksVisible() {
1341         if (mDesktopVisibilityController != null) {
1342             return mDesktopVisibilityController.areDesktopTasksVisible();
1343         }
1344         return false;
1345     }
1346 
1347     @Override
onDeviceProfileInitiated()1348     protected void onDeviceProfileInitiated() {
1349         super.onDeviceProfileInitiated();
1350         SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
1351     }
1352 
1353     @Override
dispatchDeviceProfileChanged()1354     public void dispatchDeviceProfileChanged() {
1355         super.dispatchDeviceProfileChanged();
1356         Trace.instantForTrack(TRACE_TAG_APP, "QuickstepLauncher#DeviceProfileChanged",
1357                 getDeviceProfile().toSmallString());
1358         SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
1359         TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
1360         if (taskbarManager != null) {
1361             taskbarManager.debugWhyTaskbarNotDestroyed("QuickstepLauncher#onDeviceProfileChanged");
1362         }
1363     }
1364 
1365     /**
1366      * Launches the given {@link GroupTask} in splitscreen.
1367      */
launchSplitTasks( @onNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition)1368     public void launchSplitTasks(
1369             @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
1370         // Top/left and bottom/right tasks respectively.
1371         Task task1 = groupTask.task1;
1372         // task2 should never be null when calling this method. Allow a crash to catch invalid calls
1373         Task task2 = groupTask.task2;
1374         mSplitSelectStateController.launchExistingSplitPair(
1375                 null /* launchingTaskView */,
1376                 task1.key.id,
1377                 task2.key.id,
1378                 SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
1379                 /* callback= */ success -> mSplitSelectStateController.resetState(),
1380                 /* freezeTaskList= */ false,
1381                 groupTask.mSplitBounds == null
1382                         ? SNAP_TO_50_50
1383                         : groupTask.mSplitBounds.snapPosition,
1384                 remoteTransition);
1385     }
1386 
1387     /**
1388      * Launches two apps as an app pair.
1389      */
launchAppPair(AppPairIcon appPairIcon)1390     public void launchAppPair(AppPairIcon appPairIcon) {
1391         mSplitSelectStateController.getAppPairsController().launchAppPair(appPairIcon,
1392                 CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE);
1393     }
1394 
canStartHomeSafely()1395     public boolean canStartHomeSafely() {
1396         OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper();
1397         return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely();
1398     }
1399 
1400     @Override
isBubbleBarEnabled()1401     public boolean isBubbleBarEnabled() {
1402         return (mTaskbarUIController != null && mTaskbarUIController.isBubbleBarEnabled());
1403     }
1404 
1405     @Override
hasBubbles()1406     public boolean hasBubbles() {
1407         return (mTaskbarUIController != null && mTaskbarUIController.hasBubbles());
1408     }
1409 
1410     @NonNull
getTISBindHelper()1411     public TISBindHelper getTISBindHelper() {
1412         return mTISBindHelper;
1413     }
1414 
1415     @Override
handleIncorrectSplitTargetSelection()1416     public boolean handleIncorrectSplitTargetSelection() {
1417         if (!enableSplitContextually() || !mSplitSelectStateController.isSplitSelectActive()) {
1418             return false;
1419         }
1420         mSplitSelectStateController.getSplitInstructionsView().goBoing();
1421         return true;
1422     }
1423 
1424     private static final class LauncherTaskViewController extends
1425             TaskViewTouchController<QuickstepLauncher> {
1426 
LauncherTaskViewController(QuickstepLauncher activity)1427         LauncherTaskViewController(QuickstepLauncher activity) {
1428             super(activity);
1429         }
1430 
1431         @Override
isRecentsInteractive()1432         protected boolean isRecentsInteractive() {
1433             return mContainer.isInState(OVERVIEW) || mContainer.isInState(OVERVIEW_MODAL_TASK);
1434         }
1435 
1436         @Override
isRecentsModal()1437         protected boolean isRecentsModal() {
1438             return mContainer.isInState(OVERVIEW_MODAL_TASK);
1439         }
1440 
1441         @Override
onUserControlledAnimationCreated(AnimatorPlaybackController animController)1442         protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
1443             mContainer.getStateManager().setCurrentUserControlledAnimation(animController);
1444         }
1445     }
1446 
1447     @Override
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)1448     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
1449         super.dump(prefix, fd, writer, args);
1450         if (mDepthController != null) {
1451             mDepthController.dump(prefix, writer);
1452         }
1453         RecentsView recentsView = getOverviewPanel();
1454         writer.println("\nQuickstepLauncher:");
1455         writer.println(prefix + "\tmOrientationState: " + (recentsView == null ? "recentsNull" :
1456                 recentsView.getPagedViewOrientedState()));
1457         if (recentsView != null) {
1458             recentsView.getSplitSelectController().dump(prefix, writer);
1459         }
1460         if (mAppTransitionManager != null) {
1461             mAppTransitionManager.dump(prefix + "\t" + RING_APPEAR_ANIMATION_PREFIX, writer);
1462         }
1463         if (mHotseatPredictionController != null) {
1464             mHotseatPredictionController.dump(prefix, writer);
1465         }
1466         PredictionRowView<?> predictionRowView =
1467                 getAppsView().getFloatingHeaderView().findFixedRowByType(
1468                         PredictionRowView.class);
1469         predictionRowView.dump(prefix, writer);
1470     }
1471 
1472     @Override
onCreateView(View parent, String name, Context context, AttributeSet attrs)1473     public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
1474         switch (name) {
1475             case "TextClock", "android.widget.TextClock" -> {
1476                 TextClock tc = new TextClock(context, attrs);
1477                 tc.setClockEventDelegate(AsyncClockEventDelegate.INSTANCE.get(this));
1478                 return tc;
1479             }
1480             case "AnalogClock", "android.widget.AnalogClock" -> {
1481                 AnalogClock ac = new AnalogClock(context, attrs);
1482                 ac.setClockEventDelegate(AsyncClockEventDelegate.INSTANCE.get(this));
1483                 return ac;
1484             }
1485         }
1486         return super.onCreateView(parent, name, context, attrs);
1487     }
1488 
1489     @Override
isRecentsViewVisible()1490     public boolean isRecentsViewVisible() {
1491         return getStateManager().getState().isRecentsViewVisible;
1492     }
1493 }
1494