• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.quickstep.views;
18 
19 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
20 import static android.view.Display.DEFAULT_DISPLAY;
21 import static android.widget.Toast.LENGTH_SHORT;
22 
23 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
24 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
25 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
26 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
27 import static com.android.launcher3.anim.Interpolators.LINEAR;
28 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
29 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
30 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
31 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
32 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
33 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
34 import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
35 import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
36 
37 import static java.lang.annotation.RetentionPolicy.SOURCE;
38 
39 import android.animation.Animator;
40 import android.animation.AnimatorListenerAdapter;
41 import android.animation.AnimatorSet;
42 import android.animation.ObjectAnimator;
43 import android.annotation.IdRes;
44 import android.app.ActivityOptions;
45 import android.app.ActivityTaskManager;
46 import android.content.Context;
47 import android.content.Intent;
48 import android.graphics.Canvas;
49 import android.graphics.PointF;
50 import android.graphics.Rect;
51 import android.graphics.RectF;
52 import android.graphics.drawable.Drawable;
53 import android.os.Bundle;
54 import android.os.Handler;
55 import android.util.AttributeSet;
56 import android.util.FloatProperty;
57 import android.util.Log;
58 import android.view.Display;
59 import android.view.MotionEvent;
60 import android.view.RemoteAnimationTarget;
61 import android.view.TouchDelegate;
62 import android.view.View;
63 import android.view.ViewGroup;
64 import android.view.accessibility.AccessibilityNodeInfo;
65 import android.view.animation.Interpolator;
66 import android.widget.FrameLayout;
67 import android.widget.Toast;
68 
69 import androidx.annotation.IntDef;
70 import androidx.annotation.NonNull;
71 import androidx.annotation.Nullable;
72 
73 import com.android.launcher3.DeviceProfile;
74 import com.android.launcher3.LauncherSettings;
75 import com.android.launcher3.R;
76 import com.android.launcher3.Utilities;
77 import com.android.launcher3.anim.Interpolators;
78 import com.android.launcher3.config.FeatureFlags;
79 import com.android.launcher3.model.data.WorkspaceItemInfo;
80 import com.android.launcher3.popup.SystemShortcut;
81 import com.android.launcher3.statemanager.StatefulActivity;
82 import com.android.launcher3.testing.TestLogging;
83 import com.android.launcher3.testing.shared.TestProtocol;
84 import com.android.launcher3.touch.PagedOrientationHandler;
85 import com.android.launcher3.util.ActivityOptionsWrapper;
86 import com.android.launcher3.util.ComponentKey;
87 import com.android.launcher3.util.RunnableList;
88 import com.android.launcher3.util.SplitConfigurationOptions;
89 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
90 import com.android.launcher3.util.TransformingTouchDelegate;
91 import com.android.launcher3.util.ViewPool.Reusable;
92 import com.android.quickstep.RecentsModel;
93 import com.android.quickstep.RemoteAnimationTargets;
94 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
95 import com.android.quickstep.TaskIconCache;
96 import com.android.quickstep.TaskOverlayFactory;
97 import com.android.quickstep.TaskThumbnailCache;
98 import com.android.quickstep.TaskUtils;
99 import com.android.quickstep.TaskViewUtils;
100 import com.android.quickstep.util.BorderAnimator;
101 import com.android.quickstep.util.CancellableTask;
102 import com.android.quickstep.util.RecentsOrientedState;
103 import com.android.quickstep.util.SplitSelectStateController;
104 import com.android.quickstep.util.TaskCornerRadius;
105 import com.android.quickstep.util.TransformParams;
106 import com.android.systemui.shared.recents.model.Task;
107 import com.android.systemui.shared.recents.model.ThumbnailData;
108 import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
109 import com.android.systemui.shared.system.ActivityManagerWrapper;
110 import com.android.systemui.shared.system.QuickStepContract;
111 
112 import java.lang.annotation.Retention;
113 import java.util.Arrays;
114 import java.util.Collections;
115 import java.util.HashMap;
116 import java.util.List;
117 import java.util.function.Consumer;
118 import java.util.stream.Stream;
119 
120 /**
121  * A task in the Recents view.
122  */
123 public class TaskView extends FrameLayout implements Reusable {
124 
125     private static final String TAG = TaskView.class.getSimpleName();
126     private static final boolean DEBUG = false;
127 
128     private static final RectF EMPTY_RECT_F = new RectF();
129 
130     public static final int FLAG_UPDATE_ICON = 1;
131     public static final int FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON << 1;
132 
133     public static final int FLAG_UPDATE_ALL = FLAG_UPDATE_ICON | FLAG_UPDATE_THUMBNAIL;
134 
135     /**
136      * Used in conjunction with {@link #onTaskListVisibilityChanged(boolean, int)}, providing more
137      * granularity on which components of this task require an update
138      */
139     @Retention(SOURCE)
140     @IntDef({FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL})
141     public @interface TaskDataChanges {}
142 
143     /**
144      * Type of task view
145      */
146     @Retention(SOURCE)
147     @IntDef({Type.SINGLE, Type.GROUPED, Type.DESKTOP})
148     public @interface Type {
149         int SINGLE = 1;
150         int GROUPED = 2;
151         int DESKTOP = 3;
152     }
153 
154     /** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
155     public static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
156 
157     private static final float EDGE_SCALE_DOWN_FACTOR_CAROUSEL = 0.03f;
158     private static final float EDGE_SCALE_DOWN_FACTOR_GRID = 0.00f;
159 
160     public static final long SCALE_ICON_DURATION = 120;
161     private static final long DIM_ANIM_DURATION = 700;
162 
163     private static final Interpolator GRID_INTERPOLATOR = ACCEL_DEACCEL;
164 
165     /**
166      * This technically can be a vanilla {@link TouchDelegate} class, however that class requires
167      * setting the touch bounds at construction, so we'd repeatedly be created many instances
168      * unnecessarily as scrolling occurs, whereas {@link TransformingTouchDelegate} allows touch
169      * delegated bounds only to be updated.
170      */
171     private TransformingTouchDelegate mIconTouchDelegate;
172 
173     private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
174             Collections.singletonList(new Rect());
175 
176     public static final FloatProperty<TaskView> FOCUS_TRANSITION =
177             new FloatProperty<TaskView>("focusTransition") {
178                 @Override
179                 public void setValue(TaskView taskView, float v) {
180                     taskView.setIconsAndBannersTransitionProgress(v, false /* invert */);
181                 }
182 
183                 @Override
184                 public Float get(TaskView taskView) {
185                     return taskView.mFocusTransitionProgress;
186                 }
187             };
188 
189     private static final FloatProperty<TaskView> SPLIT_SELECT_TRANSLATION_X =
190             new FloatProperty<TaskView>("splitSelectTranslationX") {
191                 @Override
192                 public void setValue(TaskView taskView, float v) {
193                     taskView.setSplitSelectTranslationX(v);
194                 }
195 
196                 @Override
197                 public Float get(TaskView taskView) {
198                     return taskView.mSplitSelectTranslationX;
199                 }
200             };
201 
202     private static final FloatProperty<TaskView> SPLIT_SELECT_TRANSLATION_Y =
203             new FloatProperty<TaskView>("splitSelectTranslationY") {
204                 @Override
205                 public void setValue(TaskView taskView, float v) {
206                     taskView.setSplitSelectTranslationY(v);
207                 }
208 
209                 @Override
210                 public Float get(TaskView taskView) {
211                     return taskView.mSplitSelectTranslationY;
212                 }
213             };
214 
215     private static final FloatProperty<TaskView> DISMISS_TRANSLATION_X =
216             new FloatProperty<TaskView>("dismissTranslationX") {
217                 @Override
218                 public void setValue(TaskView taskView, float v) {
219                     taskView.setDismissTranslationX(v);
220                 }
221 
222                 @Override
223                 public Float get(TaskView taskView) {
224                     return taskView.mDismissTranslationX;
225                 }
226             };
227 
228     private static final FloatProperty<TaskView> DISMISS_TRANSLATION_Y =
229             new FloatProperty<TaskView>("dismissTranslationY") {
230                 @Override
231                 public void setValue(TaskView taskView, float v) {
232                     taskView.setDismissTranslationY(v);
233                 }
234 
235                 @Override
236                 public Float get(TaskView taskView) {
237                     return taskView.mDismissTranslationY;
238                 }
239             };
240 
241     private static final FloatProperty<TaskView> TASK_OFFSET_TRANSLATION_X =
242             new FloatProperty<TaskView>("taskOffsetTranslationX") {
243                 @Override
244                 public void setValue(TaskView taskView, float v) {
245                     taskView.setTaskOffsetTranslationX(v);
246                 }
247 
248                 @Override
249                 public Float get(TaskView taskView) {
250                     return taskView.mTaskOffsetTranslationX;
251                 }
252             };
253 
254     private static final FloatProperty<TaskView> TASK_OFFSET_TRANSLATION_Y =
255             new FloatProperty<TaskView>("taskOffsetTranslationY") {
256                 @Override
257                 public void setValue(TaskView taskView, float v) {
258                     taskView.setTaskOffsetTranslationY(v);
259                 }
260 
261                 @Override
262                 public Float get(TaskView taskView) {
263                     return taskView.mTaskOffsetTranslationY;
264                 }
265             };
266 
267     private static final FloatProperty<TaskView> TASK_RESISTANCE_TRANSLATION_X =
268             new FloatProperty<TaskView>("taskResistanceTranslationX") {
269                 @Override
270                 public void setValue(TaskView taskView, float v) {
271                     taskView.setTaskResistanceTranslationX(v);
272                 }
273 
274                 @Override
275                 public Float get(TaskView taskView) {
276                     return taskView.mTaskResistanceTranslationX;
277                 }
278             };
279 
280     private static final FloatProperty<TaskView> TASK_RESISTANCE_TRANSLATION_Y =
281             new FloatProperty<TaskView>("taskResistanceTranslationY") {
282                 @Override
283                 public void setValue(TaskView taskView, float v) {
284                     taskView.setTaskResistanceTranslationY(v);
285                 }
286 
287                 @Override
288                 public Float get(TaskView taskView) {
289                     return taskView.mTaskResistanceTranslationY;
290                 }
291             };
292 
293     private static final FloatProperty<TaskView> NON_GRID_TRANSLATION_X =
294             new FloatProperty<TaskView>("nonGridTranslationX") {
295                 @Override
296                 public void setValue(TaskView taskView, float v) {
297                     taskView.setNonGridTranslationX(v);
298                 }
299 
300                 @Override
301                 public Float get(TaskView taskView) {
302                     return taskView.mNonGridTranslationX;
303                 }
304             };
305 
306     private static final FloatProperty<TaskView> NON_GRID_TRANSLATION_Y =
307             new FloatProperty<TaskView>("nonGridTranslationY") {
308                 @Override
309                 public void setValue(TaskView taskView, float v) {
310                     taskView.setNonGridTranslationY(v);
311                 }
312 
313                 @Override
314                 public Float get(TaskView taskView) {
315                     return taskView.mNonGridTranslationY;
316                 }
317             };
318 
319     public static final FloatProperty<TaskView> GRID_END_TRANSLATION_X =
320             new FloatProperty<TaskView>("gridEndTranslationX") {
321                 @Override
322                 public void setValue(TaskView taskView, float v) {
323                     taskView.setGridEndTranslationX(v);
324                 }
325 
326                 @Override
327                 public Float get(TaskView taskView) {
328                     return taskView.mGridEndTranslationX;
329                 }
330             };
331 
332     public static final FloatProperty<TaskView> SNAPSHOT_SCALE =
333             new FloatProperty<TaskView>("snapshotScale") {
334                 @Override
335                 public void setValue(TaskView taskView, float v) {
336                     taskView.setSnapshotScale(v);
337                 }
338 
339                 @Override
340                 public Float get(TaskView taskView) {
341                     return taskView.mSnapshotView.getScaleX();
342                 }
343             };
344 
345     @Nullable
346     protected Task mTask;
347     protected TaskThumbnailView mSnapshotView;
348     protected IconView mIconView;
349     protected final DigitalWellBeingToast mDigitalWellBeingToast;
350     protected float mFullscreenProgress;
351     private float mGridProgress;
352     protected float mTaskThumbnailSplashAlpha;
353     private float mNonGridScale = 1;
354     private float mDismissScale = 1;
355     protected final FullscreenDrawParams mCurrentFullscreenParams;
356     protected final StatefulActivity mActivity;
357 
358     // Various causes of changing primary translation, which we aggregate to setTranslationX/Y().
359     private float mDismissTranslationX;
360     private float mDismissTranslationY;
361     private float mTaskOffsetTranslationX;
362     private float mTaskOffsetTranslationY;
363     private float mTaskResistanceTranslationX;
364     private float mTaskResistanceTranslationY;
365     // The following translation variables should only be used in the same orientation as Launcher.
366     private float mBoxTranslationY;
367     // The following grid translations scales with mGridProgress.
368     private float mGridTranslationX;
369     private float mGridTranslationY;
370     // The following grid translation is used to animate closing the gap between grid and clear all.
371     private float mGridEndTranslationX;
372     // Applied as a complement to gridTranslation, for adjusting the carousel overview and quick
373     // switch.
374     private float mNonGridTranslationX;
375     private float mNonGridTranslationY;
376     // Used when in SplitScreenSelectState
377     private float mSplitSelectTranslationY;
378     private float mSplitSelectTranslationX;
379 
380     @Nullable
381     private ObjectAnimator mIconAndDimAnimator;
382     private float mIconScaleAnimStartProgress = 0;
383     private float mFocusTransitionProgress = 1;
384     private float mModalness = 0;
385     private float mStableAlpha = 1;
386 
387     private int mTaskViewId = -1;
388     /**
389      * Index 0 will contain taskID of left/top task, index 1 will contain taskId of bottom/right
390      */
391     protected int[] mTaskIdContainer = new int[]{-1, -1};
392     protected TaskIdAttributeContainer[] mTaskIdAttributeContainer =
393             new TaskIdAttributeContainer[2];
394 
395     private boolean mShowScreenshot;
396 
397     // The current background requests to load the task thumbnail and icon
398     @Nullable
399     private CancellableTask mThumbnailLoadRequest;
400     @Nullable
401     private CancellableTask mIconLoadRequest;
402 
403     private boolean mEndQuickswitchCuj;
404 
405     private final float[] mIconCenterCoords = new float[2];
406 
407     protected final PointF mLastTouchDownPosition = new PointF();
408 
409     private boolean mIsClickableAsLiveTile = true;
410 
411     @Nullable private final BorderAnimator mBorderAnimator;
412 
TaskView(Context context)413     public TaskView(Context context) {
414         this(context, null);
415     }
416 
TaskView(Context context, @Nullable AttributeSet attrs)417     public TaskView(Context context, @Nullable AttributeSet attrs) {
418         this(context, attrs, 0);
419     }
420 
TaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)421     public TaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
422         this(context, attrs, defStyleAttr, 0);
423     }
424 
TaskView( Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)425     public TaskView(
426             Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
427         super(context, attrs, defStyleAttr, defStyleRes);
428         mActivity = StatefulActivity.fromContext(context);
429         setOnClickListener(this::onClick);
430 
431         mCurrentFullscreenParams = new FullscreenDrawParams(context);
432         mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
433 
434         boolean keyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
435                 || DesktopTaskView.DESKTOP_MODE_SUPPORTED;
436 
437         setWillNotDraw(!keyboardFocusHighlightEnabled);
438 
439         mBorderAnimator = !keyboardFocusHighlightEnabled
440                 ? null
441                 : new BorderAnimator(
442                         /* borderBoundsBuilder= */ this::updateBorderBounds,
443                         /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
444                                 R.dimen.keyboard_quick_switch_border_width),
445                         /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
446                         /* borderColor= */ attrs == null
447                         ? DEFAULT_BORDER_COLOR
448                         : context.getTheme()
449                                 .obtainStyledAttributes(
450                                         attrs,
451                                         R.styleable.TaskView,
452                                         defStyleAttr,
453                                         defStyleRes)
454                                 .getColor(
455                                         R.styleable.TaskView_borderColor,
456                                         DEFAULT_BORDER_COLOR),
457                         /* invalidateViewCallback= */ TaskView.this::invalidate);
458     }
459 
updateBorderBounds(Rect bounds)460     protected void updateBorderBounds(Rect bounds) {
461         bounds.set(mSnapshotView.getLeft() + Math.round(mSnapshotView.getTranslationX()),
462                 mSnapshotView.getTop() + Math.round(mSnapshotView.getTranslationY()),
463                 mSnapshotView.getRight() + Math.round(mSnapshotView.getTranslationX()),
464                 mSnapshotView.getBottom() + Math.round(mSnapshotView.getTranslationY()));
465     }
466 
setTaskViewId(int id)467     public void setTaskViewId(int id) {
468         this.mTaskViewId = id;
469     }
470 
getTaskViewId()471     public int getTaskViewId() {
472         return mTaskViewId;
473     }
474 
475     /**
476      * Builds proto for logging
477      */
getItemInfo()478     public WorkspaceItemInfo getItemInfo() {
479         return getItemInfo(mTask);
480     }
481 
getItemInfo(@ullable Task task)482     protected WorkspaceItemInfo getItemInfo(@Nullable Task task) {
483         WorkspaceItemInfo stubInfo = new WorkspaceItemInfo();
484         stubInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK;
485         stubInfo.container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
486         if (task == null) {
487             return stubInfo;
488         }
489 
490         ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key);
491         stubInfo.user = componentKey.user;
492         stubInfo.intent = new Intent().setComponent(componentKey.componentName);
493         stubInfo.title = task.title;
494         if (getRecentsView() != null) {
495             stubInfo.screenId = getRecentsView().indexOfChild(this);
496         }
497         return stubInfo;
498     }
499 
500     @Override
onFinishInflate()501     protected void onFinishInflate() {
502         super.onFinishInflate();
503         mSnapshotView = findViewById(R.id.snapshot);
504         mIconView = findViewById(R.id.icon);
505         mIconTouchDelegate = new TransformingTouchDelegate(mIconView);
506     }
507 
508     @Override
onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)509     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
510         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
511         if (mBorderAnimator != null) {
512             mBorderAnimator.buildAnimator(gainFocus).start();
513         }
514     }
515 
516     @Override
draw(Canvas canvas)517     public void draw(Canvas canvas) {
518         super.draw(canvas);
519         if (mBorderAnimator != null) {
520             mBorderAnimator.drawBorder(canvas);
521         }
522     }
523 
524     /**
525      * Whether the taskview should take the touch event from parent. Events passed to children
526      * that might require special handling.
527      */
offerTouchToChildren(MotionEvent event)528     public boolean offerTouchToChildren(MotionEvent event) {
529         if (event.getAction() == MotionEvent.ACTION_DOWN) {
530             computeAndSetIconTouchDelegate(mIconView, mIconCenterCoords, mIconTouchDelegate);
531         }
532         if (mIconTouchDelegate != null && mIconTouchDelegate.onTouchEvent(event)) {
533             return true;
534         }
535         return false;
536     }
537 
computeAndSetIconTouchDelegate(IconView iconView, float[] tempCenterCoords, TransformingTouchDelegate transformingTouchDelegate)538     protected void computeAndSetIconTouchDelegate(IconView iconView, float[] tempCenterCoords,
539             TransformingTouchDelegate transformingTouchDelegate) {
540         float iconHalfSize = iconView.getWidth() / 2f;
541         tempCenterCoords[0] = tempCenterCoords[1] = iconHalfSize;
542         getDescendantCoordRelativeToAncestor(iconView, mActivity.getDragLayer(), tempCenterCoords,
543                 false);
544         transformingTouchDelegate.setBounds(
545                 (int) (tempCenterCoords[0] - iconHalfSize),
546                 (int) (tempCenterCoords[1] - iconHalfSize),
547                 (int) (tempCenterCoords[0] + iconHalfSize),
548                 (int) (tempCenterCoords[1] + iconHalfSize));
549     }
550 
551     /**
552      * The modalness of this view is how it should be displayed when it is shown on its own in the
553      * modal state of overview.
554      *
555      * @param modalness [0, 1] 0 being in context with other tasks, 1 being shown on its own.
556      */
setModalness(float modalness)557     public void setModalness(float modalness) {
558         if (mModalness == modalness) {
559             return;
560         }
561         mModalness = modalness;
562         mIconView.setAlpha(1 - modalness);
563         mDigitalWellBeingToast.updateBannerOffset(modalness);
564     }
565 
getDigitalWellBeingToast()566     public DigitalWellBeingToast getDigitalWellBeingToast() {
567         return mDigitalWellBeingToast;
568     }
569 
570     /**
571      * Updates this task view to the given {@param task}.
572      *
573      * TODO(b/142282126) Re-evaluate if we need to pass in isMultiWindowMode after
574      *   that issue is fixed
575      */
bind(Task task, RecentsOrientedState orientedState)576     public void bind(Task task, RecentsOrientedState orientedState) {
577         cancelPendingLoadTasks();
578         mTask = task;
579         mTaskIdContainer[0] = mTask.key.id;
580         mTaskIdAttributeContainer[0] = new TaskIdAttributeContainer(task, mSnapshotView,
581                 mIconView, STAGE_POSITION_UNDEFINED);
582         mSnapshotView.bind(task);
583         setOrientationState(orientedState);
584     }
585 
586     /**
587      * Sets up an on-click listener and the visibility for show_windows icon on top of the task.
588      */
setUpShowAllInstancesListener()589     public void setUpShowAllInstancesListener() {
590         String taskPackageName = mTaskIdAttributeContainer[0].mTask.key.getPackageName();
591 
592         // icon of the top/left task
593         View showWindowsView = findViewById(R.id.show_windows);
594         updateFilterCallback(showWindowsView, getFilterUpdateCallback(taskPackageName));
595     }
596 
597     /**
598      * Returns a callback that updates the state of the filter and the recents overview
599      *
600      * @param taskPackageName package name of the task to filter by
601      */
602     @Nullable
getFilterUpdateCallback(String taskPackageName)603     protected View.OnClickListener getFilterUpdateCallback(String taskPackageName) {
604         View.OnClickListener cb = (view) -> {
605             // update and apply a new filter
606             getRecentsView().setAndApplyFilter(taskPackageName);
607         };
608 
609         if (!getRecentsView().getFilterState().shouldShowFilterUI(taskPackageName)) {
610             cb = null;
611         }
612         return cb;
613     }
614 
615     /**
616      * Sets the correct visibility and callback on the provided filterView based on whether
617      * the callback is null or not
618      */
updateFilterCallback(@onNull View filterView, @Nullable View.OnClickListener callback)619     protected void updateFilterCallback(@NonNull View filterView,
620             @Nullable View.OnClickListener callback) {
621         // Filtering changes alpha instead of the visibility since visibility
622         // can be altered separately through RecentsView#resetFromSplitSelectionState()
623         if (callback == null) {
624             filterView.setAlpha(0);
625         } else {
626             filterView.setAlpha(1);
627         }
628 
629         filterView.setOnClickListener(callback);
630     }
631 
getTaskIdAttributeContainers()632     public TaskIdAttributeContainer[] getTaskIdAttributeContainers() {
633         return mTaskIdAttributeContainer;
634     }
635 
636     @Nullable
getTask()637     public Task getTask() {
638         return mTask;
639     }
640 
641     /**
642      * Check if given {@code taskId} is tracked in this view
643      */
containsTaskId(int taskId)644     public boolean containsTaskId(int taskId) {
645         return mTask != null && mTask.key.id == taskId;
646     }
647 
648     /**
649      * @return integer array of two elements to be size consistent with max number of tasks possible
650      *         index 0 will contain the taskId, index 1 will be -1 indicating a null taskID value
651      */
getTaskIds()652     public int[] getTaskIds() {
653         return mTaskIdContainer;
654     }
655 
containsMultipleTasks()656     public boolean containsMultipleTasks() {
657         return mTaskIdContainer[1] != -1;
658     }
659 
660     /**
661      * Returns the TaskIdAttributeContainer corresponding to a given taskId, or null if the TaskView
662      * does not contain a Task with that ID.
663      */
664     @Nullable
getTaskAttributesById(int taskId)665     public TaskIdAttributeContainer getTaskAttributesById(int taskId) {
666         for (TaskIdAttributeContainer attributes : mTaskIdAttributeContainer) {
667             if (attributes.getTask().key.id == taskId) {
668                 return attributes;
669             }
670         }
671 
672         return null;
673     }
674 
getThumbnail()675     public TaskThumbnailView getThumbnail() {
676         return mSnapshotView;
677     }
678 
refreshThumbnails(@ullable HashMap<Integer, ThumbnailData> thumbnailDatas)679     void refreshThumbnails(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas) {
680         if (mTask != null && thumbnailDatas != null) {
681             final ThumbnailData thumbnailData = thumbnailDatas.get(mTask.key.id);
682             if (thumbnailData != null) {
683                 mSnapshotView.setThumbnail(mTask, thumbnailData);
684                 return;
685             }
686         }
687 
688         mSnapshotView.refresh();
689     }
690 
691     /** TODO(b/197033698) Remove all usages of above method and migrate to this one */
getThumbnails()692     public TaskThumbnailView[] getThumbnails() {
693         return new TaskThumbnailView[]{mSnapshotView};
694     }
695 
getIconView()696     public IconView getIconView() {
697         return mIconView;
698     }
699 
700     @Override
dispatchTouchEvent(MotionEvent ev)701     public boolean dispatchTouchEvent(MotionEvent ev) {
702         RecentsView recentsView = getRecentsView();
703         if (recentsView == null || getTask() == null) {
704             return false;
705         }
706         SplitSelectStateController splitSelectStateController =
707                 recentsView.getSplitSelectController();
708         // Disable taps for split selection animation unless we have multiple tasks
709         boolean disableTapsForSplitSelect =
710                 splitSelectStateController.isSplitSelectActive()
711                         && splitSelectStateController.getInitialTaskId() == getTask().key.id
712                         && !containsMultipleTasks();
713         if (disableTapsForSplitSelect) {
714             return false;
715         }
716 
717         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
718             mLastTouchDownPosition.set(ev.getX(), ev.getY());
719         }
720         return super.dispatchTouchEvent(ev);
721     }
722 
723     /**
724      * @return taskId that split selection was initiated with,
725      *         {@link ActivityTaskManager#INVALID_TASK_ID} if no tasks in this TaskView are part of
726      *         split selection
727      */
getThisTaskCurrentlyInSplitSelection()728     protected int getThisTaskCurrentlyInSplitSelection() {
729         SplitSelectStateController splitSelectController =
730                 getRecentsView().getSplitSelectController();
731         int initSplitTaskId = INVALID_TASK_ID;
732         for (TaskIdAttributeContainer container : getTaskIdAttributeContainers()) {
733             int taskId = container.getTask().key.id;
734             if (taskId == splitSelectController.getInitialTaskId()) {
735                 initSplitTaskId = taskId;
736                 break;
737             }
738         }
739         return initSplitTaskId;
740     }
741 
onClick(View view)742     private void onClick(View view) {
743         if (getTask() == null) {
744             return;
745         }
746         if (confirmSecondSplitSelectApp()) {
747             return;
748         }
749         launchTasks();
750         mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
751                 .log(LAUNCHER_TASK_LAUNCH_TAP);
752     }
753 
754     /**
755      * @return {@code true} if user is already in split select mode and this tap was to choose the
756      *         second app. {@code false} otherwise
757      */
confirmSecondSplitSelectApp()758     protected boolean confirmSecondSplitSelectApp() {
759         int index = getLastSelectedChildTaskIndex();
760         TaskIdAttributeContainer container = mTaskIdAttributeContainer[index];
761         if (container != null) {
762             return getRecentsView().confirmSplitSelect(this, container.getTask(),
763                     container.getIconView().getDrawable(), container.getThumbnailView(),
764                     container.getThumbnailView().getThumbnail(), /* intent */ null,
765                     /* user */ null);
766         }
767         return false;
768     }
769 
770     /**
771      * Returns the task index of the last selected child task (0 or 1).
772      * If we contain multiple tasks and this TaskView is used as part of split selection, the
773      * selected child task index will be that of the remaining task.
774      */
getLastSelectedChildTaskIndex()775     protected int getLastSelectedChildTaskIndex() {
776         return 0;
777     }
778 
779     /**
780      * Starts the task associated with this view and animates the startup.
781      * @return CompletionStage to indicate the animation completion or null if the launch failed.
782      */
783     @Nullable
launchTaskAnimated()784     public RunnableList launchTaskAnimated() {
785         if (mTask != null) {
786             TestLogging.recordEvent(
787                     TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
788             ActivityOptionsWrapper opts =  mActivity.getActivityLaunchOptions(this, null);
789             opts.options.setLaunchDisplayId(
790                     getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());
791             if (ActivityManagerWrapper.getInstance()
792                     .startActivityFromRecents(mTask.key, opts.options)) {
793                 RecentsView recentsView = getRecentsView();
794                 if (recentsView.getRunningTaskViewId() != -1) {
795                     recentsView.onTaskLaunchedInLiveTileMode();
796 
797                     // Return a fresh callback in the live tile case, so that it's not accidentally
798                     // triggered by QuickstepTransitionManager.AppLaunchAnimationRunner.
799                     RunnableList callbackList = new RunnableList();
800                     recentsView.addSideTaskLaunchCallback(callbackList);
801                     return callbackList;
802                 }
803                 return opts.onEndCallback;
804             } else {
805                 notifyTaskLaunchFailed(TAG);
806                 return null;
807             }
808         } else {
809             return null;
810         }
811     }
812 
813     /**
814      * Starts the task associated with this view without any animation
815      */
launchTask(@onNull Consumer<Boolean> callback)816     public void launchTask(@NonNull Consumer<Boolean> callback) {
817         launchTask(callback, false /* freezeTaskList */);
818     }
819 
820     /**
821      * Starts the task associated with this view without any animation
822      */
launchTask(@onNull Consumer<Boolean> callback, boolean freezeTaskList)823     public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
824         if (mTask != null) {
825             TestLogging.recordEvent(
826                     TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
827 
828             // Indicate success once the system has indicated that the transition has started
829             ActivityOptions opts = makeCustomAnimation(getContext(), 0, 0,
830                     () -> callback.accept(true), MAIN_EXECUTOR.getHandler());
831             opts.setLaunchDisplayId(
832                     getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());
833             if (freezeTaskList) {
834                 opts.setFreezeRecentTasksReordering();
835             }
836             opts.setDisableStartingWindow(mSnapshotView.shouldShowSplashView());
837             Task.TaskKey key = mTask.key;
838             UI_HELPER_EXECUTOR.execute(() -> {
839                 if (!ActivityManagerWrapper.getInstance().startActivityFromRecents(key, opts)) {
840                     // If the call to start activity failed, then post the result immediately,
841                     // otherwise, wait for the animation start callback from the activity options
842                     // above
843                     MAIN_EXECUTOR.post(() -> {
844                         notifyTaskLaunchFailed(TAG);
845                         callback.accept(false);
846                     });
847                 }
848             });
849         } else {
850             callback.accept(false);
851         }
852     }
853 
854     /**
855      * Returns ActivityOptions for overriding task transition animation.
856      */
makeCustomAnimation(Context context, int enterResId, int exitResId, final Runnable callback, final Handler callbackHandler)857     private ActivityOptions makeCustomAnimation(Context context, int enterResId,
858             int exitResId, final Runnable callback, final Handler callbackHandler) {
859         return ActivityOptions.makeCustomTaskAnimation(context, enterResId, exitResId,
860                 callbackHandler,
861                 elapsedRealTime -> {
862                     if (callback != null) {
863                         callbackHandler.post(callback);
864                     }
865                 }, null /* finishedListener */);
866     }
867 
868     /**
869      * Launch of the current task (both live and inactive tasks) with an animation.
870      */
871     @Nullable
872     public RunnableList launchTasks() {
873         RecentsView recentsView = getRecentsView();
874         RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles;
875         if (isRunningTask() && remoteTargetHandles != null) {
876             if (!mIsClickableAsLiveTile) {
877                 Log.e(TAG, "TaskView is not clickable as a live tile; returning to home.");
878                 return null;
879             }
880 
881             mIsClickableAsLiveTile = false;
882             RemoteAnimationTargets targets;
883             if (remoteTargetHandles.length == 1) {
884                 targets = remoteTargetHandles[0].getTransformParams().getTargetSet();
885             } else {
886                 TransformParams topLeftParams = remoteTargetHandles[0].getTransformParams();
887                 TransformParams rightBottomParams = remoteTargetHandles[1].getTransformParams();
888                 RemoteAnimationTarget[] apps = Stream.concat(
889                         Arrays.stream(topLeftParams.getTargetSet().apps),
890                         Arrays.stream(rightBottomParams.getTargetSet().apps))
891                         .toArray(RemoteAnimationTarget[]::new);
892                 RemoteAnimationTarget[] wallpapers = Stream.concat(
893                         Arrays.stream(topLeftParams.getTargetSet().wallpapers),
894                         Arrays.stream(rightBottomParams.getTargetSet().wallpapers))
895                         .toArray(RemoteAnimationTarget[]::new);
896                 targets = new RemoteAnimationTargets(apps, wallpapers,
897                         topLeftParams.getTargetSet().nonApps,
898                         topLeftParams.getTargetSet().targetMode);
899             }
900             if (targets == null) {
901                 // If the recents animation is cancelled somehow between the parent if block and
902                 // here, try to launch the task as a non live tile task.
903                 RunnableList runnableList = launchTaskAnimated();
904                 if (runnableList == null) {
905                     Log.e(TAG, "Recents animation cancelled and cannot launch task as non-live tile"
906                             + "; returning to home");
907                 }
908                 mIsClickableAsLiveTile = true;
909                 return runnableList;
910             }
911 
912             RunnableList runnableList = new RunnableList();
913             AnimatorSet anim = new AnimatorSet();
914             TaskViewUtils.composeRecentsLaunchAnimator(
915                     anim, this, targets.apps,
916                     targets.wallpapers, targets.nonApps, true /* launcherClosing */,
917                     mActivity.getStateManager(), recentsView,
918                     recentsView.getDepthController());
919             anim.addListener(new AnimatorListenerAdapter() {
920                 @Override
921                 public void onAnimationStart(Animator animation) {
922                     recentsView.runActionOnRemoteHandles(
923                             (Consumer<RemoteTargetHandle>) remoteTargetHandle ->
924                                     remoteTargetHandle
925                                             .getTaskViewSimulator()
926                                             .setDrawsBelowRecents(false));
927                 }
928 
929                 @Override
930                 public void onAnimationEnd(Animator animator) {
931                     if (mTask != null && mTask.key.displayId != getRootViewDisplayId()) {
932                         launchTaskAnimated();
933                     }
934                     mIsClickableAsLiveTile = true;
935                     runEndCallback();
936                 }
937 
938                 @Override
939                 public void onAnimationCancel(Animator animation) {
940                     runEndCallback();
941                 }
942 
943                 private void runEndCallback() {
944                     runnableList.executeAllAndDestroy();
945                 }
946             });
947             anim.start();
948             recentsView.onTaskLaunchedInLiveTileMode();
949             return runnableList;
950         } else {
951             return launchTaskAnimated();
952         }
953     }
954 
955     /**
956      * See {@link TaskDataChanges}
957      * @param visible If this task view will be visible to the user in overview or hidden
958      */
959     public void onTaskListVisibilityChanged(boolean visible) {
960         onTaskListVisibilityChanged(visible, FLAG_UPDATE_ALL);
961     }
962 
963     /**
964      * See {@link TaskDataChanges}
965      * @param visible If this task view will be visible to the user in overview or hidden
966      */
967     public void onTaskListVisibilityChanged(boolean visible, @TaskDataChanges int changes) {
968         if (mTask == null) {
969             return;
970         }
971         cancelPendingLoadTasks();
972         if (visible) {
973             // These calls are no-ops if the data is already loaded, try and load the high
974             // resolution thumbnail if the state permits
975             RecentsModel model = RecentsModel.INSTANCE.get(getContext());
976             TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
977             TaskIconCache iconCache = model.getIconCache();
978 
979             if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
980                 mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(
981                         mTask, thumbnail -> {
982                             mSnapshotView.setThumbnail(mTask, thumbnail);
983                         });
984             }
985             if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
986                 mIconLoadRequest = iconCache.updateIconInBackground(mTask,
987                         (task) -> {
988                             setIcon(mIconView, task.icon);
989                             mDigitalWellBeingToast.initialize(mTask);
990                         });
991             }
992         } else {
993             if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
994                 mSnapshotView.setThumbnail(null, null);
995                 // Reset the task thumbnail reference as well (it will be fetched from the cache or
996                 // reloaded next time we need it)
997                 mTask.thumbnail = null;
998             }
999             if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
1000                 setIcon(mIconView, null);
1001             }
1002         }
1003     }
1004 
1005     protected boolean needsUpdate(@TaskDataChanges int dataChange, @TaskDataChanges int flag) {
1006         return (dataChange & flag) == flag;
1007     }
1008 
1009     protected void cancelPendingLoadTasks() {
1010         if (mThumbnailLoadRequest != null) {
1011             mThumbnailLoadRequest.cancel();
1012             mThumbnailLoadRequest = null;
1013         }
1014         if (mIconLoadRequest != null) {
1015             mIconLoadRequest.cancel();
1016             mIconLoadRequest = null;
1017         }
1018     }
1019 
1020     private boolean showTaskMenu(IconView iconView) {
1021         if (!getRecentsView().canLaunchFullscreenTask()) {
1022             // Don't show menu when selecting second split screen app
1023             return true;
1024         }
1025 
1026         if (!mActivity.getDeviceProfile().isTablet
1027                 && !getRecentsView().isClearAllHidden()) {
1028             getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
1029             return false;
1030         } else {
1031             mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
1032                     .log(LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS);
1033             return showTaskMenuWithContainer(iconView);
1034         }
1035     }
1036 
1037     protected boolean showTaskMenuWithContainer(IconView iconView) {
1038         TaskIdAttributeContainer menuContainer =
1039                 mTaskIdAttributeContainer[iconView == mIconView ? 0 : 1];
1040         DeviceProfile dp = mActivity.getDeviceProfile();
1041         if (dp.isTablet) {
1042             int alignedOptionIndex = 0;
1043             if (getRecentsView().isOnGridBottomRow(menuContainer.getTaskView()) && dp.isLandscape) {
1044                 if (FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) {
1045                     // With no focused task, there is less available space below the tasks, so align
1046                     // the arrow to the third option in the menu.
1047                     alignedOptionIndex = 2;
1048                 } else  {
1049                     // Bottom row of landscape grid aligns arrow to second option to avoid clipping
1050                     alignedOptionIndex = 1;
1051                 }
1052             }
1053             return TaskMenuViewWithArrow.Companion.showForTask(menuContainer, alignedOptionIndex);
1054         } else {
1055             return TaskMenuView.showForTask(menuContainer);
1056         }
1057     }
1058 
1059     protected void setIcon(IconView iconView, @Nullable Drawable icon) {
1060         if (icon != null) {
1061             iconView.setDrawable(icon);
1062             iconView.setOnClickListener(v -> {
1063                 if (confirmSecondSplitSelectApp()) {
1064                     return;
1065                 }
1066                 showTaskMenu(iconView);
1067             });
1068             iconView.setOnLongClickListener(v -> {
1069                 requestDisallowInterceptTouchEvent(true);
1070                 return showTaskMenu(iconView);
1071             });
1072         } else {
1073             iconView.setDrawable(null);
1074             iconView.setOnClickListener(null);
1075             iconView.setOnLongClickListener(null);
1076         }
1077     }
1078 
1079     public void setOrientationState(RecentsOrientedState orientationState) {
1080         setIconOrientation(orientationState);
1081         setThumbnailOrientation(orientationState);
1082     }
1083 
1084     protected void setIconOrientation(RecentsOrientedState orientationState) {
1085         PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
1086         boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
1087         DeviceProfile deviceProfile = mActivity.getDeviceProfile();
1088 
1089         boolean isGridTask = isGridTask();
1090         LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
1091 
1092         int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
1093         int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
1094         int taskMargin = deviceProfile.overviewTaskMarginPx;
1095 
1096         orientationHandler.setTaskIconParams(iconParams, taskMargin, taskIconHeight,
1097                 thumbnailTopMargin, isRtl);
1098         iconParams.width = iconParams.height = taskIconHeight;
1099         mIconView.setLayoutParams(iconParams);
1100 
1101         mIconView.setRotation(orientationHandler.getDegreesRotated());
1102         int iconDrawableSize = isGridTask ? deviceProfile.overviewTaskIconDrawableSizeGridPx
1103                 : deviceProfile.overviewTaskIconDrawableSizePx;
1104         mIconView.setDrawableSize(iconDrawableSize, iconDrawableSize);
1105     }
1106 
1107     protected void setThumbnailOrientation(RecentsOrientedState orientationState) {
1108         DeviceProfile deviceProfile = mActivity.getDeviceProfile();
1109         int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
1110 
1111         // TODO(b/271468547), we should default to setting trasnlations only on the snapshot instead
1112         //  of a hybrid of both margins and translations
1113         LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
1114         snapshotParams.topMargin = thumbnailTopMargin;
1115         mSnapshotView.setLayoutParams(snapshotParams);
1116 
1117         mSnapshotView.getTaskOverlay().updateOrientationState(orientationState);
1118         mDigitalWellBeingToast.initialize(mTask);
1119     }
1120 
1121     /**
1122      * Returns whether the task is part of overview grid and not being focused.
1123      */
1124     public boolean isGridTask() {
1125         DeviceProfile deviceProfile = mActivity.getDeviceProfile();
1126         return deviceProfile.isTablet && !isFocusedTask();
1127     }
1128 
1129     /** Whether this task view represents the desktop */
1130     public boolean isDesktopTask() {
1131         return false;
1132     }
1133 
1134     /**
1135      * Called to animate a smooth transition when going directly from an app into Overview (and
1136      * vice versa). Icons fade in, and DWB banners slide in with a "shift up" animation.
1137      */
1138     protected void setIconsAndBannersTransitionProgress(float progress, boolean invert) {
1139         if (invert) {
1140             progress = 1 - progress;
1141         }
1142         mFocusTransitionProgress = progress;
1143         float iconScalePercentage = (float) SCALE_ICON_DURATION / DIM_ANIM_DURATION;
1144         float lowerClamp = invert ? 1f - iconScalePercentage : 0;
1145         float upperClamp = invert ? 1 : iconScalePercentage;
1146         float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, lowerClamp, upperClamp)
1147                 .getInterpolation(progress);
1148         mIconView.setAlpha(scale);
1149         mDigitalWellBeingToast.updateBannerOffset(1f - scale);
1150     }
1151 
1152     public void setIconScaleAnimStartProgress(float startProgress) {
1153         mIconScaleAnimStartProgress = startProgress;
1154     }
1155 
1156     public void animateIconScaleAndDimIntoView() {
1157         if (mIconAndDimAnimator != null) {
1158             mIconAndDimAnimator.cancel();
1159         }
1160         mIconAndDimAnimator = ObjectAnimator.ofFloat(this, FOCUS_TRANSITION, 1);
1161         mIconAndDimAnimator.setCurrentFraction(mIconScaleAnimStartProgress);
1162         mIconAndDimAnimator.setDuration(DIM_ANIM_DURATION).setInterpolator(LINEAR);
1163         mIconAndDimAnimator.addListener(new AnimatorListenerAdapter() {
1164             @Override
1165             public void onAnimationEnd(Animator animation) {
1166                 mIconAndDimAnimator = null;
1167             }
1168         });
1169         mIconAndDimAnimator.start();
1170     }
1171 
1172     protected void setIconScaleAndDim(float iconScale) {
1173         setIconScaleAndDim(iconScale, false);
1174     }
1175 
1176     private void setIconScaleAndDim(float iconScale, boolean invert) {
1177         if (mIconAndDimAnimator != null) {
1178             mIconAndDimAnimator.cancel();
1179         }
1180         setIconsAndBannersTransitionProgress(iconScale, invert);
1181     }
1182 
1183     protected void resetPersistentViewTransforms() {
1184         mNonGridTranslationX = mNonGridTranslationY =
1185                 mGridTranslationX = mGridTranslationY = mBoxTranslationY = 0f;
1186         resetViewTransforms();
1187     }
1188 
1189     protected void resetViewTransforms() {
1190         // fullscreenTranslation and accumulatedTranslation should not be reset, as
1191         // resetViewTransforms is called during Quickswitch scrolling.
1192         mDismissTranslationX = mTaskOffsetTranslationX =
1193                 mTaskResistanceTranslationX = mSplitSelectTranslationX = mGridEndTranslationX = 0f;
1194         mDismissTranslationY = mTaskOffsetTranslationY = mTaskResistanceTranslationY = 0f;
1195         if (getRecentsView() == null || !getRecentsView().isSplitSelectionActive()) {
1196             mSplitSelectTranslationY = 0f;
1197         }
1198 
1199         setSnapshotScale(1f);
1200         applyTranslationX();
1201         applyTranslationY();
1202         setTranslationZ(0);
1203         setAlpha(mStableAlpha);
1204         setIconScaleAndDim(1);
1205         setColorTint(0, 0);
1206         mSnapshotView.resetViewTransforms();
1207     }
1208 
1209     public void setStableAlpha(float parentAlpha) {
1210         mStableAlpha = parentAlpha;
1211         setAlpha(mStableAlpha);
1212     }
1213 
1214     @Override
1215     public void onRecycle() {
1216         resetPersistentViewTransforms();
1217         // Clear any references to the thumbnail (it will be re-read either from the cache or the
1218         // system on next bind)
1219         mSnapshotView.setThumbnail(mTask, null);
1220         setOverlayEnabled(false);
1221         onTaskListVisibilityChanged(false);
1222     }
1223 
1224     public float getTaskCornerRadius() {
1225         return mCurrentFullscreenParams.mCornerRadius;
1226     }
1227 
1228     @Override
1229     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1230         super.onLayout(changed, left, top, right, bottom);
1231         if (mActivity.getDeviceProfile().isTablet) {
1232             setPivotX(getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : right - left);
1233             setPivotY(mSnapshotView.getTop());
1234         } else {
1235             setPivotX((right - left) * 0.5f);
1236             setPivotY(mSnapshotView.getTop() + mSnapshotView.getHeight() * 0.5f);
1237         }
1238         if (Utilities.ATLEAST_Q) {
1239             SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(0, 0, getWidth(), getHeight());
1240             setSystemGestureExclusionRects(SYSTEM_GESTURE_EXCLUSION_RECT);
1241         }
1242     }
1243 
1244     /**
1245      * How much to scale down pages near the edge of the screen.
1246      */
1247     public static float getEdgeScaleDownFactor(DeviceProfile deviceProfile) {
1248         return deviceProfile.isTablet ? EDGE_SCALE_DOWN_FACTOR_GRID
1249                 : EDGE_SCALE_DOWN_FACTOR_CAROUSEL;
1250     }
1251 
1252     private void setNonGridScale(float nonGridScale) {
1253         mNonGridScale = nonGridScale;
1254         applyScale();
1255     }
1256 
1257     public float getNonGridScale() {
1258         return mNonGridScale;
1259     }
1260 
1261     private void setSnapshotScale(float dismissScale) {
1262         mDismissScale = dismissScale;
1263         applyScale();
1264     }
1265 
1266     /**
1267      * Moves TaskView between carousel and 2 row grid.
1268      *
1269      * @param gridProgress 0 = carousel; 1 = 2 row grid.
1270      */
1271     public void setGridProgress(float gridProgress) {
1272         mGridProgress = gridProgress;
1273         applyTranslationX();
1274         applyTranslationY();
1275         applyScale();
1276     }
1277 
1278     private void applyScale() {
1279         float scale = 1;
1280         scale *= getPersistentScale();
1281         scale *= mDismissScale;
1282         setScaleX(scale);
1283         setScaleY(scale);
1284         updateSnapshotRadius();
1285     }
1286 
1287     /**
1288      * Returns multiplication of scale that is persistent (e.g. fullscreen and grid), and does not
1289      * change according to a temporary state.
1290      */
1291     public float getPersistentScale() {
1292         float scale = 1;
1293         float gridProgress = GRID_INTERPOLATOR.getInterpolation(mGridProgress);
1294         scale *= Utilities.mapRange(gridProgress, mNonGridScale, 1f);
1295         return scale;
1296     }
1297 
1298     /**
1299      * Updates alpha of task thumbnail splash on swipe up/down.
1300      */
1301     public void setTaskThumbnailSplashAlpha(float taskThumbnailSplashAlpha) {
1302         mTaskThumbnailSplashAlpha = taskThumbnailSplashAlpha;
1303         applyThumbnailSplashAlpha();
1304     }
1305 
1306     protected void applyThumbnailSplashAlpha() {
1307         mSnapshotView.setSplashAlpha(mTaskThumbnailSplashAlpha);
1308     }
1309 
1310     protected void refreshTaskThumbnailSplash() {
1311         mSnapshotView.refreshSplashView();
1312     }
1313 
1314     private void setSplitSelectTranslationX(float x) {
1315         mSplitSelectTranslationX = x;
1316         applyTranslationX();
1317     }
1318 
1319     private void setSplitSelectTranslationY(float y) {
1320         mSplitSelectTranslationY = y;
1321         applyTranslationY();
1322     }
1323 
1324     private void setDismissTranslationX(float x) {
1325         mDismissTranslationX = x;
1326         applyTranslationX();
1327     }
1328 
1329     private void setDismissTranslationY(float y) {
1330         mDismissTranslationY = y;
1331         applyTranslationY();
1332     }
1333 
1334     private void setTaskOffsetTranslationX(float x) {
1335         mTaskOffsetTranslationX = x;
1336         applyTranslationX();
1337     }
1338 
1339     private void setTaskOffsetTranslationY(float y) {
1340         mTaskOffsetTranslationY = y;
1341         applyTranslationY();
1342     }
1343 
1344     private void setTaskResistanceTranslationX(float x) {
1345         mTaskResistanceTranslationX = x;
1346         applyTranslationX();
1347     }
1348 
1349     private void setTaskResistanceTranslationY(float y) {
1350         mTaskResistanceTranslationY = y;
1351         applyTranslationY();
1352     }
1353 
1354     private void setNonGridTranslationX(float nonGridTranslationX) {
1355         mNonGridTranslationX = nonGridTranslationX;
1356         applyTranslationX();
1357     }
1358 
1359     private void setNonGridTranslationY(float nonGridTranslationY) {
1360         mNonGridTranslationY = nonGridTranslationY;
1361         applyTranslationY();
1362     }
1363 
1364     public void setGridTranslationX(float gridTranslationX) {
1365         mGridTranslationX = gridTranslationX;
1366         applyTranslationX();
1367     }
1368 
1369     public float getGridTranslationX() {
1370         return mGridTranslationX;
1371     }
1372 
1373     public void setGridTranslationY(float gridTranslationY) {
1374         mGridTranslationY = gridTranslationY;
1375         applyTranslationY();
1376     }
1377 
1378     public float getGridTranslationY() {
1379         return mGridTranslationY;
1380     }
1381 
1382     private void setGridEndTranslationX(float gridEndTranslationX) {
1383         mGridEndTranslationX = gridEndTranslationX;
1384         applyTranslationX();
1385     }
1386 
1387     public float getScrollAdjustment(boolean gridEnabled) {
1388         float scrollAdjustment = 0;
1389         if (gridEnabled) {
1390             scrollAdjustment += mGridTranslationX;
1391         } else {
1392             scrollAdjustment += getPrimaryNonGridTranslationProperty().get(this);
1393         }
1394         return scrollAdjustment;
1395     }
1396 
1397     public float getOffsetAdjustment(boolean gridEnabled) {
1398         return getScrollAdjustment(gridEnabled);
1399     }
1400 
1401     public float getSizeAdjustment(boolean fullscreenEnabled) {
1402         float sizeAdjustment = 1;
1403         if (fullscreenEnabled) {
1404             sizeAdjustment *= mNonGridScale;
1405         }
1406         return sizeAdjustment;
1407     }
1408 
1409     private void setBoxTranslationY(float boxTranslationY) {
1410         mBoxTranslationY = boxTranslationY;
1411         applyTranslationY();
1412     }
1413 
1414     private void applyTranslationX() {
1415         setTranslationX(mDismissTranslationX + mTaskOffsetTranslationX + mTaskResistanceTranslationX
1416                 + mSplitSelectTranslationX + mGridEndTranslationX + getPersistentTranslationX());
1417     }
1418 
1419     private void applyTranslationY() {
1420         setTranslationY(mDismissTranslationY + mTaskOffsetTranslationY + mTaskResistanceTranslationY
1421                 + mSplitSelectTranslationY + getPersistentTranslationY());
1422     }
1423 
1424     /**
1425      * Returns addition of translationX that is persistent (e.g. fullscreen and grid), and does not
1426      * change according to a temporary state (e.g. task offset).
1427      */
1428     public float getPersistentTranslationX() {
1429         return getNonGridTrans(mNonGridTranslationX) + getGridTrans(mGridTranslationX);
1430     }
1431 
1432     /**
1433      * Returns addition of translationY that is persistent (e.g. fullscreen and grid), and does not
1434      * change according to a temporary state (e.g. task offset).
1435      */
1436     public float getPersistentTranslationY() {
1437         return mBoxTranslationY
1438                 + getNonGridTrans(mNonGridTranslationY)
1439                 + getGridTrans(mGridTranslationY);
1440     }
1441 
1442     public FloatProperty<TaskView> getPrimarySplitTranslationProperty() {
1443         return getPagedOrientationHandler().getPrimaryValue(
1444                 SPLIT_SELECT_TRANSLATION_X, SPLIT_SELECT_TRANSLATION_Y);
1445     }
1446 
1447     public FloatProperty<TaskView> getSecondarySplitTranslationProperty() {
1448         return getPagedOrientationHandler().getSecondaryValue(
1449                 SPLIT_SELECT_TRANSLATION_X, SPLIT_SELECT_TRANSLATION_Y);
1450     }
1451 
1452     public FloatProperty<TaskView> getPrimaryDismissTranslationProperty() {
1453         return getPagedOrientationHandler().getPrimaryValue(
1454                 DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
1455     }
1456 
1457     public FloatProperty<TaskView> getSecondaryDismissTranslationProperty() {
1458         return getPagedOrientationHandler().getSecondaryValue(
1459                 DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
1460     }
1461 
1462     public FloatProperty<TaskView> getPrimaryTaskOffsetTranslationProperty() {
1463         return getPagedOrientationHandler().getPrimaryValue(
1464                 TASK_OFFSET_TRANSLATION_X, TASK_OFFSET_TRANSLATION_Y);
1465     }
1466 
1467     public FloatProperty<TaskView> getSecondaryTaskOffsetTranslationProperty() {
1468         return getPagedOrientationHandler().getSecondaryValue(
1469                 TASK_OFFSET_TRANSLATION_X, TASK_OFFSET_TRANSLATION_Y);
1470     }
1471 
1472     public FloatProperty<TaskView> getTaskResistanceTranslationProperty() {
1473         return getPagedOrientationHandler().getSecondaryValue(
1474                 TASK_RESISTANCE_TRANSLATION_X, TASK_RESISTANCE_TRANSLATION_Y);
1475     }
1476 
1477     public FloatProperty<TaskView> getPrimaryNonGridTranslationProperty() {
1478         return getPagedOrientationHandler().getPrimaryValue(
1479                 NON_GRID_TRANSLATION_X, NON_GRID_TRANSLATION_Y);
1480     }
1481 
1482     public FloatProperty<TaskView> getSecondaryNonGridTranslationProperty() {
1483         return getPagedOrientationHandler().getSecondaryValue(
1484                 NON_GRID_TRANSLATION_X, NON_GRID_TRANSLATION_Y);
1485     }
1486 
1487     @Override
1488     public boolean hasOverlappingRendering() {
1489         // TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
1490         return false;
1491     }
1492 
1493     public boolean isEndQuickswitchCuj() {
1494         return mEndQuickswitchCuj;
1495     }
1496 
1497     public void setEndQuickswitchCuj(boolean endQuickswitchCuj) {
1498         mEndQuickswitchCuj = endQuickswitchCuj;
1499     }
1500 
1501     private int getExpectedViewHeight(View view) {
1502         int expectedHeight;
1503         int h = view.getLayoutParams().height;
1504         if (h > 0) {
1505             expectedHeight = h;
1506         } else {
1507             int m = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY - 1, MeasureSpec.AT_MOST);
1508             view.measure(m, m);
1509             expectedHeight = view.getMeasuredHeight();
1510         }
1511         return expectedHeight;
1512     }
1513 
1514     @Override
1515     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1516         super.onInitializeAccessibilityNodeInfo(info);
1517 
1518         info.addAction(
1519                 new AccessibilityNodeInfo.AccessibilityAction(R.string.accessibility_close,
1520                         getContext().getText(R.string.accessibility_close)));
1521 
1522         final Context context = getContext();
1523         for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) {
1524             if (taskContainer == null) {
1525                 continue;
1526             }
1527             for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
1528                     taskContainer)) {
1529                 info.addAction(s.createAccessibilityAction(context));
1530             }
1531         }
1532 
1533         if (mDigitalWellBeingToast.hasLimit()) {
1534             info.addAction(
1535                     new AccessibilityNodeInfo.AccessibilityAction(
1536                             R.string.accessibility_app_usage_settings,
1537                             getContext().getText(R.string.accessibility_app_usage_settings)));
1538         }
1539 
1540         final RecentsView recentsView = getRecentsView();
1541         final AccessibilityNodeInfo.CollectionItemInfo itemInfo =
1542                 AccessibilityNodeInfo.CollectionItemInfo.obtain(
1543                         0, 1, recentsView.getTaskViewCount() - recentsView.indexOfChild(this) - 1,
1544                         1, false);
1545         info.setCollectionItemInfo(itemInfo);
1546     }
1547 
1548     @Override
1549     public boolean performAccessibilityAction(int action, Bundle arguments) {
1550         if (action == R.string.accessibility_close) {
1551             getRecentsView().dismissTask(this, true /*animateTaskView*/,
1552                     true /*removeTask*/);
1553             return true;
1554         }
1555 
1556         if (action == R.string.accessibility_app_usage_settings) {
1557             mDigitalWellBeingToast.openAppUsageSettings(this);
1558             return true;
1559         }
1560 
1561         for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) {
1562             if (taskContainer == null) {
1563                 continue;
1564             }
1565             for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
1566                     taskContainer)) {
1567                 if (s.hasHandlerForAction(action)) {
1568                     s.onClick(this);
1569                     return true;
1570                 }
1571             }
1572         }
1573 
1574         return super.performAccessibilityAction(action, arguments);
1575     }
1576 
1577     public RecentsView getRecentsView() {
1578         return (RecentsView) getParent();
1579     }
1580 
1581     PagedOrientationHandler getPagedOrientationHandler() {
1582         return getRecentsView().mOrientationState.getOrientationHandler();
1583     }
1584 
1585     private void notifyTaskLaunchFailed(String tag) {
1586         String msg = "Failed to launch task";
1587         if (mTask != null) {
1588             msg += " (task=" + mTask.key.baseIntent + " userId=" + mTask.key.userId + ")";
1589         }
1590         Log.w(tag, msg);
1591         Toast.makeText(getContext(), R.string.activity_not_available, LENGTH_SHORT).show();
1592     }
1593 
1594     /**
1595      * Hides the icon and shows insets when this TaskView is about to be shown fullscreen.
1596      *
1597      * @param progress: 0 = show icon and no insets; 1 = don't show icon and show full insets.
1598      */
1599     public void setFullscreenProgress(float progress) {
1600         progress = Utilities.boundToRange(progress, 0, 1);
1601         mFullscreenProgress = progress;
1602         mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
1603         mSnapshotView.getTaskOverlay().setFullscreenProgress(progress);
1604 
1605         // Animate icons and DWB banners in/out, except in QuickSwitch state, when tiles are
1606         // oversized and banner would look disproportionately large.
1607         if (mActivity.getStateManager().getState() != BACKGROUND_APP) {
1608             setIconsAndBannersTransitionProgress(progress, true);
1609         }
1610 
1611         updateSnapshotRadius();
1612     }
1613 
1614     protected void updateSnapshotRadius() {
1615         updateCurrentFullscreenParams(mSnapshotView.getPreviewPositionHelper());
1616         mSnapshotView.setFullscreenParams(mCurrentFullscreenParams);
1617     }
1618 
1619     void updateCurrentFullscreenParams(PreviewPositionHelper previewPositionHelper) {
1620         if (getRecentsView() == null) {
1621             return;
1622         }
1623         mCurrentFullscreenParams.setProgress(mFullscreenProgress, getRecentsView().getScaleX(),
1624                 getScaleX(), getWidth(), mActivity.getDeviceProfile(), previewPositionHelper);
1625     }
1626 
1627     /**
1628      * Updates TaskView scaling and translation required to support variable width if enabled, while
1629      * ensuring TaskView fits into screen in fullscreen.
1630      */
1631     void updateTaskSize() {
1632         ViewGroup.LayoutParams params = getLayoutParams();
1633         float nonGridScale;
1634         float boxTranslationY;
1635         int expectedWidth;
1636         int expectedHeight;
1637         DeviceProfile deviceProfile = mActivity.getDeviceProfile();
1638         if (deviceProfile.isTablet) {
1639             final int thumbnailPadding = deviceProfile.overviewTaskThumbnailTopMarginPx;
1640             final Rect lastComputedTaskSize = getRecentsView().getLastComputedTaskSize();
1641             final int taskWidth = lastComputedTaskSize.width();
1642             final int taskHeight = lastComputedTaskSize.height();
1643 
1644             int boxWidth;
1645             int boxHeight;
1646             boolean isFocusedTask = isFocusedTask();
1647             if (isDesktopTask()) {
1648                 Rect lastComputedDesktopTaskSize =
1649                         getRecentsView().getLastComputedDesktopTaskSize();
1650                 boxWidth = lastComputedDesktopTaskSize.width();
1651                 boxHeight = lastComputedDesktopTaskSize.height();
1652             } else if (isFocusedTask) {
1653                 // Task will be focused and should use focused task size. Use focusTaskRatio
1654                 // that is associated with the original orientation of the focused task.
1655                 boxWidth = taskWidth;
1656                 boxHeight = taskHeight;
1657             } else {
1658                 // Otherwise task is in grid, and should use lastComputedGridTaskSize.
1659                 Rect lastComputedGridTaskSize = getRecentsView().getLastComputedGridTaskSize();
1660                 boxWidth = lastComputedGridTaskSize.width();
1661                 boxHeight = lastComputedGridTaskSize.height();
1662             }
1663 
1664             // Bound width/height to the box size.
1665             expectedWidth = boxWidth;
1666             expectedHeight = boxHeight + thumbnailPadding;
1667 
1668             // Scale to to fit task Rect.
1669             nonGridScale = taskWidth / (float) boxWidth;
1670 
1671             // Align to top of task Rect.
1672             boxTranslationY = (expectedHeight - thumbnailPadding - taskHeight) / 2.0f;
1673         } else {
1674             nonGridScale = 1f;
1675             boxTranslationY = 0f;
1676             expectedWidth = ViewGroup.LayoutParams.MATCH_PARENT;
1677             expectedHeight = ViewGroup.LayoutParams.MATCH_PARENT;
1678         }
1679 
1680         setNonGridScale(nonGridScale);
1681         setBoxTranslationY(boxTranslationY);
1682         if (params.width != expectedWidth || params.height != expectedHeight) {
1683             params.width = expectedWidth;
1684             params.height = expectedHeight;
1685             setLayoutParams(params);
1686         }
1687     }
1688 
1689     private float getGridTrans(float endTranslation) {
1690         float progress = GRID_INTERPOLATOR.getInterpolation(mGridProgress);
1691         return Utilities.mapRange(progress, 0, endTranslation);
1692     }
1693 
1694     private float getNonGridTrans(float endTranslation) {
1695         return endTranslation - getGridTrans(endTranslation);
1696     }
1697 
1698     public boolean isRunningTask() {
1699         if (getRecentsView() == null) {
1700             return false;
1701         }
1702         return this == getRecentsView().getRunningTaskView();
1703     }
1704 
1705     public boolean isFocusedTask() {
1706         if (getRecentsView() == null) {
1707             return false;
1708         }
1709         return this == getRecentsView().getFocusedTaskView();
1710     }
1711 
1712     public void setShowScreenshot(boolean showScreenshot) {
1713         mShowScreenshot = showScreenshot;
1714     }
1715 
1716     public boolean showScreenshot() {
1717         if (!isRunningTask()) {
1718             return true;
1719         }
1720         return mShowScreenshot;
1721     }
1722 
1723     public void setOverlayEnabled(boolean overlayEnabled) {
1724         mSnapshotView.setOverlayEnabled(overlayEnabled);
1725     }
1726 
1727     public void initiateSplitSelect(SplitPositionOption splitPositionOption) {
1728         getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition,
1729                 getLogEventForPosition(splitPositionOption.stagePosition));
1730     }
1731 
1732     /**
1733      * Set a color tint on the snapshot and supporting views.
1734      */
1735     public void setColorTint(float amount, int tintColor) {
1736         mSnapshotView.setDimAlpha(amount);
1737         mIconView.setIconColorTint(tintColor, amount);
1738         mDigitalWellBeingToast.setBannerColorTint(tintColor, amount);
1739     }
1740 
1741 
1742     private int getRootViewDisplayId() {
1743         Display  display = getRootView().getDisplay();
1744         return display != null ? display.getDisplayId() : DEFAULT_DISPLAY;
1745     }
1746 
1747     /**
1748      *  Sets visibility for the thumbnail and associated elements (DWB banners and action chips).
1749      *  IconView is unaffected.
1750      *
1751      * @param taskId is only used when setting visibility to a non-{@link View#VISIBLE} value
1752      */
1753     void setThumbnailVisibility(int visibility, int taskId) {
1754         for (int i = 0; i < getChildCount(); i++) {
1755             View child = getChildAt(i);
1756             if (child != mIconView) {
1757                 child.setVisibility(visibility);
1758             }
1759         }
1760     }
1761 
1762     /**
1763      * We update and subsequently draw these in {@link #setFullscreenProgress(float)}.
1764      */
1765     public static class FullscreenDrawParams {
1766 
1767         private final float mCornerRadius;
1768         private final float mWindowCornerRadius;
1769 
1770         public float mCurrentDrawnCornerRadius;
1771 
1772         public FullscreenDrawParams(Context context) {
1773             mCornerRadius = TaskCornerRadius.get(context);
1774             mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context);
1775 
1776             mCurrentDrawnCornerRadius = mCornerRadius;
1777         }
1778 
1779         /**
1780          * Sets the progress in range [0, 1]
1781          */
1782         public void setProgress(float fullscreenProgress, float parentScale, float taskViewScale,
1783                 int previewWidth, DeviceProfile dp, PreviewPositionHelper pph) {
1784             mCurrentDrawnCornerRadius =
1785                     Utilities.mapRange(fullscreenProgress, mCornerRadius, mWindowCornerRadius)
1786                             / parentScale / taskViewScale;
1787         }
1788     }
1789 
1790     public class TaskIdAttributeContainer {
1791         private final TaskThumbnailView mThumbnailView;
1792         private final Task mTask;
1793         private final IconView mIconView;
1794         /** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */
1795         private @SplitConfigurationOptions.StagePosition int mStagePosition;
1796         @IdRes
1797         private final int mA11yNodeId;
1798 
1799         public TaskIdAttributeContainer(Task task, TaskThumbnailView thumbnailView,
1800                 IconView iconView, int stagePosition) {
1801             this.mTask = task;
1802             this.mThumbnailView = thumbnailView;
1803             this.mIconView = iconView;
1804             this.mStagePosition = stagePosition;
1805             this.mA11yNodeId = (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) ?
1806                     R.id.split_bottomRight_appInfo : R.id.split_topLeft_appInfo;
1807         }
1808 
1809         public TaskThumbnailView getThumbnailView() {
1810             return mThumbnailView;
1811         }
1812 
1813         public Task getTask() {
1814             return mTask;
1815         }
1816 
1817         public WorkspaceItemInfo getItemInfo() {
1818             return TaskView.this.getItemInfo(mTask);
1819         }
1820 
1821         public TaskView getTaskView() {
1822             return TaskView.this;
1823         }
1824 
1825         public IconView getIconView() {
1826             return mIconView;
1827         }
1828 
1829         public int getStagePosition() {
1830             return mStagePosition;
1831         }
1832 
1833         void setStagePosition(@SplitConfigurationOptions.StagePosition int stagePosition) {
1834             this.mStagePosition = stagePosition;
1835         }
1836 
1837         public int getA11yNodeId() {
1838             return mA11yNodeId;
1839         }
1840     }
1841 }
1842