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