• 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.view.Surface.ROTATION_0;
20 import static android.view.View.MeasureSpec.EXACTLY;
21 import static android.view.View.MeasureSpec.makeMeasureSpec;
22 
23 import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
24 import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
25 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
26 import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
27 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
28 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
29 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
30 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
31 import static com.android.launcher3.Utilities.mapToRange;
32 import static com.android.launcher3.Utilities.squaredHypot;
33 import static com.android.launcher3.Utilities.squaredTouchSlop;
34 import static com.android.launcher3.anim.Interpolators.ACCEL;
35 import static com.android.launcher3.anim.Interpolators.ACCEL_0_5;
36 import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
37 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
38 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
39 import static com.android.launcher3.anim.Interpolators.LINEAR;
40 import static com.android.launcher3.anim.Interpolators.clampToProgress;
41 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
42 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
43 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
44 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
45 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
46 import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE;
47 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
48 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
49 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
50 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
51 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
52 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
53 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
54 
55 import android.animation.Animator;
56 import android.animation.AnimatorListenerAdapter;
57 import android.animation.AnimatorSet;
58 import android.animation.LayoutTransition;
59 import android.animation.LayoutTransition.TransitionListener;
60 import android.animation.ObjectAnimator;
61 import android.animation.PropertyValuesHolder;
62 import android.animation.ValueAnimator;
63 import android.annotation.TargetApi;
64 import android.app.ActivityManager.RunningTaskInfo;
65 import android.content.Context;
66 import android.content.LocusId;
67 import android.content.res.Configuration;
68 import android.graphics.BlendMode;
69 import android.graphics.Canvas;
70 import android.graphics.Color;
71 import android.graphics.Matrix;
72 import android.graphics.Point;
73 import android.graphics.PointF;
74 import android.graphics.Rect;
75 import android.graphics.RectF;
76 import android.graphics.Typeface;
77 import android.graphics.drawable.Drawable;
78 import android.os.Build;
79 import android.os.Bundle;
80 import android.os.UserHandle;
81 import android.text.Layout;
82 import android.text.StaticLayout;
83 import android.text.TextPaint;
84 import android.util.AttributeSet;
85 import android.util.FloatProperty;
86 import android.util.SparseBooleanArray;
87 import android.view.Gravity;
88 import android.view.HapticFeedbackConstants;
89 import android.view.KeyEvent;
90 import android.view.LayoutInflater;
91 import android.view.MotionEvent;
92 import android.view.View;
93 import android.view.ViewDebug;
94 import android.view.ViewGroup;
95 import android.view.ViewTreeObserver.OnScrollChangedListener;
96 import android.view.accessibility.AccessibilityEvent;
97 import android.view.accessibility.AccessibilityNodeInfo;
98 import android.view.animation.Interpolator;
99 import android.widget.FrameLayout;
100 import android.widget.ListView;
101 import android.widget.OverScroller;
102 
103 import androidx.annotation.Nullable;
104 import androidx.annotation.UiThread;
105 import androidx.core.graphics.ColorUtils;
106 
107 import com.android.launcher3.BaseActivity;
108 import com.android.launcher3.BaseActivity.MultiWindowModeChangedListener;
109 import com.android.launcher3.DeviceProfile;
110 import com.android.launcher3.Insettable;
111 import com.android.launcher3.InvariantDeviceProfile;
112 import com.android.launcher3.PagedView;
113 import com.android.launcher3.R;
114 import com.android.launcher3.Utilities;
115 import com.android.launcher3.anim.AnimationSuccessListener;
116 import com.android.launcher3.anim.AnimatorListeners;
117 import com.android.launcher3.anim.AnimatorPlaybackController;
118 import com.android.launcher3.anim.PendingAnimation;
119 import com.android.launcher3.anim.SpringProperty;
120 import com.android.launcher3.compat.AccessibilityManagerCompat;
121 import com.android.launcher3.config.FeatureFlags;
122 import com.android.launcher3.icons.cache.HandlerRunnable;
123 import com.android.launcher3.statehandlers.DepthController;
124 import com.android.launcher3.statemanager.BaseState;
125 import com.android.launcher3.statemanager.StatefulActivity;
126 import com.android.launcher3.touch.OverScroll;
127 import com.android.launcher3.touch.PagedOrientationHandler;
128 import com.android.launcher3.util.DynamicResource;
129 import com.android.launcher3.util.IntSet;
130 import com.android.launcher3.util.MultiValueAlpha;
131 import com.android.launcher3.util.ResourceBasedOverride.Overrides;
132 import com.android.launcher3.util.SplitConfigurationOptions;
133 import com.android.launcher3.util.RunnableList;
134 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
135 import com.android.launcher3.util.Themes;
136 import com.android.launcher3.util.TranslateEdgeEffect;
137 import com.android.launcher3.util.ViewPool;
138 import com.android.quickstep.AnimatedFloat;
139 import com.android.quickstep.BaseActivityInterface;
140 import com.android.quickstep.GestureState;
141 import com.android.quickstep.RecentsAnimationController;
142 import com.android.quickstep.RecentsAnimationTargets;
143 import com.android.quickstep.RecentsModel;
144 import com.android.quickstep.RecentsModel.TaskVisualsChangeListener;
145 import com.android.quickstep.RemoteAnimationTargets;
146 import com.android.quickstep.SystemUiProxy;
147 import com.android.quickstep.TaskOverlayFactory;
148 import com.android.quickstep.TaskThumbnailCache;
149 import com.android.quickstep.TaskViewUtils;
150 import com.android.quickstep.ViewUtils;
151 import com.android.quickstep.util.LayoutUtils;
152 import com.android.quickstep.util.RecentsOrientedState;
153 import com.android.quickstep.util.SplitScreenBounds;
154 import com.android.quickstep.util.SplitSelectStateController;
155 import com.android.quickstep.util.SurfaceTransactionApplier;
156 import com.android.quickstep.util.TaskViewSimulator;
157 import com.android.quickstep.util.TransformParams;
158 import com.android.systemui.plugins.ResourceProvider;
159 import com.android.systemui.shared.recents.model.Task;
160 import com.android.systemui.shared.recents.model.Task.TaskKey;
161 import com.android.systemui.shared.recents.model.ThumbnailData;
162 import com.android.systemui.shared.system.ActivityManagerWrapper;
163 import com.android.systemui.shared.system.PackageManagerWrapper;
164 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
165 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
166 import com.android.systemui.shared.system.TaskStackChangeListener;
167 import com.android.systemui.shared.system.TaskStackChangeListeners;
168 import com.android.wm.shell.pip.IPipAnimationListener;
169 
170 import java.util.ArrayList;
171 import java.util.List;
172 import java.util.function.Consumer;
173 
174 /**
175  * A list of recent tasks.
176  */
177 @TargetApi(Build.VERSION_CODES.R)
178 public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE>,
179         STATE_TYPE extends BaseState<STATE_TYPE>> extends PagedView implements Insettable,
180         TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
181         TaskVisualsChangeListener, SplitScreenBounds.OnChangeListener {
182 
183     // TODO(b/184899234): We use this timeout to wait a fixed period after switching to the
184     // screenshot when dismissing the current live task to ensure the app can try and get stopped.
185     private static final int REMOVE_TASK_WAIT_FOR_APP_STOP_MS = 100;
186 
187     public static final FloatProperty<RecentsView> CONTENT_ALPHA =
188             new FloatProperty<RecentsView>("contentAlpha") {
189                 @Override
190                 public void setValue(RecentsView view, float v) {
191                     view.setContentAlpha(v);
192                 }
193 
194                 @Override
195                 public Float get(RecentsView view) {
196                     return view.getContentAlpha();
197                 }
198             };
199 
200     public static final FloatProperty<RecentsView> FULLSCREEN_PROGRESS =
201             new FloatProperty<RecentsView>("fullscreenProgress") {
202                 @Override
203                 public void setValue(RecentsView recentsView, float v) {
204                     recentsView.setFullscreenProgress(v);
205                 }
206 
207                 @Override
208                 public Float get(RecentsView recentsView) {
209                     return recentsView.mFullscreenProgress;
210                 }
211             };
212 
213     public static final FloatProperty<RecentsView> TASK_MODALNESS =
214             new FloatProperty<RecentsView>("taskModalness") {
215                 @Override
216                 public void setValue(RecentsView recentsView, float v) {
217                     recentsView.setTaskModalness(v);
218                 }
219 
220                 @Override
221                 public Float get(RecentsView recentsView) {
222                     return recentsView.mTaskModalness;
223                 }
224             };
225 
226     public static final FloatProperty<RecentsView> ADJACENT_PAGE_HORIZONTAL_OFFSET =
227             new FloatProperty<RecentsView>("adjacentPageHorizontalOffset") {
228                 @Override
229                 public void setValue(RecentsView recentsView, float v) {
230                     if (recentsView.mAdjacentPageHorizontalOffset != v) {
231                         recentsView.mAdjacentPageHorizontalOffset = v;
232                         recentsView.updatePageOffsets();
233                     }
234                 }
235 
236                 @Override
237                 public Float get(RecentsView recentsView) {
238                     return recentsView.mAdjacentPageHorizontalOffset;
239                 }
240             };
241 
242     /**
243      * Can be used to tint the color of the RecentsView to simulate a scrim that can views
244      * excluded from. Really should be a proper scrim.
245      * TODO(b/187528071): Remove this and replace with a real scrim.
246      */
247     private static final FloatProperty<RecentsView> COLOR_TINT =
248             new FloatProperty<RecentsView>("colorTint") {
249                 @Override
250                 public void setValue(RecentsView recentsView, float v) {
251                     recentsView.setColorTint(v);
252                 }
253 
254                 @Override
255                 public Float get(RecentsView recentsView) {
256                     return recentsView.getColorTint();
257                 }
258             };
259 
260     /**
261      * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance property, they
262      * are currently both used to apply secondary translation. Should their use cases change to be
263      * more specific, we'd want to create a similar FloatProperty just for a TaskView's
264      * offsetX/Y property
265      */
266     public static final FloatProperty<RecentsView> TASK_SECONDARY_TRANSLATION =
267             new FloatProperty<RecentsView>("taskSecondaryTranslation") {
268                 @Override
269                 public void setValue(RecentsView recentsView, float v) {
270                     recentsView.setTaskViewsResistanceTranslation(v);
271                 }
272 
273                 @Override
274                 public Float get(RecentsView recentsView) {
275                     return recentsView.mTaskViewsSecondaryTranslation;
276                 }
277             };
278 
279     /**
280      * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance property, they
281      * are currently both used to apply secondary translation. Should their use cases change to be
282      * more specific, we'd want to create a similar FloatProperty just for a TaskView's
283      * offsetX/Y property
284      */
285     public static final FloatProperty<RecentsView> TASK_PRIMARY_SPLIT_TRANSLATION =
286             new FloatProperty<RecentsView>("taskPrimarySplitTranslation") {
287                 @Override
288                 public void setValue(RecentsView recentsView, float v) {
289                     recentsView.setTaskViewsPrimarySplitTranslation(v);
290                 }
291 
292                 @Override
293                 public Float get(RecentsView recentsView) {
294                     return recentsView.mTaskViewsPrimarySplitTranslation;
295                 }
296             };
297 
298     public static final FloatProperty<RecentsView> TASK_SECONDARY_SPLIT_TRANSLATION =
299             new FloatProperty<RecentsView>("taskSecondarySplitTranslation") {
300                 @Override
301                 public void setValue(RecentsView recentsView, float v) {
302                     recentsView.setTaskViewsSecondarySplitTranslation(v);
303                 }
304 
305                 @Override
306                 public Float get(RecentsView recentsView) {
307                     return recentsView.mTaskViewsSecondarySplitTranslation;
308                 }
309             };
310 
311     /** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */
312     public static final FloatProperty<RecentsView> RECENTS_SCALE_PROPERTY =
313             new FloatProperty<RecentsView>("recentsScale") {
314                 @Override
315                 public void setValue(RecentsView view, float scale) {
316                     view.setScaleX(scale);
317                     view.setScaleY(scale);
318                     view.mLastComputedTaskStartPushOutDistance = null;
319                     view.mLastComputedTaskEndPushOutDistance = null;
320                     view.mLiveTileTaskViewSimulator.recentsViewScale.value = scale;
321                     view.setTaskViewsResistanceTranslation(view.mTaskViewsSecondaryTranslation);
322                     view.updatePageOffsets();
323                 }
324 
325                 @Override
326                 public Float get(RecentsView view) {
327                     return view.getScaleX();
328                 }
329             };
330 
331     public static final FloatProperty<RecentsView> RECENTS_GRID_PROGRESS =
332             new FloatProperty<RecentsView>("recentsGrid") {
333                 @Override
334                 public void setValue(RecentsView view, float gridProgress) {
335                     view.setGridProgress(gridProgress);
336                 }
337 
338                 @Override
339                 public Float get(RecentsView view) {
340                     return view.mGridProgress;
341                 }
342             };
343 
344     // OverScroll constants
345     private static final int OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION = 270;
346 
347     private static final int DISMISS_TASK_DURATION = 300;
348     private static final int ADDITION_TASK_DURATION = 200;
349     private static final float INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.55f;
350     private static final float ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.05f;
351 
352     protected final RecentsOrientedState mOrientationState;
353     protected final BaseActivityInterface<STATE_TYPE, ACTIVITY_TYPE> mSizeStrategy;
354     protected RecentsAnimationController mRecentsAnimationController;
355     protected SurfaceTransactionApplier mSyncTransactionApplier;
356     protected int mTaskWidth;
357     protected int mTaskHeight;
358     protected final TransformParams mLiveTileParams = new TransformParams();
359     protected final TaskViewSimulator mLiveTileTaskViewSimulator;
360     protected final Rect mLastComputedTaskSize = new Rect();
361     protected final Rect mLastComputedGridSize = new Rect();
362     protected final Rect mLastComputedGridTaskSize = new Rect();
363     // How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot.
364     protected Float mLastComputedTaskStartPushOutDistance = null;
365     protected Float mLastComputedTaskEndPushOutDistance = null;
366     protected boolean mEnableDrawingLiveTile = false;
367     protected final Rect mTempRect = new Rect();
368     protected final RectF mTempRectF = new RectF();
369     private final PointF mTempPointF = new PointF();
370     private final float[] mTempFloat = new float[1];
371     private final List<OnScrollChangedListener> mScrollListeners = new ArrayList<>();
372 
373     // The threshold at which we update the SystemUI flags when animating from the task into the app
374     public static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.85f;
375 
376     protected final ACTIVITY_TYPE mActivity;
377     private final float mFastFlingVelocity;
378     private final RecentsModel mModel;
379     private final int mRowSpacing;
380     private final int mGridSideMargin;
381     private final ClearAllButton mClearAllButton;
382     private final Rect mClearAllButtonDeadZoneRect = new Rect();
383     private final Rect mTaskViewDeadZoneRect = new Rect();
384     /**
385      * Reflects if Recents is currently in the middle of a gesture
386      */
387     private boolean mGestureActive;
388 
389     // Keeps track of the previously known visible tasks for purposes of loading/unloading task data
390     private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray();
391 
392     private final InvariantDeviceProfile mIdp;
393 
394     private final ViewPool<TaskView> mTaskViewPool;
395 
396     private final TaskOverlayFactory mTaskOverlayFactory;
397 
398     protected boolean mDisallowScrollToClearAll;
399     private boolean mOverlayEnabled;
400     protected boolean mFreezeViewVisibility;
401     private boolean mOverviewGridEnabled;
402     private boolean mOverviewFullscreenEnabled;
403 
404     private float mAdjacentPageHorizontalOffset = 0;
405     protected float mTaskViewsSecondaryTranslation = 0;
406     protected float mTaskViewsPrimarySplitTranslation = 0;
407     protected float mTaskViewsSecondarySplitTranslation = 0;
408     // Progress from 0 to 1 where 0 is a carousel and 1 is a 2 row grid.
409     private float mGridProgress = 0;
410     private final IntSet mTopRowIdSet = new IntSet();
411 
412     // The GestureEndTarget that is still in progress.
413     protected GestureState.GestureEndTarget mCurrentGestureEndTarget;
414 
415     // TODO(b/187528071): Remove these and replace with a real scrim.
416     private float mColorTint;
417     private final int mTintingColor;
418     private ObjectAnimator mTintingAnimator;
419 
420     private int mOverScrollShift = 0;
421 
422     /**
423      * TODO: Call reloadIdNeeded in onTaskStackChanged.
424      */
425     private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
426         @Override
427         public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
428             if (!mHandleTaskStackChanges) {
429                 return;
430             }
431             // Check this is for the right user
432             if (!checkCurrentOrManagedUserId(userId, getContext())) {
433                 return;
434             }
435 
436             // Remove the task immediately from the task list
437             TaskView taskView = getTaskView(taskId);
438             if (taskView != null) {
439                 removeView(taskView);
440             }
441         }
442 
443         @Override
444         public void onActivityUnpinned() {
445             if (!mHandleTaskStackChanges) {
446                 return;
447             }
448 
449             reloadIfNeeded();
450             enableLayoutTransitions();
451         }
452 
453         @Override
454         public void onTaskRemoved(int taskId) {
455             if (!mHandleTaskStackChanges) {
456                 return;
457             }
458 
459             TaskView taskView = getTaskView(taskId);
460             if (taskView == null) {
461                 return;
462             }
463             Task.TaskKey taskKey = taskView.getTask().key;
464             UI_HELPER_EXECUTOR.execute(new HandlerRunnable<>(
465                     UI_HELPER_EXECUTOR.getHandler(),
466                     () -> PackageManagerWrapper.getInstance()
467                             .getActivityInfo(taskKey.getComponent(), taskKey.userId) == null,
468                     MAIN_EXECUTOR,
469                     apkRemoved -> {
470                         if (apkRemoved) {
471                             dismissTask(taskId);
472                         } else {
473                             mModel.isTaskRemoved(taskKey.id, taskRemoved -> {
474                                 if (taskRemoved) {
475                                     dismissTask(taskId);
476                                 }
477                             });
478                         }
479                     }));
480         }
481     };
482 
483     private final PinnedStackAnimationListener mIPipAnimationListener =
484             new PinnedStackAnimationListener();
485     private int mPipCornerRadius;
486 
487     // Used to keep track of the last requested task list id, so that we do not request to load the
488     // tasks again if we have already requested it and the task list has not changed
489     private int mTaskListChangeId = -1;
490 
491     // Only valid until the launcher state changes to NORMAL
492     protected int mRunningTaskId = -1;
493     protected boolean mRunningTaskTileHidden;
494     private Task mTmpRunningTask;
495     protected int mFocusedTaskId = -1;
496     private float mFocusedTaskRatio;
497 
498     private boolean mRunningTaskIconScaledDown = false;
499     private boolean mRunningTaskShowScreenshot = false;
500 
501     private boolean mOverviewStateEnabled;
502     private boolean mHandleTaskStackChanges;
503     private boolean mSwipeDownShouldLaunchApp;
504     private boolean mTouchDownToStartHome;
505     private final float mSquaredTouchSlop;
506     private int mDownX;
507     private int mDownY;
508 
509     private PendingAnimation mPendingAnimation;
510     private LayoutTransition mLayoutTransition;
511 
512     @ViewDebug.ExportedProperty(category = "launcher")
513     protected float mContentAlpha = 1;
514     @ViewDebug.ExportedProperty(category = "launcher")
515     protected float mFullscreenProgress = 0;
516     /**
517      * How modal is the current task to be displayed, 1 means the task is fully modal and no other
518      * tasks are show. 0 means the task is displays in context in the list with other tasks.
519      */
520     @ViewDebug.ExportedProperty(category = "launcher")
521     protected float mTaskModalness = 0;
522 
523     // Keeps track of task id whose visual state should not be reset
524     private int mIgnoreResetTaskId = -1;
525 
526     // Variables for empty state
527     private final Drawable mEmptyIcon;
528     private final CharSequence mEmptyMessage;
529     private final TextPaint mEmptyMessagePaint;
530     private final Point mLastMeasureSize = new Point();
531     private final int mEmptyMessagePadding;
532     private boolean mShowEmptyMessage;
533     private OnEmptyMessageUpdatedListener mOnEmptyMessageUpdatedListener;
534     private Layout mEmptyTextLayout;
535 
536     /**
537      * Placeholder view indicating where the first split screen selected app will be placed
538      */
539     private SplitPlaceholderView mSplitPlaceholderView;
540     /**
541      * The first task that split screen selection was initiated with. When split select state is
542      * initialized, we create a
543      * {@link #createTaskDismissAnimation(TaskView, boolean, boolean, long)} for this TaskView but
544      * don't actually remove the task since the user might back out. As such, we also ensure this
545      * View doesn't go back into the {@link #mTaskViewPool}, see {@link #onViewRemoved(View)}
546      */
547     private TaskView mSplitHiddenTaskView;
548     /**
549      * Keeps track of the index of the TaskView that split screen was initialized with so we know
550      * where to insert it back into list of taskViews in case user backs out of entering split
551      * screen.
552      * NOTE: This index is the index while {@link #mSplitHiddenTaskView} was a child of recentsView,
553      * this doesn't get adjusted to reflect the new child count after the taskView is dismissed/
554      * removed from recentsView
555      */
556     private int mSplitHiddenTaskViewIndex;
557 
558     // Keeps track of the index where the first TaskView should be
559     private int mTaskViewStartIndex = 0;
560     private OverviewActionsView mActionsView;
561 
562     private MultiWindowModeChangedListener mMultiWindowModeChangedListener =
563             new MultiWindowModeChangedListener() {
564                 @Override
565                 public void onMultiWindowModeChanged(boolean inMultiWindowMode) {
566                     mOrientationState.setMultiWindowMode(inMultiWindowMode);
567                     setLayoutRotation(mOrientationState.getTouchRotation(),
568                             mOrientationState.getDisplayRotation());
569                     updateChildTaskOrientations();
570                     if (!inMultiWindowMode && mOverviewStateEnabled) {
571                         // TODO: Re-enable layout transitions for addition of the unpinned task
572                         reloadIfNeeded();
573                     }
574                 }
575             };
576 
577     private RunnableList mSideTaskLaunchCallback;
578 
RecentsView(Context context, AttributeSet attrs, int defStyleAttr, BaseActivityInterface sizeStrategy)579     public RecentsView(Context context, AttributeSet attrs, int defStyleAttr,
580             BaseActivityInterface sizeStrategy) {
581         super(context, attrs, defStyleAttr);
582         setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
583         setEnableFreeScroll(true);
584         mSizeStrategy = sizeStrategy;
585         mActivity = BaseActivity.fromContext(context);
586         mOrientationState = new RecentsOrientedState(
587                 context, mSizeStrategy, this::animateRecentsRotationInPlace);
588         final int rotation = mActivity.getDisplay().getRotation();
589         mOrientationState.setRecentsRotation(rotation);
590 
591         mFastFlingVelocity = getResources()
592                 .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
593         mModel = RecentsModel.INSTANCE.get(context);
594         mIdp = InvariantDeviceProfile.INSTANCE.get(context);
595 
596         mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
597                 .inflate(R.layout.overview_clear_all_button, this, false);
598         mClearAllButton.setOnClickListener(this::dismissAllTasks);
599         mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
600                 10 /* initial size */);
601 
602         mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
603         setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
604         mRowSpacing = getResources().getDimensionPixelSize(R.dimen.overview_grid_row_spacing);
605         mGridSideMargin = getResources().getDimensionPixelSize(R.dimen.overview_grid_side_margin);
606         mSquaredTouchSlop = squaredTouchSlop(context);
607 
608         mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents);
609         mEmptyIcon.setCallback(this);
610         mEmptyMessage = context.getText(R.string.recents_empty_message);
611         mEmptyMessagePaint = new TextPaint();
612         mEmptyMessagePaint.setColor(Themes.getAttrColor(context, android.R.attr.textColorPrimary));
613         mEmptyMessagePaint.setTextSize(getResources()
614                 .getDimension(R.dimen.recents_empty_message_text_size));
615         mEmptyMessagePaint.setTypeface(Typeface.create(Themes.getDefaultBodyFont(context),
616                 Typeface.NORMAL));
617         mEmptyMessagePaint.setAntiAlias(true);
618         mEmptyMessagePadding = getResources()
619                 .getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
620         setWillNotDraw(false);
621         updateEmptyMessage();
622         mOrientationHandler = mOrientationState.getOrientationHandler();
623 
624         mTaskOverlayFactory = Overrides.getObject(
625                 TaskOverlayFactory.class,
626                 context.getApplicationContext(),
627                 R.string.task_overlay_factory_class);
628 
629         // Initialize quickstep specific cache params here, as this is constructed only once
630         mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
631 
632         mLiveTileTaskViewSimulator = new TaskViewSimulator(getContext(), getSizeStrategy(),
633                 true /* isForLiveTile */);
634         mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
635         mLiveTileTaskViewSimulator.setOrientationState(mOrientationState);
636         mLiveTileTaskViewSimulator.setDrawsBelowRecents(true);
637 
638         mTintingColor = getForegroundScrimDimColor(context);
639     }
640 
getScroller()641     public OverScroller getScroller() {
642         return mScroller;
643     }
644 
isRtl()645     public boolean isRtl() {
646         return mIsRtl;
647     }
648 
649     @Override
initEdgeEffect()650     protected void initEdgeEffect() {
651         mEdgeGlowLeft = new TranslateEdgeEffect(getContext());
652         mEdgeGlowRight = new TranslateEdgeEffect(getContext());
653     }
654 
655     @Override
drawEdgeEffect(Canvas canvas)656     protected void drawEdgeEffect(Canvas canvas) {
657         // Do not draw edge effect
658     }
659 
660     @Override
dispatchDraw(Canvas canvas)661     protected void dispatchDraw(Canvas canvas) {
662         // Draw overscroll
663         if (mAllowOverScroll && (!mEdgeGlowRight.isFinished() || !mEdgeGlowLeft.isFinished())) {
664             final int restoreCount = canvas.save();
665 
666             int primarySize = mOrientationHandler.getPrimaryValue(getWidth(), getHeight());
667             int scroll = OverScroll.dampedScroll(getUndampedOverScrollShift(), primarySize);
668             mOrientationHandler.set(canvas, CANVAS_TRANSLATE, scroll);
669 
670             if (mOverScrollShift != scroll) {
671                 mOverScrollShift = scroll;
672                 dispatchScrollChanged();
673             }
674 
675             super.dispatchDraw(canvas);
676             canvas.restoreToCount(restoreCount);
677         } else {
678             if (mOverScrollShift != 0) {
679                 mOverScrollShift = 0;
680                 dispatchScrollChanged();
681             }
682             super.dispatchDraw(canvas);
683         }
684         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
685                 && mLiveTileParams.getTargetSet() != null) {
686             redrawLiveTile();
687         }
688     }
689 
getUndampedOverScrollShift()690     private float getUndampedOverScrollShift() {
691         final int width = getWidth();
692         final int height = getHeight();
693         int primarySize = mOrientationHandler.getPrimaryValue(width, height);
694         int secondarySize = mOrientationHandler.getSecondaryValue(width, height);
695 
696         float effectiveShift = 0;
697         if (!mEdgeGlowLeft.isFinished()) {
698             mEdgeGlowLeft.setSize(secondarySize, primarySize);
699             if (((TranslateEdgeEffect) mEdgeGlowLeft).getTranslationShift(mTempFloat)) {
700                 effectiveShift = mTempFloat[0];
701                 postInvalidateOnAnimation();
702             }
703         }
704         if (!mEdgeGlowRight.isFinished()) {
705             mEdgeGlowRight.setSize(secondarySize, primarySize);
706             if (((TranslateEdgeEffect) mEdgeGlowRight).getTranslationShift(mTempFloat)) {
707                 effectiveShift -= mTempFloat[0];
708                 postInvalidateOnAnimation();
709             }
710         }
711 
712         return effectiveShift * primarySize;
713     }
714 
715     /**
716      * Returns the view shift due to overscroll
717      */
getOverScrollShift()718     public int getOverScrollShift() {
719         return mOverScrollShift;
720     }
721 
722     @Override
onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData)723     public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) {
724         if (mHandleTaskStackChanges) {
725             TaskView taskView = getTaskView(taskId);
726             if (taskView != null) {
727                 Task task = taskView.getTask();
728                 taskView.getThumbnail().setThumbnail(task, thumbnailData);
729                 return task;
730             }
731         }
732         return null;
733     }
734 
735     @Override
onTaskIconChanged(String pkg, UserHandle user)736     public void onTaskIconChanged(String pkg, UserHandle user) {
737         for (int i = 0; i < getTaskViewCount(); i++) {
738             TaskView tv = getTaskViewAt(i);
739             Task task = tv.getTask();
740             if (task != null && task.key != null && pkg.equals(task.key.getPackageName())
741                     && task.key.userId == user.getIdentifier()) {
742                 task.icon = null;
743                 if (tv.getIconView().getDrawable() != null) {
744                     tv.onTaskListVisibilityChanged(true /* visible */);
745                 }
746             }
747         }
748     }
749 
750     /**
751      * Update the thumbnail of the task.
752      * @param refreshNow Refresh immediately if it's true.
753      */
updateThumbnail(int taskId, ThumbnailData thumbnailData, boolean refreshNow)754     public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData, boolean refreshNow) {
755         TaskView taskView = getTaskView(taskId);
756         if (taskView != null) {
757             taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData, refreshNow);
758         }
759         return taskView;
760     }
761 
762     @Override
onWindowVisibilityChanged(int visibility)763     protected void onWindowVisibilityChanged(int visibility) {
764         super.onWindowVisibilityChanged(visibility);
765         updateTaskStackListenerState();
766     }
767 
init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView)768     public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
769         mActionsView = actionsView;
770         mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
771         mSplitPlaceholderView = splitPlaceholderView;
772     }
773 
getSplitPlaceholder()774     public SplitPlaceholderView getSplitPlaceholder() {
775         return mSplitPlaceholderView;
776     }
777 
isSplitSelectionActive()778     public boolean isSplitSelectionActive() {
779         return mSplitPlaceholderView.getSplitController().isSplitSelectActive();
780     }
781 
782     @Override
onAttachedToWindow()783     protected void onAttachedToWindow() {
784         super.onAttachedToWindow();
785         updateTaskStackListenerState();
786         mModel.getThumbnailCache().getHighResLoadingState().addCallback(this);
787         mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
788         TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
789         mSyncTransactionApplier = new SurfaceTransactionApplier(this);
790         mLiveTileParams.setSyncTransactionApplier(mSyncTransactionApplier);
791         RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
792         mIPipAnimationListener.setActivityAndRecentsView(mActivity, this);
793         SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(
794                 mIPipAnimationListener);
795         mOrientationState.initListeners();
796         SplitScreenBounds.INSTANCE.addOnChangeListener(this);
797         mTaskOverlayFactory.initListeners();
798     }
799 
800     @Override
onDetachedFromWindow()801     protected void onDetachedFromWindow() {
802         super.onDetachedFromWindow();
803         updateTaskStackListenerState();
804         mModel.getThumbnailCache().getHighResLoadingState().removeCallback(this);
805         mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
806         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
807         mSyncTransactionApplier = null;
808         mLiveTileParams.setSyncTransactionApplier(null);
809         executeSideTaskLaunchCallback();
810         RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
811         SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
812         SplitScreenBounds.INSTANCE.removeOnChangeListener(this);
813         mIPipAnimationListener.setActivityAndRecentsView(null, null);
814         mOrientationState.destroyListeners();
815         mTaskOverlayFactory.removeListeners();
816     }
817 
818     @Override
onViewRemoved(View child)819     public void onViewRemoved(View child) {
820         super.onViewRemoved(child);
821 
822         // Clear the task data for the removed child if it was visible unless it's the initial
823         // taskview for entering split screen, we only pretend to dismiss the task
824         if (child instanceof TaskView && child != mSplitHiddenTaskView) {
825             TaskView taskView = (TaskView) child;
826             mHasVisibleTaskData.delete(taskView.getTask().key.id);
827             mTaskViewPool.recycle(taskView);
828             mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
829         }
830         updateTaskStartIndex(child);
831     }
832 
833     @Override
onViewAdded(View child)834     public void onViewAdded(View child) {
835         super.onViewAdded(child);
836         child.setAlpha(mContentAlpha);
837         // RecentsView is set to RTL in the constructor when system is using LTR. Here we set the
838         // child direction back to match system settings.
839         child.setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_LTR : View.LAYOUT_DIRECTION_RTL);
840         updateTaskStartIndex(child);
841         mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, false);
842         updateEmptyMessage();
843     }
844 
845     @Override
draw(Canvas canvas)846     public void draw(Canvas canvas) {
847         maybeDrawEmptyMessage(canvas);
848         super.draw(canvas);
849     }
850 
addSideTaskLaunchCallback(RunnableList callback)851     public void addSideTaskLaunchCallback(RunnableList callback) {
852         if (mSideTaskLaunchCallback == null) {
853             mSideTaskLaunchCallback = new RunnableList();
854         }
855         mSideTaskLaunchCallback.add(callback::executeAllAndDestroy);
856     }
857 
executeSideTaskLaunchCallback()858     private void executeSideTaskLaunchCallback() {
859         if (mSideTaskLaunchCallback != null) {
860             mSideTaskLaunchCallback.executeAllAndDestroy();
861             mSideTaskLaunchCallback = null;
862         }
863     }
864 
launchSideTaskInLiveTileModeForRestartedApp(int taskId)865     public void launchSideTaskInLiveTileModeForRestartedApp(int taskId) {
866         if (mRunningTaskId != -1 && mRunningTaskId == taskId) {
867             RemoteAnimationTargets targets = getLiveTileParams().getTargetSet();
868             if (targets != null && targets.findTask(taskId) != null) {
869                 launchSideTaskInLiveTileMode(taskId, targets.apps, targets.wallpapers,
870                         targets.nonApps);
871             }
872         }
873     }
874 
launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTargetCompat[] apps, RemoteAnimationTargetCompat[] wallpaper, RemoteAnimationTargetCompat[] nonApps)875     public void launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTargetCompat[] apps,
876             RemoteAnimationTargetCompat[] wallpaper, RemoteAnimationTargetCompat[] nonApps) {
877         AnimatorSet anim = new AnimatorSet();
878         TaskView taskView = getTaskView(taskId);
879         if (taskView == null || !isTaskViewVisible(taskView)) {
880             // TODO: Refine this animation.
881             SurfaceTransactionApplier surfaceApplier =
882                     new SurfaceTransactionApplier(mActivity.getDragLayer());
883             ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
884             appAnimator.setDuration(RECENTS_LAUNCH_DURATION);
885             appAnimator.setInterpolator(ACCEL_DEACCEL);
886             appAnimator.addUpdateListener(valueAnimator -> {
887                 float percent = valueAnimator.getAnimatedFraction();
888                 SurfaceParams.Builder builder = new SurfaceParams.Builder(
889                         apps[apps.length - 1].leash);
890                 Matrix matrix = new Matrix();
891                 matrix.postScale(percent, percent);
892                 matrix.postTranslate(mActivity.getDeviceProfile().widthPx * (1 - percent) / 2,
893                         mActivity.getDeviceProfile().heightPx * (1 - percent) / 2);
894                 builder.withAlpha(percent).withMatrix(matrix);
895                 surfaceApplier.scheduleApply(builder.build());
896             });
897             anim.play(appAnimator);
898             anim.addListener(new AnimatorListenerAdapter() {
899                 @Override
900                 public void onAnimationEnd(Animator animation) {
901                     finishRecentsAnimation(false /* toRecents */, null);
902                 }
903             });
904         } else {
905             TaskViewUtils.composeRecentsLaunchAnimator(anim, taskView, apps, wallpaper, nonApps,
906                     true /* launcherClosing */, mActivity.getStateManager(), this,
907                     getDepthController());
908         }
909         anim.start();
910     }
911 
updateTaskStartIndex(View affectingView)912     private void updateTaskStartIndex(View affectingView) {
913         if (!(affectingView instanceof TaskView) && !(affectingView instanceof ClearAllButton)) {
914             int childCount = getChildCount();
915 
916             mTaskViewStartIndex = 0;
917             while (mTaskViewStartIndex < childCount
918                     && !(getChildAt(mTaskViewStartIndex) instanceof TaskView)) {
919                 mTaskViewStartIndex++;
920             }
921         }
922     }
923 
isTaskViewVisible(TaskView tv)924     public boolean isTaskViewVisible(TaskView tv) {
925         if (showAsGrid()) {
926             int screenStart = mOrientationHandler.getPrimaryScroll(this);
927             int screenEnd = screenStart + mOrientationHandler.getMeasuredSize(this);
928             return isTaskViewWithinBounds(tv, screenStart, screenEnd);
929         } else {
930             // For now, just check if it's the active task or an adjacent task
931             return Math.abs(indexOfChild(tv) - getNextPage()) <= 1;
932         }
933     }
934 
isTaskViewWithinBounds(TaskView tv, int start, int end)935     private boolean isTaskViewWithinBounds(TaskView tv, int start, int end) {
936         int taskStart = mOrientationHandler.getChildStart(tv) + (int) tv.getOffsetAdjustment(
937                 showAsFullscreen(), showAsGrid());
938         int taskSize = (int) (mOrientationHandler.getMeasuredSize(tv) * tv.getSizeAdjustment(
939                 showAsFullscreen()));
940         int taskEnd = taskStart + taskSize;
941         return (taskStart >= start && taskStart <= end) || (taskEnd >= start
942                 && taskEnd <= end);
943     }
944 
getTaskView(int taskId)945     public TaskView getTaskView(int taskId) {
946         for (int i = 0; i < getTaskViewCount(); i++) {
947             TaskView taskView = getTaskViewAt(i);
948             if (taskView.hasTaskId(taskId)) {
949                 return taskView;
950             }
951         }
952         return null;
953     }
954 
setOverviewStateEnabled(boolean enabled)955     public void setOverviewStateEnabled(boolean enabled) {
956         mOverviewStateEnabled = enabled;
957         updateTaskStackListenerState();
958         mOrientationState.setRotationWatcherEnabled(enabled);
959         if (!enabled) {
960             // Reset the running task when leaving overview since it can still have a reference to
961             // its thumbnail
962             mTmpRunningTask = null;
963             if (mSplitPlaceholderView.getSplitController().isSplitSelectActive()) {
964                 cancelSplitSelect(false);
965             }
966         }
967         updateLocusId();
968     }
969 
970     /**
971      * Whether the Clear All button is hidden or fully visible. Used to determine if center
972      * displayed page is a task or the Clear All button.
973      *
974      * @return True = Clear All button not fully visible, center page is a task. False = Clear All
975      * button fully visible, center page is Clear All button.
976      */
isClearAllHidden()977     public boolean isClearAllHidden() {
978         return mClearAllButton.getAlpha() != 1f;
979     }
980 
981     @Override
onPageBeginTransition()982     protected void onPageBeginTransition() {
983         super.onPageBeginTransition();
984         mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, true);
985     }
986 
987     @Override
onPageEndTransition()988     protected void onPageEndTransition() {
989         super.onPageEndTransition();
990         if (isClearAllHidden()) {
991             mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
992         }
993         if (getNextPage() > 0) {
994             setSwipeDownShouldLaunchApp(true);
995         }
996     }
997 
998     @Override
onTouchEvent(MotionEvent ev)999     public boolean onTouchEvent(MotionEvent ev) {
1000         super.onTouchEvent(ev);
1001 
1002         if (showAsGrid()) {
1003             int taskCount = getTaskViewCount();
1004             for (int i = 0; i < taskCount; i++) {
1005                 TaskView taskView = getTaskViewAt(i);
1006                 if (isTaskViewVisible(taskView) && taskView.offerTouchToChildren(ev)) {
1007                     // Keep consuming events to pass to delegate
1008                     return true;
1009                 }
1010             }
1011         } else {
1012             TaskView taskView = getCurrentPageTaskView();
1013             if (taskView != null && taskView.offerTouchToChildren(ev)) {
1014                 // Keep consuming events to pass to delegate
1015                 return true;
1016             }
1017         }
1018 
1019         final int x = (int) ev.getX();
1020         final int y = (int) ev.getY();
1021         switch (ev.getAction()) {
1022             case MotionEvent.ACTION_UP:
1023                 if (mTouchDownToStartHome) {
1024                     startHome();
1025                 }
1026                 mTouchDownToStartHome = false;
1027                 break;
1028             case MotionEvent.ACTION_CANCEL:
1029                 mTouchDownToStartHome = false;
1030                 break;
1031             case MotionEvent.ACTION_MOVE:
1032                 // Passing the touch slop will not allow dismiss to home
1033                 if (mTouchDownToStartHome &&
1034                         (isHandlingTouch() ||
1035                                 squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop)) {
1036                     mTouchDownToStartHome = false;
1037                 }
1038                 break;
1039             case MotionEvent.ACTION_DOWN:
1040                 // Touch down anywhere but the deadzone around the visible clear all button and
1041                 // between the task views will start home on touch up
1042                 if (!isHandlingTouch() && !isModal()) {
1043                     if (mShowEmptyMessage) {
1044                         mTouchDownToStartHome = true;
1045                     } else {
1046                         updateDeadZoneRects();
1047                         final boolean clearAllButtonDeadZoneConsumed =
1048                                 mClearAllButton.getAlpha() == 1
1049                                         && mClearAllButtonDeadZoneRect.contains(x, y);
1050                         final boolean cameFromNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
1051                         if (!clearAllButtonDeadZoneConsumed && !cameFromNavBar
1052                                 && !mTaskViewDeadZoneRect.contains(x + getScrollX(), y)) {
1053                             mTouchDownToStartHome = true;
1054                         }
1055                     }
1056                 }
1057                 mDownX = x;
1058                 mDownY = y;
1059                 break;
1060         }
1061 
1062         return isHandlingTouch();
1063     }
1064 
1065     @Override
onNotSnappingToPageInFreeScroll()1066     protected void onNotSnappingToPageInFreeScroll() {
1067         int finalPos = mScroller.getFinalX();
1068         if (!showAsGrid() && finalPos > mMinScroll && finalPos < mMaxScroll) {
1069             int firstPageScroll = getScrollForPage(!mIsRtl ? 0 : getPageCount() - 1);
1070             int lastPageScroll = getScrollForPage(!mIsRtl ? getPageCount() - 1 : 0);
1071 
1072             // If scrolling ends in the half of the added space that is closer to
1073             // the end, settle to the end. Otherwise snap to the nearest page.
1074             // If flinging past one of the ends, don't change the velocity as it
1075             // will get stopped at the end anyway.
1076             int pageSnapped = finalPos < (firstPageScroll + mMinScroll) / 2
1077                     ? mMinScroll
1078                     : finalPos > (lastPageScroll + mMaxScroll) / 2
1079                             ? mMaxScroll
1080                             : getScrollForPage(mNextPage);
1081 
1082             mScroller.setFinalX(pageSnapped);
1083             // Ensure the scroll/snap doesn't happen too fast;
1084             int extraScrollDuration = OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION
1085                     - mScroller.getDuration();
1086             if (extraScrollDuration > 0) {
1087                 mScroller.extendDuration(extraScrollDuration);
1088             }
1089         }
1090     }
1091 
1092     @Override
determineScrollingStart(MotionEvent ev, float touchSlopScale)1093     protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
1094         // Enables swiping to the left or right only if the task overlay is not modal.
1095         if (!isModal()) {
1096             super.determineScrollingStart(ev, touchSlopScale);
1097         }
1098     }
1099 
applyLoadPlan(ArrayList<Task> tasks)1100     protected void applyLoadPlan(ArrayList<Task> tasks) {
1101         if (mPendingAnimation != null) {
1102             mPendingAnimation.addEndListener(success -> applyLoadPlan(tasks));
1103             return;
1104         }
1105 
1106         if (tasks == null || tasks.isEmpty()) {
1107             removeTasksViewsAndClearAllButton();
1108             onTaskStackUpdated();
1109             return;
1110         }
1111 
1112         int currentTaskId = -1;
1113         TaskView currentTaskView = getTaskViewAtByAbsoluteIndex(mCurrentPage);
1114         if (currentTaskView != null) {
1115             currentTaskId = currentTaskView.getTask().key.id;
1116         }
1117 
1118         // Unload existing visible task data
1119         unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
1120 
1121         TaskView ignoreResetTaskView =
1122                 mIgnoreResetTaskId == -1 ? null : getTaskView(mIgnoreResetTaskId);
1123 
1124         final int requiredTaskCount = tasks.size();
1125         if (getTaskViewCount() != requiredTaskCount) {
1126             if (indexOfChild(mClearAllButton) != -1) {
1127                 removeView(mClearAllButton);
1128             }
1129             for (int i = getTaskViewCount(); i < requiredTaskCount; i++) {
1130                 addView(mTaskViewPool.getView());
1131             }
1132             while (getTaskViewCount() > requiredTaskCount) {
1133                 removeView(getChildAt(getChildCount() - 1));
1134             }
1135             if (requiredTaskCount > 0) {
1136                 addView(mClearAllButton);
1137             }
1138         }
1139 
1140         // Rebind and reset all task views
1141         for (int i = requiredTaskCount - 1; i >= 0; i--) {
1142             final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex;
1143             final Task task = tasks.get(i);
1144             final TaskView taskView = (TaskView) getChildAt(pageIndex);
1145             taskView.bind(task, mOrientationState);
1146         }
1147         updateTaskSize();
1148 
1149         if (mNextPage == INVALID_PAGE) {
1150             // Set the current page to the running task, but not if settling on new task.
1151             TaskView runningTaskView = getRunningTaskView();
1152             if (runningTaskView != null) {
1153                 setCurrentPage(indexOfChild(runningTaskView));
1154             } else if (getTaskViewCount() > 0) {
1155                 setCurrentPage(indexOfChild(getTaskViewAt(0)));
1156             }
1157         } else if (currentTaskId != -1) {
1158             currentTaskView = getTaskView(currentTaskId);
1159             if (currentTaskView != null) {
1160                 setCurrentPage(indexOfChild(currentTaskView));
1161             }
1162         }
1163 
1164         if (mIgnoreResetTaskId != -1 && getTaskView(mIgnoreResetTaskId) != ignoreResetTaskView) {
1165             // If the taskView mapping is changing, do not preserve the visuals. Since we are
1166             // mostly preserving the first task, and new taskViews are added to the end, it should
1167             // generally map to the same task.
1168             mIgnoreResetTaskId = -1;
1169         }
1170         resetTaskVisuals();
1171         onTaskStackUpdated();
1172         updateEnabledOverlays();
1173     }
1174 
isModal()1175     private boolean isModal() {
1176         return mTaskModalness > 0;
1177     }
1178 
isLoadingTasks()1179     public boolean isLoadingTasks() {
1180         return mModel.isLoadingTasksInBackground();
1181     }
1182 
removeTasksViewsAndClearAllButton()1183     private void removeTasksViewsAndClearAllButton() {
1184         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
1185             removeView(getTaskViewAt(i));
1186         }
1187         if (indexOfChild(mClearAllButton) != -1) {
1188             removeView(mClearAllButton);
1189         }
1190     }
1191 
getTaskViewCount()1192     public int getTaskViewCount() {
1193         int taskViewCount = getChildCount() - mTaskViewStartIndex;
1194         if (indexOfChild(mClearAllButton) != -1) {
1195             taskViewCount--;
1196         }
1197         return taskViewCount;
1198     }
1199 
onTaskStackUpdated()1200     protected void onTaskStackUpdated() {
1201         // Lazily update the empty message only when the task stack is reapplied
1202         updateEmptyMessage();
1203     }
1204 
resetTaskVisuals()1205     public void resetTaskVisuals() {
1206         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
1207             TaskView taskView = getTaskViewAt(i);
1208             if (mIgnoreResetTaskId != taskView.getTask().key.id) {
1209                 taskView.resetViewTransforms();
1210                 taskView.setStableAlpha(mContentAlpha);
1211                 taskView.setFullscreenProgress(mFullscreenProgress);
1212                 taskView.setModalness(mTaskModalness);
1213             }
1214         }
1215         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
1216             // Since we reuse the same mLiveTileTaskViewSimulator in the RecentsView, we need
1217             // to reset the params after it settles in Overview from swipe up so that we don't
1218             // render with obsolete param values.
1219             mLiveTileTaskViewSimulator.taskPrimaryTranslation.value = 0;
1220             mLiveTileTaskViewSimulator.taskSecondaryTranslation.value = 0;
1221             mLiveTileTaskViewSimulator.fullScreenProgress.value = 0;
1222             mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
1223 
1224             // Similar to setRunningTaskHidden below, reapply the state before runningTaskView is
1225             // null.
1226             if (!mRunningTaskShowScreenshot) {
1227                 setRunningTaskViewShowScreenshot(mRunningTaskShowScreenshot);
1228             }
1229         }
1230         if (mRunningTaskTileHidden) {
1231             setRunningTaskHidden(mRunningTaskTileHidden);
1232         }
1233 
1234         // Force apply the scale.
1235         if (mIgnoreResetTaskId != mRunningTaskId) {
1236             applyRunningTaskIconScale();
1237         }
1238 
1239         updateCurveProperties();
1240         // Update the set of visible task's data
1241         loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
1242         setTaskModalness(0);
1243         setColorTint(0);
1244     }
1245 
setFullscreenProgress(float fullscreenProgress)1246     public void setFullscreenProgress(float fullscreenProgress) {
1247         mFullscreenProgress = fullscreenProgress;
1248         int taskCount = getTaskViewCount();
1249         for (int i = 0; i < taskCount; i++) {
1250             getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress);
1251         }
1252         mClearAllButton.setFullscreenProgress(fullscreenProgress);
1253 
1254         // Fade out the actions view quickly (0.1 range)
1255         mActionsView.getFullscreenAlpha().setValue(
1256                 mapToRange(fullscreenProgress, 0, 0.1f, 1f, 0f, LINEAR));
1257     }
1258 
updateTaskStackListenerState()1259     private void updateTaskStackListenerState() {
1260         boolean handleTaskStackChanges = mOverviewStateEnabled && isAttachedToWindow()
1261                 && getWindowVisibility() == VISIBLE;
1262         if (handleTaskStackChanges != mHandleTaskStackChanges) {
1263             mHandleTaskStackChanges = handleTaskStackChanges;
1264             if (handleTaskStackChanges) {
1265                 reloadIfNeeded();
1266             }
1267         }
1268     }
1269 
1270     @Override
setInsets(Rect insets)1271     public void setInsets(Rect insets) {
1272         mInsets.set(insets);
1273 
1274         // Update DeviceProfile dependant state.
1275         DeviceProfile dp = mActivity.getDeviceProfile();
1276         setOverviewGridEnabled(
1277                 mActivity.getStateManager().getState().displayOverviewTasksAsGrid(dp));
1278 
1279         // Propagate DeviceProfile change event.
1280         mLiveTileTaskViewSimulator.setDp(dp);
1281         mActionsView.setDp(dp);
1282         mOrientationState.setDeviceProfile(dp);
1283 
1284         // Update RecentsView adn TaskView's DeviceProfile dependent layout.
1285         updateOrientationHandler();
1286     }
1287 
updateOrientationHandler()1288     private void updateOrientationHandler() {
1289         updateOrientationHandler(true);
1290     }
1291 
updateOrientationHandler(boolean forceRecreateDragLayerControllers)1292     private void updateOrientationHandler(boolean forceRecreateDragLayerControllers) {
1293         // Handle orientation changes.
1294         PagedOrientationHandler oldOrientationHandler = mOrientationHandler;
1295         mOrientationHandler = mOrientationState.getOrientationHandler();
1296 
1297         mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
1298         setLayoutDirection(mIsRtl
1299                 ? View.LAYOUT_DIRECTION_RTL
1300                 : View.LAYOUT_DIRECTION_LTR);
1301         mClearAllButton.setLayoutDirection(mIsRtl
1302                 ? View.LAYOUT_DIRECTION_LTR
1303                 : View.LAYOUT_DIRECTION_RTL);
1304         mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated());
1305 
1306         if (forceRecreateDragLayerControllers
1307                 || !mOrientationHandler.equals(oldOrientationHandler)) {
1308             // Changed orientations, update controllers so they intercept accordingly.
1309             mActivity.getDragLayer().recreateControllers();
1310         }
1311 
1312         boolean isInLandscape = mOrientationState.getTouchRotation() != ROTATION_0
1313                 || mOrientationState.getRecentsActivityRotation() != ROTATION_0;
1314         mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION,
1315                 !mOrientationState.canRecentsActivityRotate() && isInLandscape);
1316 
1317         // Update TaskView's DeviceProfile dependent layout.
1318         updateChildTaskOrientations();
1319 
1320         // Recalculate DeviceProfile dependent layout.
1321         updateSizeAndPadding();
1322 
1323         requestLayout();
1324         // Reapply the current page to update page scrolls.
1325         setCurrentPage(mCurrentPage);
1326     }
1327 
1328     // Update task size and padding that are dependent on DeviceProfile and insets.
updateSizeAndPadding()1329     private void updateSizeAndPadding() {
1330         DeviceProfile dp = mActivity.getDeviceProfile();
1331         getTaskSize(mTempRect);
1332         mTaskWidth = mTempRect.width();
1333         mTaskHeight = mTempRect.height();
1334 
1335         mTempRect.top -= dp.overviewTaskThumbnailTopMarginPx;
1336         setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top,
1337                 dp.widthPx - mInsets.right - mTempRect.right,
1338                 dp.heightPx - mInsets.bottom - mTempRect.bottom);
1339 
1340         mSizeStrategy.calculateGridSize(mActivity, mActivity.getDeviceProfile(),
1341                 mLastComputedGridSize);
1342         mSizeStrategy.calculateGridTaskSize(mActivity, mActivity.getDeviceProfile(),
1343                 mLastComputedGridTaskSize, mOrientationHandler);
1344 
1345         // Force TaskView to update size from thumbnail
1346         updateTaskSize();
1347 
1348         // Update ActionsView position
1349         if (mActionsView != null) {
1350             FrameLayout.LayoutParams layoutParams =
1351                     (FrameLayout.LayoutParams) mActionsView.getLayoutParams();
1352             if (dp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
1353                 layoutParams.gravity = Gravity.BOTTOM;
1354                 layoutParams.bottomMargin =
1355                         dp.heightPx - mInsets.bottom - mLastComputedGridSize.bottom;
1356                 layoutParams.leftMargin = mLastComputedTaskSize.left;
1357                 layoutParams.rightMargin = dp.widthPx - mLastComputedTaskSize.right;
1358                 // When in modal state, remove bottom margin to avoid covering content.
1359                 mActionsView.setModalTransformY(layoutParams.bottomMargin);
1360             } else {
1361                 layoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
1362                 layoutParams.bottomMargin = 0;
1363                 layoutParams.leftMargin = 0;
1364                 layoutParams.rightMargin = 0;
1365                 mActionsView.setModalTransformY(0);
1366             }
1367             mActionsView.setLayoutParams(layoutParams);
1368         }
1369     }
1370 
1371     /**
1372      * Updates TaskView scaling and translation required to support variable width.
1373      */
updateTaskSize()1374     private void updateTaskSize() {
1375         final int taskCount = getTaskViewCount();
1376         if (taskCount == 0) {
1377             return;
1378         }
1379 
1380         float accumulatedTranslationX = 0;
1381         for (int i = 0; i < taskCount; i++) {
1382             TaskView taskView = getTaskViewAt(i);
1383             taskView.updateTaskSize();
1384             taskView.getPrimaryFullscreenTranslationProperty().set(taskView,
1385                     accumulatedTranslationX);
1386             taskView.getSecondaryFullscreenTranslationProperty().set(taskView, 0f);
1387             // Compensate space caused by TaskView scaling.
1388             float widthDiff =
1389                     taskView.getLayoutParams().width * (1 - taskView.getFullscreenScale());
1390             accumulatedTranslationX += mIsRtl ? widthDiff : -widthDiff;
1391         }
1392 
1393         mClearAllButton.setFullscreenTranslationPrimary(accumulatedTranslationX);
1394 
1395         updateGridProperties();
1396     }
1397 
getTaskSize(Rect outRect)1398     public void getTaskSize(Rect outRect) {
1399         mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect,
1400                 mOrientationHandler);
1401         mLastComputedTaskSize.set(outRect);
1402     }
1403 
1404     /**
1405      * Returns the size of task selected to enter modal state.
1406      */
getSelectedTaskSize()1407     public Point getSelectedTaskSize() {
1408         mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), mTempRect,
1409                 mOrientationHandler);
1410         int taskWidth = mTempRect.width();
1411         int taskHeight = mTempRect.height();
1412         if (mFocusedTaskId != -1) {
1413             int boxLength = Math.max(taskWidth, taskHeight);
1414             if (mFocusedTaskRatio > 1) {
1415                 taskWidth = boxLength;
1416                 taskHeight = (int) (boxLength / mFocusedTaskRatio);
1417             } else {
1418                 taskWidth = (int) (boxLength * mFocusedTaskRatio);
1419                 taskHeight = boxLength;
1420             }
1421         }
1422         return new Point(taskWidth, taskHeight);
1423     }
1424 
1425     /** Gets the last computed task size */
getLastComputedTaskSize()1426     public Rect getLastComputedTaskSize() {
1427         return mLastComputedTaskSize;
1428     }
1429 
getLastComputedGridTaskSize()1430     public Rect getLastComputedGridTaskSize() {
1431         return mLastComputedGridTaskSize;
1432     }
1433 
1434     /** Gets the task size for modal state. */
getModalTaskSize(Rect outRect)1435     public void getModalTaskSize(Rect outRect) {
1436         mSizeStrategy.calculateModalTaskSize(mActivity, mActivity.getDeviceProfile(), outRect);
1437     }
1438 
1439     @Override
computeScrollHelper()1440     protected boolean computeScrollHelper() {
1441         boolean scrolling = super.computeScrollHelper();
1442         boolean isFlingingFast = false;
1443         updateCurveProperties();
1444         if (scrolling || isHandlingTouch()) {
1445             if (scrolling) {
1446                 // Check if we are flinging quickly to disable high res thumbnail loading
1447                 isFlingingFast = mScroller.getCurrVelocity() > mFastFlingVelocity;
1448             }
1449 
1450             // After scrolling, update the visible task's data
1451             loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
1452 
1453             // After scrolling, update ActionsView's visibility.
1454             TaskView focusedTaskView = getFocusedTaskView();
1455             if (focusedTaskView != null) {
1456                 float scrollDiff = Math.abs(getScrollForPage(indexOfChild(focusedTaskView))
1457                         - mOrientationHandler.getPrimaryScroll(this));
1458                 float delta = (mGridSideMargin - scrollDiff) / (float) mGridSideMargin;
1459                 mActionsView.getScrollAlpha().setValue(Utilities.boundToRange(delta, 0, 1));
1460             }
1461         }
1462 
1463         // Update the high res thumbnail loader state
1464         mModel.getThumbnailCache().getHighResLoadingState().setFlingingFast(isFlingingFast);
1465         return scrolling;
1466     }
1467 
1468     /**
1469      * Scales and adjusts translation of adjacent pages as if on a curved carousel.
1470      */
updateCurveProperties()1471     public void updateCurveProperties() {
1472         if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
1473             return;
1474         }
1475         int scroll = mOrientationHandler.getPrimaryScroll(this);
1476         mClearAllButton.onRecentsViewScroll(scroll, mOverviewGridEnabled);
1477     }
1478 
1479     @Override
getDestinationPage(int scaledScroll)1480     protected int getDestinationPage(int scaledScroll) {
1481         if (!(mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get())) {
1482             return super.getDestinationPage(scaledScroll);
1483         }
1484 
1485         final int childCount = getChildCount();
1486         if (mPageScrolls == null || childCount != mPageScrolls.length) {
1487             return -1;
1488         }
1489 
1490         // When in tablet with variable task width, return the page which scroll is closest to
1491         // screenStart instead of page nearest to center of screen.
1492         int minDistanceFromScreenStart = Integer.MAX_VALUE;
1493         int minDistanceFromScreenStartIndex = -1;
1494         for (int i = 0; i < childCount; ++i) {
1495             int distanceFromScreenStart = Math.abs(mPageScrolls[i] - scaledScroll);
1496             if (distanceFromScreenStart < minDistanceFromScreenStart) {
1497                 minDistanceFromScreenStart = distanceFromScreenStart;
1498                 minDistanceFromScreenStartIndex = i;
1499             }
1500         }
1501         return minDistanceFromScreenStartIndex;
1502     }
1503 
1504     /**
1505      * Iterates through all the tasks, and loads the associated task data for newly visible tasks,
1506      * and unloads the associated task data for tasks that are no longer visible.
1507      */
loadVisibleTaskData(@askView.TaskDataChanges int dataChanges)1508     public void loadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) {
1509         if (!mOverviewStateEnabled || mTaskListChangeId == -1) {
1510             // Skip loading visible task data if we've already left the overview state, or if the
1511             // task list hasn't been loaded yet (the task views will not reflect the task list)
1512             return;
1513         }
1514 
1515         int lower = 0;
1516         int upper = 0;
1517         int visibleStart = 0;
1518         int visibleEnd = 0;
1519         if (showAsGrid()) {
1520             int screenStart = mOrientationHandler.getPrimaryScroll(this);
1521             int pageOrientedSize = mOrientationHandler.getMeasuredSize(this);
1522             int halfScreenSize = pageOrientedSize / 2;
1523             // Use +/- 50% screen width as visible area.
1524             visibleStart = screenStart - halfScreenSize;
1525             visibleEnd = screenStart + pageOrientedSize + halfScreenSize;
1526         } else {
1527             int centerPageIndex = getPageNearestToCenterOfScreen();
1528             int numChildren = getChildCount();
1529             lower = Math.max(0, centerPageIndex - 2);
1530             upper = Math.min(centerPageIndex + 2, numChildren - 1);
1531         }
1532 
1533         // Update the task data for the in/visible children
1534         for (int i = 0; i < getTaskViewCount(); i++) {
1535             TaskView taskView = getTaskViewAt(i);
1536             Task task = taskView.getTask();
1537             int index = indexOfChild(taskView);
1538             boolean visible;
1539             if (showAsGrid()) {
1540                 visible = isTaskViewWithinBounds(taskView, visibleStart, visibleEnd);
1541             } else {
1542                 visible = lower <= index && index <= upper;
1543             }
1544             if (visible) {
1545                 if (task == mTmpRunningTask) {
1546                     // Skip loading if this is the task that we are animating into
1547                     continue;
1548                 }
1549                 if (!mHasVisibleTaskData.get(task.key.id)) {
1550                     // Ignore thumbnail update if it's current running task during the gesture
1551                     // We snapshot at end of gesture, it will update then
1552                     int changes = dataChanges;
1553                     if (taskView == getRunningTaskView() && mGestureActive) {
1554                         changes &= ~TaskView.FLAG_UPDATE_THUMBNAIL;
1555                     }
1556                     taskView.onTaskListVisibilityChanged(true /* visible */, changes);
1557                 }
1558                 mHasVisibleTaskData.put(task.key.id, visible);
1559             } else {
1560                 if (mHasVisibleTaskData.get(task.key.id)) {
1561                     taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges);
1562                 }
1563                 mHasVisibleTaskData.delete(task.key.id);
1564             }
1565         }
1566     }
1567 
1568     /**
1569      * Unloads any associated data from the currently visible tasks
1570      */
unloadVisibleTaskData(@askView.TaskDataChanges int dataChanges)1571     private void unloadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) {
1572         for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
1573             if (mHasVisibleTaskData.valueAt(i)) {
1574                 TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
1575                 if (taskView != null) {
1576                     taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges);
1577                 }
1578             }
1579         }
1580         mHasVisibleTaskData.clear();
1581     }
1582 
1583     @Override
onHighResLoadingStateChanged(boolean enabled)1584     public void onHighResLoadingStateChanged(boolean enabled) {
1585         // Whenever the high res loading state changes, poke each of the visible tasks to see if
1586         // they want to updated their thumbnail state
1587         for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
1588             if (mHasVisibleTaskData.valueAt(i)) {
1589                 TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
1590                 if (taskView != null) {
1591                     // Poke the view again, which will trigger it to load high res if the state
1592                     // is enabled
1593                     taskView.onTaskListVisibilityChanged(true /* visible */);
1594                 }
1595             }
1596         }
1597     }
1598 
startHome()1599     public abstract void startHome();
1600 
1601     /** `true` if there is a +1 space available in overview. */
hasRecentsExtraCard()1602     public boolean hasRecentsExtraCard() {
1603         return false;
1604     }
1605 
reset()1606     public void reset() {
1607         setCurrentTask(-1);
1608         mIgnoreResetTaskId = -1;
1609         mTaskListChangeId = -1;
1610         mFocusedTaskId = -1;
1611 
1612         if (mRecentsAnimationController != null) {
1613             if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile) {
1614                 // We are still drawing the live tile, finish it now to clean up.
1615                 finishRecentsAnimation(true /* toRecents */, null);
1616             } else {
1617                 mRecentsAnimationController = null;
1618             }
1619         }
1620         setEnableDrawingLiveTile(false);
1621         mLiveTileParams.setTargetSet(null);
1622         mLiveTileTaskViewSimulator.setDrawsBelowRecents(true);
1623 
1624         // These are relatively expensive and don't need to be done this frame (RecentsView isn't
1625         // visible anyway), so defer by a frame to get off the critical path, e.g. app to home.
1626         post(() -> {
1627             unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
1628             setCurrentPage(0);
1629             LayoutUtils.setViewEnabled(mActionsView, true);
1630             if (mOrientationState.setGestureActive(false)) {
1631                 updateOrientationHandler(/* forceRecreateDragLayerControllers = */ false);
1632             }
1633         });
1634     }
1635 
getRunningTaskId()1636     public int getRunningTaskId() {
1637         return mRunningTaskId;
1638     }
1639 
getRunningTaskView()1640     public @Nullable TaskView getRunningTaskView() {
1641         return getTaskView(mRunningTaskId);
1642     }
1643 
getRunningTaskIndex()1644     public int getRunningTaskIndex() {
1645         return getTaskIndexForId(mRunningTaskId);
1646     }
1647 
getFocusedTaskView()1648     public @Nullable TaskView getFocusedTaskView() {
1649         return getTaskView(mFocusedTaskId);
1650     }
1651 
1652     /**
1653      * Returns the width to height ratio of the focused {@link TaskView}.
1654      */
getFocusedTaskRatio()1655     public float getFocusedTaskRatio() {
1656         return mFocusedTaskRatio;
1657     }
1658 
1659     /**
1660      * Get the index of the task view whose id matches {@param taskId}.
1661      * @return -1 if there is no task view for the task id, else the index of the task view.
1662      */
getTaskIndexForId(int taskId)1663     public int getTaskIndexForId(int taskId) {
1664         TaskView tv = getTaskView(taskId);
1665         return tv == null ? -1 : indexOfChild(tv);
1666     }
1667 
getTaskViewStartIndex()1668     public int getTaskViewStartIndex() {
1669         return mTaskViewStartIndex;
1670     }
1671 
1672     /**
1673      * Reloads the view if anything in recents changed.
1674      */
reloadIfNeeded()1675     public void reloadIfNeeded() {
1676         if (!mModel.isTaskListValid(mTaskListChangeId)) {
1677             mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
1678         }
1679     }
1680 
1681     /**
1682      * Called when a gesture from an app is starting.
1683      */
onGestureAnimationStart(RunningTaskInfo runningTaskInfo)1684     public void onGestureAnimationStart(RunningTaskInfo runningTaskInfo) {
1685         mGestureActive = true;
1686         // This needs to be called before the other states are set since it can create the task view
1687         if (mOrientationState.setGestureActive(true)) {
1688             updateOrientationHandler();
1689         }
1690 
1691         showCurrentTask(runningTaskInfo);
1692         setEnableFreeScroll(false);
1693         setEnableDrawingLiveTile(false);
1694         setRunningTaskHidden(true);
1695         setRunningTaskIconScaledDown(true);
1696     }
1697 
1698     /**
1699      * Called only when a swipe-up gesture from an app has completed. Only called after
1700      * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}.
1701      */
onSwipeUpAnimationSuccess()1702     public void onSwipeUpAnimationSuccess() {
1703         if (getRunningTaskView() != null) {
1704             animateUpRunningTaskIconScale();
1705         }
1706         setSwipeDownShouldLaunchApp(true);
1707     }
1708 
animateRecentsRotationInPlace(int newRotation)1709     private void animateRecentsRotationInPlace(int newRotation) {
1710         if (mOrientationState.canRecentsActivityRotate()) {
1711             // Let system take care of the rotation
1712             return;
1713         }
1714         AnimatorSet pa = setRecentsChangedOrientation(true);
1715         pa.addListener(AnimatorListeners.forSuccessCallback(() -> {
1716             setLayoutRotation(newRotation, mOrientationState.getDisplayRotation());
1717             mActivity.getDragLayer().recreateControllers();
1718             setRecentsChangedOrientation(false).start();
1719         }));
1720         pa.start();
1721     }
1722 
setRecentsChangedOrientation(boolean fadeInChildren)1723     public AnimatorSet setRecentsChangedOrientation(boolean fadeInChildren) {
1724         getRunningTaskIndex();
1725         int runningIndex = getCurrentPage();
1726         AnimatorSet as = new AnimatorSet();
1727         for (int i = 0; i < getTaskViewCount(); i++) {
1728             if (runningIndex == i) {
1729                 continue;
1730             }
1731             View taskView = getTaskViewAt(i);
1732             as.play(ObjectAnimator.ofFloat(taskView, View.ALPHA, fadeInChildren ? 0 : 1));
1733         }
1734         return as;
1735     }
1736 
1737 
updateChildTaskOrientations()1738     private void updateChildTaskOrientations() {
1739         for (int i = 0; i < getTaskViewCount(); i++) {
1740             getTaskViewAt(i).setOrientationState(mOrientationState);
1741         }
1742         TaskMenuView tv = (TaskMenuView) getTopOpenViewWithType(mActivity, TYPE_TASK_MENU);
1743         if (tv != null) {
1744             tv.onRotationChanged();
1745         }
1746     }
1747 
1748     /**
1749      * Called when a gesture from an app has finished, and an end target has been determined.
1750      */
onPrepareGestureEndAnimation( @ullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget)1751     public void onPrepareGestureEndAnimation(
1752             @Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget) {
1753         if (mSizeStrategy.stateFromGestureEndTarget(endTarget)
1754                 .displayOverviewTasksAsGrid(mActivity.getDeviceProfile())) {
1755             if (animatorSet == null) {
1756                 setGridProgress(1);
1757             } else {
1758                 animatorSet.play(ObjectAnimator.ofFloat(this, RECENTS_GRID_PROGRESS, 1));
1759             }
1760         }
1761         mCurrentGestureEndTarget = endTarget;
1762         if (endTarget == GestureState.GestureEndTarget.NEW_TASK
1763                 || endTarget == GestureState.GestureEndTarget.LAST_TASK) {
1764             // When switching to tasks in quick switch, ensures the snapped page's scroll maintain
1765             // invariant between quick switch and overview, to ensure a smooth animation transition.
1766             updateGridProperties();
1767         } else if (endTarget == GestureState.GestureEndTarget.RECENTS) {
1768             setEnableFreeScroll(true);
1769         }
1770     }
1771 
1772     /**
1773      * Called when a gesture from an app has finished, and the animation to the target has ended.
1774      */
onGestureAnimationEnd()1775     public void onGestureAnimationEnd() {
1776         mGestureActive = false;
1777         if (mOrientationState.setGestureActive(false)) {
1778             updateOrientationHandler();
1779         }
1780 
1781         setEnableFreeScroll(true);
1782         setEnableDrawingLiveTile(mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS);
1783         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
1784             setRunningTaskViewShowScreenshot(true);
1785         }
1786         setRunningTaskHidden(false);
1787         animateUpRunningTaskIconScale();
1788 
1789         if (mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS
1790                 && (!showAsGrid() || getFocusedTaskView() != null)) {
1791             animateActionsViewIn();
1792         }
1793 
1794         mCurrentGestureEndTarget = null;
1795 
1796         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mActivity.getDeviceProfile().isMultiWindowMode) {
1797             switchToScreenshot(
1798                     () -> finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
1799                             null));
1800         }
1801     }
1802 
1803     /**
1804      * Returns true if we should add a stub taskView for the running task id
1805      */
shouldAddStubTaskView(RunningTaskInfo runningTaskInfo)1806     protected boolean shouldAddStubTaskView(RunningTaskInfo runningTaskInfo) {
1807         return runningTaskInfo != null && getTaskView(runningTaskInfo.taskId) == null;
1808     }
1809 
1810     /**
1811      * Creates a task view (if necessary) to represent the task with the {@param runningTaskId}.
1812      *
1813      * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
1814      * is called.  Also scrolls the view to this task.
1815      */
showCurrentTask(RunningTaskInfo runningTaskInfo)1816     public void showCurrentTask(RunningTaskInfo runningTaskInfo) {
1817         if (shouldAddStubTaskView(runningTaskInfo)) {
1818             boolean wasEmpty = getChildCount() == 0;
1819             // Add an empty view for now until the task plan is loaded and applied
1820             final TaskView taskView = mTaskViewPool.getView();
1821             addView(taskView, mTaskViewStartIndex);
1822             if (wasEmpty) {
1823                 addView(mClearAllButton);
1824             }
1825             // The temporary running task is only used for the duration between the start of the
1826             // gesture and the task list is loaded and applied
1827             mTmpRunningTask = Task.from(new TaskKey(runningTaskInfo), runningTaskInfo, false);
1828             taskView.bind(mTmpRunningTask, mOrientationState);
1829 
1830             // Measure and layout immediately so that the scroll values is updated instantly
1831             // as the user might be quick-switching
1832             measure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
1833                     makeMeasureSpec(getMeasuredHeight(), EXACTLY));
1834             layout(getLeft(), getTop(), getRight(), getBottom());
1835         }
1836 
1837         boolean runningTaskTileHidden = mRunningTaskTileHidden;
1838         int runningTaskId = runningTaskInfo == null ? -1 : runningTaskInfo.taskId;
1839         setCurrentTask(runningTaskId);
1840         if (mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
1841             setFocusedTask(runningTaskId);
1842         }
1843         setCurrentPage(getRunningTaskIndex());
1844         setRunningTaskViewShowScreenshot(false);
1845         setRunningTaskHidden(runningTaskTileHidden);
1846         // Update task size after setting current task.
1847         updateTaskSize();
1848 
1849         // Reload the task list
1850         mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
1851     }
1852 
1853     /**
1854      * Sets the running task id, cleaning up the old running task if necessary.
1855      */
setCurrentTask(int runningTaskId)1856     public void setCurrentTask(int runningTaskId) {
1857         if (mRunningTaskId == runningTaskId) {
1858             return;
1859         }
1860 
1861         if (mRunningTaskId != -1) {
1862             // Reset the state on the old running task view
1863             setRunningTaskIconScaledDown(false);
1864             setRunningTaskViewShowScreenshot(true);
1865             setRunningTaskHidden(false);
1866         }
1867         mRunningTaskId = runningTaskId;
1868     }
1869 
1870     /**
1871      * Sets the focused task id and store the width to height ratio of the focused task.
1872      */
setFocusedTask(int focusedTaskId)1873     protected void setFocusedTask(int focusedTaskId) {
1874         mFocusedTaskId = focusedTaskId;
1875         mFocusedTaskRatio =
1876                 mLastComputedTaskSize.width() / (float) mLastComputedTaskSize.height();
1877     }
1878 
1879     /**
1880      * Hides the tile associated with {@link #mRunningTaskId}
1881      */
setRunningTaskHidden(boolean isHidden)1882     public void setRunningTaskHidden(boolean isHidden) {
1883         mRunningTaskTileHidden = isHidden;
1884         TaskView runningTask = getRunningTaskView();
1885         if (runningTask != null) {
1886             runningTask.setStableAlpha(isHidden ? 0 : mContentAlpha);
1887             if (!isHidden) {
1888                 AccessibilityManagerCompat.sendCustomAccessibilityEvent(runningTask,
1889                         AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
1890             }
1891         }
1892     }
1893 
setRunningTaskViewShowScreenshot(boolean showScreenshot)1894     private void setRunningTaskViewShowScreenshot(boolean showScreenshot) {
1895         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
1896             mRunningTaskShowScreenshot = showScreenshot;
1897             TaskView runningTaskView = getRunningTaskView();
1898             if (runningTaskView != null) {
1899                 runningTaskView.setShowScreenshot(mRunningTaskShowScreenshot);
1900             }
1901         }
1902     }
1903 
setRunningTaskIconScaledDown(boolean isScaledDown)1904     public void setRunningTaskIconScaledDown(boolean isScaledDown) {
1905         if (mRunningTaskIconScaledDown != isScaledDown) {
1906             mRunningTaskIconScaledDown = isScaledDown;
1907             applyRunningTaskIconScale();
1908         }
1909     }
1910 
isTaskIconScaledDown(TaskView taskView)1911     public boolean isTaskIconScaledDown(TaskView taskView) {
1912         return mRunningTaskIconScaledDown && getRunningTaskView() == taskView;
1913     }
1914 
applyRunningTaskIconScale()1915     private void applyRunningTaskIconScale() {
1916         TaskView firstTask = getRunningTaskView();
1917         if (firstTask != null) {
1918             firstTask.setIconScaleAndDim(mRunningTaskIconScaledDown ? 0 : 1);
1919         }
1920     }
1921 
animateActionsViewIn()1922     private void animateActionsViewIn() {
1923         ObjectAnimator anim = ObjectAnimator.ofFloat(
1924                 mActionsView.getVisibilityAlpha(), MultiValueAlpha.VALUE, 0, 1);
1925         anim.setDuration(TaskView.SCALE_ICON_DURATION);
1926         anim.start();
1927     }
1928 
animateActionsViewOut()1929     private void animateActionsViewOut() {
1930         ObjectAnimator anim = ObjectAnimator.ofFloat(
1931                 mActionsView.getVisibilityAlpha(), MultiValueAlpha.VALUE, 1, 0);
1932         anim.setDuration(TaskView.SCALE_ICON_DURATION);
1933         anim.start();
1934     }
1935 
animateUpRunningTaskIconScale()1936     public void animateUpRunningTaskIconScale() {
1937         mRunningTaskIconScaledDown = false;
1938         TaskView firstTask = getRunningTaskView();
1939         if (firstTask != null) {
1940             firstTask.setIconScaleAnimStartProgress(0f);
1941             firstTask.animateIconScaleAndDimIntoView();
1942         }
1943     }
1944 
1945     /** Updates TaskView and ClearAllButtion scaling and translation required to turn into grid
1946      * layout.
1947      * This method is used when no task dismissal has occurred.
1948      */
updateGridProperties()1949     private void updateGridProperties() {
1950         updateGridProperties(false);
1951     }
1952 
1953     /**
1954      * Updates TaskView and ClearAllButton scaling and translation required to turn into grid
1955      * layout.
1956      * This method only calculates the potential position and depends on {@link #setGridProgress} to
1957      * apply the actual scaling and translation.
1958      *
1959      * @param isTaskDismissal indicates if update was called due to task dismissal
1960      */
updateGridProperties(boolean isTaskDismissal)1961     private void updateGridProperties(boolean isTaskDismissal) {
1962         int taskCount = getTaskViewCount();
1963         if (taskCount == 0) {
1964             return;
1965         }
1966 
1967         final int boxLength = Math.max(mLastComputedGridTaskSize.width(),
1968                 mLastComputedGridTaskSize.height());
1969         int taskTopMargin = mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
1970 
1971         /*
1972          * taskGridVerticalDiff is used to position the top of a task in the top row of the grid
1973          * heightOffset is the vertical space one grid task takes + space between top and
1974          *   bottom row
1975          * Summed together they provide the top position for bottom row of grid tasks
1976          */
1977         final float taskGridVerticalDiff =
1978                 mLastComputedGridTaskSize.top - mLastComputedTaskSize.top;
1979         final float heightOffset = (boxLength + taskTopMargin) + mRowSpacing;
1980 
1981         int topRowWidth = 0;
1982         int bottomRowWidth = 0;
1983         float topAccumulatedTranslationX = 0;
1984         float bottomAccumulatedTranslationX = 0;
1985 
1986         // Contains whether the child index is in top or bottom of grid (for non-focused task)
1987         // Different from mTopRowIdSet, which contains the taskId of what task is in top row
1988         IntSet topSet = new IntSet();
1989         IntSet bottomSet = new IntSet();
1990 
1991         // Horizontal grid translation for each task
1992         float[] gridTranslations = new float[taskCount];
1993 
1994         int focusedTaskIndex = Integer.MAX_VALUE;
1995         int focusedTaskShift = 0;
1996         int focusedTaskWidthAndSpacing = 0;
1997         int snappedTaskRowWidth = 0;
1998         int snappedPage = getNextPage();
1999         TaskView snappedTaskView = getTaskViewAtByAbsoluteIndex(snappedPage);
2000 
2001         if (!isTaskDismissal) {
2002             mTopRowIdSet.clear();
2003         }
2004         for (int i = 0; i < taskCount; i++) {
2005             TaskView taskView = getTaskViewAt(i);
2006             int taskWidthAndSpacing = taskView.getLayoutParams().width + mPageSpacing;
2007             // Evenly distribute tasks between rows unless rearranging due to task dismissal, in
2008             // which case keep tasks in their respective rows. For the running task, don't join
2009             // the grid.
2010             if (taskView.isFocusedTask()) {
2011                 topRowWidth += taskWidthAndSpacing;
2012                 bottomRowWidth += taskWidthAndSpacing;
2013 
2014                 focusedTaskIndex = i;
2015                 focusedTaskWidthAndSpacing = taskWidthAndSpacing;
2016                 gridTranslations[i] += focusedTaskShift;
2017                 gridTranslations[i] += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
2018 
2019                 // Center view vertically in case it's from different orientation.
2020                 taskView.setGridTranslationY((mLastComputedTaskSize.height() + taskTopMargin
2021                         - taskView.getLayoutParams().height) / 2f);
2022 
2023                 if (taskView == snappedTaskView) {
2024                     // If focused task is snapped, the row width is just task width and spacing.
2025                     snappedTaskRowWidth = taskWidthAndSpacing;
2026                 }
2027             } else {
2028                 if (i > focusedTaskIndex) {
2029                     // For tasks after the focused task, shift by focused task's width and spacing.
2030                     gridTranslations[i] +=
2031                             mIsRtl ? focusedTaskWidthAndSpacing : -focusedTaskWidthAndSpacing;
2032                 } else {
2033                     // For task before the focused task, accumulate the width and spacing to
2034                     // calculate the distance focused task need to shift.
2035                     focusedTaskShift += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
2036                 }
2037                 int taskId = taskView.getTask().key.id;
2038                 boolean isTopRow = isTaskDismissal ? mTopRowIdSet.contains(taskId)
2039                         : topRowWidth <= bottomRowWidth;
2040                 if (isTopRow) {
2041                     topRowWidth += taskWidthAndSpacing;
2042                     topSet.add(i);
2043                     mTopRowIdSet.add(taskId);
2044 
2045                     taskView.setGridTranslationY(taskGridVerticalDiff);
2046 
2047                     // Move horizontally into empty space.
2048                     float widthOffset = 0;
2049                     for (int j = i - 1; !topSet.contains(j) && j >= 0; j--) {
2050                         if (j == focusedTaskIndex) {
2051                             continue;
2052                         }
2053                         widthOffset += getTaskViewAt(j).getLayoutParams().width + mPageSpacing;
2054                     }
2055 
2056                     float currentTaskTranslationX = mIsRtl ? widthOffset : -widthOffset;
2057                     gridTranslations[i] += topAccumulatedTranslationX + currentTaskTranslationX;
2058                     topAccumulatedTranslationX += currentTaskTranslationX;
2059                 } else {
2060                     bottomRowWidth += taskWidthAndSpacing;
2061                     bottomSet.add(i);
2062 
2063                     // Move into bottom row.
2064                     taskView.setGridTranslationY(heightOffset + taskGridVerticalDiff);
2065 
2066                     // Move horizontally into empty space.
2067                     float widthOffset = 0;
2068                     for (int j = i - 1; !bottomSet.contains(j) && j >= 0; j--) {
2069                         if (j == focusedTaskIndex) {
2070                             continue;
2071                         }
2072                         widthOffset += getTaskViewAt(j).getLayoutParams().width + mPageSpacing;
2073                     }
2074 
2075                     float currentTaskTranslationX = mIsRtl ? widthOffset : -widthOffset;
2076                     gridTranslations[i] += bottomAccumulatedTranslationX + currentTaskTranslationX;
2077                     bottomAccumulatedTranslationX += currentTaskTranslationX;
2078                 }
2079                 if (taskView == snappedTaskView) {
2080                     snappedTaskRowWidth = isTopRow ? topRowWidth : bottomRowWidth;
2081                 }
2082             }
2083         }
2084 
2085         // We need to maintain snapped task's page scroll invariant between quick switch and
2086         // overview, so we sure snapped task's grid translation is 0, and add a non-fullscreen
2087         // translationX that is the same as snapped task's full scroll adjustment.
2088         float snappedTaskFullscreenScrollAdjustment = 0;
2089         float snappedTaskGridTranslationX = 0;
2090         if (snappedTaskView != null) {
2091             snappedTaskFullscreenScrollAdjustment = snappedTaskView.getScrollAdjustment(
2092                     /*fullscreenEnabled=*/true, /*gridEnabled=*/false);
2093             snappedTaskGridTranslationX = gridTranslations[snappedPage - mTaskViewStartIndex];
2094         }
2095 
2096         for (int i = 0; i < taskCount; i++) {
2097             TaskView taskView = getTaskViewAt(i);
2098             taskView.setGridTranslationX(gridTranslations[i] - snappedTaskGridTranslationX);
2099             taskView.getPrimaryNonFullscreenTranslationProperty().set(taskView,
2100                     snappedTaskFullscreenScrollAdjustment);
2101             taskView.getSecondaryNonFullscreenTranslationProperty().set(taskView, 0f);
2102         }
2103 
2104         // Use the accumulated translation of the row containing the last task.
2105         float clearAllAccumulatedTranslation = topSet.contains(taskCount - 1)
2106                 ? topAccumulatedTranslationX : bottomAccumulatedTranslationX;
2107 
2108         // If the last task is on the shorter row, ClearAllButton will embed into the shorter row
2109         // which is not what we want. Compensate the width difference of the 2 rows in that case.
2110         float shorterRowCompensation = 0;
2111         if (topRowWidth <= bottomRowWidth) {
2112             if (topSet.contains(taskCount - 1)) {
2113                 shorterRowCompensation = bottomRowWidth - topRowWidth;
2114             }
2115         } else {
2116             if (bottomSet.contains(taskCount - 1)) {
2117                 shorterRowCompensation = topRowWidth - bottomRowWidth;
2118             }
2119         }
2120         float clearAllShorterRowCompensation =
2121                 mIsRtl ? -shorterRowCompensation : shorterRowCompensation;
2122 
2123         // If the total width is shorter than one grid's width, move ClearAllButton further away
2124         // accordingly. Update longRowWidth if ClearAllButton has been moved.
2125         float clearAllShortTotalCompensation = 0;
2126         int longRowWidth = Math.max(topRowWidth, bottomRowWidth);
2127         if (longRowWidth < mLastComputedGridSize.width()) {
2128             float shortTotalCompensation = mLastComputedGridSize.width() - longRowWidth;
2129             clearAllShortTotalCompensation =
2130                     mIsRtl ? -shortTotalCompensation : shortTotalCompensation;
2131             longRowWidth = mLastComputedGridSize.width();
2132         }
2133 
2134         float clearAllTotalTranslationX =
2135                 clearAllAccumulatedTranslation + clearAllShorterRowCompensation
2136                         + clearAllShortTotalCompensation + snappedTaskFullscreenScrollAdjustment;
2137         if (focusedTaskIndex < taskCount) {
2138             // Shift by focused task's width and spacing if a task is focused.
2139             clearAllTotalTranslationX +=
2140                     mIsRtl ? focusedTaskWidthAndSpacing : -focusedTaskWidthAndSpacing;
2141         }
2142 
2143         // Make sure there are enough space between snapped page and ClearAllButton, for the case
2144         // of swiping up after quick switch.
2145         if (snappedTaskView != null) {
2146             int distanceFromClearAll = longRowWidth - snappedTaskRowWidth;
2147             int minimumDistance =
2148                     mLastComputedGridSize.width() - snappedTaskView.getLayoutParams().width;
2149             if (distanceFromClearAll < minimumDistance) {
2150                 int distanceDifference = minimumDistance - distanceFromClearAll;
2151                 clearAllTotalTranslationX += mIsRtl ? -distanceDifference : distanceDifference;
2152             }
2153         }
2154 
2155         mClearAllButton.setGridTranslationPrimary(
2156                 clearAllTotalTranslationX - snappedTaskGridTranslationX);
2157         mClearAllButton.setGridScrollOffset(
2158                 mIsRtl ? mLastComputedTaskSize.left - mLastComputedGridSize.left
2159                         : mLastComputedTaskSize.right - mLastComputedGridSize.right);
2160 
2161         setGridProgress(mGridProgress);
2162     }
2163 
isSameGridRow(TaskView taskView1, TaskView taskView2)2164     private boolean isSameGridRow(TaskView taskView1, TaskView taskView2) {
2165         if (taskView1 == null || taskView2 == null) {
2166             return false;
2167         }
2168         int taskId1 = taskView1.getTask().key.id;
2169         int taskId2 = taskView2.getTask().key.id;
2170         if (taskId1 == mFocusedTaskId || taskId2 == mFocusedTaskId) {
2171             return false;
2172         }
2173         return (mTopRowIdSet.contains(taskId1) && mTopRowIdSet.contains(taskId2)) || (
2174                 !mTopRowIdSet.contains(taskId1) && !mTopRowIdSet.contains(taskId2));
2175     }
2176 
2177     /**
2178      * Moves TaskView and ClearAllButton between carousel and 2 row grid.
2179      *
2180      * @param gridProgress 0 = carousel; 1 = 2 row grid.
2181      */
setGridProgress(float gridProgress)2182     private void setGridProgress(float gridProgress) {
2183         int taskCount = getTaskViewCount();
2184         if (taskCount == 0) {
2185             return;
2186         }
2187 
2188         mGridProgress = gridProgress;
2189 
2190         for (int i = 0; i < taskCount; i++) {
2191             getTaskViewAt(i).setGridProgress(gridProgress);
2192         }
2193         mClearAllButton.setGridProgress(gridProgress);
2194     }
2195 
enableLayoutTransitions()2196     private void enableLayoutTransitions() {
2197         if (mLayoutTransition == null) {
2198             mLayoutTransition = new LayoutTransition();
2199             mLayoutTransition.enableTransitionType(LayoutTransition.APPEARING);
2200             mLayoutTransition.setDuration(ADDITION_TASK_DURATION);
2201             mLayoutTransition.setStartDelay(LayoutTransition.APPEARING, 0);
2202 
2203             mLayoutTransition.addTransitionListener(new TransitionListener() {
2204                 @Override
2205                 public void startTransition(LayoutTransition transition, ViewGroup viewGroup,
2206                     View view, int i) {
2207                 }
2208 
2209                 @Override
2210                 public void endTransition(LayoutTransition transition, ViewGroup viewGroup,
2211                     View view, int i) {
2212                     // When the unpinned task is added, snap to first page and disable transitions
2213                     if (view instanceof TaskView) {
2214                         snapToPage(0);
2215                         setLayoutTransition(null);
2216                     }
2217 
2218                 }
2219             });
2220         }
2221         setLayoutTransition(mLayoutTransition);
2222     }
2223 
setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp)2224     public void setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp) {
2225         mSwipeDownShouldLaunchApp = swipeDownShouldLaunchApp;
2226     }
2227 
shouldSwipeDownLaunchApp()2228     public boolean shouldSwipeDownLaunchApp() {
2229         return mSwipeDownShouldLaunchApp;
2230     }
2231 
setIgnoreResetTask(int taskId)2232     public void setIgnoreResetTask(int taskId) {
2233         mIgnoreResetTaskId = taskId;
2234     }
2235 
clearIgnoreResetTask(int taskId)2236     public void clearIgnoreResetTask(int taskId) {
2237         if (mIgnoreResetTaskId == taskId) {
2238             mIgnoreResetTaskId = -1;
2239         }
2240     }
2241 
addDismissedTaskAnimations(TaskView taskView, long duration, PendingAnimation anim)2242     private void addDismissedTaskAnimations(TaskView taskView, long duration,
2243             PendingAnimation anim) {
2244         // Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's
2245         // alpha is set to 0 so that it can be recycled in the view pool properly
2246         anim.setFloat(taskView, VIEW_ALPHA, 0, clampToProgress(ACCEL, 0, 0.5f));
2247         SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController();
2248 
2249         ResourceProvider rp = DynamicResource.provider(mActivity);
2250         SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_START)
2251                 .setDampingRatio(rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio))
2252                 .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness));
2253         FloatProperty<TaskView> dismissingTaskViewTranslate =
2254                 taskView.getSecondaryDissmissTranslationProperty();
2255         // TODO(b/186800707) translate entire grid size distance
2256         int translateDistance = mOrientationHandler.getSecondaryDimension(taskView);
2257         int positiveNegativeFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor();
2258         if (splitController.isSplitSelectActive()) {
2259             // Have the task translate towards whatever side was just pinned
2260             int dir = mOrientationHandler.getSplitTaskViewDismissDirection(splitController
2261                     .getActiveSplitPositionOption(), mActivity.getDeviceProfile());
2262             switch (dir) {
2263                 case PagedOrientationHandler.SPLIT_TRANSLATE_SECONDARY_NEGATIVE:
2264                     dismissingTaskViewTranslate = taskView
2265                             .getSecondaryDissmissTranslationProperty();
2266                     positiveNegativeFactor = -1;
2267                     break;
2268 
2269                 case PagedOrientationHandler.SPLIT_TRANSLATE_PRIMARY_POSITIVE:
2270                     dismissingTaskViewTranslate = taskView.getPrimaryDismissTranslationProperty();
2271                     positiveNegativeFactor = 1;
2272                     break;
2273 
2274                 case PagedOrientationHandler.SPLIT_TRANSLATE_PRIMARY_NEGATIVE:
2275                     dismissingTaskViewTranslate = taskView.getPrimaryDismissTranslationProperty();
2276                     positiveNegativeFactor = -1;
2277                     break;
2278                 default:
2279                     throw new IllegalStateException("Invalid split task translation: " + dir);
2280             }
2281         }
2282         // Double translation distance so dismissal drag is the full height, as we only animate
2283         // the drag for the first half of the progress.
2284         anim.add(ObjectAnimator.ofFloat(taskView, dismissingTaskViewTranslate,
2285                 positiveNegativeFactor * translateDistance * 2).setDuration(duration), LINEAR, sp);
2286 
2287         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
2288                 && taskView.isRunningTask()) {
2289             anim.addOnFrameCallback(() -> {
2290                 mLiveTileTaskViewSimulator.taskSecondaryTranslation.value =
2291                         mOrientationHandler.getSecondaryValue(
2292                                 taskView.getTranslationX(),
2293                                 taskView.getTranslationY());
2294                 redrawLiveTile();
2295             });
2296         }
2297     }
2298 
createTaskDismissAnimation(TaskView taskView, boolean animateTaskView, boolean shouldRemoveTask, long duration)2299     public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView,
2300             boolean shouldRemoveTask, long duration) {
2301         if (mPendingAnimation != null) {
2302             mPendingAnimation.createPlaybackController().dispatchOnCancel().dispatchOnEnd();
2303         }
2304         PendingAnimation anim = new PendingAnimation(duration);
2305 
2306         int count = getPageCount();
2307         if (count == 0) {
2308             return anim;
2309         }
2310 
2311         int[] oldScroll = new int[count];
2312         int[] newScroll = new int[count];
2313         getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
2314         getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView);
2315         int taskCount = getTaskViewCount();
2316         int scrollDiffPerPage = 0;
2317         if (count > 1) {
2318             scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]);
2319         }
2320         int draggedIndex = indexOfChild(taskView);
2321 
2322         boolean isFocusedTaskDismissed = taskView.getTask().key.id == mFocusedTaskId;
2323         if (isFocusedTaskDismissed && showAsGrid()) {
2324             anim.setFloat(mActionsView, VIEW_ALPHA, 0, clampToProgress(ACCEL_0_5, 0, 0.5f));
2325         }
2326         float dismissedTaskWidth = taskView.getLayoutParams().width + mPageSpacing;
2327         boolean needsCurveUpdates = false;
2328         for (int i = 0; i < count; i++) {
2329             View child = getChildAt(i);
2330             if (child == taskView) {
2331                 if (animateTaskView) {
2332                     addDismissedTaskAnimations(taskView, duration, anim);
2333                 }
2334             } else if (!showAsGrid()) {
2335                 // Compute scroll offsets from task dismissal for animation.
2336                 // If we just take newScroll - oldScroll, everything to the right of dragged task
2337                 // translates to the left. We need to offset this in some cases:
2338                 // - In RTL, add page offset to all pages, since we want pages to move to the right
2339                 // Additionally, add a page offset if:
2340                 // - Current page is rightmost page (leftmost for RTL)
2341                 // - Dragging an adjacent page on the left side (right side for RTL)
2342                 int offset = mIsRtl ? scrollDiffPerPage : 0;
2343                 if (mCurrentPage == draggedIndex) {
2344                     int lastPage = taskCount - 1;
2345                     if (mCurrentPage == lastPage) {
2346                         offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
2347                     }
2348                 } else {
2349                     // Dragging an adjacent page.
2350                     int negativeAdjacent = mCurrentPage - 1; // (Right in RTL, left in LTR)
2351                     if (draggedIndex == negativeAdjacent) {
2352                         offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
2353                     }
2354                 }
2355 
2356                 int scrollDiff = newScroll[i] - oldScroll[i] + offset;
2357                 if (scrollDiff != 0) {
2358                     FloatProperty translationProperty = child instanceof TaskView
2359                             ? ((TaskView) child).getPrimaryDismissTranslationProperty()
2360                             : mOrientationHandler.getPrimaryViewTranslate();
2361 
2362                     float additionalDismissDuration =
2363                             ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * Math.abs(
2364                                     i - draggedIndex);
2365                     anim.setFloat(child, translationProperty, scrollDiff, clampToProgress(LINEAR,
2366                             Utilities.boundToRange(INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
2367                                     + additionalDismissDuration, 0f, 1f), 1));
2368                     if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
2369                             && child instanceof TaskView
2370                             && ((TaskView) child).isRunningTask()) {
2371                         anim.addOnFrameCallback(() -> {
2372                             mLiveTileTaskViewSimulator.taskPrimaryTranslation.value =
2373                                     mOrientationHandler.getPrimaryValue(child.getTranslationX(),
2374                                             child.getTranslationY());
2375                             redrawLiveTile();
2376                         });
2377                     }
2378                     needsCurveUpdates = true;
2379                 }
2380             } else if (child instanceof TaskView) {
2381                 // Animate task with index >= dismissed index and in the same row as the
2382                 // dismissed index, or if the dismissed task was the focused task. Offset
2383                 // successive task dismissal durations for a staggered effect.
2384                 if (isFocusedTaskDismissed || (i >= draggedIndex && isSameGridRow((TaskView) child,
2385                         taskView))) {
2386                     FloatProperty translationProperty =
2387                             ((TaskView) child).getPrimaryDismissTranslationProperty();
2388                     float additionalDismissDuration =
2389                             ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * Math.abs(
2390                                     i - draggedIndex);
2391                     anim.setFloat(child, translationProperty,
2392                             !mIsRtl ? -dismissedTaskWidth : dismissedTaskWidth,
2393                             clampToProgress(LINEAR, Utilities.boundToRange(
2394                                     INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
2395                                             + additionalDismissDuration, 0f, 1f), 1));
2396                 }
2397             }
2398         }
2399 
2400         if (needsCurveUpdates) {
2401             anim.addOnFrameCallback(this::updateCurveProperties);
2402         }
2403 
2404         // Add a tiny bit of translation Z, so that it draws on top of other views
2405         if (animateTaskView) {
2406             taskView.setTranslationZ(0.1f);
2407         }
2408 
2409         mPendingAnimation = anim;
2410         mPendingAnimation.addEndListener(new Consumer<Boolean>() {
2411             @Override
2412             public void accept(Boolean success) {
2413                 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
2414                         && taskView.isRunningTask() && success) {
2415                     finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
2416                             () -> onEnd(success));
2417                 } else {
2418                     onEnd(success);
2419                 }
2420             }
2421 
2422             @SuppressWarnings("WrongCall")
2423             private void onEnd(boolean success) {
2424                 if (success) {
2425                     if (shouldRemoveTask) {
2426                         if (taskView.getTask() != null) {
2427                             if (ENABLE_QUICKSTEP_LIVE_TILE.get() && taskView.isRunningTask()) {
2428                                 finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
2429                                         () -> removeTaskInternal(taskView));
2430                             } else {
2431                                 removeTaskInternal(taskView);
2432                             }
2433                             mActivity.getStatsLogManager().logger()
2434                                     .withItemInfo(taskView.getItemInfo())
2435                                     .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
2436                         }
2437                     }
2438 
2439                     // Reset task translations as they may have updated via animations in
2440                     // createTaskDismissAnimation
2441                     resetTaskVisuals();
2442 
2443                     int pageToSnapTo = mCurrentPage;
2444                     // Snap to start if focused task was dismissed, as after quick switch it could
2445                     // be at any page but the focused task always displays at the start.
2446                     if (taskView.getTask().key.id == mFocusedTaskId) {
2447                         pageToSnapTo = mTaskViewStartIndex;
2448                     } else if (draggedIndex < pageToSnapTo || pageToSnapTo == (getTaskViewCount()
2449                             - 1)) {
2450                         pageToSnapTo -= 1;
2451                     }
2452                     removeViewInLayout(taskView);
2453 
2454                     if (getTaskViewCount() == 0) {
2455                         removeViewInLayout(mClearAllButton);
2456                         startHome();
2457                     } else {
2458                         snapToPageImmediately(pageToSnapTo);
2459                         dispatchScrollChanged();
2460                         // Grid got messed up, reapply.
2461                         updateGridProperties(true);
2462                         if (showAsGrid() && getFocusedTaskView() == null
2463                                 && mActionsView.getVisibilityAlpha().getValue() == 1) {
2464                             animateActionsViewOut();
2465                         }
2466                     }
2467                     // Update the layout synchronously so that the position of next view is
2468                     // immediately available.
2469                     onLayout(false /*  changed */, getLeft(), getTop(), getRight(), getBottom());
2470                 }
2471                 onDismissAnimationEnds();
2472                 mPendingAnimation = null;
2473             }
2474         });
2475         return anim;
2476     }
2477 
removeTaskInternal(TaskView taskView)2478     private void removeTaskInternal(TaskView taskView) {
2479         UI_HELPER_EXECUTOR.getHandler().postDelayed(() ->
2480                         ActivityManagerWrapper.getInstance().removeTask(
2481                                 taskView.getTask().key.id),
2482                 REMOVE_TASK_WAIT_FOR_APP_STOP_MS);
2483     }
2484 
2485     /**
2486      * @return {@code true} if one of the task thumbnails would intersect/overlap with the
2487      *         {@link #mSplitPlaceholderView}
2488      */
shouldShiftThumbnailsForSplitSelect(@plitConfigurationOptions.StagePosition int stagePosition)2489     public boolean shouldShiftThumbnailsForSplitSelect(@SplitConfigurationOptions.StagePosition
2490             int stagePosition) {
2491         if (!mActivity.getDeviceProfile().isTablet) {
2492             // Never enough space on phones
2493             return true;
2494         } else if (!mActivity.getDeviceProfile().isLandscape) {
2495             return false;
2496         }
2497 
2498         Rect splitBounds = new Rect();
2499         float placeholderSize = getResources().getDimension(R.dimen.split_placeholder_size);
2500         // This acts as a best approximation on where the splitplaceholder view would be,
2501         // doesn't need to be exact necessarily. This also doesn't need to take translations
2502         // into account since placeholder view is not translated
2503         if (stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT) {
2504             splitBounds.set((int) (getWidth() - placeholderSize), 0, getWidth(), getHeight());
2505         } else {
2506             splitBounds.set(0, 0, (int) (placeholderSize), getHeight());
2507         }
2508         Rect taskBounds = new Rect();
2509         int taskCount = getTaskViewCount();
2510         for (int i = 0; i < taskCount; i++) {
2511             TaskView taskView = getTaskViewAt(i);
2512             if (taskView == mSplitHiddenTaskView && taskView != getFocusedTaskView()) {
2513                 // Case where the hidden task view would have overlapped w/ placeholder,
2514                 // but because it's going to hide we don't care
2515                 // TODO (b/187312247) edge case for thumbnails that are off screen but scroll on
2516                 continue;
2517             }
2518             taskView.getBoundsOnScreen(taskBounds);
2519             if (Rect.intersects(taskBounds, splitBounds)) {
2520                 return true;
2521             }
2522         }
2523         return false;
2524     }
2525 
onDismissAnimationEnds()2526     protected void onDismissAnimationEnds() {
2527     }
2528 
createAllTasksDismissAnimation(long duration)2529     public PendingAnimation createAllTasksDismissAnimation(long duration) {
2530         if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
2531             throw new IllegalStateException("Another pending animation is still running");
2532         }
2533         PendingAnimation anim = new PendingAnimation(duration);
2534 
2535         int count = getTaskViewCount();
2536         for (int i = 0; i < count; i++) {
2537             addDismissedTaskAnimations(getTaskViewAt(i), duration, anim);
2538         }
2539 
2540         mPendingAnimation = anim;
2541         mPendingAnimation.addEndListener(isSuccess -> {
2542             if (isSuccess) {
2543                 // Remove all the task views now
2544                 finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, () -> {
2545                     UI_HELPER_EXECUTOR.getHandler().postDelayed(
2546                             ActivityManagerWrapper.getInstance()::removeAllRecentTasks,
2547                             REMOVE_TASK_WAIT_FOR_APP_STOP_MS);
2548                     removeTasksViewsAndClearAllButton();
2549                     startHome();
2550                 });
2551             }
2552             mPendingAnimation = null;
2553         });
2554         return anim;
2555     }
2556 
snapToPageRelative(int pageCount, int delta, boolean cycle)2557     private boolean snapToPageRelative(int pageCount, int delta, boolean cycle) {
2558         if (pageCount == 0) {
2559             return false;
2560         }
2561         final int newPageUnbound = getNextPage() + delta;
2562         if (!cycle && (newPageUnbound < 0 || newPageUnbound >= pageCount)) {
2563             return false;
2564         }
2565         snapToPage((newPageUnbound + pageCount) % pageCount);
2566         getChildAt(getNextPage()).requestFocus();
2567         return true;
2568     }
2569 
runDismissAnimation(PendingAnimation pendingAnim)2570     private void runDismissAnimation(PendingAnimation pendingAnim) {
2571         AnimatorPlaybackController controller = pendingAnim.createPlaybackController();
2572         controller.dispatchOnStart();
2573         controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN);
2574         controller.start();
2575     }
2576 
2577     @UiThread
dismissTask(int taskId)2578     private void dismissTask(int taskId) {
2579         TaskView taskView = getTaskView(taskId);
2580         if (taskView == null) {
2581             return;
2582         }
2583         dismissTask(taskView, true /* animate */, false /* removeTask */);
2584     }
2585 
dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask)2586     public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) {
2587         runDismissAnimation(createTaskDismissAnimation(taskView, animateTaskView, removeTask,
2588                 DISMISS_TASK_DURATION));
2589     }
2590 
2591     @SuppressWarnings("unused")
dismissAllTasks(View view)2592     private void dismissAllTasks(View view) {
2593         runDismissAnimation(createAllTasksDismissAnimation(DISMISS_TASK_DURATION));
2594         mActivity.getStatsLogManager().logger().log(LAUNCHER_TASK_CLEAR_ALL);
2595     }
2596 
dismissCurrentTask()2597     private void dismissCurrentTask() {
2598         TaskView taskView = getNextPageTaskView();
2599         if (taskView != null) {
2600             dismissTask(taskView, true /*animateTaskView*/, true /*removeTask*/);
2601         }
2602     }
2603 
2604     @Override
dispatchKeyEvent(KeyEvent event)2605     public boolean dispatchKeyEvent(KeyEvent event) {
2606         if (event.getAction() == KeyEvent.ACTION_DOWN) {
2607             switch (event.getKeyCode()) {
2608                 case KeyEvent.KEYCODE_TAB:
2609                     return snapToPageRelative(getTaskViewCount(), event.isShiftPressed() ? -1 : 1,
2610                             event.isAltPressed() /* cycle */);
2611                 case KeyEvent.KEYCODE_DPAD_RIGHT:
2612                     return snapToPageRelative(getPageCount(), mIsRtl ? -1 : 1, false /* cycle */);
2613                 case KeyEvent.KEYCODE_DPAD_LEFT:
2614                     return snapToPageRelative(getPageCount(), mIsRtl ? 1 : -1, false /* cycle */);
2615                 case KeyEvent.KEYCODE_DEL:
2616                 case KeyEvent.KEYCODE_FORWARD_DEL:
2617                     dismissCurrentTask();
2618                     return true;
2619                 case KeyEvent.KEYCODE_NUMPAD_DOT:
2620                     if (event.isAltPressed()) {
2621                         // Numpad DEL pressed while holding Alt.
2622                         dismissCurrentTask();
2623                         return true;
2624                     }
2625             }
2626         }
2627         return super.dispatchKeyEvent(event);
2628     }
2629 
2630     @Override
onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect)2631     protected void onFocusChanged(boolean gainFocus, int direction,
2632             @Nullable Rect previouslyFocusedRect) {
2633         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
2634         if (gainFocus && getChildCount() > 0) {
2635             switch (direction) {
2636                 case FOCUS_FORWARD:
2637                     setCurrentPage(0);
2638                     break;
2639                 case FOCUS_BACKWARD:
2640                 case FOCUS_RIGHT:
2641                 case FOCUS_LEFT:
2642                     setCurrentPage(getChildCount() - 1);
2643                     break;
2644             }
2645         }
2646     }
2647 
getContentAlpha()2648     public float getContentAlpha() {
2649         return mContentAlpha;
2650     }
2651 
setContentAlpha(float alpha)2652     public void setContentAlpha(float alpha) {
2653         if (alpha == mContentAlpha) {
2654             return;
2655         }
2656         alpha = Utilities.boundToRange(alpha, 0, 1);
2657         mContentAlpha = alpha;
2658         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
2659             TaskView child = getTaskViewAt(i);
2660             if (!mRunningTaskTileHidden || child.getTask().key.id != mRunningTaskId) {
2661                 child.setStableAlpha(alpha);
2662             }
2663         }
2664         mClearAllButton.setContentAlpha(mContentAlpha);
2665         int alphaInt = Math.round(alpha * 255);
2666         mEmptyMessagePaint.setAlpha(alphaInt);
2667         mEmptyIcon.setAlpha(alphaInt);
2668         mActionsView.getContentAlpha().setValue(mContentAlpha);
2669 
2670         if (alpha > 0) {
2671             setVisibility(VISIBLE);
2672         } else if (!mFreezeViewVisibility) {
2673             setVisibility(INVISIBLE);
2674         }
2675     }
2676 
2677     /**
2678      * Freezes the view visibility change. When frozen, the view will not change its visibility
2679      * to gone due to alpha changes.
2680      */
setFreezeViewVisibility(boolean freezeViewVisibility)2681     public void setFreezeViewVisibility(boolean freezeViewVisibility) {
2682         if (mFreezeViewVisibility != freezeViewVisibility) {
2683             mFreezeViewVisibility = freezeViewVisibility;
2684             if (!mFreezeViewVisibility) {
2685                 setVisibility(mContentAlpha > 0 ? VISIBLE : INVISIBLE);
2686             }
2687         }
2688     }
2689 
2690     @Override
setVisibility(int visibility)2691     public void setVisibility(int visibility) {
2692         super.setVisibility(visibility);
2693         if (mActionsView != null) {
2694             mActionsView.updateHiddenFlags(HIDDEN_NO_RECENTS, visibility != VISIBLE);
2695             if (visibility != VISIBLE) {
2696                 mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
2697             }
2698         }
2699     }
2700 
2701     @Override
onConfigurationChanged(Configuration newConfig)2702     protected void onConfigurationChanged(Configuration newConfig) {
2703         super.onConfigurationChanged(newConfig);
2704         updateRecentsRotation();
2705     }
2706 
2707     /**
2708      * Updates {@link RecentsOrientedState}'s cached RecentsView rotation.
2709      */
updateRecentsRotation()2710     public void updateRecentsRotation() {
2711         final int rotation = mActivity.getDisplay().getRotation();
2712         mOrientationState.setRecentsRotation(rotation);
2713     }
2714 
setLayoutRotation(int touchRotation, int displayRotation)2715     public void setLayoutRotation(int touchRotation, int displayRotation) {
2716         if (mOrientationState.update(touchRotation, displayRotation)) {
2717             updateOrientationHandler();
2718         }
2719     }
2720 
getPagedViewOrientedState()2721     public RecentsOrientedState getPagedViewOrientedState() {
2722         return mOrientationState;
2723     }
2724 
getPagedOrientationHandler()2725     public PagedOrientationHandler getPagedOrientationHandler() {
2726         return mOrientationHandler;
2727     }
2728 
2729     @Nullable
getNextTaskView()2730     public TaskView getNextTaskView() {
2731         return getTaskViewAtByAbsoluteIndex(getRunningTaskIndex() + 1);
2732     }
2733 
2734     @Nullable
getCurrentPageTaskView()2735     public TaskView getCurrentPageTaskView() {
2736         return getTaskViewAtByAbsoluteIndex(getCurrentPage());
2737     }
2738 
2739     @Nullable
getNextPageTaskView()2740     public TaskView getNextPageTaskView() {
2741         return getTaskViewAtByAbsoluteIndex(getNextPage());
2742     }
2743 
2744     @Nullable
getTaskViewNearestToCenterOfScreen()2745     public TaskView getTaskViewNearestToCenterOfScreen() {
2746         return getTaskViewAtByAbsoluteIndex(getPageNearestToCenterOfScreen());
2747     }
2748 
2749     /**
2750      * Returns null instead of indexOutOfBoundsError when index is not in range
2751      */
2752     @Nullable
getTaskViewAt(int index)2753     public TaskView getTaskViewAt(int index) {
2754         return getTaskViewAtByAbsoluteIndex(index + mTaskViewStartIndex);
2755     }
2756 
2757     @Nullable
getTaskViewAtByAbsoluteIndex(int index)2758     private TaskView getTaskViewAtByAbsoluteIndex(int index) {
2759         if (index < getChildCount() && index >= 0) {
2760             View child = getChildAt(index);
2761             return child instanceof TaskView ? (TaskView) child : null;
2762         }
2763         return null;
2764     }
2765 
setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener)2766     public void setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener) {
2767         mOnEmptyMessageUpdatedListener = listener;
2768     }
2769 
updateEmptyMessage()2770     public void updateEmptyMessage() {
2771         boolean isEmpty = getTaskViewCount() == 0;
2772         boolean hasSizeChanged = mLastMeasureSize.x != getWidth()
2773                 || mLastMeasureSize.y != getHeight();
2774         if (isEmpty == mShowEmptyMessage && !hasSizeChanged) {
2775             return;
2776         }
2777         setContentDescription(isEmpty ? mEmptyMessage : "");
2778         mShowEmptyMessage = isEmpty;
2779         updateEmptyStateUi(hasSizeChanged);
2780         invalidate();
2781 
2782         if (mOnEmptyMessageUpdatedListener != null) {
2783             mOnEmptyMessageUpdatedListener.onEmptyMessageUpdated(mShowEmptyMessage);
2784         }
2785     }
2786 
2787     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)2788     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
2789         super.onLayout(changed, left, top, right, bottom);
2790 
2791         updateEmptyStateUi(changed);
2792 
2793         // Update the pivots such that when the task is scaled, it fills the full page
2794         getTaskSize(mTempRect);
2795         getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect,
2796                 mActivity.getDeviceProfile(), mTempPointF);
2797         setPivotX(mTempPointF.x);
2798         setPivotY(mTempPointF.y);
2799         setTaskModalness(mTaskModalness);
2800         mLastComputedTaskStartPushOutDistance = null;
2801         mLastComputedTaskEndPushOutDistance = null;
2802         updatePageOffsets();
2803         setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO
2804                 : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
2805     }
2806 
updatePageOffsets()2807     private void updatePageOffsets() {
2808         float offset = mAdjacentPageHorizontalOffset;
2809         float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness);
2810         int count = getChildCount();
2811 
2812         TaskView runningTask = mRunningTaskId == -1 || !mRunningTaskTileHidden
2813                 ? null : getTaskView(mRunningTaskId);
2814         int midpoint = runningTask == null ? -1 : indexOfChild(runningTask);
2815         int modalMidpoint = getCurrentPage();
2816 
2817         float midpointOffsetSize = 0;
2818         float leftOffsetSize = midpoint - 1 >= 0
2819                 ? getHorizontalOffsetSize(midpoint - 1, midpoint, offset)
2820                 : 0;
2821         float rightOffsetSize = midpoint + 1 < count
2822                 ? getHorizontalOffsetSize(midpoint + 1, midpoint, offset)
2823                 : 0;
2824 
2825         boolean showAsGrid = showAsGrid();
2826         float modalMidpointOffsetSize = 0;
2827         float modalLeftOffsetSize = 0;
2828         float modalRightOffsetSize = 0;
2829         float gridOffsetSize = 0;
2830 
2831         if (showAsGrid) {
2832             // In grid, we only focus the task on the side. The reference index used for offset
2833             // calculation is the task directly next to the focus task in the grid.
2834             int referenceIndex = modalMidpoint == 0 ? 1 : 0;
2835             gridOffsetSize = referenceIndex < count
2836                     ? getHorizontalOffsetSize(referenceIndex, modalMidpoint, modalOffset)
2837                     : 0;
2838         } else {
2839             modalLeftOffsetSize = modalMidpoint - 1 >= 0
2840                     ? getHorizontalOffsetSize(modalMidpoint - 1, modalMidpoint, modalOffset)
2841                     : 0;
2842             modalRightOffsetSize = modalMidpoint + 1 < count
2843                     ? getHorizontalOffsetSize(modalMidpoint + 1, modalMidpoint, modalOffset)
2844                     : 0;
2845         }
2846 
2847         for (int i = 0; i < count; i++) {
2848             float translation = i == midpoint
2849                     ? midpointOffsetSize
2850                     : i < midpoint
2851                             ? leftOffsetSize
2852                             : rightOffsetSize;
2853             float modalTranslation = i == modalMidpoint
2854                     ? modalMidpointOffsetSize
2855                     : showAsGrid
2856                             ? gridOffsetSize
2857                             : i < modalMidpoint ? modalLeftOffsetSize : modalRightOffsetSize;
2858             float totalTranslation = translation + modalTranslation;
2859             View child = getChildAt(i);
2860             FloatProperty translationProperty = child instanceof TaskView
2861                     ? ((TaskView) child).getPrimaryTaskOffsetTranslationProperty()
2862                     : mOrientationHandler.getPrimaryViewTranslate();
2863             translationProperty.set(child, totalTranslation);
2864             if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
2865                     && i == getRunningTaskIndex()) {
2866                 mLiveTileTaskViewSimulator.taskPrimaryTranslation.value = totalTranslation;
2867                 redrawLiveTile();
2868             }
2869         }
2870         updateCurveProperties();
2871     }
2872 
2873     /**
2874      * Computes the child position with persistent translation considered (see
2875      * {@link TaskView#getPersistentTranslationX()}.
2876      */
2877     private void getPersistentChildPosition(int childIndex, int midPointScroll, RectF outRect) {
2878         View child = getChildAt(childIndex);
2879         outRect.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
2880         if (child instanceof TaskView) {
2881             TaskView taskView = (TaskView) child;
2882             outRect.offset(taskView.getPersistentTranslationX(),
2883                     taskView.getPersistentTranslationY());
2884             outRect.top += mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
2885         }
2886         outRect.offset(mOrientationHandler.getPrimaryValue(-midPointScroll, 0),
2887                 mOrientationHandler.getSecondaryValue(-midPointScroll, 0));
2888     }
2889 
2890     /**
2891      * Computes the distance to offset the given child such that it is completely offscreen when
2892      * translating away from the given midpoint.
2893      * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen.
2894      */
2895     private float getHorizontalOffsetSize(int childIndex, int midpointIndex, float offsetProgress) {
2896         if (offsetProgress == 0) {
2897             // Don't bother calculating everything below if we won't offset anyway.
2898             return 0;
2899         }
2900 
2901         // First, get the position of the task relative to the midpoint. If there is no midpoint
2902         // then we just use the normal (centered) task position.
2903         RectF taskPosition = mTempRectF;
2904         // Whether the task should be shifted to start direction (i.e. left edge for portrait, top
2905         // edge for landscape/seascape).
2906         boolean isStartShift;
2907         if (midpointIndex > -1) {
2908             // When there is a midpoint reference task, adjacent tasks have less distance to travel
2909             // to reach offscreen. Offset the task position to the task's starting point.
2910             int midpointScroll = getScrollForPage(midpointIndex);
2911             getPersistentChildPosition(midpointIndex, midpointScroll, taskPosition);
2912             float midpointStart = mOrientationHandler.getStart(taskPosition);
2913 
2914             getPersistentChildPosition(childIndex, midpointScroll, taskPosition);
2915             // Assume child does not overlap with midPointChild.
2916             isStartShift = mOrientationHandler.getStart(taskPosition) < midpointStart;
2917         } else {
2918             // Position the task at scroll position.
2919             getPersistentChildPosition(childIndex, getScrollForPage(childIndex), taskPosition);
2920             isStartShift = mIsRtl;
2921         }
2922 
2923         // Next, calculate the distance to move the task off screen. We also need to account for
2924         // RecentsView scale, because it moves tasks based on its pivot. To do this, we move the
2925         // task position to where it would be offscreen at scale = 1 (computed above), then we
2926         // apply the scale via getMatrix() to determine how much that moves the task from its
2927         // desired position, and adjust the computed distance accordingly.
2928         float distanceToOffscreen;
2929         if (isStartShift) {
2930             float desiredStart = -mOrientationHandler.getPrimarySize(taskPosition);
2931             distanceToOffscreen = -mOrientationHandler.getEnd(taskPosition);
2932             if (mLastComputedTaskStartPushOutDistance == null) {
2933                 taskPosition.offsetTo(
2934                         mOrientationHandler.getPrimaryValue(desiredStart, 0f),
2935                         mOrientationHandler.getSecondaryValue(desiredStart, 0f));
2936                 getMatrix().mapRect(taskPosition);
2937                 mLastComputedTaskStartPushOutDistance = mOrientationHandler.getEnd(taskPosition)
2938                         / mOrientationHandler.getPrimaryScale(this);
2939             }
2940             distanceToOffscreen -= mLastComputedTaskStartPushOutDistance;
2941         } else {
2942             float desiredStart = mOrientationHandler.getPrimarySize(this);
2943             distanceToOffscreen = desiredStart - mOrientationHandler.getStart(taskPosition);
2944             if (mLastComputedTaskEndPushOutDistance == null) {
2945                 taskPosition.offsetTo(
2946                         mOrientationHandler.getPrimaryValue(desiredStart, 0f),
2947                         mOrientationHandler.getSecondaryValue(desiredStart, 0f));
2948                 getMatrix().mapRect(taskPosition);
2949                 mLastComputedTaskEndPushOutDistance = (mOrientationHandler.getStart(taskPosition)
2950                         - desiredStart) / mOrientationHandler.getPrimaryScale(this);
2951             }
2952             distanceToOffscreen -= mLastComputedTaskEndPushOutDistance;
2953         }
2954         return distanceToOffscreen * offsetProgress;
2955     }
2956 
2957     protected void setTaskViewsResistanceTranslation(float translation) {
2958         mTaskViewsSecondaryTranslation = translation;
2959         for (int i = 0; i < getTaskViewCount(); i++) {
2960             TaskView task = getTaskViewAt(i);
2961             task.getTaskResistanceTranslationProperty().set(task, translation / getScaleY());
2962         }
2963         mLiveTileTaskViewSimulator.recentsViewSecondaryTranslation.value = translation;
2964     }
2965 
2966     protected void setTaskViewsPrimarySplitTranslation(float translation) {
2967         mTaskViewsPrimarySplitTranslation = translation;
2968         for (int i = 0; i < getTaskViewCount(); i++) {
2969             TaskView task = getTaskViewAt(i);
2970             task.getPrimarySplitTranslationProperty().set(task, translation);
2971         }
2972     }
2973 
2974     protected void setTaskViewsSecondarySplitTranslation(float translation) {
2975         mTaskViewsSecondarySplitTranslation = translation;
2976         for (int i = 0; i < getTaskViewCount(); i++) {
2977             TaskView task = getTaskViewAt(i);
2978             task.getSecondarySplitTranslationProperty().set(task, translation);
2979         }
2980     }
2981 
2982     /**
2983      * Resets the visuals when exit modal state.
2984      */
2985     public void resetModalVisuals() {
2986         TaskView taskView = getCurrentPageTaskView();
2987         if (taskView != null) {
2988             taskView.getThumbnail().getTaskOverlay().resetModalVisuals();
2989         }
2990     }
2991 
2992     public void initiateSplitSelect(TaskView taskView, SplitPositionOption splitPositionOption) {
2993         mSplitHiddenTaskView = taskView;
2994         SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController();
2995         Rect initialBounds = new Rect(taskView.getLeft(), taskView.getTop(), taskView.getRight(),
2996                 taskView.getBottom());
2997         splitController.setInitialTaskSelect(taskView, splitPositionOption, initialBounds);
2998         mSplitHiddenTaskViewIndex = indexOfChild(taskView);
2999         mSplitPlaceholderView.setLayoutParams(
3000                 splitController.getLayoutParamsForActivePosition(getResources(),
3001                         mActivity.getDeviceProfile()));
3002         mSplitPlaceholderView.setIcon(taskView.getIconView());
3003     }
3004 
3005     public PendingAnimation createSplitSelectInitAnimation() {
3006         int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
3007         return createTaskDismissAnimation(mSplitHiddenTaskView, true, false, duration);
3008     }
3009 
3010     public void confirmSplitSelect(TaskView taskView) {
3011         mSplitPlaceholderView.getSplitController().setSecondTaskId(taskView);
3012         resetTaskVisuals();
3013         setTranslationY(0);
3014     }
3015 
3016     public PendingAnimation cancelSplitSelect(boolean animate) {
3017         SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController();
3018         SplitPositionOption splitOption = splitController.getActiveSplitPositionOption();
3019         Rect initialBounds = splitController.getInitialBounds();
3020         splitController.resetState();
3021         int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
3022         PendingAnimation pendingAnim = new PendingAnimation(duration);
3023         if (!animate) {
3024             resetFromSplitSelectionState();
3025             return pendingAnim;
3026         }
3027 
3028         addViewInLayout(mSplitHiddenTaskView, mSplitHiddenTaskViewIndex,
3029                 mSplitHiddenTaskView.getLayoutParams());
3030         mSplitHiddenTaskView.setAlpha(0);
3031         int[] oldScroll = new int[getChildCount()];
3032         getPageScrolls(oldScroll, false,
3033                 view -> view.getVisibility() != GONE && view != mSplitHiddenTaskView);
3034 
3035         int[] newScroll = new int[getChildCount()];
3036         getPageScrolls(newScroll, false, SIMPLE_SCROLL_LOGIC);
3037 
3038         boolean needsCurveUpdates = false;
3039         for (int i = mSplitHiddenTaskViewIndex; i >= 0; i--) {
3040             View child = getChildAt(i);
3041             if (child == mSplitHiddenTaskView) {
3042                 TaskView taskView = (TaskView) child;
3043 
3044                 int dir = mOrientationHandler.getSplitTaskViewDismissDirection(splitOption,
3045                         mActivity.getDeviceProfile());
3046                 FloatProperty<TaskView> dismissingTaskViewTranslate;
3047                 Rect hiddenBounds = new Rect(taskView.getLeft(), taskView.getTop(),
3048                         taskView.getRight(), taskView.getBottom());
3049                 int distanceDelta = 0;
3050                 if (dir == PagedOrientationHandler.SPLIT_TRANSLATE_SECONDARY_NEGATIVE) {
3051                     dismissingTaskViewTranslate = taskView
3052                             .getSecondaryDissmissTranslationProperty();
3053                     distanceDelta = initialBounds.top - hiddenBounds.top;
3054                     taskView.layout(initialBounds.left, hiddenBounds.top, initialBounds.right,
3055                             hiddenBounds.bottom);
3056                 } else {
3057                     dismissingTaskViewTranslate = taskView
3058                             .getPrimaryDismissTranslationProperty();
3059                     distanceDelta = initialBounds.left - hiddenBounds.left;
3060                     taskView.layout(hiddenBounds.left, initialBounds.top, hiddenBounds.right,
3061                             initialBounds.bottom);
3062                     if (dir == PagedOrientationHandler.SPLIT_TRANSLATE_PRIMARY_POSITIVE) {
3063                         distanceDelta *= -1;
3064                     }
3065                 }
3066                 pendingAnim.add(ObjectAnimator.ofFloat(mSplitHiddenTaskView,
3067                         dismissingTaskViewTranslate,
3068                         distanceDelta));
3069                 pendingAnim.add(ObjectAnimator.ofFloat(mSplitHiddenTaskView, ALPHA, 1));
3070             } else {
3071                 // If insertion is on last index (furthest from clear all), we directly add the view
3072                 // else we translate all views to the right of insertion index further right,
3073                 // ignore views to left
3074                 if (showAsGrid()) {
3075                     // TODO(b/186800707) handle more elegantly for grid
3076                     continue;
3077                 }
3078                 int scrollDiff = newScroll[i] - oldScroll[i];
3079                 if (scrollDiff != 0) {
3080                     FloatProperty translationProperty = child instanceof TaskView
3081                             ? ((TaskView) child).getPrimaryDismissTranslationProperty()
3082                             : mOrientationHandler.getPrimaryViewTranslate();
3083 
3084                     ResourceProvider rp = DynamicResource.provider(mActivity);
3085                     SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_END)
3086                             .setDampingRatio(
3087                                     rp.getFloat(R.dimen.dismiss_task_trans_x_damping_ratio))
3088                             .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_x_stiffness));
3089                     pendingAnim.add(ObjectAnimator.ofFloat(child, translationProperty, scrollDiff)
3090                             .setDuration(duration), ACCEL, sp);
3091                     needsCurveUpdates = true;
3092                 }
3093             }
3094         }
3095 
3096         if (needsCurveUpdates) {
3097             pendingAnim.addOnFrameCallback(this::updateCurveProperties);
3098         }
3099 
3100         pendingAnim.addListener(new AnimationSuccessListener() {
3101             @Override
3102             public void onAnimationSuccess(Animator animator) {
3103                 // TODO(b/186800707) Figure out how to undo for grid view
3104                 //  Need to handle cases where dismissed task is
3105                 //  * Top Row
3106                 //  * Bottom Row
3107                 //  * Focused Task
3108                 updateGridProperties();
3109                 resetFromSplitSelectionState();
3110             }
3111         });
3112 
3113         return pendingAnim;
3114     }
3115 
3116     private void resetFromSplitSelectionState() {
3117         mSplitHiddenTaskView.setTranslationY(0);
3118         if (!showAsGrid()) {
3119             // TODO(b/186800707)
3120             int pageToSnapTo = mCurrentPage;
3121             if (mSplitHiddenTaskViewIndex <= pageToSnapTo) {
3122                 pageToSnapTo += 1;
3123             } else {
3124                 pageToSnapTo = mSplitHiddenTaskViewIndex;
3125             }
3126             snapToPageImmediately(pageToSnapTo);
3127         }
3128         onLayout(false /*  changed */, getLeft(), getTop(), getRight(), getBottom());
3129         resetTaskVisuals();
3130         mSplitHiddenTaskView = null;
3131         mSplitHiddenTaskViewIndex = -1;
3132     }
3133 
3134     private void updateDeadZoneRects() {
3135         // Get the deadzone rect surrounding the clear all button to not dismiss overview to home
3136         mClearAllButtonDeadZoneRect.setEmpty();
3137         if (mClearAllButton.getWidth() > 0) {
3138             int verticalMargin = getResources()
3139                     .getDimensionPixelSize(R.dimen.recents_clear_all_deadzone_vertical_margin);
3140             mClearAllButton.getHitRect(mClearAllButtonDeadZoneRect);
3141             mClearAllButtonDeadZoneRect.inset(-getPaddingRight() / 2, -verticalMargin);
3142         }
3143 
3144         // Get the deadzone rect between the task views
3145         mTaskViewDeadZoneRect.setEmpty();
3146         int count = getTaskViewCount();
3147         if (count > 0) {
3148             final View taskView = getTaskViewAt(0);
3149             getTaskViewAt(count - 1).getHitRect(mTaskViewDeadZoneRect);
3150             mTaskViewDeadZoneRect.union(taskView.getLeft(), taskView.getTop(), taskView.getRight(),
3151                     taskView.getBottom());
3152         }
3153     }
3154 
3155     private void updateEmptyStateUi(boolean sizeChanged) {
3156         boolean hasValidSize = getWidth() > 0 && getHeight() > 0;
3157         if (sizeChanged && hasValidSize) {
3158             mEmptyTextLayout = null;
3159             mLastMeasureSize.set(getWidth(), getHeight());
3160         }
3161 
3162         if (mShowEmptyMessage && hasValidSize && mEmptyTextLayout == null) {
3163             int availableWidth = mLastMeasureSize.x - mEmptyMessagePadding - mEmptyMessagePadding;
3164             mEmptyTextLayout = StaticLayout.Builder.obtain(mEmptyMessage, 0, mEmptyMessage.length(),
3165                     mEmptyMessagePaint, availableWidth)
3166                     .setAlignment(Layout.Alignment.ALIGN_CENTER)
3167                     .build();
3168             int totalHeight = mEmptyTextLayout.getHeight()
3169                     + mEmptyMessagePadding + mEmptyIcon.getIntrinsicHeight();
3170 
3171             int top = (mLastMeasureSize.y - totalHeight) / 2;
3172             int left = (mLastMeasureSize.x - mEmptyIcon.getIntrinsicWidth()) / 2;
3173             mEmptyIcon.setBounds(left, top, left + mEmptyIcon.getIntrinsicWidth(),
3174                     top + mEmptyIcon.getIntrinsicHeight());
3175         }
3176     }
3177 
3178     @Override
3179     protected boolean verifyDrawable(Drawable who) {
3180         return super.verifyDrawable(who) || (mShowEmptyMessage && who == mEmptyIcon);
3181     }
3182 
3183     protected void maybeDrawEmptyMessage(Canvas canvas) {
3184         if (mShowEmptyMessage && mEmptyTextLayout != null) {
3185             // Offset to center in the visible (non-padded) part of RecentsView
3186             mTempRect.set(mInsets.left + getPaddingLeft(), mInsets.top + getPaddingTop(),
3187                     mInsets.right + getPaddingRight(), mInsets.bottom + getPaddingBottom());
3188             canvas.save();
3189             canvas.translate(getScrollX() + (mTempRect.left - mTempRect.right) / 2,
3190                     (mTempRect.top - mTempRect.bottom) / 2);
3191             mEmptyIcon.draw(canvas);
3192             canvas.translate(mEmptyMessagePadding,
3193                     mEmptyIcon.getBounds().bottom + mEmptyMessagePadding);
3194             mEmptyTextLayout.draw(canvas);
3195             canvas.restore();
3196         }
3197     }
3198 
3199     /**
3200      * Animate adjacent tasks off screen while scaling up.
3201      *
3202      * If launching one of the adjacent tasks, parallax the center task and other adjacent task
3203      * to the right.
3204      */
3205     public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv) {
3206         AnimatorSet anim = new AnimatorSet();
3207 
3208         int taskIndex = indexOfChild(tv);
3209         int centerTaskIndex = getCurrentPage();
3210         boolean launchingCenterTask = taskIndex == centerTaskIndex;
3211 
3212         float toScale = getMaxScaleForFullScreen();
3213         RecentsView recentsView = tv.getRecentsView();
3214         if (launchingCenterTask) {
3215             anim.play(ObjectAnimator.ofFloat(recentsView, RECENTS_SCALE_PROPERTY, toScale));
3216             anim.play(ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, 1));
3217         } else {
3218             // We are launching an adjacent task, so parallax the center and other adjacent task.
3219             float displacementX = tv.getWidth() * (toScale - 1f);
3220             float primaryTranslation = mIsRtl ? -displacementX : displacementX;
3221             anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex),
3222                     mOrientationHandler.getPrimaryViewTranslate(), primaryTranslation));
3223             int runningTaskIndex = recentsView.getRunningTaskIndex();
3224             if (ENABLE_QUICKSTEP_LIVE_TILE.get() && runningTaskIndex != -1
3225                     && runningTaskIndex != taskIndex) {
3226                 anim.play(ObjectAnimator.ofFloat(
3227                         recentsView.getLiveTileTaskViewSimulator().taskPrimaryTranslation,
3228                         AnimatedFloat.VALUE,
3229                         primaryTranslation));
3230             }
3231 
3232             int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - taskIndex);
3233             if (otherAdjacentTaskIndex >= 0 && otherAdjacentTaskIndex < getPageCount()) {
3234                 PropertyValuesHolder[] properties = new PropertyValuesHolder[3];
3235                 properties[0] = PropertyValuesHolder.ofFloat(
3236                         mOrientationHandler.getPrimaryViewTranslate(), primaryTranslation);
3237                 properties[1] = PropertyValuesHolder.ofFloat(View.SCALE_X, 1);
3238                 properties[2] = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1);
3239 
3240                 anim.play(ObjectAnimator.ofPropertyValuesHolder(getPageAt(otherAdjacentTaskIndex),
3241                         properties));
3242             }
3243         }
3244         return anim;
3245     }
3246 
3247     /**
3248      * Returns the scale up required on the view, so that it coves the screen completely
3249      */
3250     public float getMaxScaleForFullScreen() {
3251         getTaskSize(mTempRect);
3252         return getPagedViewOrientedState().getFullScreenScaleAndPivot(
3253                 mTempRect, mActivity.getDeviceProfile(), mTempPointF);
3254     }
3255 
3256     public PendingAnimation createTaskLaunchAnimation(
3257             TaskView tv, long duration, Interpolator interpolator) {
3258         if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
3259             throw new IllegalStateException("Another pending animation is still running");
3260         }
3261 
3262         int count = getTaskViewCount();
3263         if (count == 0) {
3264             return new PendingAnimation(duration);
3265         }
3266 
3267         // When swiping down from overview to tasks, ensures the snapped page's scroll maintain
3268         // invariant between quick switch and overview, to ensure a smooth animation transition.
3269         updateGridProperties();
3270         updateScrollSynchronously();
3271 
3272         int targetSysUiFlags = tv.getThumbnail().getSysUiStatusNavFlags();
3273         final boolean[] passedOverviewThreshold = new boolean[] {false};
3274         ValueAnimator progressAnim = ValueAnimator.ofFloat(0, 1);
3275         progressAnim.addUpdateListener(animator -> {
3276             // Once we pass a certain threshold, update the sysui flags to match the target
3277             // tasks' flags
3278             if (animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD) {
3279                 mActivity.getSystemUiController().updateUiState(
3280                         UI_STATE_FULLSCREEN_TASK, targetSysUiFlags);
3281             } else {
3282                 mActivity.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, 0);
3283             }
3284 
3285             // Passing the threshold from taskview to fullscreen app will vibrate
3286             final boolean passed = animator.getAnimatedFraction() >=
3287                     SUCCESS_TRANSITION_PROGRESS;
3288             if (passed != passedOverviewThreshold[0]) {
3289                 passedOverviewThreshold[0] = passed;
3290                 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
3291                         HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3292             }
3293         });
3294 
3295         AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv);
3296 
3297         DepthController depthController = getDepthController();
3298         if (depthController != null) {
3299             ObjectAnimator depthAnimator = ObjectAnimator.ofFloat(depthController, DEPTH,
3300                     BACKGROUND_APP.getDepth(mActivity));
3301             anim.play(depthAnimator);
3302         }
anim.play(progressAnim)3303         anim.play(progressAnim);
anim.setInterpolator(interpolator)3304         anim.setInterpolator(interpolator);
3305 
3306         mPendingAnimation = new PendingAnimation(duration);
mPendingAnimation.add(anim)3307         mPendingAnimation.add(anim);
3308         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
mLiveTileTaskViewSimulator.addOverviewToAppAnim(mPendingAnimation, interpolator)3309             mLiveTileTaskViewSimulator.addOverviewToAppAnim(mPendingAnimation, interpolator);
mPendingAnimation.addOnFrameCallback(this::redrawLiveTile)3310             mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
3311         }
3312         mPendingAnimation.addEndListener(isSuccess -> {
3313             if (isSuccess) {
3314                 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && tv.isRunningTask()) {
3315                     finishRecentsAnimation(false /* toRecents */, null);
3316                     onTaskLaunchAnimationEnd(true /* success */);
3317                 } else {
3318                     tv.launchTask(this::onTaskLaunchAnimationEnd);
3319                 }
3320                 Task task = tv.getTask();
3321                 if (task != null) {
3322                     mActivity.getStatsLogManager().logger().withItemInfo(tv.getItemInfo())
3323                             .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
3324                 }
3325             } else {
3326                 onTaskLaunchAnimationEnd(false);
3327             }
3328             mPendingAnimation = null;
3329         });
3330         return mPendingAnimation;
3331     }
3332 
onTaskLaunchAnimationEnd(boolean success)3333     protected void onTaskLaunchAnimationEnd(boolean success) {
3334         if (success) {
3335             resetTaskVisuals();
3336         }
3337     }
3338 
3339     @Override
notifyPageSwitchListener(int prevPage)3340     protected void notifyPageSwitchListener(int prevPage) {
3341         super.notifyPageSwitchListener(prevPage);
3342         loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
3343         updateEnabledOverlays();
3344     }
3345 
3346     @Override
getCurrentPageDescription()3347     protected String getCurrentPageDescription() {
3348         return "";
3349     }
3350 
3351     @Override
addChildrenForAccessibility(ArrayList<View> outChildren)3352     public void addChildrenForAccessibility(ArrayList<View> outChildren) {
3353         // Add children in reverse order
3354         for (int i = getChildCount() - 1; i >= 0; --i) {
3355             outChildren.add(getChildAt(i));
3356         }
3357     }
3358 
3359     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)3360     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
3361         super.onInitializeAccessibilityNodeInfo(info);
3362         final AccessibilityNodeInfo.CollectionInfo
3363                 collectionInfo = AccessibilityNodeInfo.CollectionInfo.obtain(
3364                 1, getTaskViewCount(), false,
3365                 AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_NONE);
3366         info.setCollectionInfo(collectionInfo);
3367     }
3368 
3369     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)3370     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
3371         super.onInitializeAccessibilityEvent(event);
3372 
3373         final int taskViewCount = getTaskViewCount();
3374         event.setScrollable(taskViewCount > 0);
3375 
3376         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
3377             final int[] visibleTasks = getVisibleChildrenRange();
3378             event.setFromIndex(taskViewCount - visibleTasks[1]);
3379             event.setToIndex(taskViewCount - visibleTasks[0]);
3380             event.setItemCount(taskViewCount);
3381         }
3382     }
3383 
3384     @Override
getAccessibilityClassName()3385     public CharSequence getAccessibilityClassName() {
3386         // To hear position-in-list related feedback from Talkback.
3387         return ListView.class.getName();
3388     }
3389 
3390     @Override
isPageOrderFlipped()3391     protected boolean isPageOrderFlipped() {
3392         return true;
3393     }
3394 
setEnableDrawingLiveTile(boolean enableDrawingLiveTile)3395     public void setEnableDrawingLiveTile(boolean enableDrawingLiveTile) {
3396         mEnableDrawingLiveTile = enableDrawingLiveTile;
3397     }
3398 
redrawLiveTile()3399     public void redrawLiveTile() {
3400         if (mLiveTileParams.getTargetSet() != null) {
3401             mLiveTileTaskViewSimulator.apply(mLiveTileParams);
3402         }
3403     }
3404 
getLiveTileTaskViewSimulator()3405     public TaskViewSimulator getLiveTileTaskViewSimulator() {
3406         return mLiveTileTaskViewSimulator;
3407     }
3408 
getLiveTileParams()3409     public TransformParams getLiveTileParams() {
3410         return mLiveTileParams;
3411     }
3412 
3413     // TODO: To be removed in a follow up CL
setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController, RecentsAnimationTargets recentsAnimationTargets)3414     public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController,
3415             RecentsAnimationTargets recentsAnimationTargets) {
3416         mRecentsAnimationController = recentsAnimationController;
3417         if (recentsAnimationTargets != null && recentsAnimationTargets.apps.length > 0) {
3418             if (mSyncTransactionApplier != null) {
3419                 recentsAnimationTargets.addReleaseCheck(mSyncTransactionApplier);
3420             }
3421             mLiveTileTaskViewSimulator.setPreview(
3422                     recentsAnimationTargets.apps[recentsAnimationTargets.apps.length - 1]);
3423             mLiveTileParams.setTargetSet(recentsAnimationTargets);
3424         }
3425     }
3426 
finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete)3427     public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) {
3428         finishRecentsAnimation(toRecents, true /* shouldPip */, onFinishComplete);
3429     }
3430 
finishRecentsAnimation(boolean toRecents, boolean shouldPip, Runnable onFinishComplete)3431     public void finishRecentsAnimation(boolean toRecents, boolean shouldPip,
3432             Runnable onFinishComplete) {
3433         if (!toRecents && ENABLE_QUICKSTEP_LIVE_TILE.get()) {
3434             // Reset the minimized state since we force-toggled the minimized state when entering
3435             // overview, but never actually finished the recents animation.  This is a catch all for
3436             // cases where we haven't already reset it.
3437             SystemUiProxy p = SystemUiProxy.INSTANCE.getNoCreate();
3438             if (p != null) {
3439                 p.setSplitScreenMinimized(false);
3440             }
3441         }
3442 
3443         if (mRecentsAnimationController == null) {
3444             if (onFinishComplete != null) {
3445                 onFinishComplete.run();
3446             }
3447             return;
3448         }
3449 
3450         final boolean sendUserLeaveHint = toRecents && shouldPip;
3451         if (sendUserLeaveHint) {
3452             // Notify the SysUI to use fade-in animation when entering PiP from live tile.
3453             final SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(getContext());
3454             systemUiProxy.notifySwipeToHomeFinished();
3455             systemUiProxy.setShelfHeight(true, mActivity.getDeviceProfile().hotseatBarSizePx);
3456         }
3457         mRecentsAnimationController.finish(toRecents, () -> {
3458             if (onFinishComplete != null) {
3459                 onFinishComplete.run();
3460             }
3461             onRecentsAnimationComplete();
3462         }, sendUserLeaveHint);
3463     }
3464 
3465     /**
3466      * Called when a running recents animation has finished or canceled.
3467      */
onRecentsAnimationComplete()3468     public void onRecentsAnimationComplete() {
3469         // At this point, the recents animation is not running and if the animation was canceled
3470         // by a display rotation then reset this state to show the screenshot
3471         setRunningTaskViewShowScreenshot(true);
3472         // After we finish the recents animation, the current task id should be correctly
3473         // reset so that when the task is launched from Overview later, it goes through the
3474         // flow of starting a new task instead of finishing recents animation to app. A
3475         // typical example of this is (1) user swipes up from app to Overview (2) user
3476         // taps on QSB (3) user goes back to Overview and launch the most recent task.
3477         setCurrentTask(-1);
3478         mRecentsAnimationController = null;
3479         executeSideTaskLaunchCallback();
3480     }
3481 
setDisallowScrollToClearAll(boolean disallowScrollToClearAll)3482     public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) {
3483         if (mDisallowScrollToClearAll != disallowScrollToClearAll) {
3484             mDisallowScrollToClearAll = disallowScrollToClearAll;
3485             updateMinAndMaxScrollX();
3486         }
3487     }
3488 
3489     /**
3490      * Updates page scroll synchronously and layout child views.
3491      */
updateScrollSynchronously()3492     public void updateScrollSynchronously() {
3493         onLayout(false /*  changed */, getLeft(), getTop(), getRight(), getBottom());
3494         updateMinAndMaxScrollX();
3495     }
3496 
3497     @Override
computeMinScroll()3498     protected int computeMinScroll() {
3499         if (getTaskViewCount() > 0) {
3500             if (mIsRtl) {
3501                 // If we aren't showing the clear all button, use the rightmost task as the min
3502                 // scroll.
3503                 return getScrollForPage(mDisallowScrollToClearAll ? indexOfChild(
3504                         getTaskViewAt(getTaskViewCount() - 1)) : indexOfChild(mClearAllButton));
3505             } else {
3506                 TaskView focusedTaskView = showAsGrid() ? getFocusedTaskView() : null;
3507                 return getScrollForPage(focusedTaskView != null ? indexOfChild(focusedTaskView)
3508                         : mTaskViewStartIndex);
3509             }
3510         }
3511         return super.computeMinScroll();
3512     }
3513 
3514     @Override
computeMaxScroll()3515     protected int computeMaxScroll() {
3516         if (getTaskViewCount() > 0) {
3517             if (mIsRtl) {
3518                 TaskView focusedTaskView = showAsGrid() ? getFocusedTaskView() : null;
3519                 return getScrollForPage(focusedTaskView != null ? indexOfChild(focusedTaskView)
3520                         : mTaskViewStartIndex);
3521             } else {
3522                 // If we aren't showing the clear all button, use the leftmost task as the min
3523                 // scroll.
3524                 return getScrollForPage(mDisallowScrollToClearAll ? indexOfChild(
3525                         getTaskViewAt(getTaskViewCount() - 1)) : indexOfChild(mClearAllButton));
3526             }
3527         }
3528         return super.computeMaxScroll();
3529     }
3530 
3531     /**
3532      * Returns page scroll of ClearAllButton.
3533      */
getClearAllScroll()3534     public int getClearAllScroll() {
3535         return getScrollForPage(indexOfChild(mClearAllButton));
3536     }
3537 
3538     @Override
getPageScrolls(int[] outPageScrolls, boolean layoutChildren, ComputePageScrollsLogic scrollLogic)3539     protected boolean getPageScrolls(int[] outPageScrolls, boolean layoutChildren,
3540             ComputePageScrollsLogic scrollLogic) {
3541         int[] newPageScrolls = new int[outPageScrolls.length];
3542         super.getPageScrolls(newPageScrolls, layoutChildren, scrollLogic);
3543         boolean showAsFullscreen = showAsFullscreen();
3544         boolean showAsGrid = showAsGrid();
3545 
3546         // Align ClearAllButton to the left (RTL) or right (non-RTL), which is different from other
3547         // TaskViews. This must be called after laying out ClearAllButton.
3548         if (layoutChildren) {
3549             int clearAllWidthDiff = mOrientationHandler.getPrimaryValue(mTaskWidth, mTaskHeight)
3550                     - mOrientationHandler.getPrimarySize(mClearAllButton);
3551             mClearAllButton.setScrollOffsetPrimary(mIsRtl ? clearAllWidthDiff : -clearAllWidthDiff);
3552         }
3553 
3554         boolean pageScrollChanged = false;
3555         final int childCount = getChildCount();
3556         for (int i = 0; i < childCount; i++) {
3557             View child = getChildAt(i);
3558             float scrollDiff = 0;
3559             if (child instanceof TaskView) {
3560                 scrollDiff = ((TaskView) child).getScrollAdjustment(showAsFullscreen, showAsGrid);
3561             } else if (child instanceof ClearAllButton) {
3562                 scrollDiff = ((ClearAllButton) child).getScrollAdjustment(showAsFullscreen,
3563                         showAsGrid);
3564             }
3565 
3566             final int pageScroll = newPageScrolls[i] + (int) scrollDiff;
3567             if (outPageScrolls[i] != pageScroll) {
3568                 pageScrollChanged = true;
3569                 outPageScrolls[i] = pageScroll;
3570             }
3571         }
3572         return pageScrollChanged;
3573     }
3574 
3575     @Override
getChildOffset(int index)3576     protected int getChildOffset(int index) {
3577         int childOffset = super.getChildOffset(index);
3578         View child = getChildAt(index);
3579         if (child instanceof TaskView) {
3580             childOffset += ((TaskView) child).getOffsetAdjustment(showAsFullscreen(),
3581                     showAsGrid());
3582         } else if (child instanceof ClearAllButton) {
3583             childOffset += ((ClearAllButton) child).getOffsetAdjustment(mOverviewFullscreenEnabled,
3584                     showAsGrid());
3585         }
3586         return childOffset;
3587     }
3588 
3589     @Override
getChildVisibleSize(int index)3590     protected int getChildVisibleSize(int index) {
3591         final TaskView taskView = getTaskViewAtByAbsoluteIndex(index);
3592         if (taskView == null) {
3593             return super.getChildVisibleSize(index);
3594         }
3595         return (int) (super.getChildVisibleSize(index) * taskView.getSizeAdjustment(
3596                 showAsFullscreen()));
3597     }
3598 
getClearAllButton()3599     public ClearAllButton getClearAllButton() {
3600         return mClearAllButton;
3601     }
3602 
3603     /**
3604      * @return How many pixels the running task is offset on the currently laid out dominant axis.
3605      */
getScrollOffset()3606     public int getScrollOffset() {
3607         return getScrollOffset(getRunningTaskIndex());
3608     }
3609 
3610     /**
3611      * Returns how many pixels the page is offset on the currently laid out dominant axis.
3612      */
getScrollOffset(int pageIndex)3613     public int getScrollOffset(int pageIndex) {
3614         if (pageIndex == -1) {
3615             return 0;
3616         }
3617 
3618         int overScrollShift = getOverScrollShift();
3619         if (mAdjacentPageHorizontalOffset > 0) {
3620             // Don't dampen the scroll (due to overscroll) if the adjacent tasks are offscreen, so
3621             // that the page can move freely given there's no visual indication why it shouldn't.
3622             overScrollShift = (int) Utilities.mapRange(mAdjacentPageHorizontalOffset,
3623                     overScrollShift, getUndampedOverScrollShift());
3624         }
3625         return getScrollForPage(pageIndex) - mOrientationHandler.getPrimaryScroll(this)
3626                 + overScrollShift;
3627     }
3628 
3629     /**
3630      * Returns how many pixels the task is offset on the currently laid out secondary axis
3631      * according to {@link #mGridProgress}.
3632      */
getGridTranslationSecondary(int pageIndex)3633     public float getGridTranslationSecondary(int pageIndex) {
3634         TaskView taskView = getTaskViewAtByAbsoluteIndex(pageIndex);
3635         if (taskView == null) {
3636             return 0;
3637         }
3638 
3639         return mOrientationHandler.getSecondaryValue(taskView.getGridTranslationX(),
3640                 taskView.getGridTranslationY());
3641     }
3642 
getEventDispatcher(float navbarRotation)3643     public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
3644         float degreesRotated;
3645         if (navbarRotation == 0) {
3646             degreesRotated = mOrientationHandler.getDegreesRotated();
3647         } else {
3648             degreesRotated = -navbarRotation;
3649         }
3650         if (degreesRotated == 0) {
3651             return super::onTouchEvent;
3652         }
3653 
3654         // At this point the event coordinates have already been transformed, so we need to
3655         // undo that transformation since PagedView also accommodates for the transformation via
3656         // PagedOrientationHandler
3657         return e -> {
3658             if (navbarRotation != 0
3659                     && mOrientationState.isMultipleOrientationSupportedByDevice()
3660                     && !mOrientationState.getOrientationHandler().isLayoutNaturalToLauncher()) {
3661                 mOrientationState.flipVertical(e);
3662                 super.onTouchEvent(e);
3663                 mOrientationState.flipVertical(e);
3664                 return;
3665             }
3666             mOrientationState.transformEvent(-degreesRotated, e, true);
3667             super.onTouchEvent(e);
3668             mOrientationState.transformEvent(-degreesRotated, e, false);
3669         };
3670     }
3671 
3672     private void updateEnabledOverlays() {
3673         int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1;
3674         int taskCount = getTaskViewCount();
3675         for (int i = mTaskViewStartIndex; i < mTaskViewStartIndex + taskCount; i++) {
3676             getTaskViewAtByAbsoluteIndex(i).setOverlayEnabled(i == overlayEnabledPage);
3677         }
3678     }
3679 
3680     public void setOverlayEnabled(boolean overlayEnabled) {
3681         if (mOverlayEnabled != overlayEnabled) {
3682             mOverlayEnabled = overlayEnabled;
3683             updateEnabledOverlays();
3684         }
3685     }
3686 
3687     public void setOverviewGridEnabled(boolean overviewGridEnabled) {
3688         if (mOverviewGridEnabled != overviewGridEnabled) {
3689             mOverviewGridEnabled = overviewGridEnabled;
3690             // Request layout to ensure scroll position is recalculated with updated mGridProgress.
3691             requestLayout();
3692         }
3693     }
3694 
3695     public void setOverviewFullscreenEnabled(boolean overviewFullscreenEnabled) {
3696         if (mOverviewFullscreenEnabled != overviewFullscreenEnabled) {
3697             mOverviewFullscreenEnabled = overviewFullscreenEnabled;
3698             // Request layout to ensure scroll position is recalculated with updated
3699             // mFullscreenProgress.
3700             requestLayout();
3701         }
3702     }
3703 
3704     /**
3705      * Switch the current running task view to static snapshot mode,
3706      * capturing the snapshot at the same time.
3707      */
3708     public void switchToScreenshot(Runnable onFinishRunnable) {
3709         if (mRecentsAnimationController == null) {
3710             if (onFinishRunnable != null) {
3711                 onFinishRunnable.run();
3712             }
3713             return;
3714         }
3715         switchToScreenshot(mRunningTaskId == -1 ? null
3716                 : mRecentsAnimationController.screenshotTask(mRunningTaskId), onFinishRunnable);
3717     }
3718 
3719     /**
3720      * Switch the current running task view to static snapshot mode, using the
3721      * provided thumbnail data as the snapshot.
3722      */
3723     public void switchToScreenshot(ThumbnailData thumbnailData, Runnable onFinishRunnable) {
3724         TaskView taskView = getRunningTaskView();
3725         if (taskView != null) {
3726             taskView.setShowScreenshot(true);
3727             if (thumbnailData != null) {
3728                 taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData);
3729             } else {
3730                 taskView.getThumbnail().refresh();
3731             }
3732             ViewUtils.postFrameDrawn(taskView, onFinishRunnable);
3733         } else {
3734             onFinishRunnable.run();
3735         }
3736     }
3737 
3738     /**
3739      * The current task is fully modal (modalness = 1) when it is shown on its own in a modal
3740      * way. Modalness 0 means the task is shown in context with all the other tasks.
3741      */
3742     private void setTaskModalness(float modalness) {
3743         mTaskModalness = modalness;
3744         updatePageOffsets();
3745         if (getCurrentPageTaskView() != null) {
3746             getCurrentPageTaskView().setModalness(modalness);
3747         }
3748         // Only show actions view when it's modal for in-place landscape mode.
3749         boolean inPlaceLandscape = !mOrientationState.canRecentsActivityRotate()
3750                 && mOrientationState.getTouchRotation() != ROTATION_0;
3751         mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, modalness < 1 && inPlaceLandscape);
3752         mActionsView.setTaskModalness(modalness);
3753     }
3754 
3755     @Nullable
3756     protected DepthController getDepthController() {
3757         return null;
3758     }
3759 
3760     @Override
3761     public void onSecondaryWindowBoundsChanged() {
3762         // Invalidate the task view size
3763         setInsets(mInsets);
3764     }
3765 
3766     /**
3767      * Enables or disables modal state for RecentsView
3768      * @param isModalState
3769      */
3770     public void setModalStateEnabled(boolean isModalState) { }
3771 
3772     public TaskOverlayFactory getTaskOverlayFactory() {
3773         return mTaskOverlayFactory;
3774     }
3775 
3776     public BaseActivityInterface getSizeStrategy() {
3777         return mSizeStrategy;
3778     }
3779 
3780     /**
3781      * Set all the task views to color tint scrim mode, dimming or tinting them all. Allows the
3782      * tasks to be dimmed while other elements in the recents view are left alone.
3783      */
3784     public void showForegroundScrim(boolean show) {
3785         if (!show && mColorTint == 0) {
3786             if (mTintingAnimator != null) {
3787                 mTintingAnimator.cancel();
3788                 mTintingAnimator = null;
3789             }
3790             return;
3791         }
3792 
3793         mTintingAnimator = ObjectAnimator.ofFloat(this, COLOR_TINT, show ? 0.5f : 0f);
3794         mTintingAnimator.setAutoCancel(true);
3795         mTintingAnimator.start();
3796     }
3797 
3798     /** Tint the RecentsView and TaskViews in to simulate a scrim. */
3799     // TODO(b/187528071): Replace this tinting with a scrim on top of RecentsView
3800     private void setColorTint(float tintAmount) {
3801         mColorTint = tintAmount;
3802 
3803         for (int i = 0; i < getTaskViewCount(); i++) {
3804             getTaskViewAt(i).setColorTint(mColorTint, mTintingColor);
3805         }
3806 
3807         Drawable scrimBg = mActivity.getScrimView().getBackground();
3808         if (scrimBg != null) {
3809             if (tintAmount == 0f) {
3810                 scrimBg.setTintList(null);
3811             } else {
3812                 scrimBg.setTintBlendMode(BlendMode.SRC_OVER);
3813                 scrimBg.setTint(
3814                         ColorUtils.setAlphaComponent(mTintingColor, (int) (255 * tintAmount)));
3815             }
3816         }
3817     }
3818 
3819     private float getColorTint() {
3820         return mColorTint;
3821     }
3822 
3823     /** Returns {@code true} if the overview tasks are displayed as a grid. */
3824     public boolean showAsGrid() {
3825         return mOverviewGridEnabled || (mCurrentGestureEndTarget != null
3826                 && mSizeStrategy.stateFromGestureEndTarget(
3827                 mCurrentGestureEndTarget).displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
3828     }
3829 
3830     private boolean showAsFullscreen() {
3831         return mOverviewFullscreenEnabled
3832                 && mCurrentGestureEndTarget != GestureState.GestureEndTarget.RECENTS;
3833     }
3834 
3835     public boolean shouldShowOverviewActionsForState(STATE_TYPE state) {
3836         return !state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile())
3837                 || getFocusedTaskView() != null;
3838     }
3839 
3840     /**
3841      * Used to register callbacks for when our empty message state changes.
3842      *
3843      * @see #setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener)
3844      * @see #updateEmptyMessage()
3845      */
3846     public interface OnEmptyMessageUpdatedListener {
3847         /** @param isEmpty Whether RecentsView is empty (i.e. has no children) */
3848         void onEmptyMessageUpdated(boolean isEmpty);
3849     }
3850 
3851     /**
3852      * Adds a listener for scroll changes
3853      */
3854     public void addOnScrollChangedListener(OnScrollChangedListener listener) {
3855         mScrollListeners.add(listener);
3856     }
3857 
3858     /**
3859      * Removes a previously added scroll change listener
3860      */
3861     public void removeOnScrollChangedListener(OnScrollChangedListener listener) {
3862         mScrollListeners.remove(listener);
3863     }
3864 
3865     /**
3866      * @return Corner radius in pixel value for PiP window, which is updated via
3867      *         {@link #mIPipAnimationListener}
3868      */
3869     public int getPipCornerRadius() {
3870         return mPipCornerRadius;
3871     }
3872 
3873     @Override
3874     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
3875         super.onScrollChanged(l, t, oldl, oldt);
3876         dispatchScrollChanged();
3877     }
3878 
3879     private void dispatchScrollChanged() {
3880         mLiveTileTaskViewSimulator.setScroll(getScrollOffset());
3881         for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
3882             mScrollListeners.get(i).onScrollChanged();
3883         }
3884     }
3885 
3886     private static class PinnedStackAnimationListener<T extends BaseActivity> extends
3887             IPipAnimationListener.Stub {
3888         private T mActivity;
3889         private RecentsView mRecentsView;
3890 
3891         public void setActivityAndRecentsView(T activity, RecentsView recentsView) {
3892             mActivity = activity;
3893             mRecentsView = recentsView;
3894         }
3895 
3896         @Override
3897         public void onPipAnimationStarted() {
3898             MAIN_EXECUTOR.execute(() -> {
3899                 // Needed for activities that auto-enter PiP, which will not trigger a remote
3900                 // animation to be created
3901                 if (mActivity != null) {
3902                     mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
3903                 }
3904             });
3905         }
3906 
3907         @Override
3908         public void onPipCornerRadiusChanged(int cornerRadius) {
3909             if (mRecentsView != null) {
3910                 mRecentsView.mPipCornerRadius = cornerRadius;
3911             }
3912         }
3913     }
3914 
3915     /** Get the color used for foreground scrimming the RecentsView for sharing. */
3916     public static int getForegroundScrimDimColor(Context context) {
3917         int baseColor = Themes.getAttrColor(context, R.attr.overviewScrimColor);
3918         // The Black blending is temporary until we have the proper color token.
3919         return ColorUtils.blendARGB(Color.BLACK, baseColor, 0.25f);
3920     }
3921 
3922     /** Get the RecentsAnimationController */
3923     @Nullable
3924     public RecentsAnimationController getRecentsAnimationController() {
3925         return mRecentsAnimationController;
3926     }
3927 
3928     /** Update the current activity locus id to show the enabled state of Overview */
3929     public void updateLocusId() {
3930         String locusId = "Overview";
3931 
3932         if (mOverviewStateEnabled && mActivity.isStarted()) {
3933             locusId += "|ENABLED";
3934         } else {
3935             locusId += "|DISABLED";
3936         }
3937 
3938         final LocusId id = new LocusId(locusId);
3939         // Set locus context is a binder call, don't want it to happen during a transition
3940         UI_HELPER_EXECUTOR.post(() -> mActivity.setLocusContext(id, Bundle.EMPTY));
3941     }
3942 }
3943