• 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.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
31 import static com.android.launcher3.Utilities.comp;
32 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
33 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
34 import static com.android.launcher3.anim.Interpolators.LINEAR;
35 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
36 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
37 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
38 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
39 
40 import android.animation.Animator;
41 import android.animation.AnimatorListenerAdapter;
42 import android.animation.ObjectAnimator;
43 import android.animation.TimeInterpolator;
44 import android.animation.ValueAnimator;
45 import android.app.ActivityOptions;
46 import android.content.Context;
47 import android.content.Intent;
48 import android.graphics.Outline;
49 import android.graphics.Rect;
50 import android.graphics.RectF;
51 import android.graphics.drawable.Drawable;
52 import android.graphics.drawable.GradientDrawable;
53 import android.graphics.drawable.InsetDrawable;
54 import android.os.Bundle;
55 import android.os.Handler;
56 import android.util.AttributeSet;
57 import android.util.FloatProperty;
58 import android.util.Log;
59 import android.view.MotionEvent;
60 import android.view.Surface;
61 import android.view.TouchDelegate;
62 import android.view.View;
63 import android.view.ViewOutlineProvider;
64 import android.view.accessibility.AccessibilityNodeInfo;
65 import android.widget.FrameLayout;
66 import android.widget.Toast;
67 
68 import com.android.launcher3.BaseDraggingActivity;
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.AnimatorPlaybackController;
74 import com.android.launcher3.anim.Interpolators;
75 import com.android.launcher3.anim.PendingAnimation;
76 import com.android.launcher3.logging.UserEventDispatcher;
77 import com.android.launcher3.model.data.WorkspaceItemInfo;
78 import com.android.launcher3.popup.SystemShortcut;
79 import com.android.launcher3.testing.TestLogging;
80 import com.android.launcher3.testing.TestProtocol;
81 import com.android.launcher3.touch.PagedOrientationHandler;
82 import com.android.launcher3.userevent.nano.LauncherLogProto;
83 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
84 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
85 import com.android.launcher3.util.ComponentKey;
86 import com.android.launcher3.util.TransformingTouchDelegate;
87 import com.android.launcher3.util.ViewPool.Reusable;
88 import com.android.quickstep.RecentsModel;
89 import com.android.quickstep.TaskIconCache;
90 import com.android.quickstep.TaskOverlayFactory;
91 import com.android.quickstep.TaskThumbnailCache;
92 import com.android.quickstep.TaskUtils;
93 import com.android.quickstep.util.RecentsOrientedState;
94 import com.android.quickstep.util.TaskCornerRadius;
95 import com.android.quickstep.views.RecentsView.PageCallbacks;
96 import com.android.quickstep.views.RecentsView.ScrollState;
97 import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
98 import com.android.systemui.shared.recents.model.Task;
99 import com.android.systemui.shared.system.ActivityManagerWrapper;
100 import com.android.systemui.shared.system.ActivityOptionsCompat;
101 import com.android.systemui.shared.system.QuickStepContract;
102 
103 import java.util.Collections;
104 import java.util.List;
105 import java.util.function.Consumer;
106 
107 /**
108  * A task in the Recents view.
109  */
110 public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
111 
112     private static final String TAG = TaskView.class.getSimpleName();
113 
114     /** A curve of x from 0 to 1, where 0 is the center of the screen and 1 is the edge. */
115     private static final TimeInterpolator CURVE_INTERPOLATOR
116             = x -> (float) -Math.cos(x * Math.PI) / 2f + .5f;
117 
118     /**
119      * The alpha of a black scrim on a page in the carousel as it leaves the screen.
120      * In the resting position of the carousel, the adjacent pages have about half this scrim.
121      */
122     public static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
123 
124     /**
125      * How much to scale down pages near the edge of the screen.
126      */
127     public static final float EDGE_SCALE_DOWN_FACTOR = 0.03f;
128 
129     public static final long SCALE_ICON_DURATION = 120;
130     private static final long DIM_ANIM_DURATION = 700;
131     /**
132      * This technically can be a vanilla {@link TouchDelegate} class, however that class requires
133      * setting the touch bounds at construction, so we'd repeatedly be created many instances
134      * unnecessarily as scrolling occurs, whereas {@link TransformingTouchDelegate} allows touch
135      * delegated bounds only to be updated.
136      */
137     private TransformingTouchDelegate mIconTouchDelegate;
138     private TransformingTouchDelegate mChipTouchDelegate;
139 
140     private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
141             Collections.singletonList(new Rect());
142 
143     private static final FloatProperty<TaskView> FOCUS_TRANSITION =
144             new FloatProperty<TaskView>("focusTransition") {
145                 @Override
146                 public void setValue(TaskView taskView, float v) {
147                     taskView.setIconAndDimTransitionProgress(v, false /* invert */);
148                 }
149 
150                 @Override
151                 public Float get(TaskView taskView) {
152                     return taskView.mFocusTransitionProgress;
153                 }
154             };
155 
156     private static final FloatProperty<TaskView> FILL_DISMISS_GAP_TRANSLATION_X =
157             new FloatProperty<TaskView>("fillDismissGapTranslationX") {
158                 @Override
159                 public void setValue(TaskView taskView, float v) {
160                     taskView.setFillDismissGapTranslationX(v);
161                 }
162 
163                 @Override
164                 public Float get(TaskView taskView) {
165                     return taskView.mFillDismissGapTranslationX;
166                 }
167             };
168 
169     private static final FloatProperty<TaskView> FILL_DISMISS_GAP_TRANSLATION_Y =
170             new FloatProperty<TaskView>("fillDismissGapTranslationY") {
171                 @Override
172                 public void setValue(TaskView taskView, float v) {
173                     taskView.setFillDismissGapTranslationY(v);
174                 }
175 
176                 @Override
177                 public Float get(TaskView taskView) {
178                     return taskView.mFillDismissGapTranslationY;
179                 }
180             };
181 
182     private static final FloatProperty<TaskView> TASK_OFFSET_TRANSLATION_X =
183             new FloatProperty<TaskView>("taskOffsetTranslationX") {
184                 @Override
185                 public void setValue(TaskView taskView, float v) {
186                     taskView.setTaskOffsetTranslationX(v);
187                 }
188 
189                 @Override
190                 public Float get(TaskView taskView) {
191                     return taskView.mTaskOffsetTranslationX;
192                 }
193             };
194 
195     private static final FloatProperty<TaskView> TASK_OFFSET_TRANSLATION_Y =
196             new FloatProperty<TaskView>("taskOffsetTranslationY") {
197                 @Override
198                 public void setValue(TaskView taskView, float v) {
199                     taskView.setTaskOffsetTranslationY(v);
200                 }
201 
202                 @Override
203                 public Float get(TaskView taskView) {
204                     return taskView.mTaskOffsetTranslationY;
205                 }
206             };
207 
208     private final OnAttachStateChangeListener mTaskMenuStateListener =
209             new OnAttachStateChangeListener() {
210                 @Override
211                 public void onViewAttachedToWindow(View view) {
212                 }
213 
214                 @Override
215                 public void onViewDetachedFromWindow(View view) {
216                     if (mMenuView != null) {
217                         mMenuView.removeOnAttachStateChangeListener(this);
218                         mMenuView = null;
219                     }
220                 }
221             };
222 
223     private final TaskOutlineProvider mOutlineProvider;
224 
225     private Task mTask;
226     private TaskThumbnailView mSnapshotView;
227     private TaskMenuView mMenuView;
228     private IconView mIconView;
229     private final DigitalWellBeingToast mDigitalWellBeingToast;
230     private float mCurveScale;
231     private float mFullscreenProgress;
232     private final FullscreenDrawParams mCurrentFullscreenParams;
233     private final BaseDraggingActivity mActivity;
234 
235     // Various causes of changing primary translation, which we aggregate to setTranslationX/Y().
236     // TODO: We should do this for secondary translation properties as well.
237     private float mFillDismissGapTranslationX;
238     private float mFillDismissGapTranslationY;
239     private float mTaskOffsetTranslationX;
240     private float mTaskOffsetTranslationY;
241 
242     private ObjectAnimator mIconAndDimAnimator;
243     private float mIconScaleAnimStartProgress = 0;
244     private float mFocusTransitionProgress = 1;
245     private float mModalness = 0;
246     private float mStableAlpha = 1;
247 
248     private boolean mShowScreenshot;
249 
250     // The current background requests to load the task thumbnail and icon
251     private TaskThumbnailCache.ThumbnailLoadRequest mThumbnailLoadRequest;
252     private TaskIconCache.IconLoadRequest mIconLoadRequest;
253 
254     // Order in which the footers appear. Lower order appear below higher order.
255     public static final int INDEX_DIGITAL_WELLBEING_TOAST = 0;
256     private final FooterWrapper[] mFooters = new FooterWrapper[2];
257     private float mFooterVerticalOffset = 0;
258     private float mFooterAlpha = 1;
259     private int mStackHeight;
260     private View mContextualChipWrapper;
261     private View mContextualChip;
262     private final float[] mIconCenterCoords = new float[2];
263     private final float[] mChipCenterCoords = new float[2];
264 
TaskView(Context context)265     public TaskView(Context context) {
266         this(context, null);
267     }
268 
TaskView(Context context, AttributeSet attrs)269     public TaskView(Context context, AttributeSet attrs) {
270         this(context, attrs, 0);
271     }
272 
TaskView(Context context, AttributeSet attrs, int defStyleAttr)273     public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
274         super(context, attrs, defStyleAttr);
275         mActivity = BaseDraggingActivity.fromContext(context);
276         setOnClickListener((view) -> {
277             if (getTask() == null) {
278                 return;
279             }
280             if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
281                 if (isRunningTask()) {
282                     createLaunchAnimationForRunningTask().start();
283                 } else {
284                     launchTask(true /* animate */);
285                 }
286             } else {
287                 launchTask(true /* animate */);
288             }
289 
290             mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
291                     Touch.TAP, Direction.NONE, getRecentsView().indexOfChild(this),
292                     TaskUtils.getLaunchComponentKeyForTask(getTask().key));
293             mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
294                     .log(LAUNCHER_TASK_LAUNCH_TAP);
295         });
296 
297         mCurrentFullscreenParams = new FullscreenDrawParams(context);
298         mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
299 
300         mOutlineProvider = new TaskOutlineProvider(getContext(), mCurrentFullscreenParams);
301         setOutlineProvider(mOutlineProvider);
302     }
303 
304     /**
305      * Builds proto for logging
306      */
getItemInfo()307     public WorkspaceItemInfo getItemInfo() {
308         ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(getTask().key);
309         WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
310         dummyInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK;
311         dummyInfo.container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
312         dummyInfo.user = componentKey.user;
313         dummyInfo.intent = new Intent().setComponent(componentKey.componentName);
314         dummyInfo.title = TaskUtils.getTitle(getContext(), getTask());
315         dummyInfo.screenId = getRecentsView().indexOfChild(this);
316         return dummyInfo;
317     }
318 
319     @Override
onFinishInflate()320     protected void onFinishInflate() {
321         super.onFinishInflate();
322         mSnapshotView = findViewById(R.id.snapshot);
323         mIconView = findViewById(R.id.icon);
324         mIconTouchDelegate = new TransformingTouchDelegate(mIconView);
325     }
326 
327     /**
328      * Whether the taskview should take the touch event from parent. Events passed to children
329      * that might require special handling.
330      */
offerTouchToChildren(MotionEvent event)331     public boolean offerTouchToChildren(MotionEvent event) {
332         if (event.getAction() == MotionEvent.ACTION_DOWN) {
333             computeAndSetIconTouchDelegate();
334             computeAndSetChipTouchDelegate();
335         }
336         if (mIconTouchDelegate != null && mIconTouchDelegate.onTouchEvent(event)) {
337             return true;
338         }
339         if (mChipTouchDelegate != null && mChipTouchDelegate.onTouchEvent(event)) {
340             return true;
341         }
342         return false;
343     }
344 
computeAndSetIconTouchDelegate()345     private void computeAndSetIconTouchDelegate() {
346         float iconHalfSize = mIconView.getWidth() / 2f;
347         mIconCenterCoords[0] = mIconCenterCoords[1] = iconHalfSize;
348         getDescendantCoordRelativeToAncestor(mIconView, mActivity.getDragLayer(), mIconCenterCoords,
349                 false);
350         mIconTouchDelegate.setBounds(
351                 (int) (mIconCenterCoords[0] - iconHalfSize),
352                 (int) (mIconCenterCoords[1] - iconHalfSize),
353                 (int) (mIconCenterCoords[0] + iconHalfSize),
354                 (int) (mIconCenterCoords[1] + iconHalfSize));
355     }
356 
computeAndSetChipTouchDelegate()357     private void computeAndSetChipTouchDelegate() {
358         if (mContextualChipWrapper != null) {
359             float chipHalfWidth = mContextualChipWrapper.getWidth() / 2f;
360             float chipHalfHeight = mContextualChipWrapper.getHeight() / 2f;
361             mChipCenterCoords[0] = chipHalfWidth;
362             mChipCenterCoords[1] = chipHalfHeight;
363             getDescendantCoordRelativeToAncestor(mContextualChipWrapper, mActivity.getDragLayer(),
364                     mChipCenterCoords,
365                     false);
366             mChipTouchDelegate.setBounds(
367                     (int) (mChipCenterCoords[0] - chipHalfWidth),
368                     (int) (mChipCenterCoords[1] - chipHalfHeight),
369                     (int) (mChipCenterCoords[0] + chipHalfWidth),
370                     (int) (mChipCenterCoords[1] + chipHalfHeight));
371         }
372     }
373 
374     /**
375      * The modalness of this view is how it should be displayed when it is shown on its own in the
376      * modal state of overview.
377      *
378      * @param modalness [0, 1] 0 being in context with other tasks, 1 being shown on its own.
379      */
setModalness(float modalness)380     public void setModalness(float modalness) {
381         if (mModalness == modalness) {
382             return;
383         }
384         mModalness = modalness;
385         mIconView.setAlpha(comp(modalness));
386         if (mContextualChip != null) {
387             mContextualChip.setScaleX(comp(modalness));
388             mContextualChip.setScaleY(comp(modalness));
389         }
390         if (mContextualChipWrapper != null) {
391             mContextualChipWrapper.setAlpha(comp(modalness));
392         }
393         updateFooterVerticalOffset(mFooterVerticalOffset);
394     }
395 
getMenuView()396     public TaskMenuView getMenuView() {
397         return mMenuView;
398     }
399 
getDigitalWellBeingToast()400     public DigitalWellBeingToast getDigitalWellBeingToast() {
401         return mDigitalWellBeingToast;
402     }
403 
404     /**
405      * Updates this task view to the given {@param task}.
406      *
407      * TODO(b/142282126) Re-evaluate if we need to pass in isMultiWindowMode after
408      *   that issue is fixed
409      */
bind(Task task, RecentsOrientedState orientedState)410     public void bind(Task task, RecentsOrientedState orientedState) {
411         cancelPendingLoadTasks();
412         mTask = task;
413         mSnapshotView.bind(task);
414         setOrientationState(orientedState);
415     }
416 
getTask()417     public Task getTask() {
418         return mTask;
419     }
420 
getThumbnail()421     public TaskThumbnailView getThumbnail() {
422         return mSnapshotView;
423     }
424 
getIconView()425     public IconView getIconView() {
426         return mIconView;
427     }
428 
createLaunchAnimationForRunningTask()429     public AnimatorPlaybackController createLaunchAnimationForRunningTask() {
430         final PendingAnimation pendingAnimation = getRecentsView().createTaskLaunchAnimation(
431                 this, RECENTS_LAUNCH_DURATION, TOUCH_RESPONSE_INTERPOLATOR);
432         AnimatorPlaybackController currentAnimation = pendingAnimation.createPlaybackController();
433         currentAnimation.setEndAction(() -> {
434             pendingAnimation.finish(true, Touch.SWIPE);
435             launchTask(false);
436         });
437         return currentAnimation;
438     }
439 
launchTask(boolean animate)440     public void launchTask(boolean animate) {
441         launchTask(animate, false /* freezeTaskList */);
442     }
443 
launchTask(boolean animate, boolean freezeTaskList)444     public void launchTask(boolean animate, boolean freezeTaskList) {
445         launchTask(animate, freezeTaskList, (result) -> {
446             if (!result) {
447                 notifyTaskLaunchFailed(TAG);
448             }
449         }, getHandler());
450     }
451 
launchTask(boolean animate, Consumer<Boolean> resultCallback, Handler resultCallbackHandler)452     public void launchTask(boolean animate, Consumer<Boolean> resultCallback,
453             Handler resultCallbackHandler) {
454         launchTask(animate, false /* freezeTaskList */, resultCallback, resultCallbackHandler);
455     }
456 
launchTask(boolean animate, boolean freezeTaskList, Consumer<Boolean> resultCallback, Handler resultCallbackHandler)457     public void launchTask(boolean animate, boolean freezeTaskList, Consumer<Boolean> resultCallback,
458             Handler resultCallbackHandler) {
459         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
460             RecentsView recentsView = getRecentsView();
461             if (isRunningTask()) {
462                 recentsView.finishRecentsAnimation(false /* toRecents */,
463                         () -> resultCallbackHandler.post(() -> resultCallback.accept(true)));
464             } else {
465                 // This is a workaround against the WM issue that app open is not correctly animated
466                 // when recents animation is being cleaned up (b/143774568). When that's possible,
467                 // we should rely on the framework side to cancel the recents animation, and we will
468                 // clean up the screenshot on the launcher side while we launch the next task.
469                 recentsView.switchToScreenshot(null,
470                         () -> recentsView.finishRecentsAnimation(true /* toRecents */,
471                                 () -> launchTaskInternal(animate, freezeTaskList, resultCallback,
472                                         resultCallbackHandler)));
473             }
474         } else {
475             launchTaskInternal(animate, freezeTaskList, resultCallback, resultCallbackHandler);
476         }
477     }
478 
launchTaskInternal(boolean animate, boolean freezeTaskList, Consumer<Boolean> resultCallback, Handler resultCallbackHandler)479     private void launchTaskInternal(boolean animate, boolean freezeTaskList,
480             Consumer<Boolean> resultCallback, Handler resultCallbackHandler) {
481         if (mTask != null) {
482             final ActivityOptions opts;
483             TestLogging.recordEvent(
484                     TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
485             if (animate) {
486                 opts = mActivity.getActivityLaunchOptions(this);
487                 if (freezeTaskList) {
488                     ActivityOptionsCompat.setFreezeRecentTasksList(opts);
489                 }
490                 ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key,
491                         opts, resultCallback, resultCallbackHandler);
492             } else {
493                 opts = ActivityOptionsCompat.makeCustomAnimation(getContext(), 0, 0, () -> {
494                     if (resultCallback != null) {
495                         // Only post the animation start after the system has indicated that the
496                         // transition has started
497                         resultCallbackHandler.post(() -> resultCallback.accept(true));
498                     }
499                 }, resultCallbackHandler);
500                 if (freezeTaskList) {
501                     ActivityOptionsCompat.setFreezeRecentTasksList(opts);
502                 }
503                 ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key,
504                         opts, (success) -> {
505                             if (resultCallback != null && !success) {
506                                 // If the call to start activity failed, then post the result
507                                 // immediately, otherwise, wait for the animation start callback
508                                 // from the activity options above
509                                 resultCallbackHandler.post(() -> resultCallback.accept(false));
510                             }
511                         }, resultCallbackHandler);
512             }
513             getRecentsView().onTaskLaunched(mTask);
514         }
515     }
516 
onTaskListVisibilityChanged(boolean visible)517     public void onTaskListVisibilityChanged(boolean visible) {
518         if (mTask == null) {
519             return;
520         }
521         cancelPendingLoadTasks();
522         if (visible) {
523             // These calls are no-ops if the data is already loaded, try and load the high
524             // resolution thumbnail if the state permits
525             RecentsModel model = RecentsModel.INSTANCE.get(getContext());
526             TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
527             TaskIconCache iconCache = model.getIconCache();
528             mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(
529                     mTask, thumbnail -> mSnapshotView.setThumbnail(mTask, thumbnail));
530             mIconLoadRequest = iconCache.updateIconInBackground(mTask,
531                     (task) -> {
532                         setIcon(task.icon);
533                         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) {
534                             getRecentsView().updateLiveTileIcon(task.icon);
535                         }
536                         mDigitalWellBeingToast.initialize(mTask);
537                     });
538         } else {
539             mSnapshotView.setThumbnail(null, null);
540             setIcon(null);
541             // Reset the task thumbnail reference as well (it will be fetched from the cache or
542             // reloaded next time we need it)
543             mTask.thumbnail = null;
544         }
545     }
546 
cancelPendingLoadTasks()547     private void cancelPendingLoadTasks() {
548         if (mThumbnailLoadRequest != null) {
549             mThumbnailLoadRequest.cancel();
550             mThumbnailLoadRequest = null;
551         }
552         if (mIconLoadRequest != null) {
553             mIconLoadRequest.cancel();
554             mIconLoadRequest = null;
555         }
556     }
557 
showTaskMenu(int action)558     private boolean showTaskMenu(int action) {
559         if (!getRecentsView().isClearAllHidden()) {
560             getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
561         } else {
562             mMenuView = TaskMenuView.showForTask(this);
563             mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
564                     .log(LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS);
565             UserEventDispatcher.newInstance(getContext()).logActionOnItem(action, Direction.NONE,
566                     LauncherLogProto.ItemType.TASK_ICON);
567             if (mMenuView != null) {
568                 mMenuView.addOnAttachStateChangeListener(mTaskMenuStateListener);
569             }
570         }
571         return mMenuView != null;
572     }
573 
setIcon(Drawable icon)574     private void setIcon(Drawable icon) {
575         if (icon != null) {
576             mIconView.setDrawable(icon);
577             mIconView.setOnClickListener(v -> showTaskMenu(Touch.TAP));
578             mIconView.setOnLongClickListener(v -> {
579                 requestDisallowInterceptTouchEvent(true);
580                 return showTaskMenu(Touch.LONGPRESS);
581             });
582         } else {
583             mIconView.setDrawable(null);
584             mIconView.setOnClickListener(null);
585             mIconView.setOnLongClickListener(null);
586         }
587     }
588 
setOrientationState(RecentsOrientedState orientationState)589     public void setOrientationState(RecentsOrientedState orientationState) {
590         PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
591         boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
592         LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
593         int thumbnailPadding = (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin);
594         LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
595         switch (orientationHandler.getRotation()) {
596             case ROTATION_90:
597                 iconParams.gravity = (isRtl ? START : END) | CENTER_VERTICAL;
598                 iconParams.rightMargin = -thumbnailPadding;
599                 iconParams.leftMargin = 0;
600                 iconParams.topMargin = snapshotParams.topMargin / 2;
601                 break;
602             case ROTATION_180:
603                 iconParams.gravity = BOTTOM | CENTER_HORIZONTAL;
604                 iconParams.bottomMargin = -thumbnailPadding;
605                 iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin = 0;
606                 break;
607             case ROTATION_270:
608                 iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
609                 iconParams.leftMargin = -thumbnailPadding;
610                 iconParams.rightMargin = 0;
611                 iconParams.topMargin = snapshotParams.topMargin / 2;
612                 break;
613             case Surface.ROTATION_0:
614             default:
615                 iconParams.gravity = TOP | CENTER_HORIZONTAL;
616                 iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin = 0;
617                 break;
618         }
619         mIconView.setLayoutParams(iconParams);
620         mIconView.setRotation(orientationHandler.getDegreesRotated());
621 
622         if (mMenuView != null) {
623             mMenuView.onRotationChanged();
624         }
625     }
626 
setIconAndDimTransitionProgress(float progress, boolean invert)627     private void setIconAndDimTransitionProgress(float progress, boolean invert) {
628         if (invert) {
629             progress = 1 - progress;
630         }
631         mFocusTransitionProgress = progress;
632         mSnapshotView.setDimAlphaMultipler(progress);
633         float iconScalePercentage = (float) SCALE_ICON_DURATION / DIM_ANIM_DURATION;
634         float lowerClamp = invert ? 1f - iconScalePercentage : 0;
635         float upperClamp = invert ? 1 : iconScalePercentage;
636         float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, lowerClamp, upperClamp)
637                 .getInterpolation(progress);
638         mIconView.setScaleX(scale);
639         mIconView.setScaleY(scale);
640         if (mContextualChip != null && mContextualChipWrapper != null) {
641             mContextualChipWrapper.setAlpha(scale);
642             mContextualChip.setScaleX(scale);
643             mContextualChip.setScaleY(scale);
644         }
645         updateFooterVerticalOffset(1.0f - scale);
646     }
647 
setIconScaleAnimStartProgress(float startProgress)648     public void setIconScaleAnimStartProgress(float startProgress) {
649         mIconScaleAnimStartProgress = startProgress;
650     }
651 
animateIconScaleAndDimIntoView()652     public void animateIconScaleAndDimIntoView() {
653         if (mIconAndDimAnimator != null) {
654             mIconAndDimAnimator.cancel();
655         }
656         mIconAndDimAnimator = ObjectAnimator.ofFloat(this, FOCUS_TRANSITION, 1);
657         mIconAndDimAnimator.setCurrentFraction(mIconScaleAnimStartProgress);
658         mIconAndDimAnimator.setDuration(DIM_ANIM_DURATION).setInterpolator(LINEAR);
659         mIconAndDimAnimator.addListener(new AnimatorListenerAdapter() {
660             @Override
661             public void onAnimationEnd(Animator animation) {
662                 mIconAndDimAnimator = null;
663             }
664         });
665         mIconAndDimAnimator.start();
666     }
667 
setIconScaleAndDim(float iconScale)668     protected void setIconScaleAndDim(float iconScale) {
669         setIconScaleAndDim(iconScale, false);
670     }
671 
setIconScaleAndDim(float iconScale, boolean invert)672     private void setIconScaleAndDim(float iconScale, boolean invert) {
673         if (mIconAndDimAnimator != null) {
674             mIconAndDimAnimator.cancel();
675         }
676         setIconAndDimTransitionProgress(iconScale, invert);
677     }
678 
resetViewTransforms()679     protected void resetViewTransforms() {
680         setCurveScale(1);
681         mFillDismissGapTranslationX = mTaskOffsetTranslationX = 0f;
682         mFillDismissGapTranslationY = mTaskOffsetTranslationY = 0f;
683         setTranslationX(0f);
684         setTranslationY(0f);
685         setTranslationZ(0);
686         setAlpha(mStableAlpha);
687         setIconScaleAndDim(1);
688     }
689 
setStableAlpha(float parentAlpha)690     public void setStableAlpha(float parentAlpha) {
691         mStableAlpha = parentAlpha;
692         setAlpha(mStableAlpha);
693     }
694 
695     @Override
onRecycle()696     public void onRecycle() {
697         resetViewTransforms();
698         // Clear any references to the thumbnail (it will be re-read either from the cache or the
699         // system on next bind)
700         mSnapshotView.setThumbnail(mTask, null);
701         setOverlayEnabled(false);
702         onTaskListVisibilityChanged(false);
703     }
704 
705     @Override
onPageScroll(ScrollState scrollState)706     public void onPageScroll(ScrollState scrollState) {
707         // Don't do anything if it's modal.
708         if (mModalness > 0) {
709             return;
710         }
711 
712         float curveInterpolation =
713                 CURVE_INTERPOLATOR.getInterpolation(scrollState.linearInterpolation);
714         float curveScaleForCurveInterpolation = getCurveScaleForCurveInterpolation(
715                 curveInterpolation);
716         mSnapshotView.setDimAlpha(curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
717         setCurveScale(curveScaleForCurveInterpolation);
718 
719         mFooterAlpha = Utilities.boundToRange(1.0f - 2 * scrollState.linearInterpolation, 0f, 1f);
720         for (FooterWrapper footer : mFooters) {
721             if (footer != null) {
722                 footer.mView.setAlpha(mFooterAlpha);
723             }
724         }
725 
726         if (mMenuView != null) {
727             PagedOrientationHandler pagedOrientationHandler = getPagedOrientationHandler();
728             RecentsView recentsView = getRecentsView();
729             mMenuView.setPosition(getX() - recentsView.getScrollX(),
730                     getY() - recentsView.getScrollY(), pagedOrientationHandler);
731             mMenuView.setScaleX(getScaleX());
732             mMenuView.setScaleY(getScaleY());
733         }
734     }
735 
736     /**
737      * Sets the footer at the specific index and returns the previously set footer.
738      */
setFooter(int index, View view)739     public View setFooter(int index, View view) {
740         View oldFooter = null;
741 
742         // If the footer are is already collapsed, do not animate entry
743         boolean shouldAnimateEntry = mFooterVerticalOffset <= 0;
744 
745         if (mFooters[index] != null) {
746             oldFooter = mFooters[index].mView;
747             mFooters[index].release();
748             removeView(oldFooter);
749 
750             // If we are replacing an existing footer, do not animate entry
751             shouldAnimateEntry = false;
752         }
753         if (view != null) {
754             int indexToAdd = getChildCount();
755             for (int i = index - 1; i >= 0; i--) {
756                 if (mFooters[i] != null) {
757                     indexToAdd = indexOfChild(mFooters[i].mView);
758                     break;
759                 }
760             }
761 
762             addView(view, indexToAdd);
763             LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
764             layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL;
765             layoutParams.bottomMargin =
766                     ((MarginLayoutParams) mSnapshotView.getLayoutParams()).bottomMargin;
767             view.setAlpha(mFooterAlpha);
768             mFooters[index] = new FooterWrapper(view);
769             if (shouldAnimateEntry) {
770                 mFooters[index].animateEntry();
771             }
772         } else {
773             mFooters[index] = null;
774         }
775 
776         mStackHeight = 0;
777         for (FooterWrapper footer : mFooters) {
778             if (footer != null) {
779                 footer.setVerticalShift(mStackHeight);
780                 mStackHeight += footer.mExpectedHeight;
781             }
782         }
783 
784         return oldFooter;
785     }
786 
787     /**
788      * Sets the contextual chip.
789      *
790      * @param view Wrapper view containing contextual chip.
791      */
setContextualChip(View view)792     public void setContextualChip(View view) {
793         if (mContextualChipWrapper != null) {
794             removeView(mContextualChipWrapper);
795         }
796         if (view != null) {
797             mContextualChipWrapper = view;
798             LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
799                     LayoutParams.WRAP_CONTENT);
800             layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL;
801             int expectedChipHeight = getExpectedViewHeight(view);
802             float chipOffset = getResources().getDimension(R.dimen.chip_hint_vertical_offset);
803             layoutParams.bottomMargin = (int)
804                     (((MarginLayoutParams) mSnapshotView.getLayoutParams()).bottomMargin
805                             - expectedChipHeight + chipOffset);
806             mContextualChip = ((FrameLayout) mContextualChipWrapper).getChildAt(0);
807             mContextualChip.setScaleX(0f);
808             mContextualChip.setScaleY(0f);
809             GradientDrawable scrimDrawable = (GradientDrawable) getResources().getDrawable(
810                     R.drawable.chip_scrim_gradient, mActivity.getTheme());
811             float cornerRadius = getTaskCornerRadius();
812             scrimDrawable.setCornerRadii(
813                     new float[]{0, 0, 0, 0, cornerRadius, cornerRadius, cornerRadius,
814                             cornerRadius});
815             InsetDrawable scrimDrawableInset = new InsetDrawable(scrimDrawable, 0, 0, 0,
816                     (int) (expectedChipHeight - chipOffset));
817             mContextualChipWrapper.setBackground(scrimDrawableInset);
818             mContextualChipWrapper.setPadding(0, 0, 0, 0);
819             mContextualChipWrapper.setAlpha(0f);
820             addView(view, getChildCount(), layoutParams);
821             if (mContextualChip != null) {
822                 mContextualChip.animate().scaleX(1f).scaleY(1f).setDuration(50);
823             }
824             if (mContextualChipWrapper != null) {
825                 mChipTouchDelegate = new TransformingTouchDelegate(mContextualChipWrapper);
826                 mContextualChipWrapper.animate().alpha(1f).setDuration(50);
827             }
828         }
829     }
830 
getTaskCornerRadius()831     public float getTaskCornerRadius() {
832         return TaskCornerRadius.get(mActivity);
833     }
834 
835     /**
836      * Clears the contextual chip from TaskView.
837      *
838      * @return The contextual chip wrapper view to be recycled.
839      */
clearContextualChip()840     public View clearContextualChip() {
841         if (mContextualChipWrapper != null) {
842             removeView(mContextualChipWrapper);
843         }
844         View oldContextualChipWrapper = mContextualChipWrapper;
845         mContextualChipWrapper = null;
846         mContextualChip = null;
847         mChipTouchDelegate = null;
848         return oldContextualChipWrapper;
849     }
850 
851     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)852     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
853         super.onLayout(changed, left, top, right, bottom);
854         setPivotX((right - left) * 0.5f);
855         setPivotY(mSnapshotView.getTop() + mSnapshotView.getHeight() * 0.5f);
856         if (Utilities.ATLEAST_Q) {
857             SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(0, 0, getWidth(), getHeight());
858             setSystemGestureExclusionRects(SYSTEM_GESTURE_EXCLUSION_RECT);
859         }
860 
861         mStackHeight = 0;
862         for (FooterWrapper footer : mFooters) {
863             if (footer != null) {
864                 mStackHeight += footer.mView.getHeight();
865             }
866         }
867         updateFooterVerticalOffset(0);
868     }
869 
updateFooterVerticalOffset(float offset)870     private void updateFooterVerticalOffset(float offset) {
871         mFooterVerticalOffset = offset;
872 
873         for (FooterWrapper footer : mFooters) {
874             if (footer != null) {
875                 footer.updateFooterOffset();
876             }
877         }
878     }
879 
getCurveScaleForInterpolation(float linearInterpolation)880     public static float getCurveScaleForInterpolation(float linearInterpolation) {
881         float curveInterpolation = CURVE_INTERPOLATOR.getInterpolation(linearInterpolation);
882         return getCurveScaleForCurveInterpolation(curveInterpolation);
883     }
884 
getCurveScaleForCurveInterpolation(float curveInterpolation)885     private static float getCurveScaleForCurveInterpolation(float curveInterpolation) {
886         return 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR;
887     }
888 
setCurveScale(float curveScale)889     private void setCurveScale(float curveScale) {
890         mCurveScale = curveScale;
891         setScaleX(mCurveScale);
892         setScaleY(mCurveScale);
893     }
894 
getCurveScale()895     public float getCurveScale() {
896         return mCurveScale;
897     }
898 
setFillDismissGapTranslationX(float x)899     private void setFillDismissGapTranslationX(float x) {
900         mFillDismissGapTranslationX = x;
901         applyTranslationX();
902     }
903 
setFillDismissGapTranslationY(float y)904     private void setFillDismissGapTranslationY(float y) {
905         mFillDismissGapTranslationY = y;
906         applyTranslationY();
907     }
908 
setTaskOffsetTranslationX(float x)909     private void setTaskOffsetTranslationX(float x) {
910         mTaskOffsetTranslationX = x;
911         applyTranslationX();
912     }
913 
setTaskOffsetTranslationY(float y)914     private void setTaskOffsetTranslationY(float y) {
915         mTaskOffsetTranslationY = y;
916         applyTranslationY();
917     }
918 
applyTranslationX()919     private void applyTranslationX() {
920         setTranslationX(mFillDismissGapTranslationX + mTaskOffsetTranslationX);
921     }
922 
applyTranslationY()923     private void applyTranslationY() {
924         setTranslationY(mFillDismissGapTranslationY + mTaskOffsetTranslationY);
925     }
926 
getPrimaryFillDismissGapTranslationProperty()927     public FloatProperty<TaskView> getPrimaryFillDismissGapTranslationProperty() {
928         return getPagedOrientationHandler().getPrimaryValue(
929                 FILL_DISMISS_GAP_TRANSLATION_X, FILL_DISMISS_GAP_TRANSLATION_Y);
930     }
931 
getPrimaryTaskOffsetTranslationProperty()932     public FloatProperty<TaskView> getPrimaryTaskOffsetTranslationProperty() {
933         return getPagedOrientationHandler().getPrimaryValue(
934                 TASK_OFFSET_TRANSLATION_X, TASK_OFFSET_TRANSLATION_Y);
935     }
936 
937     @Override
hasOverlappingRendering()938     public boolean hasOverlappingRendering() {
939         // TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
940         return false;
941     }
942 
943     private static final class TaskOutlineProvider extends ViewOutlineProvider {
944 
945         private final int mMarginTop;
946         private FullscreenDrawParams mFullscreenParams;
947 
TaskOutlineProvider(Context context, FullscreenDrawParams fullscreenParams)948         TaskOutlineProvider(Context context, FullscreenDrawParams fullscreenParams) {
949             mMarginTop = context.getResources().getDimensionPixelSize(
950                     R.dimen.task_thumbnail_top_margin);
951             mFullscreenParams = fullscreenParams;
952         }
953 
setFullscreenParams(FullscreenDrawParams params)954         public void setFullscreenParams(FullscreenDrawParams params) {
955             mFullscreenParams = params;
956         }
957 
958         @Override
getOutline(View view, Outline outline)959         public void getOutline(View view, Outline outline) {
960             RectF insets = mFullscreenParams.mCurrentDrawnInsets;
961             float scale = mFullscreenParams.mScale;
962             outline.setRoundRect(0,
963                     (int) (mMarginTop * scale),
964                     (int) ((insets.left + view.getWidth() + insets.right) * scale),
965                     (int) ((insets.top + view.getHeight() + insets.bottom) * scale),
966                     mFullscreenParams.mCurrentDrawnCornerRadius);
967         }
968     }
969 
970     private class FooterWrapper extends ViewOutlineProvider {
971 
972         final View mView;
973         final ViewOutlineProvider mOldOutlineProvider;
974         final ViewOutlineProvider mDelegate;
975 
976         final int mExpectedHeight;
977         final int mOldPaddingBottom;
978 
979         int mAnimationOffset = 0;
980         int mEntryAnimationOffset = 0;
981 
FooterWrapper(View view)982         public FooterWrapper(View view) {
983             mView = view;
984             mOldOutlineProvider = view.getOutlineProvider();
985             mDelegate = mOldOutlineProvider == null
986                     ? ViewOutlineProvider.BACKGROUND : mOldOutlineProvider;
987 
988             mExpectedHeight = getExpectedViewHeight(view);
989             mOldPaddingBottom = view.getPaddingBottom();
990 
991             if (mOldOutlineProvider != null) {
992                 view.setOutlineProvider(this);
993                 view.setClipToOutline(true);
994             }
995         }
996 
setVerticalShift(int shift)997         public void setVerticalShift(int shift) {
998             mView.setPadding(mView.getPaddingLeft(), mView.getPaddingTop(),
999                     mView.getPaddingRight(), mOldPaddingBottom + shift);
1000         }
1001 
1002         @Override
getOutline(View view, Outline outline)1003         public void getOutline(View view, Outline outline) {
1004             mDelegate.getOutline(view, outline);
1005             outline.offset(0, -mAnimationOffset - mEntryAnimationOffset);
1006         }
1007 
updateFooterOffset()1008         void updateFooterOffset() {
1009             float offset = Utilities.or(mFooterVerticalOffset, mModalness);
1010             mAnimationOffset = Math.round(mStackHeight * offset);
1011             mView.setTranslationY(mAnimationOffset + mEntryAnimationOffset
1012                     + mCurrentFullscreenParams.mCurrentDrawnInsets.bottom
1013                     + mCurrentFullscreenParams.mCurrentDrawnInsets.top);
1014             mView.invalidateOutline();
1015         }
1016 
release()1017         void release() {
1018             mView.setOutlineProvider(mOldOutlineProvider);
1019             setVerticalShift(0);
1020         }
1021 
animateEntry()1022         void animateEntry() {
1023             ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
1024             animator.addUpdateListener(anim -> {
1025                float factor = 1 - anim.getAnimatedFraction();
1026                int totalShift = mExpectedHeight + mView.getPaddingBottom() - mOldPaddingBottom;
1027                 mEntryAnimationOffset = Math.round(factor * totalShift);
1028                 updateFooterOffset();
1029             });
1030             animator.setDuration(100);
1031             animator.start();
1032         }
1033     }
1034 
getExpectedViewHeight(View view)1035     private int getExpectedViewHeight(View view) {
1036         int expectedHeight;
1037         int h = view.getLayoutParams().height;
1038         if (h > 0) {
1039             expectedHeight = h;
1040         } else {
1041             int m = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY - 1, MeasureSpec.AT_MOST);
1042             view.measure(m, m);
1043             expectedHeight = view.getMeasuredHeight();
1044         }
1045         return expectedHeight;
1046     }
1047 
1048     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)1049     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1050         super.onInitializeAccessibilityNodeInfo(info);
1051 
1052         info.addAction(
1053                 new AccessibilityNodeInfo.AccessibilityAction(R.string.accessibility_close,
1054                         getContext().getText(R.string.accessibility_close)));
1055 
1056         final Context context = getContext();
1057         for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) {
1058             info.addAction(s.createAccessibilityAction(context));
1059         }
1060 
1061         if (mDigitalWellBeingToast.hasLimit()) {
1062             info.addAction(
1063                     new AccessibilityNodeInfo.AccessibilityAction(
1064                             R.string.accessibility_app_usage_settings,
1065                             getContext().getText(R.string.accessibility_app_usage_settings)));
1066         }
1067 
1068         final RecentsView recentsView = getRecentsView();
1069         final AccessibilityNodeInfo.CollectionItemInfo itemInfo =
1070                 AccessibilityNodeInfo.CollectionItemInfo.obtain(
1071                         0, 1, recentsView.getTaskViewCount() - recentsView.indexOfChild(this) - 1,
1072                         1, false);
1073         info.setCollectionItemInfo(itemInfo);
1074     }
1075 
1076     @Override
performAccessibilityAction(int action, Bundle arguments)1077     public boolean performAccessibilityAction(int action, Bundle arguments) {
1078         if (action == R.string.accessibility_close) {
1079             getRecentsView().dismissTask(this, true /*animateTaskView*/,
1080                     true /*removeTask*/);
1081             return true;
1082         }
1083 
1084         if (action == R.string.accessibility_app_usage_settings) {
1085             mDigitalWellBeingToast.openAppUsageSettings(this);
1086             return true;
1087         }
1088 
1089         for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) {
1090             if (s.hasHandlerForAction(action)) {
1091                 s.onClick(this);
1092                 return true;
1093             }
1094         }
1095 
1096         return super.performAccessibilityAction(action, arguments);
1097     }
1098 
getRecentsView()1099     public RecentsView getRecentsView() {
1100         return (RecentsView) getParent();
1101     }
1102 
getPagedOrientationHandler()1103     PagedOrientationHandler getPagedOrientationHandler() {
1104         return getRecentsView().mOrientationState.getOrientationHandler();
1105     }
1106 
notifyTaskLaunchFailed(String tag)1107     public void notifyTaskLaunchFailed(String tag) {
1108         String msg = "Failed to launch task";
1109         if (mTask != null) {
1110             msg += " (task=" + mTask.key.baseIntent + " userId=" + mTask.key.userId + ")";
1111         }
1112         Log.w(tag, msg);
1113         Toast.makeText(getContext(), R.string.activity_not_available, LENGTH_SHORT).show();
1114     }
1115 
1116     /**
1117      * Hides the icon and shows insets when this TaskView is about to be shown fullscreen.
1118      *
1119      * @param progress: 0 = show icon and no insets; 1 = don't show icon and show full insets.
1120      */
setFullscreenProgress(float progress)1121     public void setFullscreenProgress(float progress) {
1122         progress = Utilities.boundToRange(progress, 0, 1);
1123         mFullscreenProgress = progress;
1124         boolean isFullscreen = mFullscreenProgress > 0;
1125         mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
1126         setClipChildren(!isFullscreen);
1127         setClipToPadding(!isFullscreen);
1128 
1129         TaskThumbnailView thumbnail = getThumbnail();
1130         updateCurrentFullscreenParams(thumbnail.getPreviewPositionHelper());
1131 
1132         if (!getRecentsView().isTaskIconScaledDown(this)) {
1133             // Some of the items in here are dependent on the current fullscreen params, but don't
1134             // update them if the icon is supposed to be scaled down.
1135             setIconScaleAndDim(progress, true /* invert */);
1136         }
1137 
1138         thumbnail.setFullscreenParams(mCurrentFullscreenParams);
1139         mOutlineProvider.setFullscreenParams(mCurrentFullscreenParams);
1140         invalidateOutline();
1141     }
1142 
1143     void updateCurrentFullscreenParams(PreviewPositionHelper previewPositionHelper) {
1144         if (getRecentsView() == null) {
1145             return;
1146         }
1147         mCurrentFullscreenParams.setProgress(
1148                 mFullscreenProgress,
1149                 getRecentsView().getScaleX(),
1150                 getWidth(), mActivity.getDeviceProfile(),
1151                 previewPositionHelper);
1152     }
1153 
1154     public boolean isRunningTask() {
1155         if (getRecentsView() == null) {
1156             return false;
1157         }
1158         return this == getRecentsView().getRunningTaskView();
1159     }
1160 
1161     public void setShowScreenshot(boolean showScreenshot) {
1162         mShowScreenshot = showScreenshot;
1163     }
1164 
1165     public boolean showScreenshot() {
1166         if (!isRunningTask()) {
1167             return true;
1168         }
1169         return mShowScreenshot;
1170     }
1171 
1172     public void setOverlayEnabled(boolean overlayEnabled) {
1173         mSnapshotView.setOverlayEnabled(overlayEnabled);
1174     }
1175 
1176     /**
1177      * We update and subsequently draw these in {@link #setFullscreenProgress(float)}.
1178      */
1179     public static class FullscreenDrawParams {
1180 
1181         private final float mCornerRadius;
1182         private final float mWindowCornerRadius;
1183 
1184         public RectF mCurrentDrawnInsets = new RectF();
1185         public float mCurrentDrawnCornerRadius;
1186         /** The current scale we apply to the thumbnail to adjust for new left/right insets. */
1187         public float mScale = 1;
1188 
1189         public FullscreenDrawParams(Context context) {
1190             mCornerRadius = TaskCornerRadius.get(context);
1191             mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context.getResources());
1192 
1193             mCurrentDrawnCornerRadius = mCornerRadius;
1194         }
1195 
1196         /**
1197          * Sets the progress in range [0, 1]
1198          */
1199         public void setProgress(float fullscreenProgress, float parentScale, int previewWidth,
1200                 DeviceProfile dp, PreviewPositionHelper pph) {
1201             RectF insets = pph.getInsetsToDrawInFullscreen();
1202 
1203             float currentInsetsLeft = insets.left * fullscreenProgress;
1204             float currentInsetsRight = insets.right * fullscreenProgress;
1205             mCurrentDrawnInsets.set(currentInsetsLeft, insets.top * fullscreenProgress,
1206                     currentInsetsRight, insets.bottom * fullscreenProgress);
1207             float fullscreenCornerRadius = dp.isMultiWindowMode ? 0 : mWindowCornerRadius;
1208 
1209             mCurrentDrawnCornerRadius =
1210                     Utilities.mapRange(fullscreenProgress, mCornerRadius, fullscreenCornerRadius)
1211                             / parentScale;
1212 
1213             // We scaled the thumbnail to fit the content (excluding insets) within task view width.
1214             // Now that we are drawing left/right insets again, we need to scale down to fit them.
1215             if (previewWidth > 0) {
1216                 mScale = previewWidth / (previewWidth + currentInsetsLeft + currentInsetsRight);
1217             }
1218         }
1219 
1220     }
1221 }
1222