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