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 androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS;
20 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
21 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
22 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
23 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
24 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
25 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
26 import static com.android.launcher3.Utilities.squaredHypot;
27 import static com.android.launcher3.Utilities.squaredTouchSlop;
28 import static com.android.launcher3.anim.Interpolators.ACCEL;
29 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
30 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
31 import static com.android.launcher3.anim.Interpolators.LINEAR;
32 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
33 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
34 import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
35 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
36 import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON;
37 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
38 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
39 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
40 
41 import android.animation.Animator;
42 import android.animation.AnimatorSet;
43 import android.animation.LayoutTransition;
44 import android.animation.LayoutTransition.TransitionListener;
45 import android.animation.ObjectAnimator;
46 import android.animation.TimeInterpolator;
47 import android.animation.ValueAnimator;
48 import android.annotation.TargetApi;
49 import android.app.ActivityManager;
50 import android.content.ComponentName;
51 import android.content.Context;
52 import android.content.Intent;
53 import android.graphics.Canvas;
54 import android.graphics.Matrix;
55 import android.graphics.Point;
56 import android.graphics.Rect;
57 import android.graphics.RectF;
58 import android.graphics.Typeface;
59 import android.graphics.drawable.Drawable;
60 import android.os.Build;
61 import android.os.Handler;
62 import android.text.Layout;
63 import android.text.StaticLayout;
64 import android.text.TextPaint;
65 import android.util.AttributeSet;
66 import android.util.FloatProperty;
67 import android.util.SparseBooleanArray;
68 import android.view.HapticFeedbackConstants;
69 import android.view.KeyEvent;
70 import android.view.LayoutInflater;
71 import android.view.MotionEvent;
72 import android.view.View;
73 import android.view.ViewDebug;
74 import android.view.ViewGroup;
75 import android.view.WindowInsets;
76 import android.view.accessibility.AccessibilityEvent;
77 import android.view.accessibility.AccessibilityNodeInfo;
78 import android.widget.ListView;
79 
80 import androidx.annotation.Nullable;
81 import androidx.dynamicanimation.animation.SpringForce;
82 
83 import com.android.launcher3.BaseActivity;
84 import com.android.launcher3.DeviceProfile;
85 import com.android.launcher3.Insettable;
86 import com.android.launcher3.InvariantDeviceProfile;
87 import com.android.launcher3.LauncherState;
88 import com.android.launcher3.PagedView;
89 import com.android.launcher3.R;
90 import com.android.launcher3.Utilities;
91 import com.android.launcher3.anim.AnimatorPlaybackController;
92 import com.android.launcher3.anim.PropertyListBuilder;
93 import com.android.launcher3.anim.SpringObjectAnimator;
94 import com.android.launcher3.config.FeatureFlags;
95 import com.android.launcher3.graphics.RotationMode;
96 import com.android.launcher3.userevent.nano.LauncherLogProto;
97 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
98 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
99 import com.android.launcher3.util.ComponentKey;
100 import com.android.launcher3.util.OverScroller;
101 import com.android.launcher3.util.PendingAnimation;
102 import com.android.launcher3.util.Themes;
103 import com.android.launcher3.util.ViewPool;
104 import com.android.quickstep.RecentsAnimationWrapper;
105 import com.android.quickstep.RecentsModel;
106 import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener;
107 import com.android.quickstep.TaskThumbnailCache;
108 import com.android.quickstep.TaskUtils;
109 import com.android.quickstep.util.ClipAnimationHelper;
110 import com.android.systemui.shared.recents.model.Task;
111 import com.android.systemui.shared.recents.model.ThumbnailData;
112 import com.android.systemui.shared.system.ActivityManagerWrapper;
113 import com.android.systemui.shared.system.LauncherEventUtil;
114 import com.android.systemui.shared.system.PackageManagerWrapper;
115 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
116 import com.android.systemui.shared.system.TaskStackChangeListener;
117 
118 import java.util.ArrayList;
119 import java.util.function.Consumer;
120 
121 /**
122  * A list of recent tasks.
123  */
124 @TargetApi(Build.VERSION_CODES.P)
125 public abstract class RecentsView<T extends BaseActivity> extends PagedView implements Insettable,
126         TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
127         InvariantDeviceProfile.OnIDPChangeListener, TaskThumbnailChangeListener {
128 
129     private static final String TAG = RecentsView.class.getSimpleName();
130 
131     public static final FloatProperty<RecentsView> CONTENT_ALPHA =
132             new FloatProperty<RecentsView>("contentAlpha") {
133                 @Override
134                 public void setValue(RecentsView view, float v) {
135                     view.setContentAlpha(v);
136                 }
137 
138                 @Override
139                 public Float get(RecentsView view) {
140                     return view.getContentAlpha();
141                 }
142             };
143 
144     public static final FloatProperty<RecentsView> FULLSCREEN_PROGRESS =
145             new FloatProperty<RecentsView>("fullscreenProgress") {
146                 @Override
147                 public void setValue(RecentsView recentsView, float v) {
148                     recentsView.setFullscreenProgress(v);
149                 }
150 
151                 @Override
152                 public Float get(RecentsView recentsView) {
153                     return recentsView.mFullscreenProgress;
154                 }
155             };
156 
157     protected RecentsAnimationWrapper mRecentsAnimationWrapper;
158     protected ClipAnimationHelper mClipAnimationHelper;
159     protected SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
160     protected int mTaskWidth;
161     protected int mTaskHeight;
162     protected boolean mEnableDrawingLiveTile = false;
163     protected final Rect mTempRect = new Rect();
164     protected final RectF mTempRectF = new RectF();
165 
166     private static final int DISMISS_TASK_DURATION = 300;
167     private static final int ADDITION_TASK_DURATION = 200;
168     // The threshold at which we update the SystemUI flags when animating from the task into the app
169     public static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.85f;
170 
171     protected final T mActivity;
172     private final float mFastFlingVelocity;
173     private final RecentsModel mModel;
174     private final int mTaskTopMargin;
175     private final ClearAllButton mClearAllButton;
176     private final Rect mClearAllButtonDeadZoneRect = new Rect();
177     private final Rect mTaskViewDeadZoneRect = new Rect();
178     protected final ClipAnimationHelper mTempClipAnimationHelper;
179 
180     private final ScrollState mScrollState = new ScrollState();
181     // Keeps track of the previously known visible tasks for purposes of loading/unloading task data
182     private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray();
183 
184     private final InvariantDeviceProfile mIdp;
185 
186     private final ViewPool<TaskView> mTaskViewPool;
187 
188     private boolean mDwbToastShown;
189     protected boolean mDisallowScrollToClearAll;
190     private boolean mOverlayEnabled;
191     private boolean mFreezeViewVisibility;
192 
193     /**
194      * TODO: Call reloadIdNeeded in onTaskStackChanged.
195      */
196     private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
197         @Override
198         public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
199             if (!mHandleTaskStackChanges) {
200                 return;
201             }
202             // Check this is for the right user
203             if (!checkCurrentOrManagedUserId(userId, getContext())) {
204                 return;
205             }
206 
207             // Remove the task immediately from the task list
208             TaskView taskView = getTaskView(taskId);
209             if (taskView != null) {
210                 removeView(taskView);
211             }
212         }
213 
214         @Override
215         public void onActivityUnpinned() {
216             if (!mHandleTaskStackChanges) {
217                 return;
218             }
219 
220             reloadIfNeeded();
221             enableLayoutTransitions();
222         }
223 
224         @Override
225         public void onTaskRemoved(int taskId) {
226             if (!mHandleTaskStackChanges) {
227                 return;
228             }
229 
230             UI_HELPER_EXECUTOR.execute(() -> {
231                 TaskView taskView = getTaskView(taskId);
232                 if (taskView == null) {
233                     return;
234                 }
235                 Handler handler = taskView.getHandler();
236                 if (handler == null) {
237                     return;
238                 }
239 
240                 // TODO: Add callbacks from AM reflecting adding/removing from the recents list, and
241                 //       remove all these checks
242                 Task.TaskKey taskKey = taskView.getTask().key;
243                 if (PackageManagerWrapper.getInstance().getActivityInfo(taskKey.getComponent(),
244                         taskKey.userId) == null) {
245                     // The package was uninstalled
246                     handler.post(() ->
247                             dismissTask(taskView, true /* animate */, false /* removeTask */));
248                 } else {
249                     mModel.findTaskWithId(taskKey.id, (key) -> {
250                         if (key == null) {
251                             // The task was removed from the recents list
252                             handler.post(() -> dismissTask(taskView, true /* animate */,
253                                     false /* removeTask */));
254                         }
255                     });
256                 }
257             });
258         }
259 
260         @Override
261         public void onPinnedStackAnimationStarted() {
262             // Needed for activities that auto-enter PiP, which will not trigger a remote
263             // animation to be created
264             mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
265         }
266     };
267 
268     // Used to keep track of the last requested task list id, so that we do not request to load the
269     // tasks again if we have already requested it and the task list has not changed
270     private int mTaskListChangeId = -1;
271 
272     // Only valid until the launcher state changes to NORMAL
273     protected int mRunningTaskId = -1;
274     protected boolean mRunningTaskTileHidden;
275     private Task mTmpRunningTask;
276 
277     private boolean mRunningTaskIconScaledDown = false;
278 
279     private boolean mOverviewStateEnabled;
280     private boolean mHandleTaskStackChanges;
281     private boolean mSwipeDownShouldLaunchApp;
282     private boolean mTouchDownToStartHome;
283     private final float mSquaredTouchSlop;
284     private int mDownX;
285     private int mDownY;
286 
287     private PendingAnimation mPendingAnimation;
288     private LayoutTransition mLayoutTransition;
289 
290     @ViewDebug.ExportedProperty(category = "launcher")
291     protected float mContentAlpha = 1;
292     @ViewDebug.ExportedProperty(category = "launcher")
293     protected float mFullscreenProgress = 0;
294 
295     // Keeps track of task id whose visual state should not be reset
296     private int mIgnoreResetTaskId = -1;
297 
298     // Variables for empty state
299     private final Drawable mEmptyIcon;
300     private final CharSequence mEmptyMessage;
301     private final TextPaint mEmptyMessagePaint;
302     private final Point mLastMeasureSize = new Point();
303     private final int mEmptyMessagePadding;
304     private boolean mShowEmptyMessage;
305     private Layout mEmptyTextLayout;
306     private LiveTileOverlay mLiveTileOverlay;
307 
308     // Keeps track of the index where the first TaskView should be
309     private int mTaskViewStartIndex = 0;
310 
311     private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener =
312             (inMultiWindowMode) -> {
313         if (!inMultiWindowMode && mOverviewStateEnabled) {
314             // TODO: Re-enable layout transitions for addition of the unpinned task
315             reloadIfNeeded();
316         }
317     };
318 
RecentsView(Context context, AttributeSet attrs, int defStyleAttr)319     public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
320         super(context, attrs, defStyleAttr);
321         setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
322         setEnableFreeScroll(true);
323 
324         mFastFlingVelocity = getResources()
325                 .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
326         mActivity = (T) BaseActivity.fromContext(context);
327         mModel = RecentsModel.INSTANCE.get(context);
328         mIdp = InvariantDeviceProfile.INSTANCE.get(context);
329         mTempClipAnimationHelper = new ClipAnimationHelper(context);
330 
331         mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
332                 .inflate(R.layout.overview_clear_all_button, this, false);
333         mClearAllButton.setOnClickListener(this::dismissAllTasks);
334         mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
335                 10 /* initial size */);
336 
337         mIsRtl = !Utilities.isRtl(getResources());
338         setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
339         mTaskTopMargin = getResources()
340                 .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
341         mSquaredTouchSlop = squaredTouchSlop(context);
342 
343         mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents);
344         mEmptyIcon.setCallback(this);
345         mEmptyMessage = context.getText(R.string.recents_empty_message);
346         mEmptyMessagePaint = new TextPaint();
347         mEmptyMessagePaint.setColor(Themes.getAttrColor(context, android.R.attr.textColorPrimary));
348         mEmptyMessagePaint.setTextSize(getResources()
349                 .getDimension(R.dimen.recents_empty_message_text_size));
350         mEmptyMessagePaint.setTypeface(Typeface.create(Themes.getDefaultBodyFont(context),
351                 Typeface.NORMAL));
352         mEmptyMessagePadding = getResources()
353                 .getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
354         setWillNotDraw(false);
355         updateEmptyMessage();
356 
357         // Initialize quickstep specific cache params here, as this is constructed only once
358         mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
359     }
360 
getScroller()361     public OverScroller getScroller() {
362         return mScroller;
363     }
364 
isRtl()365     public boolean isRtl() {
366         return mIsRtl;
367     }
368 
369     @Override
onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData)370     public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) {
371         if (mHandleTaskStackChanges) {
372             TaskView taskView = getTaskView(taskId);
373             if (taskView != null) {
374                 Task task = taskView.getTask();
375                 taskView.getThumbnail().setThumbnail(task, thumbnailData);
376                 return task;
377             }
378         }
379         return null;
380     }
381 
updateThumbnail(int taskId, ThumbnailData thumbnailData)382     public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
383         TaskView taskView = getTaskView(taskId);
384         if (taskView != null) {
385             taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData);
386         }
387         return taskView;
388     }
389 
390     @Override
onWindowVisibilityChanged(int visibility)391     protected void onWindowVisibilityChanged(int visibility) {
392         super.onWindowVisibilityChanged(visibility);
393         updateTaskStackListenerState();
394     }
395 
396     @Override
onIdpChanged(int changeFlags, InvariantDeviceProfile idp)397     public void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) {
398         if ((changeFlags & CHANGE_FLAG_ICON_PARAMS) == 0) {
399             return;
400         }
401         mModel.getIconCache().clear();
402         reset();
403     }
404 
405     @Override
onAttachedToWindow()406     protected void onAttachedToWindow() {
407         super.onAttachedToWindow();
408         updateTaskStackListenerState();
409         mModel.getThumbnailCache().getHighResLoadingState().addCallback(this);
410         mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
411         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
412         mSyncTransactionApplier = new SyncRtSurfaceTransactionApplierCompat(this);
413         RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
414         mIdp.addOnChangeListener(this);
415     }
416 
417     @Override
onDetachedFromWindow()418     protected void onDetachedFromWindow() {
419         super.onDetachedFromWindow();
420         updateTaskStackListenerState();
421         mModel.getThumbnailCache().getHighResLoadingState().removeCallback(this);
422         mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
423         ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
424         mSyncTransactionApplier = null;
425         RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
426         mIdp.removeOnChangeListener(this);
427     }
428 
429     @Override
onViewRemoved(View child)430     public void onViewRemoved(View child) {
431         super.onViewRemoved(child);
432 
433         // Clear the task data for the removed child if it was visible
434         if (child instanceof TaskView) {
435             TaskView taskView = (TaskView) child;
436             mHasVisibleTaskData.delete(taskView.getTask().key.id);
437             mTaskViewPool.recycle(taskView);
438         }
439     }
440 
isTaskViewVisible(TaskView tv)441     public boolean isTaskViewVisible(TaskView tv) {
442         // For now, just check if it's the active task or an adjacent task
443         return Math.abs(indexOfChild(tv) - getNextPage()) <= 1;
444     }
445 
getTaskView(int taskId)446     public TaskView getTaskView(int taskId) {
447         for (int i = 0; i < getTaskViewCount(); i++) {
448             TaskView tv = getTaskViewAt(i);
449             if (tv.getTask() != null && tv.getTask().key != null && tv.getTask().key.id == taskId) {
450                 return tv;
451             }
452         }
453         return null;
454     }
455 
setOverviewStateEnabled(boolean enabled)456     public void setOverviewStateEnabled(boolean enabled) {
457         mOverviewStateEnabled = enabled;
458         updateTaskStackListenerState();
459     }
460 
onDigitalWellbeingToastShown()461     public void onDigitalWellbeingToastShown() {
462         if (!mDwbToastShown) {
463             mDwbToastShown = true;
464             mActivity.getUserEventDispatcher().logActionTip(
465                     LauncherEventUtil.VISIBLE,
466                     LauncherLogProto.TipType.DWB_TOAST);
467         }
468     }
469 
470     @Override
onPageEndTransition()471     protected void onPageEndTransition() {
472         super.onPageEndTransition();
473         if (getNextPage() > 0) {
474             setSwipeDownShouldLaunchApp(true);
475         }
476     }
477 
478     @Override
onTouchEvent(MotionEvent ev)479     public boolean onTouchEvent(MotionEvent ev) {
480         super.onTouchEvent(ev);
481         final int x = (int) ev.getX();
482         final int y = (int) ev.getY();
483         switch (ev.getAction()) {
484             case MotionEvent.ACTION_UP:
485                 if (mTouchDownToStartHome) {
486                     startHome();
487                 }
488                 mTouchDownToStartHome = false;
489                 break;
490             case MotionEvent.ACTION_CANCEL:
491                 mTouchDownToStartHome = false;
492                 break;
493             case MotionEvent.ACTION_MOVE:
494                 // Passing the touch slop will not allow dismiss to home
495                 if (mTouchDownToStartHome &&
496                         (isHandlingTouch() ||
497                                 squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop)) {
498                     mTouchDownToStartHome = false;
499                 }
500                 break;
501             case MotionEvent.ACTION_DOWN:
502                 // Touch down anywhere but the deadzone around the visible clear all button and
503                 // between the task views will start home on touch up
504                 if (!isHandlingTouch()) {
505                     if (mShowEmptyMessage) {
506                         mTouchDownToStartHome = true;
507                     } else {
508                         updateDeadZoneRects();
509                         final boolean clearAllButtonDeadZoneConsumed =
510                                 mClearAllButton.getAlpha() == 1
511                                         && mClearAllButtonDeadZoneRect.contains(x, y);
512                         final boolean cameFromNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
513                         if (!clearAllButtonDeadZoneConsumed && !cameFromNavBar
514                                 && !mTaskViewDeadZoneRect.contains(x + getScrollX(), y)) {
515                             mTouchDownToStartHome = true;
516                         }
517                     }
518                 }
519                 mDownX = x;
520                 mDownY = y;
521                 break;
522         }
523 
524 
525         // Do not let touch escape to siblings below this view.
526         return isHandlingTouch() || shouldStealTouchFromSiblingsBelow(ev);
527     }
528 
shouldStealTouchFromSiblingsBelow(MotionEvent ev)529     protected boolean shouldStealTouchFromSiblingsBelow(MotionEvent ev) {
530         return true;
531     }
532 
applyLoadPlan(ArrayList<Task> tasks)533     protected void applyLoadPlan(ArrayList<Task> tasks) {
534         if (mPendingAnimation != null) {
535             mPendingAnimation.addEndListener((onEndListener) -> applyLoadPlan(tasks));
536             return;
537         }
538 
539         if (tasks == null || tasks.isEmpty()) {
540             removeTasksViewsAndClearAllButton();
541             onTaskStackUpdated();
542             return;
543         }
544 
545         // Unload existing visible task data
546         unloadVisibleTaskData();
547 
548         TaskView ignoreResetTaskView =
549                 mIgnoreResetTaskId == -1 ? null : getTaskView(mIgnoreResetTaskId);
550 
551         final int requiredTaskCount = tasks.size();
552         if (getTaskViewCount() != requiredTaskCount) {
553             if (indexOfChild(mClearAllButton) != -1) {
554                 removeView(mClearAllButton);
555             }
556             for (int i = getTaskViewCount(); i < requiredTaskCount; i++) {
557                 addView(mTaskViewPool.getView());
558             }
559             while (getTaskViewCount() > requiredTaskCount) {
560                 removeView(getChildAt(getChildCount() - 1));
561             }
562             if (requiredTaskCount > 0) {
563                 addView(mClearAllButton);
564             }
565         }
566 
567         // Rebind and reset all task views
568         for (int i = requiredTaskCount - 1; i >= 0; i--) {
569             final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex;
570             final Task task = tasks.get(i);
571             final TaskView taskView = (TaskView) getChildAt(pageIndex);
572             taskView.bind(task);
573         }
574 
575         if (mNextPage == INVALID_PAGE) {
576             // Set the current page to the running task, but not if settling on new task.
577             TaskView runningTaskView = getRunningTaskView();
578             if (runningTaskView != null) {
579                 setCurrentPage(indexOfChild(runningTaskView));
580             } else if (getTaskViewCount() > 0) {
581                 setCurrentPage(indexOfChild(getTaskViewAt(0)));
582             }
583         }
584 
585         if (mIgnoreResetTaskId != -1 && getTaskView(mIgnoreResetTaskId) != ignoreResetTaskView) {
586             // If the taskView mapping is changing, do not preserve the visuals. Since we are
587             // mostly preserving the first task, and new taskViews are added to the end, it should
588             // generally map to the same task.
589             mIgnoreResetTaskId = -1;
590         }
591         resetTaskVisuals();
592         onTaskStackUpdated();
593         updateEnabledOverlays();
594     }
595 
removeTasksViewsAndClearAllButton()596     private void removeTasksViewsAndClearAllButton() {
597         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
598             removeView(getTaskViewAt(i));
599         }
600         if (indexOfChild(mClearAllButton) != -1) {
601             removeView(mClearAllButton);
602         }
603     }
604 
getTaskViewCount()605     public int getTaskViewCount() {
606         int taskViewCount = getChildCount() - mTaskViewStartIndex;
607         if (indexOfChild(mClearAllButton) != -1) {
608             taskViewCount--;
609         }
610         return taskViewCount;
611     }
612 
onTaskStackUpdated()613     protected void onTaskStackUpdated() { }
614 
resetTaskVisuals()615     public void resetTaskVisuals() {
616         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
617             TaskView taskView = getTaskViewAt(i);
618             if (mIgnoreResetTaskId != taskView.getTask().key.id) {
619                 taskView.resetVisualProperties();
620                 taskView.setStableAlpha(mContentAlpha);
621             }
622         }
623         if (mRunningTaskTileHidden) {
624             setRunningTaskHidden(mRunningTaskTileHidden);
625         }
626 
627         // Force apply the scale.
628         if (mIgnoreResetTaskId != mRunningTaskId) {
629             applyRunningTaskIconScale();
630         }
631 
632         updateCurveProperties();
633         // Update the set of visible task's data
634         loadVisibleTaskData();
635     }
636 
setFullscreenProgress(float fullscreenProgress)637     public void setFullscreenProgress(float fullscreenProgress) {
638         mFullscreenProgress = fullscreenProgress;
639         int taskCount = getTaskViewCount();
640         for (int i = 0; i < taskCount; i++) {
641             getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress);
642         }
643     }
644 
updateTaskStackListenerState()645     private void updateTaskStackListenerState() {
646         boolean handleTaskStackChanges = mOverviewStateEnabled && isAttachedToWindow()
647                 && getWindowVisibility() == VISIBLE;
648         if (handleTaskStackChanges != mHandleTaskStackChanges) {
649             mHandleTaskStackChanges = handleTaskStackChanges;
650             if (handleTaskStackChanges) {
651                 reloadIfNeeded();
652             }
653         }
654     }
655 
656     @Override
setInsets(Rect insets)657     public void setInsets(Rect insets) {
658         mInsets.set(insets);
659         DeviceProfile dp = mActivity.getDeviceProfile();
660         getTaskSize(dp, mTempRect);
661         mTaskWidth = mTempRect.width();
662         mTaskHeight = mTempRect.height();
663 
664         mTempRect.top -= mTaskTopMargin;
665         setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top,
666                 dp.widthPx - mInsets.right - mTempRect.right,
667                 dp.heightPx - mInsets.bottom - mTempRect.bottom);
668     }
669 
getTaskSize(DeviceProfile dp, Rect outRect)670     protected abstract void getTaskSize(DeviceProfile dp, Rect outRect);
671 
getTaskSize(Rect outRect)672     public void getTaskSize(Rect outRect) {
673         getTaskSize(mActivity.getDeviceProfile(), outRect);
674     }
675 
676     @Override
computeScrollHelper()677     protected boolean computeScrollHelper() {
678         boolean scrolling = super.computeScrollHelper();
679         boolean isFlingingFast = false;
680         updateCurveProperties();
681         if (scrolling || isHandlingTouch()) {
682             if (scrolling) {
683                 // Check if we are flinging quickly to disable high res thumbnail loading
684                 isFlingingFast = mScroller.getCurrVelocity() > mFastFlingVelocity;
685             }
686 
687             // After scrolling, update the visible task's data
688             loadVisibleTaskData();
689         }
690 
691         // Update the high res thumbnail loader state
692         mModel.getThumbnailCache().getHighResLoadingState().setFlingingFast(isFlingingFast);
693         return scrolling;
694     }
695 
696     /**
697      * Scales and adjusts translation of adjacent pages as if on a curved carousel.
698      */
updateCurveProperties()699     public void updateCurveProperties() {
700         if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
701             return;
702         }
703         int scrollX = getScrollX();
704         final int halfPageWidth = getNormalChildWidth() / 2;
705         final int screenCenter = mInsets.left + getPaddingLeft() + scrollX + halfPageWidth;
706         final int halfScreenWidth = getMeasuredWidth() / 2;
707         final int pageSpacing = mPageSpacing;
708         mScrollState.scrollFromEdge = mIsRtl ? scrollX : (mMaxScrollX - scrollX);
709 
710         final int pageCount = getPageCount();
711         for (int i = 0; i < pageCount; i++) {
712             View page = getPageAt(i);
713             float pageCenter = page.getLeft() + page.getTranslationX() + halfPageWidth;
714             float distanceFromScreenCenter = screenCenter - pageCenter;
715             float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
716             mScrollState.linearInterpolation = Math.min(1,
717                     Math.abs(distanceFromScreenCenter) / distanceToReachEdge);
718             ((PageCallbacks) page).onPageScroll(mScrollState);
719         }
720     }
721 
722     /**
723      * Iterates through all the tasks, and loads the associated task data for newly visible tasks,
724      * and unloads the associated task data for tasks that are no longer visible.
725      */
loadVisibleTaskData()726     public void loadVisibleTaskData() {
727         if (!mOverviewStateEnabled || mTaskListChangeId == -1) {
728             // Skip loading visible task data if we've already left the overview state, or if the
729             // task list hasn't been loaded yet (the task views will not reflect the task list)
730             return;
731         }
732 
733         int centerPageIndex = getPageNearestToCenterOfScreen();
734         int numChildren = getChildCount();
735         int lower = Math.max(0, centerPageIndex - 2);
736         int upper = Math.min(centerPageIndex + 2, numChildren - 1);
737 
738         // Update the task data for the in/visible children
739         for (int i = 0; i < getTaskViewCount(); i++) {
740             TaskView taskView = getTaskViewAt(i);
741             Task task = taskView.getTask();
742             int index = indexOfChild(taskView);
743             boolean visible = lower <= index && index <= upper;
744             if (visible) {
745                 if (task == mTmpRunningTask) {
746                     // Skip loading if this is the task that we are animating into
747                     continue;
748                 }
749                 if (!mHasVisibleTaskData.get(task.key.id)) {
750                     taskView.onTaskListVisibilityChanged(true /* visible */);
751                 }
752                 mHasVisibleTaskData.put(task.key.id, visible);
753             } else {
754                 if (mHasVisibleTaskData.get(task.key.id)) {
755                     taskView.onTaskListVisibilityChanged(false /* visible */);
756                 }
757                 mHasVisibleTaskData.delete(task.key.id);
758             }
759         }
760     }
761 
762     /**
763      * Unloads any associated data from the currently visible tasks
764      */
unloadVisibleTaskData()765     private void unloadVisibleTaskData() {
766         for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
767             if (mHasVisibleTaskData.valueAt(i)) {
768                 TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
769                 if (taskView != null) {
770                     taskView.onTaskListVisibilityChanged(false /* visible */);
771                 }
772             }
773         }
774         mHasVisibleTaskData.clear();
775     }
776 
777     @Override
onHighResLoadingStateChanged(boolean enabled)778     public void onHighResLoadingStateChanged(boolean enabled) {
779         // Whenever the high res loading state changes, poke each of the visible tasks to see if
780         // they want to updated their thumbnail state
781         for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
782             if (mHasVisibleTaskData.valueAt(i)) {
783                 TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
784                 if (taskView != null) {
785                     // Poke the view again, which will trigger it to load high res if the state
786                     // is enabled
787                     taskView.onTaskListVisibilityChanged(true /* visible */);
788                 }
789             }
790         }
791     }
792 
startHome()793     public abstract void startHome();
794 
reset()795     public void reset() {
796         setCurrentTask(-1);
797         mIgnoreResetTaskId = -1;
798         mTaskListChangeId = -1;
799 
800         mRecentsAnimationWrapper = null;
801         mClipAnimationHelper = null;
802 
803         unloadVisibleTaskData();
804         setCurrentPage(0);
805         mDwbToastShown = false;
806         mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0);
807     }
808 
getRunningTaskView()809     public @Nullable TaskView getRunningTaskView() {
810         return getTaskView(mRunningTaskId);
811     }
812 
getRunningTaskIndex()813     public int getRunningTaskIndex() {
814         TaskView tv = getRunningTaskView();
815         return tv == null ? -1 : indexOfChild(tv);
816     }
817 
getTaskViewStartIndex()818     public int getTaskViewStartIndex() {
819         return mTaskViewStartIndex;
820     }
821 
822     /**
823      * Reloads the view if anything in recents changed.
824      */
reloadIfNeeded()825     public void reloadIfNeeded() {
826         if (!mModel.isTaskListValid(mTaskListChangeId)) {
827             mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
828         }
829     }
830 
831     /**
832      * Called when a gesture from an app is starting.
833      */
onGestureAnimationStart(int runningTaskId)834     public void onGestureAnimationStart(int runningTaskId) {
835         // This needs to be called before the other states are set since it can create the task view
836         showCurrentTask(runningTaskId);
837         setEnableFreeScroll(false);
838         setEnableDrawingLiveTile(false);
839         setRunningTaskHidden(true);
840         setRunningTaskIconScaledDown(true);
841     }
842 
843     /**
844      * Called only when a swipe-up gesture from an app has completed. Only called after
845      * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}.
846      */
onSwipeUpAnimationSuccess()847     public void onSwipeUpAnimationSuccess() {
848         if (getRunningTaskView() != null) {
849             float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() && mLiveTileOverlay != null
850                     ? mLiveTileOverlay.cancelIconAnimation()
851                     : 0f;
852             animateUpRunningTaskIconScale(startProgress);
853         }
854         setSwipeDownShouldLaunchApp(true);
855     }
856 
857     /**
858      * Called when a gesture from an app has finished.
859      */
onGestureAnimationEnd()860     public void onGestureAnimationEnd() {
861         setEnableFreeScroll(true);
862         setEnableDrawingLiveTile(true);
863         setOnScrollChangeListener(null);
864         setRunningTaskViewShowScreenshot(true);
865         setRunningTaskHidden(false);
866         animateUpRunningTaskIconScale();
867     }
868 
869     /**
870      * Creates a task view (if necessary) to represent the task with the {@param runningTaskId}.
871      *
872      * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
873      * is called.  Also scrolls the view to this task.
874      */
showCurrentTask(int runningTaskId)875     public void showCurrentTask(int runningTaskId) {
876         if (getTaskView(runningTaskId) == null) {
877             boolean wasEmpty = getTaskViewCount() == 0;
878             // Add an empty view for now until the task plan is loaded and applied
879             final TaskView taskView = mTaskViewPool.getView();
880             addView(taskView, mTaskViewStartIndex);
881             if (wasEmpty) {
882                 addView(mClearAllButton);
883             }
884             // The temporary running task is only used for the duration between the start of the
885             // gesture and the task list is loaded and applied
886             mTmpRunningTask = new Task(new Task.TaskKey(runningTaskId, 0, new Intent(),
887                     new ComponentName(getContext(), getClass()), 0, 0), null, null, "", "", 0, 0,
888                     false, true, false, false, new ActivityManager.TaskDescription(), 0,
889                     new ComponentName("", ""), false);
890             taskView.bind(mTmpRunningTask);
891         }
892 
893         boolean runningTaskTileHidden = mRunningTaskTileHidden;
894         setCurrentTask(runningTaskId);
895         setCurrentPage(getRunningTaskIndex());
896         setRunningTaskViewShowScreenshot(false);
897         setRunningTaskHidden(runningTaskTileHidden);
898 
899         // Reload the task list
900         mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
901     }
902 
903     /**
904      * Sets the running task id, cleaning up the old running task if necessary.
905      * @param runningTaskId
906      */
setCurrentTask(int runningTaskId)907     public void setCurrentTask(int runningTaskId) {
908         if (mRunningTaskId == runningTaskId) {
909             return;
910         }
911 
912         if (mRunningTaskId != -1) {
913             // Reset the state on the old running task view
914             setRunningTaskIconScaledDown(false);
915             setRunningTaskViewShowScreenshot(true);
916             setRunningTaskHidden(false);
917         }
918         mRunningTaskId = runningTaskId;
919     }
920 
921     /**
922      * Hides the tile associated with {@link #mRunningTaskId}
923      */
setRunningTaskHidden(boolean isHidden)924     public void setRunningTaskHidden(boolean isHidden) {
925         mRunningTaskTileHidden = isHidden;
926         TaskView runningTask = getRunningTaskView();
927         if (runningTask != null) {
928             runningTask.setStableAlpha(isHidden ? 0 : mContentAlpha);
929         }
930     }
931 
setRunningTaskViewShowScreenshot(boolean showScreenshot)932     private void setRunningTaskViewShowScreenshot(boolean showScreenshot) {
933         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
934             TaskView runningTaskView = getRunningTaskView();
935             if (runningTaskView != null) {
936                 runningTaskView.setShowScreenshot(showScreenshot);
937             }
938         }
939     }
940 
showNextTask()941     public void showNextTask() {
942         TaskView runningTaskView = getRunningTaskView();
943         if (runningTaskView == null) {
944             // Launch the first task
945             if (getTaskViewCount() > 0) {
946                 getTaskViewAt(0).launchTask(true);
947             }
948         } else {
949             if (getNextTaskView() != null) {
950                 getNextTaskView().launchTask(true);
951             } else {
952                 runningTaskView.launchTask(true);
953             }
954         }
955     }
956 
setRunningTaskIconScaledDown(boolean isScaledDown)957     public void setRunningTaskIconScaledDown(boolean isScaledDown) {
958         if (mRunningTaskIconScaledDown != isScaledDown) {
959             mRunningTaskIconScaledDown = isScaledDown;
960             applyRunningTaskIconScale();
961         }
962     }
963 
isTaskIconScaledDown(TaskView taskView)964     public boolean isTaskIconScaledDown(TaskView taskView) {
965         return mRunningTaskIconScaledDown && getRunningTaskView() == taskView;
966     }
967 
applyRunningTaskIconScale()968     private void applyRunningTaskIconScale() {
969         TaskView firstTask = getRunningTaskView();
970         if (firstTask != null) {
971             firstTask.setIconScaleAndDim(mRunningTaskIconScaledDown ? 0 : 1);
972         }
973     }
974 
animateUpRunningTaskIconScale()975     public void animateUpRunningTaskIconScale() {
976         animateUpRunningTaskIconScale(0);
977     }
978 
animateUpRunningTaskIconScale(float startProgress)979     public void animateUpRunningTaskIconScale(float startProgress) {
980         mRunningTaskIconScaledDown = false;
981         TaskView firstTask = getRunningTaskView();
982         if (firstTask != null) {
983             firstTask.animateIconScaleAndDimIntoView();
984             firstTask.setIconScaleAnimStartProgress(startProgress);
985         }
986     }
987 
enableLayoutTransitions()988     private void enableLayoutTransitions() {
989         if (mLayoutTransition == null) {
990             mLayoutTransition = new LayoutTransition();
991             mLayoutTransition.enableTransitionType(LayoutTransition.APPEARING);
992             mLayoutTransition.setDuration(ADDITION_TASK_DURATION);
993             mLayoutTransition.setStartDelay(LayoutTransition.APPEARING, 0);
994 
995             mLayoutTransition.addTransitionListener(new TransitionListener() {
996                 @Override
997                 public void startTransition(LayoutTransition transition, ViewGroup viewGroup,
998                     View view, int i) {
999                 }
1000 
1001                 @Override
1002                 public void endTransition(LayoutTransition transition, ViewGroup viewGroup,
1003                     View view, int i) {
1004                     // When the unpinned task is added, snap to first page and disable transitions
1005                     if (view instanceof TaskView) {
1006                         snapToPage(0);
1007                         disableLayoutTransitions();
1008                     }
1009 
1010                 }
1011             });
1012         }
1013         setLayoutTransition(mLayoutTransition);
1014     }
1015 
disableLayoutTransitions()1016     private void disableLayoutTransitions() {
1017         setLayoutTransition(null);
1018     }
1019 
setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp)1020     public void setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp) {
1021         mSwipeDownShouldLaunchApp = swipeDownShouldLaunchApp;
1022     }
1023 
shouldSwipeDownLaunchApp()1024     public boolean shouldSwipeDownLaunchApp() {
1025         return mSwipeDownShouldLaunchApp;
1026     }
1027 
1028     public interface PageCallbacks {
1029 
1030         /**
1031          * Updates the page UI based on scroll params.
1032          */
onPageScroll(ScrollState scrollState)1033         default void onPageScroll(ScrollState scrollState) {}
1034     }
1035 
1036     public static class ScrollState {
1037 
1038         /**
1039          * The progress from 0 to 1, where 0 is the center
1040          * of the screen and 1 is the edge of the screen.
1041          */
1042         public float linearInterpolation;
1043 
1044         /**
1045          * The amount by which all the content is scrolled relative to the end of the list.
1046          */
1047         public float scrollFromEdge;
1048     }
1049 
setIgnoreResetTask(int taskId)1050     public void setIgnoreResetTask(int taskId) {
1051         mIgnoreResetTaskId = taskId;
1052     }
1053 
clearIgnoreResetTask(int taskId)1054     public void clearIgnoreResetTask(int taskId) {
1055         if (mIgnoreResetTaskId == taskId) {
1056             mIgnoreResetTaskId = -1;
1057         }
1058     }
1059 
addDismissedTaskAnimations(View taskView, AnimatorSet anim, long duration)1060     private void addDismissedTaskAnimations(View taskView, AnimatorSet anim, long duration) {
1061         addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim);
1062         if (QUICKSTEP_SPRINGS.get() && taskView instanceof TaskView)
1063             addAnim(new SpringObjectAnimator<>(taskView, VIEW_TRANSLATE_Y,
1064                             MIN_VISIBLE_CHANGE_PIXELS, SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY,
1065                             SpringForce.STIFFNESS_MEDIUM,
1066                             0, -taskView.getHeight()),
1067                     duration, LINEAR, anim);
1068         else {
1069             addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
1070                     duration, LINEAR, anim);
1071         }
1072     }
1073 
removeTask(Task task, int index, PendingAnimation.OnEndListener onEndListener, boolean shouldLog)1074     private void removeTask(Task task, int index, PendingAnimation.OnEndListener onEndListener,
1075                             boolean shouldLog) {
1076         if (task != null) {
1077             ActivityManagerWrapper.getInstance().removeTask(task.key.id);
1078             if (shouldLog) {
1079                 ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key);
1080                 mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
1081                         onEndListener.logAction, Direction.UP, index, componentKey);
1082                 mActivity.getStatsLogManager().logTaskDismiss(this, componentKey);
1083             }
1084         }
1085     }
1086 
createTaskDismissAnimation(TaskView taskView, boolean animateTaskView, boolean shouldRemoveTask, long duration)1087     public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView,
1088             boolean shouldRemoveTask, long duration) {
1089         if (mPendingAnimation != null) {
1090             mPendingAnimation.finish(false, Touch.SWIPE);
1091         }
1092         AnimatorSet anim = new AnimatorSet();
1093         PendingAnimation pendingAnimation = new PendingAnimation(anim);
1094 
1095         int count = getPageCount();
1096         if (count == 0) {
1097             return pendingAnimation;
1098         }
1099 
1100         int[] oldScroll = new int[count];
1101         getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
1102 
1103         int[] newScroll = new int[count];
1104         getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView);
1105 
1106         int taskCount = getTaskViewCount();
1107         int scrollDiffPerPage = 0;
1108         if (count > 1) {
1109             scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]);
1110         }
1111         int draggedIndex = indexOfChild(taskView);
1112 
1113         boolean needsCurveUpdates = false;
1114         for (int i = 0; i < count; i++) {
1115             View child = getChildAt(i);
1116             if (child == taskView) {
1117                 if (animateTaskView) {
1118                     addDismissedTaskAnimations(taskView, anim, duration);
1119                 }
1120             } else {
1121                 // If we just take newScroll - oldScroll, everything to the right of dragged task
1122                 // translates to the left. We need to offset this in some cases:
1123                 // - In RTL, add page offset to all pages, since we want pages to move to the right
1124                 // Additionally, add a page offset if:
1125                 // - Current page is rightmost page (leftmost for RTL)
1126                 // - Dragging an adjacent page on the left side (right side for RTL)
1127                 int offset = mIsRtl ? scrollDiffPerPage : 0;
1128                 if (mCurrentPage == draggedIndex) {
1129                     int lastPage = taskCount - 1;
1130                     if (mCurrentPage == lastPage) {
1131                         offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
1132                     }
1133                 } else {
1134                     // Dragging an adjacent page.
1135                     int negativeAdjacent = mCurrentPage - 1; // (Right in RTL, left in LTR)
1136                     if (draggedIndex == negativeAdjacent) {
1137                         offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
1138                     }
1139                 }
1140                 int scrollDiff = newScroll[i] - oldScroll[i] + offset;
1141                 if (scrollDiff != 0) {
1142                     if (QUICKSTEP_SPRINGS.get() && child instanceof TaskView) {
1143                         addAnim(new SpringObjectAnimator<>(child, VIEW_TRANSLATE_X,
1144                                 MIN_VISIBLE_CHANGE_PIXELS, SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY,
1145                                 SpringForce.STIFFNESS_MEDIUM,
1146                                 0, scrollDiff), duration, ACCEL, anim);
1147                     } else {
1148                         addAnim(ObjectAnimator.ofFloat(child, TRANSLATION_X, scrollDiff), duration,
1149                                 ACCEL, anim);
1150                     }
1151 
1152                     needsCurveUpdates = true;
1153                 }
1154             }
1155         }
1156 
1157         if (needsCurveUpdates) {
1158             ValueAnimator va = ValueAnimator.ofFloat(0, 1);
1159             va.addUpdateListener((a) -> updateCurveProperties());
1160             anim.play(va);
1161         }
1162 
1163         // Add a tiny bit of translation Z, so that it draws on top of other views
1164         if (animateTaskView) {
1165             taskView.setTranslationZ(0.1f);
1166         }
1167 
1168         mPendingAnimation = pendingAnimation;
1169         mPendingAnimation.addEndListener(new Consumer<PendingAnimation.OnEndListener>() {
1170             @Override
1171             public void accept(PendingAnimation.OnEndListener onEndListener) {
1172                 if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
1173                         taskView.isRunningTask() && onEndListener.isSuccess) {
1174                     finishRecentsAnimation(true /* toHome */, () -> onEnd(onEndListener));
1175                 } else {
1176                     onEnd(onEndListener);
1177                 }
1178             }
1179 
1180             private void onEnd(PendingAnimation.OnEndListener onEndListener) {
1181                 if (onEndListener.isSuccess) {
1182                     if (shouldRemoveTask) {
1183                         removeTask(taskView.getTask(), draggedIndex, onEndListener, true);
1184                     }
1185 
1186                     int pageToSnapTo = mCurrentPage;
1187                     if (draggedIndex < pageToSnapTo ||
1188                             pageToSnapTo == (getTaskViewCount() - 1)) {
1189                         pageToSnapTo -= 1;
1190                     }
1191                     removeView(taskView);
1192 
1193                     if (getTaskViewCount() == 0) {
1194                         removeView(mClearAllButton);
1195                         startHome();
1196                     } else {
1197                         snapToPageImmediately(pageToSnapTo);
1198                     }
1199                 }
1200                 resetTaskVisuals();
1201                 mPendingAnimation = null;
1202             }
1203         });
1204         return pendingAnimation;
1205     }
1206 
createAllTasksDismissAnimation(long duration)1207     public PendingAnimation createAllTasksDismissAnimation(long duration) {
1208         if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
1209             throw new IllegalStateException("Another pending animation is still running");
1210         }
1211         AnimatorSet anim = new AnimatorSet();
1212         PendingAnimation pendingAnimation = new PendingAnimation(anim);
1213 
1214         int count = getTaskViewCount();
1215         for (int i = 0; i < count; i++) {
1216             addDismissedTaskAnimations(getTaskViewAt(i), anim, duration);
1217         }
1218 
1219         mPendingAnimation = pendingAnimation;
1220         mPendingAnimation.addEndListener((onEndListener) -> {
1221             if (onEndListener.isSuccess) {
1222                 // Remove all the task views now
1223                 ActivityManagerWrapper.getInstance().removeAllRecentTasks();
1224                 removeTasksViewsAndClearAllButton();
1225                 startHome();
1226             }
1227             mPendingAnimation = null;
1228         });
1229         return pendingAnimation;
1230     }
1231 
addAnim(Animator anim, long duration, TimeInterpolator interpolator, AnimatorSet set)1232     private static void addAnim(Animator anim, long duration,
1233             TimeInterpolator interpolator, AnimatorSet set) {
1234         anim.setDuration(duration).setInterpolator(interpolator);
1235         set.play(anim);
1236     }
1237 
snapToPageRelative(int pageCount, int delta, boolean cycle)1238     private boolean snapToPageRelative(int pageCount, int delta, boolean cycle) {
1239         if (pageCount == 0) {
1240             return false;
1241         }
1242         final int newPageUnbound = getNextPage() + delta;
1243         if (!cycle && (newPageUnbound < 0 || newPageUnbound >= pageCount)) {
1244             return false;
1245         }
1246         snapToPage((newPageUnbound + pageCount) % pageCount);
1247         getChildAt(getNextPage()).requestFocus();
1248         return true;
1249     }
1250 
runDismissAnimation(PendingAnimation pendingAnim)1251     private void runDismissAnimation(PendingAnimation pendingAnim) {
1252         AnimatorPlaybackController controller = AnimatorPlaybackController.wrap(
1253                 pendingAnim.anim, DISMISS_TASK_DURATION);
1254         controller.dispatchOnStart();
1255         controller.setEndAction(() -> pendingAnim.finish(true, Touch.SWIPE));
1256         controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN);
1257         controller.start();
1258     }
1259 
dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask)1260     public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) {
1261         runDismissAnimation(createTaskDismissAnimation(taskView, animateTaskView, removeTask,
1262                 DISMISS_TASK_DURATION));
1263     }
1264 
1265     @SuppressWarnings("unused")
dismissAllTasks(View view)1266     private void dismissAllTasks(View view) {
1267         runDismissAnimation(createAllTasksDismissAnimation(DISMISS_TASK_DURATION));
1268         mActivity.getUserEventDispatcher().logActionOnControl(TAP, CLEAR_ALL_BUTTON);
1269     }
1270 
dismissCurrentTask()1271     private void dismissCurrentTask() {
1272         TaskView taskView = getTaskView(getNextPage());
1273         if (taskView != null) {
1274             dismissTask(taskView, true /*animateTaskView*/, true /*removeTask*/);
1275         }
1276     }
1277 
1278     @Override
dispatchKeyEvent(KeyEvent event)1279     public boolean dispatchKeyEvent(KeyEvent event) {
1280         if (event.getAction() == KeyEvent.ACTION_DOWN) {
1281             switch (event.getKeyCode()) {
1282                 case KeyEvent.KEYCODE_TAB:
1283                     return snapToPageRelative(getTaskViewCount(), event.isShiftPressed() ? -1 : 1,
1284                             event.isAltPressed() /* cycle */);
1285                 case KeyEvent.KEYCODE_DPAD_RIGHT:
1286                     return snapToPageRelative(getPageCount(), mIsRtl ? -1 : 1, false /* cycle */);
1287                 case KeyEvent.KEYCODE_DPAD_LEFT:
1288                     return snapToPageRelative(getPageCount(), mIsRtl ? 1 : -1, false /* cycle */);
1289                 case KeyEvent.KEYCODE_DEL:
1290                 case KeyEvent.KEYCODE_FORWARD_DEL:
1291                     dismissCurrentTask();
1292                     return true;
1293                 case KeyEvent.KEYCODE_NUMPAD_DOT:
1294                     if (event.isAltPressed()) {
1295                         // Numpad DEL pressed while holding Alt.
1296                         dismissCurrentTask();
1297                         return true;
1298                     }
1299             }
1300         }
1301         return super.dispatchKeyEvent(event);
1302     }
1303 
1304     @Override
onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect)1305     protected void onFocusChanged(boolean gainFocus, int direction,
1306             @Nullable Rect previouslyFocusedRect) {
1307         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1308         if (gainFocus && getChildCount() > 0) {
1309             switch (direction) {
1310                 case FOCUS_FORWARD:
1311                     setCurrentPage(0);
1312                     break;
1313                 case FOCUS_BACKWARD:
1314                 case FOCUS_RIGHT:
1315                 case FOCUS_LEFT:
1316                     setCurrentPage(getChildCount() - 1);
1317                     break;
1318             }
1319         }
1320     }
1321 
getContentAlpha()1322     public float getContentAlpha() {
1323         return mContentAlpha;
1324     }
1325 
setContentAlpha(float alpha)1326     public void setContentAlpha(float alpha) {
1327         if (alpha == mContentAlpha) {
1328             return;
1329         }
1330         alpha = Utilities.boundToRange(alpha, 0, 1);
1331         mContentAlpha = alpha;
1332         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
1333             TaskView child = getTaskViewAt(i);
1334             if (!mRunningTaskTileHidden || child.getTask().key.id != mRunningTaskId) {
1335                 child.setStableAlpha(alpha);
1336             }
1337         }
1338         mClearAllButton.setContentAlpha(mContentAlpha);
1339 
1340         int alphaInt = Math.round(alpha * 255);
1341         mEmptyMessagePaint.setAlpha(alphaInt);
1342         mEmptyIcon.setAlpha(alphaInt);
1343 
1344         if (alpha > 0) {
1345             setVisibility(VISIBLE);
1346         } else if (!mFreezeViewVisibility) {
1347             setVisibility(GONE);
1348         }
1349     }
1350 
1351     /**
1352      * Freezes the view visibility change. When frozen, the view will not change its visibility
1353      * to gone due to alpha changes.
1354      */
setFreezeViewVisibility(boolean freezeViewVisibility)1355     public void setFreezeViewVisibility(boolean freezeViewVisibility) {
1356         if (mFreezeViewVisibility != freezeViewVisibility) {
1357             mFreezeViewVisibility = freezeViewVisibility;
1358 
1359             if (!mFreezeViewVisibility) {
1360                 setVisibility(mContentAlpha > 0 ? VISIBLE : GONE);
1361             }
1362         }
1363     }
1364 
1365     @Override
onViewAdded(View child)1366     public void onViewAdded(View child) {
1367         super.onViewAdded(child);
1368         child.setAlpha(mContentAlpha);
1369     }
1370 
1371     @Nullable
getNextTaskView()1372     public TaskView getNextTaskView() {
1373         return getTaskViewAtByAbsoluteIndex(getRunningTaskIndex() + 1);
1374     }
1375 
1376     @Nullable
getPreviousTaskView()1377     public TaskView getPreviousTaskView() {
1378         return getTaskViewAtByAbsoluteIndex(getRunningTaskIndex() - 1);
1379     }
1380 
1381     @Nullable
getCurrentPageTaskView()1382     public TaskView getCurrentPageTaskView() {
1383         return getTaskViewAtByAbsoluteIndex(getCurrentPage());
1384     }
1385 
1386     @Nullable
getNextPageTaskView()1387     public TaskView getNextPageTaskView() {
1388         return getTaskViewAtByAbsoluteIndex(getNextPage());
1389     }
1390 
1391     @Nullable
getTaskViewNearestToCenterOfScreen()1392     public TaskView getTaskViewNearestToCenterOfScreen() {
1393         return getTaskViewAtByAbsoluteIndex(getPageNearestToCenterOfScreen());
1394     }
1395 
1396     /**
1397      * Returns null instead of indexOutOfBoundsError when index is not in range
1398      */
1399     @Nullable
getTaskViewAt(int index)1400     public TaskView getTaskViewAt(int index) {
1401         return getTaskViewAtByAbsoluteIndex(index + mTaskViewStartIndex);
1402     }
1403 
1404     @Nullable
getTaskViewAtByAbsoluteIndex(int index)1405     private TaskView getTaskViewAtByAbsoluteIndex(int index) {
1406         if (index < getChildCount() && index >= 0) {
1407             View child = getChildAt(index);
1408             return child instanceof TaskView ? (TaskView) child : null;
1409         }
1410         return null;
1411     }
1412 
updateEmptyMessage()1413     public void updateEmptyMessage() {
1414         boolean isEmpty = getTaskViewCount() == 0;
1415         boolean hasSizeChanged = mLastMeasureSize.x != getWidth()
1416                 || mLastMeasureSize.y != getHeight();
1417         if (isEmpty == mShowEmptyMessage && !hasSizeChanged) {
1418             return;
1419         }
1420         setContentDescription(isEmpty ? mEmptyMessage : "");
1421         mShowEmptyMessage = isEmpty;
1422         updateEmptyStateUi(hasSizeChanged);
1423         invalidate();
1424     }
1425 
1426     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)1427     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1428         super.onLayout(changed, left, top, right, bottom);
1429         updateEmptyStateUi(changed);
1430 
1431         // Set the pivot points to match the task preview center
1432         setPivotY(((mInsets.top + getPaddingTop() + mTaskTopMargin)
1433                 + (getHeight() - mInsets.bottom - getPaddingBottom())) / 2);
1434         setPivotX(((mInsets.left + getPaddingLeft())
1435                 + (getWidth() - mInsets.right - getPaddingRight())) / 2);
1436     }
1437 
updateDeadZoneRects()1438     private void updateDeadZoneRects() {
1439         // Get the deadzone rect surrounding the clear all button to not dismiss overview to home
1440         mClearAllButtonDeadZoneRect.setEmpty();
1441         if (mClearAllButton.getWidth() > 0) {
1442             int verticalMargin = getResources()
1443                     .getDimensionPixelSize(R.dimen.recents_clear_all_deadzone_vertical_margin);
1444             mClearAllButton.getHitRect(mClearAllButtonDeadZoneRect);
1445             mClearAllButtonDeadZoneRect.inset(-getPaddingRight() / 2, -verticalMargin);
1446         }
1447 
1448         // Get the deadzone rect between the task views
1449         mTaskViewDeadZoneRect.setEmpty();
1450         int count = getTaskViewCount();
1451         if (count > 0) {
1452             final View taskView = getTaskViewAt(0);
1453             getTaskViewAt(count - 1).getHitRect(mTaskViewDeadZoneRect);
1454             mTaskViewDeadZoneRect.union(taskView.getLeft(), taskView.getTop(), taskView.getRight(),
1455                     taskView.getBottom());
1456         }
1457     }
1458 
updateEmptyStateUi(boolean sizeChanged)1459     private void updateEmptyStateUi(boolean sizeChanged) {
1460         boolean hasValidSize = getWidth() > 0 && getHeight() > 0;
1461         if (sizeChanged && hasValidSize) {
1462             mEmptyTextLayout = null;
1463             mLastMeasureSize.set(getWidth(), getHeight());
1464         }
1465 
1466         if (mShowEmptyMessage && hasValidSize && mEmptyTextLayout == null) {
1467             int availableWidth = mLastMeasureSize.x - mEmptyMessagePadding - mEmptyMessagePadding;
1468             mEmptyTextLayout = StaticLayout.Builder.obtain(mEmptyMessage, 0, mEmptyMessage.length(),
1469                     mEmptyMessagePaint, availableWidth)
1470                     .setAlignment(Layout.Alignment.ALIGN_CENTER)
1471                     .build();
1472             int totalHeight = mEmptyTextLayout.getHeight()
1473                     + mEmptyMessagePadding + mEmptyIcon.getIntrinsicHeight();
1474 
1475             int top = (mLastMeasureSize.y - totalHeight) / 2;
1476             int left = (mLastMeasureSize.x - mEmptyIcon.getIntrinsicWidth()) / 2;
1477             mEmptyIcon.setBounds(left, top, left + mEmptyIcon.getIntrinsicWidth(),
1478                     top + mEmptyIcon.getIntrinsicHeight());
1479         }
1480     }
1481 
1482     @Override
verifyDrawable(Drawable who)1483     protected boolean verifyDrawable(Drawable who) {
1484         return super.verifyDrawable(who) || (mShowEmptyMessage && who == mEmptyIcon);
1485     }
1486 
maybeDrawEmptyMessage(Canvas canvas)1487     protected void maybeDrawEmptyMessage(Canvas canvas) {
1488         if (mShowEmptyMessage && mEmptyTextLayout != null) {
1489             // Offset to center in the visible (non-padded) part of RecentsView
1490             mTempRect.set(mInsets.left + getPaddingLeft(), mInsets.top + getPaddingTop(),
1491                     mInsets.right + getPaddingRight(), mInsets.bottom + getPaddingBottom());
1492             canvas.save();
1493             canvas.translate(getScrollX() + (mTempRect.left - mTempRect.right) / 2,
1494                     (mTempRect.top - mTempRect.bottom) / 2);
1495             mEmptyIcon.draw(canvas);
1496             canvas.translate(mEmptyMessagePadding,
1497                     mEmptyIcon.getBounds().bottom + mEmptyMessagePadding);
1498             mEmptyTextLayout.draw(canvas);
1499             canvas.restore();
1500         }
1501     }
1502 
1503     /**
1504      * Animate adjacent tasks off screen while scaling up.
1505      *
1506      * If launching one of the adjacent tasks, parallax the center task and other adjacent task
1507      * to the right.
1508      */
createAdjacentPageAnimForTaskLaunch( TaskView tv, ClipAnimationHelper clipAnimationHelper)1509     public AnimatorSet createAdjacentPageAnimForTaskLaunch(
1510             TaskView tv, ClipAnimationHelper clipAnimationHelper) {
1511         AnimatorSet anim = new AnimatorSet();
1512 
1513         int taskIndex = indexOfChild(tv);
1514         int centerTaskIndex = getCurrentPage();
1515         boolean launchingCenterTask = taskIndex == centerTaskIndex;
1516 
1517         LauncherState.ScaleAndTranslation toScaleAndTranslation = clipAnimationHelper
1518                 .getScaleAndTranslation();
1519         float toScale = toScaleAndTranslation.scale;
1520         float toTranslationY = toScaleAndTranslation.translationY;
1521         if (launchingCenterTask) {
1522             RecentsView recentsView = tv.getRecentsView();
1523             anim.play(ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, toScale));
1524             anim.play(ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y, toTranslationY));
1525             anim.play(ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, 1));
1526         } else {
1527             // We are launching an adjacent task, so parallax the center and other adjacent task.
1528             float displacementX = tv.getWidth() * (toScale - tv.getCurveScale());
1529             anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex), TRANSLATION_X,
1530                     mIsRtl ? -displacementX : displacementX));
1531 
1532             int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - taskIndex);
1533             if (otherAdjacentTaskIndex >= 0 && otherAdjacentTaskIndex < getPageCount()) {
1534                 anim.play(new PropertyListBuilder()
1535                         .translationX(mIsRtl ? -displacementX : displacementX)
1536                         .scale(1)
1537                         .build(getPageAt(otherAdjacentTaskIndex)));
1538             }
1539         }
1540         return anim;
1541     }
1542 
createTaskLauncherAnimation(TaskView tv, long duration)1543     public PendingAnimation createTaskLauncherAnimation(TaskView tv, long duration) {
1544         if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
1545             throw new IllegalStateException("Another pending animation is still running");
1546         }
1547 
1548         int count = getTaskViewCount();
1549         if (count == 0) {
1550             return new PendingAnimation(new AnimatorSet());
1551         }
1552 
1553         int targetSysUiFlags = tv.getThumbnail().getSysUiStatusNavFlags();
1554         final boolean[] passedOverviewThreshold = new boolean[] {false};
1555         ValueAnimator progressAnim = ValueAnimator.ofFloat(0, 1);
1556         progressAnim.setInterpolator(LINEAR);
1557         progressAnim.addUpdateListener(animator -> {
1558             // Once we pass a certain threshold, update the sysui flags to match the target
1559             // tasks' flags
1560             mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW,
1561                     animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD
1562                             ? targetSysUiFlags
1563                             : 0);
1564 
1565             onTaskLaunchAnimationUpdate(animator.getAnimatedFraction(), tv);
1566 
1567             // Passing the threshold from taskview to fullscreen app will vibrate
1568             final boolean passed = animator.getAnimatedFraction() >=
1569                     SUCCESS_TRANSITION_PROGRESS;
1570             if (passed != passedOverviewThreshold[0]) {
1571                 passedOverviewThreshold[0] = passed;
1572                 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
1573                         HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
1574             }
1575         });
1576 
1577         ClipAnimationHelper clipAnimationHelper = new ClipAnimationHelper(mActivity);
1578         clipAnimationHelper.fromTaskThumbnailView(tv.getThumbnail(), this);
1579         clipAnimationHelper.prepareAnimation(mActivity.getDeviceProfile(), true /* isOpening */);
1580         AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv, clipAnimationHelper);
1581         anim.play(progressAnim);
1582         anim.setDuration(duration);
1583 
1584         Consumer<Boolean> onTaskLaunchFinish = this::onTaskLaunched;
1585 
1586         mPendingAnimation = new PendingAnimation(anim);
1587         mPendingAnimation.addEndListener((onEndListener) -> {
1588             if (onEndListener.isSuccess) {
1589                 Consumer<Boolean> onLaunchResult = (result) -> {
1590                     onTaskLaunchFinish.accept(result);
1591                     if (!result) {
1592                         tv.notifyTaskLaunchFailed(TAG);
1593                     }
1594                 };
1595                 tv.launchTask(false, onLaunchResult, getHandler());
1596                 Task task = tv.getTask();
1597                 if (task != null) {
1598                     mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
1599                             onEndListener.logAction, Direction.DOWN, indexOfChild(tv),
1600                             TaskUtils.getLaunchComponentKeyForTask(task.key));
1601                 }
1602             } else {
1603                 onTaskLaunchFinish.accept(false);
1604             }
1605             mPendingAnimation = null;
1606         });
1607         return mPendingAnimation;
1608     }
1609 
onTaskLaunchAnimationUpdate(float progress, TaskView tv)1610     protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) {
1611     }
1612 
shouldUseMultiWindowTaskSizeStrategy()1613     public abstract boolean shouldUseMultiWindowTaskSizeStrategy();
1614 
onTaskLaunched(boolean success)1615     protected void onTaskLaunched(boolean success) {
1616         if (success) {
1617             resetTaskVisuals();
1618         }
1619     }
1620 
1621     @Override
notifyPageSwitchListener(int prevPage)1622     protected void notifyPageSwitchListener(int prevPage) {
1623         super.notifyPageSwitchListener(prevPage);
1624         loadVisibleTaskData();
1625         updateEnabledOverlays();
1626     }
1627 
1628     @Override
getCurrentPageDescription()1629     protected String getCurrentPageDescription() {
1630         return "";
1631     }
1632 
1633     @Override
addChildrenForAccessibility(ArrayList<View> outChildren)1634     public void addChildrenForAccessibility(ArrayList<View> outChildren) {
1635         // Add children in reverse order
1636         for (int i = getChildCount() - 1; i >= 0; --i) {
1637             outChildren.add(getChildAt(i));
1638         }
1639     }
1640 
1641     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)1642     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1643         super.onInitializeAccessibilityNodeInfo(info);
1644         final AccessibilityNodeInfo.CollectionInfo
1645                 collectionInfo = AccessibilityNodeInfo.CollectionInfo.obtain(
1646                 1, getTaskViewCount(), false,
1647                 AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_NONE);
1648         info.setCollectionInfo(collectionInfo);
1649     }
1650 
1651     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)1652     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1653         super.onInitializeAccessibilityEvent(event);
1654 
1655         final int taskViewCount = getTaskViewCount();
1656         event.setScrollable(taskViewCount > 0);
1657 
1658         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
1659             final int[] visibleTasks = getVisibleChildrenRange();
1660             event.setFromIndex(taskViewCount - visibleTasks[1] - 1);
1661             event.setToIndex(taskViewCount - visibleTasks[0] - 1);
1662             event.setItemCount(taskViewCount);
1663         }
1664     }
1665 
1666     @Override
getAccessibilityClassName()1667     public CharSequence getAccessibilityClassName() {
1668         // To hear position-in-list related feedback from Talkback.
1669         return ListView.class.getName();
1670     }
1671 
1672     @Override
isPageOrderFlipped()1673     protected boolean isPageOrderFlipped() {
1674         return true;
1675     }
1676 
setEnableDrawingLiveTile(boolean enableDrawingLiveTile)1677     public void setEnableDrawingLiveTile(boolean enableDrawingLiveTile) {
1678         mEnableDrawingLiveTile = enableDrawingLiveTile;
1679     }
1680 
redrawLiveTile(boolean mightNeedToRefill)1681     public void redrawLiveTile(boolean mightNeedToRefill) { }
1682 
setRecentsAnimationWrapper(RecentsAnimationWrapper recentsAnimationWrapper)1683     public void setRecentsAnimationWrapper(RecentsAnimationWrapper recentsAnimationWrapper) {
1684         mRecentsAnimationWrapper = recentsAnimationWrapper;
1685     }
1686 
setClipAnimationHelper(ClipAnimationHelper clipAnimationHelper)1687     public void setClipAnimationHelper(ClipAnimationHelper clipAnimationHelper) {
1688         mClipAnimationHelper = clipAnimationHelper;
1689     }
1690 
setLiveTileOverlay(LiveTileOverlay liveTileOverlay)1691     public void setLiveTileOverlay(LiveTileOverlay liveTileOverlay) {
1692         mLiveTileOverlay = liveTileOverlay;
1693     }
1694 
updateLiveTileIcon(Drawable icon)1695     public void updateLiveTileIcon(Drawable icon) {
1696         if (mLiveTileOverlay != null) {
1697             mLiveTileOverlay.setIcon(icon);
1698         }
1699     }
1700 
finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete)1701     public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) {
1702         if (mRecentsAnimationWrapper == null) {
1703             if (onFinishComplete != null) {
1704                 onFinishComplete.run();
1705             }
1706             return;
1707         }
1708 
1709         mRecentsAnimationWrapper.finish(toRecents, onFinishComplete);
1710     }
1711 
setDisallowScrollToClearAll(boolean disallowScrollToClearAll)1712     public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) {
1713         if (mDisallowScrollToClearAll != disallowScrollToClearAll) {
1714             mDisallowScrollToClearAll = disallowScrollToClearAll;
1715             updateMinAndMaxScrollX();
1716         }
1717     }
1718 
1719     @Override
computeMinScrollX()1720     protected int computeMinScrollX() {
1721         if (getTaskViewCount() > 0) {
1722             if (mDisallowScrollToClearAll) {
1723                 // We aren't showing the clear all button,
1724                 // so use the leftmost task as the min scroll.
1725                 if (mIsRtl) {
1726                     return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
1727                 }
1728                 return getScrollForPage(mTaskViewStartIndex);
1729             }
1730             if (mIsRtl) {
1731                 return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
1732             }
1733             return getScrollForPage(mTaskViewStartIndex);
1734         }
1735         return super.computeMinScrollX();
1736     }
1737 
1738     @Override
computeMaxScrollX()1739     protected int computeMaxScrollX() {
1740         if (getTaskViewCount() > 0) {
1741             if (mDisallowScrollToClearAll) {
1742                 // We aren't showing the clear all button,
1743                 // so use the rightmost task as the min scroll.
1744                 if (mIsRtl) {
1745                     return getScrollForPage(mTaskViewStartIndex);
1746                 }
1747                 return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
1748             }
1749             if (mIsRtl) {
1750                 return getScrollForPage(mTaskViewStartIndex);
1751             }
1752             return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
1753         }
1754         return super.computeMaxScrollX();
1755     }
1756 
getClearAllButton()1757     public ClearAllButton getClearAllButton() {
1758         return mClearAllButton;
1759     }
1760 
1761     /**
1762      * @return How many pixels the running task is offset on the x-axis due to the current scrollX.
1763      */
getScrollOffset()1764     public float getScrollOffset() {
1765         if (getRunningTaskIndex() == -1) {
1766             return 0;
1767         }
1768         int startScroll = getScrollForPage(getRunningTaskIndex());
1769         int offsetX = startScroll - getScrollX();
1770         offsetX *= getScaleX();
1771         return offsetX;
1772     }
1773 
getEventDispatcher(RotationMode rotationMode)1774     public Consumer<MotionEvent> getEventDispatcher(RotationMode rotationMode) {
1775         if (rotationMode.isTransposed) {
1776             Matrix transform = new Matrix();
1777             transform.setRotate(-rotationMode.surfaceRotation);
1778 
1779             if (getWidth() > 0 && getHeight() > 0) {
1780                 float scale = ((float) getWidth()) / getHeight();
1781                 transform.postScale(scale, 1 / scale);
1782             }
1783 
1784             Matrix inverse = new Matrix();
1785             transform.invert(inverse);
1786             return e -> {
1787                 e.transform(transform);
1788                 super.onTouchEvent(e);
1789                 e.transform(inverse);
1790             };
1791         } else {
1792             return super::onTouchEvent;
1793         }
1794     }
1795 
getTempClipAnimationHelper()1796     public ClipAnimationHelper getTempClipAnimationHelper() {
1797         return mTempClipAnimationHelper;
1798     }
1799 
updateEnabledOverlays()1800     private void updateEnabledOverlays() {
1801         int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1;
1802         int taskCount = getTaskViewCount();
1803         for (int i = 0; i < taskCount; i++) {
1804             getTaskViewAt(i).setOverlayEnabled(i == overlayEnabledPage);
1805         }
1806     }
1807 
setOverlayEnabled(boolean overlayEnabled)1808     public void setOverlayEnabled(boolean overlayEnabled) {
1809         if (mOverlayEnabled != overlayEnabled) {
1810             mOverlayEnabled = overlayEnabled;
1811             updateEnabledOverlays();
1812         }
1813     }
1814 
getLeftGestureMargin()1815     public int getLeftGestureMargin() {
1816         final WindowInsets insets = getRootWindowInsets();
1817         return Math.max(insets.getSystemGestureInsets().left, insets.getSystemWindowInsetLeft());
1818     }
1819 
getRightGestureMargin()1820     public int getRightGestureMargin() {
1821         final WindowInsets insets = getRootWindowInsets();
1822         return Math.max(insets.getSystemGestureInsets().right, insets.getSystemWindowInsetRight());
1823     }
1824 
1825     @Override
addView(View child, int index)1826     public void addView(View child, int index) {
1827         super.addView(child, index);
1828         if (isExtraCardView(child, index)) {
1829             mTaskViewStartIndex++;
1830         }
1831     }
1832 
1833     @Override
removeView(View view)1834     public void removeView(View view) {
1835         if (isExtraCardView(view, indexOfChild(view))) {
1836             mTaskViewStartIndex--;
1837         }
1838         super.removeView(view);
1839     }
1840 
isExtraCardView(View view, int index)1841     private boolean isExtraCardView(View view, int index) {
1842         return !(view instanceof TaskView) && !(view instanceof ClearAllButton)
1843                 && index <= mTaskViewStartIndex;
1844     }
1845 }
1846