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