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.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
24 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
25 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
26 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
27 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
28 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
29 import static com.android.launcher3.Utilities.mapToRange;
30 import static com.android.launcher3.Utilities.squaredHypot;
31 import static com.android.launcher3.Utilities.squaredTouchSlop;
32 import static com.android.launcher3.anim.Interpolators.ACCEL;
33 import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
34 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
35 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
36 import static com.android.launcher3.anim.Interpolators.LINEAR;
37 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
38 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
39 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
40 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
41 import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
42 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
43 import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON;
44 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
45 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
46 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
47 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_GESTURE_RUNNING;
48 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
49 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
50 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
51 
52 import android.animation.AnimatorSet;
53 import android.animation.LayoutTransition;
54 import android.animation.LayoutTransition.TransitionListener;
55 import android.animation.ObjectAnimator;
56 import android.animation.ValueAnimator;
57 import android.annotation.TargetApi;
58 import android.app.ActivityManager;
59 import android.content.ComponentName;
60 import android.content.Context;
61 import android.content.Intent;
62 import android.content.res.Configuration;
63 import android.graphics.Canvas;
64 import android.graphics.Point;
65 import android.graphics.PointF;
66 import android.graphics.Rect;
67 import android.graphics.Typeface;
68 import android.graphics.drawable.Drawable;
69 import android.os.Build;
70 import android.os.Handler;
71 import android.os.UserHandle;
72 import android.text.Layout;
73 import android.text.StaticLayout;
74 import android.text.TextPaint;
75 import android.util.AttributeSet;
76 import android.util.FloatProperty;
77 import android.util.Property;
78 import android.util.SparseBooleanArray;
79 import android.view.HapticFeedbackConstants;
80 import android.view.KeyEvent;
81 import android.view.LayoutInflater;
82 import android.view.MotionEvent;
83 import android.view.View;
84 import android.view.ViewDebug;
85 import android.view.ViewGroup;
86 import android.view.accessibility.AccessibilityEvent;
87 import android.view.accessibility.AccessibilityNodeInfo;
88 import android.view.animation.Interpolator;
89 import android.widget.ListView;
90 
91 import androidx.annotation.Nullable;
92 
93 import com.android.launcher3.BaseActivity;
94 import com.android.launcher3.DeviceProfile;
95 import com.android.launcher3.Insettable;
96 import com.android.launcher3.InvariantDeviceProfile;
97 import com.android.launcher3.PagedView;
98 import com.android.launcher3.R;
99 import com.android.launcher3.Utilities;
100 import com.android.launcher3.anim.AnimationSuccessListener;
101 import com.android.launcher3.anim.AnimatorPlaybackController;
102 import com.android.launcher3.anim.PendingAnimation;
103 import com.android.launcher3.anim.PendingAnimation.EndState;
104 import com.android.launcher3.anim.PropertyListBuilder;
105 import com.android.launcher3.anim.SpringProperty;
106 import com.android.launcher3.compat.AccessibilityManagerCompat;
107 import com.android.launcher3.config.FeatureFlags;
108 import com.android.launcher3.statehandlers.DepthController;
109 import com.android.launcher3.statemanager.StatefulActivity;
110 import com.android.launcher3.touch.PagedOrientationHandler;
111 import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
112 import com.android.launcher3.userevent.nano.LauncherLogProto;
113 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
114 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
115 import com.android.launcher3.util.ComponentKey;
116 import com.android.launcher3.util.DynamicResource;
117 import com.android.launcher3.util.MultiValueAlpha;
118 import com.android.launcher3.util.OverScroller;
119 import com.android.launcher3.util.Themes;
120 import com.android.launcher3.util.ViewPool;
121 import com.android.quickstep.BaseActivityInterface;
122 import com.android.quickstep.RecentsAnimationController;
123 import com.android.quickstep.RecentsAnimationTargets;
124 import com.android.quickstep.RecentsModel;
125 import com.android.quickstep.RecentsModel.TaskVisualsChangeListener;
126 import com.android.quickstep.SystemUiProxy;
127 import com.android.quickstep.TaskThumbnailCache;
128 import com.android.quickstep.TaskUtils;
129 import com.android.quickstep.ViewUtils;
130 import com.android.quickstep.util.LayoutUtils;
131 import com.android.quickstep.util.RecentsOrientedState;
132 import com.android.quickstep.util.SplitScreenBounds;
133 import com.android.quickstep.util.SurfaceTransactionApplier;
134 import com.android.quickstep.util.TransformParams;
135 import com.android.systemui.plugins.ResourceProvider;
136 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
137 import com.android.systemui.shared.recents.model.Task;
138 import com.android.systemui.shared.recents.model.ThumbnailData;
139 import com.android.systemui.shared.system.ActivityManagerWrapper;
140 import com.android.systemui.shared.system.LauncherEventUtil;
141 import com.android.systemui.shared.system.PackageManagerWrapper;
142 import com.android.systemui.shared.system.TaskStackChangeListener;
143 
144 import java.util.ArrayList;
145 import java.util.function.Consumer;
146 
147 /**
148  * A list of recent tasks.
149  */
150 @TargetApi(Build.VERSION_CODES.P)
151 public abstract class RecentsView<T extends StatefulActivity> extends PagedView implements
152         Insettable, TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
153         InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener,
154         SplitScreenBounds.OnChangeListener {
155 
156     private static final String TAG = RecentsView.class.getSimpleName();
157 
158     public static final FloatProperty<RecentsView> CONTENT_ALPHA =
159             new FloatProperty<RecentsView>("contentAlpha") {
160                 @Override
161                 public void setValue(RecentsView view, float v) {
162                     view.setContentAlpha(v);
163                 }
164 
165                 @Override
166                 public Float get(RecentsView view) {
167                     return view.getContentAlpha();
168                 }
169             };
170 
171     public static final FloatProperty<RecentsView> FULLSCREEN_PROGRESS =
172             new FloatProperty<RecentsView>("fullscreenProgress") {
173                 @Override
174                 public void setValue(RecentsView recentsView, float v) {
175                     recentsView.setFullscreenProgress(v);
176                 }
177 
178                 @Override
179                 public Float get(RecentsView recentsView) {
180                     return recentsView.mFullscreenProgress;
181                 }
182             };
183 
184     public static final FloatProperty<RecentsView> TASK_MODALNESS =
185             new FloatProperty<RecentsView>("taskModalness") {
186                 @Override
187                 public void setValue(RecentsView recentsView, float v) {
188                     recentsView.setTaskModalness(v);
189                 }
190 
191                 @Override
192                 public Float get(RecentsView recentsView) {
193                     return recentsView.mTaskModalness;
194                 }
195             };
196 
197     public static final FloatProperty<RecentsView> ADJACENT_PAGE_OFFSET =
198             new FloatProperty<RecentsView>("adjacentPageOffset") {
199                 @Override
200                 public void setValue(RecentsView recentsView, float v) {
201                     if (recentsView.mAdjacentPageOffset != v) {
202                         recentsView.mAdjacentPageOffset = v;
203                         recentsView.updatePageOffsets();
204                     }
205                 }
206 
207                 @Override
208                 public Float get(RecentsView recentsView) {
209                     return recentsView.mAdjacentPageOffset;
210                 }
211             };
212 
213     protected RecentsOrientedState mOrientationState;
214     protected final BaseActivityInterface mSizeStrategy;
215     protected RecentsAnimationController mRecentsAnimationController;
216     protected RecentsAnimationTargets mRecentsAnimationTargets;
217     protected SurfaceTransactionApplier mSyncTransactionApplier;
218     protected int mTaskWidth;
219     protected int mTaskHeight;
220     protected boolean mEnableDrawingLiveTile = false;
221     protected final Rect mTempRect = new Rect();
222     private final PointF mTempPointF = new PointF();
223 
224     private static final int DISMISS_TASK_DURATION = 300;
225     private static final int ADDITION_TASK_DURATION = 200;
226     // The threshold at which we update the SystemUI flags when animating from the task into the app
227     public static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.85f;
228 
229     protected final T mActivity;
230     private final float mFastFlingVelocity;
231     private final RecentsModel mModel;
232     private final int mTaskTopMargin;
233     private final ClearAllButton mClearAllButton;
234     private final Rect mClearAllButtonDeadZoneRect = new Rect();
235     private final Rect mTaskViewDeadZoneRect = new Rect();
236 
237     private final ScrollState mScrollState = new ScrollState();
238     // Keeps track of the previously known visible tasks for purposes of loading/unloading task data
239     private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray();
240 
241     private final InvariantDeviceProfile mIdp;
242 
243     private final ViewPool<TaskView> mTaskViewPool;
244 
245     private boolean mDwbToastShown;
246     protected boolean mDisallowScrollToClearAll;
247     private boolean mOverlayEnabled;
248     protected boolean mFreezeViewVisibility;
249 
250     private float mAdjacentPageOffset = 0;
251 
252     /**
253      * TODO: Call reloadIdNeeded in onTaskStackChanged.
254      */
255     private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
256         @Override
257         public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
258             if (!mHandleTaskStackChanges) {
259                 return;
260             }
261             // Check this is for the right user
262             if (!checkCurrentOrManagedUserId(userId, getContext())) {
263                 return;
264             }
265 
266             // Remove the task immediately from the task list
267             TaskView taskView = getTaskView(taskId);
268             if (taskView != null) {
269                 removeView(taskView);
270             }
271         }
272 
273         @Override
274         public void onActivityUnpinned() {
275             if (!mHandleTaskStackChanges) {
276                 return;
277             }
278 
279             reloadIfNeeded();
280             enableLayoutTransitions();
281         }
282 
283         @Override
284         public void onTaskRemoved(int taskId) {
285             if (!mHandleTaskStackChanges) {
286                 return;
287             }
288 
289             UI_HELPER_EXECUTOR.execute(() -> {
290                 TaskView taskView = getTaskView(taskId);
291                 if (taskView == null) {
292                     return;
293                 }
294                 Handler handler = taskView.getHandler();
295                 if (handler == null) {
296                     return;
297                 }
298 
299                 // TODO: Add callbacks from AM reflecting adding/removing from the recents list, and
300                 //       remove all these checks
301                 Task.TaskKey taskKey = taskView.getTask().key;
302                 if (PackageManagerWrapper.getInstance().getActivityInfo(taskKey.getComponent(),
303                         taskKey.userId) == null) {
304                     // The package was uninstalled
305                     handler.post(() ->
306                             dismissTask(taskView, true /* animate */, false /* removeTask */));
307                 } else {
308                     mModel.findTaskWithId(taskKey.id, (key) -> {
309                         if (key == null) {
310                             // The task was removed from the recents list
311                             handler.post(() -> dismissTask(taskView, true /* animate */,
312                                     false /* removeTask */));
313                         }
314                     });
315                 }
316             });
317         }
318     };
319 
320     private final PinnedStackAnimationListener mIPinnedStackAnimationListener =
321             new PinnedStackAnimationListener();
322 
323     // Used to keep track of the last requested task list id, so that we do not request to load the
324     // tasks again if we have already requested it and the task list has not changed
325     private int mTaskListChangeId = -1;
326 
327     // Only valid until the launcher state changes to NORMAL
328     protected int mRunningTaskId = -1;
329     protected boolean mRunningTaskTileHidden;
330     private Task mTmpRunningTask;
331 
332     private boolean mRunningTaskIconScaledDown = false;
333 
334     private boolean mOverviewStateEnabled;
335     private boolean mHandleTaskStackChanges;
336     private boolean mSwipeDownShouldLaunchApp;
337     private boolean mTouchDownToStartHome;
338     private final float mSquaredTouchSlop;
339     private int mDownX;
340     private int mDownY;
341 
342     private PendingAnimation mPendingAnimation;
343     private LayoutTransition mLayoutTransition;
344 
345     @ViewDebug.ExportedProperty(category = "launcher")
346     protected float mContentAlpha = 1;
347     @ViewDebug.ExportedProperty(category = "launcher")
348     protected float mFullscreenProgress = 0;
349     /**
350      * How modal is the current task to be displayed, 1 means the task is fully modal and no other
351      * tasks are show. 0 means the task is displays in context in the list with other tasks.
352      */
353     @ViewDebug.ExportedProperty(category = "launcher")
354     protected float mTaskModalness = 0;
355 
356     // Keeps track of task id whose visual state should not be reset
357     private int mIgnoreResetTaskId = -1;
358 
359     // Variables for empty state
360     private final Drawable mEmptyIcon;
361     private final CharSequence mEmptyMessage;
362     private final TextPaint mEmptyMessagePaint;
363     private final Point mLastMeasureSize = new Point();
364     private final int mEmptyMessagePadding;
365     private boolean mShowEmptyMessage;
366     private OnEmptyMessageUpdatedListener mOnEmptyMessageUpdatedListener;
367     private Layout mEmptyTextLayout;
368     private boolean mLiveTileOverlayAttached;
369 
370     // Keeps track of the index where the first TaskView should be
371     private int mTaskViewStartIndex = 0;
372     private OverviewActionsView mActionsView;
373 
374     private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener =
375             (inMultiWindowMode) -> {
376                 if (mOrientationState != null) {
377                     mOrientationState.setMultiWindowMode(inMultiWindowMode);
378                     setLayoutRotation(mOrientationState.getTouchRotation(),
379                             mOrientationState.getDisplayRotation());
380                     rotateAllChildTasks();
381                 }
382                 if (!inMultiWindowMode && mOverviewStateEnabled) {
383                     // TODO: Re-enable layout transitions for addition of the unpinned task
384                     reloadIfNeeded();
385                 }
386             };
387 
RecentsView(Context context, AttributeSet attrs, int defStyleAttr, BaseActivityInterface sizeStrategy)388     public RecentsView(Context context, AttributeSet attrs, int defStyleAttr,
389             BaseActivityInterface sizeStrategy) {
390         super(context, attrs, defStyleAttr);
391         setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
392         setEnableFreeScroll(true);
393         mSizeStrategy = sizeStrategy;
394         mActivity = BaseActivity.fromContext(context);
395         mOrientationState = new RecentsOrientedState(
396                 context, mSizeStrategy, this::animateRecentsRotationInPlace);
397         mOrientationState.setActivityConfiguration(context.getResources().getConfiguration());
398 
399         mFastFlingVelocity = getResources()
400                 .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
401         mModel = RecentsModel.INSTANCE.get(context);
402         mIdp = InvariantDeviceProfile.INSTANCE.get(context);
403 
404         mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
405                 .inflate(R.layout.overview_clear_all_button, this, false);
406         mClearAllButton.setOnClickListener(this::dismissAllTasks);
407         mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
408                 10 /* initial size */);
409 
410         mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
411         setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
412         mTaskTopMargin = getResources()
413                 .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
414         mSquaredTouchSlop = squaredTouchSlop(context);
415 
416         mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents);
417         mEmptyIcon.setCallback(this);
418         mEmptyMessage = context.getText(R.string.recents_empty_message);
419         mEmptyMessagePaint = new TextPaint();
420         mEmptyMessagePaint.setColor(Themes.getAttrColor(context, android.R.attr.textColorPrimary));
421         mEmptyMessagePaint.setTextSize(getResources()
422                 .getDimension(R.dimen.recents_empty_message_text_size));
423         mEmptyMessagePaint.setTypeface(Typeface.create(Themes.getDefaultBodyFont(context),
424                 Typeface.NORMAL));
425         mEmptyMessagePadding = getResources()
426                 .getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
427         setWillNotDraw(false);
428         updateEmptyMessage();
429         mOrientationHandler = mOrientationState.getOrientationHandler();
430 
431         // Initialize quickstep specific cache params here, as this is constructed only once
432         mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
433     }
434 
getScroller()435     public OverScroller getScroller() {
436         return mScroller;
437     }
438 
isRtl()439     public boolean isRtl() {
440         return mIsRtl;
441     }
442 
443     @Override
onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData)444     public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) {
445         if (mHandleTaskStackChanges) {
446             TaskView taskView = getTaskView(taskId);
447             if (taskView != null) {
448                 Task task = taskView.getTask();
449                 taskView.getThumbnail().setThumbnail(task, thumbnailData);
450                 return task;
451             }
452         }
453         return null;
454     }
455 
456     @Override
onTaskIconChanged(String pkg, UserHandle user)457     public void onTaskIconChanged(String pkg, UserHandle user) {
458         for (int i = 0; i < getTaskViewCount(); i++) {
459             TaskView tv = getTaskViewAt(i);
460             Task task = tv.getTask();
461             if (task != null && task.key != null && pkg.equals(task.key.getPackageName())
462                     && task.key.userId == user.getIdentifier()) {
463                 task.icon = null;
464                 if (tv.getIconView().getDrawable() != null) {
465                     tv.onTaskListVisibilityChanged(true /* visible */);
466                 }
467             }
468         }
469     }
470 
471     /**
472      * Update the thumbnail of the task.
473      * @param refreshNow Refresh immediately if it's true.
474      */
updateThumbnail(int taskId, ThumbnailData thumbnailData, boolean refreshNow)475     public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData, boolean refreshNow) {
476         TaskView taskView = getTaskView(taskId);
477         if (taskView != null) {
478             taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData, refreshNow);
479         }
480         return taskView;
481     }
482 
483     /** See {@link #updateThumbnail(int, ThumbnailData, boolean)} */
updateThumbnail(int taskId, ThumbnailData thumbnailData)484     public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
485         return updateThumbnail(taskId, thumbnailData, true /* refreshNow */);
486     }
487 
488     @Override
onWindowVisibilityChanged(int visibility)489     protected void onWindowVisibilityChanged(int visibility) {
490         super.onWindowVisibilityChanged(visibility);
491         updateTaskStackListenerState();
492     }
493 
494     @Override
onIdpChanged(int changeFlags, InvariantDeviceProfile idp)495     public void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) {
496         if ((changeFlags & CHANGE_FLAG_ICON_PARAMS) == 0) {
497             return;
498         }
499         mModel.getIconCache().clear();
500         unloadVisibleTaskData();
501         loadVisibleTaskData();
502     }
503 
init(OverviewActionsView actionsView)504     public void init(OverviewActionsView actionsView) {
505         mActionsView = actionsView;
506         mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
507     }
508 
509     @Override
onAttachedToWindow()510     protected void onAttachedToWindow() {
511         super.onAttachedToWindow();
512         updateTaskStackListenerState();
513         mModel.getThumbnailCache().getHighResLoadingState().addCallback(this);
514         mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
515         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
516         mSyncTransactionApplier = new SurfaceTransactionApplier(this);
517         RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
518         mIdp.addOnChangeListener(this);
519         mIPinnedStackAnimationListener.setActivity(mActivity);
520         SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(
521                 mIPinnedStackAnimationListener);
522         mOrientationState.initListeners();
523         SplitScreenBounds.INSTANCE.addOnChangeListener(this);
524     }
525 
526     @Override
onDetachedFromWindow()527     protected void onDetachedFromWindow() {
528         super.onDetachedFromWindow();
529         updateTaskStackListenerState();
530         mModel.getThumbnailCache().getHighResLoadingState().removeCallback(this);
531         mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
532         ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
533         mSyncTransactionApplier = null;
534         RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
535         mIdp.removeOnChangeListener(this);
536         SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
537         SplitScreenBounds.INSTANCE.removeOnChangeListener(this);
538         mIPinnedStackAnimationListener.setActivity(null);
539         mOrientationState.destroyListeners();
540     }
541 
542     @Override
onViewRemoved(View child)543     public void onViewRemoved(View child) {
544         super.onViewRemoved(child);
545 
546         // Clear the task data for the removed child if it was visible
547         if (child instanceof TaskView) {
548             TaskView taskView = (TaskView) child;
549             mHasVisibleTaskData.delete(taskView.getTask().key.id);
550             mTaskViewPool.recycle(taskView);
551             mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
552         }
553         updateTaskStartIndex(child);
554     }
555 
556     @Override
onViewAdded(View child)557     public void onViewAdded(View child) {
558         super.onViewAdded(child);
559         child.setAlpha(mContentAlpha);
560         // RecentsView is set to RTL in the constructor when system is using LTR. Here we set the
561         // child direction back to match system settings.
562         child.setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_LTR : View.LAYOUT_DIRECTION_RTL);
563         updateTaskStartIndex(child);
564         mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, false);
565         updateEmptyMessage();
566     }
567 
568     @Override
draw(Canvas canvas)569     public void draw(Canvas canvas) {
570         maybeDrawEmptyMessage(canvas);
571         super.draw(canvas);
572     }
573 
updateTaskStartIndex(View affectingView)574     private void updateTaskStartIndex(View affectingView) {
575         if (!(affectingView instanceof TaskView) && !(affectingView instanceof ClearAllButton)) {
576             int childCount = getChildCount();
577 
578             mTaskViewStartIndex = 0;
579             while (mTaskViewStartIndex < childCount
580                     && !(getChildAt(mTaskViewStartIndex) instanceof TaskView)) {
581                 mTaskViewStartIndex++;
582             }
583         }
584     }
585 
isTaskViewVisible(TaskView tv)586     public boolean isTaskViewVisible(TaskView tv) {
587         // For now, just check if it's the active task or an adjacent task
588         return Math.abs(indexOfChild(tv) - getNextPage()) <= 1;
589     }
590 
getTaskView(int taskId)591     public TaskView getTaskView(int taskId) {
592         for (int i = 0; i < getTaskViewCount(); i++) {
593             TaskView tv = getTaskViewAt(i);
594             if (tv.getTask() != null && tv.getTask().key != null && tv.getTask().key.id == taskId) {
595                 return tv;
596             }
597         }
598         return null;
599     }
600 
setOverviewStateEnabled(boolean enabled)601     public void setOverviewStateEnabled(boolean enabled) {
602         mOverviewStateEnabled = enabled;
603         updateTaskStackListenerState();
604         mOrientationState.setRotationWatcherEnabled(enabled);
605         if (!enabled) {
606             // Reset the running task when leaving overview since it can still have a reference to
607             // its thumbnail
608             mTmpRunningTask = null;
609         }
610     }
611 
onDigitalWellbeingToastShown()612     public void onDigitalWellbeingToastShown() {
613         if (!mDwbToastShown) {
614             mDwbToastShown = true;
615             mActivity.getUserEventDispatcher().logActionTip(
616                     LauncherEventUtil.VISIBLE,
617                     LauncherLogProto.TipType.DWB_TOAST);
618         }
619     }
620 
621     /**
622      * Whether the Clear All button is hidden or fully visible. Used to determine if center
623      * displayed page is a task or the Clear All button.
624      *
625      * @return True = Clear All button not fully visible, center page is a task. False = Clear All
626      * button fully visible, center page is Clear All button.
627      */
isClearAllHidden()628     public boolean isClearAllHidden() {
629         return mClearAllButton.getAlpha() != 1f;
630     }
631 
632     @Override
onPageBeginTransition()633     protected void onPageBeginTransition() {
634         super.onPageBeginTransition();
635         mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, true);
636     }
637 
638     @Override
onPageEndTransition()639     protected void onPageEndTransition() {
640         super.onPageEndTransition();
641         if (isClearAllHidden()) {
642             mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
643         }
644         if (getNextPage() > 0) {
645             setSwipeDownShouldLaunchApp(true);
646         }
647     }
648 
649     @Override
onTouchEvent(MotionEvent ev)650     public boolean onTouchEvent(MotionEvent ev) {
651         super.onTouchEvent(ev);
652         final int x = (int) ev.getX();
653         final int y = (int) ev.getY();
654         switch (ev.getAction()) {
655             case MotionEvent.ACTION_UP:
656                 if (mTouchDownToStartHome) {
657                     startHome();
658                 }
659                 mTouchDownToStartHome = false;
660                 break;
661             case MotionEvent.ACTION_CANCEL:
662                 mTouchDownToStartHome = false;
663                 break;
664             case MotionEvent.ACTION_MOVE:
665                 // Passing the touch slop will not allow dismiss to home
666                 if (mTouchDownToStartHome &&
667                         (isHandlingTouch() ||
668                                 squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop)) {
669                     mTouchDownToStartHome = false;
670                 }
671                 break;
672             case MotionEvent.ACTION_DOWN:
673                 // Touch down anywhere but the deadzone around the visible clear all button and
674                 // between the task views will start home on touch up
675                 if (!isHandlingTouch() && !isModal()) {
676                     if (mShowEmptyMessage) {
677                         mTouchDownToStartHome = true;
678                     } else {
679                         updateDeadZoneRects();
680                         final boolean clearAllButtonDeadZoneConsumed =
681                                 mClearAllButton.getAlpha() == 1
682                                         && mClearAllButtonDeadZoneRect.contains(x, y);
683                         final boolean cameFromNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
684                         if (!clearAllButtonDeadZoneConsumed && !cameFromNavBar
685                                 && !mTaskViewDeadZoneRect.contains(x + getScrollX(), y)) {
686                             mTouchDownToStartHome = true;
687                         }
688                     }
689                 }
690                 mDownX = x;
691                 mDownY = y;
692                 break;
693         }
694 
695 
696         // Do not let touch escape to siblings below this view.
697         return isHandlingTouch() || shouldStealTouchFromSiblingsBelow(ev);
698     }
699 
700     @Override
determineScrollingStart(MotionEvent ev, float touchSlopScale)701     protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
702         // Enables swiping to the left or right only if the task overlay is not modal.
703         if (!isModal()) {
704             super.determineScrollingStart(ev, touchSlopScale);
705         }
706     }
shouldStealTouchFromSiblingsBelow(MotionEvent ev)707     protected boolean shouldStealTouchFromSiblingsBelow(MotionEvent ev) {
708         return true;
709     }
710 
applyLoadPlan(ArrayList<Task> tasks)711     protected void applyLoadPlan(ArrayList<Task> tasks) {
712         if (mPendingAnimation != null) {
713             mPendingAnimation.addEndListener((endState) -> applyLoadPlan(tasks));
714             return;
715         }
716 
717         if (tasks == null || tasks.isEmpty()) {
718             removeTasksViewsAndClearAllButton();
719             onTaskStackUpdated();
720             return;
721         }
722 
723         // Unload existing visible task data
724         unloadVisibleTaskData();
725 
726         TaskView ignoreResetTaskView =
727                 mIgnoreResetTaskId == -1 ? null : getTaskView(mIgnoreResetTaskId);
728 
729         final int requiredTaskCount = tasks.size();
730         if (getTaskViewCount() != requiredTaskCount) {
731             if (indexOfChild(mClearAllButton) != -1) {
732                 removeView(mClearAllButton);
733             }
734             for (int i = getTaskViewCount(); i < requiredTaskCount; i++) {
735                 addView(mTaskViewPool.getView());
736             }
737             while (getTaskViewCount() > requiredTaskCount) {
738                 removeView(getChildAt(getChildCount() - 1));
739             }
740             if (requiredTaskCount > 0) {
741                 addView(mClearAllButton);
742             }
743         }
744 
745         // Rebind and reset all task views
746         for (int i = requiredTaskCount - 1; i >= 0; i--) {
747             final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex;
748             final Task task = tasks.get(i);
749             final TaskView taskView = (TaskView) getChildAt(pageIndex);
750             taskView.bind(task, mOrientationState);
751         }
752 
753         if (mNextPage == INVALID_PAGE) {
754             // Set the current page to the running task, but not if settling on new task.
755             TaskView runningTaskView = getRunningTaskView();
756             if (runningTaskView != null) {
757                 setCurrentPage(indexOfChild(runningTaskView));
758             } else if (getTaskViewCount() > 0) {
759                 setCurrentPage(indexOfChild(getTaskViewAt(0)));
760             }
761         }
762 
763         if (mIgnoreResetTaskId != -1 && getTaskView(mIgnoreResetTaskId) != ignoreResetTaskView) {
764             // If the taskView mapping is changing, do not preserve the visuals. Since we are
765             // mostly preserving the first task, and new taskViews are added to the end, it should
766             // generally map to the same task.
767             mIgnoreResetTaskId = -1;
768         }
769         resetTaskVisuals();
770         onTaskStackUpdated();
771         updateEnabledOverlays();
772     }
773 
isModal()774     private boolean isModal() {
775         return mTaskModalness > 0;
776     }
777 
removeTasksViewsAndClearAllButton()778     private void removeTasksViewsAndClearAllButton() {
779         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
780             removeView(getTaskViewAt(i));
781         }
782         if (indexOfChild(mClearAllButton) != -1) {
783             removeView(mClearAllButton);
784         }
785     }
786 
getTaskViewCount()787     public int getTaskViewCount() {
788         int taskViewCount = getChildCount() - mTaskViewStartIndex;
789         if (indexOfChild(mClearAllButton) != -1) {
790             taskViewCount--;
791         }
792         return taskViewCount;
793     }
794 
onTaskStackUpdated()795     protected void onTaskStackUpdated() {
796         // Lazily update the empty message only when the task stack is reapplied
797         updateEmptyMessage();
798     }
799 
resetTaskVisuals()800     public void resetTaskVisuals() {
801         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
802             TaskView taskView = getTaskViewAt(i);
803             if (mIgnoreResetTaskId != taskView.getTask().key.id) {
804                 taskView.resetViewTransforms();
805                 taskView.setStableAlpha(mContentAlpha);
806                 taskView.setFullscreenProgress(mFullscreenProgress);
807                 taskView.setModalness(mTaskModalness);
808             }
809         }
810         if (mRunningTaskTileHidden) {
811             setRunningTaskHidden(mRunningTaskTileHidden);
812         }
813 
814         // Force apply the scale.
815         if (mIgnoreResetTaskId != mRunningTaskId) {
816             applyRunningTaskIconScale();
817         }
818 
819         updateCurveProperties();
820         // Update the set of visible task's data
821         loadVisibleTaskData();
822         setTaskModalness(0);
823     }
824 
setFullscreenProgress(float fullscreenProgress)825     public void setFullscreenProgress(float fullscreenProgress) {
826         mFullscreenProgress = fullscreenProgress;
827         int taskCount = getTaskViewCount();
828         for (int i = 0; i < taskCount; i++) {
829             getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress);
830         }
831         // Fade out the actions view quickly (0.1 range)
832         mActionsView.getFullscreenAlpha().setValue(
833                 mapToRange(fullscreenProgress, 0, 0.1f, 1f, 0f, LINEAR));
834     }
835 
updateTaskStackListenerState()836     private void updateTaskStackListenerState() {
837         boolean handleTaskStackChanges = mOverviewStateEnabled && isAttachedToWindow()
838                 && getWindowVisibility() == VISIBLE;
839         if (handleTaskStackChanges != mHandleTaskStackChanges) {
840             mHandleTaskStackChanges = handleTaskStackChanges;
841             if (handleTaskStackChanges) {
842                 reloadIfNeeded();
843             }
844         }
845     }
846 
847     @Override
setInsets(Rect insets)848     public void setInsets(Rect insets) {
849         mInsets.set(insets);
850         resetPaddingFromTaskSize();
851     }
852 
resetPaddingFromTaskSize()853     private void resetPaddingFromTaskSize() {
854         DeviceProfile dp = mActivity.getDeviceProfile();
855         getTaskSize(mTempRect);
856         mTaskWidth = mTempRect.width();
857         mTaskHeight = mTempRect.height();
858 
859         mTempRect.top -= mTaskTopMargin;
860         setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top,
861                 dp.widthPx - mInsets.right - mTempRect.right,
862                 dp.heightPx - mInsets.bottom - mTempRect.bottom);
863     }
864 
getTaskSize(Rect outRect)865     public void getTaskSize(Rect outRect) {
866         mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect,
867                 mOrientationHandler);
868     }
869 
870     /** Gets the task size for modal state. */
getModalTaskSize(Rect outRect)871     public void getModalTaskSize(Rect outRect) {
872         mSizeStrategy.calculateModalTaskSize(mActivity, mActivity.getDeviceProfile(), outRect);
873     }
874 
875     @Override
computeScrollHelper()876     protected boolean computeScrollHelper() {
877         boolean scrolling = super.computeScrollHelper();
878         boolean isFlingingFast = false;
879         updateCurveProperties();
880         if (scrolling || isHandlingTouch()) {
881             if (scrolling) {
882                 // Check if we are flinging quickly to disable high res thumbnail loading
883                 isFlingingFast = mScroller.getCurrVelocity() > mFastFlingVelocity;
884             }
885 
886             // After scrolling, update the visible task's data
887             loadVisibleTaskData();
888         }
889 
890         // Update the high res thumbnail loader state
891         mModel.getThumbnailCache().getHighResLoadingState().setFlingingFast(isFlingingFast);
892         return scrolling;
893     }
894 
895     /**
896      * Scales and adjusts translation of adjacent pages as if on a curved carousel.
897      */
updateCurveProperties()898     public void updateCurveProperties() {
899         if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
900             return;
901         }
902         mOrientationHandler.getCurveProperties(this, mInsets, mScrollState);
903         mScrollState.scrollFromEdge =
904                 mIsRtl ? mScrollState.scroll : (mMaxScroll - mScrollState.scroll);
905 
906         final int pageCount = getPageCount();
907         for (int i = 0; i < pageCount; i++) {
908             View page = getPageAt(i);
909             mScrollState.updateInterpolation(mOrientationHandler.getChildStartWithTranslation(page),
910                     mPageSpacing);
911             ((PageCallbacks) page).onPageScroll(mScrollState);
912         }
913     }
914 
915     /**
916      * Iterates through all the tasks, and loads the associated task data for newly visible tasks,
917      * and unloads the associated task data for tasks that are no longer visible.
918      */
loadVisibleTaskData()919     public void loadVisibleTaskData() {
920         if (!mOverviewStateEnabled || mTaskListChangeId == -1) {
921             // Skip loading visible task data if we've already left the overview state, or if the
922             // task list hasn't been loaded yet (the task views will not reflect the task list)
923             return;
924         }
925 
926         int centerPageIndex = getPageNearestToCenterOfScreen();
927         int numChildren = getChildCount();
928         int lower = Math.max(0, centerPageIndex - 2);
929         int upper = Math.min(centerPageIndex + 2, numChildren - 1);
930 
931         // Update the task data for the in/visible children
932         for (int i = 0; i < getTaskViewCount(); i++) {
933             TaskView taskView = getTaskViewAt(i);
934             Task task = taskView.getTask();
935             int index = indexOfChild(taskView);
936             boolean visible = lower <= index && index <= upper;
937             if (visible) {
938                 if (task == mTmpRunningTask) {
939                     // Skip loading if this is the task that we are animating into
940                     continue;
941                 }
942                 if (!mHasVisibleTaskData.get(task.key.id)) {
943                     taskView.onTaskListVisibilityChanged(true /* visible */);
944                 }
945                 mHasVisibleTaskData.put(task.key.id, visible);
946             } else {
947                 if (mHasVisibleTaskData.get(task.key.id)) {
948                     taskView.onTaskListVisibilityChanged(false /* visible */);
949                 }
950                 mHasVisibleTaskData.delete(task.key.id);
951             }
952         }
953     }
954 
955     /**
956      * Unloads any associated data from the currently visible tasks
957      */
unloadVisibleTaskData()958     private void unloadVisibleTaskData() {
959         for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
960             if (mHasVisibleTaskData.valueAt(i)) {
961                 TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
962                 if (taskView != null) {
963                     taskView.onTaskListVisibilityChanged(false /* visible */);
964                 }
965             }
966         }
967         mHasVisibleTaskData.clear();
968     }
969 
970     @Override
onHighResLoadingStateChanged(boolean enabled)971     public void onHighResLoadingStateChanged(boolean enabled) {
972         // Whenever the high res loading state changes, poke each of the visible tasks to see if
973         // they want to updated their thumbnail state
974         for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
975             if (mHasVisibleTaskData.valueAt(i)) {
976                 TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
977                 if (taskView != null) {
978                     // Poke the view again, which will trigger it to load high res if the state
979                     // is enabled
980                     taskView.onTaskListVisibilityChanged(true /* visible */);
981                 }
982             }
983         }
984     }
985 
startHome()986     public abstract void startHome();
987 
988     /** `true` if there is a +1 space available in overview. */
hasRecentsExtraCard()989     public boolean hasRecentsExtraCard() {
990         return false;
991     }
992 
reset()993     public void reset() {
994         setCurrentTask(-1);
995         mIgnoreResetTaskId = -1;
996         mTaskListChangeId = -1;
997 
998         mRecentsAnimationController = null;
999         mRecentsAnimationTargets = null;
1000 
1001         unloadVisibleTaskData();
1002         setCurrentPage(0);
1003         mDwbToastShown = false;
1004         mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0);
1005         LayoutUtils.setViewEnabled(mActionsView, true);
1006         if (mOrientationState.setGestureActive(false)) {
1007             updateOrientationHandler();
1008         }
1009     }
1010 
getRunningTaskView()1011     public @Nullable TaskView getRunningTaskView() {
1012         return getTaskView(mRunningTaskId);
1013     }
1014 
getRunningTaskIndex()1015     public int getRunningTaskIndex() {
1016         return getTaskIndexForId(mRunningTaskId);
1017     }
1018 
1019     /**
1020      * Get the index of the task view whose id matches {@param taskId}.
1021      * @return -1 if there is no task view for the task id, else the index of the task view.
1022      */
getTaskIndexForId(int taskId)1023     public int getTaskIndexForId(int taskId) {
1024         TaskView tv = getTaskView(taskId);
1025         return tv == null ? -1 : indexOfChild(tv);
1026     }
1027 
getTaskViewStartIndex()1028     public int getTaskViewStartIndex() {
1029         return mTaskViewStartIndex;
1030     }
1031 
1032     /**
1033      * Reloads the view if anything in recents changed.
1034      */
reloadIfNeeded()1035     public void reloadIfNeeded() {
1036         if (!mModel.isTaskListValid(mTaskListChangeId)) {
1037             mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
1038         }
1039     }
1040 
1041     /**
1042      * Called when a gesture from an app is starting.
1043      */
onGestureAnimationStart(int runningTaskId)1044     public void onGestureAnimationStart(int runningTaskId) {
1045         // This needs to be called before the other states are set since it can create the task view
1046         if (mOrientationState.setGestureActive(true)) {
1047             updateOrientationHandler();
1048         }
1049 
1050         showCurrentTask(runningTaskId);
1051         setEnableFreeScroll(false);
1052         setEnableDrawingLiveTile(false);
1053         setRunningTaskHidden(true);
1054         setRunningTaskIconScaledDown(true);
1055         mActionsView.updateHiddenFlags(HIDDEN_GESTURE_RUNNING, true);
1056     }
1057 
1058     /**
1059      * Called only when a swipe-up gesture from an app has completed. Only called after
1060      * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}.
1061      */
onSwipeUpAnimationSuccess()1062     public void onSwipeUpAnimationSuccess() {
1063         if (getRunningTaskView() != null) {
1064             float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() && mLiveTileOverlayAttached
1065                     ? LiveTileOverlay.INSTANCE.cancelIconAnimation()
1066                     : 0f;
1067             animateUpRunningTaskIconScale(startProgress);
1068         }
1069         setSwipeDownShouldLaunchApp(true);
1070     }
1071 
animateRecentsRotationInPlace(int newRotation)1072     private void animateRecentsRotationInPlace(int newRotation) {
1073         if (mOrientationState.canRecentsActivityRotate()) {
1074             // Let system take care of the rotation
1075             return;
1076         }
1077         AnimatorSet pa = setRecentsChangedOrientation(true);
1078         pa.addListener(AnimationSuccessListener.forRunnable(() -> {
1079             setLayoutRotation(newRotation, mOrientationState.getDisplayRotation());
1080             mActivity.getDragLayer().recreateControllers();
1081             rotateAllChildTasks();
1082             setRecentsChangedOrientation(false).start();
1083         }));
1084         pa.start();
1085     }
1086 
setRecentsChangedOrientation(boolean fadeInChildren)1087     public AnimatorSet setRecentsChangedOrientation(boolean fadeInChildren) {
1088         getRunningTaskIndex();
1089         int runningIndex = getCurrentPage();
1090         AnimatorSet as = new AnimatorSet();
1091         for (int i = 0; i < getTaskViewCount(); i++) {
1092             if (runningIndex == i) {
1093                 continue;
1094             }
1095             View taskView = getTaskViewAt(i);
1096             as.play(ObjectAnimator.ofFloat(taskView, View.ALPHA, fadeInChildren ? 0 : 1));
1097         }
1098         return as;
1099     }
1100 
1101 
rotateAllChildTasks()1102     private void rotateAllChildTasks() {
1103         for (int i = 0; i < getTaskViewCount(); i++) {
1104             getTaskViewAt(i).setOrientationState(mOrientationState);
1105         }
1106     }
1107 
1108     /**
1109      * Called when a gesture from an app has finished.
1110      */
onGestureAnimationEnd()1111     public void onGestureAnimationEnd() {
1112         if (mOrientationState.setGestureActive(false)) {
1113             updateOrientationHandler();
1114         }
1115 
1116         setOnScrollChangeListener(null);
1117         setEnableFreeScroll(true);
1118         setEnableDrawingLiveTile(true);
1119         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
1120             setRunningTaskViewShowScreenshot(true);
1121         }
1122         setRunningTaskHidden(false);
1123         animateUpRunningTaskIconScale();
1124         animateActionsViewIn();
1125     }
1126 
1127     /**
1128      * Returns true if we should add a dummy taskView for the running task id
1129      */
shouldAddDummyTaskView(int runningTaskId)1130     protected boolean shouldAddDummyTaskView(int runningTaskId) {
1131         return getTaskView(runningTaskId) == null;
1132     }
1133 
1134     /**
1135      * Creates a task view (if necessary) to represent the task with the {@param runningTaskId}.
1136      *
1137      * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
1138      * is called.  Also scrolls the view to this task.
1139      */
showCurrentTask(int runningTaskId)1140     public void showCurrentTask(int runningTaskId) {
1141         if (shouldAddDummyTaskView(runningTaskId)) {
1142             boolean wasEmpty = getChildCount() == 0;
1143             // Add an empty view for now until the task plan is loaded and applied
1144             final TaskView taskView = mTaskViewPool.getView();
1145             addView(taskView, mTaskViewStartIndex);
1146             if (wasEmpty) {
1147                 addView(mClearAllButton);
1148             }
1149             // The temporary running task is only used for the duration between the start of the
1150             // gesture and the task list is loaded and applied
1151             mTmpRunningTask = new Task(new Task.TaskKey(runningTaskId, 0, new Intent(),
1152                     new ComponentName(getContext(), getClass()), 0, 0), null, null, "", "", 0, 0,
1153                     false, true, false, false, new ActivityManager.TaskDescription(), 0,
1154                     new ComponentName("", ""), false);
1155             taskView.bind(mTmpRunningTask, mOrientationState);
1156 
1157             // Measure and layout immediately so that the scroll values is updated instantly
1158             // as the user might be quick-switching
1159             measure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
1160                     makeMeasureSpec(getMeasuredHeight(), EXACTLY));
1161             layout(getLeft(), getTop(), getRight(), getBottom());
1162         }
1163 
1164         boolean runningTaskTileHidden = mRunningTaskTileHidden;
1165         setCurrentTask(runningTaskId);
1166         setCurrentPage(getRunningTaskIndex());
1167         setRunningTaskViewShowScreenshot(false);
1168         setRunningTaskHidden(runningTaskTileHidden);
1169 
1170         // Reload the task list
1171         mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
1172     }
1173 
1174     /**
1175      * Sets the running task id, cleaning up the old running task if necessary.
1176      * @param runningTaskId
1177      */
setCurrentTask(int runningTaskId)1178     public void setCurrentTask(int runningTaskId) {
1179         if (mRunningTaskId == runningTaskId) {
1180             return;
1181         }
1182 
1183         if (mRunningTaskId != -1) {
1184             // Reset the state on the old running task view
1185             setRunningTaskIconScaledDown(false);
1186             setRunningTaskViewShowScreenshot(true);
1187             setRunningTaskHidden(false);
1188         }
1189         mRunningTaskId = runningTaskId;
1190     }
1191 
1192     /**
1193      * Hides the tile associated with {@link #mRunningTaskId}
1194      */
setRunningTaskHidden(boolean isHidden)1195     public void setRunningTaskHidden(boolean isHidden) {
1196         mRunningTaskTileHidden = isHidden;
1197         TaskView runningTask = getRunningTaskView();
1198         if (runningTask != null) {
1199             runningTask.setStableAlpha(isHidden ? 0 : mContentAlpha);
1200             if (!isHidden) {
1201                 AccessibilityManagerCompat.sendCustomAccessibilityEvent(runningTask,
1202                         AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
1203             }
1204         }
1205     }
1206 
setRunningTaskViewShowScreenshot(boolean showScreenshot)1207     private void setRunningTaskViewShowScreenshot(boolean showScreenshot) {
1208         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
1209             TaskView runningTaskView = getRunningTaskView();
1210             if (runningTaskView != null) {
1211                 runningTaskView.setShowScreenshot(showScreenshot);
1212             }
1213         }
1214     }
1215 
showNextTask()1216     public void showNextTask() {
1217         TaskView runningTaskView = getRunningTaskView();
1218         if (runningTaskView == null) {
1219             // Launch the first task
1220             if (getTaskViewCount() > 0) {
1221                 getTaskViewAt(0).launchTask(true);
1222             }
1223         } else {
1224             if (getNextTaskView() != null) {
1225                 getNextTaskView().launchTask(true);
1226             } else {
1227                 runningTaskView.launchTask(true);
1228             }
1229         }
1230     }
1231 
setRunningTaskIconScaledDown(boolean isScaledDown)1232     public void setRunningTaskIconScaledDown(boolean isScaledDown) {
1233         if (mRunningTaskIconScaledDown != isScaledDown) {
1234             mRunningTaskIconScaledDown = isScaledDown;
1235             applyRunningTaskIconScale();
1236         }
1237     }
1238 
isTaskIconScaledDown(TaskView taskView)1239     public boolean isTaskIconScaledDown(TaskView taskView) {
1240         return mRunningTaskIconScaledDown && getRunningTaskView() == taskView;
1241     }
1242 
applyRunningTaskIconScale()1243     private void applyRunningTaskIconScale() {
1244         TaskView firstTask = getRunningTaskView();
1245         if (firstTask != null) {
1246             firstTask.setIconScaleAndDim(mRunningTaskIconScaledDown ? 0 : 1);
1247         }
1248     }
1249 
animateActionsViewIn()1250     private void animateActionsViewIn() {
1251         mActionsView.updateHiddenFlags(HIDDEN_GESTURE_RUNNING, false);
1252         ObjectAnimator anim = ObjectAnimator.ofFloat(
1253                 mActionsView.getVisibilityAlpha(), MultiValueAlpha.VALUE, 0, 1);
1254         anim.setDuration(TaskView.SCALE_ICON_DURATION);
1255         anim.start();
1256     }
1257 
animateUpRunningTaskIconScale()1258     public void animateUpRunningTaskIconScale() {
1259         animateUpRunningTaskIconScale(0);
1260     }
1261 
animateUpRunningTaskIconScale(float startProgress)1262     public void animateUpRunningTaskIconScale(float startProgress) {
1263         mRunningTaskIconScaledDown = false;
1264         TaskView firstTask = getRunningTaskView();
1265         if (firstTask != null) {
1266             firstTask.animateIconScaleAndDimIntoView();
1267             firstTask.setIconScaleAnimStartProgress(startProgress);
1268         }
1269     }
1270 
enableLayoutTransitions()1271     private void enableLayoutTransitions() {
1272         if (mLayoutTransition == null) {
1273             mLayoutTransition = new LayoutTransition();
1274             mLayoutTransition.enableTransitionType(LayoutTransition.APPEARING);
1275             mLayoutTransition.setDuration(ADDITION_TASK_DURATION);
1276             mLayoutTransition.setStartDelay(LayoutTransition.APPEARING, 0);
1277 
1278             mLayoutTransition.addTransitionListener(new TransitionListener() {
1279                 @Override
1280                 public void startTransition(LayoutTransition transition, ViewGroup viewGroup,
1281                     View view, int i) {
1282                 }
1283 
1284                 @Override
1285                 public void endTransition(LayoutTransition transition, ViewGroup viewGroup,
1286                     View view, int i) {
1287                     // When the unpinned task is added, snap to first page and disable transitions
1288                     if (view instanceof TaskView) {
1289                         snapToPage(0);
1290                         disableLayoutTransitions();
1291                     }
1292 
1293                 }
1294             });
1295         }
1296         setLayoutTransition(mLayoutTransition);
1297     }
1298 
disableLayoutTransitions()1299     private void disableLayoutTransitions() {
1300         setLayoutTransition(null);
1301     }
1302 
setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp)1303     public void setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp) {
1304         mSwipeDownShouldLaunchApp = swipeDownShouldLaunchApp;
1305     }
1306 
shouldSwipeDownLaunchApp()1307     public boolean shouldSwipeDownLaunchApp() {
1308         return mSwipeDownShouldLaunchApp;
1309     }
1310 
1311     public interface PageCallbacks {
1312 
1313         /**
1314          * Updates the page UI based on scroll params.
1315          */
onPageScroll(ScrollState scrollState)1316         default void onPageScroll(ScrollState scrollState) {}
1317     }
1318 
1319     public static class ScrollState extends CurveProperties {
1320 
1321         /**
1322          * The progress from 0 to 1, where 0 is the center
1323          * of the screen and 1 is the edge of the screen.
1324          */
1325         public float linearInterpolation;
1326 
1327         /**
1328          * The amount by which all the content is scrolled relative to the end of the list.
1329          */
1330         public float scrollFromEdge;
1331 
1332         /**
1333          * Updates linearInterpolation for the provided child position
1334          */
updateInterpolation(float childStart, int pageSpacing)1335         public void updateInterpolation(float childStart, int pageSpacing) {
1336             float pageCenter = childStart + halfPageSize;
1337             float distanceFromScreenCenter = screenCenter - pageCenter;
1338             float distanceToReachEdge = halfScreenSize + halfPageSize + pageSpacing;
1339             linearInterpolation = Math.min(1,
1340                     Math.abs(distanceFromScreenCenter) / distanceToReachEdge);
1341         }
1342     }
1343 
setIgnoreResetTask(int taskId)1344     public void setIgnoreResetTask(int taskId) {
1345         mIgnoreResetTaskId = taskId;
1346     }
1347 
clearIgnoreResetTask(int taskId)1348     public void clearIgnoreResetTask(int taskId) {
1349         if (mIgnoreResetTaskId == taskId) {
1350             mIgnoreResetTaskId = -1;
1351         }
1352     }
1353 
addDismissedTaskAnimations(View taskView, long duration, PendingAnimation anim)1354     private void addDismissedTaskAnimations(View taskView, long duration, PendingAnimation anim) {
1355         // Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's
1356         // alpha is set to 0 so that it can be recycled in the view pool properly
1357         anim.setFloat(taskView, VIEW_ALPHA, 0, ACCEL_2);
1358         FloatProperty<View> secondaryViewTranslate =
1359             mOrientationHandler.getSecondaryViewTranslate();
1360         int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
1361         int verticalFactor = mOrientationHandler.getTaskDismissDirectionFactor();
1362 
1363         ResourceProvider rp = DynamicResource.provider(mActivity);
1364         SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_START)
1365                 .setDampingRatio(rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio))
1366                 .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness));
1367 
1368         anim.add(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate,
1369                 verticalFactor * secondaryTaskDimension).setDuration(duration), LINEAR, sp);
1370     }
1371 
removeTask(TaskView taskView, int index, EndState endState)1372     private void removeTask(TaskView taskView, int index, EndState endState) {
1373         if (taskView.getTask() != null) {
1374             ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
1375             ComponentKey compKey = TaskUtils.getLaunchComponentKeyForTask(taskView.getTask().key);
1376             mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
1377                     endState.logAction, Direction.UP, index, compKey);
1378             mActivity.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo())
1379                     .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
1380         }
1381     }
1382 
createTaskDismissAnimation(TaskView taskView, boolean animateTaskView, boolean shouldRemoveTask, long duration)1383     public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView,
1384             boolean shouldRemoveTask, long duration) {
1385         if (mPendingAnimation != null) {
1386             mPendingAnimation.finish(false, Touch.SWIPE);
1387         }
1388         PendingAnimation anim = new PendingAnimation(duration);
1389 
1390         int count = getPageCount();
1391         if (count == 0) {
1392             return anim;
1393         }
1394 
1395         int[] oldScroll = new int[count];
1396         int[] newScroll = new int[count];
1397         getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
1398         getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView);
1399         int taskCount = getTaskViewCount();
1400         int scrollDiffPerPage = 0;
1401         if (count > 1) {
1402             scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]);
1403         }
1404         int draggedIndex = indexOfChild(taskView);
1405 
1406         boolean needsCurveUpdates = false;
1407         for (int i = 0; i < count; i++) {
1408             View child = getChildAt(i);
1409             if (child == taskView) {
1410                 if (animateTaskView) {
1411                     addDismissedTaskAnimations(taskView, duration, anim);
1412                 }
1413             } else {
1414                 // If we just take newScroll - oldScroll, everything to the right of dragged task
1415                 // translates to the left. We need to offset this in some cases:
1416                 // - In RTL, add page offset to all pages, since we want pages to move to the right
1417                 // Additionally, add a page offset if:
1418                 // - Current page is rightmost page (leftmost for RTL)
1419                 // - Dragging an adjacent page on the left side (right side for RTL)
1420                 int offset = mIsRtl ? scrollDiffPerPage : 0;
1421                 if (mCurrentPage == draggedIndex) {
1422                     int lastPage = taskCount - 1;
1423                     if (mCurrentPage == lastPage) {
1424                         offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
1425                     }
1426                 } else {
1427                     // Dragging an adjacent page.
1428                     int negativeAdjacent = mCurrentPage - 1; // (Right in RTL, left in LTR)
1429                     if (draggedIndex == negativeAdjacent) {
1430                         offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
1431                     }
1432                 }
1433                 int scrollDiff = newScroll[i] - oldScroll[i] + offset;
1434                 if (scrollDiff != 0) {
1435                     Property translationProperty = mOrientationHandler.getPrimaryViewTranslate();
1436 
1437                     ResourceProvider rp = DynamicResource.provider(mActivity);
1438                     SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_END)
1439                             .setDampingRatio(
1440                                     rp.getFloat(R.dimen.dismiss_task_trans_x_damping_ratio))
1441                             .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_x_stiffness));
1442                     anim.add(ObjectAnimator.ofFloat(child, translationProperty, scrollDiff)
1443                             .setDuration(duration), ACCEL, sp);
1444                     needsCurveUpdates = true;
1445                 }
1446             }
1447         }
1448 
1449         if (needsCurveUpdates) {
1450             anim.addOnFrameCallback(this::updateCurveProperties);
1451         }
1452 
1453         // Add a tiny bit of translation Z, so that it draws on top of other views
1454         if (animateTaskView) {
1455             taskView.setTranslationZ(0.1f);
1456         }
1457 
1458         mPendingAnimation = anim;
1459         mPendingAnimation.addEndListener(new Consumer<EndState>() {
1460             @Override
1461             public void accept(EndState endState) {
1462                 if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
1463                         taskView.isRunningTask() && endState.isSuccess) {
1464                     finishRecentsAnimation(true /* toHome */, () -> onEnd(endState));
1465                 } else {
1466                     onEnd(endState);
1467                 }
1468             }
1469 
1470             @SuppressWarnings("WrongCall")
1471             private void onEnd(EndState endState) {
1472                 if (endState.isSuccess) {
1473                     if (shouldRemoveTask) {
1474                         removeTask(taskView, draggedIndex, endState);
1475                     }
1476 
1477                     int pageToSnapTo = mCurrentPage;
1478                     if (draggedIndex < pageToSnapTo ||
1479                             pageToSnapTo == (getTaskViewCount() - 1)) {
1480                         pageToSnapTo -= 1;
1481                     }
1482                     removeViewInLayout(taskView);
1483 
1484                     if (getTaskViewCount() == 0) {
1485                         removeViewInLayout(mClearAllButton);
1486                         startHome();
1487                     } else {
1488                         snapToPageImmediately(pageToSnapTo);
1489                     }
1490                     // Update the layout synchronously so that the position of next view is
1491                     // immediately available.
1492                     onLayout(false /*  changed */, getLeft(), getTop(), getRight(), getBottom());
1493                 }
1494                 resetTaskVisuals();
1495                 mPendingAnimation = null;
1496             }
1497         });
1498         return anim;
1499     }
1500 
createAllTasksDismissAnimation(long duration)1501     public PendingAnimation createAllTasksDismissAnimation(long duration) {
1502         if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
1503             throw new IllegalStateException("Another pending animation is still running");
1504         }
1505         PendingAnimation anim = new PendingAnimation(duration);
1506 
1507         int count = getTaskViewCount();
1508         for (int i = 0; i < count; i++) {
1509             addDismissedTaskAnimations(getTaskViewAt(i), duration, anim);
1510         }
1511 
1512         mPendingAnimation = anim;
1513         mPendingAnimation.addEndListener((endState) -> {
1514             if (endState.isSuccess) {
1515                 // Remove all the task views now
1516                 ActivityManagerWrapper.getInstance().removeAllRecentTasks();
1517                 removeTasksViewsAndClearAllButton();
1518                 startHome();
1519             }
1520             mPendingAnimation = null;
1521         });
1522         return anim;
1523     }
1524 
snapToPageRelative(int pageCount, int delta, boolean cycle)1525     private boolean snapToPageRelative(int pageCount, int delta, boolean cycle) {
1526         if (pageCount == 0) {
1527             return false;
1528         }
1529         final int newPageUnbound = getNextPage() + delta;
1530         if (!cycle && (newPageUnbound < 0 || newPageUnbound >= pageCount)) {
1531             return false;
1532         }
1533         snapToPage((newPageUnbound + pageCount) % pageCount);
1534         getChildAt(getNextPage()).requestFocus();
1535         return true;
1536     }
1537 
runDismissAnimation(PendingAnimation pendingAnim)1538     protected void runDismissAnimation(PendingAnimation pendingAnim) {
1539         AnimatorPlaybackController controller = pendingAnim.createPlaybackController();
1540         controller.dispatchOnStart();
1541         controller.setEndAction(() -> pendingAnim.finish(true, Touch.SWIPE));
1542         controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN);
1543         controller.start();
1544     }
1545 
dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask)1546     public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) {
1547         runDismissAnimation(createTaskDismissAnimation(taskView, animateTaskView, removeTask,
1548                 DISMISS_TASK_DURATION));
1549     }
1550 
1551     @SuppressWarnings("unused")
dismissAllTasks(View view)1552     private void dismissAllTasks(View view) {
1553         runDismissAnimation(createAllTasksDismissAnimation(DISMISS_TASK_DURATION));
1554         mActivity.getUserEventDispatcher().logActionOnControl(TAP, CLEAR_ALL_BUTTON);
1555     }
1556 
dismissCurrentTask()1557     private void dismissCurrentTask() {
1558         TaskView taskView = getNextPageTaskView();
1559         if (taskView != null) {
1560             dismissTask(taskView, true /*animateTaskView*/, true /*removeTask*/);
1561         }
1562     }
1563 
1564     @Override
dispatchKeyEvent(KeyEvent event)1565     public boolean dispatchKeyEvent(KeyEvent event) {
1566         if (event.getAction() == KeyEvent.ACTION_DOWN) {
1567             switch (event.getKeyCode()) {
1568                 case KeyEvent.KEYCODE_TAB:
1569                     return snapToPageRelative(getTaskViewCount(), event.isShiftPressed() ? -1 : 1,
1570                             event.isAltPressed() /* cycle */);
1571                 case KeyEvent.KEYCODE_DPAD_RIGHT:
1572                     return snapToPageRelative(getPageCount(), mIsRtl ? -1 : 1, false /* cycle */);
1573                 case KeyEvent.KEYCODE_DPAD_LEFT:
1574                     return snapToPageRelative(getPageCount(), mIsRtl ? 1 : -1, false /* cycle */);
1575                 case KeyEvent.KEYCODE_DEL:
1576                 case KeyEvent.KEYCODE_FORWARD_DEL:
1577                     dismissCurrentTask();
1578                     return true;
1579                 case KeyEvent.KEYCODE_NUMPAD_DOT:
1580                     if (event.isAltPressed()) {
1581                         // Numpad DEL pressed while holding Alt.
1582                         dismissCurrentTask();
1583                         return true;
1584                     }
1585             }
1586         }
1587         return super.dispatchKeyEvent(event);
1588     }
1589 
1590     @Override
onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect)1591     protected void onFocusChanged(boolean gainFocus, int direction,
1592             @Nullable Rect previouslyFocusedRect) {
1593         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1594         if (gainFocus && getChildCount() > 0) {
1595             switch (direction) {
1596                 case FOCUS_FORWARD:
1597                     setCurrentPage(0);
1598                     break;
1599                 case FOCUS_BACKWARD:
1600                 case FOCUS_RIGHT:
1601                 case FOCUS_LEFT:
1602                     setCurrentPage(getChildCount() - 1);
1603                     break;
1604             }
1605         }
1606     }
1607 
getContentAlpha()1608     public float getContentAlpha() {
1609         return mContentAlpha;
1610     }
1611 
setContentAlpha(float alpha)1612     public void setContentAlpha(float alpha) {
1613         if (alpha == mContentAlpha) {
1614             return;
1615         }
1616         alpha = Utilities.boundToRange(alpha, 0, 1);
1617         mContentAlpha = alpha;
1618         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
1619             TaskView child = getTaskViewAt(i);
1620             if (!mRunningTaskTileHidden || child.getTask().key.id != mRunningTaskId) {
1621                 child.setStableAlpha(alpha);
1622             }
1623         }
1624         mClearAllButton.setContentAlpha(mContentAlpha);
1625         int alphaInt = Math.round(alpha * 255);
1626         mEmptyMessagePaint.setAlpha(alphaInt);
1627         mEmptyIcon.setAlpha(alphaInt);
1628         mActionsView.getContentAlpha().setValue(mContentAlpha);
1629 
1630         if (alpha > 0) {
1631             setVisibility(VISIBLE);
1632         } else if (!mFreezeViewVisibility) {
1633             setVisibility(GONE);
1634         }
1635     }
1636 
1637     /**
1638      * Freezes the view visibility change. When frozen, the view will not change its visibility
1639      * to gone due to alpha changes.
1640      */
setFreezeViewVisibility(boolean freezeViewVisibility)1641     public void setFreezeViewVisibility(boolean freezeViewVisibility) {
1642         if (mFreezeViewVisibility != freezeViewVisibility) {
1643             mFreezeViewVisibility = freezeViewVisibility;
1644             if (!mFreezeViewVisibility) {
1645                 setVisibility(mContentAlpha > 0 ? VISIBLE : GONE);
1646             }
1647         }
1648     }
1649 
1650     @Override
setVisibility(int visibility)1651     public void setVisibility(int visibility) {
1652         super.setVisibility(visibility);
1653         if (mActionsView != null) {
1654             mActionsView.updateHiddenFlags(HIDDEN_NO_RECENTS, visibility != VISIBLE);
1655         }
1656     }
1657 
1658     @Override
onConfigurationChanged(Configuration newConfig)1659     protected void onConfigurationChanged(Configuration newConfig) {
1660         super.onConfigurationChanged(newConfig);
1661         if (mOrientationState.setActivityConfiguration(newConfig)) {
1662             updateOrientationHandler();
1663         }
1664     }
1665 
setLayoutRotation(int touchRotation, int displayRotation)1666     public void setLayoutRotation(int touchRotation, int displayRotation) {
1667         if (mOrientationState.update(touchRotation, displayRotation)) {
1668             updateOrientationHandler();
1669         }
1670     }
1671 
updateOrientationHandler()1672     private void updateOrientationHandler() {
1673         mOrientationHandler = mOrientationState.getOrientationHandler();
1674         mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
1675         setLayoutDirection(mIsRtl
1676                 ? View.LAYOUT_DIRECTION_RTL
1677                 : View.LAYOUT_DIRECTION_LTR);
1678         mClearAllButton.setLayoutDirection(mIsRtl
1679                 ? View.LAYOUT_DIRECTION_LTR
1680                 : View.LAYOUT_DIRECTION_RTL);
1681         mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated());
1682         mActivity.getDragLayer().recreateControllers();
1683         boolean isInLandscape = mOrientationState.getTouchRotation() != 0
1684                 || mOrientationState.getRecentsActivityRotation() != ROTATION_0;
1685         mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION,
1686                 !mOrientationState.canRecentsActivityRotate() && isInLandscape);
1687         resetPaddingFromTaskSize();
1688         requestLayout();
1689         // Reapply the current page to update page scrolls.
1690         setCurrentPage(mCurrentPage);
1691     }
1692 
getPagedViewOrientedState()1693     public RecentsOrientedState getPagedViewOrientedState() {
1694         return mOrientationState;
1695     }
1696 
getPagedOrientationHandler()1697     public PagedOrientationHandler getPagedOrientationHandler() {
1698         return mOrientationHandler;
1699     }
1700 
1701     @Nullable
getNextTaskView()1702     public TaskView getNextTaskView() {
1703         return getTaskViewAtByAbsoluteIndex(getRunningTaskIndex() + 1);
1704     }
1705 
1706     @Nullable
getCurrentPageTaskView()1707     public TaskView getCurrentPageTaskView() {
1708         return getTaskViewAtByAbsoluteIndex(getCurrentPage());
1709     }
1710 
1711     @Nullable
getNextPageTaskView()1712     public TaskView getNextPageTaskView() {
1713         return getTaskViewAtByAbsoluteIndex(getNextPage());
1714     }
1715 
1716     @Nullable
getTaskViewNearestToCenterOfScreen()1717     public TaskView getTaskViewNearestToCenterOfScreen() {
1718         return getTaskViewAtByAbsoluteIndex(getPageNearestToCenterOfScreen());
1719     }
1720 
1721     /**
1722      * Returns null instead of indexOutOfBoundsError when index is not in range
1723      */
1724     @Nullable
getTaskViewAt(int index)1725     public TaskView getTaskViewAt(int index) {
1726         return getTaskViewAtByAbsoluteIndex(index + mTaskViewStartIndex);
1727     }
1728 
1729     @Nullable
getTaskViewAtByAbsoluteIndex(int index)1730     private TaskView getTaskViewAtByAbsoluteIndex(int index) {
1731         if (index < getChildCount() && index >= 0) {
1732             View child = getChildAt(index);
1733             return child instanceof TaskView ? (TaskView) child : null;
1734         }
1735         return null;
1736     }
1737 
setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener)1738     public void setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener) {
1739         mOnEmptyMessageUpdatedListener = listener;
1740     }
1741 
updateEmptyMessage()1742     public void updateEmptyMessage() {
1743         boolean isEmpty = getTaskViewCount() == 0;
1744         boolean hasSizeChanged = mLastMeasureSize.x != getWidth()
1745                 || mLastMeasureSize.y != getHeight();
1746         if (isEmpty == mShowEmptyMessage && !hasSizeChanged) {
1747             return;
1748         }
1749         setContentDescription(isEmpty ? mEmptyMessage : "");
1750         mShowEmptyMessage = isEmpty;
1751         updateEmptyStateUi(hasSizeChanged);
1752         invalidate();
1753 
1754         if (mOnEmptyMessageUpdatedListener != null) {
1755             mOnEmptyMessageUpdatedListener.onEmptyMessageUpdated(mShowEmptyMessage);
1756         }
1757     }
1758 
1759     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)1760     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1761         super.onLayout(changed, left, top, right, bottom);
1762 
1763         updateEmptyStateUi(changed);
1764 
1765         // Update the pivots such that when the task is scaled, it fills the full page
1766         getTaskSize(mTempRect);
1767         getPagedViewOrientedState().getFullScreenScaleAndPivot(
1768                 mTempRect, mActivity.getDeviceProfile(), mTempPointF);
1769         setPivotX(mTempPointF.x);
1770         setPivotY(mTempPointF.y);
1771         setTaskModalness(mTaskModalness);
1772         updatePageOffsets();
1773         setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO
1774                 : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
1775     }
1776 
updatePageOffsets()1777     private void updatePageOffsets() {
1778         float offset = mAdjacentPageOffset * getWidth();
1779         float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness) * getWidth();
1780         if (mIsRtl) {
1781             offset = -offset;
1782             modalOffset = -modalOffset;
1783         }
1784         int count = getChildCount();
1785 
1786         TaskView runningTask = mRunningTaskId == -1 || !mRunningTaskTileHidden
1787                 ? null : getTaskView(mRunningTaskId);
1788         int midPoint = runningTask == null ? -1 : indexOfChild(runningTask);
1789         int currentPage = getCurrentPage();
1790 
1791         for (int i = 0; i < count; i++) {
1792             float translation = i == midPoint ? 0 : (i < midPoint ? -offset : offset);
1793             float modalTranslation =
1794                     i == currentPage ? 0 : (i < currentPage ? -modalOffset : modalOffset);
1795             getChildAt(i).setTranslationX(translation + modalTranslation);
1796         }
1797         updateCurveProperties();
1798     }
1799 
1800     /**
1801      * TODO: Do not assume motion across X axis for adjacent page
1802      */
getPageOffsetScale()1803     public float getPageOffsetScale() {
1804         return Math.max(getWidth(), 1);
1805     }
1806 
1807     /**
1808      * Resets the visuals when exit modal state.
1809      */
resetModalVisuals()1810     public void resetModalVisuals() {
1811         TaskView taskView = getCurrentPageTaskView();
1812         if (taskView != null) {
1813             taskView.getThumbnail().getTaskOverlay().resetModalVisuals();
1814         }
1815     }
1816 
updateDeadZoneRects()1817     private void updateDeadZoneRects() {
1818         // Get the deadzone rect surrounding the clear all button to not dismiss overview to home
1819         mClearAllButtonDeadZoneRect.setEmpty();
1820         if (mClearAllButton.getWidth() > 0) {
1821             int verticalMargin = getResources()
1822                     .getDimensionPixelSize(R.dimen.recents_clear_all_deadzone_vertical_margin);
1823             mClearAllButton.getHitRect(mClearAllButtonDeadZoneRect);
1824             mClearAllButtonDeadZoneRect.inset(-getPaddingRight() / 2, -verticalMargin);
1825         }
1826 
1827         // Get the deadzone rect between the task views
1828         mTaskViewDeadZoneRect.setEmpty();
1829         int count = getTaskViewCount();
1830         if (count > 0) {
1831             final View taskView = getTaskViewAt(0);
1832             getTaskViewAt(count - 1).getHitRect(mTaskViewDeadZoneRect);
1833             mTaskViewDeadZoneRect.union(taskView.getLeft(), taskView.getTop(), taskView.getRight(),
1834                     taskView.getBottom());
1835         }
1836     }
1837 
updateEmptyStateUi(boolean sizeChanged)1838     private void updateEmptyStateUi(boolean sizeChanged) {
1839         boolean hasValidSize = getWidth() > 0 && getHeight() > 0;
1840         if (sizeChanged && hasValidSize) {
1841             mEmptyTextLayout = null;
1842             mLastMeasureSize.set(getWidth(), getHeight());
1843         }
1844 
1845         if (mShowEmptyMessage && hasValidSize && mEmptyTextLayout == null) {
1846             int availableWidth = mLastMeasureSize.x - mEmptyMessagePadding - mEmptyMessagePadding;
1847             mEmptyTextLayout = StaticLayout.Builder.obtain(mEmptyMessage, 0, mEmptyMessage.length(),
1848                     mEmptyMessagePaint, availableWidth)
1849                     .setAlignment(Layout.Alignment.ALIGN_CENTER)
1850                     .build();
1851             int totalHeight = mEmptyTextLayout.getHeight()
1852                     + mEmptyMessagePadding + mEmptyIcon.getIntrinsicHeight();
1853 
1854             int top = (mLastMeasureSize.y - totalHeight) / 2;
1855             int left = (mLastMeasureSize.x - mEmptyIcon.getIntrinsicWidth()) / 2;
1856             mEmptyIcon.setBounds(left, top, left + mEmptyIcon.getIntrinsicWidth(),
1857                     top + mEmptyIcon.getIntrinsicHeight());
1858         }
1859     }
1860 
1861     @Override
verifyDrawable(Drawable who)1862     protected boolean verifyDrawable(Drawable who) {
1863         return super.verifyDrawable(who) || (mShowEmptyMessage && who == mEmptyIcon);
1864     }
1865 
maybeDrawEmptyMessage(Canvas canvas)1866     protected void maybeDrawEmptyMessage(Canvas canvas) {
1867         if (mShowEmptyMessage && mEmptyTextLayout != null) {
1868             // Offset to center in the visible (non-padded) part of RecentsView
1869             mTempRect.set(mInsets.left + getPaddingLeft(), mInsets.top + getPaddingTop(),
1870                     mInsets.right + getPaddingRight(), mInsets.bottom + getPaddingBottom());
1871             canvas.save();
1872             canvas.translate(getScrollX() + (mTempRect.left - mTempRect.right) / 2,
1873                     (mTempRect.top - mTempRect.bottom) / 2);
1874             mEmptyIcon.draw(canvas);
1875             canvas.translate(mEmptyMessagePadding,
1876                     mEmptyIcon.getBounds().bottom + mEmptyMessagePadding);
1877             mEmptyTextLayout.draw(canvas);
1878             canvas.restore();
1879         }
1880     }
1881 
1882     /**
1883      * Animate adjacent tasks off screen while scaling up.
1884      *
1885      * If launching one of the adjacent tasks, parallax the center task and other adjacent task
1886      * to the right.
1887      */
createAdjacentPageAnimForTaskLaunch(TaskView tv)1888     public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv) {
1889         AnimatorSet anim = new AnimatorSet();
1890 
1891         int taskIndex = indexOfChild(tv);
1892         int centerTaskIndex = getCurrentPage();
1893         boolean launchingCenterTask = taskIndex == centerTaskIndex;
1894 
1895         float toScale = getMaxScaleForFullScreen();
1896         if (launchingCenterTask) {
1897             RecentsView recentsView = tv.getRecentsView();
1898             anim.play(ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, toScale));
1899             anim.play(ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, 1));
1900         } else {
1901             // We are launching an adjacent task, so parallax the center and other adjacent task.
1902             float displacementX = tv.getWidth() * (toScale - tv.getCurveScale());
1903             anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex), TRANSLATION_X,
1904                     mIsRtl ? -displacementX : displacementX));
1905 
1906             int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - taskIndex);
1907             if (otherAdjacentTaskIndex >= 0 && otherAdjacentTaskIndex < getPageCount()) {
1908                 anim.play(new PropertyListBuilder()
1909                         .translationX(mIsRtl ? -displacementX : displacementX)
1910                         .scale(1)
1911                         .build(getPageAt(otherAdjacentTaskIndex)));
1912             }
1913         }
1914         return anim;
1915     }
1916 
1917     /**
1918      * Returns the scale up required on the view, so that it coves the screen completely
1919      */
getMaxScaleForFullScreen()1920     public float getMaxScaleForFullScreen() {
1921         getTaskSize(mTempRect);
1922         return getPagedViewOrientedState().getFullScreenScaleAndPivot(
1923                 mTempRect, mActivity.getDeviceProfile(), mTempPointF);
1924     }
1925 
createTaskLaunchAnimation( TaskView tv, long duration, Interpolator interpolator)1926     public PendingAnimation createTaskLaunchAnimation(
1927             TaskView tv, long duration, Interpolator interpolator) {
1928         if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
1929             throw new IllegalStateException("Another pending animation is still running");
1930         }
1931 
1932         int count = getTaskViewCount();
1933         if (count == 0) {
1934             return new PendingAnimation(duration);
1935         }
1936 
1937         int targetSysUiFlags = tv.getThumbnail().getSysUiStatusNavFlags();
1938         final boolean[] passedOverviewThreshold = new boolean[] {false};
1939         ValueAnimator progressAnim = ValueAnimator.ofFloat(0, 1);
1940         progressAnim.addUpdateListener(animator -> {
1941             // Once we pass a certain threshold, update the sysui flags to match the target
1942             // tasks' flags
1943             mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW,
1944                     animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD
1945                             ? targetSysUiFlags
1946                             : 0);
1947 
1948             onTaskLaunchAnimationUpdate(animator.getAnimatedFraction(), tv);
1949 
1950             // Passing the threshold from taskview to fullscreen app will vibrate
1951             final boolean passed = animator.getAnimatedFraction() >=
1952                     SUCCESS_TRANSITION_PROGRESS;
1953             if (passed != passedOverviewThreshold[0]) {
1954                 passedOverviewThreshold[0] = passed;
1955                 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
1956                         HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
1957             }
1958         });
1959 
1960         AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv);
1961 
1962         DepthController depthController = getDepthController();
1963         if (depthController != null) {
1964             ObjectAnimator depthAnimator = ObjectAnimator.ofFloat(depthController, DEPTH,
1965                     BACKGROUND_APP.getDepth(mActivity));
1966             anim.play(depthAnimator);
1967         }
1968         anim.play(progressAnim);
1969         anim.setInterpolator(interpolator);
1970 
1971         mPendingAnimation = new PendingAnimation(duration);
1972         mPendingAnimation.add(anim);
1973         mPendingAnimation.addEndListener((endState) -> {
1974             if (endState.isSuccess) {
1975                 Consumer<Boolean> onLaunchResult = (result) -> {
1976                     onTaskLaunchAnimationEnd(result);
1977                     if (!result) {
1978                         tv.notifyTaskLaunchFailed(TAG);
1979                     }
1980                 };
1981                 tv.launchTask(false, onLaunchResult, getHandler());
1982                 Task task = tv.getTask();
1983                 if (task != null) {
1984                     mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
1985                             endState.logAction, Direction.DOWN, indexOfChild(tv),
1986                             TaskUtils.getLaunchComponentKeyForTask(task.key));
1987                     mActivity.getStatsLogManager().logger().withItemInfo(tv.getItemInfo())
1988                             .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
1989                 }
1990             } else {
1991                 onTaskLaunchAnimationEnd(false);
1992             }
1993             mPendingAnimation = null;
1994         });
1995         return mPendingAnimation;
1996     }
1997 
onTaskLaunchAnimationUpdate(float progress, TaskView tv)1998     protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) {
1999     }
2000 
shouldUseMultiWindowTaskSizeStrategy()2001     public abstract boolean shouldUseMultiWindowTaskSizeStrategy();
2002 
onTaskLaunchAnimationEnd(boolean success)2003     protected void onTaskLaunchAnimationEnd(boolean success) {
2004         if (success) {
2005             resetTaskVisuals();
2006         }
2007     }
2008 
2009     /**
2010      * Called when task activity is launched
2011      */
onTaskLaunched(Task task)2012     public void onTaskLaunched(Task task){ }
2013 
2014     @Override
notifyPageSwitchListener(int prevPage)2015     protected void notifyPageSwitchListener(int prevPage) {
2016         super.notifyPageSwitchListener(prevPage);
2017         loadVisibleTaskData();
2018         updateEnabledOverlays();
2019     }
2020 
2021     @Override
getCurrentPageDescription()2022     protected String getCurrentPageDescription() {
2023         return "";
2024     }
2025 
2026     @Override
addChildrenForAccessibility(ArrayList<View> outChildren)2027     public void addChildrenForAccessibility(ArrayList<View> outChildren) {
2028         // Add children in reverse order
2029         for (int i = getChildCount() - 1; i >= 0; --i) {
2030             outChildren.add(getChildAt(i));
2031         }
2032     }
2033 
2034     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)2035     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
2036         super.onInitializeAccessibilityNodeInfo(info);
2037         final AccessibilityNodeInfo.CollectionInfo
2038                 collectionInfo = AccessibilityNodeInfo.CollectionInfo.obtain(
2039                 1, getTaskViewCount(), false,
2040                 AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_NONE);
2041         info.setCollectionInfo(collectionInfo);
2042     }
2043 
2044     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)2045     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
2046         super.onInitializeAccessibilityEvent(event);
2047 
2048         final int taskViewCount = getTaskViewCount();
2049         event.setScrollable(taskViewCount > 0);
2050 
2051         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
2052             final int[] visibleTasks = getVisibleChildrenRange();
2053             event.setFromIndex(taskViewCount - visibleTasks[1]);
2054             event.setToIndex(taskViewCount - visibleTasks[0]);
2055             event.setItemCount(taskViewCount);
2056         }
2057     }
2058 
2059     @Override
getAccessibilityClassName()2060     public CharSequence getAccessibilityClassName() {
2061         // To hear position-in-list related feedback from Talkback.
2062         return ListView.class.getName();
2063     }
2064 
2065     @Override
isPageOrderFlipped()2066     protected boolean isPageOrderFlipped() {
2067         return true;
2068     }
2069 
setEnableDrawingLiveTile(boolean enableDrawingLiveTile)2070     public void setEnableDrawingLiveTile(boolean enableDrawingLiveTile) {
2071         mEnableDrawingLiveTile = enableDrawingLiveTile;
2072     }
2073 
redrawLiveTile(boolean mightNeedToRefill)2074     public void redrawLiveTile(boolean mightNeedToRefill) { }
2075 
2076     // TODO: To be removed in a follow up CL
setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController, RecentsAnimationTargets recentsAnimationTargets)2077     public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController,
2078             RecentsAnimationTargets recentsAnimationTargets) {
2079         mRecentsAnimationController = recentsAnimationController;
2080         mRecentsAnimationTargets = recentsAnimationTargets;
2081     }
2082 
setLiveTileOverlayAttached(boolean liveTileOverlayAttached)2083     public void setLiveTileOverlayAttached(boolean liveTileOverlayAttached) {
2084         mLiveTileOverlayAttached = liveTileOverlayAttached;
2085     }
2086 
updateLiveTileIcon(Drawable icon)2087     public void updateLiveTileIcon(Drawable icon) {
2088         if (mLiveTileOverlayAttached) {
2089             LiveTileOverlay.INSTANCE.setIcon(icon);
2090         }
2091     }
2092 
finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete)2093     public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) {
2094         if (mRecentsAnimationController == null) {
2095             if (onFinishComplete != null) {
2096                 onFinishComplete.run();
2097             }
2098             return;
2099         }
2100 
2101         mRecentsAnimationController.finish(toRecents, () -> {
2102             if (onFinishComplete != null) {
2103                 onFinishComplete.run();
2104                 // After we finish the recents animation, the current task id should be correctly
2105                 // reset so that when the task is launched from Overview later, it goes through the
2106                 // flow of starting a new task instead of finishing recents animation to app. A
2107                 // typical example of this is (1) user swipes up from app to Overview (2) user
2108                 // taps on QSB (3) user goes back to Overview and launch the most recent task.
2109                 setCurrentTask(-1);
2110             }
2111         });
2112     }
2113 
setDisallowScrollToClearAll(boolean disallowScrollToClearAll)2114     public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) {
2115         if (mDisallowScrollToClearAll != disallowScrollToClearAll) {
2116             mDisallowScrollToClearAll = disallowScrollToClearAll;
2117             updateMinAndMaxScrollX();
2118         }
2119     }
2120 
2121     @Override
computeMinScroll()2122     protected int computeMinScroll() {
2123         if (getTaskViewCount() > 0) {
2124             if (mDisallowScrollToClearAll) {
2125                 // We aren't showing the clear all button,
2126                 // so use the leftmost task as the min scroll.
2127                 if (mIsRtl) {
2128                     return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
2129                 }
2130                 return getScrollForPage(mTaskViewStartIndex);
2131             }
2132             if (mIsRtl) {
2133                 return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
2134             }
2135             return getScrollForPage(mTaskViewStartIndex);
2136         }
2137         return super.computeMinScroll();
2138     }
2139 
2140     @Override
computeMaxScroll()2141     protected int computeMaxScroll() {
2142         if (getTaskViewCount() > 0) {
2143             if (mDisallowScrollToClearAll) {
2144                 // We aren't showing the clear all button,
2145                 // so use the rightmost task as the min scroll.
2146                 if (mIsRtl) {
2147                     return getScrollForPage(mTaskViewStartIndex);
2148                 }
2149                 return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
2150             }
2151             if (mIsRtl) {
2152                 return getScrollForPage(mTaskViewStartIndex);
2153             }
2154             return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
2155         }
2156         return super.computeMaxScroll();
2157     }
2158 
getClearAllButton()2159     public ClearAllButton getClearAllButton() {
2160         return mClearAllButton;
2161     }
2162 
2163     @Override
onOverscroll(int amount)2164     protected boolean onOverscroll(int amount) {
2165         // overscroll should only be accepted on -1 direction (for clear all button)
2166         if ((amount > 0 && !mIsRtl) || (amount < 0 && mIsRtl)) return false;
2167         return super.onOverscroll(amount);
2168     }
2169 
2170     /**
2171      * @return How many pixels the running task is offset on the currently laid out dominant axis.
2172      */
getScrollOffset()2173     public int getScrollOffset() {
2174         return getScrollOffset(getRunningTaskIndex());
2175     }
2176 
2177     /**
2178      * @return How many pixels the page is offset on the currently laid out dominant axis.
2179      */
getScrollOffset(int pageIndex)2180     public int getScrollOffset(int pageIndex) {
2181         if (pageIndex == -1) {
2182             return 0;
2183         }
2184         return getScrollForPage(pageIndex) - mOrientationHandler.getPrimaryScroll(this);
2185     }
2186 
getEventDispatcher(float navbarRotation)2187     public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
2188         float degreesRotated;
2189         if (navbarRotation == 0) {
2190             degreesRotated = mOrientationHandler.getDegreesRotated();
2191         } else {
2192             degreesRotated = -navbarRotation;
2193         }
2194         if (degreesRotated == 0) {
2195             return super::onTouchEvent;
2196         }
2197 
2198         // At this point the event coordinates have already been transformed, so we need to
2199         // undo that transformation since PagedView also accommodates for the transformation via
2200         // PagedOrientationHandler
2201         return e -> {
2202             if (navbarRotation != 0
2203                     && mOrientationState.isMultipleOrientationSupportedByDevice()
2204                     && !mOrientationState.getOrientationHandler().isLayoutNaturalToLauncher()) {
2205                 mOrientationState.flipVertical(e);
2206                 super.onTouchEvent(e);
2207                 mOrientationState.flipVertical(e);
2208                 return;
2209             }
2210             mOrientationState.transformEvent(-degreesRotated, e, true);
2211             super.onTouchEvent(e);
2212             mOrientationState.transformEvent(-degreesRotated, e, false);
2213         };
2214     }
2215 
2216     public TransformParams getLiveTileParams(
2217             boolean mightNeedToRefill) {
2218         return null;
2219     }
2220 
2221     private void updateEnabledOverlays() {
2222         int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1;
2223         int taskCount = getTaskViewCount();
2224         for (int i = mTaskViewStartIndex; i < mTaskViewStartIndex + taskCount; i++) {
2225             getTaskViewAtByAbsoluteIndex(i).setOverlayEnabled(i == overlayEnabledPage);
2226         }
2227     }
2228 
2229     public void setOverlayEnabled(boolean overlayEnabled) {
2230         if (mOverlayEnabled != overlayEnabled) {
2231             mOverlayEnabled = overlayEnabled;
2232             updateEnabledOverlays();
2233         }
2234     }
2235 
2236     /** If it's in the live tile mode, switch the running task into screenshot mode. */
2237     public void switchToScreenshot(ThumbnailData thumbnailData, Runnable onFinishRunnable) {
2238         TaskView taskView = getRunningTaskView();
2239         if (taskView != null) {
2240             taskView.setShowScreenshot(true);
2241             if (thumbnailData != null) {
2242                 taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData);
2243             } else {
2244                 taskView.getThumbnail().refresh();
2245             }
2246             ViewUtils.postDraw(taskView, onFinishRunnable);
2247         } else {
2248             onFinishRunnable.run();
2249         }
2250     }
2251 
2252     /**
2253      * The current task is fully modal (modalness = 1) when it is shown on its own in a modal
2254      * way. Modalness 0 means the task is shown in context with all the other tasks.
2255      */
2256     private void setTaskModalness(float modalness) {
2257         mTaskModalness = modalness;
2258         updatePageOffsets();
2259         if (getCurrentPageTaskView() != null) {
2260             getCurrentPageTaskView().setModalness(modalness);
2261         }
2262         // Only show actions view when it's modal for in-place landscape mode.
2263         boolean inPlaceLandscape = !mOrientationState.canRecentsActivityRotate()
2264                 && mOrientationState.getTouchRotation() != ROTATION_0;
2265         mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, modalness < 1 && inPlaceLandscape);
2266     }
2267 
2268     @Nullable
2269     protected DepthController getDepthController() {
2270         return null;
2271     }
2272 
2273     @Override
2274     public void onSecondaryWindowBoundsChanged() {
2275         // Invalidate the task view size
2276         setInsets(mInsets);
2277         requestLayout();
2278     }
2279 
2280     /**
2281      * Enables or disables modal state for RecentsView
2282      * @param isModalState
2283      */
2284     public void setModalStateEnabled(boolean isModalState) { }
2285 
2286     public BaseActivityInterface getSizeStrategy() {
2287         return mSizeStrategy;
2288     }
2289 
2290     /**
2291      * Used to register callbacks for when our empty message state changes.
2292      *
2293      * @see #setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener)
2294      * @see #updateEmptyMessage()
2295      */
2296     public interface OnEmptyMessageUpdatedListener {
2297         /** @param isEmpty Whether RecentsView is empty (i.e. has no children) */
2298         void onEmptyMessageUpdated(boolean isEmpty);
2299     }
2300 
2301     private static class PinnedStackAnimationListener<T extends BaseActivity> extends
2302             IPinnedStackAnimationListener.Stub {
2303         private T mActivity;
2304 
2305         public void setActivity(T activity) {
2306             mActivity = activity;
2307         }
2308 
2309         @Override
2310         public void onPinnedStackAnimationStarted() {
2311             // Needed for activities that auto-enter PiP, which will not trigger a remote
2312             // animation to be created
2313             mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
2314         }
2315     }
2316 }
2317