• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.views;
18 
19 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
20 import static android.os.Trace.traceBegin;
21 import static android.os.Trace.traceEnd;
22 import static android.view.Surface.ROTATION_0;
23 import static android.view.View.MeasureSpec.EXACTLY;
24 import static android.view.View.MeasureSpec.makeMeasureSpec;
25 
26 import static com.android.app.animation.Interpolators.ACCELERATE;
27 import static com.android.app.animation.Interpolators.ACCELERATE_0_75;
28 import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
29 import static com.android.app.animation.Interpolators.DECELERATE_2;
30 import static com.android.app.animation.Interpolators.EMPHASIZED;
31 import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
32 import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
33 import static com.android.app.animation.Interpolators.FINAL_FRAME;
34 import static com.android.app.animation.Interpolators.LINEAR;
35 import static com.android.app.animation.Interpolators.clampToProgress;
36 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
37 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
38 import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
39 import static com.android.launcher3.Flags.enableDesktopExplodedView;
40 import static com.android.launcher3.Flags.enableDesktopTaskAlphaAnimation;
41 import static com.android.launcher3.Flags.enableGridOnlyOverview;
42 import static com.android.launcher3.Flags.enableLargeDesktopWindowingTile;
43 import static com.android.launcher3.Flags.enableOverviewBackgroundWallpaperBlur;
44 import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
45 import static com.android.launcher3.Flags.enableSeparateExternalDisplayTasks;
46 import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
47 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
48 import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
49 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
50 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
51 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
52 import static com.android.launcher3.Utilities.mapToRange;
53 import static com.android.launcher3.Utilities.squaredHypot;
54 import static com.android.launcher3.Utilities.squaredTouchSlop;
55 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ACTIONS_SPLIT;
56 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ORIENTATION_CHANGED;
57 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
58 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
59 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
60 import static com.android.launcher3.statehandlers.DesktopVisibilityController.INACTIVE_DESK_ID;
61 import static com.android.launcher3.testing.shared.TestProtocol.DISMISS_ANIMATION_ENDS_MESSAGE;
62 import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE;
63 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
64 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
65 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
66 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
67 import static com.android.quickstep.BaseContainerInterface.getTaskDimension;
68 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
69 import static com.android.quickstep.util.DesksUtils.areMultiDesksFlagsEnabled;
70 import static com.android.quickstep.util.LogUtils.splitFailureMessage;
71 import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
72 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_ACTIONS_IN_MENU;
73 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_DESKTOP;
74 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
75 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
76 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
77 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SELECT_ACTIVE;
78 import static com.android.quickstep.views.RecentsViewUtils.DESK_EXPLODE_PROGRESS;
79 import static com.android.quickstep.views.TaskView.SPLIT_ALPHA;
80 
81 import android.animation.Animator;
82 import android.animation.AnimatorListenerAdapter;
83 import android.animation.AnimatorSet;
84 import android.animation.LayoutTransition;
85 import android.animation.LayoutTransition.TransitionListener;
86 import android.animation.ObjectAnimator;
87 import android.animation.PropertyValuesHolder;
88 import android.animation.ValueAnimator;
89 import android.annotation.SuppressLint;
90 import android.content.Context;
91 import android.content.Intent;
92 import android.content.LocusId;
93 import android.content.res.Configuration;
94 import android.graphics.Bitmap;
95 import android.graphics.BlendMode;
96 import android.graphics.Canvas;
97 import android.graphics.Color;
98 import android.graphics.Matrix;
99 import android.graphics.Point;
100 import android.graphics.PointF;
101 import android.graphics.Rect;
102 import android.graphics.RectF;
103 import android.graphics.Typeface;
104 import android.graphics.drawable.Drawable;
105 import android.os.Bundle;
106 import android.os.SystemClock;
107 import android.os.Trace;
108 import android.os.UserHandle;
109 import android.os.VibrationEffect;
110 import android.text.Layout;
111 import android.text.StaticLayout;
112 import android.text.TextPaint;
113 import android.util.AttributeSet;
114 import android.util.FloatProperty;
115 import android.util.Log;
116 import android.util.Pair;
117 import android.util.SparseBooleanArray;
118 import android.view.HapticFeedbackConstants;
119 import android.view.KeyEvent;
120 import android.view.LayoutInflater;
121 import android.view.MotionEvent;
122 import android.view.RemoteAnimationTarget;
123 import android.view.View;
124 import android.view.ViewDebug;
125 import android.view.ViewGroup;
126 import android.view.ViewTreeObserver.OnScrollChangedListener;
127 import android.view.accessibility.AccessibilityEvent;
128 import android.view.accessibility.AccessibilityNodeInfo;
129 import android.view.animation.Interpolator;
130 import android.widget.ListView;
131 import android.widget.OverScroller;
132 import android.widget.Toast;
133 import android.window.DesktopModeFlags;
134 import android.window.PictureInPictureSurfaceTransaction;
135 import android.window.TransitionInfo;
136 
137 import androidx.annotation.NonNull;
138 import androidx.annotation.Nullable;
139 import androidx.annotation.UiThread;
140 import androidx.core.graphics.ColorUtils;
141 import androidx.dynamicanimation.animation.SpringAnimation;
142 
143 import com.android.internal.jank.Cuj;
144 import com.android.launcher3.AbstractFloatingView;
145 import com.android.launcher3.BaseActivity.MultiWindowModeChangedListener;
146 import com.android.launcher3.DeviceProfile;
147 import com.android.launcher3.Flags;
148 import com.android.launcher3.Insettable;
149 import com.android.launcher3.PagedView;
150 import com.android.launcher3.R;
151 import com.android.launcher3.Utilities;
152 import com.android.launcher3.anim.AnimatedFloat;
153 import com.android.launcher3.anim.AnimatorListeners;
154 import com.android.launcher3.anim.AnimatorPlaybackController;
155 import com.android.launcher3.anim.PendingAnimation;
156 import com.android.launcher3.anim.SpringProperty;
157 import com.android.launcher3.compat.AccessibilityManagerCompat;
158 import com.android.launcher3.config.FeatureFlags;
159 import com.android.launcher3.desktop.DesktopRecentsTransitionController;
160 import com.android.launcher3.logger.LauncherAtom;
161 import com.android.launcher3.logging.StatsLogManager;
162 import com.android.launcher3.model.data.ItemInfo;
163 import com.android.launcher3.statehandlers.DepthController;
164 import com.android.launcher3.statehandlers.DesktopVisibilityController;
165 import com.android.launcher3.statemanager.BaseState;
166 import com.android.launcher3.statemanager.StateManager;
167 import com.android.launcher3.statemanager.StatefulContainer;
168 import com.android.launcher3.testing.TestLogging;
169 import com.android.launcher3.testing.shared.TestProtocol;
170 import com.android.launcher3.touch.OverScroll;
171 import com.android.launcher3.util.CancellableTask;
172 import com.android.launcher3.util.DynamicResource;
173 import com.android.launcher3.util.IntArray;
174 import com.android.launcher3.util.IntSet;
175 import com.android.launcher3.util.ResourceBasedOverride.Overrides;
176 import com.android.launcher3.util.RunnableList;
177 import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
178 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
179 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
180 import com.android.launcher3.util.Themes;
181 import com.android.launcher3.util.TraceHelper;
182 import com.android.launcher3.util.TranslateEdgeEffect;
183 import com.android.launcher3.util.VibratorWrapper;
184 import com.android.launcher3.util.ViewPool;
185 import com.android.launcher3.util.coroutines.DispatcherProvider;
186 import com.android.launcher3.util.window.WindowManagerProxy.DesktopVisibilityListener;
187 import com.android.quickstep.BaseContainerInterface;
188 import com.android.quickstep.GestureState;
189 import com.android.quickstep.HighResLoadingState;
190 import com.android.quickstep.OverviewCommandHelper;
191 import com.android.quickstep.RecentsAnimationController;
192 import com.android.quickstep.RecentsAnimationTargets;
193 import com.android.quickstep.RecentsFilterState;
194 import com.android.quickstep.RecentsModel;
195 import com.android.quickstep.RemoteAnimationTargets;
196 import com.android.quickstep.RemoteTargetGluer;
197 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
198 import com.android.quickstep.RotationTouchHelper;
199 import com.android.quickstep.SplitSelectionListener;
200 import com.android.quickstep.SystemUiProxy;
201 import com.android.quickstep.TaskOverlayFactory;
202 import com.android.quickstep.TaskViewUtils;
203 import com.android.quickstep.TopTaskTracker;
204 import com.android.quickstep.ViewUtils;
205 import com.android.quickstep.fallback.window.RecentsWindowFlags;
206 import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
207 import com.android.quickstep.recents.data.RecentTasksRepository;
208 import com.android.quickstep.recents.data.RecentsDeviceProfileRepository;
209 import com.android.quickstep.recents.data.RecentsDeviceProfileRepositoryImpl;
210 import com.android.quickstep.recents.data.RecentsRotationStateRepository;
211 import com.android.quickstep.recents.data.RecentsRotationStateRepositoryImpl;
212 import com.android.quickstep.recents.di.RecentsDependencies;
213 import com.android.quickstep.recents.viewmodel.RecentsViewData;
214 import com.android.quickstep.recents.viewmodel.RecentsViewModel;
215 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
216 import com.android.quickstep.util.AnimUtils;
217 import com.android.quickstep.util.DesktopTask;
218 import com.android.quickstep.util.GroupTask;
219 import com.android.quickstep.util.LayoutUtils;
220 import com.android.quickstep.util.RecentsAtomicAnimationFactory;
221 import com.android.quickstep.util.RecentsOrientedState;
222 import com.android.quickstep.util.SingleTask;
223 import com.android.quickstep.util.SplitAnimationController.Companion.SplitAnimInitProps;
224 import com.android.quickstep.util.SplitAnimationTimings;
225 import com.android.quickstep.util.SplitSelectStateController;
226 import com.android.quickstep.util.SplitTask;
227 import com.android.quickstep.util.SurfaceTransaction;
228 import com.android.quickstep.util.SurfaceTransactionApplier;
229 import com.android.quickstep.util.TaskGridNavHelper;
230 import com.android.quickstep.util.TaskViewSimulator;
231 import com.android.quickstep.util.TaskVisualsChangeListener;
232 import com.android.quickstep.util.TransformParams;
233 import com.android.quickstep.util.VibrationConstants;
234 import com.android.systemui.plugins.ResourceProvider;
235 import com.android.systemui.shared.recents.model.Task;
236 import com.android.systemui.shared.recents.model.Task.TaskKey;
237 import com.android.systemui.shared.recents.model.ThumbnailData;
238 import com.android.systemui.shared.system.ActivityManagerWrapper;
239 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
240 import com.android.systemui.shared.system.PackageManagerWrapper;
241 import com.android.systemui.shared.system.TaskStackChangeListener;
242 import com.android.systemui.shared.system.TaskStackChangeListeners;
243 import com.android.wm.shell.common.pip.IPipAnimationListener;
244 import com.android.wm.shell.shared.GroupedTaskInfo;
245 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
246 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
247 
248 import kotlin.Unit;
249 import kotlin.collections.CollectionsKt;
250 import kotlin.jvm.functions.Function0;
251 
252 import kotlinx.coroutines.CoroutineScope;
253 
254 import java.util.ArrayList;
255 import java.util.Arrays;
256 import java.util.Collections;
257 import java.util.HashMap;
258 import java.util.HashSet;
259 import java.util.List;
260 import java.util.Map;
261 import java.util.Objects;
262 import java.util.Optional;
263 import java.util.Set;
264 import java.util.function.Consumer;
265 import java.util.stream.Collectors;
266 /**
267  * A list of recent tasks.
268  *
269  * @param <CONTAINER_TYPE> : the container that should host recents view
270  * @param <STATE_TYPE>     : the type of base state that will be used
271  */
272 public abstract class RecentsView<
273         CONTAINER_TYPE extends Context & RecentsViewContainer & StatefulContainer<STATE_TYPE>,
274         STATE_TYPE extends BaseState<STATE_TYPE>> extends PagedView implements Insettable,
275         HighResLoadingState.HighResLoadingStateChangedCallback,
276         TaskVisualsChangeListener, DesktopVisibilityListener {
277 
278     protected static final String TAG = "RecentsView";
279     private static final boolean DEBUG = false;
280 
281     public static final FloatProperty<RecentsView<?, ?>> CONTENT_ALPHA =
282             new FloatProperty<>("contentAlpha") {
283                 @Override
284                 public void setValue(RecentsView view, float v) {
285                     view.setContentAlpha(v);
286                 }
287 
288                 @Override
289                 public Float get(RecentsView view) {
290                     return view.getContentAlpha();
291                 }
292             };
293 
294     public static final FloatProperty<RecentsView<?, ?>> FULLSCREEN_PROGRESS =
295             new FloatProperty<>("fullscreenProgress") {
296                 @Override
297                 public void setValue(RecentsView recentsView, float v) {
298                     recentsView.setFullscreenProgress(v);
299                 }
300 
301                 @Override
302                 public Float get(RecentsView recentsView) {
303                     return recentsView.mFullscreenProgress;
304                 }
305             };
306 
307     public static final FloatProperty<RecentsView<?, ?>> TASK_MODALNESS =
308             new FloatProperty<>("taskModalness") {
309                 @Override
310                 public void setValue(RecentsView recentsView, float v) {
311                     recentsView.setTaskModalness(v);
312                 }
313 
314                 @Override
315                 public Float get(RecentsView recentsView) {
316                     return recentsView.mTaskModalness;
317                 }
318             };
319 
320     public static final FloatProperty<RecentsView<?, ?>> ADJACENT_PAGE_HORIZONTAL_OFFSET =
321             new FloatProperty<>("adjacentPageHorizontalOffset") {
322                 @Override
323                 public void setValue(RecentsView recentsView, float v) {
324                     if (recentsView.mAdjacentPageHorizontalOffset != v) {
325                         recentsView.mAdjacentPageHorizontalOffset = v;
326                         recentsView.updatePageOffsets();
327                     }
328                 }
329 
330                 @Override
331                 public Float get(RecentsView recentsView) {
332                     return recentsView.mAdjacentPageHorizontalOffset;
333                 }
334             };
335 
336     public static final FloatProperty<RecentsView<?, ?>> RUNNING_TASK_ATTACH_ALPHA =
337             new FloatProperty<>("runningTaskAttachAlpha") {
338                 @Override
339                 public void setValue(RecentsView recentsView, float v) {
340                     recentsView.mRunningTaskAttachAlpha = v;
341                     recentsView.applyAttachAlpha();
342                 }
343 
344                 @Override
345                 public Float get(RecentsView recentsView) {
346                     return recentsView.mRunningTaskAttachAlpha;
347                 }
348             };
349 
350     public static final int SCROLL_VIBRATION_PRIMITIVE =
351             VibrationEffect.Composition.PRIMITIVE_LOW_TICK;
352     public static final float SCROLL_VIBRATION_PRIMITIVE_SCALE = 0.6f;
353     public static final VibrationEffect SCROLL_VIBRATION_FALLBACK =
354             VibrationConstants.EFFECT_TEXTURE_TICK;
355     public static final int UNBOUND_TASK_VIEW_ID = -1;
356 
357     /**
358      * Can be used to tint the color of the RecentsView to simulate a scrim that can views
359      * excluded from. Really should be a proper scrim.
360      */
361     private static final FloatProperty<RecentsView<?, ?>> COLOR_TINT =
362             new FloatProperty<>("colorTint") {
363                 @Override
364                 public void setValue(RecentsView recentsView, float v) {
365                     recentsView.setColorTint(v);
366                 }
367 
368                 @Override
369                 public Float get(RecentsView recentsView) {
370                     return recentsView.getColorTint();
371                 }
372             };
373 
374     /**
375      * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance property, they
376      * are currently both used to apply secondary translation. Should their use cases change to be
377      * more specific, we'd want to create a similar FloatProperty just for a TaskView's
378      * offsetX/Y property
379      */
380     public static final FloatProperty<RecentsView<?, ?>> TASK_SECONDARY_TRANSLATION =
381             new FloatProperty<>("taskSecondaryTranslation") {
382                 @Override
383                 public void setValue(RecentsView recentsView, float v) {
384                     recentsView.setTaskViewsResistanceTranslation(v);
385                 }
386 
387                 @Override
388                 public Float get(RecentsView recentsView) {
389                     return recentsView.mTaskViewsSecondaryTranslation;
390                 }
391             };
392 
393     /**
394      * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance property, they
395      * are currently both used to apply secondary translation. Should their use cases change to be
396      * more specific, we'd want to create a similar FloatProperty just for a TaskView's
397      * offsetX/Y property
398      */
399     public static final FloatProperty<RecentsView<?, ?>> TASK_PRIMARY_SPLIT_TRANSLATION =
400             new FloatProperty<>("taskPrimarySplitTranslation") {
401                 @Override
402                 public void setValue(RecentsView recentsView, float v) {
403                     recentsView.setTaskViewsPrimarySplitTranslation(v);
404                 }
405 
406                 @Override
407                 public Float get(RecentsView recentsView) {
408                     return recentsView.mTaskViewsPrimarySplitTranslation;
409                 }
410             };
411 
412     public static final FloatProperty<RecentsView<?, ?>> TASK_SECONDARY_SPLIT_TRANSLATION =
413             new FloatProperty<>("taskSecondarySplitTranslation") {
414                 @Override
415                 public void setValue(RecentsView recentsView, float v) {
416                     recentsView.setTaskViewsSecondarySplitTranslation(v);
417                 }
418 
419                 @Override
420                 public Float get(RecentsView recentsView) {
421                     return recentsView.mTaskViewsSecondarySplitTranslation;
422                 }
423             };
424 
425     /** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */
426     public static final FloatProperty<RecentsView<?, ?>> RECENTS_SCALE_PROPERTY =
427             new FloatProperty<>("recentsScale") {
428                 @Override
429                 public void setValue(RecentsView view, float scale) {
430                     view.setScaleX(scale);
431                     view.setScaleY(scale);
432                     view.mLastComputedTaskStartPushOutDistance = null;
433                     view.mLastComputedTaskEndPushOutDistance = null;
434                     view.runActionOnRemoteHandles(new Consumer<RemoteTargetHandle>() {
435                         @Override
436                         public void accept(RemoteTargetHandle remoteTargetHandle) {
437                             remoteTargetHandle.getTaskViewSimulator().recentsViewScale.value =
438                                     scale;
439                         }
440                     });
441                     view.setTaskViewsResistanceTranslation(view.mTaskViewsSecondaryTranslation);
442                     view.updateTaskViewsSnapshotRadius();
443                     view.updatePageOffsets();
444                 }
445 
446                 @Override
447                 public Float get(RecentsView view) {
448                     return view.getScaleX();
449                 }
450             };
451 
452     /**
453      * Progress of Recents view from carousel layout to grid layout. If Recents is not shown as a
454      * grid, then the value remains 0.
455      */
456     public static final FloatProperty<RecentsView<?, ?>> RECENTS_GRID_PROGRESS =
457             new FloatProperty<>("recentsGrid") {
458                 @Override
459                 public void setValue(RecentsView view, float gridProgress) {
460                     view.setGridProgress(gridProgress);
461                 }
462 
463                 @Override
464                 public Float get(RecentsView view) {
465                     return view.mGridProgress;
466                 }
467             };
468 
469     public static final FloatProperty<RecentsView<?, ?>> DESKTOP_CAROUSEL_DETACH_PROGRESS =
470             new FloatProperty<>("desktopCarouselDetachProgress") {
471                 @Override
472                 public void setValue(RecentsView view, float offset) {
473                     view.mDesktopCarouselDetachProgress = offset;
474                     view.applyAttachAlpha();
475                     view.updatePageOffsets();
476                 }
477 
478                 @Override
479                 public Float get(RecentsView view) {
480                     return view.mDesktopCarouselDetachProgress;
481                 }
482             };
483 
484     /**
485      * Alpha of the task thumbnail splash, where being in BackgroundAppState has a value of 1, and
486      * being in any other state has a value of 0.
487      */
488     public static final FloatProperty<RecentsView<?, ?>> TASK_THUMBNAIL_SPLASH_ALPHA =
489             new FloatProperty<>("taskThumbnailSplashAlpha") {
490                 @Override
491                 public void setValue(RecentsView view, float taskThumbnailSplashAlpha) {
492                     view.setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha);
493                 }
494 
495                 @Override
496                 public Float get(RecentsView view) {
497                     return view.mTaskThumbnailSplashAlpha;
498                 }
499             };
500 
501     // OverScroll constants
502     private static final int OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION = 270;
503 
504     private static final int DEFAULT_ACTIONS_VIEW_ALPHA_ANIMATION_DURATION = 300;
505 
506     private static final int DISMISS_TASK_DURATION = 300;
507     private static final int ADDITION_TASK_DURATION = 200;
508     private static final float INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.55f;
509     private static final float ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.05f;
510     private static final float ANIMATION_DISMISS_PROGRESS_MIDPOINT = 0.5f;
511     private static final float END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.75f;
512 
513     private static final float SIGNIFICANT_MOVE_SCREEN_WIDTH_PERCENTAGE = 0.15f;
514 
515     private static final float FOREGROUND_SCRIM_TINT = 0.32f;
516 
517     protected final RecentsOrientedState mOrientationState;
518     protected final BaseContainerInterface<STATE_TYPE, ?> mSizeStrategy;
519     @Nullable
520     protected RecentsAnimationController mRecentsAnimationController;
521     @Nullable
522     protected SurfaceTransactionApplier mSyncTransactionApplier;
523     protected int mTaskWidth;
524     protected int mTaskHeight;
525     // Used to position the top of a task in the top row of the grid
526     private float mTaskGridVerticalDiff;
527     // The vertical space one grid task takes + space between top and bottom row.
528     private float mTopBottomRowHeightDiff;
529     // mTaskGridVerticalDiff and mTopBottomRowHeightDiff summed together provides the top
530     // position for bottom row of grid tasks.
531 
532     @Nullable
533     protected RemoteTargetHandle[] mRemoteTargetHandles;
534     protected final Rect mLastComputedTaskSize = new Rect();
535     protected final Rect mLastComputedGridSize = new Rect();
536     protected final Rect mLastComputedGridTaskSize = new Rect();
537     // How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot.
538     @Nullable
539     protected Float mLastComputedTaskStartPushOutDistance = null;
540     @Nullable
541     protected Float mLastComputedTaskEndPushOutDistance = null;
542     protected boolean mEnableDrawingLiveTile = false;
543     protected final Rect mTempRect = new Rect();
544     protected final RectF mTempRectF = new RectF();
545     private final PointF mTempPointF = new PointF();
546     private final Matrix mTempMatrix = new Matrix();
547     private final float[] mTempFloat = new float[1];
548     private final List<OnScrollChangedListener> mScrollListeners = new ArrayList<>();
549 
550     // The threshold at which we update the SystemUI flags when animating from the task into the app
551     public static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.85f;
552 
553     protected final CONTAINER_TYPE mContainer;
554     private final float mFastFlingVelocity;
555     private final int mScrollHapticMinGapMillis;
556     private final RecentsModel mModel;
557     private final int mSplitPlaceholderSize;
558     private final int mSplitPlaceholderInset;
559     private final ClearAllButton mClearAllButton;
560     @Nullable
561     private AddDesktopButton mAddDesktopButton = null;
562     private final Rect mClearAllButtonDeadZoneRect = new Rect();
563     private final Rect mTaskViewDeadZoneRect = new Rect();
564     private final Rect mTopRowDeadZoneRect = new Rect();
565     private final Rect mBottomRowDeadZoneRect = new Rect();
566 
567     @Nullable
568     private DesktopVisibilityController mDesktopVisibilityController = null;
569 
570     /**
571      * Reflects if Recents is currently in the middle of a gesture, and if so, which related
572      * [GroupedTaskInfo] is running. If a gesture is not in progress, this will be null.
573      */
574     private @Nullable GroupedTaskInfo mActiveGestureGroupedTaskInfo;
575 
576     // Keeps track of the previously known visible tasks for purposes of loading/unloading task data
577     private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray();
578 
579     /**
580      * Getting views should be done via {@link #getTaskViewFromPool(int)}
581      */
582     private final ViewPool<TaskView> mTaskViewPool;
583     private final ViewPool<GroupedTaskView> mGroupedTaskViewPool;
584     private final ViewPool<DesktopTaskView> mDesktopTaskViewPool;
585 
586     protected final TaskOverlayFactory mTaskOverlayFactory;
587 
588     protected boolean mDisallowScrollToClearAll;
589     // True if it is not allowed to scroll to [AddDesktopButton].
590     protected boolean mDisallowScrollToAddDesk;
591     private boolean mOverlayEnabled;
592     protected boolean mFreezeViewVisibility;
593     private boolean mOverviewGridEnabled;
594     private boolean mOverviewFullscreenEnabled;
595     private boolean mOverviewSelectEnabled;
596 
597     private boolean mShouldClampScrollOffset;
598     private int mClampedScrollOffsetBound;
599 
600     private float mAdjacentPageHorizontalOffset = 0;
601     private float mDesktopCarouselDetachProgress = 0;
602     protected float mTaskViewsSecondaryTranslation = 0;
603     protected float mTaskViewsPrimarySplitTranslation = 0;
604     protected float mTaskViewsSecondarySplitTranslation = 0;
605     // Progress from 0 to 1 where 0 is a carousel and 1 is a 2 row grid.
606     private float mGridProgress = 0;
607     private float mTaskThumbnailSplashAlpha = 0;
608     private boolean mBorderEnabled = false;
609     private boolean mShowAsGridLastOnLayout = false;
610     protected final IntSet mTopRowIdSet = new IntSet();
611     private int mClearAllShortTotalWidthTranslation = 0;
612 
613     // The GestureEndTarget that is still in progress.
614     @Nullable
615     protected GestureState.GestureEndTarget mCurrentGestureEndTarget;
616 
617     private float mColorTint;
618     private final int mTintingColor;
619     @Nullable
620     private ObjectAnimator mTintingAnimator;
621 
622     private int mOverScrollShift = 0;
623     private long mScrollLastHapticTimestamp;
624 
625     private int mKeyboardTaskFocusSnapAnimationDuration;
626     private int mKeyboardTaskFocusIndex = INVALID_PAGE;
627 
628     private Map<TaskView, Integer> mTaskViewsDismissPrimaryTranslations =
629             new HashMap<TaskView, Integer>();
630 
631     /**
632      * TODO: Call reloadIdNeeded in onTaskStackChanged.
633      */
634     private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
635         @Override
636         public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
637             if (!mHandleTaskStackChanges) {
638                 return;
639             }
640             // Check this is for the right user
641             if (!checkCurrentOrManagedUserId(userId, getContext())) {
642                 return;
643             }
644 
645             // Remove the task immediately from the task list
646             TaskView taskView = getTaskViewByTaskId(taskId);
647             if (taskView != null) {
648                 removeView(taskView);
649             }
650         }
651 
652         @Override
653         public void onActivityUnpinned() {
654             if (!mHandleTaskStackChanges) {
655                 return;
656             }
657 
658             reloadIfNeeded();
659             enableLayoutTransitions();
660         }
661 
662         @Override
663         public void onTaskRemoved(int taskId) {
664             if (!mHandleTaskStackChanges) {
665                 Log.d(TAG, "onTaskRemoved: " + taskId + ", not handling task stack changes");
666                 return;
667             }
668 
669             TaskContainer taskContainer = mUtils.getTaskContainerById(taskId);
670             if (taskContainer == null) {
671                 Log.d(TAG, "onTaskRemoved: " + taskId + ", no associated Task");
672                 return;
673             }
674             Log.d(TAG, "onTaskRemoved: " + taskId);
675             TaskKey taskKey = taskContainer.getTask().key;
676             UI_HELPER_EXECUTOR.execute(new CancellableTask<>(
677                     () -> PackageManagerWrapper.getInstance()
678                             .getActivityInfo(taskKey.getComponent(), taskKey.userId) == null,
679                     MAIN_EXECUTOR,
680                     apkRemoved -> {
681                         if (apkRemoved) {
682                             dismissTask(taskId, /*animate=*/true, /*removeTask=*/false);
683                         } else {
684                             mModel.isTaskRemoved(taskKey.id, taskRemoved -> {
685                                 if (taskRemoved) {
686                                     dismissTask(taskId, /*animate=*/true, /*removeTask=*/false);
687                                 }
688                             }, RecentsFilterState.getFilter(mFilterState.getPackageNameToFilter()));
689                         }
690                     }));
691         }
692     };
693 
694     private final PinnedStackAnimationListener mIPipAnimationListener =
695             new PinnedStackAnimationListener();
696     private int mPipCornerRadius;
697     private int mPipShadowRadius;
698 
699     // Used to keep track of the last requested task list id, so that we do not request to load the
700     // tasks again if we have already requested it and the task list has not changed
701     private int mTaskListChangeId = -1;
702 
703     // Only valid until the launcher state changes to NORMAL
704     /**
705      * ID for the current running TaskView view, unique amongst TaskView instances. ID's are set
706      * through {@link #getTaskViewFromPool(boolean)} and incremented by {@link #mTaskViewIdCount}
707      */
708     protected int mRunningTaskViewId = -1;
709     private int mTaskViewIdCount;
710     protected boolean mRunningTaskTileHidden;
711     protected int mFocusedTaskViewId = INVALID_TASK_ID;
712 
713     private boolean mTaskIconVisible = true;
714     private boolean mRunningTaskShowScreenshot = false;
715     private float mRunningTaskAttachAlpha;
716 
717     private boolean mOverviewStateEnabled;
718     private boolean mHandleTaskStackChanges;
719     private boolean mSwipeDownShouldLaunchApp;
720     private boolean mTouchDownToStartHome;
721     private final float mSquaredTouchSlop;
722     private int mDownX;
723     private int mDownY;
724 
725     @Nullable
726     private PendingAnimation mPendingAnimation;
727     @Nullable
728     private LayoutTransition mLayoutTransition;
729 
730     @ViewDebug.ExportedProperty(category = "launcher")
731     protected float mContentAlpha = 1;
732     @ViewDebug.ExportedProperty(category = "launcher")
733     protected float mFullscreenProgress = 0;
734     /**
735      * How modal is the current task to be displayed, 1 means the task is fully modal and no other
736      * tasks are show. 0 means the task is displays in context in the list with other tasks.
737      */
738     @ViewDebug.ExportedProperty(category = "launcher")
739     protected float mTaskModalness = 0;
740 
741     // Keeps track of task id whose visual state should not be reset
742     private int mIgnoreResetTaskId = -1;
743     protected boolean mLoadPlanEverApplied;
744 
745     // Variables for empty state
746     private final Drawable mEmptyIcon;
747     private final CharSequence mEmptyMessage;
748     private final TextPaint mEmptyMessagePaint;
749     private final Point mLastMeasureSize = new Point();
750     private final int mEmptyMessagePadding;
751     private boolean mShowEmptyMessage;
752     @Nullable
753     private OnEmptyMessageUpdatedListener mOnEmptyMessageUpdatedListener;
754     @Nullable
755     private Layout mEmptyTextLayout;
756 
757     /**
758      * Placeholder view indicating where the first split screen selected app will be placed
759      */
760     protected SplitSelectStateController mSplitSelectStateController;
761 
762     /**
763      * The first task that split screen selection was initiated with. When split select state is
764      * initialized, we create a
765      * {@link #createTaskDismissAnimation(TaskView, boolean, boolean, long, boolean)} for this
766      * TaskView but don't actually remove the task since the user might back out. As such, we also
767      * ensure this View doesn't go back into the {@link #mTaskViewPool},
768      * see {@link #onViewRemoved(View)}
769      */
770     @Nullable
771     private TaskView mSplitHiddenTaskView;
772     @Nullable
773     private TaskView mSecondSplitHiddenView;
774     @Nullable
775     private SplitBounds mSplitBoundsConfig;
776     private final Toast mSplitUnsupportedToast = Toast.makeText(getContext(),
777             R.string.toast_split_app_unsupported, Toast.LENGTH_SHORT);
778 
779     @Nullable
780     private SplitSelectSource mSplitSelectSource;
781 
782     private final SplitSelectionListener mSplitSelectionListener = new SplitSelectionListener() {
783         @Override
784         public void onSplitSelectionConfirmed() {
785         }
786 
787         @Override
788         public void onSplitSelectionActive() {
789         }
790 
791         @Override
792         public void onSplitSelectionExit(boolean launchedSplit) {
793             resetFromSplitSelectionState();
794         }
795     };
796 
797     /**
798      * Keeps track of the index of the TaskView that split screen was initialized with so we know
799      * where to insert it back into list of taskViews in case user backs out of entering split
800      * screen.
801      * NOTE: This index is the index while {@link #mSplitHiddenTaskView} was a child of recentsView,
802      * this doesn't get adjusted to reflect the new child count after the taskView is dismissed/
803      * removed from recentsView
804      */
805     private int mSplitHiddenTaskViewIndex = -1;
806     @Nullable
807     private FloatingTaskView mSecondFloatingTaskView;
808     /**
809      * A fullscreen scrim that goes behind the splitscreen animation to hide color conflicts and
810      * possible flickers. Removed after tasks + divider finish animating in.
811      */
812     private View mSplitScrim;
813 
814     /**
815      * The task to be removed and immediately re-added. Should not be added to task pool.
816      */
817     @Nullable
818     private TaskView mMovingTaskView;
819 
820     private OverviewActionsView mActionsView;
821     private ObjectAnimator mActionsViewAlphaAnimator;
822     private float mActionsViewAlphaAnimatorFinalValue;
823 
824     @Nullable
825     private DesktopRecentsTransitionController mDesktopRecentsTransitionController;
826 
827     private MultiWindowModeChangedListener mMultiWindowModeChangedListener =
828             new MultiWindowModeChangedListener() {
829                 @Override
830                 public void onMultiWindowModeChanged(boolean inMultiWindowMode) {
831                     mOrientationState.setMultiWindowMode(inMultiWindowMode);
832                     setLayoutRotation(mOrientationState.getTouchRotation(),
833                             mOrientationState.getDisplayRotation());
834                     mUtils.updateChildTaskOrientations();
835                     if (!inMultiWindowMode && mOverviewStateEnabled) {
836                         // TODO: Re-enable layout transitions for addition of the unpinned task
837                         reloadIfNeeded();
838                     }
839                 }
840             };
841 
842     @Nullable
843     private RunnableList mSideTaskLaunchCallback;
844     @Nullable
845     private TaskLaunchListener mTaskLaunchListener;
846     @Nullable
847     private Runnable mOnTaskLaunchCancelledRunnable;
848 
849 
850     // keeps track of the state of the filter for tasks in recents view
851     private final RecentsFilterState mFilterState = new RecentsFilterState();
852 
853     private int mOffsetMidpointIndexOverride = INVALID_PAGE;
854 
855     /**
856      * Whether or not any task has been dismissed i.e. swiped away by the user, in the lifetime of
857      * RecentsView being open and displayed to the user. It is reset in the {@link #reset()} method
858      * i.e. when RecentsView closes.
859      */
860     private boolean mAnyTaskHasBeenDismissed;
861 
862     protected final RecentsViewModel mRecentsViewModel;
863     private final RecentsViewModelHelper mHelper;
864     protected final RecentsViewUtils mUtils = new RecentsViewUtils(this);
865     protected final RecentsDismissUtils mDismissUtils = new RecentsDismissUtils(this);
866 
867     private final Matrix mTmpMatrix = new Matrix();
868 
869     private int mTaskViewCount = 0;
870 
871     protected final BlurUtils mBlurUtils = new BlurUtils(this);
872 
873     @Nullable
getFirstTaskView()874     public TaskView getFirstTaskView() {
875         return mUtils.getFirstTaskView();
876     }
877 
RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)878     public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
879         super(context, attrs, defStyleAttr);
880         setEnableFreeScroll(true);
881 
882         mContainer = RecentsViewContainer.containerFromContext(context);
883         mSizeStrategy = getContainerInterface(mContainer.getDisplayId());
884 
885         mOrientationState = new RecentsOrientedState(
886                 context, mSizeStrategy, this::animateRecentsRotationInPlace);
887         final int rotation = mContainer.getDisplay().getRotation();
888         mOrientationState.setRecentsRotation(rotation);
889 
890         // Start Recents Dependency graph
891         if (enableRefactorTaskThumbnail()) {
892             RecentsDependencies recentsDependencies = RecentsDependencies.Companion.maybeInitialize(
893                     context);
894             String scopeId = recentsDependencies.createRecentsViewScope(context);
895             mRecentsViewModel = new RecentsViewModel(
896                     recentsDependencies.inject(RecentTasksRepository.class, scopeId),
897                     recentsDependencies.inject(RecentsViewData.class, scopeId)
898             );
899             mHelper = new RecentsViewModelHelper(
900                     mRecentsViewModel,
901                     recentsDependencies.inject(CoroutineScope.class, scopeId),
902                     recentsDependencies.inject(DispatcherProvider.class, scopeId)
903             );
904 
905             recentsDependencies.provide(RecentsRotationStateRepository.class, scopeId,
906                     () -> new RecentsRotationStateRepositoryImpl(mOrientationState));
907 
908             recentsDependencies.provide(RecentsDeviceProfileRepository.class, scopeId,
909                     () -> new RecentsDeviceProfileRepositoryImpl(mContainer));
910         } else {
911             mRecentsViewModel = null;
912             mHelper = null;
913         }
914 
915         mScrollHapticMinGapMillis = getResources()
916                 .getInteger(R.integer.recentsScrollHapticMinGapMillis);
917         mFastFlingVelocity = getResources()
918                 .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
919         mModel = RecentsModel.INSTANCE.get(context);
920 
921         mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
922                 .inflate(R.layout.overview_clear_all_button, this, false);
923         mClearAllButton.setOnClickListener(this::dismissAllTasks);
924 
925         if (DesktopModeStatus.enableMultipleDesktops(mContext)) {
926             mAddDesktopButton = (AddDesktopButton) LayoutInflater.from(context).inflate(
927                     R.layout.overview_add_desktop_button, this, false);
928             mAddDesktopButton.setOnClickListener(this::createDesk);
929 
930             mDesktopVisibilityController = DesktopVisibilityController.INSTANCE.get(mContext);
931         }
932 
933         mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
934                 10 /* initial size */);
935         int groupedViewPoolInitialSize = enableRefactorTaskThumbnail() ? 2 : 10;
936         mGroupedTaskViewPool = new ViewPool<>(context, this,
937                 R.layout.task_grouped, 20 /* max size */, groupedViewPoolInitialSize);
938         int desktopViewPoolInitialSize = DesktopModeStatus.canEnterDesktopMode(mContext) ? 1 : 0;
939         mDesktopTaskViewPool = new ViewPool<>(context, this, R.layout.task_desktop,
940                 5 /* max size */, desktopViewPoolInitialSize);
941 
942         setOrientationHandler(mOrientationState.getOrientationHandler());
943         mIsRtl = getPagedOrientationHandler().getRecentsRtlSetting(getResources());
944         setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
945         mSplitPlaceholderSize = getResources().getDimensionPixelSize(
946                 R.dimen.split_placeholder_size);
947         mSplitPlaceholderInset = getResources().getDimensionPixelSize(
948                 R.dimen.split_placeholder_inset);
949         mSquaredTouchSlop = squaredTouchSlop(context);
950         mClampedScrollOffsetBound = getResources().getDimensionPixelSize(
951                 R.dimen.transient_taskbar_clamped_offset_bound);
952 
953         mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents);
954         mEmptyIcon.setCallback(this);
955         mEmptyMessage = context.getText(R.string.recents_empty_message);
956         mEmptyMessagePaint = new TextPaint();
957         mEmptyMessagePaint.setColor(Themes.getAttrColor(context, android.R.attr.textColorPrimary));
958         mEmptyMessagePaint.setTextSize(getResources()
959                 .getDimension(R.dimen.recents_empty_message_text_size));
960         Typeface typeface = Typeface.create(
961                 Typeface.create(Themes.getDefaultBodyFont(context), Typeface.NORMAL),
962                 getFontWeight(),
963                 false);
964         mEmptyMessagePaint.setTypeface(typeface);
965         mEmptyMessagePaint.setAntiAlias(true);
966         mEmptyMessagePadding = getResources()
967                 .getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
968         setWillNotDraw(false);
969         updateEmptyMessage();
970 
971         mTaskOverlayFactory = Overrides.getObject(
972                 TaskOverlayFactory.class,
973                 context.getApplicationContext(),
974                 R.string.task_overlay_factory_class);
975 
976         // Initialize quickstep specific cache params here, as this is constructed only once
977         mContainer.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
978 
979         mTintingColor = getForegroundScrimDimColor(context);
980 
981         // if multi-instance feature is enabled
982         if (FeatureFlags.ENABLE_MULTI_INSTANCE.get()) {
983             // invalidate the current list of tasks if filter changes with a fading in/out animation
984             mFilterState.setOnFilterUpdatedListener(() -> {
985                 Animator animatorFade = getStateManager().createStateElementAnimation(
986                         RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM, 1f, 0f);
987                 Animator animatorAppear = getStateManager().createStateElementAnimation(
988                         RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM, 0f, 1f);
989                 animatorFade.addListener(new AnimatorListenerAdapter() {
990                     @Override
991                     public void onAnimationEnd(@NonNull Animator animation) {
992                         RecentsView.this.invalidateTaskList();
993                         updateClearAllFunction();
994                         reloadIfNeeded();
995                         if (mPendingAnimation != null) {
996                             mPendingAnimation.addEndListener(success -> {
997                                 animatorAppear.start();
998                             });
999                         } else {
1000                             animatorAppear.start();
1001                         }
1002                     }
1003                 });
1004                 animatorFade.start();
1005             });
1006         }
1007         // make sure filter is turned off by default
1008         mFilterState.setFilterBy(null);
1009     }
1010 
1011     /** Get the state of the filter */
getFilterState()1012     public RecentsFilterState getFilterState() {
1013         return mFilterState;
1014     }
1015 
1016     /**
1017      * Toggles the filter and reloads the recents view if needed.
1018      *
1019      * @param packageName package name to filter by if the filter is being turned on;
1020      *                    should be null if filter is being turned off
1021      */
setAndApplyFilter(@ullable String packageName)1022     public void setAndApplyFilter(@Nullable String packageName) {
1023         mFilterState.setFilterBy(packageName);
1024     }
1025 
1026     /**
1027      * Updates the "Clear All" button and its function depending on the recents view state.
1028      *
1029      * TODO: add a different button for going back to overview. Present solution is for demo only.
1030      */
updateClearAllFunction()1031     public void updateClearAllFunction() {
1032         if (mFilterState.isFiltered()) {
1033             mClearAllButton.setText(R.string.recents_back);
1034             mClearAllButton.setOnClickListener((view) -> {
1035                 this.setAndApplyFilter(null);
1036             });
1037         } else {
1038             mClearAllButton.setText(R.string.recents_clear_all);
1039             mClearAllButton.setOnClickListener(this::dismissAllTasks);
1040         }
1041     }
1042 
1043     /**
1044      * Invalidates the list of tasks so that an update occurs to the list of tasks if requested.
1045      */
invalidateTaskList()1046     private void invalidateTaskList() {
1047         mTaskListChangeId = -1;
1048     }
1049 
getScroller()1050     public OverScroller getScroller() {
1051         return mScroller;
1052     }
1053 
isRtl()1054     public boolean isRtl() {
1055         return mIsRtl;
1056     }
1057 
1058     @Override
initEdgeEffect()1059     protected void initEdgeEffect() {
1060         mEdgeGlowLeft = new TranslateEdgeEffect(getContext());
1061         mEdgeGlowRight = new TranslateEdgeEffect(getContext());
1062     }
1063 
1064     @Override
drawEdgeEffect(Canvas canvas)1065     protected void drawEdgeEffect(Canvas canvas) {
1066         // Do not draw edge effect
1067     }
1068 
1069     @Override
dispatchDraw(Canvas canvas)1070     protected void dispatchDraw(Canvas canvas) {
1071         // Draw overscroll
1072         if (mAllowOverScroll && (!mEdgeGlowRight.isFinished() || !mEdgeGlowLeft.isFinished())) {
1073             final int restoreCount = canvas.save();
1074 
1075             int primarySize = getPagedOrientationHandler().getPrimaryValue(getWidth(), getHeight());
1076             int scroll = OverScroll.dampedScroll(getUndampedOverScrollShift(), primarySize);
1077             getPagedOrientationHandler().setPrimary(canvas, CANVAS_TRANSLATE, scroll);
1078 
1079             if (mOverScrollShift != scroll) {
1080                 mOverScrollShift = scroll;
1081                 dispatchScrollChanged();
1082             }
1083 
1084             super.dispatchDraw(canvas);
1085             canvas.restoreToCount(restoreCount);
1086         } else {
1087             if (mOverScrollShift != 0) {
1088                 mOverScrollShift = 0;
1089                 dispatchScrollChanged();
1090             }
1091             super.dispatchDraw(canvas);
1092         }
1093         if (mEnableDrawingLiveTile && mRemoteTargetHandles != null) {
1094             redrawLiveTile();
1095         }
1096     }
1097 
getUndampedOverScrollShift()1098     private float getUndampedOverScrollShift() {
1099         final int width = getWidth();
1100         final int height = getHeight();
1101         int primarySize = getPagedOrientationHandler().getPrimaryValue(width, height);
1102         int secondarySize = getPagedOrientationHandler().getSecondaryValue(width, height);
1103 
1104         float effectiveShift = 0;
1105         if (!mEdgeGlowLeft.isFinished()) {
1106             mEdgeGlowLeft.setSize(secondarySize, primarySize);
1107             if (((TranslateEdgeEffect) mEdgeGlowLeft).getTranslationShift(mTempFloat)) {
1108                 effectiveShift = mTempFloat[0];
1109                 postInvalidateOnAnimation();
1110             }
1111         }
1112         if (!mEdgeGlowRight.isFinished()) {
1113             mEdgeGlowRight.setSize(secondarySize, primarySize);
1114             if (((TranslateEdgeEffect) mEdgeGlowRight).getTranslationShift(mTempFloat)) {
1115                 effectiveShift -= mTempFloat[0];
1116                 postInvalidateOnAnimation();
1117             }
1118         }
1119 
1120         return effectiveShift * primarySize;
1121     }
1122 
1123     /**
1124      * Returns the view shift due to overscroll
1125      */
getOverScrollShift()1126     public int getOverScrollShift() {
1127         return mOverScrollShift;
1128     }
1129 
1130     @Override
1131     @Nullable
onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData)1132     public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) {
1133         if (enableRefactorTaskThumbnail()) {
1134             return null;
1135         }
1136         if (mHandleTaskStackChanges) {
1137             if (!enableRefactorTaskThumbnail()) {
1138                 TaskView taskView = getTaskViewByTaskId(taskId);
1139                 if (taskView != null) {
1140                     for (TaskContainer container : taskView.getTaskContainers()) {
1141                         if (taskId != container.getTask().key.id) {
1142                             continue;
1143                         }
1144                         container.getThumbnailViewDeprecated().setThumbnail(container.getTask(),
1145                                 thumbnailData);
1146                     }
1147                 }
1148             }
1149         }
1150         return null;
1151     }
1152 
1153     @Override
onTaskIconChanged(@onNull String pkg, @NonNull UserHandle user)1154     public void onTaskIconChanged(@NonNull String pkg, @NonNull UserHandle user) {
1155         for (TaskView taskView : getTaskViews()) {
1156             Task firstTask = taskView.getFirstTask();
1157             if (firstTask != null && pkg.equals(firstTask.key.getPackageName())
1158                     && firstTask.key.userId == user.getIdentifier()) {
1159                 firstTask.icon = null;
1160                 if (taskView.getTaskContainers().stream().anyMatch(
1161                         container -> container.getIconView().getDrawable() != null)) {
1162                     taskView.onTaskListVisibilityChanged(true /* visible */);
1163                 }
1164             }
1165         }
1166     }
1167 
1168     @Override
onTaskIconChanged(int taskId)1169     public void onTaskIconChanged(int taskId) {
1170         if (enableRefactorTaskThumbnail()) {
1171             return;
1172         }
1173         TaskView taskView = getTaskViewByTaskId(taskId);
1174         if (taskView != null) {
1175             taskView.refreshTaskThumbnailSplash();
1176         }
1177     }
1178 
1179     /** Updates the thumbnail(s) of the relevant TaskView. */
updateThumbnail(Map<Integer, ThumbnailData> thumbnailData)1180     public void updateThumbnail(Map<Integer, ThumbnailData> thumbnailData) {
1181         if (!enableRefactorTaskThumbnail()) {
1182             for (Map.Entry<Integer, ThumbnailData> entry : thumbnailData.entrySet()) {
1183                 Integer id = entry.getKey();
1184                 ThumbnailData thumbnail = entry.getValue();
1185                 TaskView taskView = getTaskViewByTaskId(id);
1186                 if (taskView == null) {
1187                     continue;
1188                 }
1189                 // taskView could be a GroupedTaskView, so select the relevant task by ID
1190                 TaskContainer taskContainer = taskView.getTaskContainerById(id);
1191                 if (taskContainer == null) {
1192                     continue;
1193                 }
1194                 Task task = taskContainer.getTask();
1195                 TaskThumbnailViewDeprecated taskThumbnailViewDeprecated =
1196                         taskContainer.getThumbnailViewDeprecated();
1197                 taskThumbnailViewDeprecated.setThumbnail(task, thumbnail, /*refreshNow=*/false);
1198             }
1199         }
1200     }
1201 
1202     @Override
onWindowVisibilityChanged(int visibility)1203     protected void onWindowVisibilityChanged(int visibility) {
1204         super.onWindowVisibilityChanged(visibility);
1205         updateTaskStackListenerState();
1206     }
1207 
init(OverviewActionsView actionsView, SplitSelectStateController splitController, @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController)1208     public void init(OverviewActionsView actionsView, SplitSelectStateController splitController,
1209             @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) {
1210         mActionsView = actionsView;
1211         mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, !hasTaskViews());
1212         // Update flags for 1p/3p launchers
1213         mActionsView.updateFor3pLauncher(!supportsAppPairs());
1214         mSplitSelectStateController = splitController;
1215         mDesktopRecentsTransitionController = desktopRecentsTransitionController;
1216     }
1217 
getSplitSelectController()1218     public SplitSelectStateController getSplitSelectController() {
1219         return mSplitSelectStateController;
1220     }
1221 
isSplitSelectionActive()1222     public boolean isSplitSelectionActive() {
1223         return mSplitSelectStateController.isSplitSelectActive();
1224     }
1225 
1226     /**
1227      * See overridden implementations
1228      *
1229      * @return {@code true} if child TaskViews can be launched when user taps on them
1230      */
canLaunchFullscreenTask()1231     public boolean canLaunchFullscreenTask() {
1232         return true;
1233     }
1234 
1235     @Override
onAttachedToWindow()1236     protected void onAttachedToWindow() {
1237         super.onAttachedToWindow();
1238         updateTaskStackListenerState();
1239         mModel.getThumbnailCache().getHighResLoadingState().addCallback(this);
1240         mContainer.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
1241         TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
1242         mSyncTransactionApplier = new SurfaceTransactionApplier(this);
1243         runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
1244                 .setSyncTransactionApplier(mSyncTransactionApplier));
1245         RecentsModel.INSTANCE.get(mContext).addThumbnailChangeListener(this);
1246         mIPipAnimationListener.setActivityAndRecentsView(mContainer, this);
1247         SystemUiProxy.INSTANCE.get(mContext).setPipAnimationListener(
1248                 mIPipAnimationListener);
1249         mOrientationState.initListeners();
1250         mTaskOverlayFactory.initListeners();
1251         mSplitSelectStateController.registerSplitListener(mSplitSelectionListener);
1252         if (mDesktopVisibilityController != null) {
1253             mDesktopVisibilityController.registerDesktopVisibilityListener(this);
1254         }
1255     }
1256 
1257     @Override
onDetachedFromWindow()1258     protected void onDetachedFromWindow() {
1259         super.onDetachedFromWindow();
1260 
1261         updateTaskStackListenerState();
1262         mModel.getThumbnailCache().getHighResLoadingState().removeCallback(this);
1263         mContainer.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
1264         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
1265         mSyncTransactionApplier = null;
1266         runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
1267                 .setSyncTransactionApplier(null));
1268         executeSideTaskLaunchCallback();
1269         RecentsModel.INSTANCE.get(mContext).removeThumbnailChangeListener(this);
1270         SystemUiProxy.INSTANCE.get(mContext).setPipAnimationListener(null);
1271         mIPipAnimationListener.setActivityAndRecentsView(null, null);
1272         mOrientationState.destroyListeners();
1273         mTaskOverlayFactory.removeListeners();
1274         mSplitSelectStateController.unregisterSplitListener(mSplitSelectionListener);
1275         if (mDesktopVisibilityController != null) {
1276             mDesktopVisibilityController.unregisterDesktopVisibilityListener(this);
1277         }
1278         reset();
1279     }
1280 
1281     /**
1282      * Execute clean-up logic needed when the view is destroyed.
1283      */
destroy()1284     public void destroy() {
1285         Log.d(TAG, "destroy");
1286         if (enableRefactorTaskThumbnail()) {
1287             try {
1288                 mTaskViewPool.killOngoingInitializations();
1289                 mGroupedTaskViewPool.killOngoingInitializations();
1290                 mDesktopTaskViewPool.killOngoingInitializations();
1291             } catch (InterruptedException e) {
1292                 Log.e(TAG, "Ongoing initializations could not be killed", e);
1293             }
1294             mHelper.onDestroy();
1295             RecentsDependencies.destroy(getContext());
1296         }
1297     }
1298 
1299     @Override
onViewRemoved(View child)1300     public void onViewRemoved(View child) {
1301         traceBegin(Trace.TRACE_TAG_APP, "RecentsView.onViewRemoved");
1302         super.onViewRemoved(child);
1303         // Clear the task data for the removed child if it was visible unless:
1304         // - It's the initial taskview for entering split screen, we only pretend to dismiss the
1305         // task
1306         // - It's the focused task to be moved to the front, we immediately re-add the task
1307         if (child instanceof TaskView) {
1308             mTaskViewCount = Math.max(0, --mTaskViewCount);
1309             if (child != mSplitHiddenTaskView && child != mMovingTaskView) {
1310                 clearAndRecycleTaskView((TaskView) child);
1311             }
1312         }
1313         traceEnd(Trace.TRACE_TAG_APP);
1314     }
1315 
clearAndRecycleTaskView(TaskView taskView)1316     private void clearAndRecycleTaskView(TaskView taskView) {
1317         for (int i : taskView.getTaskIds()) {
1318             mHasVisibleTaskData.delete(i);
1319         }
1320         if (taskView instanceof GroupedTaskView) {
1321             mGroupedTaskViewPool.recycle((GroupedTaskView) taskView);
1322         } else if (taskView instanceof DesktopTaskView) {
1323             mDesktopTaskViewPool.recycle((DesktopTaskView) taskView);
1324         } else {
1325             mTaskViewPool.recycle(taskView);
1326         }
1327         mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, !hasTaskViews());
1328     }
1329 
1330     @Override
onViewAdded(View child)1331     public void onViewAdded(View child) {
1332         traceBegin(Trace.TRACE_TAG_APP, "RecentsView.onViewAdded");
1333         super.onViewAdded(child);
1334         if (child instanceof TaskView) {
1335             mTaskViewCount++;
1336         }
1337         child.setAlpha(mContentAlpha);
1338         // RecentsView is set to RTL in the constructor when system is using LTR. Here we set the
1339         // child direction back to match system settings.
1340         child.setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_LTR : View.LAYOUT_DIRECTION_RTL);
1341         mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, false);
1342         updateEmptyMessage();
1343         traceEnd(Trace.TRACE_TAG_APP);
1344     }
1345 
1346     @Override
draw(Canvas canvas)1347     public void draw(Canvas canvas) {
1348         maybeDrawEmptyMessage(canvas);
1349         super.draw(canvas);
1350     }
1351 
1352     @Override
requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate)1353     public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
1354         if (isModal()) {
1355             // Do not scroll when clicking on a modal grid task, as it will already be centered
1356             // on screen.
1357             return false;
1358         }
1359         return super.requestChildRectangleOnScreen(child, rectangle, immediate);
1360     }
1361 
addSideTaskLaunchCallback(RunnableList callback)1362     public void addSideTaskLaunchCallback(RunnableList callback) {
1363         if (mSideTaskLaunchCallback == null) {
1364             mSideTaskLaunchCallback = new RunnableList();
1365         }
1366         mSideTaskLaunchCallback.add(callback::executeAllAndDestroy);
1367     }
1368 
1369     /**
1370      * This is a one-time callback when touching in live tile mode. It's reset to null right
1371      * after it's called.
1372      */
setTaskLaunchListener(TaskLaunchListener taskLaunchListener)1373     public void setTaskLaunchListener(TaskLaunchListener taskLaunchListener) {
1374         mTaskLaunchListener = taskLaunchListener;
1375     }
1376 
onTaskLaunchedInLiveTileMode()1377     public void onTaskLaunchedInLiveTileMode() {
1378         if (mTaskLaunchListener != null) {
1379             mTaskLaunchListener.onTaskLaunched();
1380             mTaskLaunchListener = null;
1381         }
1382     }
1383 
1384     /**
1385      * This is a one-time callback when touching in live tile mode. It's reset to null right
1386      * after it's called.
1387      */
setTaskLaunchCancelledRunnable(Runnable onTaskLaunchCancelledRunnable)1388     public void setTaskLaunchCancelledRunnable(Runnable onTaskLaunchCancelledRunnable) {
1389         mOnTaskLaunchCancelledRunnable = onTaskLaunchCancelledRunnable;
1390     }
1391 
onTaskLaunchedInLiveTileModeCancelled()1392     public void onTaskLaunchedInLiveTileModeCancelled() {
1393         if (mOnTaskLaunchCancelledRunnable != null) {
1394             mOnTaskLaunchCancelledRunnable.run();
1395             mOnTaskLaunchCancelledRunnable = null;
1396         }
1397     }
1398 
executeSideTaskLaunchCallback()1399     private void executeSideTaskLaunchCallback() {
1400         if (mSideTaskLaunchCallback != null) {
1401             mSideTaskLaunchCallback.executeAllAndDestroy();
1402             mSideTaskLaunchCallback = null;
1403         }
1404     }
1405 
1406     /**
1407      * TODO(b/195675206) Check both taskIDs from runningTaskViewId
1408      *  and launch if either of them is {@param taskId}
1409      */
launchSideTaskInLiveTileModeForRestartedApp(int taskId)1410     public void launchSideTaskInLiveTileModeForRestartedApp(int taskId) {
1411         int runningTaskViewId = getTaskViewIdFromTaskId(taskId);
1412         if (mRunningTaskViewId == -1 ||
1413                 mRunningTaskViewId != runningTaskViewId ||
1414                 mRemoteTargetHandles == null) {
1415             return;
1416         }
1417 
1418         TransformParams params = mRemoteTargetHandles[0].getTransformParams();
1419         RemoteAnimationTargets targets = params.getTargetSet();
1420         if (targets != null && targets.findTask(taskId) != null) {
1421             launchSideTaskInLiveTileMode(taskId, targets.apps, targets.wallpapers,
1422                     targets.nonApps, /* transitionInfo= */ null);
1423         }
1424     }
1425 
launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpaper, RemoteAnimationTarget[] nonApps, @Nullable TransitionInfo transitionInfo)1426     public void launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTarget[] apps,
1427             RemoteAnimationTarget[] wallpaper, RemoteAnimationTarget[] nonApps,
1428             @Nullable TransitionInfo transitionInfo) {
1429         AnimatorSet anim = new AnimatorSet();
1430         TaskView taskView = getTaskViewByTaskId(taskId);
1431         if (taskView == null || !isTaskViewVisible(taskView)) {
1432             // TODO: Refine this animation.
1433             SurfaceTransactionApplier surfaceApplier =
1434                     new SurfaceTransactionApplier(mContainer.getDragLayer());
1435             ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
1436             appAnimator.setDuration(RECENTS_LAUNCH_DURATION);
1437             appAnimator.setInterpolator(ACCELERATE_DECELERATE);
1438             final Matrix matrix = new Matrix();
1439             appAnimator.addUpdateListener(valueAnimator -> {
1440                 float percent = valueAnimator.getAnimatedFraction();
1441                 SurfaceTransaction transaction = new SurfaceTransaction();
1442                 for (int i = apps.length - 1; i >= 0; --i) {
1443                     RemoteAnimationTarget app = apps[i];
1444 
1445                     float dx = mContainer.getDeviceProfile().widthPx * (1 - percent) / 2
1446                             + app.screenSpaceBounds.left * percent;
1447                     float dy = mContainer.getDeviceProfile().heightPx * (1 - percent) / 2
1448                             + app.screenSpaceBounds.top * percent;
1449                     matrix.setScale(percent, percent);
1450                     matrix.postTranslate(dx, dy);
1451                     transaction.forSurface(app.leash)
1452                             .setAlpha(percent)
1453                             .setMatrix(matrix);
1454                 }
1455                 surfaceApplier.scheduleApply(transaction);
1456             });
1457             appAnimator.addListener(new AnimatorListenerAdapter() {
1458                 @Override
1459                 public void onAnimationStart(Animator animation) {
1460                     super.onAnimationStart(animation);
1461                     final SurfaceTransaction showTransaction = new SurfaceTransaction();
1462                     for (int i = apps.length - 1; i >= 0; --i) {
1463                         showTransaction.getTransaction().show(apps[i].leash);
1464                         showTransaction.forSurface(apps[i].leash).setLayer(
1465                                 Integer.MAX_VALUE - 1000 + apps[i].prefixOrderIndex);
1466                     }
1467                     surfaceApplier.scheduleApply(showTransaction);
1468                 }
1469             });
1470             anim.play(appAnimator);
1471             anim.addListener(new AnimatorListenerAdapter() {
1472                 @Override
1473                 public void onAnimationEnd(Animator animation) {
1474                     finishRecentsAnimation(false /* toRecents */, true /*shouldPip*/,
1475                             allAppsAreTranslucent(apps), null);
1476                 }
1477             });
1478         } else {
1479             TaskViewUtils.composeRecentsLaunchAnimator(anim, taskView, apps, wallpaper, nonApps,
1480                     true /* launcherClosing */, getStateManager(), this,
1481                     getDepthController(), transitionInfo);
1482         }
1483         anim.start();
1484     }
1485 
allAppsAreTranslucent(RemoteAnimationTarget[] apps)1486     private boolean allAppsAreTranslucent(RemoteAnimationTarget[] apps) {
1487         if (apps == null) {
1488             return false;
1489         }
1490         for (int i = apps.length - 1; i >= 0; --i) {
1491             if (!apps[i].isTranslucent) {
1492                 return false;
1493             }
1494         }
1495         return true;
1496     }
1497 
isTaskViewVisible(TaskView tv)1498     public boolean isTaskViewVisible(TaskView tv) {
1499         if (showAsGrid()) {
1500             int screenStart = getPagedOrientationHandler().getPrimaryScroll(this);
1501             int screenEnd = screenStart + getPagedOrientationHandler().getMeasuredSize(this);
1502             return isTaskViewWithinBounds(tv, screenStart, screenEnd, /*taskViewTranslation=*/ 0);
1503         } else {
1504             // For now, just check if it's the active task or an adjacent task
1505             return Math.abs(indexOfChild(tv) - getNextPage()) <= 1;
1506         }
1507     }
1508 
isTaskViewFullyVisible(TaskView tv)1509     public boolean isTaskViewFullyVisible(TaskView tv) {
1510         if (showAsGrid()) {
1511             int screenStart = getPagedOrientationHandler().getPrimaryScroll(this);
1512             int screenEnd = screenStart + getPagedOrientationHandler().getMeasuredSize(this);
1513             return isTaskViewFullyWithinBounds(tv, screenStart, screenEnd);
1514         } else {
1515             // For now, just check if it's the active task
1516             return indexOfChild(tv) == getNextPage();
1517         }
1518     }
1519 
1520     @Nullable
getLastGridTaskView()1521     private TaskView getLastGridTaskView() {
1522         return getLastGridTaskView(mUtils.getTopRowIdArray(), mUtils.getBottomRowIdArray());
1523     }
1524 
1525     @Nullable
getLastGridTaskView(IntArray topRowIdArray, IntArray bottomRowIdArray)1526     private TaskView getLastGridTaskView(IntArray topRowIdArray, IntArray bottomRowIdArray) {
1527         if (topRowIdArray.isEmpty() && bottomRowIdArray.isEmpty()) {
1528             return null;
1529         }
1530         int lastTaskViewId = topRowIdArray.size() >= bottomRowIdArray.size() ? topRowIdArray.get(
1531                 topRowIdArray.size() - 1) : bottomRowIdArray.get(bottomRowIdArray.size() - 1);
1532         return getTaskViewFromTaskViewId(lastTaskViewId);
1533     }
1534 
getSnapToLastTaskScrollDiff()1535     private int getSnapToLastTaskScrollDiff() {
1536         // Snap to a position where ClearAll is just invisible.
1537         int screenStart = getPagedOrientationHandler().getPrimaryScroll(this);
1538         int clearAllScroll = getScrollForPage(indexOfChild(mClearAllButton));
1539         int clearAllWidth = getPagedOrientationHandler().getPrimarySize(mClearAllButton);
1540         int lastTaskScroll = getLastTaskScroll(clearAllScroll, clearAllWidth);
1541         return screenStart - lastTaskScroll;
1542     }
1543 
getLastTaskScroll(int clearAllScroll, int clearAllWidth)1544     private int getLastTaskScroll(int clearAllScroll, int clearAllWidth) {
1545         int distance = clearAllWidth + getClearAllExtraPageSpacing();
1546         return clearAllScroll + (mIsRtl ? distance : -distance);
1547     }
1548 
1549     /**
1550      * Launch running task view if it is instance of DesktopTaskView.
1551      * @return provides runnable list to attach runnable at end of Desktop Mode launch
1552      */
1553     @Nullable
launchRunningDesktopTaskView()1554     public RunnableList launchRunningDesktopTaskView() {
1555         TaskView taskView = getRunningTaskView();
1556         if (taskView instanceof DesktopTaskView) {
1557             return taskView.launchWithAnimation();
1558         }
1559         return null;
1560     }
1561 
1562     /*
1563      * Returns if TaskView is within screen bounds defined in [screenStart, screenEnd].
1564      *
1565      * @param taskViewTranslation taskView is considered within bounds if either translated or
1566      * original position of taskView is within screen bounds.
1567      */
isTaskViewWithinBounds(TaskView taskView, int screenStart, int screenEnd, int taskViewTranslation)1568     protected boolean isTaskViewWithinBounds(TaskView taskView, int screenStart, int screenEnd,
1569             int taskViewTranslation) {
1570         int taskStart = getPagedOrientationHandler().getChildStart(taskView)
1571                 + (int) taskView.getOffsetAdjustment(showAsGrid());
1572         int taskSize = (int) (getPagedOrientationHandler().getMeasuredSize(taskView)
1573                 * taskView.getSizeAdjustment(showAsFullscreen()));
1574         int taskEnd = taskStart + taskSize;
1575 
1576         int translatedTaskStart = taskStart + taskViewTranslation;
1577         int translatedTaskEnd = taskEnd + taskViewTranslation;
1578 
1579         taskStart = Math.min(taskStart, translatedTaskStart);
1580         taskEnd = Math.max(taskEnd, translatedTaskEnd);
1581 
1582         return (taskStart >= screenStart && taskStart <= screenEnd) || (taskEnd >= screenStart
1583                 && taskEnd <= screenEnd);
1584     }
1585 
isTaskViewFullyWithinBounds(TaskView tv, int start, int end)1586     private boolean isTaskViewFullyWithinBounds(TaskView tv, int start, int end) {
1587         int taskStart = getPagedOrientationHandler().getChildStart(tv)
1588                 + (int) tv.getOffsetAdjustment(showAsGrid());
1589         int taskSize = (int) (getPagedOrientationHandler().getMeasuredSize(tv)
1590                 * tv.getSizeAdjustment(showAsFullscreen()));
1591         int taskEnd = taskStart + taskSize;
1592         return taskStart >= start && taskEnd <= end;
1593     }
1594 
1595     /**
1596      * Returns true if the given TaskView is in expected scroll position.
1597      */
isTaskInExpectedScrollPosition(@onNull TaskView taskView)1598     public boolean isTaskInExpectedScrollPosition(@NonNull TaskView taskView) {
1599         return getScrollForPage(indexOfChild(taskView))
1600                 == getPagedOrientationHandler().getPrimaryScroll(this);
1601     }
1602 
1603     /**
1604      * Returns true if the focused TaskView is in expected scroll position.
1605      */
isFocusedTaskInExpectedScrollPosition()1606     public boolean isFocusedTaskInExpectedScrollPosition() {
1607         TaskView focusedTask = getFocusedTaskView();
1608         return focusedTask != null && isTaskInExpectedScrollPosition(focusedTask);
1609     }
1610 
1611     /**
1612      * Returns a {@link TaskView} that has taskId matching {@code taskId} or null if no match.
1613      */
1614     @Nullable
getTaskViewByTaskId(int taskId)1615     public TaskView getTaskViewByTaskId(int taskId) {
1616         if (taskId == INVALID_TASK_ID) {
1617             return null;
1618         }
1619 
1620         for (TaskView taskView : getTaskViews()) {
1621             if (taskView.containsTaskId(taskId)) {
1622                 return taskView;
1623             }
1624         }
1625         return null;
1626     }
1627 
1628     /**
1629      * Returns a {@link TaskView} that has taskIds matching {@code taskIds} or null if no match.
1630      */
1631     @Nullable
getTaskViewByTaskIds(int[] taskIds)1632     public TaskView getTaskViewByTaskIds(int[] taskIds) {
1633         if (!hasAllValidTaskIds(taskIds)) {
1634             return null;
1635         }
1636 
1637         // We're looking for a taskView that matches these ids, regardless of order
1638         int[] taskIdsCopy = Arrays.copyOf(taskIds, taskIds.length);
1639         Arrays.sort(taskIdsCopy);
1640 
1641         for (TaskView taskView : getTaskViews()) {
1642             int[] taskViewIdsCopy = taskView.getTaskIds();
1643             Arrays.sort(taskViewIdsCopy);
1644             if (Arrays.equals(taskIdsCopy, taskViewIdsCopy)) {
1645                 return taskView;
1646             }
1647         }
1648         return null;
1649     }
1650 
1651     /** Returns false if {@code taskIds} is null or contains any invalid values, true otherwise */
hasAllValidTaskIds(int[] taskIds)1652     private boolean hasAllValidTaskIds(int[] taskIds) {
1653         return taskIds != null
1654                 && taskIds.length > 0
1655                 && Arrays.stream(taskIds).noneMatch(taskId -> taskId == INVALID_TASK_ID);
1656     }
1657 
setOverviewStateEnabled(boolean enabled)1658     public void setOverviewStateEnabled(boolean enabled) {
1659         mOverviewStateEnabled = enabled;
1660         updateTaskStackListenerState();
1661         mOrientationState.setRotationWatcherEnabled(enabled);
1662         if (!enabled) {
1663             mSplitBoundsConfig = null;
1664             mTaskOverlayFactory.clearAllActiveState();
1665         }
1666         updateLocusId();
1667     }
1668 
1669     /**
1670      * Enable or disable showing border on hover and focus change on task views
1671      */
setTaskBorderEnabled(boolean enabled)1672     public void setTaskBorderEnabled(boolean enabled) {
1673         mBorderEnabled = enabled;
1674         for (TaskView taskView : getTaskViews()) {
1675             taskView.setBorderEnabled(enabled);
1676         }
1677         mClearAllButton.setBorderEnabled(enabled);
1678         if (mAddDesktopButton != null) {
1679             mAddDesktopButton.setBorderEnabled(enabled);
1680         }
1681     }
1682 
1683     /**
1684      * Whether the Clear All button is hidden or fully visible. Used to determine if center
1685      * displayed page is a task or the Clear All button.
1686      *
1687      * @return True = Clear All button not fully visible, center page is a task. False = Clear All
1688      * button fully visible, center page is Clear All button.
1689      */
isClearAllHidden()1690     public boolean isClearAllHidden() {
1691         return mClearAllButton.getAlpha() != 1f;
1692     }
1693 
1694     @Override
onPageBeginTransition()1695     protected void onPageBeginTransition() {
1696         super.onPageBeginTransition();
1697         if (!mContainer.getDeviceProfile().isTablet) {
1698             mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, true);
1699         }
1700         if (mOverviewStateEnabled) { // only when in overview
1701             InteractionJankMonitorWrapper.begin(/* view= */ this, Cuj.CUJ_RECENTS_SCROLLING);
1702         }
1703     }
1704 
1705     @Override
onPageEndTransition()1706     protected void onPageEndTransition() {
1707         super.onPageEndTransition();
1708         ActiveGestureProtoLogProxy.logOnPageEndTransition(getNextPage());
1709         if (isClearAllHidden() && !mContainer.getDeviceProfile().isTablet) {
1710             mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
1711         }
1712         if (getNextPage() > 0) {
1713             setSwipeDownShouldLaunchApp(true);
1714         }
1715         InteractionJankMonitorWrapper.end(Cuj.CUJ_RECENTS_SCROLLING);
1716     }
1717 
1718     @Override
isSignificantMove(float absoluteDelta, int pageOrientedSize)1719     protected boolean isSignificantMove(float absoluteDelta, int pageOrientedSize) {
1720         DeviceProfile deviceProfile = mContainer.getDeviceProfile();
1721         if (!deviceProfile.isTablet) {
1722             return super.isSignificantMove(absoluteDelta, pageOrientedSize);
1723         }
1724 
1725         return absoluteDelta
1726                 > deviceProfile.availableWidthPx * SIGNIFICANT_MOVE_SCREEN_WIDTH_PERCENTAGE;
1727     }
1728 
1729     @Override
onInterceptTouchEvent(MotionEvent ev)1730     public boolean onInterceptTouchEvent(MotionEvent ev) {
1731         boolean intercept = super.onInterceptTouchEvent(ev);
1732         if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1733             Log.d("b/318590728", "onInterceptTouchEvent: " + ev);
1734         }
1735         return intercept;
1736     }
1737 
1738     @Override
onTouchEvent(MotionEvent ev)1739     public boolean onTouchEvent(MotionEvent ev) {
1740         super.onTouchEvent(ev);
1741 
1742         if (showAsGrid()) {
1743             for (TaskView taskView : getTaskViews()) {
1744                 if (isTaskViewVisible(taskView) && taskView.offerTouchToChildren(ev)) {
1745                     // Keep consuming events to pass to delegate
1746                     return true;
1747                 }
1748             }
1749         } else {
1750             TaskView taskView = getCurrentPageTaskView();
1751             if (taskView != null && taskView.offerTouchToChildren(ev)) {
1752                 // Keep consuming events to pass to delegate
1753                 return true;
1754             }
1755         }
1756 
1757         final int x = (int) ev.getX();
1758         final int y = (int) ev.getY();
1759         switch (ev.getAction()) {
1760             case MotionEvent.ACTION_UP:
1761                 if (mTouchDownToStartHome) {
1762                     startHome();
1763                 }
1764                 mTouchDownToStartHome = false;
1765                 break;
1766             case MotionEvent.ACTION_CANCEL:
1767                 mTouchDownToStartHome = false;
1768                 break;
1769             case MotionEvent.ACTION_MOVE:
1770                 // Passing the touch slop will not allow dismiss to home
1771                 if (mTouchDownToStartHome &&
1772                         (isHandlingTouch() ||
1773                                 squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop)) {
1774                     mTouchDownToStartHome = false;
1775                 }
1776                 break;
1777             case MotionEvent.ACTION_DOWN:
1778                 // Touch down anywhere but the deadzone around the visible clear all button and
1779                 // between the task views will start home on touch up
1780                 if (!isHandlingTouch() && !isModal()) {
1781                     if (mShowEmptyMessage) {
1782                         mTouchDownToStartHome = true;
1783                     } else {
1784                         updateDeadZoneRects();
1785                         final boolean clearAllButtonDeadZoneConsumed =
1786                                 mClearAllButton.getAlpha() == 1
1787                                         && mClearAllButtonDeadZoneRect.contains(x, y);
1788                         final boolean cameFromNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
1789                         int adjustedX = x + getScrollX();
1790                         if (!clearAllButtonDeadZoneConsumed && !cameFromNavBar
1791                                 && !mTaskViewDeadZoneRect.contains(adjustedX, y)
1792                                 && !mTopRowDeadZoneRect.contains(adjustedX, y)
1793                                 && !mBottomRowDeadZoneRect.contains(adjustedX, y)) {
1794                             mTouchDownToStartHome = true;
1795                         }
1796                     }
1797                 }
1798                 mDownX = x;
1799                 mDownY = y;
1800                 break;
1801         }
1802 
1803         return isHandlingTouch();
1804     }
1805 
1806     @Override
onNotSnappingToPageInFreeScroll()1807     protected void onNotSnappingToPageInFreeScroll() {
1808         int finalPos = mScroller.getFinalX();
1809         if (finalPos > mMinScroll && finalPos < mMaxScroll) {
1810             int firstPageScroll = getScrollForPage(!mIsRtl ? 0 : getPageCount() - 1);
1811             int lastPageScroll = getScrollForPage(!mIsRtl ? getPageCount() - 1 : 0);
1812 
1813             // If scrolling ends in the half of the added space that is closer to
1814             // the end, settle to the end. Otherwise snap to the nearest page.
1815             // If flinging past one of the ends, don't change the velocity as it
1816             // will get stopped at the end anyway.
1817             int pageSnapped = finalPos < (firstPageScroll + mMinScroll) / 2
1818                     ? mMinScroll
1819                     : finalPos > (lastPageScroll + mMaxScroll) / 2
1820                             ? mMaxScroll
1821                             : getScrollForPage(mNextPage);
1822 
1823             if (showAsGrid()) {
1824                 if (isSplitSelectionActive()) {
1825                     return;
1826                 }
1827                 TaskView taskView = getTaskViewAt(mNextPage);
1828                 boolean shouldSnapToLargeTask = taskView != null && taskView.isLargeTile()
1829                         && !mUtils.isAnySmallTaskFullyVisible();
1830                 boolean shouldSnapToClearAll = mNextPage == indexOfChild(mClearAllButton);
1831                 // Snap to large tile when grid tasks aren't fully visible or the clear all button.
1832                 if (!shouldSnapToLargeTask && !shouldSnapToClearAll) {
1833                     return;
1834                 }
1835             }
1836 
1837             mScroller.setFinalX(pageSnapped);
1838             // Ensure the scroll/snap doesn't happen too fast;
1839             int extraScrollDuration = OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION
1840                     - mScroller.getDuration();
1841             if (extraScrollDuration > 0) {
1842                 mScroller.extendDuration(extraScrollDuration);
1843             }
1844         }
1845     }
1846 
1847     @Override
onEdgeAbsorbingScroll()1848     protected void onEdgeAbsorbingScroll() {
1849         vibrateForScroll();
1850     }
1851 
1852     @Override
onScrollOverPageChanged()1853     protected void onScrollOverPageChanged() {
1854         vibrateForScroll();
1855     }
1856 
vibrateForScroll()1857     private void vibrateForScroll() {
1858         long now = SystemClock.uptimeMillis();
1859         if (now - mScrollLastHapticTimestamp > mScrollHapticMinGapMillis) {
1860             mScrollLastHapticTimestamp = now;
1861             VibratorWrapper.INSTANCE.get(mContext).vibrate(SCROLL_VIBRATION_PRIMITIVE,
1862                     SCROLL_VIBRATION_PRIMITIVE_SCALE, SCROLL_VIBRATION_FALLBACK);
1863         }
1864     }
1865 
1866     @Override
determineScrollingStart(MotionEvent ev, float touchSlopScale)1867     protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
1868         // Enables swiping to the left or right only if the task overlay is not modal.
1869         if (!isModal()) {
1870             super.determineScrollingStart(ev, touchSlopScale);
1871         }
1872     }
1873 
1874     /**
1875      * Moves the running task to the expected position in the carousel. In tablets, this minimize
1876      * animation required to move the running task into focused task position.
1877      */
moveRunningTaskToExpectedPosition()1878     public void moveRunningTaskToExpectedPosition() {
1879         TaskView runningTaskView = getRunningTaskView();
1880         if (runningTaskView == null || mCurrentPage != indexOfChild(runningTaskView)) {
1881             return;
1882         }
1883 
1884         int runningTaskExpectedIndex = mUtils.getRunningTaskExpectedIndex(runningTaskView);
1885         if (mCurrentPage == runningTaskExpectedIndex) {
1886             return;
1887         }
1888 
1889         int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this);
1890         int currentPageScroll = getScrollForPage(mCurrentPage);
1891         mCurrentPageScrollDiff = primaryScroll - currentPageScroll;
1892 
1893         mMovingTaskView = runningTaskView;
1894         removeView(runningTaskView);
1895         mMovingTaskView = null;
1896         runningTaskView.resetPersistentViewTransforms();
1897 
1898         addView(runningTaskView, runningTaskExpectedIndex);
1899         setCurrentPage(runningTaskExpectedIndex);
1900 
1901         updateTaskSize();
1902     }
1903 
1904     @Override
onScrollerAnimationAborted()1905     protected void onScrollerAnimationAborted() {
1906         ActiveGestureProtoLogProxy.logOnScrollerAnimationAborted();
1907     }
1908 
1909     @Override
isPageScrollsInitialized()1910     protected boolean isPageScrollsInitialized() {
1911         return super.isPageScrollsInitialized() && mLoadPlanEverApplied;
1912     }
1913 
applyLoadPlan(List<GroupTask> taskGroups)1914     protected void applyLoadPlan(List<GroupTask> taskGroups) {
1915         if (mPendingAnimation != null) {
1916             final List<GroupTask> finalTaskGroups = taskGroups;
1917             mPendingAnimation.addEndListener(success -> applyLoadPlan(finalTaskGroups));
1918             return;
1919         }
1920 
1921         if (taskGroups == null) {
1922             Log.d(TAG, "applyLoadPlan - taskGroups is null");
1923         } else {
1924             Log.d(TAG, "applyLoadPlan - taskGroups: " + taskGroups.stream().map(
1925                     GroupTask::toString).toList());
1926         }
1927         mLoadPlanEverApplied = true;
1928         if (taskGroups == null || taskGroups.isEmpty()) {
1929             removeAllTaskViews();
1930             onTaskStackUpdated();
1931             // With all tasks removed, touch handling in PagedView is disabled and we need to reset
1932             // touch state or otherwise values will be obsolete.
1933             resetTouchState();
1934             if (isPageScrollsInitialized()) {
1935                 onPageScrollsInitialized();
1936             }
1937             return;
1938         }
1939 
1940         // Start here to avoid early returns and empty cases which have special logic
1941         traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan");
1942 
1943         TaskView currentTaskView = getTaskViewAt(mCurrentPage);
1944         int[] currentTaskIds = null;
1945         // Track the current DesktopTaskView through [deskId] as a desk can be empty without any
1946         // tasks.
1947         int currentTaskViewDeskId = INACTIVE_DESK_ID;
1948         if (areMultiDesksFlagsEnabled()
1949                 && currentTaskView instanceof DesktopTaskView desktopTaskView) {
1950             currentTaskViewDeskId = desktopTaskView.getDeskId();
1951         } else if (currentTaskView != null) {
1952             currentTaskIds = currentTaskView.getTaskIds();
1953         }
1954 
1955         // Unload existing visible task data
1956         unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
1957 
1958         TaskView ignoreResetTaskView =
1959                 mIgnoreResetTaskId == INVALID_TASK_ID
1960                         ? null : getTaskViewByTaskId(mIgnoreResetTaskId);
1961 
1962         // Save running task ID if it exists before rebinding all taskViews, otherwise the task from
1963         // the runningTaskView currently bound could get assigned to another TaskView
1964         TaskView runningTaskView = getRunningTaskView();
1965         int[] runningTaskIds = null;
1966 
1967         // Track the running TaskView through [deskId] as a desk can be empty without any tasks.
1968         int runningTaskViewDeskId = INACTIVE_DESK_ID;
1969         if (areMultiDesksFlagsEnabled()
1970                 && runningTaskView instanceof DesktopTaskView desktopTaskView) {
1971             runningTaskViewDeskId = desktopTaskView.getDeskId();
1972         } else if (runningTaskView != null) {
1973             runningTaskIds = runningTaskView.getTaskIds();
1974         }
1975 
1976         int[] focusedTaskIds = getTaskIdsForTaskViewId(mFocusedTaskViewId);
1977         // Reset the focused task to avoiding initializing TaskViews layout as focused task during
1978         // binding. The focused task view will be updated after all the TaskViews are bound.
1979         setFocusedTaskViewId(INVALID_TASK_ID);
1980 
1981         // Removing views sets the currentPage to 0, so we save this and restore it after
1982         // the new set of views are added
1983         int previousCurrentPage = mCurrentPage;
1984         int previousFocusedPage = indexOfChild(getFocusedChild());
1985         // TaskIds will no longer be valid after remove and re-add, clearing mTopRowIdSet.
1986         mAnyTaskHasBeenDismissed = false;
1987         mTopRowIdSet.clear();
1988         traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.removeAllViews");
1989         removeAllViews();
1990         traceEnd(Trace.TRACE_TAG_APP);
1991         // If we are entering Overview as a result of initiating a split from somewhere else
1992         // (e.g. split from Home), we need to make sure the staged app is not drawn as a thumbnail.
1993         int stagedTaskIdToBeRemoved;
1994         if (isSplitSelectionActive()) {
1995             stagedTaskIdToBeRemoved = mSplitSelectStateController.getInitialTaskId();
1996             updateCurrentTaskActionsVisibility();
1997         } else {
1998             stagedTaskIdToBeRemoved = INVALID_TASK_ID;
1999         }
2000         // update the map of instance counts
2001         mFilterState.updateInstanceCountMap(taskGroups);
2002 
2003         // Clear out desktop view if it is set
2004 
2005         // Move Desktop Tasks to the end of the list
2006         if (enableLargeDesktopWindowingTile()) {
2007             taskGroups = mUtils.sortDesktopTasksToFront(taskGroups);
2008         }
2009         if (enableSeparateExternalDisplayTasks()) {
2010             taskGroups = mUtils.sortExternalDisplayTasksToFront(taskGroups);
2011         }
2012 
2013         if (mAddDesktopButton != null) {
2014             // Add `mAddDesktopButton` as the first child.
2015             addView(mAddDesktopButton);
2016         }
2017         traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop");
2018 
2019         // Add views as children based on whether it's grouped or single task. Looping through
2020         // taskGroups backwards populates the thumbnail grid from least recent to most recent.
2021         for (int i = taskGroups.size() - 1; i >= 0; i--) {
2022             GroupTask groupTask = taskGroups.get(i);
2023             boolean containsStagedTask = stagedTaskIdToBeRemoved != INVALID_TASK_ID
2024                     && groupTask.containsTask(stagedTaskIdToBeRemoved);
2025             boolean shouldSkipGroupTask = containsStagedTask && groupTask instanceof SingleTask;
2026 
2027             if ((isSplitSelectionActive() && groupTask.taskViewType == TaskViewType.DESKTOP)
2028                     || shouldSkipGroupTask) {
2029                 // To avoid these tasks from being chosen as the app pair, the creation of a
2030                 // TaskView is bypassed. The staged task is already selected for the app pair,
2031                 // and the Desktop task should be hidden when selecting a pair.
2032                 continue;
2033             }
2034 
2035             // If we need to remove half of a pair of tasks, force a TaskView with Type.SINGLE
2036             // to be a temporary container for the remaining task.
2037             traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop.createTaskView");
2038             TaskView taskView = getTaskViewFromPool(
2039                     containsStagedTask ? TaskViewType.SINGLE : groupTask.taskViewType);
2040             traceEnd(Trace.TRACE_TAG_APP);
2041             traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop.bind");
2042             if (taskView instanceof GroupedTaskView groupedTaskView) {
2043                 var splitTask = (SplitTask) groupTask;
2044                 groupedTaskView.bind(splitTask.getTopLeftTask(),
2045                         splitTask.getBottomRightTask(), mOrientationState,
2046                         mTaskOverlayFactory, splitTask.getSplitBounds());
2047             } else if (taskView instanceof DesktopTaskView desktopTaskView) {
2048                 desktopTaskView.bind((DesktopTask) groupTask, mOrientationState,
2049                         mTaskOverlayFactory);
2050             } else if (groupTask instanceof SplitTask splitTask) {
2051                 Task task = splitTask.getTopLeftTask().key.id == stagedTaskIdToBeRemoved
2052                         ? splitTask.getBottomRightTask()
2053                         : splitTask.getTopLeftTask();
2054                 taskView.bind(task, mOrientationState, mTaskOverlayFactory);
2055             } else {
2056                 taskView.bind(((SingleTask) groupTask).getTask(), mOrientationState,
2057                         mTaskOverlayFactory);
2058             }
2059             traceEnd(Trace.TRACE_TAG_APP);
2060             traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop.addTaskView");
2061             addView(taskView);
2062             traceEnd(Trace.TRACE_TAG_APP);
2063 
2064             // enables instance filtering if the feature flag for it is on
2065             if (FeatureFlags.ENABLE_MULTI_INSTANCE.get()) {
2066                 taskView.setUpShowAllInstancesListener();
2067             }
2068         }
2069         // For loop end trace
2070         traceEnd(Trace.TRACE_TAG_APP);
2071 
2072         addView(mClearAllButton);
2073 
2074         // Keep same previous focused task
2075         TaskView newFocusedTaskView = null;
2076         if (!enableGridOnlyOverview()) {
2077             newFocusedTaskView = getTaskViewByTaskIds(focusedTaskIds);
2078             if (enableLargeDesktopWindowingTile()
2079                     && newFocusedTaskView instanceof DesktopTaskView) {
2080                 newFocusedTaskView = null;
2081             }
2082             // If the list changed, maybe the focused task doesn't exist anymore.
2083             if (newFocusedTaskView == null) {
2084                 newFocusedTaskView = mUtils.getFirstNonDesktopTaskView();
2085             }
2086         }
2087         setFocusedTaskViewId(
2088                 newFocusedTaskView != null ? newFocusedTaskView.getTaskViewId() : INVALID_TASK_ID);
2089 
2090         traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.layouts");
2091         updateTaskSize();
2092         mUtils.updateChildTaskOrientations();
2093         traceEnd(Trace.TRACE_TAG_APP);
2094 
2095         TaskView newRunningTaskView = mUtils.getDesktopTaskViewForDeskId(runningTaskViewDeskId);
2096         if (newRunningTaskView == null) {
2097             // Update mRunningTaskViewId to be the new TaskView that was assigned by binding
2098             // the full list of tasks to taskViews
2099             newRunningTaskView = getTaskViewByTaskIds(runningTaskIds);
2100         }
2101         if (newRunningTaskView != null) {
2102             setRunningTaskViewId(newRunningTaskView.getTaskViewId());
2103         } else {
2104             if (mActiveGestureGroupedTaskInfo != null) {
2105                 // This will update mRunningTaskViewId and create a stub view if necessary.
2106                 // We try to avoid this because it can cause a scroll jump, but it is needed
2107                 // for cases where the running task isn't included in this load plan (e.g. if
2108                 // the current running task is excludedFromRecents.)
2109                 showCurrentTask(mActiveGestureGroupedTaskInfo, "applyLoadPlan");
2110                 newRunningTaskView = getRunningTaskView();
2111             } else {
2112                 setRunningTaskViewId(INVALID_TASK_ID);
2113             }
2114         }
2115 
2116         int targetPage = -1;
2117         if (mNextPage != INVALID_PAGE) {
2118             // Restore mCurrentPage but don't call setCurrentPage() as that clobbers the scroll.
2119             mCurrentPage = previousCurrentPage;
2120             currentTaskView = mUtils.getDesktopTaskViewForDeskId(currentTaskViewDeskId);
2121             if (currentTaskView == null) {
2122                 currentTaskView = getTaskViewByTaskIds(currentTaskIds);
2123             }
2124             if (currentTaskView != null) {
2125                 targetPage = indexOfChild(currentTaskView);
2126             }
2127         } else if (previousFocusedPage != INVALID_PAGE) {
2128             targetPage = previousFocusedPage;
2129         } else {
2130             targetPage = indexOfChild(
2131                     mUtils.getExpectedCurrentTask(newRunningTaskView, newFocusedTaskView));
2132         }
2133         if (targetPage != -1 && mCurrentPage != targetPage) {
2134             int finalTargetPage = targetPage;
2135             runOnPageScrollsInitialized(() -> {
2136                 // TODO(b/246283207): Remove logging once root cause of flake detected.
2137                 if (Utilities.isRunningInTestHarness()) {
2138                     Log.d("b/246283207", "RecentsView#applyLoadPlan() -> "
2139                             + "previousCurrentPage: " + previousCurrentPage
2140                             + ", targetPage: " + finalTargetPage
2141                             + ", getScrollForPage(targetPage): "
2142                             + getScrollForPage(finalTargetPage));
2143                 }
2144                 setCurrentPage(finalTargetPage);
2145             });
2146         }
2147 
2148         traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.cleanupStates");
2149         if (mIgnoreResetTaskId != INVALID_TASK_ID &&
2150                 getTaskViewByTaskId(mIgnoreResetTaskId) != ignoreResetTaskView) {
2151             // If the taskView mapping is changing, do not preserve the visuals. Since we are
2152             // mostly preserving the first task, and new taskViews are added to the end, it should
2153             // generally map to the same task.
2154             mIgnoreResetTaskId = INVALID_TASK_ID;
2155         }
2156 
2157         resetTaskVisuals();
2158         onTaskStackUpdated();
2159         updateEnabledOverlays();
2160         if (isPageScrollsInitialized()) {
2161             onPageScrollsInitialized();
2162         }
2163         traceEnd(Trace.TRACE_TAG_APP);
2164 
2165         // applyLoadPlan end trace
2166         traceEnd(Trace.TRACE_TAG_APP);
2167     }
2168 
isModal()2169     private boolean isModal() {
2170         return mTaskModalness > 0;
2171     }
2172 
isLoadingTasks()2173     public boolean isLoadingTasks() {
2174         return mModel.isLoadingTasksInBackground();
2175     }
2176 
removeAllTaskViews()2177     private void removeAllTaskViews() {
2178         // This handles an edge case where applyLoadPlan happens during a gesture when the only
2179         // Task is one with excludeFromRecents, in which case we should not remove it.
2180         CollectionsKt
2181                 .filter(getTaskViews(), taskView -> !isGestureActive() || !taskView.isRunningTask())
2182                 .forEach(this::removeView);
2183         if (!hasTaskViews()) {
2184             removeView(mAddDesktopButton);
2185             removeView(mClearAllButton);
2186         }
2187     }
2188 
2189     /** Returns true if there are at least one TaskView has been added to the RecentsView. */
hasTaskViews()2190     public boolean hasTaskViews() {
2191         return mUtils.hasTaskViews();
2192     }
2193 
getTaskViewCount()2194     public int getTaskViewCount() {
2195         return mTaskViewCount;
2196     }
2197 
2198     /** Counts {@link TaskView}s that are not {@link DesktopTaskView} instances. */
getNonDesktopTaskViewCount()2199     public int getNonDesktopTaskViewCount() {
2200         return mUtils.getNonDesktopTaskViewCount();
2201     }
2202 
2203     /**
2204      * Returns the number of tasks in the top row of the overview grid.
2205      */
getTopRowTaskCountForTablet()2206     public int getTopRowTaskCountForTablet() {
2207         return mTopRowIdSet.size();
2208     }
2209 
2210     /**
2211      * Returns the number of tasks in the bottom row of the overview grid.
2212      */
getBottomRowTaskCountForTablet()2213     public int getBottomRowTaskCountForTablet() {
2214         return getTaskViewCount() - mTopRowIdSet.size() - (enableGridOnlyOverview() ? 0 : 1);
2215     }
2216 
onTaskStackUpdated()2217     protected void onTaskStackUpdated() {
2218         // Lazily update the empty message only when the task stack is reapplied
2219         updateEmptyMessage();
2220     }
2221 
resetTaskVisuals()2222     public void resetTaskVisuals() {
2223         for (TaskView taskView : getTaskViews()) {
2224             if (Arrays.stream(taskView.getTaskIds()).noneMatch(
2225                     taskId -> taskId == mIgnoreResetTaskId)) {
2226                 taskView.resetViewTransforms();
2227                 taskView.setIconVisibleForGesture(mTaskIconVisible);
2228                 taskView.setStableAlpha(mContentAlpha);
2229                 taskView.setFullscreenProgress(mFullscreenProgress);
2230                 taskView.setModalness(mTaskModalness);
2231                 taskView.setTaskThumbnailSplashAlpha(mTaskThumbnailSplashAlpha);
2232                 taskView.setBorderEnabled(mBorderEnabled);
2233             }
2234         }
2235         // resetTaskVisuals is called at the end of dismiss animation which could update
2236         // primary and secondary translation of the live tile cut out. We will need to do so
2237         // here accordingly.
2238         runActionOnRemoteHandles(remoteTargetHandle -> {
2239             TaskViewSimulator simulator = remoteTargetHandle.getTaskViewSimulator();
2240             simulator.taskPrimaryTranslation.value = 0;
2241             simulator.taskSecondaryTranslation.value = 0;
2242             simulator.fullScreenProgress.value = 0;
2243             simulator.recentsViewScale.value = 1;
2244         });
2245         // Reapply runningTask related attributes as they might have been reset by
2246         // resetViewTransforms().
2247         setRunningTaskViewShowScreenshot(mRunningTaskShowScreenshot);
2248         applyAttachAlpha();
2249 
2250         updateCurveProperties();
2251         // Update the set of visible task's data
2252         loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
2253         setTaskModalness(0);
2254         setColorTint(0);
2255     }
2256 
setFullscreenProgress(float fullscreenProgress)2257     public void setFullscreenProgress(float fullscreenProgress) {
2258         mFullscreenProgress = fullscreenProgress;
2259         for (TaskView taskView : getTaskViews()) {
2260             taskView.setFullscreenProgress(mFullscreenProgress);
2261         }
2262         mClearAllButton.setFullscreenProgress(fullscreenProgress);
2263 
2264         // Fade out the actions view quickly (0.1 range)
2265         mActionsView.getFullscreenAlpha().updateValue(
2266                 mapToRange(fullscreenProgress, 0, 0.1f, 1f, 0f, LINEAR));
2267     }
2268 
updateTaskStackListenerState()2269     private void updateTaskStackListenerState() {
2270         boolean handleTaskStackChanges = mOverviewStateEnabled && isAttachedToWindow()
2271                 && getWindowVisibility() == VISIBLE;
2272         if (handleTaskStackChanges != mHandleTaskStackChanges) {
2273             Log.d(TAG, "updateTaskStackListenerState: " + handleTaskStackChanges);
2274             mHandleTaskStackChanges = handleTaskStackChanges;
2275             if (handleTaskStackChanges) {
2276                 reloadIfNeeded();
2277             }
2278         }
2279     }
2280 
2281     @Override
setInsets(Rect insets)2282     public void setInsets(Rect insets) {
2283         mInsets.set(insets);
2284 
2285         // Update DeviceProfile dependant state.
2286         DeviceProfile dp = mContainer.getDeviceProfile();
2287         setOverviewGridEnabled(
2288                 getStateManager().getState().displayOverviewTasksAsGrid(dp));
2289         if (enableGridOnlyOverview()) {
2290             mActionsView.updateHiddenFlags(HIDDEN_ACTIONS_IN_MENU, dp.isTablet);
2291         }
2292         setPageSpacing(dp.overviewPageSpacing);
2293 
2294         // Propagate DeviceProfile change event.
2295         runActionOnRemoteHandles(
2296                 remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator().setDp(dp));
2297         mOrientationState.setDeviceProfile(dp);
2298 
2299         // Update RecentsView and TaskView's DeviceProfile dependent layout.
2300         updateOrientationHandler();
2301         mActionsView.updateDimension(dp, mLastComputedTaskSize);
2302     }
2303 
updateOrientationHandler()2304     private void updateOrientationHandler() {
2305         updateOrientationHandler(true);
2306     }
2307 
updateOrientationHandler(boolean forceRecreateDragLayerControllers)2308     private void updateOrientationHandler(boolean forceRecreateDragLayerControllers) {
2309         // Handle orientation changes.
2310         RecentsPagedOrientationHandler oldOrientationHandler = getPagedOrientationHandler();
2311         setOrientationHandler(mOrientationState.getOrientationHandler());
2312 
2313         mIsRtl = getPagedOrientationHandler().getRecentsRtlSetting(getResources());
2314         setLayoutDirection(mIsRtl
2315                 ? View.LAYOUT_DIRECTION_RTL
2316                 : View.LAYOUT_DIRECTION_LTR);
2317         mClearAllButton.setLayoutDirection(mIsRtl
2318                 ? View.LAYOUT_DIRECTION_LTR
2319                 : View.LAYOUT_DIRECTION_RTL);
2320         mClearAllButton.setRotation(getPagedOrientationHandler().getDegreesRotated());
2321 
2322         boolean isOrientationHandlerChanged =
2323                 !getPagedOrientationHandler().equals(oldOrientationHandler);
2324         if (forceRecreateDragLayerControllers || isOrientationHandlerChanged) {
2325             // Changed orientations, update controllers so they intercept accordingly.
2326             mContainer.getDragLayer().recreateControllers();
2327             onOrientationChanged();
2328             resetTaskVisuals();
2329             // Log fake orientation changed.
2330             if (isOrientationHandlerChanged) {
2331                 logOrientationChanged();
2332             }
2333         }
2334 
2335         boolean isInLandscape = mOrientationState.getTouchRotation() != ROTATION_0
2336                 || mOrientationState.getRecentsActivityRotation() != ROTATION_0;
2337         mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION,
2338                 !mOrientationState.isRecentsActivityRotationAllowed() && isInLandscape);
2339 
2340         // Recalculate DeviceProfile dependent layout.
2341         updateSizeAndPadding();
2342 
2343         // Update TaskView's DeviceProfile dependent layout.
2344         mUtils.updateChildTaskOrientations();
2345 
2346         requestLayout();
2347         // Reapply the current page to update page scrolls.
2348         setCurrentPage(mCurrentPage);
2349     }
2350 
onOrientationChanged()2351     private void onOrientationChanged() {
2352         // If overview is in modal state when rotate, reset it to overview state without running
2353         // animation.
2354         setModalStateEnabled(/* taskId= */ INVALID_TASK_ID, /* animate= */ false);
2355         if (isSplitSelectionActive()) {
2356             onRotateInSplitSelectionState();
2357         }
2358     }
2359 
2360     // Update task size and padding that are dependent on DeviceProfile and insets.
updateSizeAndPadding()2361     private void updateSizeAndPadding() {
2362         DeviceProfile dp = mContainer.getDeviceProfile();
2363         getTaskSize(mLastComputedTaskSize);
2364         mTaskWidth = mLastComputedTaskSize.width();
2365         mTaskHeight = mLastComputedTaskSize.height();
2366         setPadding(mLastComputedTaskSize.left - mInsets.left,
2367                 mLastComputedTaskSize.top - dp.overviewTaskThumbnailTopMarginPx - mInsets.top,
2368                 dp.widthPx - mInsets.right - mLastComputedTaskSize.right,
2369                 dp.heightPx - mInsets.bottom - mLastComputedTaskSize.bottom);
2370 
2371         mSizeStrategy.calculateGridSize(dp, mContainer, mLastComputedGridSize);
2372         mSizeStrategy.calculateGridTaskSize(mContainer, dp, mLastComputedGridTaskSize,
2373                 getPagedOrientationHandler());
2374 
2375         mTaskGridVerticalDiff = mLastComputedGridTaskSize.top - mLastComputedTaskSize.top;
2376         mTopBottomRowHeightDiff =
2377                 mLastComputedGridTaskSize.height() + dp.overviewTaskThumbnailTopMarginPx
2378                         + dp.overviewRowSpacing;
2379 
2380         // Force TaskView to update size from thumbnail
2381         updateTaskSize();
2382         updatePivots();
2383     }
2384 
2385     /**
2386      * Updates TaskView scaling and translation required to support variable width.
2387      */
updateTaskSize()2388     private void updateTaskSize() {
2389         if (!hasTaskViews()) {
2390             return;
2391         }
2392 
2393         float accumulatedTranslationX = 0;
2394         for (TaskView taskView : getTaskViews()) {
2395             taskView.updateTaskSize(mLastComputedTaskSize, mLastComputedGridTaskSize);
2396             taskView.setNonGridTranslationX(accumulatedTranslationX);
2397             // Compensate space caused by TaskView scaling.
2398             float widthDiff =
2399                     taskView.getLayoutParams().width * (1 - taskView.getNonGridScale());
2400             accumulatedTranslationX += mIsRtl ? widthDiff : -widthDiff;
2401         }
2402 
2403         mClearAllButton.setFullscreenTranslationPrimary(accumulatedTranslationX);
2404 
2405         float taskAlignmentTranslationY = getTaskAlignmentTranslationY();
2406         mClearAllButton.setTaskAlignmentTranslationY(taskAlignmentTranslationY);
2407         if (mAddDesktopButton != null) {
2408             mAddDesktopButton.setTranslationY(taskAlignmentTranslationY);
2409         }
2410 
2411         updateGridProperties();
2412     }
2413 
getTaskSize(Rect outRect)2414     public void getTaskSize(Rect outRect) {
2415         mSizeStrategy.calculateTaskSize(mContainer, mContainer.getDeviceProfile(), outRect,
2416                 getPagedOrientationHandler());
2417     }
2418 
2419     /**
2420      * Returns the currently selected TaskView in Select mode.
2421      */
2422     @Nullable
getSelectedTaskView()2423     public TaskView getSelectedTaskView() {
2424         return mUtils.getSelectedTaskView();
2425     }
2426 
2427     /**
2428      * Sets the selected TaskView in Select mode.
2429      */
setSelectedTask(int lastSelectedTaskId)2430     public void setSelectedTask(int lastSelectedTaskId) {
2431         mUtils.setSelectedTaskView(getTaskViewByTaskId(lastSelectedTaskId));
2432     }
2433 
2434     /**
2435      * Returns the bounds of the task selected to enter modal state.
2436      */
getSelectedTaskBounds()2437     public Rect getSelectedTaskBounds() {
2438         if (getSelectedTaskView() == null) {
2439             return mLastComputedTaskSize;
2440         }
2441         return getTaskBounds(getSelectedTaskView());
2442     }
2443 
2444     /**
2445      * Get the Y translation that should be applied to the non-TaskView item inside the RecentsView
2446      * (ClearAllButton and AddDesktopButton) in the original layout position, before scrolling. This
2447      * is done to make sure the button is aligned to the middle of Task thumbnail in y coordinate.
2448      */
getTaskAlignmentTranslationY()2449     private float getTaskAlignmentTranslationY() {
2450         DeviceProfile deviceProfile = mContainer.getDeviceProfile();
2451         if (deviceProfile.isTablet) {
2452             return deviceProfile.overviewRowSpacing;
2453         }
2454         return deviceProfile.overviewTaskThumbnailTopMarginPx / 2.0f;
2455     }
2456 
getTaskBounds(TaskView taskView)2457     protected Rect getTaskBounds(TaskView taskView) {
2458         int selectedPage = indexOfChild(taskView);
2459         int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this);
2460         int selectedPageScroll = getScrollForPage(selectedPage);
2461         boolean isTopRow = mTopRowIdSet.contains(taskView.getTaskViewId());
2462         Rect outRect = new Rect(
2463                 taskView.isGridTask() ? mLastComputedGridTaskSize : mLastComputedTaskSize);
2464         outRect.offset(
2465                 -(primaryScroll - (selectedPageScroll + getOffsetFromScrollPosition(selectedPage))),
2466                 (int) (showAsGrid() && enableGridOnlyOverview() && !isTopRow
2467                         ? mTopBottomRowHeightDiff : 0));
2468         return outRect;
2469     }
2470 
2471     /** Gets the last computed task size */
getLastComputedTaskSize()2472     public Rect getLastComputedTaskSize() {
2473         return mLastComputedTaskSize;
2474     }
2475 
getLastComputedGridTaskSize()2476     public Rect getLastComputedGridTaskSize() {
2477         return mLastComputedGridTaskSize;
2478     }
2479 
2480     /** Gets the task size for modal state. */
getModalTaskSize(Rect outRect)2481     public void getModalTaskSize(Rect outRect) {
2482         mSizeStrategy.calculateModalTaskSize(mContainer, mContainer.getDeviceProfile(), outRect,
2483                 getPagedOrientationHandler());
2484     }
2485 
2486     @Override
computeScrollHelper()2487     protected boolean computeScrollHelper() {
2488         boolean scrolling = super.computeScrollHelper();
2489         boolean isFlingingFast = false;
2490         updateCurveProperties();
2491         if (scrolling || isHandlingTouch()) {
2492             if (scrolling) {
2493                 // Check if we are flinging quickly to disable high res thumbnail loading
2494                 isFlingingFast = mScroller.getCurrVelocity() > mFastFlingVelocity;
2495             }
2496 
2497             // After scrolling, update the visible task's data
2498             loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
2499         }
2500 
2501         // Update ActionsView's visibility when scroll changes.
2502         updateActionsViewFocusedScroll();
2503 
2504         // Update the high res thumbnail loader state
2505         mModel.getThumbnailCache().getHighResLoadingState().setFlingingFast(isFlingingFast);
2506         return scrolling;
2507     }
2508 
updateActionsViewFocusedScroll()2509     private void updateActionsViewFocusedScroll() {
2510         if (showAsGrid()) {
2511             float actionsViewAlphaValue = isFocusedTaskInExpectedScrollPosition() ? 1 : 0;
2512             // If animation is already in progress towards the same end value, do not restart.
2513             if (mActionsViewAlphaAnimator == null || !mActionsViewAlphaAnimator.isStarted()
2514                     || (mActionsViewAlphaAnimator.isStarted()
2515                     && mActionsViewAlphaAnimatorFinalValue != actionsViewAlphaValue)) {
2516                 animateActionsViewAlpha(actionsViewAlphaValue,
2517                         DEFAULT_ACTIONS_VIEW_ALPHA_ANIMATION_DURATION);
2518             }
2519         }
2520     }
2521 
animateActionsViewAlpha(float alphaValue, long duration)2522     private void animateActionsViewAlpha(float alphaValue, long duration) {
2523         mActionsViewAlphaAnimator = ObjectAnimator.ofFloat(mActionsView.getVisibilityAlpha(),
2524                 AnimatedFloat.VALUE, alphaValue);
2525         mActionsViewAlphaAnimatorFinalValue = alphaValue;
2526         mActionsViewAlphaAnimator.setDuration(duration);
2527         // Set autocancel to prevent race-conditiony setting of alpha from other animations
2528         mActionsViewAlphaAnimator.setAutoCancel(true);
2529         mActionsViewAlphaAnimator.start();
2530     }
2531 
2532     /**
2533      * Scales and adjusts translation of adjacent pages as if on a curved carousel.
2534      */
updateCurveProperties()2535     public void updateCurveProperties() {
2536         if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
2537             return;
2538         }
2539         int scroll = getPagedOrientationHandler().getPrimaryScroll(this);
2540         mClearAllButton.onRecentsViewScroll(scroll, mOverviewGridEnabled);
2541 
2542         // Clear all button alpha was set by the previous line.
2543         mActionsView.getIndexScrollAlpha().updateValue(1 - mClearAllButton.getScrollAlpha());
2544     }
2545 
2546     @Override
getDestinationPage(int scaledScroll)2547     protected int getDestinationPage(int scaledScroll) {
2548         if (!mContainer.getDeviceProfile().isTablet) {
2549             return super.getDestinationPage(scaledScroll);
2550         }
2551         if (!isPageScrollsInitialized()) {
2552             Log.e(TAG,
2553                     "Cannot get destination page: RecentsView not properly initialized",
2554                     new IllegalStateException());
2555             return INVALID_PAGE;
2556         }
2557 
2558         // When in tablet with variable task width, return the page which scroll is closest to
2559         // screenStart instead of page nearest to center of screen.
2560         int minDistanceFromScreenStart = Integer.MAX_VALUE;
2561         int minDistanceFromScreenStartIndex = INVALID_PAGE;
2562         for (int i = 0; i < getChildCount(); ++i) {
2563             // Do not set the destination page to the AddDesktopButton, which has the same page
2564             // scrolls as the first [TaskView] and shouldn't be scrolled to.
2565             if (getChildAt(i) instanceof AddDesktopButton) {
2566                 continue;
2567             }
2568             int distanceFromScreenStart = Math.abs(mPageScrolls[i] - scaledScroll);
2569             if (distanceFromScreenStart < minDistanceFromScreenStart) {
2570                 minDistanceFromScreenStart = distanceFromScreenStart;
2571                 minDistanceFromScreenStartIndex = i;
2572             }
2573         }
2574         return minDistanceFromScreenStartIndex;
2575     }
2576 
2577     /**
2578      * Iterates through all the tasks, and loads the associated task data for newly visible tasks,
2579      * and unloads the associated task data for tasks that are no longer visible.
2580      */
loadVisibleTaskData(@askView.TaskDataChanges int dataChanges)2581     public void loadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) {
2582         boolean hasLeftOverview = !mOverviewStateEnabled && mScroller.isFinished();
2583         if (hasLeftOverview || mTaskListChangeId == -1) {
2584             // Skip loading visible task data if we've already left the overview state, or if the
2585             // task list hasn't been loaded yet (the task views will not reflect the task list)
2586             return;
2587         }
2588 
2589         int lowerIndex, upperIndex, visibleStart, visibleEnd;
2590         if (showAsGrid()) {
2591             int screenStart = getPagedOrientationHandler().getPrimaryScroll(this);
2592             int pageOrientedSize = getPagedOrientationHandler().getMeasuredSize(this);
2593             // For GRID_ONLY_OVERVIEW, use +/- 1 task column as visible area for preloading
2594             // adjacent thumbnails, otherwise use +/-50% screen width
2595             int extraWidth =
2596                     enableGridOnlyOverview() ? getLastComputedTaskSize().width() + getPageSpacing()
2597                             : pageOrientedSize / 2;
2598             lowerIndex = upperIndex = 0;
2599             visibleStart = screenStart - extraWidth;
2600             visibleEnd = screenStart + pageOrientedSize + extraWidth;
2601         } else {
2602             int centerPageIndex = getPageNearestToCenterOfScreen();
2603             int numChildren = getChildCount();
2604             lowerIndex = Math.max(0, centerPageIndex - 2);
2605             upperIndex = Math.min(centerPageIndex + 2, numChildren - 1);
2606             visibleStart = visibleEnd = 0;
2607         }
2608 
2609         List<Integer> visibleTaskIds = new ArrayList<>();
2610         // Update the task data for the in/visible children
2611         getTaskViews().forEachWithIndexInParent((index, taskView) -> {
2612             List<TaskContainer> containers = taskView.getTaskContainers();
2613             if (containers.isEmpty()) {
2614                 return;
2615             }
2616             boolean visible;
2617             if (showAsGrid()) {
2618                 visible = isTaskViewWithinBounds(taskView, visibleStart, visibleEnd,
2619                         mTaskViewsDismissPrimaryTranslations.getOrDefault(taskView, 0));
2620             } else {
2621                 visible = index >= lowerIndex && index <= upperIndex;
2622             }
2623             if (visible) {
2624                 // Default update all non-null tasks, then remove running ones
2625                 List<Task> tasksToUpdate = containers.stream()
2626                         .map(TaskContainer::getTask)
2627                         .collect(Collectors.toCollection(ArrayList::new));
2628                 if (enableRefactorTaskThumbnail()) {
2629                     visibleTaskIds.addAll(
2630                             tasksToUpdate.stream().map((task) -> task.key.id).toList());
2631                 }
2632                 if (tasksToUpdate.isEmpty()) {
2633                     return;
2634                 }
2635                 int visibilityChanges = 0;
2636                 for (Task task : tasksToUpdate) {
2637                     if (!mHasVisibleTaskData.get(task.key.id)) {
2638                         // Ignore thumbnail update if it's current running task during the gesture
2639                         // We snapshot at end of gesture, it will update then
2640                         int changes = dataChanges;
2641                         if (taskView == getRunningTaskView() && isGestureActive()) {
2642                             changes &= ~TaskView.FLAG_UPDATE_THUMBNAIL;
2643                         }
2644                         visibilityChanges |= changes;
2645                     }
2646                     mHasVisibleTaskData.put(task.key.id, true);
2647                 }
2648                 if (visibilityChanges != 0) {
2649                     taskView.onTaskListVisibilityChanged(true /* visible */, visibilityChanges);
2650                 }
2651             } else {
2652                 int visibilityChanges = 0;
2653                 for (TaskContainer container : containers) {
2654                     if (container == null) {
2655                         continue;
2656                     }
2657 
2658                     if (mHasVisibleTaskData.get(container.getTask().key.id)) {
2659                         visibilityChanges = dataChanges;
2660                     }
2661                     mHasVisibleTaskData.delete(container.getTask().key.id);
2662                 }
2663                 if (visibilityChanges != 0) {
2664                     taskView.onTaskListVisibilityChanged(false /* visible */, visibilityChanges);
2665                 }
2666             }
2667         });
2668         if (enableRefactorTaskThumbnail()) {
2669             mRecentsViewModel.updateVisibleTasks(visibleTaskIds);
2670         }
2671     }
2672 
2673     /**
2674      * Unloads any associated data from the currently visible tasks
2675      */
unloadVisibleTaskData(@askView.TaskDataChanges int dataChanges)2676     private void unloadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) {
2677         for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
2678             if (mHasVisibleTaskData.valueAt(i)) {
2679                 TaskView taskView = getTaskViewByTaskId(mHasVisibleTaskData.keyAt(i));
2680                 if (taskView != null) {
2681                     taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges);
2682                 }
2683             }
2684         }
2685         mHasVisibleTaskData.clear();
2686     }
2687 
2688     @Override
onHighResLoadingStateChanged(boolean enabled)2689     public void onHighResLoadingStateChanged(boolean enabled) {
2690         // Preload cache when no overview task is visible (e.g. not in overview page), so when
2691         // user goes to overview next time, the task thumbnails would show up without delay
2692         if (mHasVisibleTaskData.size() == 0) {
2693             mModel.preloadCacheIfNeeded();
2694         }
2695 
2696         if (enableRefactorTaskThumbnail()) {
2697             return;
2698         }
2699 
2700         // Whenever the high res loading state changes, poke each of the visible tasks to see if
2701         // they want to updated their thumbnail state
2702         for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
2703             if (mHasVisibleTaskData.valueAt(i)) {
2704                 TaskView taskView = getTaskViewByTaskId(mHasVisibleTaskData.keyAt(i));
2705                 if (taskView != null) {
2706                     // Poke the view again, which will trigger it to load high res if the state
2707                     // is enabled
2708                     taskView.onTaskListVisibilityChanged(true /* visible */);
2709                 }
2710             }
2711         }
2712     }
2713 
startHome()2714     public void startHome() {
2715         startHome(mContainer.isStarted());
2716     }
2717 
startHome(boolean animated)2718     public void startHome(boolean animated) {
2719         if (!canStartHomeSafely()) return;
2720         handleStartHome(animated);
2721     }
2722 
handleStartHome(boolean animated)2723     protected abstract void handleStartHome(boolean animated);
2724 
2725     /** Returns whether user can start home based on state in {@link OverviewCommandHelper}. */
canStartHomeSafely()2726     protected abstract boolean canStartHomeSafely();
2727 
2728     /** Returns the state manager used in RecentsView **/
2729     public abstract StateManager<STATE_TYPE,
getStateManager()2730             ? extends StatefulContainer<STATE_TYPE>> getStateManager();
2731 
reset()2732     public void reset() {
2733         setCurrentTask(-1);
2734         mCurrentPageScrollDiff = 0;
2735         mIgnoreResetTaskId = -1;
2736         mTaskListChangeId = -1;
2737         setFocusedTaskViewId(INVALID_TASK_ID);
2738         mAnyTaskHasBeenDismissed = false;
2739 
2740         if (enableRefactorTaskThumbnail()) {
2741             // TODO(b/353917593): RecentsView is never destroyed, so its dependencies need to
2742             //  be cleaned up during the reset, but re-created when RecentsView is "resumed".
2743             // RecentsDependencies.Companion.destroy();
2744         }
2745 
2746         Log.d(TAG, "reset - mEnableDrawingLiveTile: " + mEnableDrawingLiveTile
2747                 + ", mRecentsAnimationController: " + mRecentsAnimationController);
2748         if (mEnableDrawingLiveTile) {
2749             if (mRecentsAnimationController != null) {
2750                 // We owns mRecentsAnimationController, finish it now to clean up.
2751                 finishRecentsAnimation(true /* toRecents */, null);
2752             } else {
2753                 // Only clean up target set if we no longer owns mRecentsAnimationController.
2754                 runActionOnRemoteHandles(remoteTargetHandle ->
2755                         remoteTargetHandle.getTransformParams().setTargetSet(null));
2756             }
2757             setEnableDrawingLiveTile(false);
2758         }
2759         mBlurUtils.setDrawLiveTileBelowRecents(false);
2760         // These are relatively expensive and don't need to be done this frame (RecentsView isn't
2761         // visible anyway), so defer by a frame to get off the critical path, e.g. app to home.
2762         post(this::onReset);
2763     }
2764 
onReset()2765     private void onReset() {
2766         unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
2767         setCurrentPage(0);
2768         LayoutUtils.setViewEnabled(mActionsView, true);
2769         if (mOrientationState.setGestureActive(false)) {
2770             updateOrientationHandler(/* forceRecreateDragLayerControllers = */ false);
2771         }
2772         if (enableRefactorTaskThumbnail()) {
2773             mRecentsViewModel.onReset();
2774         }
2775     }
2776 
getRunningTaskViewId()2777     public int getRunningTaskViewId() {
2778         return mRunningTaskViewId;
2779     }
2780 
getTaskIdsForRunningTaskView()2781     protected int[] getTaskIdsForRunningTaskView() {
2782         return getTaskIdsForTaskViewId(mRunningTaskViewId);
2783     }
2784 
getTaskIdsForTaskViewId(int taskViewId)2785     private int[] getTaskIdsForTaskViewId(int taskViewId) {
2786         // For now 2 distinct task IDs is max for split screen
2787         TaskView runningTaskView = getTaskViewFromTaskViewId(taskViewId);
2788         if (runningTaskView == null) {
2789             return new int[0];
2790         }
2791 
2792         return runningTaskView.getTaskIds();
2793     }
2794 
getRunningTaskView()2795     public @Nullable TaskView getRunningTaskView() {
2796         return getTaskViewFromTaskViewId(mRunningTaskViewId);
2797     }
2798 
getFocusedTaskView()2799     public @Nullable TaskView getFocusedTaskView() {
2800         return getTaskViewFromTaskViewId(mFocusedTaskViewId);
2801     }
2802 
2803     @Nullable
getTaskViewFromTaskViewId(int taskViewId)2804     TaskView getTaskViewFromTaskViewId(int taskViewId) {
2805         if (taskViewId == -1) {
2806             return null;
2807         }
2808 
2809         for (TaskView taskView : getTaskViews()) {
2810             if (taskView.getTaskViewId() == taskViewId) {
2811                 return taskView;
2812             }
2813         }
2814         return null;
2815     }
2816 
getRunningTaskIndex()2817     public int getRunningTaskIndex() {
2818         TaskView taskView = getRunningTaskView();
2819         return taskView == null ? -1 : indexOfChild(taskView);
2820     }
2821 
getHomeTaskView()2822     protected @Nullable TaskView getHomeTaskView() {
2823         return null;
2824     }
2825 
2826     /**
2827      * Handle the edge case where Recents could increment task count very high over long
2828      * period of device usage. Probably will never happen, but meh.
2829      */
getTaskViewFromPool(TaskViewType type)2830     protected TaskView getTaskViewFromPool(TaskViewType type) {
2831         TaskView taskView;
2832         switch (type) {
2833             case GROUPED:
2834                 taskView = mGroupedTaskViewPool.getView();
2835                 break;
2836             case DESKTOP:
2837                 taskView = mDesktopTaskViewPool.getView();
2838                 break;
2839             case SINGLE:
2840             default:
2841                 taskView = mTaskViewPool.getView();
2842         }
2843         taskView.setTaskViewId(mTaskViewIdCount);
2844         if (mTaskViewIdCount == Integer.MAX_VALUE) {
2845             mTaskViewIdCount = 0;
2846         } else {
2847             mTaskViewIdCount++;
2848         }
2849 
2850         return taskView;
2851     }
2852 
2853     /**
2854      * Get the index of the task view whose id matches {@param taskId}.
2855      *
2856      * @return -1 if there is no task view for the task id, else the index of the task view.
2857      */
getTaskIndexForId(int taskId)2858     public int getTaskIndexForId(int taskId) {
2859         TaskView tv = getTaskViewByTaskId(taskId);
2860         return tv == null ? -1 : indexOfChild(tv);
2861     }
2862 
2863     /**
2864      * Reloads the view if anything in recents changed.
2865      */
reloadIfNeeded()2866     public void reloadIfNeeded() {
2867         if (!mModel.isTaskListValid(mTaskListChangeId)) {
2868             mTaskListChangeId = mModel.getTasks(this::applyLoadPlan, RecentsFilterState
2869                     .getFilter(mFilterState.getPackageNameToFilter()));
2870             Log.d(TAG, "reloadIfNeeded - getTasks: " + mTaskListChangeId);
2871             if (enableRefactorTaskThumbnail()) {
2872                 mRecentsViewModel.refreshAllTaskData();
2873             }
2874         } else {
2875             Log.d(TAG, "reloadIfNeeded - task list still valid: " + mTaskListChangeId);
2876         }
2877     }
2878 
2879     /**
2880      * Called when a gesture from an app is starting.
2881      */
2882     // TODO: b/401582344 - Implement a way to exclude the `DesktopWallpaperActivity` from being
2883     //  considered in Overview.
onGestureAnimationStart(GroupedTaskInfo groupedTaskInfo)2884     public void onGestureAnimationStart(GroupedTaskInfo groupedTaskInfo) {
2885         Log.d(TAG, "onGestureAnimationStart - groupedTaskInfo: " + groupedTaskInfo);
2886         mActiveGestureGroupedTaskInfo = groupedTaskInfo;
2887 
2888         // This needs to be called before the other states are set since it can create the task view
2889         if (mOrientationState.setGestureActive(true)) {
2890             reapplyActiveRotation();
2891             // Force update to ensure the initial task size is computed even if the orientation has
2892             // not changed.
2893             updateSizeAndPadding();
2894         }
2895 
2896         showCurrentTask(groupedTaskInfo, "onGestureAnimationStart");
2897         setEnableFreeScroll(false);
2898         setEnableDrawingLiveTile(false);
2899         setRunningTaskHidden(true);
2900         setTaskIconVisible(false);
2901     }
2902 
2903     /**
2904      * Returns whether the running task's attach alpha should be updated during the attach animation
2905      */
shouldUpdateRunningTaskAlpha()2906     public boolean shouldUpdateRunningTaskAlpha() {
2907         return enableDesktopTaskAlphaAnimation() && getRunningTaskView() instanceof DesktopTaskView;
2908     }
2909 
isGestureActive()2910     private boolean isGestureActive() {
2911         return mActiveGestureGroupedTaskInfo != null;
2912     }
2913 
2914     /**
2915      * Called only when a swipe-up gesture from an app has completed. Only called after
2916      * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}.
2917      */
onSwipeUpAnimationSuccess()2918     public void onSwipeUpAnimationSuccess() {
2919         startIconFadeInOnGestureComplete();
2920         setSwipeDownShouldLaunchApp(true);
2921     }
2922 
animateRecentsRotationInPlace(int newRotation)2923     private void animateRecentsRotationInPlace(int newRotation) {
2924         if (mOrientationState.isRecentsActivityRotationAllowed()) {
2925             // Let system take care of the rotation
2926             return;
2927         }
2928 
2929         if (mRunningTaskShowScreenshot) {
2930             animateRotation(newRotation);
2931         } else {
2932             // Animate the rotation and stops running task
2933             switchToScreenshot(() -> {
2934                 animateRotation(newRotation);
2935                 finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
2936                         null /* onFinishComplete */);
2937             });
2938         }
2939     }
2940 
animateRotation(int newRotation)2941     private void animateRotation(int newRotation) {
2942         AbstractFloatingView.closeAllOpenViewsExcept(mContainer, false, TYPE_REBIND_SAFE);
2943         AnimatorSet pa = setRecentsChangedOrientation(true);
2944         pa.addListener(AnimatorListeners.forSuccessCallback(() -> {
2945             setLayoutRotation(newRotation, mOrientationState.getDisplayRotation());
2946             mContainer.getDragLayer().recreateControllers();
2947             setRecentsChangedOrientation(false).start();
2948         }));
2949         pa.start();
2950     }
2951 
setRecentsChangedOrientation(boolean fadeOut)2952     public AnimatorSet setRecentsChangedOrientation(boolean fadeOut) {
2953         AnimatorSet as = new AnimatorSet();
2954         as.play(ObjectAnimator.ofFloat(this, View.ALPHA, fadeOut ? 0 : 1));
2955         return as;
2956     }
2957 
2958     /**
2959      * Called when a gesture from an app has finished, and an end target has been determined.
2960      */
onPrepareGestureEndAnimation( @ullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget, RemoteTargetHandle[] remoteTargetHandles)2961     public void onPrepareGestureEndAnimation(
2962             @Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
2963             RemoteTargetHandle[] remoteTargetHandles) {
2964         Log.d(TAG, "onPrepareGestureEndAnimation - endTarget: " + endTarget);
2965         mCurrentGestureEndTarget = endTarget;
2966         boolean isOverviewEndTarget = endTarget == GestureState.GestureEndTarget.RECENTS;
2967         if (isOverviewEndTarget) {
2968             updateGridProperties();
2969         }
2970 
2971         BaseState<?> endState = mSizeStrategy.stateFromGestureEndTarget(endTarget);
2972         // Starting the desk exploded animation when the gesture from an app is released.
2973         if (enableDesktopExplodedView()) {
2974             if (animatorSet == null) {
2975                 mUtils.setDeskExplodeProgress(endState.showExplodedDesktopView() ? 1f : 0f);
2976             } else {
2977                 animatorSet.play(
2978                         ObjectAnimator.ofFloat(this, DESK_EXPLODE_PROGRESS,
2979                                 endState.showExplodedDesktopView() ? 1f : 0f));
2980             }
2981 
2982             for (TaskView taskView : getTaskViews()) {
2983                 if (taskView instanceof DesktopTaskView desktopTaskView) {
2984                     desktopTaskView.setRemoteTargetHandles(remoteTargetHandles);
2985                 }
2986             }
2987         }
2988 
2989         if (endState.displayOverviewTasksAsGrid(mContainer.getDeviceProfile())) {
2990             TaskView runningTaskView = getRunningTaskView();
2991             float runningTaskGridTranslationX = 0;
2992             float runningTaskGridTranslationY = 0;
2993             if (runningTaskView != null) {
2994                 // Apply the grid translation to running task unless it's being snapped to
2995                 // and removes the current translation applied to the running task.
2996                 runningTaskGridTranslationX = runningTaskView.getGridTranslationX()
2997                         - runningTaskView.getNonGridTranslationX();
2998                 runningTaskGridTranslationY = runningTaskView.getGridTranslationY();
2999             }
3000             for (RemoteTargetHandle remoteTargetHandle : remoteTargetHandles) {
3001                 TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator();
3002                 if (animatorSet == null) {
3003                     setGridProgress(1);
3004                     if (enableGridOnlyOverview()) {
3005                         tvs.taskGridTranslationX.value = runningTaskGridTranslationX;
3006                         tvs.taskGridTranslationY.value = runningTaskGridTranslationY;
3007                     } else {
3008                         tvs.taskPrimaryTranslation.value = runningTaskGridTranslationX;
3009                         tvs.taskSecondaryTranslation.value = runningTaskGridTranslationY;
3010                     }
3011                 } else {
3012                     animatorSet.play(ObjectAnimator.ofFloat(this, RECENTS_GRID_PROGRESS, 1));
3013                     if (enableGridOnlyOverview()) {
3014                         animatorSet.play(tvs.carouselScale.animateToValue(1));
3015                         animatorSet.play(tvs.taskGridTranslationX.animateToValue(
3016                                 runningTaskGridTranslationX));
3017                         animatorSet.play(tvs.taskGridTranslationY.animateToValue(
3018                                 runningTaskGridTranslationY));
3019                     } else {
3020                         animatorSet.play(tvs.taskPrimaryTranslation.animateToValue(
3021                                 runningTaskGridTranslationX));
3022                         animatorSet.play(tvs.taskSecondaryTranslation.animateToValue(
3023                                 runningTaskGridTranslationY));
3024                     }
3025                 }
3026             }
3027         }
3028         int splashAlpha = endState.showTaskThumbnailSplash() ? 1 : 0;
3029         if (animatorSet == null) {
3030             setTaskThumbnailSplashAlpha(splashAlpha);
3031         } else {
3032             animatorSet.play(
3033                     ObjectAnimator.ofFloat(this, TASK_THUMBNAIL_SPLASH_ALPHA, splashAlpha));
3034         }
3035         if (enableLargeDesktopWindowingTile()) {
3036             if (animatorSet != null) {
3037                 animatorSet.play(
3038                         ObjectAnimator.ofFloat(this, DESKTOP_CAROUSEL_DETACH_PROGRESS, 0f));
3039             } else {
3040                 DESKTOP_CAROUSEL_DETACH_PROGRESS.set(this, 0f);
3041             }
3042         }
3043     }
3044 
3045     /**
3046      * Called when a gesture from an app has finished, and the animation to the target has ended.
3047      */
onGestureAnimationEnd()3048     public void onGestureAnimationEnd() {
3049         mActiveGestureGroupedTaskInfo = null;
3050         if (mOrientationState.setGestureActive(false)) {
3051             updateOrientationHandler(/* forceRecreateDragLayerControllers = */ false);
3052         }
3053 
3054         setEnableFreeScroll(true);
3055         setEnableDrawingLiveTile(mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS);
3056         Log.d(TAG, "onGestureAnimationEnd - mEnableDrawingLiveTile: " + mEnableDrawingLiveTile);
3057         setRunningTaskHidden(false);
3058         startIconFadeInOnGestureComplete();
3059         animateActionsViewIn();
3060 
3061         if (mEnableDrawingLiveTile) {
3062             if (enableDesktopExplodedView()) {
3063                 for (TaskView taskView : getTaskViews()) {
3064                     if (taskView instanceof DesktopTaskView desktopTaskView) {
3065                         desktopTaskView.setRemoteTargetHandles(mRemoteTargetHandles);
3066                     }
3067                 }
3068             }
3069             TaskView runningTaskView = getRunningTaskView();
3070             if (showAsGrid() && enableGridOnlyOverview() && runningTaskView != null) {
3071                 runActionOnRemoteHandles(remoteTargetHandle -> {
3072                     TaskViewSimulator taskViewSimulator = remoteTargetHandle.getTaskViewSimulator();
3073                     // After settling in Overview, recentsScroll will be used to adjust horizontally
3074                     // location and taskGridTranslationX doesn't needs to be applied.
3075                     taskViewSimulator.taskGridTranslationX.value = 0;
3076                     taskViewSimulator.taskGridTranslationY.value =
3077                             runningTaskView.getGridTranslationY();
3078                 });
3079             }
3080         }
3081 
3082         mCurrentGestureEndTarget = null;
3083     }
3084 
3085     /**
3086      * Returns true if we should add a stub taskView for the running task id
3087      */
shouldAddStubTaskView(GroupedTaskInfo groupedTaskInfo)3088     protected boolean shouldAddStubTaskView(GroupedTaskInfo groupedTaskInfo) {
3089         int[] runningTaskIds;
3090         if (groupedTaskInfo != null) {
3091             runningTaskIds = groupedTaskInfo.getTaskInfoList().stream().mapToInt(
3092                     taskInfo -> taskInfo.taskId).toArray();
3093         } else {
3094             runningTaskIds = new int[0];
3095         }
3096         TaskView matchingTaskView = null;
3097         if (groupedTaskInfo != null && groupedTaskInfo.isBaseType(GroupedTaskInfo.TYPE_DESK)
3098                 && runningTaskIds.length == 1) {
3099             // TODO(b/342635213): Unsure if it's expected, desktop runningTasks only have a single
3100             // taskId, therefore we match any DesktopTaskView that contains the runningTaskId.
3101             TaskView taskview = getTaskViewByTaskId(runningTaskIds[0]);
3102             if (taskview instanceof DesktopTaskView) {
3103                 matchingTaskView = taskview;
3104             }
3105         } else {
3106             matchingTaskView = getTaskViewByTaskIds(runningTaskIds);
3107         }
3108         return matchingTaskView == null;
3109     }
3110 
3111     /**
3112      * Creates a task view (if necessary) to represent the tasks with the {@param groupedTaskInfo}.
3113      *
3114      * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
3115      * is called.  Also scrolls the view to this task.
3116      */
showCurrentTask(GroupedTaskInfo groupedTaskInfo, String caller)3117     private void showCurrentTask(GroupedTaskInfo groupedTaskInfo, String caller) {
3118         Log.d(TAG, "showCurrentTask(" + caller + ") - groupedTaskInfo: " + groupedTaskInfo);
3119         if (groupedTaskInfo == null) {
3120             return;
3121         }
3122 
3123         int runningTaskViewId = -1;
3124         if (shouldAddStubTaskView(groupedTaskInfo)) {
3125             boolean wasEmpty = getChildCount() == 0;
3126             // Add an empty view for now until the task plan is loaded and applied
3127             final TaskView taskView;
3128             if (groupedTaskInfo.isBaseType(GroupedTaskInfo.TYPE_DESK)) {
3129                 taskView = mUtils.createDesktopTaskViewForActiveDesk(groupedTaskInfo);
3130             } else if (groupedTaskInfo.isBaseType(GroupedTaskInfo.TYPE_SPLIT)) {
3131                 taskView = getTaskViewFromPool(TaskViewType.GROUPED);
3132                 // When we create a placeholder task view mSplitBoundsConfig will be null, but with
3133                 // the actual app running we won't need to show the thumbnail until all the tasks
3134                 // load later anyways
3135                 ((GroupedTaskView) taskView).bind(Task.from(groupedTaskInfo.getTaskInfo1()),
3136                         Task.from(groupedTaskInfo.getTaskInfo2()), mOrientationState,
3137                         mTaskOverlayFactory, mSplitBoundsConfig);
3138             } else {
3139                 taskView = getTaskViewFromPool(TaskViewType.SINGLE);
3140                 taskView.bind(Task.from(groupedTaskInfo.getTaskInfo1()), mOrientationState,
3141                         mTaskOverlayFactory);
3142             }
3143             if (mAddDesktopButton != null && wasEmpty) {
3144                 addView(mAddDesktopButton);
3145             }
3146             addView(taskView, mUtils.getRunningTaskExpectedIndex(taskView));
3147             runningTaskViewId = taskView.getTaskViewId();
3148             if (wasEmpty) {
3149                 addView(mClearAllButton);
3150             }
3151 
3152             // Measure and layout immediately so that the scroll values is updated instantly
3153             // as the user might be quick-switching
3154             measure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
3155                     makeMeasureSpec(getMeasuredHeight(), EXACTLY));
3156             layout(getLeft(), getTop(), getRight(), getBottom());
3157         } else {
3158             var runningTaskView = getTaskViewByTaskId(groupedTaskInfo.getTaskInfo1().taskId);
3159             if (runningTaskView != null) {
3160                 runningTaskViewId = runningTaskView.getTaskViewId();
3161             }
3162         }
3163 
3164         boolean runningTaskTileHidden = mRunningTaskTileHidden;
3165         setCurrentTask(runningTaskViewId);
3166 
3167         int focusedTaskViewId;
3168         if (enableGridOnlyOverview()) {
3169             focusedTaskViewId = INVALID_TASK_ID;
3170         } else if (enableLargeDesktopWindowingTile()
3171                 && getRunningTaskView() instanceof DesktopTaskView) {
3172             TaskView focusedTaskView = mUtils.getFirstNonDesktopTaskView();
3173             focusedTaskViewId =
3174                     focusedTaskView != null ? focusedTaskView.getTaskViewId() : INVALID_TASK_ID;
3175         } else {
3176             focusedTaskViewId = runningTaskViewId;
3177         }
3178         setFocusedTaskViewId(focusedTaskViewId);
3179 
3180         runOnPageScrollsInitialized(() -> setCurrentPage(getRunningTaskIndex()));
3181         setRunningTaskViewShowScreenshot(false);
3182         setRunningTaskHidden(runningTaskTileHidden);
3183         // Update task size after setting current task.
3184         updateTaskSize();
3185         mUtils.updateChildTaskOrientations();
3186 
3187         // Reload the task list
3188         reloadIfNeeded();
3189     }
3190 
3191     /**
3192      * Sets the running task id, cleaning up the old running task if necessary.
3193      */
setCurrentTask(int runningTaskViewId)3194     public void setCurrentTask(int runningTaskViewId) {
3195         if (mRunningTaskViewId == runningTaskViewId) {
3196             return;
3197         }
3198 
3199         if (mRunningTaskViewId != -1) {
3200             // Reset the state on the old running task view
3201             setTaskIconVisible(true);
3202             setRunningTaskViewShowScreenshot(true);
3203             setRunningTaskHidden(false);
3204         }
3205         setRunningTaskViewId(runningTaskViewId);
3206     }
3207 
setRunningTaskViewId(int runningTaskViewId)3208     private void setRunningTaskViewId(int runningTaskViewId) {
3209         mRunningTaskViewId = runningTaskViewId;
3210 
3211         if (enableRefactorTaskThumbnail()) {
3212             TaskView runningTaskView = getTaskViewFromTaskViewId(runningTaskViewId);
3213             mRecentsViewModel.updateRunningTask(
3214                     runningTaskView != null ? runningTaskView.getTaskIdSet()
3215                             : Collections.emptySet());
3216         }
3217     }
3218 
setFocusedTaskViewId(int viewId)3219     private void setFocusedTaskViewId(int viewId) {
3220         mFocusedTaskViewId = viewId;
3221     }
3222 
getTaskViewIdFromTaskId(int taskId)3223     private int getTaskViewIdFromTaskId(int taskId) {
3224         TaskView taskView = getTaskViewByTaskId(taskId);
3225         return taskView != null ? taskView.getTaskViewId() : -1;
3226     }
3227 
3228     /**
3229      * Hides the tile associated with {@link #mRunningTaskViewId}
3230      */
setRunningTaskHidden(boolean isHidden)3231     public void setRunningTaskHidden(boolean isHidden) {
3232         mRunningTaskTileHidden = isHidden;
3233         // mRunningTaskAttachAlpha can be changed by RUNNING_TASK_ATTACH_ALPHA animation without
3234         // changing mRunningTaskTileHidden.
3235         mRunningTaskAttachAlpha = isHidden ? 0f : 1f;
3236         TaskView runningTask = getRunningTaskView();
3237         if (runningTask == null) {
3238             return;
3239         }
3240         applyAttachAlpha();
3241         if (!isHidden) {
3242             AccessibilityManagerCompat.sendCustomAccessibilityEvent(
3243                     runningTask, AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
3244         }
3245     }
3246 
applyAttachAlpha()3247     private void applyAttachAlpha() {
3248         // Only hide non running task carousel when it's fully off screen, otherwise it needs to
3249         // be visible to move to on screen.
3250         mUtils.applyAttachAlpha(
3251                 /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress == 1f);
3252     }
3253 
setRunningTaskViewShowScreenshot(boolean showScreenshot)3254     private void setRunningTaskViewShowScreenshot(boolean showScreenshot) {
3255         setRunningTaskViewShowScreenshot(showScreenshot, /*updatedThumbnails=*/null);
3256     }
3257 
setRunningTaskViewShowScreenshot(boolean showScreenshot, @Nullable Map<Integer, ThumbnailData> updatedThumbnails)3258     private void setRunningTaskViewShowScreenshot(boolean showScreenshot,
3259             @Nullable Map<Integer, ThumbnailData> updatedThumbnails) {
3260         mRunningTaskShowScreenshot = showScreenshot;
3261         TaskView runningTaskView = getRunningTaskView();
3262         if (runningTaskView != null) {
3263             runningTaskView.setShouldShowScreenshot(mRunningTaskShowScreenshot, updatedThumbnails);
3264         }
3265         if (enableRefactorTaskThumbnail()) {
3266             mRecentsViewModel.setRunningTaskShowScreenshot(showScreenshot);
3267         }
3268     }
3269 
3270     /**
3271      * Updates icon visibility when going in or out of overview.
3272      */
setTaskIconVisible(boolean isVisible)3273     public void setTaskIconVisible(boolean isVisible) {
3274         if (mTaskIconVisible != isVisible) {
3275             mTaskIconVisible = isVisible;
3276             for (TaskView taskView : getTaskViews()) {
3277                 taskView.setIconVisibleForGesture(mTaskIconVisible);
3278             }
3279         }
3280     }
3281 
animateActionsViewIn()3282     private void animateActionsViewIn() {
3283         if (!showAsGrid() || isFocusedTaskInExpectedScrollPosition()) {
3284             animateActionsViewAlpha(1, TaskView.FADE_IN_ICON_DURATION);
3285         }
3286     }
3287 
3288     /**
3289      * Updates icon visibility when gesture is settled.
3290      */
startIconFadeInOnGestureComplete()3291     public void startIconFadeInOnGestureComplete() {
3292         mTaskIconVisible = true;
3293         for (TaskView taskView : getTaskViews()) {
3294             taskView.startIconFadeInOnGestureComplete();
3295         }
3296     }
3297 
3298     /**
3299      * Updates TaskView and ClearAllButtion scaling and translation required to turn into grid
3300      * layout.
3301      *
3302      * Skips rebalance.
3303      */
updateGridProperties()3304     private void updateGridProperties() {
3305         updateGridProperties(null);
3306     }
3307 
3308     /**
3309      * Updates TaskView and ClearAllButton scaling and translation required to turn into grid
3310      * layout.
3311      *
3312      * This method only calculates the potential position and depends on {@link #setGridProgress} to
3313      * apply the actual scaling and translation.
3314      *
3315      * @param lastVisibleTaskViewDuringDismiss which TaskView to start rebalancing from. Use
3316      *                                         `null` to skip rebalance.
3317      */
updateGridProperties(TaskView lastVisibleTaskViewDuringDismiss)3318     private void updateGridProperties(TaskView lastVisibleTaskViewDuringDismiss) {
3319         if (!hasTaskViews()) {
3320             return;
3321         }
3322 
3323         DeviceProfile deviceProfile = mContainer.getDeviceProfile();
3324         int taskTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
3325 
3326         int topRowWidth = 0;
3327         int bottomRowWidth = 0;
3328         int largeTileRowWidth = 0;
3329         float topAccumulatedTranslationX = 0;
3330         float bottomAccumulatedTranslationX = 0;
3331 
3332         // Horizontal grid translation for each task.
3333         Map<TaskView, Float> gridTranslations = new HashMap<>();
3334 
3335         TaskView lastLargeTaskView = mUtils.getLastLargeTaskView();
3336         int focusedTaskViewShift = 0;
3337         int largeTaskWidthAndSpacing = 0;
3338         int snappedTaskRowWidth = 0;
3339         int expectedCurrentTaskRowWidth = 0;
3340         int snappedPage = isKeyboardTaskFocusPending() ? mKeyboardTaskFocusIndex : getNextPage();
3341         TaskView snappedTaskView = getTaskViewAt(snappedPage);
3342         TaskView homeTaskView = getHomeTaskView();
3343         TaskView expectedCurrentTaskView = mUtils.getExpectedCurrentTask(getRunningTaskView(),
3344                 getFocusedTaskView());
3345         TaskView nextFocusedTaskView = null;
3346 
3347         // Don't clear the top row, if the user has dismissed a task, to maintain the task order.
3348         if (!mAnyTaskHasBeenDismissed) {
3349             mTopRowIdSet.clear();
3350         }
3351 
3352         // Consecutive task views in the top row or bottom row, which means another one set will
3353         // be cleared up while starting to add TaskViews to one of them. Also means only one of
3354         // them can be non-empty at most.
3355         Set<TaskView> lastTopTaskViews = new HashSet<>();
3356         Set<TaskView> lastBottomTaskViews = new HashSet<>();
3357 
3358         int largeTasksCount = 0;
3359         // True if the last large TaskView has been visited during the TaskViews iteration.
3360         boolean encounteredLastLargeTaskView = false;
3361         // True if the highest index visible TaskView has been visited during the TaskViews
3362         // iteration.
3363         boolean encounteredLastVisibleTaskView = false;
3364         for (TaskView taskView : getTaskViews()) {
3365             if (taskView == lastLargeTaskView) {
3366                 encounteredLastLargeTaskView = true;
3367             }
3368             if (taskView == lastVisibleTaskViewDuringDismiss) {
3369                 encounteredLastVisibleTaskView = true;
3370             }
3371             float gridTranslation = 0f;
3372             int taskWidthAndSpacing = taskView.getLayoutParams().width + mPageSpacing;
3373             // Evenly distribute tasks between rows unless rearranging due to task dismissal, in
3374             // which case keep tasks in their respective rows. For the running task, don't join
3375             // the grid.
3376             if (taskView.isLargeTile()) {
3377                 largeTasksCount++;
3378                 // DesktopTaskView`s are hidden during split select state, so we shouldn't count
3379                 // them when calculating row width.
3380                 if (!(taskView instanceof DesktopTaskView && isSplitSelectionActive())) {
3381                     topRowWidth += taskWidthAndSpacing;
3382                     bottomRowWidth += taskWidthAndSpacing;
3383                     largeTileRowWidth += taskWidthAndSpacing;
3384                 }
3385                 gridTranslation += focusedTaskViewShift;
3386                 gridTranslation += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
3387 
3388                 // Center view vertically in case it's from different orientation.
3389                 taskView.setGridTranslationY((mLastComputedTaskSize.height() + taskTopMargin
3390                         - taskView.getLayoutParams().height) / 2f);
3391 
3392                 largeTaskWidthAndSpacing = taskWidthAndSpacing;
3393 
3394                 if (taskView == snappedTaskView) {
3395                     snappedTaskRowWidth = largeTileRowWidth;
3396                 }
3397                 if (taskView == expectedCurrentTaskView) {
3398                     expectedCurrentTaskRowWidth = largeTileRowWidth;
3399                 }
3400             } else {
3401                 if (encounteredLastLargeTaskView) {
3402                     // For tasks after the last large task, shift by large task's width and spacing.
3403                     gridTranslation +=
3404                             mIsRtl ? largeTaskWidthAndSpacing : -largeTaskWidthAndSpacing;
3405                 } else {
3406                     // For TaskViews before the new focused TaskView, accumulate the width and
3407                     // spacing to calculate the distance the new focused TaskView needs to shift.
3408                     // This could happen for example after multiple times of dismissing the
3409                     // focused TaskView, the triggered rebalance might set a non-first TaskView
3410                     // inside `mChildren` as the new focused TaskView.
3411                     focusedTaskViewShift += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
3412                 }
3413                 int taskViewId = taskView.getTaskViewId();
3414 
3415                 boolean isTopRow;
3416                 if (mAnyTaskHasBeenDismissed) {
3417                     // Rebalance the grid starting after a certain index.
3418                     if (encounteredLastVisibleTaskView) {
3419                         mTopRowIdSet.remove(taskViewId);
3420                         isTopRow = topRowWidth <= bottomRowWidth;
3421                     } else {
3422                         isTopRow = mTopRowIdSet.contains(taskViewId);
3423                     }
3424                 } else {
3425                     isTopRow = topRowWidth <= bottomRowWidth;
3426                 }
3427 
3428                 if (isTopRow) {
3429                     if (homeTaskView != null && nextFocusedTaskView == null) {
3430                         // TaskView will be focused when swipe up, don't count towards row width.
3431                         nextFocusedTaskView = taskView;
3432                     } else {
3433                         topRowWidth += taskWidthAndSpacing;
3434                     }
3435                     mTopRowIdSet.add(taskViewId);
3436                     taskView.setGridTranslationY(mTaskGridVerticalDiff);
3437 
3438                     // Move horizontally into empty space.
3439                     float widthOffset = 0;
3440                     for (TaskView bottomTaskView : lastBottomTaskViews) {
3441                         widthOffset += bottomTaskView.getLayoutParams().width + mPageSpacing;
3442                     }
3443 
3444                     float currentTaskTranslationX = mIsRtl ? widthOffset : -widthOffset;
3445                     gridTranslation += topAccumulatedTranslationX + currentTaskTranslationX;
3446                     topAccumulatedTranslationX += currentTaskTranslationX;
3447                     lastTopTaskViews.add(taskView);
3448                     lastBottomTaskViews.clear();
3449                 } else {
3450                     bottomRowWidth += taskWidthAndSpacing;
3451 
3452                     // Move into bottom row.
3453                     taskView.setGridTranslationY(mTopBottomRowHeightDiff + mTaskGridVerticalDiff);
3454 
3455                     // Move horizontally into empty space.
3456                     float widthOffset = 0;
3457                     for (TaskView topTaskView : lastTopTaskViews) {
3458                         widthOffset += topTaskView.getLayoutParams().width + mPageSpacing;
3459                     }
3460 
3461                     float currentTaskTranslationX = mIsRtl ? widthOffset : -widthOffset;
3462                     gridTranslation += bottomAccumulatedTranslationX + currentTaskTranslationX;
3463                     bottomAccumulatedTranslationX += currentTaskTranslationX;
3464                     lastBottomTaskViews.add(taskView);
3465                     lastTopTaskViews.clear();
3466                 }
3467                 int taskViewRowWidth = isTopRow ? topRowWidth : bottomRowWidth;
3468                 if (taskView == snappedTaskView) {
3469                     snappedTaskRowWidth = taskViewRowWidth;
3470                 }
3471                 if (taskView == expectedCurrentTaskView) {
3472                     expectedCurrentTaskRowWidth = taskViewRowWidth;
3473                 }
3474             }
3475             gridTranslations.put(taskView, gridTranslation);
3476         }
3477 
3478         // We need to maintain snapped task's page scroll invariant between quick switch and
3479         // overview, so we sure snapped task's grid translation is 0, and add a non-fullscreen
3480         // translationX that is the same as snapped task's full scroll adjustment.
3481         float snappedTaskNonGridScrollAdjustment = 0;
3482         float snappedTaskGridTranslationX = 0;
3483         if (snappedTaskView != null) {
3484             snappedTaskNonGridScrollAdjustment = snappedTaskView.getScrollAdjustment(
3485                     /*gridEnabled=*/false);
3486             snappedTaskGridTranslationX = gridTranslations.getOrDefault(snappedTaskView, 0f);
3487         }
3488 
3489         // Use the accumulated translation of the row containing the last task.
3490         float clearAllAccumulatedTranslation = !lastTopTaskViews.isEmpty()
3491                 ? topAccumulatedTranslationX : bottomAccumulatedTranslationX;
3492 
3493         // If the last task is on the shorter row, ClearAllButton will embed into the shorter row
3494         // which is not what we want. Compensate the width difference of the 2 rows in that case.
3495         float shorterRowCompensation = 0;
3496         if (topRowWidth <= bottomRowWidth) {
3497             if (!lastTopTaskViews.isEmpty()) {
3498                 shorterRowCompensation = bottomRowWidth - topRowWidth;
3499             }
3500         } else {
3501             if (!lastBottomTaskViews.isEmpty()) {
3502                 shorterRowCompensation = topRowWidth - bottomRowWidth;
3503             }
3504         }
3505         float clearAllShorterRowCompensation =
3506                 mIsRtl ? -shorterRowCompensation : shorterRowCompensation;
3507 
3508         // If the total width is shorter than one grid's width, move ClearAllButton further away
3509         // accordingly. Update longRowWidth if ClearAllButton has been moved.
3510         float clearAllShortTotalWidthTranslation = 0;
3511         int longRowWidth = Math.max(topRowWidth, bottomRowWidth);
3512 
3513         // If first task is not in the expected position (mLastComputedTaskSize) and being too close
3514         // to ClearAllButton, then apply extra translation to ClearAllButton.
3515         int rowWidthAfterExpectedCurrentTask = longRowWidth - expectedCurrentTaskRowWidth;
3516         int expectedCurrentTaskWidthAndSpacing =
3517                 (expectedCurrentTaskView != null
3518                         ? expectedCurrentTaskView.getLayoutParams().width
3519                         : 0
3520                 ) + mPageSpacing;
3521         int firstTaskStart = mLastComputedGridSize.left + rowWidthAfterExpectedCurrentTask
3522                 + expectedCurrentTaskWidthAndSpacing;
3523         int expectedFirstTaskStart = mLastComputedTaskSize.right;
3524         if (firstTaskStart < expectedFirstTaskStart) {
3525             mClearAllShortTotalWidthTranslation = expectedFirstTaskStart - firstTaskStart;
3526             clearAllShortTotalWidthTranslation = mIsRtl
3527                     ? -mClearAllShortTotalWidthTranslation : mClearAllShortTotalWidthTranslation;
3528             if (snappedTaskRowWidth == longRowWidth) {
3529                 // Updated snappedTaskRowWidth as well if it's same as longRowWidth.
3530                 snappedTaskRowWidth += mClearAllShortTotalWidthTranslation;
3531             }
3532             longRowWidth += mClearAllShortTotalWidthTranslation;
3533         } else {
3534             mClearAllShortTotalWidthTranslation = 0;
3535         }
3536 
3537         float clearAllTotalTranslationX =
3538                 clearAllAccumulatedTranslation + clearAllShorterRowCompensation
3539                         + clearAllShortTotalWidthTranslation + snappedTaskNonGridScrollAdjustment;
3540         if (largeTasksCount > 0) {
3541             // Shift by focused task's width and spacing if a task is focused.
3542             clearAllTotalTranslationX +=
3543                     mIsRtl ? largeTaskWidthAndSpacing : -largeTaskWidthAndSpacing;
3544         }
3545 
3546         // Make sure there are enough space between snapped page and ClearAllButton, for the case
3547         // of swiping up after quick switch.
3548         if (snappedTaskView != null) {
3549             int distanceFromClearAll = longRowWidth - snappedTaskRowWidth;
3550             // ClearAllButton should be off screen when snapped task is in its snapped position.
3551             int minimumDistance =
3552                     (mIsRtl
3553                             ? mLastComputedTaskSize.left
3554                             : deviceProfile.widthPx - mLastComputedTaskSize.right)
3555                             - deviceProfile.overviewGridSideMargin - mPageSpacing
3556                             + (mTaskWidth - snappedTaskView.getLayoutParams().width)
3557                             - mClearAllShortTotalWidthTranslation;
3558             if (distanceFromClearAll < minimumDistance) {
3559                 int distanceDifference = minimumDistance - distanceFromClearAll;
3560                 snappedTaskGridTranslationX += mIsRtl ? distanceDifference : -distanceDifference;
3561             }
3562         }
3563 
3564         for (TaskView taskView : getTaskViews()) {
3565             taskView.setGridTranslationX(
3566                     gridTranslations.getOrDefault(taskView, 0f) - snappedTaskGridTranslationX
3567                             + snappedTaskNonGridScrollAdjustment);
3568         }
3569 
3570         if (mAddDesktopButton != null) {
3571             TaskView firstTaskView = getFirstTaskView();
3572             float translationX = 0f;
3573             if (firstTaskView != null) {
3574                 translationX += firstTaskView.getGridTranslationX();
3575             }
3576             if (focusedTaskViewShift != 0) {
3577                 // If the focused task is inserted between `firstTaskView` and
3578                 // `mAddDesktopButton`, shift `mAddDesktopButton` to accommodate.
3579                 translationX += largeTaskWidthAndSpacing;
3580             }
3581             mAddDesktopButton.setGridTranslationX(translationX);
3582         }
3583 
3584         mClearAllButton.setGridTranslationPrimary(
3585                 clearAllTotalTranslationX - snappedTaskGridTranslationX);
3586         mClearAllButton.setGridScrollOffset(
3587                 mIsRtl ? mLastComputedTaskSize.left - mLastComputedGridSize.left
3588                         : mLastComputedTaskSize.right - mLastComputedGridSize.right);
3589         setGridProgress(mGridProgress);
3590     }
3591 
isSameGridRow(TaskView taskView1, TaskView taskView2)3592     protected boolean isSameGridRow(TaskView taskView1, TaskView taskView2) {
3593         if (taskView1 == null || taskView2 == null) {
3594             return false;
3595         }
3596         if (taskView1.isLargeTile() || taskView2.isLargeTile()) {
3597             return false;
3598         }
3599         int taskViewId1 = taskView1.getTaskViewId();
3600         int taskViewId2 = taskView2.getTaskViewId();
3601         return (mTopRowIdSet.contains(taskViewId1) && mTopRowIdSet.contains(taskViewId2)) || (
3602                 !mTopRowIdSet.contains(taskViewId1) && !mTopRowIdSet.contains(taskViewId2));
3603     }
3604 
3605     /**
3606      * Moves TaskView and ClearAllButton between carousel and 2 row grid.
3607      *
3608      * @param gridProgress 0 = carousel; 1 = 2 row grid.
3609      */
setGridProgress(float gridProgress)3610     private void setGridProgress(float gridProgress) {
3611         mGridProgress = gridProgress;
3612 
3613         for (TaskView taskView : getTaskViews()) {
3614             taskView.setGridProgress(gridProgress);
3615         }
3616         mClearAllButton.setGridProgress(gridProgress);
3617     }
3618 
setTaskThumbnailSplashAlpha(float taskThumbnailSplashAlpha)3619     private void setTaskThumbnailSplashAlpha(float taskThumbnailSplashAlpha) {
3620         mTaskThumbnailSplashAlpha = taskThumbnailSplashAlpha;
3621         for (TaskView taskView : getTaskViews()) {
3622             taskView.setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha);
3623         }
3624     }
3625 
enableLayoutTransitions()3626     private void enableLayoutTransitions() {
3627         if (mLayoutTransition == null) {
3628             mLayoutTransition = new LayoutTransition();
3629             mLayoutTransition.enableTransitionType(LayoutTransition.APPEARING);
3630             mLayoutTransition.setDuration(ADDITION_TASK_DURATION);
3631             mLayoutTransition.setStartDelay(LayoutTransition.APPEARING, 0);
3632 
3633             mLayoutTransition.addTransitionListener(new TransitionListener() {
3634                 @Override
3635                 public void startTransition(LayoutTransition transition, ViewGroup viewGroup,
3636                         View view, int i) {
3637                 }
3638 
3639                 @Override
3640                 public void endTransition(LayoutTransition transition, ViewGroup viewGroup,
3641                         View view, int i) {
3642                     // When the unpinned task is added, snap to first page and disable transitions
3643                     if (view instanceof TaskView) {
3644                         snapToPage(0);
3645                         setLayoutTransition(null);
3646                     }
3647 
3648                 }
3649             });
3650         }
3651         setLayoutTransition(mLayoutTransition);
3652     }
3653 
setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp)3654     public void setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp) {
3655         mSwipeDownShouldLaunchApp = swipeDownShouldLaunchApp;
3656     }
3657 
shouldSwipeDownLaunchApp()3658     public boolean shouldSwipeDownLaunchApp() {
3659         return mSwipeDownShouldLaunchApp;
3660     }
3661 
setIgnoreResetTask(int taskId)3662     public void setIgnoreResetTask(int taskId) {
3663         mIgnoreResetTaskId = taskId;
3664     }
3665 
clearIgnoreResetTask(int taskId)3666     public void clearIgnoreResetTask(int taskId) {
3667         if (mIgnoreResetTaskId == taskId) {
3668             mIgnoreResetTaskId = -1;
3669         }
3670     }
3671 
addDismissedTaskAnimations(TaskView taskView, long duration, PendingAnimation anim)3672     private void addDismissedTaskAnimations(TaskView taskView, long duration,
3673             PendingAnimation anim) {
3674         // Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's
3675         // alpha is set to 0 so that it can be recycled in the view pool properly
3676         anim.setFloat(taskView, VIEW_ALPHA, 0,
3677                 clampToProgress(isOnGridBottomRow(taskView) ? ACCELERATE : FINAL_FRAME, 0, 0.5f));
3678         FloatProperty<TaskView> secondaryViewTranslate =
3679                 taskView.getSecondaryDismissTranslationProperty();
3680         int secondaryTaskDimension = getPagedOrientationHandler().getSecondaryDimension(taskView);
3681         int verticalFactor = getPagedOrientationHandler().getSecondaryTranslationDirectionFactor();
3682 
3683         ResourceProvider rp = DynamicResource.provider(mContainer);
3684         SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_START)
3685                 .setDampingRatio(rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio))
3686                 .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness));
3687 
3688         anim.add(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate,
3689                 verticalFactor * secondaryTaskDimension * 2).setDuration(duration), LINEAR, sp);
3690 
3691         if (taskView.isRunningTask()) {
3692             anim.addOnFrameCallback(() -> {
3693                 if (!mEnableDrawingLiveTile) return;
3694                 runActionOnRemoteHandles(remoteTargetHandle ->
3695                         remoteTargetHandle.getTaskViewSimulator().taskSecondaryTranslation.value =
3696                                 taskView.getSecondaryDismissTranslationProperty().get(taskView));
3697                 redrawLiveTile();
3698             });
3699         }
3700     }
3701 
3702     /**
3703      * Places an {@link FloatingTaskView} on top of the thumbnail for {@link #mSplitHiddenTaskView}
3704      * and then animates it into the split position that was desired
3705      */
createInitialSplitSelectAnimation(PendingAnimation anim)3706     private void createInitialSplitSelectAnimation(PendingAnimation anim) {
3707         getPagedOrientationHandler().getInitialSplitPlaceholderBounds(mSplitPlaceholderSize,
3708                 mSplitPlaceholderInset, mContainer.getDeviceProfile(),
3709                 mSplitSelectStateController.getActiveSplitStagePosition(), mTempRect);
3710         SplitAnimationTimings timings =
3711                 AnimUtils.getDeviceOverviewToSplitTimings(mContainer.getDeviceProfile().isTablet);
3712 
3713         RectF startingTaskRect = new RectF();
3714         safeRemoveDragLayerView(mSplitSelectStateController.getFirstFloatingTaskView());
3715         SplitAnimInitProps splitAnimInitProps =
3716                 mSplitSelectStateController.getSplitAnimationController().getFirstAnimInitViews(
3717                         () -> mSplitHiddenTaskView, () -> mSplitSelectSource);
3718         if (mSplitSelectStateController.isAnimateCurrentTaskDismissal()) {
3719             // Create the split select animation from Overview
3720             mSplitHiddenTaskView.setThumbnailVisibility(INVISIBLE,
3721                     mSplitSelectStateController.getInitialTaskId());
3722             anim.setViewAlpha(splitAnimInitProps.getIconView(), 0, clampToProgress(LINEAR,
3723                     timings.getIconFadeStartOffset(),
3724                     timings.getIconFadeEndOffset()));
3725         }
3726 
3727         FloatingTaskView firstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mContainer,
3728                 splitAnimInitProps.getOriginalView(),
3729                 splitAnimInitProps.getOriginalBitmap(),
3730                 splitAnimInitProps.getIconDrawable(), startingTaskRect);
3731         firstFloatingTaskView.setAlpha(1);
3732         firstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
3733                 splitAnimInitProps.getFadeWithThumbnail(), splitAnimInitProps.isStagedTask());
3734         mSplitSelectStateController.setFirstFloatingTaskView(firstFloatingTaskView);
3735 
3736         // Allow user to click staged app to launch into fullscreen
3737         firstFloatingTaskView.setOnClickListener(view ->
3738                 mSplitSelectStateController.getSplitAnimationController().
3739                         playAnimPlaceholderToFullscreen(mContainer, view,
3740                                 Optional.of(() -> resetFromSplitSelectionState())));
3741         firstFloatingTaskView.setContentDescription(splitAnimInitProps.getContentDescription());
3742 
3743         // SplitInstructionsView: animate in
3744         safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView());
3745         SplitInstructionsView splitInstructionsView =
3746                 SplitInstructionsView.getSplitInstructionsView(mContainer);
3747         splitInstructionsView.setAlpha(0);
3748         anim.setViewAlpha(splitInstructionsView, 1, clampToProgress(LINEAR,
3749                 timings.getInstructionsContainerFadeInStartOffset(),
3750                 timings.getInstructionsContainerFadeInEndOffset()));
3751         anim.addFloat(splitInstructionsView, splitInstructionsView.UNFOLD, 0.1f, 1,
3752                 clampToProgress(EMPHASIZED_DECELERATE,
3753                         timings.getInstructionsUnfoldStartOffset(),
3754                         timings.getInstructionsUnfoldEndOffset()));
3755         mSplitSelectStateController.setSplitInstructionsView(splitInstructionsView);
3756 
3757         InteractionJankMonitorWrapper.begin(this, Cuj.CUJ_SPLIT_SCREEN_ENTER,
3758                 "First tile selected");
3759         anim.addListener(new AnimatorListenerAdapter() {
3760             @Override
3761             public void onAnimationStart(Animator animation) {
3762                 if (mSplitHiddenTaskView == getRunningTaskView()) {
3763                     finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
3764                             null /* onFinishComplete */);
3765                 } else {
3766                     switchToScreenshot(
3767                             () -> finishRecentsAnimation(true /* toRecents */,
3768                                     false /* shouldPip */, null /* onFinishComplete */));
3769                 }
3770             }
3771         });
3772         anim.addEndListener(success -> {
3773             if (success) {
3774                 InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER);
3775             } else {
3776                 // If transition to split select was interrupted, clean up to prevent glitches
3777                 mSplitSelectStateController.resetState();
3778                 InteractionJankMonitorWrapper.cancel(Cuj.CUJ_SPLIT_SCREEN_ENTER);
3779             }
3780 
3781             updateCurrentTaskActionsVisibility();
3782         });
3783     }
3784 
3785     /**
3786      * Creates a {@link PendingAnimation} for dismissing the specified {@link TaskView}.
3787      *
3788      * @param dismissedTaskView           the {@link TaskView} to be dismissed
3789      * @param animateTaskView             whether the {@link TaskView} to be dismissed should be
3790      *                                    animated
3791      * @param shouldRemoveTask            whether the associated {@link Task} should be removed from
3792      *                                    ActivityManager after dismissal
3793      * @param duration                    duration of the animation
3794      * @param dismissingForSplitSelection task dismiss animation is used for entering split
3795      *                                    selection state from app icon
3796      * @param isExpressiveDismiss         runs expressive animations controlled via
3797      *                                    {@link RecentsDismissUtils}
3798      */
createTaskDismissAnimation(PendingAnimation anim, @Nullable TaskView dismissedTaskView, boolean animateTaskView, boolean shouldRemoveTask, long duration, boolean dismissingForSplitSelection, boolean isExpressiveDismiss)3799     public void createTaskDismissAnimation(PendingAnimation anim,
3800             @Nullable TaskView dismissedTaskView,
3801             boolean animateTaskView, boolean shouldRemoveTask, long duration,
3802             boolean dismissingForSplitSelection, boolean isExpressiveDismiss) {
3803         if (mPendingAnimation != null) {
3804             mPendingAnimation.createPlaybackController().dispatchOnCancel().dispatchOnEnd();
3805         }
3806 
3807         int count = getPageCount();
3808         if (count == 0) {
3809             return;
3810         }
3811 
3812         boolean showAsGrid = showAsGrid();
3813         int taskCount = getTaskViewCount();
3814         int dismissedIndex = indexOfChild(dismissedTaskView);
3815         int dismissedTaskViewId =
3816                 dismissedTaskView != null ? dismissedTaskView.getTaskViewId() : INVALID_TASK_ID;
3817 
3818         // Grid specific properties.
3819         boolean isFocusedTaskDismissed = false;
3820         boolean isStagingFocusedTask = false;
3821         boolean isSlidingTasks = false;
3822         TaskView nextFocusedTaskView = null;
3823         boolean nextFocusedTaskFromTop = false;
3824         float dismissedTaskWidth = 0;
3825         float nextFocusedTaskWidth = 0;
3826 
3827         int[] oldScroll = new int[count];
3828         int[] newScroll = new int[count];
3829         int scrollDiffPerPage = 0;
3830         // Non-grid specific properties.
3831         boolean needsCurveUpdates = false;
3832         boolean areAllDesktopTasksDismissed = false;
3833 
3834         if (showAsGrid) {
3835             if (dismissedTaskView != null) {
3836                 dismissedTaskWidth = dismissedTaskView.getLayoutParams().width + mPageSpacing;
3837             }
3838             isFocusedTaskDismissed = dismissedTaskViewId != INVALID_TASK_ID
3839                     && dismissedTaskViewId == mFocusedTaskViewId;
3840             if (dismissingForSplitSelection && getTaskViewAt(
3841                     mCurrentPage) instanceof DesktopTaskView) {
3842                 areAllDesktopTasksDismissed = true;
3843             }
3844             if (isFocusedTaskDismissed) {
3845                 if (isSplitSelectionActive()) {
3846                     isStagingFocusedTask = true;
3847                 } else {
3848                     nextFocusedTaskFromTop =
3849                             !mTopRowIdSet.isEmpty() && mTopRowIdSet.size() >= (taskCount - 1) / 2f;
3850                     // Pick the next focused task from the preferred row.
3851                     for (TaskView taskView : getTaskViews()) {
3852                         if (taskView == dismissedTaskView || taskView.isLargeTile()) {
3853                             continue;
3854                         }
3855                         boolean isTopRow = mTopRowIdSet.contains(taskView.getTaskViewId());
3856                         if ((nextFocusedTaskFromTop && isTopRow
3857                                 || (!nextFocusedTaskFromTop && !isTopRow))) {
3858                             nextFocusedTaskView = taskView;
3859                             break;
3860                         }
3861                     }
3862                     if (nextFocusedTaskView != null) {
3863                         nextFocusedTaskWidth =
3864                                 nextFocusedTaskView.getLayoutParams().width + mPageSpacing;
3865                     }
3866                 }
3867             }
3868         }
3869 
3870         getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
3871         getPageScrolls(newScroll, false,
3872                 v -> v.getVisibility() != GONE && v != dismissedTaskView);
3873         if (count > 1) {
3874             scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]);
3875         }
3876 
3877         isSlidingTasks = isStagingFocusedTask || areAllDesktopTasksDismissed;
3878         float dismissTranslationInterpolationEnd = 1;
3879         boolean closeGapBetweenClearAll = false;
3880         boolean isClearAllHidden = isClearAllHidden();
3881         boolean snapToLastTask = false;
3882         boolean isLeftRightSplit =
3883                 mContainer.getDeviceProfile().isLeftRightSplit && isSplitSelectionActive();
3884         TaskView lastGridTaskView = showAsGrid ? getLastGridTaskView() : null;
3885         int currentPageScroll = getScrollForPage(mCurrentPage);
3886         int lastGridTaskScroll = getScrollForPage(indexOfChild(lastGridTaskView));
3887         boolean currentPageSnapsToEndOfGrid = currentPageScroll == lastGridTaskScroll;
3888 
3889         int topGridRowSize = mTopRowIdSet.size();
3890         int numLargeTiles = mUtils.getLargeTileCount();
3891         int bottomGridRowSize = taskCount - mTopRowIdSet.size() - numLargeTiles;
3892         boolean topRowLonger = topGridRowSize > bottomGridRowSize;
3893         boolean bottomRowLonger = bottomGridRowSize > topGridRowSize;
3894         boolean dismissedTaskFromTop = mTopRowIdSet.contains(dismissedTaskViewId);
3895         boolean dismissedTaskFromBottom = !dismissedTaskFromTop && !isFocusedTaskDismissed;
3896         if (dismissedTaskFromTop || (isFocusedTaskDismissed && nextFocusedTaskFromTop)) {
3897             topGridRowSize--;
3898         }
3899         if (dismissedTaskFromBottom || (isFocusedTaskDismissed && !nextFocusedTaskFromTop)) {
3900             bottomGridRowSize--;
3901         }
3902         int longRowWidth = Math.max(topGridRowSize, bottomGridRowSize)
3903                 * (mLastComputedGridTaskSize.width() + mPageSpacing);
3904         if (!enableGridOnlyOverview() && !isStagingFocusedTask) {
3905             longRowWidth += mLastComputedTaskSize.width() + mPageSpacing;
3906         }
3907         // Compensate the removed gap if we don't already have shortTotalCompensation,
3908         // and adjust accordingly to the new shortTotalCompensation after dismiss.
3909         int newClearAllShortTotalWidthTranslation = 0;
3910         if (mClearAllShortTotalWidthTranslation == 0) {
3911             // If first task is not in the expected position (mLastComputedTaskSize) and being too
3912             // close  to ClearAllButton, then apply extra translation to ClearAllButton.
3913             int firstTaskStart = mLastComputedGridSize.left + longRowWidth;
3914             int expectedFirstTaskStart = mLastComputedTaskSize.right;
3915             if (firstTaskStart < expectedFirstTaskStart) {
3916                 newClearAllShortTotalWidthTranslation = expectedFirstTaskStart - firstTaskStart;
3917             }
3918         }
3919         if (lastGridTaskView != null && (
3920                 (!isExpressiveDismiss && lastGridTaskView.isVisibleToUser()) || (isExpressiveDismiss
3921                         && (isTaskViewVisible(lastGridTaskView)
3922                         || lastGridTaskView == dismissedTaskView)))) {
3923             // After dismissal, animate translation of the remaining tasks to fill any gap left
3924             // between the end of the grid and the clear all button. Only animate if the clear
3925             // all button is visible or would become visible after dismissal.
3926             float longGridRowWidthDiff = 0;
3927 
3928             float gapWidth = 0;
3929             if ((topRowLonger && dismissedTaskFromTop)
3930                     || (bottomRowLonger && dismissedTaskFromBottom)) {
3931                 gapWidth = dismissedTaskWidth;
3932             } else if (nextFocusedTaskView != null
3933                     && ((topRowLonger && nextFocusedTaskFromTop)
3934                     || (bottomRowLonger && !nextFocusedTaskFromTop))) {
3935                 gapWidth = nextFocusedTaskWidth;
3936             }
3937             if (gapWidth > 0) {
3938                 if (mClearAllShortTotalWidthTranslation == 0) {
3939                     float gapCompensation = gapWidth - newClearAllShortTotalWidthTranslation;
3940                     longGridRowWidthDiff += mIsRtl ? -gapCompensation : gapCompensation;
3941                 }
3942                 if (isClearAllHidden) {
3943                     // If ClearAllButton isn't fully shown, snap to the last task.
3944                     snapToLastTask = true;
3945                 }
3946             }
3947             if (isLeftRightSplit && !isStagingFocusedTask) {
3948                 // LastTask's scroll is the minimum scroll in split select, if current scroll is
3949                 // beyond that, we'll need to snap to last task instead.
3950                 TaskView lastTask = getLastGridTaskView();
3951                 if (lastTask != null) {
3952                     int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this);
3953                     int lastTaskScroll = getScrollForPage(indexOfChild(lastTask));
3954                     if ((mIsRtl && primaryScroll < lastTaskScroll)
3955                             || (!mIsRtl && primaryScroll > lastTaskScroll)) {
3956                         snapToLastTask = true;
3957                     }
3958                 }
3959             }
3960             if (snapToLastTask) {
3961                 longGridRowWidthDiff += getSnapToLastTaskScrollDiff();
3962             } else if (isLeftRightSplit && currentPageSnapsToEndOfGrid) {
3963                 // Use last task as reference point for scroll diff and snapping calculation as it's
3964                 // the only invariant point in landscape split screen.
3965                 snapToLastTask = true;
3966             }
3967             if (mUtils.getGridTaskCount() == 1 && dismissedTaskView.isGridTask()) {
3968                 TaskView lastLargeTile = mUtils.getLastLargeTaskView();
3969                 if (lastLargeTile != null) {
3970                     // Calculate the distance to put last large tile back to middle of the screen.
3971                     int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this);
3972                     int lastLargeTileScroll = getScrollForPage(indexOfChild(lastLargeTile));
3973                     longGridRowWidthDiff = primaryScroll - lastLargeTileScroll;
3974 
3975                     if (!isClearAllHidden) {
3976                         // If ClearAllButton is visible, reduce the distance by scroll difference
3977                         // between ClearAllButton and the last task.
3978                         longGridRowWidthDiff += getLastTaskScroll(/*clearAllScroll=*/0,
3979                                 getPagedOrientationHandler().getPrimarySize(mClearAllButton));
3980                     }
3981                 }
3982             }
3983 
3984             // If we need to animate the grid to compensate the clear all gap, we split the second
3985             // half of the dismiss pending animation (in which the non-dismissed tasks slide into
3986             // place) in half again, making the first quarter the existing non-dismissal sliding
3987             // and the second quarter this new animation of gap filling. This is due to the fact
3988             // that PendingAnimation is a single animation, not a sequence of animations, so we
3989             // fake it using interpolation.
3990             if (longGridRowWidthDiff != 0) {
3991                 closeGapBetweenClearAll = true;
3992                 // Stagger the offsets of each additional task for a delayed animation. We use
3993                 // half here as this animation is half of half of an animation (1/4th).
3994                 float halfAdditionalDismissTranslationOffset =
3995                         (0.5f * ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET);
3996                 dismissTranslationInterpolationEnd = Utilities.boundToRange(
3997                         END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
3998                                 + (taskCount - 1) * halfAdditionalDismissTranslationOffset,
3999                         END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET, 1);
4000                 for (TaskView taskView : getTaskViews()) {
4001                     anim.setFloat(taskView, TaskView.GRID_END_TRANSLATION_X, longGridRowWidthDiff,
4002                             clampToProgress(LINEAR, dismissTranslationInterpolationEnd, 1));
4003                     dismissTranslationInterpolationEnd = Utilities.boundToRange(
4004                             dismissTranslationInterpolationEnd
4005                                     - halfAdditionalDismissTranslationOffset,
4006                             END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET, 1);
4007                     if (mEnableDrawingLiveTile && taskView.isRunningTask()) {
4008                         anim.addOnFrameCallback(() -> {
4009                             runActionOnRemoteHandles(
4010                                     remoteTargetHandle ->
4011                                             remoteTargetHandle.getTaskViewSimulator()
4012                                                     .taskPrimaryTranslation.value =
4013                                                     TaskView.GRID_END_TRANSLATION_X.get(taskView));
4014                             redrawLiveTile();
4015                         });
4016                     }
4017                 }
4018 
4019                 // Change alpha of clear all if translating grid to hide it
4020                 if (isClearAllHidden) {
4021                     anim.setFloat(mClearAllButton, DISMISS_ALPHA, 0, LINEAR);
4022                     anim.addListener(new AnimatorListenerAdapter() {
4023                         @Override
4024                         public void onAnimationEnd(Animator animation) {
4025                             super.onAnimationEnd(animation);
4026                             mClearAllButton.setDismissAlpha(1);
4027                         }
4028                     });
4029                 }
4030             }
4031         }
4032 
4033         SplitAnimationTimings splitTimings =
4034                 AnimUtils.getDeviceOverviewToSplitTimings(mContainer.getDeviceProfile().isTablet);
4035 
4036         int distanceFromDismissedTask = 1;
4037         int slidingTranslation = 0;
4038         if (isSlidingTasks) {
4039             int nextSnappedPage = indexOfChild(isStagingFocusedTask
4040                     ? mUtils.getFirstSmallTaskView()
4041                     : mUtils.getFirstNonDesktopTaskView());
4042             slidingTranslation = getPagedOrientationHandler().getPrimaryScroll(this)
4043                     - getScrollForPage(nextSnappedPage);
4044             slidingTranslation += mIsRtl ? newClearAllShortTotalWidthTranslation
4045                     : -newClearAllShortTotalWidthTranslation;
4046         }
4047         mTaskViewsDismissPrimaryTranslations.clear();
4048         int lastTaskViewIndex = indexOfChild(mUtils.getLastTaskView());
4049         for (int i = 0; i < count; i++) {
4050             View child = getChildAt(i);
4051             if (child == dismissedTaskView) {
4052                 if (animateTaskView && !dismissingForSplitSelection) {
4053                     addDismissedTaskAnimations(dismissedTaskView, duration, anim);
4054                 }
4055             } else if (!showAsGrid || (enableLargeDesktopWindowingTile()
4056                     && dismissedTaskView != null && dismissedTaskView.isLargeTile()
4057                     && nextFocusedTaskView == null && !dismissingForSplitSelection)) {
4058                 int offset = getOffsetToDismissedTask(scrollDiffPerPage, dismissedIndex,
4059                         lastTaskViewIndex);
4060                 int scrollDiff = newScroll[i] - oldScroll[i] + offset;
4061                 if (scrollDiff != 0) {
4062                     if (!isExpressiveDismiss) {
4063                         translateTaskWhenDismissed(
4064                                 child,
4065                                 Math.abs(i - dismissedIndex),
4066                                 scrollDiff,
4067                                 anim,
4068                                 splitTimings);
4069                     }
4070                     if (child instanceof TaskView taskView) {
4071                         mTaskViewsDismissPrimaryTranslations.put(taskView, scrollDiffPerPage);
4072                     }
4073                     needsCurveUpdates = true;
4074                 }
4075             } else if (child instanceof TaskView taskView) {
4076                 // Animate task with index >= dismissed index and in the same row as the
4077                 // dismissed index or next focused index. Offset successive task dismissal
4078                 // durations for a staggered effect.
4079                 int staggerColumn = isSlidingTasks
4080                         ? (int) Math.ceil(distanceFromDismissedTask / 2f)
4081                         : distanceFromDismissedTask;
4082                 // Set timings based on if user is initiating splitscreen on the focused task,
4083                 // or splitting/dismissing some other task.
4084                 final float animationStartProgress;
4085                 if (isSlidingTasks) {
4086                     float slidingStartOffset = splitTimings.getGridSlideStartOffset()
4087                             + (splitTimings.getGridSlideStaggerOffset() * staggerColumn);
4088                     if (areAllDesktopTasksDismissed) {
4089                         animationStartProgress = Utilities.boundToRange(
4090                                 slidingStartOffset
4091                                         + splitTimings.getDesktopFadeSplitAnimationEndOffset(),
4092                                 0f,
4093                                 dismissTranslationInterpolationEnd);
4094                     } else {
4095                         animationStartProgress = Utilities.boundToRange(
4096                                 slidingStartOffset,
4097                                 0f,
4098                                 dismissTranslationInterpolationEnd);
4099                     }
4100                 } else {
4101                     animationStartProgress = Utilities.boundToRange(
4102                             INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
4103                                     + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
4104                                     * staggerColumn, 0f, dismissTranslationInterpolationEnd);
4105                 }
4106 
4107                 final float animationEndProgress;
4108                 if (isSlidingTasks && taskView != nextFocusedTaskView) {
4109                     animationEndProgress = Utilities.boundToRange(
4110                             splitTimings.getGridSlideStartOffset()
4111                                     + (splitTimings.getGridSlideStaggerOffset() * staggerColumn)
4112                                     + splitTimings.getGridSlideDurationOffset(),
4113                             0f,
4114                             dismissTranslationInterpolationEnd);
4115                 } else {
4116                     animationEndProgress = dismissTranslationInterpolationEnd;
4117                 }
4118 
4119                 Interpolator dismissInterpolator = isSlidingTasks ? EMPHASIZED : LINEAR;
4120 
4121                 float primaryTranslation = 0;
4122                 if (taskView == nextFocusedTaskView) {
4123                     // Enlarge the task to be focused next, and translate into focus position.
4124                     float scale = mTaskWidth / (float) mLastComputedGridTaskSize.width();
4125                     anim.setFloat(taskView, TaskView.DISMISS_SCALE, scale,
4126                             clampToProgress(LINEAR, animationStartProgress,
4127                                     dismissTranslationInterpolationEnd));
4128                     primaryTranslation += dismissedTaskWidth;
4129                     float secondaryTranslation = -mTaskGridVerticalDiff;
4130                     if (!nextFocusedTaskFromTop) {
4131                         secondaryTranslation -= mTopBottomRowHeightDiff;
4132                     }
4133                     anim.setFloat(taskView, taskView.getSecondaryDismissTranslationProperty(),
4134                             secondaryTranslation, clampToProgress(LINEAR, animationStartProgress,
4135                                     dismissTranslationInterpolationEnd));
4136                     anim.add(taskView.getDismissIconFadeOutAnimator(),
4137                             clampToProgress(LINEAR, 0f, ANIMATION_DISMISS_PROGRESS_MIDPOINT));
4138                 } else if ((isFocusedTaskDismissed && nextFocusedTaskView != null && isSameGridRow(
4139                         taskView, nextFocusedTaskView))
4140                         || (!isFocusedTaskDismissed && i >= dismissedIndex && isSameGridRow(
4141                         taskView, dismissedTaskView))) {
4142                     primaryTranslation +=
4143                             nextFocusedTaskView != null ? nextFocusedTaskWidth : dismissedTaskWidth;
4144                 }
4145                 if (!(taskView instanceof DesktopTaskView)) {
4146                     primaryTranslation += mIsRtl ? slidingTranslation : -slidingTranslation;
4147                 }
4148 
4149                 if (primaryTranslation != 0) {
4150                     float finalTranslation = mIsRtl ? primaryTranslation : -primaryTranslation;
4151                     float startTranslation = 0;
4152                     if (!(taskView instanceof DesktopTaskView) && slidingTranslation != 0) {
4153                         startTranslation = isTaskViewVisible(taskView) ? 0
4154                                 : finalTranslation + (mIsRtl ? -mLastComputedTaskSize.right
4155                                         : mLastComputedTaskSize.right);
4156                     }
4157                     // Expressive dismiss will animate the translations of taskViews itself.
4158                     if (!isExpressiveDismiss) {
4159                         Animator dismissAnimator = ObjectAnimator.ofFloat(taskView,
4160                                 taskView.getPrimaryDismissTranslationProperty(),
4161                                 startTranslation, finalTranslation);
4162                         dismissAnimator.setInterpolator(
4163                                 clampToProgress(dismissInterpolator, animationStartProgress,
4164                                         animationEndProgress));
4165                         anim.add(dismissAnimator);
4166                     }
4167                     mTaskViewsDismissPrimaryTranslations.put(taskView, (int) finalTranslation);
4168                     distanceFromDismissedTask++;
4169                 }
4170             }
4171         }
4172         if (dismissingForSplitSelection) {
4173             createInitialSplitSelectAnimation(anim);
4174         }
4175 
4176         if (needsCurveUpdates) {
4177             anim.addOnFrameCallback(this::updateCurveProperties);
4178         }
4179 
4180         // Add a tiny bit of translation Z, so that it draws on top of other views. This is relevant
4181         // (e.g.) when we dismiss a task by sliding it upward: if there is a row of icons above, we
4182         // want the dragged task to stay above all other views.
4183         if (animateTaskView && dismissedTaskView != null) {
4184             dismissedTaskView.setTranslationZ(0.1f);
4185         }
4186         loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
4187         if (!dismissingForSplitSelection) {
4188             anim.addStartListener(() -> InteractionJankMonitorWrapper.begin(this,
4189                     Cuj.CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS));
4190         }
4191         mPendingAnimation = anim;
4192         final TaskView finalNextFocusedTaskView = nextFocusedTaskView;
4193         final boolean finalCloseGapBetweenClearAll = closeGapBetweenClearAll;
4194         final boolean finalSnapToLastTask = snapToLastTask;
4195         final boolean finalIsFocusedTaskDismissed = isFocusedTaskDismissed;
4196         mPendingAnimation.addEndListener(new Consumer<>() {
4197             @Override
4198             public void accept(Boolean success) {
4199                 if (mEnableDrawingLiveTile && dismissedTaskView != null
4200                         && dismissedTaskView.isRunningTask() && success) {
4201                     finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
4202                             () -> onEnd(true));
4203                 } else {
4204                     onEnd(success);
4205                 }
4206             }
4207 
4208             @SuppressWarnings("WrongCall")
4209             private void onEnd(boolean success) {
4210                 // Reset task translations as they may have updated via animations in
4211                 // createTaskDismissAnimation
4212                 resetTaskVisuals();
4213 
4214                 if (success) {
4215                     mAnyTaskHasBeenDismissed = true;
4216                     if (shouldRemoveTask && dismissedTaskView != null) {
4217                         if (dismissedTaskView.isRunningTask()) {
4218                             finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
4219                                     () -> removeTaskInternal(dismissedTaskView));
4220                         } else {
4221                             removeTaskInternal(dismissedTaskView);
4222                         }
4223                         mContainer.getStatsLogManager().logger()
4224                                 .withItemInfo(dismissedTaskView.getItemInfo())
4225                                 .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
4226                     }
4227 
4228                     int pageToSnapTo = mCurrentPage;
4229                     mCurrentPageScrollDiff = 0;
4230                     int taskViewIdToSnapTo = -1;
4231                     if (showAsGrid) {
4232                         if (finalCloseGapBetweenClearAll) {
4233                             if (finalSnapToLastTask) {
4234                                 // Last task will be determined after removing dismissed task.
4235                                 pageToSnapTo = -1;
4236                             } else if (taskCount > 2) {
4237                                 pageToSnapTo = indexOfChild(mClearAllButton);
4238                             } else if (isClearAllHidden) {
4239                                 // Snap to focused task if clear all is hidden.
4240                                 pageToSnapTo = indexOfChild(getFirstTaskView());
4241                             }
4242                         } else {
4243                             // Get the id of the task view we will snap to based on the current
4244                             // page's relative position as the order of indices change over time due
4245                             // to dismissals.
4246                             TaskView snappedTaskView = getTaskViewAt(mCurrentPage);
4247                             boolean calculateScrollDiff = true;
4248                             if (snappedTaskView != null && !finalSnapToLastTask) {
4249                                 if (snappedTaskView.getTaskViewId() == mFocusedTaskViewId) {
4250                                     if (finalNextFocusedTaskView != null) {
4251                                         taskViewIdToSnapTo =
4252                                                 finalNextFocusedTaskView.getTaskViewId();
4253                                     } else if (dismissedTaskViewId != mFocusedTaskViewId) {
4254                                         taskViewIdToSnapTo = mFocusedTaskViewId;
4255                                     } else {
4256                                         // Won't focus next task in split select, so snap to the
4257                                         // first task.
4258                                         pageToSnapTo = indexOfChild(getFirstTaskView());
4259                                         calculateScrollDiff = false;
4260                                     }
4261                                 } else {
4262                                     int snappedTaskViewId = snappedTaskView.getTaskViewId();
4263                                     boolean isSnappedTaskInTopRow = mTopRowIdSet.contains(
4264                                             snappedTaskViewId);
4265                                     IntArray taskViewIdArray =
4266                                             isSnappedTaskInTopRow ? mUtils.getTopRowIdArray()
4267                                                     : mUtils.getBottomRowIdArray();
4268                                     int snappedIndex = taskViewIdArray.indexOf(snappedTaskViewId);
4269                                     taskViewIdArray.removeValue(dismissedTaskViewId);
4270                                     if (finalNextFocusedTaskView != null) {
4271                                         taskViewIdArray.removeValue(
4272                                                 finalNextFocusedTaskView.getTaskViewId());
4273                                     }
4274                                     if (snappedIndex >= 0
4275                                             && snappedIndex < taskViewIdArray.size()) {
4276                                         taskViewIdToSnapTo = taskViewIdArray.get(snappedIndex);
4277                                     } else if (snappedIndex == taskViewIdArray.size()) {
4278                                         // If the snapped task is the last item from the
4279                                         // dismissed row,
4280                                         // snap to the same column in the other grid row
4281                                         IntArray inverseRowTaskViewIdArray =
4282                                                 isSnappedTaskInTopRow ? mUtils.getBottomRowIdArray()
4283                                                         : mUtils.getTopRowIdArray();
4284                                         if (snappedIndex < inverseRowTaskViewIdArray.size()) {
4285                                             taskViewIdToSnapTo = inverseRowTaskViewIdArray.get(
4286                                                     snappedIndex);
4287                                         }
4288                                     }
4289                                 }
4290                             }
4291 
4292                             if (calculateScrollDiff) {
4293                                 int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(
4294                                         RecentsView.this);
4295                                 int currentPageScroll = getScrollForPage(mCurrentPage);
4296                                 mCurrentPageScrollDiff = primaryScroll - currentPageScroll;
4297                             }
4298                         }
4299                     } else if (dismissedIndex < pageToSnapTo || pageToSnapTo == lastTaskViewIndex) {
4300                         pageToSnapTo--;
4301                     }
4302                     boolean isHomeTaskDismissed = dismissedTaskView == getHomeTaskView();
4303                     removeViewInLayout(dismissedTaskView);
4304                     mTopRowIdSet.remove(dismissedTaskViewId);
4305 
4306                     if (taskCount == 1) {
4307                         removeViewInLayout(mClearAllButton);
4308                         removeViewInLayout(mAddDesktopButton);
4309                         if (isHomeTaskDismissed) {
4310                             updateEmptyMessage();
4311                         } else if (!mSplitSelectStateController.isSplitSelectActive()) {
4312                             startHome();
4313                         }
4314                     } else {
4315                         // Update focus task and its size.
4316                         if (finalIsFocusedTaskDismissed && finalNextFocusedTaskView != null) {
4317                             setFocusedTaskViewId(enableGridOnlyOverview()
4318                                     ? INVALID_TASK_ID
4319                                     : finalNextFocusedTaskView.getTaskViewId());
4320                             mTopRowIdSet.remove(mFocusedTaskViewId);
4321                             finalNextFocusedTaskView.getDismissIconFadeInAnimator().start();
4322                         }
4323                         updateTaskSize();
4324                         mUtils.updateChildTaskOrientations();
4325                         // Update scroll and snap to page.
4326                         updateScrollSynchronously();
4327 
4328                         if (showAsGrid) {
4329                             // Rebalance tasks in the grid
4330                             TaskView highestVisibleTaskView = getHighestVisibleTaskView();
4331                             if (highestVisibleTaskView != null) {
4332                                 boolean shouldRebalance;
4333                                 int screenStart = getPagedOrientationHandler().getPrimaryScroll(
4334                                         RecentsView.this);
4335                                 int taskStart = getPagedOrientationHandler().getChildStart(
4336                                         highestVisibleTaskView)
4337                                         + (int) highestVisibleTaskView.getOffsetAdjustment(
4338                                                 /*gridEnabled=*/true);
4339 
4340                                 // Rebalance only if there is a maximum gap between the task and the
4341                                 // screen's edge; this ensures that rebalanced tasks are outside the
4342                                 // visible screen.
4343                                 if (mIsRtl) {
4344                                     shouldRebalance = taskStart <= screenStart + mPageSpacing;
4345                                 } else {
4346                                     int screenEnd = screenStart
4347                                             + getPagedOrientationHandler().getMeasuredSize(
4348                                             RecentsView.this);
4349                                     int taskSize = (int) (
4350                                             getPagedOrientationHandler().getMeasuredSize(
4351                                                     highestVisibleTaskView) * highestVisibleTaskView
4352                                                     .getSizeAdjustment(/*fullscreenEnabled=*/
4353                                                             false));
4354                                     int taskEnd = taskStart + taskSize;
4355 
4356                                     shouldRebalance = taskEnd >= screenEnd - mPageSpacing;
4357                                 }
4358 
4359                                 if (shouldRebalance) {
4360                                     updateGridProperties(highestVisibleTaskView);
4361                                     updateScrollSynchronously();
4362                                 }
4363                             }
4364 
4365                             IntArray topRowIdArray = mUtils.getTopRowIdArray();
4366                             IntArray bottomRowIdArray = mUtils.getBottomRowIdArray();
4367                             if (finalSnapToLastTask) {
4368                                 // If snapping to last task, find the last task after dismissal.
4369                                 pageToSnapTo = indexOfChild(
4370                                         getLastGridTaskView(topRowIdArray, bottomRowIdArray));
4371 
4372                                 if (pageToSnapTo == INVALID_PAGE) {
4373                                     // Snap to latest large tile page after dismissing the
4374                                     // last grid task. This will prevent snapping to page 0 when
4375                                     // desktop task is visible as large tile.
4376                                     pageToSnapTo = indexOfChild(mUtils.getLastLargeTaskView());
4377                                 }
4378                             } else if (taskViewIdToSnapTo != -1) {
4379                                 // If snapping to another page due to indices rearranging, find
4380                                 // the new index after dismissal & rearrange using the task view id.
4381                                 pageToSnapTo = indexOfChild(
4382                                         getTaskViewFromTaskViewId(taskViewIdToSnapTo));
4383                                 if (!currentPageSnapsToEndOfGrid) {
4384                                     // If it wasn't snapped to one of the last pages, but is now
4385                                     // snapped to last pages, we'll need to compensate for the
4386                                     // offset from the page's scroll to its visual position.
4387                                     mCurrentPageScrollDiff += getOffsetFromScrollPosition(
4388                                             pageToSnapTo, topRowIdArray, bottomRowIdArray);
4389                                 }
4390                             }
4391                         }
4392                         pageBeginTransition();
4393                         setCurrentPage(pageToSnapTo);
4394                         // Update various scroll-dependent UI.
4395                         dispatchScrollChanged();
4396                         updateActionsViewFocusedScroll();
4397                         if (isClearAllHidden() && !mContainer.getDeviceProfile().isTablet) {
4398                             mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING,
4399                                     false);
4400                         }
4401                     }
4402                 }
4403                 updateCurrentTaskActionsVisibility();
4404                 onDismissAnimationEnds();
4405                 mPendingAnimation = null;
4406                 mTaskViewsDismissPrimaryTranslations.clear();
4407 
4408                 if (!dismissingForSplitSelection && success) {
4409                     InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS);
4410                 } else if (!dismissingForSplitSelection) {
4411                     InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS);
4412                 }
4413             }
4414         });
4415     }
4416 
4417     /**
4418      * Compute scroll offsets from task dismissal for animation.
4419      * If we just take newScroll - oldScroll, everything to the right of dragged task
4420      * translates to the left. We need to offset this in some cases:
4421      * - In RTL, add page offset to all pages, since we want pages to move to the right
4422      * Additionally, add a page offset if:
4423      * - Current page is rightmost page (leftmost for RTL)
4424      * - Dragging an adjacent page on the left side (right side for RTL)
4425      */
getOffsetToDismissedTask(int scrollDiffPerPage, int dismissedIndex, int lastTaskViewIndex)4426     private int getOffsetToDismissedTask(int scrollDiffPerPage, int dismissedIndex,
4427             int lastTaskViewIndex) {
4428         // If `mCurrentPage` is beyond `lastTaskViewIndex`, use the last TaskView instead to
4429         // calculate offset.
4430         int currentPage = Math.min(mCurrentPage, lastTaskViewIndex);
4431         int offset = mIsRtl ? scrollDiffPerPage : 0;
4432         if (currentPage == dismissedIndex) {
4433             if (currentPage == lastTaskViewIndex) {
4434                 offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
4435             }
4436         } else {
4437             // Dismissing an adjacent page.
4438             int negativeAdjacent = currentPage - 1; // (Right in RTL, left in LTR)
4439             if (dismissedIndex == negativeAdjacent) {
4440                 offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
4441             }
4442         }
4443         return offset;
4444     }
4445 
translateTaskWhenDismissed( View view, int indexDiff, int scrollDiffPerPage, PendingAnimation pendingAnimation, SplitAnimationTimings splitTimings)4446     private void translateTaskWhenDismissed(
4447             View view,
4448             int indexDiff,
4449             int scrollDiffPerPage,
4450             PendingAnimation pendingAnimation,
4451             SplitAnimationTimings splitTimings) {
4452         // No need to translate the AddDesktopButton on dismissing a TaskView, which should be
4453         // always at the right most position, even when dismissing the last TaskView.
4454         if (view instanceof AddDesktopButton) {
4455             return;
4456         }
4457         FloatProperty translationProperty = view instanceof TaskView
4458                 ? ((TaskView) view).getPrimaryDismissTranslationProperty()
4459                 : getPagedOrientationHandler().getPrimaryViewTranslate();
4460 
4461         float additionalDismissDuration =
4462                 ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * indexDiff;
4463 
4464         // We are in non-grid layout.
4465         // If dismissing for split select, use split timings.
4466         // If not, use dismiss timings.
4467         float animationStartProgress = isSplitSelectionActive()
4468                 ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset(), 0f, 1f)
4469                 : Utilities.boundToRange(
4470                         INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
4471                                 + additionalDismissDuration, 0f, 1f);
4472 
4473         float animationEndProgress = isSplitSelectionActive()
4474                 ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset()
4475                 + splitTimings.getGridSlideDurationOffset(), 0f, 1f)
4476                 : 1f;
4477 
4478         // Slide tiles in horizontally to fill dismissed area
4479         pendingAnimation.setFloat(
4480                 view,
4481                 translationProperty,
4482                 scrollDiffPerPage,
4483                 clampToProgress(
4484                         splitTimings.getGridSlidePrimaryInterpolator(),
4485                         animationStartProgress,
4486                         animationEndProgress
4487                 )
4488         );
4489         if (mEnableDrawingLiveTile && view instanceof TaskView
4490                 && ((TaskView) view).isRunningTask()) {
4491             pendingAnimation.addOnFrameCallback(() -> {
4492                 runActionOnRemoteHandles(
4493                         remoteTargetHandle ->
4494                                 remoteTargetHandle.getTaskViewSimulator()
4495                                         .taskPrimaryTranslation.value =
4496                                         getPagedOrientationHandler().getPrimaryValue(
4497                                                 view.getTranslationX(),
4498                                                 view.getTranslationY()
4499                                         ));
4500                 redrawLiveTile();
4501             });
4502         }
4503     }
4504 
4505     /**
4506      * Hides all overview actions if user is halfway through split selection, shows otherwise.
4507      * We only show split option if:
4508      * * Focused view is a single app
4509      * * Device is large screen
4510      */
updateCurrentTaskActionsVisibility()4511     private void updateCurrentTaskActionsVisibility() {
4512         TaskView taskView = getCurrentPageTaskView();
4513         boolean isCurrentSplit = taskView instanceof GroupedTaskView;
4514         GroupedTaskView groupedTaskView = isCurrentSplit ? (GroupedTaskView) taskView : null;
4515         // Update flags to see if entire actions bar should be hidden.
4516         mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SELECT_ACTIVE, isSplitSelectionActive());
4517         // Update flags to see if actions bar should show buttons for a single task or a pair of
4518         // tasks.
4519         boolean canSaveAppPair = isCurrentSplit && supportsAppPairs() &&
4520                 getSplitSelectController().getAppPairsController().canSaveAppPair(groupedTaskView);
4521         mActionsView.updateForGroupedTask(isCurrentSplit, canSaveAppPair);
4522 
4523         boolean isCurrentDesktop = taskView instanceof DesktopTaskView;
4524         mActionsView.updateHiddenFlags(HIDDEN_DESKTOP, isCurrentDesktop);
4525     }
4526 
4527     /** Returns if app pairs are supported in this launcher. Overridden in subclasses. */
supportsAppPairs()4528     public boolean supportsAppPairs() {
4529         return true;
4530     }
4531 
4532     /**
4533      * Iterate the grid by columns instead of by TaskView index, starting after the focused task and
4534      * up to the last balanced column.
4535      *
4536      * @return the highest visible TaskView between both rows
4537      */
getHighestVisibleTaskView()4538     private TaskView getHighestVisibleTaskView() {
4539         if (mTopRowIdSet.isEmpty()) return null; // return earlier
4540 
4541         TaskView lastVisibleTaskView = null;
4542         IntArray topRowIdArray = mUtils.getTopRowIdArray();
4543         IntArray bottomRowIdArray = mUtils.getBottomRowIdArray();
4544         int balancedColumns = Math.min(bottomRowIdArray.size(), topRowIdArray.size());
4545 
4546         for (int i = 0; i < balancedColumns; i++) {
4547             TaskView topTask = getTaskViewFromTaskViewId(topRowIdArray.get(i));
4548 
4549             if (isTaskViewVisible(topTask)) {
4550                 TaskView bottomTask = getTaskViewFromTaskViewId(bottomRowIdArray.get(i));
4551                 lastVisibleTaskView =
4552                         indexOfChild(topTask) > indexOfChild(bottomTask) ? topTask : bottomTask;
4553             } else if (lastVisibleTaskView != null) {
4554                 break;
4555             }
4556         }
4557 
4558         return lastVisibleTaskView;
4559     }
4560 
removeTaskInternal(@onNull TaskView dismissedTaskView)4561     private void removeTaskInternal(@NonNull TaskView dismissedTaskView) {
4562         UI_HELPER_EXECUTOR
4563                 .getHandler()
4564                 .post(
4565                         () -> {
4566                             if (dismissedTaskView instanceof DesktopTaskView desktopTaskView) {
4567                                 removeDesktopTaskView(desktopTaskView);
4568                             } else {
4569                                 for (int taskId : dismissedTaskView.getTaskIds()) {
4570                                     ActivityManagerWrapper.getInstance().removeTask(taskId);
4571                                 }
4572                             }
4573                         });
4574     }
4575 
removeDesktopTaskView(DesktopTaskView desktopTaskView)4576     private void removeDesktopTaskView(DesktopTaskView desktopTaskView) {
4577         if (areMultiDesksFlagsEnabled()) {
4578             SystemUiProxy.INSTANCE
4579                     .get(getContext())
4580                     .removeDesk(desktopTaskView.getDeskId());
4581         } else if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
4582             SystemUiProxy.INSTANCE
4583                     .get(getContext())
4584                     .removeDefaultDeskInDisplay(
4585                             mContainer.getDisplay().getDisplayId());
4586         }
4587     }
4588 
onDismissAnimationEnds()4589     protected void onDismissAnimationEnds() {
4590         AccessibilityManagerCompat.sendTestProtocolEventToTest(getContext(),
4591                 DISMISS_ANIMATION_ENDS_MESSAGE);
4592     }
4593 
createAllTasksDismissAnimation(long duration)4594     public PendingAnimation createAllTasksDismissAnimation(long duration) {
4595         if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
4596             throw new IllegalStateException("Another pending animation is still running");
4597         }
4598         PendingAnimation anim = new PendingAnimation(duration);
4599 
4600         for (TaskView taskView : getTaskViews()) {
4601             addDismissedTaskAnimations(taskView, duration, anim);
4602         }
4603 
4604         mPendingAnimation = anim;
4605         mPendingAnimation.addEndListener(isSuccess -> {
4606             if (isSuccess) {
4607                 // Remove desktops first, since desks can be empty (so they have no recent tasks),
4608                 // and closing all tasks on a desk doesn't always necessarily mean that the desk
4609                 // will be removed. So, there are no guarantees that the below call to
4610                 // `ActivityManagerWrapper::removeAllRecentTasks()` will be enough.
4611                 SystemUiProxy.INSTANCE.get(getContext()).removeAllDesks();
4612 
4613                 // Remove all the task views now
4614                 finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, () -> {
4615                     UI_HELPER_EXECUTOR.getHandler().post(
4616                             ActivityManagerWrapper.getInstance()::removeAllRecentTasks);
4617                     removeAllTaskViews();
4618                     startHome();
4619                 });
4620             }
4621             mPendingAnimation = null;
4622         });
4623         return anim;
4624     }
4625 
snapToPageRelative(int delta, boolean cycle, TaskGridNavHelper.TaskNavDirection direction)4626     private boolean snapToPageRelative(int delta, boolean cycle,
4627             TaskGridNavHelper.TaskNavDirection direction) {
4628         // Set next page if scroll animation is still running, otherwise cannot snap to the
4629         // next page on successive key presses. Setting the current page aborts the scroll.
4630         if (!mScroller.isFinished()) {
4631             setCurrentPage(getNextPage());
4632         }
4633         int pageCount = getPageCount();
4634         if (pageCount == 0) {
4635             return false;
4636         }
4637         final int newPageUnbound = getNextPageInternal(delta, direction, cycle);
4638         if (!cycle && (newPageUnbound < 0 || newPageUnbound > pageCount)) {
4639             return false;
4640         }
4641         snapToPage((newPageUnbound + pageCount) % pageCount);
4642         getChildAt(getNextPage()).requestFocus();
4643         return true;
4644     }
4645 
getNextPageInternal(int delta, TaskGridNavHelper.TaskNavDirection direction, boolean cycle)4646     private int getNextPageInternal(int delta, TaskGridNavHelper.TaskNavDirection direction,
4647             boolean cycle) {
4648         if (!showAsGrid()) {
4649             return getNextPage() + delta;
4650         }
4651 
4652         // Init task grid nav helper with top/bottom id arrays.
4653         TaskGridNavHelper taskGridNavHelper = new TaskGridNavHelper(mUtils.getTopRowIdArray(),
4654                 mUtils.getBottomRowIdArray(), mUtils.getLargeTaskViewIds(),
4655                 mAddDesktopButton != null);
4656 
4657         // Get current page's task view ID.
4658         TaskView currentPageTaskView = getCurrentPageTaskView();
4659         int currentPageTaskViewId;
4660         final int clearAllButtonIndex = indexOfChild(mClearAllButton);
4661         final int addDesktopButtonIndex = indexOfChild(mAddDesktopButton);
4662         if (currentPageTaskView != null) {
4663             currentPageTaskViewId = currentPageTaskView.getTaskViewId();
4664         } else if (mCurrentPage == clearAllButtonIndex) {
4665             currentPageTaskViewId = TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID;
4666         } else if (mCurrentPage == addDesktopButtonIndex) {
4667             currentPageTaskViewId = TaskGridNavHelper.ADD_DESK_PLACEHOLDER_ID;
4668         } else {
4669             return INVALID_PAGE;
4670         }
4671 
4672         final int nextGridPage =
4673                 taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
4674         if (nextGridPage == TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID) {
4675             return clearAllButtonIndex;
4676         }
4677         if (nextGridPage == TaskGridNavHelper.ADD_DESK_PLACEHOLDER_ID) {
4678             return addDesktopButtonIndex;
4679         }
4680         return indexOfChild(getTaskViewFromTaskViewId(nextGridPage));
4681     }
4682 
runDismissAnimation(PendingAnimation pendingAnim)4683     private void runDismissAnimation(PendingAnimation pendingAnim) {
4684         AnimatorPlaybackController controller = pendingAnim.createPlaybackController();
4685         controller.dispatchOnStart();
4686         controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN);
4687         controller.start();
4688     }
4689 
4690     @UiThread
dismissTask(int taskId, boolean animate, boolean removeTask)4691     public void dismissTask(int taskId, boolean animate, boolean removeTask) {
4692         TaskView taskView = getTaskViewByTaskId(taskId);
4693         if (taskView == null) {
4694             Log.d(TAG, "dismissTask: " + taskId + ",  no associated TaskView");
4695             return;
4696         }
4697         Log.d(TAG, "dismissTask: " + taskId);
4698 
4699         if (enableDesktopExplodedView() && taskView instanceof  DesktopTaskView desktopTaskView) {
4700             desktopTaskView.removeTaskFromExplodedView(taskId, animate);
4701 
4702             if (removeTask) {
4703                 ActivityManagerWrapper.getInstance().removeTask(taskId);
4704             }
4705         } else {
4706             dismissTaskView(taskView, animate, removeTask);
4707         }
4708     }
4709 
4710     /** Dismisses the entire [taskView]. */
dismissTaskView(TaskView taskView, boolean animateTaskView, boolean removeTask)4711     public void dismissTaskView(TaskView taskView, boolean animateTaskView, boolean removeTask) {
4712         PendingAnimation pa = new PendingAnimation(DISMISS_TASK_DURATION);
4713         createTaskDismissAnimation(pa, taskView, animateTaskView, removeTask, DISMISS_TASK_DURATION,
4714                 false /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */);
4715         runDismissAnimation(pa);
4716     }
4717 
expressiveDismissTaskView(TaskView taskView, Function0<Unit> onEndRunnable)4718     protected void expressiveDismissTaskView(TaskView taskView, Function0<Unit> onEndRunnable) {
4719         PendingAnimation pa = new PendingAnimation(DISMISS_TASK_DURATION);
4720         createTaskDismissAnimation(pa, taskView, false /* animateTaskView */, true /* removeTask */,
4721                 DISMISS_TASK_DURATION, false /* dismissingForSplitSelection*/,
4722                 true /* isExpressiveDismiss */);
4723         pa.addEndListener((success) -> onEndRunnable.invoke());
4724         runDismissAnimation(pa);
4725     }
4726 
4727     @SuppressWarnings("unused")
dismissAllTasks(View view)4728     private void dismissAllTasks(View view) {
4729         runDismissAnimation(createAllTasksDismissAnimation(DISMISS_TASK_DURATION));
4730         mContainer.getStatsLogManager().logger().log(LAUNCHER_TASK_CLEAR_ALL);
4731     }
4732 
dismissCurrentTask()4733     private void dismissCurrentTask() {
4734         TaskView taskView = getNextPageTaskView();
4735         if (taskView != null) {
4736             dismissTaskView(taskView, true /*animateTaskView*/, true /*removeTask*/);
4737         }
4738     }
4739 
createDesk(View view)4740     private void createDesk(View view) {
4741         SystemUiProxy.INSTANCE
4742                 .get(getContext())
4743                 .createDesk(mContainer.getDisplay().getDisplayId());
4744     }
4745 
4746     @Override
dispatchKeyEvent(KeyEvent event)4747     public boolean dispatchKeyEvent(KeyEvent event) {
4748         if (isHandlingTouch() || event.getAction() != KeyEvent.ACTION_DOWN) {
4749             return super.dispatchKeyEvent(event);
4750         }
4751 
4752         if (mUtils.shouldInterceptKeyEvent(event)) {
4753             return super.dispatchKeyEvent(event);
4754         }
4755 
4756         switch (event.getKeyCode()) {
4757             case KeyEvent.KEYCODE_TAB:
4758                 return snapToPageRelative(event.isShiftPressed() ? -1 : 1, true /* cycle */,
4759                         TaskGridNavHelper.TaskNavDirection.TAB);
4760             case KeyEvent.KEYCODE_DPAD_RIGHT:
4761                 return snapToPageRelative(mIsRtl ? -1 : 1, true /* cycle */,
4762                         TaskGridNavHelper.TaskNavDirection.RIGHT);
4763             case KeyEvent.KEYCODE_DPAD_LEFT:
4764                 return snapToPageRelative(mIsRtl ? 1 : -1, true /* cycle */,
4765                         TaskGridNavHelper.TaskNavDirection.LEFT);
4766             case KeyEvent.KEYCODE_DPAD_UP:
4767                 return snapToPageRelative(1, false /* cycle */,
4768                         TaskGridNavHelper.TaskNavDirection.UP);
4769             case KeyEvent.KEYCODE_DPAD_DOWN:
4770                 return snapToPageRelative(1, false /* cycle */,
4771                         TaskGridNavHelper.TaskNavDirection.DOWN);
4772             case KeyEvent.KEYCODE_DEL:
4773             case KeyEvent.KEYCODE_FORWARD_DEL:
4774                 dismissCurrentTask();
4775                 return true;
4776             case KeyEvent.KEYCODE_NUMPAD_DOT:
4777                 if (event.isAltPressed()) {
4778                     // Numpad DEL pressed while holding Alt.
4779                     dismissCurrentTask();
4780                     return true;
4781                 }
4782         }
4783         return super.dispatchKeyEvent(event);
4784     }
4785 
4786     @Override
onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect)4787     protected void onFocusChanged(boolean gainFocus, int direction,
4788             @Nullable Rect previouslyFocusedRect) {
4789         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
4790         if (gainFocus && getChildCount() > 0) {
4791             switch (direction) {
4792                 case FOCUS_FORWARD:
4793                     setCurrentPage(0);
4794                     break;
4795                 case FOCUS_BACKWARD:
4796                 case FOCUS_RIGHT:
4797                 case FOCUS_LEFT:
4798                     setCurrentPage(getChildCount() - 1);
4799                     break;
4800             }
4801         }
4802     }
4803 
getContentAlpha()4804     public float getContentAlpha() {
4805         return mContentAlpha;
4806     }
4807 
setContentAlpha(float alpha)4808     public void setContentAlpha(float alpha) {
4809         if (alpha == mContentAlpha) {
4810             return;
4811         }
4812         alpha = Utilities.boundToRange(alpha, 0, 1);
4813         mContentAlpha = alpha;
4814 
4815         for (TaskView taskView : getTaskViews()) {
4816             taskView.setStableAlpha(alpha);
4817         }
4818         mClearAllButton.setContentAlpha(mContentAlpha);
4819 
4820         if (mAddDesktopButton != null) {
4821             mAddDesktopButton.setContentAlpha(mContentAlpha);
4822         }
4823         int alphaInt = Math.round(alpha * 255);
4824         mEmptyMessagePaint.setAlpha(alphaInt);
4825         mEmptyIcon.setAlpha(alphaInt);
4826         mActionsView.getContentAlpha().updateValue(mContentAlpha);
4827 
4828         if (alpha > 0) {
4829             setVisibility(VISIBLE);
4830         } else if (!mFreezeViewVisibility) {
4831             setVisibility(INVISIBLE);
4832         }
4833     }
4834 
4835     /**
4836      * Freezes the view visibility change. When frozen, the view will not change its visibility
4837      * to gone due to alpha changes.
4838      */
setFreezeViewVisibility(boolean freezeViewVisibility)4839     public void setFreezeViewVisibility(boolean freezeViewVisibility) {
4840         if (mFreezeViewVisibility != freezeViewVisibility) {
4841             mFreezeViewVisibility = freezeViewVisibility;
4842             if (!mFreezeViewVisibility) {
4843                 setVisibility(mContentAlpha > 0 ? VISIBLE : INVISIBLE);
4844             }
4845         }
4846     }
4847 
4848     @Override
setVisibility(int visibility)4849     public void setVisibility(int visibility) {
4850         super.setVisibility(visibility);
4851         if (mActionsView != null) {
4852             mActionsView.updateHiddenFlags(HIDDEN_NO_RECENTS, visibility != VISIBLE);
4853             if (visibility != VISIBLE) {
4854                 mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
4855             }
4856         }
4857     }
4858 
4859     @Override
onConfigurationChanged(Configuration newConfig)4860     protected void onConfigurationChanged(Configuration newConfig) {
4861         super.onConfigurationChanged(newConfig);
4862         updateRecentsRotation();
4863         onOrientationChanged();
4864     }
4865 
4866     /**
4867      * Updates {@link RecentsOrientedState}'s cached RecentsView rotation.
4868      */
updateRecentsRotation()4869     public void updateRecentsRotation() {
4870         final int rotation = TraceHelper.allowIpcs(
4871                 "RecentsView.updateRecentsRotation", () -> mContainer.getDisplay().getRotation());
4872         // Log real orientation change.
4873         if (mOrientationState.setRecentsRotation(rotation)) {
4874             logOrientationChanged();
4875         }
4876     }
4877 
reapplyActiveRotation()4878     public void reapplyActiveRotation() {
4879         RotationTouchHelper rotationTouchHelper = RotationTouchHelper.INSTANCE.get(getContext());
4880         setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(),
4881                 rotationTouchHelper.getDisplayRotation());
4882     }
4883 
setLayoutRotation(int touchRotation, int displayRotation)4884     public void setLayoutRotation(int touchRotation, int displayRotation) {
4885         if (mOrientationState.update(touchRotation, displayRotation)) {
4886             updateOrientationHandler();
4887         }
4888     }
4889 
getPagedViewOrientedState()4890     public RecentsOrientedState getPagedViewOrientedState() {
4891         return mOrientationState;
4892     }
4893 
getPagedOrientationHandler()4894     public RecentsPagedOrientationHandler getPagedOrientationHandler() {
4895         return (RecentsPagedOrientationHandler) super.getPagedOrientationHandler();
4896     }
4897 
4898     @Nullable
getNextTaskView()4899     public TaskView getNextTaskView() {
4900         return getTaskViewAt(getRunningTaskIndex() + 1);
4901     }
4902 
4903     @Nullable
getPreviousTaskView()4904     public TaskView getPreviousTaskView() {
4905         return getTaskViewAt(getRunningTaskIndex() - 1);
4906     }
4907 
4908     @Nullable
getLastLargeTaskView()4909     public TaskView getLastLargeTaskView() {
4910         return mUtils.getLastLargeTaskView();
4911     }
4912 
getLargeTilesCount()4913     public int getLargeTilesCount() {
4914         return mUtils.getLargeTileCount();
4915     }
4916 
4917     @Nullable
getCurrentPageTaskView()4918     public TaskView getCurrentPageTaskView() {
4919         return getTaskViewAt(getCurrentPage());
4920     }
4921 
4922     @Nullable
getNextPageTaskView()4923     public TaskView getNextPageTaskView() {
4924         return getTaskViewAt(getNextPage());
4925     }
4926 
4927     @Nullable
getTaskViewNearestToCenterOfScreen()4928     public TaskView getTaskViewNearestToCenterOfScreen() {
4929         return getTaskViewAt(getPageNearestToCenterOfScreen());
4930     }
4931 
4932     /**
4933      * Returns null instead of indexOutOfBoundsError when index is not in range
4934      */
4935     @Nullable
getTaskViewAt(int index)4936     public TaskView getTaskViewAt(int index) {
4937         View child = getChildAt(index);
4938         return child instanceof TaskView ? (TaskView) child : null;
4939     }
4940 
4941     /**
4942      * Returns iterable [TaskView] children.
4943      */
getTaskViews()4944     public RecentsViewUtils.TaskViewsIterable getTaskViews() {
4945         return mUtils.getTaskViews();
4946     }
4947 
setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener)4948     public void setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener) {
4949         mOnEmptyMessageUpdatedListener = listener;
4950     }
4951 
updateEmptyMessage()4952     public void updateEmptyMessage() {
4953         boolean isEmpty = !hasTaskViews();
4954         boolean hasSizeChanged = mLastMeasureSize.x != getWidth()
4955                 || mLastMeasureSize.y != getHeight();
4956         if (isEmpty == mShowEmptyMessage && !hasSizeChanged) {
4957             return;
4958         }
4959         setContentDescription(isEmpty ? mEmptyMessage : "");
4960         setFocusable(isEmpty);
4961         mShowEmptyMessage = isEmpty;
4962         updateEmptyStateUi(hasSizeChanged);
4963         invalidate();
4964 
4965         if (mOnEmptyMessageUpdatedListener != null) {
4966             mOnEmptyMessageUpdatedListener.onEmptyMessageUpdated(mShowEmptyMessage);
4967         }
4968     }
4969 
4970     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)4971     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
4972         // If we're going to a state without overview panel, avoid unnecessary onLayout that
4973         // cause TaskViews to re-arrange during animation to that state.
4974         if (!mOverviewStateEnabled && !mFirstLayout) {
4975             return;
4976         }
4977 
4978         mShowAsGridLastOnLayout = showAsGrid();
4979 
4980         super.onLayout(changed, left, top, right, bottom);
4981 
4982         updateEmptyStateUi(changed);
4983 
4984         setTaskModalness(mTaskModalness);
4985         mLastComputedTaskStartPushOutDistance = null;
4986         mLastComputedTaskEndPushOutDistance = null;
4987         updatePageOffsets();
4988         runActionOnRemoteHandles(
4989                 remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
4990                         .setScroll(getScrollOffset()));
4991         setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO
4992                 : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
4993     }
4994 
updatePivots()4995     private void updatePivots() {
4996         if (mOverviewSelectEnabled && !enableGridOnlyOverview()) {
4997             mTempPointF.set(mLastComputedTaskSize.centerX(), mLastComputedTaskSize.bottom);
4998         } else {
4999             mTempRect.set(mLastComputedTaskSize);
5000             getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect,
5001                     mContainer.getDeviceProfile(), mTempPointF);
5002         }
5003         setPivotX(mTempPointF.x);
5004         setPivotY(mTempPointF.y);
5005         if (enableGridOnlyOverview()) {
5006             runActionOnRemoteHandles(remoteTargetHandle ->
5007                     remoteTargetHandle.getTaskViewSimulator().setPivotOverride(mTempPointF));
5008         }
5009     }
5010 
5011     /**
5012      * Sets whether we should force-override the page offset mid-point to the current task, rather
5013      * than the running task, when updating page offsets.
5014      */
setOffsetMidpointIndexOverride(int offsetMidpointIndexOverride)5015     public void setOffsetMidpointIndexOverride(int offsetMidpointIndexOverride) {
5016         if (!enableAdditionalHomeAnimations()) {
5017             return;
5018         }
5019         mOffsetMidpointIndexOverride = offsetMidpointIndexOverride;
5020         updatePageOffsets();
5021     }
5022 
updatePageOffsets()5023     private void updatePageOffsets() {
5024         float offset = mAdjacentPageHorizontalOffset;
5025         float modalOffset = ACCELERATE_0_75.getInterpolation(mTaskModalness);
5026         int count = getChildCount();
5027         boolean showAsGrid = showAsGrid();
5028 
5029         TaskView runningTask = mRunningTaskViewId == INVALID_PAGE || !mRunningTaskTileHidden
5030                 ? null : getRunningTaskView();
5031         int midpoint = mOffsetMidpointIndexOverride == INVALID_PAGE
5032                 ? (runningTask == null ? INVALID_PAGE : indexOfChild(runningTask))
5033                 : mOffsetMidpointIndexOverride;
5034         int modalMidpoint = getCurrentPage();
5035         TaskView carouselHiddenMidpointTask = runningTask != null ? runningTask
5036                 : mUtils.getFirstTaskViewInCarousel(/*nonRunningTaskCarouselHidden=*/true,
5037                         /*runningTaskView=*/null);
5038         int carouselHiddenMidpoint = indexOfChild(carouselHiddenMidpointTask);
5039         boolean shouldCalculateOffsetForAllTasks = showAsGrid
5040                 && (enableGridOnlyOverview() || enableLargeDesktopWindowingTile())
5041                 && mTaskModalness > 0;
5042         if (shouldCalculateOffsetForAllTasks) {
5043             modalMidpoint = indexOfChild(getSelectedTaskView());
5044         }
5045 
5046         float midpointOffsetSize = 0;
5047         float leftOffsetSize = midpoint - 1 >= 0
5048                 ? getHorizontalOffsetSize(midpoint - 1, midpoint, offset)
5049                 : 0;
5050         float rightOffsetSize = midpoint + 1 < count
5051                 ? getHorizontalOffsetSize(midpoint + 1, midpoint, offset)
5052                 : 0;
5053 
5054         float modalMidpointOffsetSize = 0;
5055         float modalLeftOffsetSize = 0;
5056         float modalRightOffsetSize = 0;
5057         float gridOffsetSize = 0;
5058         float carouselHiddenOffsetSize = 0;
5059 
5060         if (showAsGrid) {
5061             // In grid, we only focus the task on the side. The reference index used for offset
5062             // calculation is the task directly next to the focus task in the grid.
5063             int referenceIndex = modalMidpoint == 0 ? 1 : 0;
5064             gridOffsetSize = referenceIndex < count
5065                     ? getHorizontalOffsetSize(referenceIndex, modalMidpoint, modalOffset)
5066                     : 0;
5067         } else {
5068             modalLeftOffsetSize = modalMidpoint - 1 >= 0
5069                     ? getHorizontalOffsetSize(modalMidpoint - 1, modalMidpoint, modalOffset)
5070                     : 0;
5071             modalRightOffsetSize = modalMidpoint + 1 < count
5072                     ? getHorizontalOffsetSize(modalMidpoint + 1, modalMidpoint, modalOffset)
5073                     : 0;
5074         }
5075 
5076         int primarySize = getPagedOrientationHandler().getPrimaryValue(getWidth(), getHeight());
5077         float maxOverscroll = primarySize * OverScroll.OVERSCROLL_DAMP_FACTOR;
5078         for (int i = 0; i < count; i++) {
5079             View child = getChildAt(i);
5080             float translation = i == midpoint
5081                     ? midpointOffsetSize
5082                     : i < midpoint
5083                             ? leftOffsetSize
5084                             : rightOffsetSize;
5085             if (shouldCalculateOffsetForAllTasks) {
5086                 gridOffsetSize = getHorizontalOffsetSize(i, modalMidpoint, modalOffset);
5087                 gridOffsetSize = Math.abs(gridOffsetSize) * (i <= modalMidpoint ? 1 : -1);
5088             }
5089             if (enableLargeDesktopWindowingTile()) {
5090                 if (child instanceof TaskView
5091                         && !mUtils.isVisibleInCarousel((TaskView) child,
5092                         runningTask, /*nonRunningTaskCarouselHidden=*/true)) {
5093                     // Increment carouselHiddenOffsetSize by maxOverscroll so it won't be on screen
5094                     // even when user overscroll.
5095                     carouselHiddenOffsetSize = (Math.abs(getMaxHorizontalOffsetSize(i,
5096                             carouselHiddenMidpoint)) + maxOverscroll)
5097                             * mDesktopCarouselDetachProgress;
5098                     carouselHiddenOffsetSize = carouselHiddenOffsetSize * (
5099                             i <= carouselHiddenMidpoint ? 1 : -1);
5100                 } else {
5101                     carouselHiddenOffsetSize = 0;
5102                 }
5103             }
5104             float modalTranslation = i == modalMidpoint
5105                     ? modalMidpointOffsetSize
5106                     : showAsGrid
5107                             ? gridOffsetSize
5108                             : i < modalMidpoint ? modalLeftOffsetSize : modalRightOffsetSize;
5109             boolean skipTranslationOffset = enableDesktopTaskAlphaAnimation()
5110                     && i == getRunningTaskIndex()
5111                     && child instanceof DesktopTaskView;
5112             float totalTranslationX = (skipTranslationOffset ? 0f : translation) + modalTranslation
5113                     + carouselHiddenOffsetSize;
5114             if (child instanceof TaskView taskView) {
5115                 taskView.getPrimaryTaskOffsetTranslationProperty().set(taskView, totalTranslationX);
5116             } else if (child instanceof ClearAllButton) {
5117                 getPagedOrientationHandler().getPrimaryViewTranslate().set(child,
5118                         totalTranslationX);
5119             } else if (child instanceof AddDesktopButton addDesktopButton) {
5120                 addDesktopButton.setOffsetTranslationX(totalTranslationX);
5121             }
5122             if (mEnableDrawingLiveTile && i == getRunningTaskIndex()) {
5123                 runActionOnRemoteHandles(
5124                         remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
5125                                 .taskPrimaryTranslation.value = totalTranslationX);
5126                 redrawLiveTile();
5127             }
5128 
5129             if (showAsGrid && enableGridOnlyOverview() && child instanceof TaskView taskView) {
5130                 float totalTranslationY = getVerticalOffsetSize(taskView, modalOffset);
5131                 FloatProperty<TaskView> translationPropertyY =
5132                         taskView.getSecondaryTaskOffsetTranslationProperty();
5133                 translationPropertyY.set(taskView, totalTranslationY);
5134             }
5135         }
5136         updateCurveProperties();
5137     }
5138 
5139     /**
5140      * Computes the child position with persistent translation considered (see
5141      * {@link TaskView#getPersistentTranslationX()}.
5142      */
5143     private void getPersistentChildPosition(int childIndex, int midPointScroll, RectF outRect) {
5144         View child = getChildAt(childIndex);
5145         outRect.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
5146         if (child instanceof TaskView) {
5147             TaskView taskView = (TaskView) child;
5148             outRect.offset(taskView.getPersistentTranslationX(),
5149                     taskView.getPersistentTranslationY());
5150             outRect.top += mContainer.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
5151 
5152             mTempMatrix.reset();
5153             float persistentScale = taskView.getPersistentScale();
5154             mTempMatrix.postScale(persistentScale, persistentScale,
5155                     mIsRtl ? outRect.right : outRect.left, outRect.top);
5156             mTempMatrix.mapRect(outRect);
5157         }
5158         outRect.offset(getPagedOrientationHandler().getPrimaryValue(-midPointScroll, 0),
5159                 getPagedOrientationHandler().getSecondaryValue(-midPointScroll, 0));
5160     }
5161 
5162     /**
5163      * Computes the distance to offset the given child such that it is completely offscreen when
5164      * translating away from the given midpoint.
5165      *
5166      * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen.
5167      */
5168     private float getHorizontalOffsetSize(int childIndex, int midpointIndex, float offsetProgress) {
5169         if (offsetProgress == 0) {
5170             // Don't bother calculating everything below if we won't offset anyway.
5171             return 0;
5172         }
5173 
5174         return getMaxHorizontalOffsetSize(childIndex, midpointIndex) * offsetProgress;
5175     }
5176 
5177     /**
5178      * Computes the distance to offset the given child such that it is completely offscreen when
5179      * translating away from the given midpoint.
5180      */
5181     private float getMaxHorizontalOffsetSize(int childIndex, int midpointIndex) {
5182         // First, get the position of the task relative to the midpoint. If there is no midpoint
5183         // then we just use the normal (centered) task position.
5184         RectF taskPosition = mTempRectF;
5185         // Whether the task should be shifted to start direction (i.e. left edge for portrait, top
5186         // edge for landscape/seascape).
5187         boolean isStartShift;
5188         if (midpointIndex > -1) {
5189             // When there is a midpoint reference task, adjacent tasks have less distance to travel
5190             // to reach offscreen. Offset the task position to the task's starting point, and offset
5191             // by current page's scroll diff.
5192             int midpointScroll = getScrollForPage(midpointIndex)
5193                     + getPagedOrientationHandler().getPrimaryScroll(this)
5194                     - getScrollForPage(mCurrentPage);
5195 
5196             getPersistentChildPosition(midpointIndex, midpointScroll, taskPosition);
5197             float midpointStart = getPagedOrientationHandler().getStart(taskPosition);
5198 
5199             getPersistentChildPosition(childIndex, midpointScroll, taskPosition);
5200             // Assume child does not overlap with midPointChild.
5201             isStartShift = getPagedOrientationHandler().getStart(taskPosition) < midpointStart;
5202         } else {
5203             // Position the task at scroll position.
5204             getPersistentChildPosition(childIndex, getScrollForPage(childIndex), taskPosition);
5205             isStartShift = mIsRtl;
5206         }
5207 
5208         // Next, calculate the distance to move the task off screen. We also need to account for
5209         // RecentsView scale, because it moves tasks based on its pivot. To do this, we move the
5210         // task position to where it would be offscreen at scale = 1 (computed above), then we
5211         // apply the scale via getMatrix() to determine how much that moves the task from its
5212         // desired position, and adjust the computed distance accordingly.
5213         float distanceToOffscreen;
5214         if (isStartShift) {
5215             float desiredStart = -getPagedOrientationHandler().getPrimarySize(taskPosition);
5216             distanceToOffscreen = -getPagedOrientationHandler().getEnd(taskPosition);
5217             if (mLastComputedTaskStartPushOutDistance == null) {
5218                 taskPosition.offsetTo(
5219                         getPagedOrientationHandler().getPrimaryValue(desiredStart, 0f),
5220                         getPagedOrientationHandler().getSecondaryValue(desiredStart, 0f));
5221                 getMatrix().mapRect(taskPosition);
5222                 mLastComputedTaskStartPushOutDistance = getPagedOrientationHandler().getEnd(
5223                         taskPosition) / getPagedOrientationHandler().getPrimaryScale(this);
5224             }
5225             distanceToOffscreen -= mLastComputedTaskStartPushOutDistance;
5226         } else {
5227             float desiredStart = getPagedOrientationHandler().getPrimarySize(this);
5228             distanceToOffscreen = desiredStart - getPagedOrientationHandler().getStart(
5229                     taskPosition);
5230             if (mLastComputedTaskEndPushOutDistance == null) {
5231                 taskPosition.offsetTo(
5232                         getPagedOrientationHandler().getPrimaryValue(desiredStart, 0f),
5233                         getPagedOrientationHandler().getSecondaryValue(desiredStart, 0f));
5234                 getMatrix().mapRect(taskPosition);
5235                 mLastComputedTaskEndPushOutDistance = (getPagedOrientationHandler().getStart(
5236                         taskPosition) - desiredStart)
5237                         / getPagedOrientationHandler().getPrimaryScale(this);
5238             }
5239             distanceToOffscreen -= mLastComputedTaskEndPushOutDistance;
5240         }
5241         return distanceToOffscreen;
5242     }
5243 
5244     /**
5245      * Computes the vertical distance to offset a given child such that it is completely offscreen.
5246      *
5247      * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen.
5248      */
5249     private float getVerticalOffsetSize(TaskView taskView, float offsetProgress) {
5250         if (offsetProgress == 0 || !(showAsGrid() && enableGridOnlyOverview())
5251                 || getSelectedTaskView() == null) {
5252             // Don't bother calculating everything below if we won't offset vertically.
5253             return 0;
5254         }
5255 
5256         // First, get the position of the task relative to the top row.
5257         Rect taskPosition = getTaskBounds(taskView);
5258 
5259         boolean isSelectedTaskTopRow = mTopRowIdSet.contains(getSelectedTaskView().getTaskViewId());
5260         boolean isChildTopRow = mTopRowIdSet.contains(taskView.getTaskViewId());
5261         // Whether the task should be shifted to the top.
5262         boolean isTopShift = !isSelectedTaskTopRow && isChildTopRow;
5263         boolean isBottomShift = isSelectedTaskTopRow && !isChildTopRow;
5264 
5265         // Next, calculate the distance to move the task off screen at scale = 1.
5266         float distanceToOffscreen = 0;
5267         if (isTopShift) {
5268             distanceToOffscreen = -taskPosition.bottom;
5269         } else if (isBottomShift) {
5270             distanceToOffscreen = mContainer.getDeviceProfile().heightPx - taskPosition.top;
5271         }
5272         return distanceToOffscreen * offsetProgress;
5273     }
5274 
5275     protected void setTaskViewsResistanceTranslation(float translation) {
5276         mTaskViewsSecondaryTranslation = translation;
5277         for (TaskView taskView : getTaskViews()) {
5278             taskView.getTaskResistanceTranslationProperty().set(taskView,
5279                     translation / getScaleY());
5280         }
5281         runActionOnRemoteHandles(
5282                 remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
5283                         .recentsViewSecondaryTranslation.value = translation);
5284     }
5285 
5286     private void updateTaskViewsSnapshotRadius() {
5287         for (TaskView taskView : getTaskViews()) {
5288             taskView.updateFullscreenParams();
5289         }
5290     }
5291 
5292     protected void setTaskViewsPrimarySplitTranslation(float translation) {
5293         mTaskViewsPrimarySplitTranslation = translation;
5294         for (TaskView taskView : getTaskViews()) {
5295             taskView.getPrimarySplitTranslationProperty().set(taskView, translation);
5296         }
5297     }
5298 
5299     protected void setTaskViewsSecondarySplitTranslation(float translation) {
5300         mTaskViewsSecondarySplitTranslation = translation;
5301         for (TaskView taskView : getTaskViews()) {
5302             if (taskView == mSplitHiddenTaskView && !taskView.containsMultipleTasks()) {
5303                 continue;
5304             }
5305             taskView.getSecondarySplitTranslationProperty().set(taskView, translation);
5306         }
5307     }
5308 
5309     /**
5310      * Resets the visuals when exit modal state.
5311      */
5312     public void resetModalVisuals() {
5313         if (getSelectedTaskView() != null) {
5314             getSelectedTaskView().taskContainers.forEach(
5315                     taskContainer -> taskContainer.getOverlay().resetModalVisuals());
5316         }
5317     }
5318 
5319     /**
5320      * Primarily used by overview actions to initiate split from focused task, logs the source
5321      * of split invocation as such.
5322      */
5323     public void initiateSplitSelect(TaskContainer taskContainer) {
5324         int defaultSplitPosition = getPagedOrientationHandler()
5325                 .getDefaultSplitPosition(mContainer.getDeviceProfile());
5326         initiateSplitSelect(taskContainer, defaultSplitPosition, LAUNCHER_OVERVIEW_ACTIONS_SPLIT);
5327     }
5328 
5329     /** TODO(b/266477929): Consolidate this call w/ the one below */
5330     public void initiateSplitSelect(TaskContainer taskContainer,
5331             @StagePosition int stagePosition,
5332             StatsLogManager.EventEnum splitEvent) {
5333         TaskView taskView = taskContainer.getTaskView();
5334         mSplitHiddenTaskView = taskView;
5335         mSplitSelectStateController.setInitialTaskSelect(null /*intent*/, stagePosition,
5336                 taskContainer.getItemInfo(), splitEvent, taskContainer.getTask().key.id);
5337         mSplitSelectStateController.setAnimateCurrentTaskDismissal(
5338                 true /*animateCurrentTaskDismissal*/);
5339         mSplitHiddenTaskViewIndex = indexOfChild(taskView);
5340     }
5341 
5342     /**
5343      * Called when staging a split from Home/AllApps/Overview (Taskbar),
5344      * using the icon long-press menu.
5345      * Attempts to initiate split with an existing taskView, if one exists
5346      */
5347     public void initiateSplitSelect(SplitSelectSource splitSelectSource) {
5348         TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "enterSplitSelect");
5349         mSplitSelectSource = splitSelectSource;
5350         mSplitHiddenTaskView = getTaskViewByTaskId(splitSelectSource.alreadyRunningTaskId);
5351         mSplitHiddenTaskViewIndex = indexOfChild(mSplitHiddenTaskView);
5352         mSplitSelectStateController
5353                 .setAnimateCurrentTaskDismissal(splitSelectSource.animateCurrentTaskDismissal
5354                         && mSplitHiddenTaskView != null
5355                         && !(mSplitHiddenTaskView instanceof DesktopTaskView));
5356 
5357         // Prevent dismissing whole task if we're only initiating from one of 2 tasks in split pair
5358         mSplitSelectStateController.setDismissingFromSplitPair(mSplitHiddenTaskView != null
5359                 && mSplitHiddenTaskView instanceof GroupedTaskView);
5360         mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
5361                 splitSelectSource.position.stagePosition, splitSelectSource.getItemInfo(),
5362                 splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTaskId);
5363     }
5364 
5365     /**
5366      * Animate DesktopTaskView(s) to hide in split select
5367      */
5368     public void handleDesktopTaskInSplitSelectState(PendingAnimation builder,
5369             Interpolator deskTopFadeInterPolator) {
5370         SplitAnimationTimings timings = AnimUtils.getDeviceOverviewToSplitTimings(
5371                 mContainer.getDeviceProfile().isTablet);
5372         if (enableLargeDesktopWindowingTile()) {
5373             getTaskViews().forEachWithIndexInParent((index, taskView) -> {
5374                 if (taskView instanceof DesktopTaskView) {
5375                     // Setting pivot to scale down from screen centre.
5376                     if (isTaskViewVisible(taskView)) {
5377                         float pivotX = 0f;
5378                         if (index < mCurrentPage) {
5379                             pivotX = mIsRtl ? taskView.getWidth() / 2f - mPageSpacing
5380                                     - taskView.getWidth()
5381                                     : taskView.getWidth() / 2f + mPageSpacing + taskView.getWidth();
5382                         } else if (index == mCurrentPage) {
5383                             pivotX = taskView.getWidth() / 2f;
5384                         } else {
5385                             pivotX = mIsRtl ? taskView.getWidth() + mPageSpacing
5386                                     + taskView.getWidth()
5387                                     : taskView.getWidth() - mPageSpacing - taskView.getWidth();
5388                         }
5389                         taskView.setPivotX(pivotX);
5390                         taskView.setPivotY(taskView.getHeight() / 2f);
5391                         builder.add(ObjectAnimator
5392                                         .ofFloat(taskView, TaskView.DISMISS_SCALE, 0.95f),
5393                                 clampToProgress(timings.getDesktopTaskScaleInterpolator(), 0f,
5394                                         timings.getDesktopFadeSplitAnimationEndOffset()));
5395                     }
5396                     builder.addFloat(taskView, SPLIT_ALPHA, 1f, 0f,
5397                             clampToProgress(deskTopFadeInterPolator, 0f,
5398                                     timings.getDesktopFadeSplitAnimationEndOffset()));
5399                 }
5400             });
5401         }
5402     }
5403 
5404     /**
5405      * While exiting from split mode, show all existing DesktopTaskViews.
5406      */
5407     public void resetDesktopTaskFromSplitSelectState() {
5408         if (enableLargeDesktopWindowingTile()) {
5409             for (TaskView taskView : getTaskViews()) {
5410                 if (taskView instanceof DesktopTaskView) {
5411                     taskView.setSplitAlpha(1f);
5412                 }
5413             }
5414         }
5415     }
5416 
5417     /**
5418      * Modifies a PendingAnimation with the animations for entering split staging
5419      */
5420     public void createSplitSelectInitAnimation(PendingAnimation builder, int duration) {
5421         boolean isInitiatingSplitFromTaskView =
5422                 mSplitSelectStateController.isAnimateCurrentTaskDismissal();
5423         boolean isInitiatingTaskViewSplitPair =
5424                 mSplitSelectStateController.isDismissingFromSplitPair();
5425         if (isInitiatingSplitFromTaskView && isInitiatingTaskViewSplitPair
5426                 && mSplitHiddenTaskView instanceof GroupedTaskView groupedTaskView) {
5427             // Splitting from Overview for split pair task
5428             createInitialSplitSelectAnimation(builder);
5429 
5430             // Animate pair thumbnail into full thumbnail
5431             boolean primaryTaskSelected = groupedTaskView.getLeftTopTaskContainer().getTask().key.id
5432                     == mSplitSelectStateController.getInitialTaskId();
5433             TaskContainer taskContainer =
5434                     primaryTaskSelected ? groupedTaskView.getRightBottomTaskContainer()
5435                             : groupedTaskView.getLeftTopTaskContainer();
5436             mSplitSelectStateController.getSplitAnimationController()
5437                     .addInitialSplitFromPair(taskContainer, builder,
5438                             mContainer.getDeviceProfile(),
5439                             mSplitHiddenTaskView.getLayoutParams().width,
5440                             mSplitHiddenTaskView.getLayoutParams().height,
5441                             primaryTaskSelected);
5442             builder.addOnFrameCallback(() -> {
5443                 if (!enableRefactorTaskThumbnail()) {
5444                     taskContainer.getThumbnailViewDeprecated().refreshSplashView();
5445                 }
5446                 mSplitHiddenTaskView.updateFullscreenParams();
5447             });
5448         } else if (isInitiatingSplitFromTaskView) {
5449             if (Flags.enableHoverOfChildElementsInTaskview()) {
5450                 mSplitHiddenTaskView.setBorderEnabled(false);
5451             }
5452             // Splitting from Overview for fullscreen task
5453             createTaskDismissAnimation(builder, mSplitHiddenTaskView, true, false, duration,
5454                     true /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */);
5455         } else {
5456             // Splitting from Home
5457             TaskView currentPageTaskView = getTaskViewAt(mCurrentPage);
5458             // When current page is a Desktop task it needs special handling to
5459             // display correct animation in split mode
5460             if (currentPageTaskView instanceof DesktopTaskView) {
5461                 createTaskDismissAnimation(builder, null, true, false, duration,
5462                         true /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */);
5463             } else {
5464                 createInitialSplitSelectAnimation(builder);
5465             }
5466         }
5467     }
5468 
5469     /**
5470      * Confirms the selection of the next split task. The extra data is passed through because the
5471      * user may be selecting a subtask in a group.
5472      *
5473      * @param containerTaskView If our second selected app is currently running in Recents, this is
5474      *                          the "container" TaskView from Recents. If we are starting a fresh
5475      *                          instance of the app from an Intent, this will be null.
5476      * @param task              The Task corresponding to our second selected app. If we are
5477      *                          starting a fresh
5478      *                          instance of the app from an Intent, this will be null.
5479      * @param drawable          The Drawable corresponding to our second selected app's icon.
5480      * @param secondView        The View representing the current space on the screen where the
5481      *                          second app
5482      *                          is (either the ThumbnailView or the tapped icon).
5483      * @param intent            If we are launching a fresh instance of the app, this is the Intent
5484      *                          for it. If
5485      *                          the second app is already running in Recents, this will be null.
5486      * @param user              If we are launching a fresh instance of the app, this is the
5487      *                          UserHandle for it.
5488      *                          If the second app is already running in Recents, this will be null.
5489      * @return true if waiting for confirmation of second app or if split animations are running,
5490      * false otherwise
5491      */
5492     public boolean confirmSplitSelect(TaskView containerTaskView, Task task, Drawable drawable,
5493             View secondView, @Nullable Bitmap thumbnail, Intent intent, UserHandle user,
5494             ItemInfo itemInfo) {
5495         if (canLaunchFullscreenTask()) {
5496             return false;
5497         }
5498         if (mSplitSelectStateController.isBothSplitAppsConfirmed()) {
5499             Log.w(TAG, splitFailureMessage(
5500                     "confirmSplitSelect", "both apps have already been set"));
5501             return true;
5502         }
5503         // Second task is selected either as an already-running Task or an Intent
5504         if (task != null) {
5505             if (!task.isDockable) {
5506                 // Task does not support split screen
5507                 mSplitUnsupportedToast.show();
5508                 Log.w(TAG, splitFailureMessage("confirmSplitSelect",
5509                         "selected Task (" + task.key.getPackageName()
5510                                 + ") is not dockable / does not support splitscreen"));
5511                 return true;
5512             }
5513             mSplitSelectStateController.setSecondTask(task, itemInfo);
5514         } else {
5515             mSplitSelectStateController.setSecondTask(intent, user, itemInfo);
5516         }
5517 
5518         RectF secondTaskStartingBounds = new RectF();
5519         Rect secondTaskEndingBounds = new Rect();
5520         // TODO(194414938) starting bounds seem slightly off, investigate
5521         Rect firstTaskStartingBounds = new Rect();
5522         Rect firstTaskEndingBounds = mTempRect;
5523 
5524         boolean isTablet = mContainer.getDeviceProfile().isTablet;
5525         SplitAnimationTimings timings = AnimUtils.getDeviceSplitToConfirmTimings(isTablet);
5526         PendingAnimation pendingAnimation = new PendingAnimation(timings.getDuration());
5527 
5528         int halfDividerSize = getResources()
5529                 .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
5530         getPagedOrientationHandler().getFinalSplitPlaceholderBounds(halfDividerSize,
5531                 mContainer.getDeviceProfile(),
5532                 mSplitSelectStateController.getActiveSplitStagePosition(), firstTaskEndingBounds,
5533                 secondTaskEndingBounds);
5534 
5535         mSplitScrim = mSplitSelectStateController.getSplitAnimationController()
5536                 .addScrimBehindAnim(pendingAnimation, mContainer, getContext());
5537         FloatingTaskView firstFloatingTaskView =
5538                 mSplitSelectStateController.getFirstFloatingTaskView();
5539         firstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
5540         firstFloatingTaskView.addConfirmAnimation(pendingAnimation,
5541                 new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
5542                 false /* fadeWithThumbnail */, true /* isStagedTask */);
5543 
5544         safeRemoveDragLayerView(mSecondFloatingTaskView);
5545 
5546         mSecondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mContainer, secondView,
5547                 thumbnail, drawable, secondTaskStartingBounds);
5548         mSecondFloatingTaskView.setAlpha(1);
5549         mSecondFloatingTaskView.addConfirmAnimation(pendingAnimation, secondTaskStartingBounds,
5550                 secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isStagedTask */);
5551 
5552         pendingAnimation.setViewAlpha(mSplitSelectStateController.getSplitInstructionsView(), 0,
5553                 clampToProgress(LINEAR, timings.getInstructionsFadeStartOffset(),
5554                         timings.getInstructionsFadeEndOffset()));
5555 
5556         pendingAnimation.addEndListener(aBoolean -> {
5557             mSplitSelectStateController.launchSplitTasks(
5558                     aBoolean1 -> {
5559                         InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER);
5560                         mSplitSelectStateController.resetState();
5561                     });
5562         });
5563 
5564         mSecondSplitHiddenView = containerTaskView;
5565         if (mSecondSplitHiddenView != null) {
5566             mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE,
5567                     mSplitSelectStateController.getSecondTaskId());
5568         }
5569 
5570         InteractionJankMonitorWrapper.begin(this, Cuj.CUJ_SPLIT_SCREEN_ENTER,
5571                 "Second tile selected");
5572 
5573         // Fade out all other views underneath placeholders
5574         ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA, 1, 0);
5575         pendingAnimation.add(tvFade, DECELERATE_2, SpringProperty.DEFAULT);
5576         pendingAnimation.buildAnim().start();
5577         return true;
5578     }
5579 
5580     @SuppressLint("WrongCall")
5581     protected void resetFromSplitSelectionState() {
5582         safeRemoveDragLayerView(mSplitSelectStateController.getFirstFloatingTaskView());
5583         safeRemoveDragLayerView(mSecondFloatingTaskView);
5584         safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView());
5585         safeRemoveDragLayerView(mSplitScrim);
5586         mSecondFloatingTaskView = null;
5587         mSplitSelectSource = null;
5588         mSplitSelectStateController.getSplitAnimationController()
5589                 .removeSplitInstructionsView(mContainer);
5590 
5591         if (mSecondSplitHiddenView != null) {
5592             mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
5593             mSecondSplitHiddenView = null;
5594         }
5595 
5596         // We are leaving split selection state, so it is safe to reset thumbnail translations for
5597         // the next time split is invoked.
5598         setTaskViewsPrimarySplitTranslation(0);
5599         setTaskViewsSecondarySplitTranslation(0);
5600 
5601         if (mSplitHiddenTaskViewIndex == -1) {
5602             return;
5603         }
5604         if (!mContainer.getDeviceProfile().isTablet) {
5605             int pageToSnapTo = mCurrentPage;
5606             if (mSplitHiddenTaskViewIndex <= pageToSnapTo) {
5607                 pageToSnapTo += 1;
5608             } else {
5609                 pageToSnapTo = mSplitHiddenTaskViewIndex;
5610             }
5611             snapToPageImmediately(pageToSnapTo);
5612         }
5613         onLayout(false /*  changed */, getLeft(), getTop(), getRight(), getBottom());
5614 
5615         resetTaskVisuals();
5616         mSplitHiddenTaskViewIndex = -1;
5617         if (mSplitHiddenTaskView != null) {
5618             mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
5619             // mSplitHiddenTaskView is set when split select animation starts. The TaskView is only
5620             // removed when when the animation finishes. So in the case of overview being dismissed
5621             // during the animation, we should not call clearAndRecycleTaskView() because it has
5622             // not been removed yet.
5623             if (mSplitHiddenTaskView.getParent() == null) {
5624                 clearAndRecycleTaskView(mSplitHiddenTaskView);
5625             }
5626             mSplitHiddenTaskView = null;
5627         }
5628     }
5629 
5630     private void safeRemoveDragLayerView(@Nullable View viewToRemove) {
5631         if (viewToRemove != null) {
5632             mContainer.getDragLayer().removeView(viewToRemove);
5633         }
5634     }
5635 
5636     /**
5637      * Returns how much additional translation there should be for each of the child TaskViews.
5638      * Note that the translation can be its primary or secondary dimension.
5639      */
5640     public float getSplitSelectTranslation() {
5641         DeviceProfile deviceProfile = mContainer.getDeviceProfile();
5642         RecentsPagedOrientationHandler orientationHandler = getPagedOrientationHandler();
5643         int splitPosition = getSplitSelectController().getActiveSplitStagePosition();
5644         int splitPlaceholderSize =
5645                 mContainer.getResources().getDimensionPixelSize(R.dimen.split_placeholder_size);
5646         int direction = orientationHandler.getSplitTranslationDirectionFactor(
5647                 splitPosition, deviceProfile);
5648 
5649         if (deviceProfile.isTablet && deviceProfile.isLeftRightSplit) {
5650             // Only shift TaskViews if there is not enough space on the side of
5651             // mLastComputedTaskSize to minimize motion.
5652             int sideSpace = mIsRtl
5653                     ? deviceProfile.widthPx - mLastComputedTaskSize.right
5654                     : mLastComputedTaskSize.left;
5655             int extraSpace = splitPlaceholderSize + mPageSpacing - sideSpace;
5656             if (extraSpace <= 0f) {
5657                 return 0f;
5658             }
5659 
5660             return extraSpace * direction;
5661         }
5662 
5663         return splitPlaceholderSize * direction;
5664     }
5665 
5666     protected void onRotateInSplitSelectionState() {
5667         getPagedOrientationHandler().getInitialSplitPlaceholderBounds(mSplitPlaceholderSize,
5668                 mSplitPlaceholderInset, mContainer.getDeviceProfile(),
5669                 mSplitSelectStateController.getActiveSplitStagePosition(), mTempRect);
5670         mTempRectF.set(mTempRect);
5671         FloatingTaskView firstFloatingTaskView =
5672                 mSplitSelectStateController.getFirstFloatingTaskView();
5673         firstFloatingTaskView.updateOrientationHandler(getPagedOrientationHandler());
5674         firstFloatingTaskView.update(mTempRectF, /*progress=*/1f);
5675 
5676         RecentsPagedOrientationHandler orientationHandler = getPagedOrientationHandler();
5677         Pair<FloatProperty<RecentsView<?, ?>>, FloatProperty<RecentsView<?, ?>>> taskViewsFloat =
5678                 orientationHandler.getSplitSelectTaskOffset(
5679                         TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
5680                         mContainer.getDeviceProfile());
5681         taskViewsFloat.first.set(this, getSplitSelectTranslation());
5682         taskViewsFloat.second.set(this, 0f);
5683 
5684         if (mSplitSelectStateController.getSplitInstructionsView() != null) {
5685             mSplitSelectStateController.getSplitInstructionsView().ensureProperRotation();
5686         }
5687     }
5688 
5689     private void updateDeadZoneRects() {
5690         // Get the deadzone rect surrounding the clear all button to not dismiss overview to home
5691         mClearAllButtonDeadZoneRect.setEmpty();
5692         if (mClearAllButton.getWidth() > 0) {
5693             int verticalMargin = getResources()
5694                     .getDimensionPixelSize(R.dimen.recents_clear_all_deadzone_vertical_margin);
5695             mClearAllButton.getHitRect(mClearAllButtonDeadZoneRect);
5696             mClearAllButtonDeadZoneRect.inset(-getPaddingRight() / 2, -verticalMargin);
5697         }
5698 
5699         mUtils.updateTaskViewDeadZoneRect(mTaskViewDeadZoneRect, mTopRowDeadZoneRect,
5700                 mBottomRowDeadZoneRect);
5701     }
5702 
5703     private void updateEmptyStateUi(boolean sizeChanged) {
5704         boolean hasValidSize = getWidth() > 0 && getHeight() > 0;
5705         if (sizeChanged && hasValidSize) {
5706             mEmptyTextLayout = null;
5707             mLastMeasureSize.set(getWidth(), getHeight());
5708         }
5709 
5710         if (mShowEmptyMessage && hasValidSize && mEmptyTextLayout == null) {
5711             int availableWidth = mLastMeasureSize.x - mEmptyMessagePadding - mEmptyMessagePadding;
5712             mEmptyTextLayout = StaticLayout.Builder.obtain(mEmptyMessage, 0, mEmptyMessage.length(),
5713                             mEmptyMessagePaint, availableWidth)
5714                     .setAlignment(Layout.Alignment.ALIGN_CENTER)
5715                     .build();
5716             int totalHeight = mEmptyTextLayout.getHeight()
5717                     + mEmptyMessagePadding + mEmptyIcon.getIntrinsicHeight();
5718 
5719             int top = (mLastMeasureSize.y - totalHeight) / 2;
5720             int left = (mLastMeasureSize.x - mEmptyIcon.getIntrinsicWidth()) / 2;
5721             mEmptyIcon.setBounds(left, top, left + mEmptyIcon.getIntrinsicWidth(),
5722                     top + mEmptyIcon.getIntrinsicHeight());
5723         }
5724     }
5725 
5726     @Override
verifyDrawable(Drawable who)5727     protected boolean verifyDrawable(Drawable who) {
5728         return super.verifyDrawable(who) || (mShowEmptyMessage && who == mEmptyIcon);
5729     }
5730 
maybeDrawEmptyMessage(Canvas canvas)5731     protected void maybeDrawEmptyMessage(Canvas canvas) {
5732         if (mShowEmptyMessage && mEmptyTextLayout != null) {
5733             // Offsets icon and text up so that the vertical center of screen (accounting for
5734             // insets) is between icon and text.
5735             int offset = (mEmptyIcon.getIntrinsicHeight() + mEmptyMessagePadding) / 2;
5736 
5737             canvas.save();
5738             canvas.translate(getScrollX() + (mInsets.left - mInsets.right) / 2f,
5739                     (mInsets.top - mInsets.bottom) / 2f - offset);
5740             mEmptyIcon.draw(canvas);
5741             canvas.translate(mEmptyMessagePadding,
5742                     mEmptyIcon.getBounds().bottom + mEmptyMessagePadding);
5743             mEmptyTextLayout.draw(canvas);
5744             canvas.restore();
5745         }
5746     }
5747 
5748     /**
5749      * Animate adjacent tasks off screen while scaling up.
5750      *
5751      * If launching one of the adjacent tasks, parallax the center task and other adjacent task
5752      * to the right.
5753      */
5754     @SuppressLint("Recycle")
createAdjacentPageAnimForTaskLaunch(TaskView taskView)5755     public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView taskView) {
5756         AnimatorSet anim = new AnimatorSet();
5757 
5758         int taskIndex = indexOfChild(taskView);
5759         int centerTaskIndex = getCurrentPage();
5760 
5761         float toScale = getMaxScaleForFullScreen();
5762         boolean showAsGrid = showAsGrid();
5763         boolean zoomInTaskView = showAsGrid ? taskView.isLargeTile() : taskIndex == centerTaskIndex;
5764         if (zoomInTaskView) {
5765             anim.play(ObjectAnimator.ofFloat(this, RECENTS_SCALE_PROPERTY, toScale));
5766             anim.play(ObjectAnimator.ofFloat(this, FULLSCREEN_PROGRESS, 1));
5767             anim.addListener(new AnimatorListenerAdapter() {
5768                 @Override
5769                 public void onAnimationStart(@NonNull Animator animation) {
5770                     taskView.getThumbnailBounds(mTempRect, /*relativeToDragLayer=*/true);
5771                     getTaskDimension(mContext, mContainer.getDeviceProfile(), mTempPointF);
5772                     Rect fullscreenBounds = new Rect(0, 0, (int) mTempPointF.x,
5773                             (int) mTempPointF.y);
5774                     Utilities.getPivotsForScalingRectToRect(mTempRect, fullscreenBounds,
5775                             mTempPointF);
5776                     setPivotX(mTempPointF.x);
5777                     setPivotY(mTempPointF.y);
5778 
5779                     // If live tile is not launching, apply pivot to live tile as well and bring it
5780                     // above RecentsView to avoid wallpaper blur from being applied to it.
5781                     if (!taskView.isRunningTask()) {
5782                         runActionOnRemoteHandles(
5783                                 remoteTargetHandle ->
5784                                         remoteTargetHandle.getTaskViewSimulator()
5785                                                 .setPivotOverride(mTempPointF));
5786                         mBlurUtils.setDrawLiveTileBelowRecents(false);
5787                     }
5788                 }
5789 
5790                 @Override
5791                 public void onAnimationEnd(Animator animation) {
5792                     // If live tile is not launching, reset the pivot applied above.
5793                     if (!taskView.isRunningTask()) {
5794                         runActionOnRemoteHandles(
5795                                 remoteTargetHandle -> {
5796                                     remoteTargetHandle.getTaskViewSimulator().setPivotOverride(
5797                                             null);
5798                                 });
5799                     }
5800                 }
5801             });
5802         } else if (!showAsGrid) {
5803             // We are launching an adjacent task, so parallax the center and other adjacent task.
5804             float displacementX = taskView.getWidth() * (toScale - 1f);
5805             float primaryTranslation = mIsRtl ? -displacementX : displacementX;
5806             anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex),
5807                     getPagedOrientationHandler().getPrimaryViewTranslate(), primaryTranslation));
5808             int runningTaskIndex = getRunningTaskIndex();
5809             if (runningTaskIndex != -1 && runningTaskIndex != taskIndex
5810                     && getRemoteTargetHandles() != null) {
5811                 for (RemoteTargetHandle remoteHandle : getRemoteTargetHandles()) {
5812                     anim.play(ObjectAnimator.ofFloat(
5813                             remoteHandle.getTaskViewSimulator().taskPrimaryTranslation,
5814                             AnimatedFloat.VALUE,
5815                             primaryTranslation));
5816                 }
5817             }
5818 
5819             int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - taskIndex);
5820             if (otherAdjacentTaskIndex >= 0 && otherAdjacentTaskIndex < getPageCount()) {
5821                 PropertyValuesHolder[] properties = new PropertyValuesHolder[3];
5822                 properties[0] = PropertyValuesHolder.ofFloat(
5823                         getPagedOrientationHandler().getPrimaryViewTranslate(), primaryTranslation);
5824                 properties[1] = PropertyValuesHolder.ofFloat(View.SCALE_X, 1);
5825                 properties[2] = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1);
5826 
5827                 anim.play(ObjectAnimator.ofPropertyValuesHolder(getPageAt(otherAdjacentTaskIndex),
5828                         properties));
5829             }
5830         }
5831         anim.play(ObjectAnimator.ofFloat(this, TASK_THUMBNAIL_SPLASH_ALPHA, 0, 1));
5832         if (taskView instanceof DesktopTaskView) {
5833             anim.play(ObjectAnimator.ofArgb(mContainer.getScrimView(), VIEW_BACKGROUND_COLOR,
5834                     Color.TRANSPARENT));
5835             if (enableDesktopExplodedView()) {
5836                 anim.play(ObjectAnimator.ofFloat(this, DESK_EXPLODE_PROGRESS, 1f, 0f));
5837             }
5838         }
5839         DepthController depthController = getDepthController();
5840         if (depthController != null) {
5841             float targetDepth = taskView instanceof DesktopTaskView ? 0 : BACKGROUND_APP.getDepth(
5842                     mContainer);
5843             anim.play(ObjectAnimator.ofFloat(depthController.stateDepth, MULTI_PROPERTY_VALUE,
5844                     targetDepth));
5845         }
5846         return anim;
5847     }
5848 
5849     /**
5850      * Returns the scale up required on the view, so that it coves the screen completely
5851      */
getMaxScaleForFullScreen()5852     public float getMaxScaleForFullScreen() {
5853         if (mLastComputedTaskSize.isEmpty()) {
5854             getTaskSize(mLastComputedTaskSize);
5855         }
5856         mTempRect.set(mLastComputedTaskSize);
5857         return getPagedViewOrientedState().getFullScreenScaleAndPivot(
5858                 mTempRect, mContainer.getDeviceProfile(), mTempPointF);
5859     }
5860 
5861     /**
5862      * Clears the existing PendingAnimation.
5863      */
clearPendingAnimation()5864     public void clearPendingAnimation() {
5865         mPendingAnimation = null;
5866     }
5867 
createTaskLaunchAnimation( TaskView taskView, long duration, Interpolator interpolator)5868     public PendingAnimation createTaskLaunchAnimation(
5869             TaskView taskView, long duration, Interpolator interpolator) {
5870         if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
5871             throw new IllegalStateException("Another pending animation is still running");
5872         }
5873 
5874         if (!hasTaskViews()) {
5875             return new PendingAnimation(duration);
5876         }
5877 
5878         // When swiping down from overview to tasks, ensures the snapped page's scroll maintain
5879         // invariant between quick switch and overview, to ensure a smooth animation transition.
5880         updateGridProperties();
5881         updateScrollSynchronously();
5882 
5883         int targetSysUiFlags = taskView.getSysUiStatusNavFlags();
5884         final boolean[] passedOverviewThreshold = new boolean[]{false};
5885         AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(taskView);
5886         anim.play(new AnimatedFloat(v -> {
5887             // Once we pass a certain threshold, update the sysui flags to match the target
5888             // tasks' flags
5889             if (v > UPDATE_SYSUI_FLAGS_THRESHOLD) {
5890                 mContainer.getSystemUiController().updateUiState(
5891                         UI_STATE_FULLSCREEN_TASK, targetSysUiFlags);
5892             } else {
5893                 mContainer.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, 0);
5894             }
5895 
5896             // Passing the threshold from taskview to fullscreen app will vibrate
5897             final boolean passed = v >= SUCCESS_TRANSITION_PROGRESS;
5898             if (passed != passedOverviewThreshold[0]) {
5899                 passedOverviewThreshold[0] = passed;
5900                 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
5901                         HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
5902                 // Also update recents animation controller state if it is ongoing.
5903                 if (mRecentsAnimationController != null) {
5904                     mRecentsAnimationController.setWillFinishToHome(!passed);
5905                 }
5906             }
5907         }).animateToValue(0f, 1f));
5908         anim.setInterpolator(interpolator);
5909 
5910         mPendingAnimation = new PendingAnimation(duration);
5911         mPendingAnimation.add(anim);
5912         if (taskView.isRunningTask()) {
5913             runActionOnRemoteHandles(
5914                     remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
5915                             .addOverviewToAppAnim(mPendingAnimation, interpolator));
5916             mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
5917         }
5918         mPendingAnimation.addListener(new AnimatorListenerAdapter() {
5919             @Override
5920             public void onAnimationStart(Animator animation) {
5921                 mBlurUtils.setDrawLiveTileBelowRecents(false);
5922             }
5923         });
5924         mPendingAnimation.addEndListener(isSuccess -> {
5925             if (isSuccess) {
5926                 if (taskView instanceof GroupedTaskView && hasAllValidTaskIds(taskView.getTaskIds())
5927                         && mRemoteTargetHandles != null) {
5928                     // TODO(b/194414938): make this part of the animations instead.
5929                     TaskViewUtils.createSplitAuxiliarySurfacesAnimator(
5930                             mRemoteTargetHandles[0].getTransformParams().getTargetSet().nonApps,
5931                             true /*shown*/, (dividerAnimator) -> {
5932                                 dividerAnimator.start();
5933                                 dividerAnimator.end();
5934                             });
5935                 }
5936                 if (taskView.isRunningTask()) {
5937                     finishRecentsAnimation(false /* toRecents */, null);
5938                     onTaskLaunchAnimationEnd(true /* success */);
5939                 } else {
5940                     taskView.launchWithoutAnimation(this::onTaskLaunchAnimationEnd);
5941                 }
5942                 mContainer.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo())
5943                         .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
5944             } else {
5945                 onTaskLaunchAnimationEnd(false);
5946             }
5947             mPendingAnimation = null;
5948         });
5949         return mPendingAnimation;
5950     }
5951 
onTaskLaunchAnimationEnd(boolean success)5952     protected Unit onTaskLaunchAnimationEnd(boolean success) {
5953         if (success) {
5954             resetTaskVisuals();
5955         } else {
5956             // If launch animation didn't complete i.e. user dragged live tile down and then
5957             // back up and returned to Overview, then we need to ensure we reset the
5958             // view to draw below recents so that it can't be interacted with.
5959             mBlurUtils.setDrawLiveTileBelowRecents(true);
5960             redrawLiveTile();
5961         }
5962         return Unit.INSTANCE;
5963     }
5964 
5965     @Override
notifyPageSwitchListener(int prevPage)5966     protected void notifyPageSwitchListener(int prevPage) {
5967         super.notifyPageSwitchListener(prevPage);
5968         updateCurrentTaskActionsVisibility();
5969         loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
5970         updateEnabledOverlays();
5971         if (enableRefactorTaskThumbnail()) {
5972             mUtils.updateCentralTask();
5973         }
5974     }
5975 
5976     @Override
getCurrentPageDescription()5977     protected String getCurrentPageDescription() {
5978         return "";
5979     }
5980 
5981     @Override
addChildrenForAccessibility(ArrayList<View> outChildren)5982     public void addChildrenForAccessibility(ArrayList<View> outChildren) {
5983         outChildren.addAll(getAccessibilityChildren());
5984     }
5985 
getAccessibilityChildren()5986     public List<View> getAccessibilityChildren() {
5987         return mUtils.getAccessibilityChildren();
5988     }
5989 
5990     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)5991     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
5992         super.onInitializeAccessibilityNodeInfo(info);
5993         final AccessibilityNodeInfo.CollectionInfo
5994                 collectionInfo = new AccessibilityNodeInfo.CollectionInfo(
5995                 1, getAccessibilityChildren().size(), false);
5996         info.setCollectionInfo(collectionInfo);
5997     }
5998 
5999     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)6000     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
6001         super.onInitializeAccessibilityEvent(event);
6002         event.setScrollable(hasTaskViews());
6003 
6004         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
6005             final List<View> accessibilityChildren = getAccessibilityChildren();
6006             final int[] visibleTasks = getVisibleChildrenRange();
6007             event.setFromIndex(accessibilityChildren.indexOf(getChildAt(visibleTasks[1])));
6008             event.setToIndex(accessibilityChildren.indexOf(getChildAt(visibleTasks[0])));
6009             event.setItemCount(accessibilityChildren.size());
6010         }
6011     }
6012 
6013     @Override
getAccessibilityClassName()6014     public CharSequence getAccessibilityClassName() {
6015         // To hear position-in-list related feedback from Talkback.
6016         return ListView.class.getName();
6017     }
6018 
6019     @Override
isPageOrderFlipped()6020     protected boolean isPageOrderFlipped() {
6021         return true;
6022     }
6023 
setEnableDrawingLiveTile(boolean enableDrawingLiveTile)6024     public void setEnableDrawingLiveTile(boolean enableDrawingLiveTile) {
6025         mEnableDrawingLiveTile = enableDrawingLiveTile;
6026     }
6027 
getEnableDrawingLiveTile()6028     public boolean getEnableDrawingLiveTile() {
6029         return mEnableDrawingLiveTile;
6030     }
6031 
redrawLiveTile()6032     public void redrawLiveTile() {
6033         runActionOnRemoteHandles(remoteTargetHandle -> {
6034             TransformParams params = remoteTargetHandle.getTransformParams();
6035             if (params.getTargetSet() != null) {
6036                 remoteTargetHandle.getTaskViewSimulator().apply(params);
6037             }
6038         });
6039     }
6040 
6041     @Nullable
getRemoteTargetHandles()6042     public RemoteTargetHandle[] getRemoteTargetHandles() {
6043         return mRemoteTargetHandles;
6044     }
6045 
6046     // TODO: To be removed in a follow up CL
setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController, RecentsAnimationTargets recentsAnimationTargets)6047     public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController,
6048             RecentsAnimationTargets recentsAnimationTargets) {
6049         Log.d(TAG, "setRecentsAnimationTargets "
6050                 + "- recentsAnimationController: " + recentsAnimationController
6051                 + ", recentsAnimationTargets: " + recentsAnimationTargets);
6052         mRecentsAnimationController = recentsAnimationController;
6053         mSplitSelectStateController.setRecentsAnimationRunning(true);
6054         if (recentsAnimationTargets == null || recentsAnimationTargets.apps.length == 0) {
6055             return;
6056         }
6057 
6058         RemoteTargetGluer gluer;
6059         if (recentsAnimationTargets.hasDesktopTasks(mContext)) {
6060             gluer = new RemoteTargetGluer(getContext(), getSizeStrategy(), recentsAnimationTargets,
6061                     true /* forDesktop */);
6062             mRemoteTargetHandles = gluer.assignTargetsForDesktop(
6063                     recentsAnimationTargets, /* transitionInfo= */ null);
6064         } else {
6065             gluer = new RemoteTargetGluer(getContext(), getSizeStrategy(), recentsAnimationTargets,
6066                     false);
6067             mRemoteTargetHandles = gluer.assignTargetsForSplitScreen(recentsAnimationTargets);
6068         }
6069         mSplitBoundsConfig = gluer.getSplitBounds();
6070         // Add release check to the targets from the RemoteTargetGluer and not the targets
6071         // passed in because in the event we're in split screen, we use the passed in targets
6072         // to create new RemoteAnimationTargets in assignTargetsForSplitScreen(), and the
6073         // mSyncTransactionApplier doesn't get transferred over
6074         runActionOnRemoteHandles(remoteTargetHandle -> {
6075             final TransformParams params = remoteTargetHandle.getTransformParams();
6076             if (RecentsWindowFlags.Companion.getEnableOverviewInWindow()) {
6077                 params.setHomeBuilderProxy((builder, app, transformParams) -> {
6078                     mTmpMatrix.setScale(
6079                             1f, 1f, app.localBounds.exactCenterX(), app.localBounds.exactCenterY());
6080                     builder.setMatrix(mTmpMatrix).setAlpha(1f).setShow();
6081                 });
6082             }
6083 
6084             if (mSyncTransactionApplier != null) {
6085                 params.setSyncTransactionApplier(mSyncTransactionApplier);
6086                 params.getTargetSet().addReleaseCheck(mSyncTransactionApplier);
6087             }
6088 
6089             TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator();
6090             tvs.setOrientationState(mOrientationState);
6091             tvs.setDp(mContainer.getDeviceProfile());
6092             tvs.recentsViewScale.value = 1;
6093         });
6094 
6095         TaskView runningTaskView = getRunningTaskView();
6096         if (runningTaskView instanceof GroupedTaskView) {
6097             // We initially create a GroupedTaskView in showCurrentTask() before launcher even
6098             // receives the leashes for the remote apps, so the mSplitBoundsConfig that gets passed
6099             // in there is either null or outdated, so we need to update here as soon as we're
6100             // notified.
6101             ((GroupedTaskView) runningTaskView).updateSplitBoundsConfig(mSplitBoundsConfig);
6102         }
6103     }
6104 
6105     /** Helper to avoid writing some for-loops to iterate over {@link #mRemoteTargetHandles} */
runActionOnRemoteHandles(Consumer<RemoteTargetHandle> consumer)6106     public void runActionOnRemoteHandles(Consumer<RemoteTargetHandle> consumer) {
6107         if (mRemoteTargetHandles == null) {
6108             return;
6109         }
6110 
6111         for (RemoteTargetHandle handle : mRemoteTargetHandles) {
6112             consumer.accept(handle);
6113         }
6114     }
6115 
6116     /**
6117      * Finish recents animation.
6118      */
finishRecentsAnimation(boolean toRecents, @Nullable Runnable onFinishComplete)6119     public void finishRecentsAnimation(boolean toRecents, @Nullable Runnable onFinishComplete) {
6120         finishRecentsAnimation(toRecents, true /* shouldPip */, onFinishComplete);
6121     }
6122 
6123     /**
6124      * Finish recents animation.
6125      */
finishRecentsAnimation(boolean toRecents, boolean shouldPip, @Nullable Runnable onFinishComplete)6126     public void finishRecentsAnimation(boolean toRecents, boolean shouldPip,
6127             @Nullable Runnable onFinishComplete) {
6128         finishRecentsAnimation(toRecents, shouldPip, false, onFinishComplete);
6129     }
6130     /**
6131      * NOTE: Whatever value gets passed through to the toRecents param may need to also be set on
6132      * {@link #mRecentsAnimationController#setWillFinishToHome}.
6133      */
finishRecentsAnimation(boolean toRecents, boolean shouldPip, boolean allAppTargetsAreTranslucent, @Nullable Runnable onFinishComplete)6134     public void finishRecentsAnimation(boolean toRecents, boolean shouldPip,
6135             boolean allAppTargetsAreTranslucent, @Nullable Runnable onFinishComplete) {
6136         Log.d(TAG, "finishRecentsAnimation - mRecentsAnimationController: "
6137                 + mRecentsAnimationController);
6138         // TODO(b/197232424#comment#10) Move this back into onRecentsAnimationComplete(). Maybe?
6139         cleanupRemoteTargets();
6140 
6141         if (mRecentsAnimationController == null) {
6142             if (onFinishComplete != null) {
6143                 onFinishComplete.run();
6144             }
6145             return;
6146         }
6147 
6148         final boolean sendUserLeaveHint = toRecents && shouldPip;
6149         if (sendUserLeaveHint && !com.android.wm.shell.Flags.enablePip2()) {
6150             // Notify the SysUI to use fade-in animation when entering PiP from live tile.
6151             // Note: PiP2 handles entering differently, so skip if enable_pip2=true.
6152             final SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(getContext());
6153             systemUiProxy.setPipAnimationTypeToAlpha();
6154             systemUiProxy.setShelfHeight(true, mContainer.getDeviceProfile().hotseatBarSizePx);
6155             // Transaction to hide the task to avoid flicker for entering PiP from split-screen.
6156             // See also {@link AbsSwipeUpHandler#maybeFinishSwipeToHome}.
6157             PictureInPictureSurfaceTransaction tx =
6158                     new PictureInPictureSurfaceTransaction.Builder()
6159                             .setAlpha(0f)
6160                             .build();
6161             tx.setShouldDisableCanAffectSystemUiFlags(false);
6162             int[] taskIds = TopTaskTracker.INSTANCE.get(getContext()).getRunningSplitTaskIds();
6163             for (int taskId : taskIds) {
6164                 mRecentsAnimationController.setFinishTaskTransaction(taskId,
6165                         tx, null /* overlay */);
6166             }
6167         }
6168         mRecentsAnimationController.finish(toRecents, allAppTargetsAreTranslucent, () -> {
6169             if (onFinishComplete != null) {
6170                 onFinishComplete.run();
6171             }
6172             onRecentsAnimationComplete();
6173         }, sendUserLeaveHint);
6174     }
6175 
6176     /**
6177      * Called when a running recents animation has finished or canceled.
6178      */
onRecentsAnimationComplete()6179     public void onRecentsAnimationComplete() {
6180         Log.d(TAG, "onRecentsAnimationComplete "
6181                 + "- mRecentsAnimationController: " + mRecentsAnimationController
6182                 + ", mSideTaskLaunchCallback: " + mSideTaskLaunchCallback);
6183         // At this point, the recents animation is not running and if the animation was canceled
6184         // by a display rotation then reset this state to show the screenshot
6185         setRunningTaskViewShowScreenshot(true);
6186         // After we finish the recents animation, the current task id should be correctly
6187         // reset so that when the task is launched from Overview later, it goes through the
6188         // flow of starting a new task instead of finishing recents animation to app. A
6189         // typical example of this is (1) user swipes up from app to Overview (2) user
6190         // taps on QSB (3) user goes back to Overview and launch the most recent task.
6191         setCurrentTask(-1);
6192         mRecentsAnimationController = null;
6193         mSplitSelectStateController.setRecentsAnimationRunning(false);
6194         executeSideTaskLaunchCallback();
6195         if (enableOverviewBackgroundWallpaperBlur()) {
6196             mBlurUtils.setDrawLiveTileBelowRecents(false);
6197         }
6198     }
6199 
setDisallowScrollToClearAll(boolean disallowScrollToClearAll)6200     public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) {
6201         if (mDisallowScrollToClearAll != disallowScrollToClearAll) {
6202             mDisallowScrollToClearAll = disallowScrollToClearAll;
6203             updateMinAndMaxScrollX();
6204         }
6205     }
6206     /**
6207      * Update the value of [mDisallowScrollToAddDesk]
6208      */
setDisallowScrollToAddDesk(boolean disallowScrollToAddDesk)6209     public void setDisallowScrollToAddDesk(boolean disallowScrollToAddDesk) {
6210         if (mDisallowScrollToAddDesk != disallowScrollToAddDesk) {
6211             mDisallowScrollToAddDesk = disallowScrollToAddDesk;
6212             updateMinAndMaxScrollX();
6213         }
6214     }
6215 
6216 
6217 
6218     /**
6219      * Updates page scroll synchronously after measure and layout child views.
6220      */
6221     @SuppressLint("WrongCall")
updateScrollSynchronously()6222     public void updateScrollSynchronously() {
6223         // onMeasure is needed to update child's measured width which is used in scroll calculation,
6224         // in case TaskView sizes has changed when being focused/unfocused.
6225         onMeasure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
6226                 makeMeasureSpec(getMeasuredHeight(), EXACTLY));
6227         onLayout(false /*  changed */, getLeft(), getTop(), getRight(), getBottom());
6228         updateMinAndMaxScrollX();
6229     }
6230 
6231     @Override
getChildGap(int fromIndex, int toIndex)6232     protected int getChildGap(int fromIndex, int toIndex) {
6233         int clearAllIndex = indexOfChild(mClearAllButton);
6234         return fromIndex == clearAllIndex || toIndex == clearAllIndex
6235                 ? getClearAllExtraPageSpacing() : 0;
6236     }
6237 
getClearAllExtraPageSpacing()6238     protected int getClearAllExtraPageSpacing() {
6239         return showAsGrid()
6240                 ? Math.max(mContainer.getDeviceProfile().overviewGridSideMargin - mPageSpacing, 0)
6241                 : 0;
6242     }
6243 
6244     @Override
updateMinAndMaxScrollX()6245     protected void updateMinAndMaxScrollX() {
6246         super.updateMinAndMaxScrollX();
6247         if (DEBUG) {
6248             Log.d(TAG, "updateMinAndMaxScrollX - mMinScroll: " + mMinScroll);
6249             Log.d(TAG, "updateMinAndMaxScrollX - mMaxScroll: " + mMaxScroll);
6250         }
6251     }
6252 
6253     @Override
computeMinScroll()6254     protected int computeMinScroll() {
6255         if (!hasTaskViews()) {
6256             return super.computeMinScroll();
6257         }
6258 
6259         return getScrollForPage(mIsRtl ? getLastViewIndex() : getFirstViewIndex());
6260     }
6261 
6262     @Override
computeMaxScroll()6263     protected int computeMaxScroll() {
6264         if (!hasTaskViews()) {
6265             return super.computeMaxScroll();
6266         }
6267 
6268         return getScrollForPage(mIsRtl ? getFirstViewIndex() : getLastViewIndex());
6269     }
6270 
getFirstViewIndex()6271     private int getFirstViewIndex() {
6272         final View firstView;
6273         if (mShowAsGridLastOnLayout) {
6274             // For grid Overview, it always start if a large tile (focused task or desktop task) if
6275             // they exist, otherwise it start with the first task.
6276             TaskView firstLargeTaskView = mUtils.getFirstLargeTaskView();
6277             if (firstLargeTaskView != null) {
6278                 firstView = firstLargeTaskView;
6279             } else {
6280                 firstView = mUtils.getFirstSmallTaskView();
6281             }
6282         } else {
6283             firstView = mUtils.getFirstTaskViewInCarousel(
6284                     /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0);
6285         }
6286         return indexOfChild(firstView);
6287     }
6288 
getLastViewIndex()6289     private int getLastViewIndex() {
6290         final View lastView;
6291         if (!mDisallowScrollToClearAll) {
6292             // When ClearAllButton is present, it always end with ClearAllButton.
6293             lastView = mClearAllButton;
6294         } else if (mShowAsGridLastOnLayout) {
6295             // When ClearAllButton is absent, for the grid Overview, it always end with a grid task
6296             // if they exist, otherwise it ends with a large tile (focused task or desktop task).
6297             TaskView lastGridTaskView = getLastGridTaskView();
6298             if (lastGridTaskView != null) {
6299                 lastView = lastGridTaskView;
6300             } else {
6301                 lastView = mUtils.getLastLargeTaskView();
6302             }
6303         } else {
6304             lastView = mUtils.getLastTaskViewInCarousel(
6305                     /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0);
6306         }
6307         return indexOfChild(lastView);
6308     }
6309 
6310     /**
6311      * Returns page scroll of ClearAllButton.
6312      */
getClearAllScroll()6313     public int getClearAllScroll() {
6314         return getScrollForPage(indexOfChild(mClearAllButton));
6315     }
6316 
6317     @Override
getPageScrolls(int[] outPageScrolls, boolean layoutChildren, ComputePageScrollsLogic scrollLogic)6318     protected boolean getPageScrolls(int[] outPageScrolls, boolean layoutChildren,
6319             ComputePageScrollsLogic scrollLogic) {
6320         int[] newPageScrolls = new int[outPageScrolls.length];
6321         super.getPageScrolls(newPageScrolls, layoutChildren, scrollLogic);
6322         boolean showAsFullscreen = showAsFullscreen();
6323         boolean showAsGrid = showAsGrid();
6324 
6325         // Align ClearAllButton to the left (RTL) or right (non-RTL), which is different from other
6326         // TaskViews. This must be called after laying out ClearAllButton.
6327         if (layoutChildren) {
6328             int clearAllWidthDiff = getPagedOrientationHandler().getPrimaryValue(mTaskWidth,
6329                     mTaskHeight) - getPagedOrientationHandler().getPrimarySize(mClearAllButton);
6330             mClearAllButton.setScrollOffsetPrimary(mIsRtl ? clearAllWidthDiff : -clearAllWidthDiff);
6331         }
6332 
6333         int[] oldPageScrolls = Arrays.copyOf(outPageScrolls, outPageScrolls.length);
6334         int clearAllIndex = indexOfChild(mClearAllButton);
6335         int clearAllScroll = 0;
6336         int clearAllWidth = getPagedOrientationHandler().getPrimarySize(mClearAllButton);
6337         if (clearAllIndex != -1 && clearAllIndex < outPageScrolls.length) {
6338             float scrollDiff = mClearAllButton.getScrollAdjustment(showAsFullscreen, showAsGrid);
6339             clearAllScroll = newPageScrolls[clearAllIndex] + Math.round(scrollDiff);
6340             outPageScrolls[clearAllIndex] = clearAllScroll;
6341         }
6342 
6343         int lastTaskScroll = getLastTaskScroll(clearAllScroll, clearAllWidth);
6344         getTaskViews().forEachWithIndexInParent((index, taskView) -> {
6345             float scrollDiff = taskView.getScrollAdjustment(showAsGrid);
6346             int pageScroll = newPageScrolls[index] + Math.round(scrollDiff);
6347             if ((mIsRtl && pageScroll < lastTaskScroll)
6348                     || (!mIsRtl && pageScroll > lastTaskScroll)) {
6349                 pageScroll = lastTaskScroll;
6350             }
6351             outPageScrolls[index] = pageScroll;
6352             if (DEBUG) {
6353                 Log.d(TAG,
6354                         "getPageScrolls - outPageScrolls[" + index + "]: " + outPageScrolls[index]);
6355             }
6356         });
6357 
6358         int addDesktopButtonIndex = indexOfChild(mAddDesktopButton);
6359         if (addDesktopButtonIndex >= 0 && addDesktopButtonIndex < outPageScrolls.length) {
6360             int firstViewIndex = getFirstViewIndex();
6361             if (firstViewIndex >= 0 && firstViewIndex < outPageScrolls.length) {
6362                 // If we can scroll to [AddDesktopButton], make its page scroll equal to
6363                 // the first [TaskView]. Otherwise, make its page scroll out of range of
6364                 // [minScroll, maxScroll].
6365                 if (!mDisallowScrollToAddDesk) {
6366                     outPageScrolls[addDesktopButtonIndex] = outPageScrolls[firstViewIndex];
6367                 } else {
6368                     outPageScrolls[addDesktopButtonIndex] =
6369                             outPageScrolls[firstViewIndex] + (mIsRtl ? 1 : -1);
6370                 }
6371             }
6372 
6373             if (DEBUG) {
6374                 Log.d(TAG, "getPageScrolls - addDesktopButtonScroll: "
6375                         + outPageScrolls[addDesktopButtonIndex]);
6376             }
6377         }
6378         if (DEBUG) {
6379             Log.d(TAG, "getPageScrolls - clearAllScroll: " + clearAllScroll);
6380         }
6381         return !Arrays.equals(oldPageScrolls, outPageScrolls);
6382     }
6383 
6384     @Override
getChildOffset(int index)6385     protected int getChildOffset(int index) {
6386         int childOffset = super.getChildOffset(index);
6387         View child = getChildAt(index);
6388         if (child instanceof TaskView) {
6389             childOffset += ((TaskView) child).getOffsetAdjustment(showAsGrid());
6390         } else if (child instanceof ClearAllButton) {
6391             childOffset += ((ClearAllButton) child).getOffsetAdjustment(mOverviewFullscreenEnabled,
6392                     showAsGrid());
6393         }
6394         return childOffset;
6395     }
6396 
6397     @Override
getChildVisibleSize(int childIndex)6398     protected int getChildVisibleSize(int childIndex) {
6399         final TaskView taskView = getTaskViewAt(childIndex);
6400         if (taskView == null) {
6401             return super.getChildVisibleSize(childIndex);
6402         }
6403         return (int) (super.getChildVisibleSize(childIndex) * taskView.getSizeAdjustment(
6404                 showAsFullscreen()));
6405     }
6406 
getClearAllButton()6407     public ClearAllButton getClearAllButton() {
6408         return mClearAllButton;
6409     }
6410 
6411     @Nullable
getAddDeskButton()6412     public AddDesktopButton getAddDeskButton() {
6413         return mAddDesktopButton;
6414     }
6415 
6416     /**
6417      * @return How many pixels the running task is offset on the currently laid out dominant axis.
6418      */
getScrollOffset()6419     public int getScrollOffset() {
6420         return getScrollOffset(getRunningTaskIndex());
6421     }
6422 
6423     /**
6424      * Returns how many pixels the running task is offset on the currently laid out dominant axis
6425      * specifically during a Keyboard task focus.
6426      */
getScrollOffsetForKeyboardTaskFocus()6427     public int getScrollOffsetForKeyboardTaskFocus() {
6428         if (!isKeyboardTaskFocusPending()) {
6429             return getScrollOffset(getRunningTaskIndex());
6430         }
6431         return getPagedOrientationHandler().getPrimaryScroll(this)
6432                 - getScrollForPage(mKeyboardTaskFocusIndex)
6433                 + getScrollOffset(getRunningTaskIndex());
6434     }
6435 
6436     /**
6437      * Sets whether or not we should clamp the scroll offset.
6438      * This is used to avoid x-axis movement when swiping up transient taskbar.
6439      * Should only be set at the beginning and end of the gesture, otherwise a jump may occur.
6440      *
6441      * @param clampScrollOffset When true, we clamp the scroll to 0 before the clamp threshold is
6442      *                          met.
6443      */
setClampScrollOffset(boolean clampScrollOffset)6444     public void setClampScrollOffset(boolean clampScrollOffset) {
6445         mShouldClampScrollOffset = clampScrollOffset;
6446     }
6447 
6448     /**
6449      * Returns how many pixels the page is offset on the currently laid out dominant axis.
6450      */
getScrollOffset(int pageIndex)6451     public int getScrollOffset(int pageIndex) {
6452         int unclampedOffset = getUnclampedScrollOffset(pageIndex);
6453         if (!mShouldClampScrollOffset) {
6454             return unclampedOffset;
6455         }
6456         if (Math.abs(unclampedOffset) < mClampedScrollOffsetBound) {
6457             return 0;
6458         }
6459         return unclampedOffset
6460                 - Math.round(Math.signum(unclampedOffset) * mClampedScrollOffsetBound);
6461     }
6462 
6463     /**
6464      * Returns how many pixels the page is offset on the currently laid out dominant axis.
6465      */
getUnclampedScrollOffset(int pageIndex)6466     private int getUnclampedScrollOffset(int pageIndex) {
6467         if (pageIndex == INVALID_PAGE) {
6468             return 0;
6469         }
6470         // Don't dampen the scroll (due to overscroll) if the adjacent tasks are offscreen, so that
6471         // the page can move freely given there's no visual indication why it shouldn't.
6472         int overScrollShift = mAdjacentPageHorizontalOffset > 0
6473                 ? (int) Utilities.mapRange(
6474                 mAdjacentPageHorizontalOffset,
6475                 getOverScrollShift(),
6476                 getUndampedOverScrollShift())
6477                 : getOverScrollShift();
6478         return getScrollForPage(pageIndex) - getPagedOrientationHandler().getPrimaryScroll(this)
6479                 + overScrollShift + getOffsetFromScrollPosition(pageIndex);
6480     }
6481 
6482     /**
6483      * Returns how many pixels the page is offset from its scroll position.
6484      */
getOffsetFromScrollPosition(int pageIndex)6485     private int getOffsetFromScrollPosition(int pageIndex) {
6486         return getOffsetFromScrollPosition(pageIndex, mUtils.getTopRowIdArray(),
6487                 mUtils.getBottomRowIdArray());
6488     }
6489 
getOffsetFromScrollPosition( int pageIndex, IntArray topRowIdArray, IntArray bottomRowIdArray)6490     private int getOffsetFromScrollPosition(
6491             int pageIndex, IntArray topRowIdArray, IntArray bottomRowIdArray) {
6492         if (!showAsGrid()) {
6493             return 0;
6494         }
6495 
6496         TaskView taskView = getTaskViewAt(pageIndex);
6497         if (taskView == null) {
6498             return 0;
6499         }
6500 
6501         TaskView lastGridTaskView = getLastGridTaskView(topRowIdArray, bottomRowIdArray);
6502         if (lastGridTaskView == null) {
6503             return 0;
6504         }
6505 
6506         if (getScrollForPage(pageIndex) != getScrollForPage(indexOfChild(lastGridTaskView))) {
6507             return 0;
6508         }
6509 
6510         // Check distance from lastGridTaskView to taskView.
6511         int lastGridTaskViewPosition =
6512                 getPositionInRow(lastGridTaskView, topRowIdArray, bottomRowIdArray);
6513         int taskViewPosition = getPositionInRow(taskView, topRowIdArray, bottomRowIdArray);
6514         int gridTaskSizeAndSpacing = mLastComputedGridTaskSize.width() + mPageSpacing;
6515         int positionDiff = gridTaskSizeAndSpacing * (lastGridTaskViewPosition - taskViewPosition);
6516 
6517         int taskEnd = getLastTaskEnd() + (mIsRtl ? positionDiff : -positionDiff);
6518         int normalTaskEnd = mIsRtl
6519                 ? mLastComputedGridTaskSize.left
6520                 : mLastComputedGridTaskSize.right;
6521         return taskEnd - normalTaskEnd;
6522     }
6523 
getLastTaskEnd()6524     private int getLastTaskEnd() {
6525         return mIsRtl
6526                 ? mLastComputedGridSize.left + mPageSpacing + mClearAllShortTotalWidthTranslation
6527                 : mLastComputedGridSize.right - mPageSpacing - mClearAllShortTotalWidthTranslation;
6528     }
6529 
getPositionInRow( TaskView taskView, IntArray topRowIdArray, IntArray bottomRowIdArray)6530     private int getPositionInRow(
6531             TaskView taskView, IntArray topRowIdArray, IntArray bottomRowIdArray) {
6532         int position = topRowIdArray.indexOf(taskView.getTaskViewId());
6533         return position != -1 ? position : bottomRowIdArray.indexOf(taskView.getTaskViewId());
6534     }
6535 
6536     /**
6537      * @return true if the task in on the bottom of the grid
6538      */
isOnGridBottomRow(TaskView taskView)6539     public boolean isOnGridBottomRow(TaskView taskView) {
6540         return showAsGrid()
6541                 && !mTopRowIdSet.contains(taskView.getTaskViewId())
6542                 && !taskView.isLargeTile();
6543     }
6544 
getEventDispatcher(float navbarRotation)6545     public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
6546         float degreesRotated;
6547         if (navbarRotation == 0) {
6548             degreesRotated = getPagedOrientationHandler().getDegreesRotated();
6549         } else {
6550             degreesRotated = -navbarRotation;
6551         }
6552         if (degreesRotated == 0) {
6553             return super::onTouchEvent;
6554         }
6555 
6556         // At this point the event coordinates have already been transformed, so we need to
6557         // undo that transformation since PagedView also accommodates for the transformation via
6558         // PagedOrientationHandler
6559         return e -> {
6560             if (navbarRotation != 0
6561                     && mOrientationState.isMultipleOrientationSupportedByDevice()
6562                     && !mOrientationState.getOrientationHandler().isLayoutNaturalToLauncher()) {
6563                 mOrientationState.flipVertical(e);
6564                 super.onTouchEvent(e);
6565                 mOrientationState.flipVertical(e);
6566                 return;
6567             }
6568             mOrientationState.transformEvent(-degreesRotated, e, true);
6569             super.onTouchEvent(e);
6570             mOrientationState.transformEvent(-degreesRotated, e, false);
6571         };
6572     }
6573 
6574     private void updateEnabledOverlays() {
6575         if (enableRefactorTaskThumbnail()) {
6576             Set<Integer> fullyVisibleTaskIds = new HashSet<>();
6577             for (TaskView taskView : getTaskViews()) {
6578                 if (isTaskViewFullyVisible(taskView)) {
6579                     fullyVisibleTaskIds.addAll(taskView.getTaskIdSet());
6580                 }
6581             }
6582             mRecentsViewModel.updateTasksFullyVisible(fullyVisibleTaskIds);
6583         } else {
6584             TaskView focusedTaskView = getFocusedTaskView();
6585             for (TaskView taskView : getTaskViews()) {
6586                 if (taskView == focusedTaskView) {
6587                     continue;
6588                 }
6589                 taskView.setOverlayEnabled(mOverlayEnabled && isTaskViewFullyVisible(taskView));
6590             }
6591             // Focus task overlay should be enabled and refreshed at last
6592             if (focusedTaskView != null) {
6593                 focusedTaskView.setOverlayEnabled(
6594                         mOverlayEnabled && isTaskViewFullyVisible(focusedTaskView));
6595             }
6596         }
6597     }
6598 
6599     public void setOverlayEnabled(boolean overlayEnabled) {
6600         if (mOverlayEnabled != overlayEnabled) {
6601             mOverlayEnabled = overlayEnabled;
6602             updateEnabledOverlays();
6603 
6604             if (enableRefactorTaskThumbnail()) {
6605                 mRecentsViewModel.setOverlayEnabled(overlayEnabled);
6606             }
6607         }
6608     }
6609 
6610     public void setOverviewGridEnabled(boolean overviewGridEnabled) {
6611         if (mOverviewGridEnabled != overviewGridEnabled) {
6612             mOverviewGridEnabled = overviewGridEnabled;
6613             updateActionsViewFocusedScroll();
6614             // Request layout to ensure scroll position is recalculated with updated mGridProgress.
6615             requestLayout();
6616         }
6617     }
6618 
6619     public void setOverviewFullscreenEnabled(boolean overviewFullscreenEnabled) {
6620         if (mOverviewFullscreenEnabled != overviewFullscreenEnabled) {
6621             mOverviewFullscreenEnabled = overviewFullscreenEnabled;
6622             // Request layout to ensure scroll position is recalculated with updated
6623             // mFullscreenProgress.
6624             requestLayout();
6625         }
6626     }
6627 
6628     /**
6629      * Update whether RecentsView is in select mode. Should be enabled before transitioning to
6630      * select mode, and only disabled after transitioning from select mode.
6631      */
6632     public void setOverviewSelectEnabled(boolean overviewSelectEnabled) {
6633         if (mOverviewSelectEnabled != overviewSelectEnabled) {
6634             mOverviewSelectEnabled = overviewSelectEnabled;
6635             updatePivots();
6636             if (!mOverviewSelectEnabled) {
6637                 setSelectedTask(INVALID_TASK_ID);
6638             }
6639         }
6640     }
6641 
6642     /**
6643      * Switch the current running task view to static snapshot mode,
6644      * capturing the snapshot at the same time.
6645      */
6646     public void switchToScreenshot(Runnable onFinishRunnable) {
6647         if (mRecentsAnimationController == null) {
6648             if (onFinishRunnable != null) {
6649                 onFinishRunnable.run();
6650             }
6651             return;
6652         }
6653 
6654         TaskView taskView = getRunningTaskView();
6655         if (taskView == null) {
6656             onFinishRunnable.run();
6657             return;
6658         }
6659 
6660         Map<Integer, ThumbnailData> updatedThumbnails = mUtils.screenshotTasks(taskView);
6661         if (enableRefactorTaskThumbnail()) {
6662             mHelper.switchToScreenshot(taskView, updatedThumbnails, onFinishRunnable);
6663         } else {
6664             setRunningTaskViewShowScreenshot(true, updatedThumbnails);
6665             ViewUtils.postFrameDrawn(taskView, onFinishRunnable);
6666         }
6667     }
6668 
6669     /**
6670      * Switch the current running task view to static snapshot mode, using the
6671      * provided thumbnail data as the snapshot.
6672      * TODO(b/195609063) Consolidate this method w/ the one above, except this thumbnail data comes
6673      *  from gesture state, which is a larger change of it having to keep track of multiple tasks.
6674      *  OR. Maybe it doesn't need to pass in a thumbnail and we can use the exact same flow as above
6675      */
6676     public void switchToScreenshot(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas,
6677             Runnable onFinishRunnable) {
6678         final TaskView taskView = getRunningTaskView();
6679         if (taskView != null) {
6680             if (enableRefactorTaskThumbnail()) {
6681                 mHelper.switchToScreenshot(taskView, thumbnailDatas, onFinishRunnable);
6682             } else {
6683                 taskView.setShouldShowScreenshot(true, thumbnailDatas);
6684                 ViewUtils.postFrameDrawn(taskView, onFinishRunnable);
6685             }
6686         } else {
6687             onFinishRunnable.run();
6688         }
6689     }
6690 
6691     /**
6692      * The current task is fully modal (modalness = 1) when it is shown on its own in a modal
6693      * way. Modalness 0 means the task is shown in context with all the other tasks.
6694      */
6695     private void setTaskModalness(float modalness) {
6696         mTaskModalness = modalness;
6697         updatePageOffsets();
6698         if (getSelectedTaskView() != null) {
6699             if (enableGridOnlyOverview()) {
6700                 for (TaskView taskView : getTaskViews()) {
6701                     taskView.setModalness(modalness);
6702                 }
6703             } else {
6704                 getSelectedTaskView().setModalness(modalness);
6705             }
6706         } else if (getCurrentPageTaskView() != null) {
6707             getCurrentPageTaskView().setModalness(modalness);
6708         }
6709         // Only show actions view when it's modal for in-place landscape mode.
6710         boolean inPlaceLandscape = !mOrientationState.isRecentsActivityRotationAllowed()
6711                 && mOrientationState.getTouchRotation() != ROTATION_0;
6712         mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, modalness < 1 && inPlaceLandscape);
6713     }
6714 
6715     @Nullable
6716     protected DepthController getDepthController() {
6717         return null;
6718     }
6719 
6720     @Nullable
6721     protected DesktopRecentsTransitionController getDesktopRecentsController() {
6722         return mDesktopRecentsTransitionController;
6723     }
6724 
6725     /** Enables or disables modal state for RecentsView */
6726     public abstract void setModalStateEnabled(int taskId, boolean animate);
6727 
6728     public TaskOverlayFactory getTaskOverlayFactory() {
6729         return mTaskOverlayFactory;
6730     }
6731 
6732     public BaseContainerInterface getSizeStrategy() {
6733         return mSizeStrategy;
6734     }
6735 
6736 
6737     /**
6738      * Returns the container interface
6739      */
6740     protected abstract BaseContainerInterface<STATE_TYPE, ?> getContainerInterface(int displayId);
6741 
6742     /**
6743      * Set all the task views to color tint scrim mode, dimming or tinting them all. Allows the
6744      * tasks to be dimmed while other elements in the recents view are left alone.
6745      */
6746     public void showForegroundScrim(boolean show) {
6747         if (!show && mColorTint == 0) {
6748             if (mTintingAnimator != null) {
6749                 mTintingAnimator.cancel();
6750                 mTintingAnimator = null;
6751             }
6752             return;
6753         }
6754 
6755         mTintingAnimator = ObjectAnimator.ofFloat(this, COLOR_TINT,
6756                 show ? FOREGROUND_SCRIM_TINT : 0f);
6757         mTintingAnimator.setAutoCancel(true);
6758         mTintingAnimator.start();
6759     }
6760 
6761     /** Tint the RecentsView and TaskViews in to simulate a scrim. */
6762     private void setColorTint(float tintAmount) {
6763         mColorTint = tintAmount;
6764 
6765         for (TaskView taskView : getTaskViews()) {
6766             taskView.setColorTint(mColorTint, mTintingColor);
6767         }
6768 
6769         Drawable scrimBg = mContainer.getScrimView().getBackground();
6770         if (scrimBg != null) {
6771             if (tintAmount == 0f) {
6772                 scrimBg.setTintList(null);
6773             } else {
6774                 scrimBg.setTintBlendMode(BlendMode.SRC_OVER);
6775                 scrimBg.setTint(
6776                         ColorUtils.setAlphaComponent(mTintingColor, (int) (255 * tintAmount)));
6777             }
6778         }
6779     }
6780 
6781     private float getColorTint() {
6782         return mColorTint;
6783     }
6784 
6785     /** Returns {@code true} if the overview tasks are displayed as a grid. */
6786     public boolean showAsGrid() {
6787         return mOverviewGridEnabled || (mCurrentGestureEndTarget != null
6788                 && mSizeStrategy.stateFromGestureEndTarget(mCurrentGestureEndTarget)
6789                 .displayOverviewTasksAsGrid(mContainer.getDeviceProfile()));
6790     }
6791 
6792     protected boolean showAsFullscreen() {
6793         return mOverviewFullscreenEnabled
6794                 && mCurrentGestureEndTarget != GestureState.GestureEndTarget.RECENTS;
6795     }
6796 
6797     public void cleanupRemoteTargets() {
6798         Log.d(TAG, "cleanupRemoteTargets - mRemoteTargetHandles: " + Arrays.toString(
6799                 mRemoteTargetHandles));
6800         mRemoteTargetHandles = null;
6801     }
6802 
6803     /**
6804      * Used to register callbacks for when our empty message state changes.
6805      *
6806      * @see #setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener)
6807      * @see #updateEmptyMessage()
6808      */
6809     public interface OnEmptyMessageUpdatedListener {
6810         /** @param isEmpty Whether RecentsView is empty (i.e. has no children) */
6811         void onEmptyMessageUpdated(boolean isEmpty);
6812     }
6813 
6814     /**
6815      * Adds a listener for scroll changes
6816      */
6817     public void addOnScrollChangedListener(OnScrollChangedListener listener) {
6818         mScrollListeners.add(listener);
6819     }
6820 
6821     /**
6822      * Removes a previously added scroll change listener
6823      */
6824     public void removeOnScrollChangedListener(OnScrollChangedListener listener) {
6825         mScrollListeners.remove(listener);
6826     }
6827 
6828     /**
6829      * @return Corner radius in pixel value for PiP window, which is updated via
6830      * {@link #mIPipAnimationListener}
6831      */
6832     public int getPipCornerRadius() {
6833         return mPipCornerRadius;
6834     }
6835 
6836     /**
6837      * @return Shadow radius in pixel value for PiP window, which is updated via
6838      * {@link #mIPipAnimationListener}
6839      */
6840     public int getPipShadowRadius() {
6841         return mPipShadowRadius;
6842     }
6843 
6844     @Override
6845     public boolean scrollLeft() {
6846         if (!showAsGrid()) {
6847             return super.scrollLeft();
6848         }
6849 
6850         int targetPage = getNextPage();
6851         if (targetPage >= 0) {
6852             // Find the next page that is not fully visible.
6853             TaskView taskView = getTaskViewAt(targetPage);
6854             while ((taskView == null || isTaskViewFullyVisible(taskView)) && targetPage - 1 >= 0) {
6855                 taskView = getTaskViewAt(--targetPage);
6856             }
6857             // Target a scroll where targetPage is on left of screen but still fully visible.
6858             int normalTaskEnd = mIsRtl
6859                     ? mLastComputedGridTaskSize.left
6860                     : mLastComputedGridTaskSize.right;
6861             int targetScroll = getScrollForPage(targetPage) + normalTaskEnd - getLastTaskEnd();
6862             // Find a page that is close to targetScroll while not over it.
6863             while (targetPage - 1 >= 0
6864                     && (mIsRtl
6865                     ? getScrollForPage(targetPage - 1) < targetScroll
6866                     : getScrollForPage(targetPage - 1) > targetScroll)) {
6867                 targetPage--;
6868             }
6869             snapToPage(targetPage);
6870             return true;
6871         }
6872 
6873         return mAllowOverScroll;
6874     }
6875 
6876     @Override
6877     public boolean scrollRight() {
6878         if (!showAsGrid()) {
6879             return super.scrollRight();
6880         }
6881 
6882         int targetPage = getNextPage();
6883         if (targetPage < getChildCount()) {
6884             // Find the next page that is not fully visible.
6885             TaskView taskView = getTaskViewAt(targetPage);
6886             while ((taskView != null && isTaskViewFullyVisible(taskView))
6887                     && targetPage + 1 < getChildCount()) {
6888                 taskView = getTaskViewAt(++targetPage);
6889             }
6890             snapToPage(targetPage);
6891             return true;
6892         }
6893         return mAllowOverScroll;
6894     }
6895 
6896     @Override
6897     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
6898         super.onScrollChanged(l, t, oldl, oldt);
6899         dispatchScrollChanged();
6900     }
6901 
6902     /**
6903      * Prepares this RecentsView to scroll properly for an upcoming child view focus request from
6904      * keyboard quick switching
6905      */
6906     public void setKeyboardTaskFocusIndex(int taskIndex) {
6907         mKeyboardTaskFocusIndex = taskIndex;
6908     }
6909 
6910     /** Returns whether this RecentsView will be scrolling to a child view for a focus request */
6911     public boolean isKeyboardTaskFocusPending() {
6912         return mKeyboardTaskFocusIndex != INVALID_PAGE;
6913     }
6914 
6915     private boolean isKeyboardTaskFocusPendingForChild(View child) {
6916         return isKeyboardTaskFocusPending() && mKeyboardTaskFocusIndex == indexOfChild(child);
6917     }
6918 
6919     @Override
6920     protected int getSnapAnimationDuration() {
6921         return isKeyboardTaskFocusPending()
6922                 ? mKeyboardTaskFocusSnapAnimationDuration : super.getSnapAnimationDuration();
6923     }
6924 
6925     @Override
6926     protected void onVelocityValuesUpdated() {
6927         super.onVelocityValuesUpdated();
6928         mKeyboardTaskFocusSnapAnimationDuration =
6929                 getResources().getInteger(R.integer.config_keyboardTaskFocusSnapAnimationDuration);
6930     }
6931 
6932     @Override
6933     protected boolean shouldHandleRequestChildFocus(View child) {
6934         // If we are already scrolling to a task view and we aren't focusing to this child from
6935         // keyboard quick switch, then the focus request has already been handled
6936         return mScroller.isFinished() || isKeyboardTaskFocusPendingForChild(child);
6937     }
6938 
6939     @Override
6940     public void requestChildFocus(View child, View focused) {
6941         if (isKeyboardTaskFocusPendingForChild(child)) {
6942             updateGridProperties();
6943             updateScrollSynchronously();
6944         }
6945         super.requestChildFocus(child, focused);
6946     }
6947 
6948     private void dispatchScrollChanged() {
6949         runActionOnRemoteHandles(remoteTargetHandle ->
6950                 remoteTargetHandle.getTaskViewSimulator().setScroll(getScrollOffset()));
6951         for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
6952             mScrollListeners.get(i).onScrollChanged();
6953         }
6954     }
6955 
6956     private static class PinnedStackAnimationListener<T extends RecentsViewContainer> extends
6957             IPipAnimationListener.Stub {
6958         @Nullable
6959         private T mActivity;
6960         @Nullable
6961         private RecentsView mRecentsView;
6962 
6963         public void setActivityAndRecentsView(@Nullable T activity,
6964                 @Nullable RecentsView recentsView) {
6965             mActivity = activity;
6966             mRecentsView = recentsView;
6967         }
6968 
6969         @Override
6970         public void onPipAnimationStarted() {
6971             MAIN_EXECUTOR.execute(() -> {
6972                 // Needed for activities that auto-enter PiP, which will not trigger a remote
6973                 // animation to be created
6974                 if (mActivity != null) {
6975                     mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
6976                 }
6977             });
6978         }
6979 
6980         @Override
6981         public void onPipResourceDimensionsChanged(int cornerRadius, int shadowRadius) {
6982             if (mRecentsView != null) {
6983                 mRecentsView.mPipCornerRadius = cornerRadius;
6984                 mRecentsView.mPipShadowRadius = shadowRadius;
6985             }
6986         }
6987 
6988         @Override
6989         public void onExpandPip() {
6990             MAIN_EXECUTOR.execute(() -> {
6991                 if (mRecentsView == null
6992                         || mRecentsView.mSizeStrategy.getTaskbarController() == null) {
6993                     return;
6994                 }
6995                 // Hide the task bar when leaving PiP to prevent it from flickering once
6996                 // the app settles in full-screen mode.
6997                 mRecentsView.mSizeStrategy.getTaskbarController().onExpandPip();
6998             });
6999         }
7000     }
7001 
7002     @Override
7003     public void onCanCreateDesksChanged(boolean canCreateDesks) {
7004         // TODO: b/389209338 - update the AddDesktopButton's visibility on this.
7005     }
7006 
7007     @Override
7008     public void onDeskAdded(int displayId, int deskId) {
7009         // Ignore desk changes that don't belong to this display.
7010         if (displayId != mContainer.getDisplay().getDisplayId()) {
7011             return;
7012         }
7013 
7014         if (mUtils.getDesktopTaskViewForDeskId(deskId) != null) {
7015             Log.e(TAG, "A task view for this desk has already been added.");
7016             return;
7017         }
7018 
7019         TaskView currentTaskView = getTaskViewAt(mCurrentPage);
7020 
7021         // We assume that a newly added desk is always empty and gets added to the left of the
7022         // `AddNewDesktopButton`.
7023         DesktopTaskView desktopTaskView =
7024                 (DesktopTaskView) getTaskViewFromPool(TaskViewType.DESKTOP);
7025         desktopTaskView.bind(new DesktopTask(deskId, displayId, new ArrayList<>()),
7026                 mOrientationState, mTaskOverlayFactory);
7027 
7028         Objects.requireNonNull(mAddDesktopButton);
7029         final int insertionIndex = 1 + indexOfChild(mAddDesktopButton);
7030         addView(desktopTaskView, insertionIndex);
7031 
7032         updateTaskSize();
7033         mUtils.updateChildTaskOrientations();
7034         updateScrollSynchronously();
7035 
7036         // Set Current Page based on the stored TaskView.
7037         if (currentTaskView != null) {
7038             setCurrentPage(indexOfChild(currentTaskView));
7039         }
7040     }
7041 
7042     @Override
7043     public void onDeskRemoved(int displayId, int deskId) {
7044         // Ignore desk changes that don't belong to this display.
7045         if (displayId != mContainer.getDisplay().getDisplayId()) {
7046             return;
7047         }
7048 
7049         // We need to distinguish between desk removals that are triggered from outside of overview
7050         // vs. the ones that were initiated from overview by dismissing the corresponding desktop
7051         // task view.
7052         var taskView = mUtils.getDesktopTaskViewForDeskId(deskId);
7053         if (taskView != null) {
7054             dismissTaskView(taskView, true, true);
7055         }
7056     }
7057 
7058     @Override
7059     public void onActiveDeskChanged(int displayId, int newActiveDesk, int oldActiveDesk) {
7060         // TODO: b/400870600 - We may need to add code here to special case when an empty desk gets
7061         // activated, since `RemoteDesktopLaunchTransitionRunner` doesn't always get triggered.
7062     }
7063 
7064     /** Get the color used for foreground scrimming the RecentsView for sharing. */
7065     public static int getForegroundScrimDimColor(Context context) {
7066         return context.getColor(R.color.overview_foreground_scrim_color);
7067     }
7068 
7069     /** Get the RecentsAnimationController */
7070     @Nullable
7071     public RecentsAnimationController getRecentsAnimationController() {
7072         return mRecentsAnimationController;
7073     }
7074 
7075     @Nullable
7076     public SplitInstructionsView getSplitInstructionsView() {
7077         return mSplitSelectStateController.getSplitInstructionsView();
7078     }
7079 
7080     /** Update the current activity locus id to show the enabled state of Overview */
7081     public void updateLocusId() {
7082         String locusId = "Overview";
7083 
7084         if (mOverviewStateEnabled && mContainer.isStarted()) {
7085             locusId += "|ENABLED";
7086         } else {
7087             locusId += "|DISABLED";
7088         }
7089 
7090         final LocusId id = new LocusId(locusId);
7091         // Set locus context is a binder call, don't want it to happen during a transition
7092         UI_HELPER_EXECUTOR.post(() -> mContainer.setLocusContext(id, Bundle.EMPTY));
7093     }
7094 
7095     /**
7096      * Moves the provided task into desktop mode, and invoke {@code successCallback} if succeeded.
7097      */
7098     public void moveTaskToDesktop(TaskContainer taskContainer,
7099             DesktopModeTransitionSource transitionSource,
7100             Runnable successCallback) {
7101         if (!DesktopModeStatus.canEnterDesktopMode(mContext)) {
7102             return;
7103         }
7104         switchToScreenshot(() -> finishRecentsAnimation(/* toRecents= */true, /* shouldPip= */false,
7105                 () -> moveTaskToDesktopInternal(taskContainer, successCallback, transitionSource)));
7106     }
7107 
7108     private void moveTaskToDesktopInternal(TaskContainer taskContainer,
7109             Runnable successCallback, DesktopModeTransitionSource transitionSource) {
7110         if (mDesktopRecentsTransitionController == null) {
7111             return;
7112         }
7113 
7114         mDesktopRecentsTransitionController.moveToDesktop(taskContainer, transitionSource,
7115                 successCallback);
7116     }
7117 
7118     /**
7119      * Move the provided task into external display and invoke {@code successCallback} if succeeded.
7120      */
7121     public void moveTaskToExternalDisplay(TaskContainer taskContainer, Runnable successCallback) {
7122         if (!DesktopModeStatus.canEnterDesktopMode(mContext)) {
7123             return;
7124         }
7125         switchToScreenshot(() -> finishRecentsAnimation(/* toRecents= */true, /* shouldPip= */false,
7126                 () -> moveTaskToDesktopInternal(taskContainer, successCallback)));
7127     }
7128 
7129     private void moveTaskToDesktopInternal(TaskContainer taskContainer, Runnable successCallback) {
7130         if (mDesktopRecentsTransitionController == null) {
7131             return;
7132         }
7133         mDesktopRecentsTransitionController.moveToExternalDisplay(taskContainer.getTask().key.id);
7134         successCallback.run();
7135     }
7136 
7137 
7138     // Logs when the orientation of Overview changes. We log both real and fake orientation changes.
7139     private void logOrientationChanged() {
7140         // Only log when Overview is showing.
7141         if (mOverviewStateEnabled) {
7142             mContainer.getStatsLogManager()
7143                     .logger()
7144                     .withContainerInfo(
7145                             LauncherAtom.ContainerInfo.newBuilder()
7146                                     .setTaskSwitcherContainer(
7147                                             LauncherAtom.TaskSwitcherContainer.newBuilder()
7148                                                     .setOrientationHandler(
7149                                                             getPagedOrientationHandler()
7150                                                                     .getHandlerTypeForLogging()))
7151                                     .build())
7152                     .log(LAUNCHER_OVERVIEW_ORIENTATION_CHANGED);
7153         }
7154     }
7155 
7156     private int getFontWeight() {
7157         int fontWeightAdjustment = getResources().getConfiguration().fontWeightAdjustment;
7158         if (fontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
7159             return Typeface.Builder.NORMAL_WEIGHT + fontWeightAdjustment;
7160         }
7161         return Typeface.Builder.NORMAL_WEIGHT;
7162     }
7163 
7164     /**
7165      * Creates the spring animations which run as a task settles back into its place in overview.
7166      *
7167      * <p>When a task dismiss is cancelled, the task will return to its original position via a
7168      * spring animation. As it passes the threshold of its settling state, its neighbors will
7169      * spring in response to the perceived impact of the settling task.
7170      */
7171     public SpringAnimation createTaskDismissSettlingSpringAnimation(TaskView draggedTaskView,
7172             float velocity, boolean isDismissing, int dismissLength,
7173             Function0<Unit> onEndRunnable) {
7174         return mDismissUtils.createTaskDismissSettlingSpringAnimation(draggedTaskView, velocity,
7175                 isDismissing, dismissLength, onEndRunnable);
7176     }
7177 
7178     /**
7179      * Animates RecentsView's scale to the provided value, using spring animations.
7180      */
7181     public SpringAnimation animateRecentsScale(float scale) {
7182         return mDismissUtils.animateRecentsScale(scale);
7183     }
7184 
7185     public interface TaskLaunchListener {
7186         void onTaskLaunched();
7187     }
7188 
7189     /**
7190      * Sets whether the remote animation targets should draw below the recents view.
7191      *
7192      * @param drawBelowRecents  whether the surface should draw below Recents.
7193      * @param remoteTargetHandles collection of remoteTargetHandles in Recents.
7194      */
7195     public void setDrawBelowRecents(boolean drawBelowRecents,
7196             RemoteTargetHandle[] remoteTargetHandles) {
7197         mBlurUtils.setDrawBelowRecents(drawBelowRecents, remoteTargetHandles);
7198     }
7199 }
7200