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