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