• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.systemui.recents.views;
18 
19 import android.animation.Animator;
20 import android.animation.ObjectAnimator;
21 import android.animation.ValueAnimator;
22 import android.content.Context;
23 import android.graphics.*;
24 import android.graphics.drawable.Drawable;
25 import android.graphics.drawable.LayerDrawable;
26 import android.util.AttributeSet;
27 import android.view.View;
28 import android.view.ViewOutlineProvider;
29 import android.view.animation.AccelerateInterpolator;
30 import android.widget.FrameLayout;
31 import com.android.systemui.R;
32 import com.android.systemui.recents.AlternateRecentsComponent;
33 import com.android.systemui.recents.Constants;
34 import com.android.systemui.recents.RecentsConfiguration;
35 import com.android.systemui.recents.model.RecentsTaskLoader;
36 import com.android.systemui.recents.model.Task;
37 
38 /* A task view */
39 public class TaskView extends FrameLayout implements Task.TaskCallbacks,
40         TaskViewFooter.TaskFooterViewCallbacks, View.OnClickListener, View.OnLongClickListener {
41 
42     /** The TaskView callbacks */
43     interface TaskViewCallbacks {
onTaskViewAppIconClicked(TaskView tv)44         public void onTaskViewAppIconClicked(TaskView tv);
onTaskViewAppInfoClicked(TaskView tv)45         public void onTaskViewAppInfoClicked(TaskView tv);
onTaskViewClicked(TaskView tv, Task task, boolean lockToTask)46         public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask);
onTaskViewDismissed(TaskView tv)47         public void onTaskViewDismissed(TaskView tv);
onTaskViewClipStateChanged(TaskView tv)48         public void onTaskViewClipStateChanged(TaskView tv);
onTaskViewFullScreenTransitionCompleted()49         public void onTaskViewFullScreenTransitionCompleted();
onTaskViewFocusChanged(TaskView tv, boolean focused)50         public void onTaskViewFocusChanged(TaskView tv, boolean focused);
51     }
52 
53     RecentsConfiguration mConfig;
54 
55     float mTaskProgress;
56     ObjectAnimator mTaskProgressAnimator;
57     ObjectAnimator mDimAnimator;
58     float mMaxDimScale;
59     int mDim;
60     AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(1f);
61     PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.MULTIPLY);
62 
63     Task mTask;
64     boolean mTaskDataLoaded;
65     boolean mIsFocused;
66     boolean mFocusAnimationsEnabled;
67     boolean mIsFullScreenView;
68     boolean mClipViewInStack;
69     AnimateableViewBounds mViewBounds;
70     Paint mLayerPaint = new Paint();
71 
72     View mContent;
73     TaskViewThumbnail mThumbnailView;
74     TaskViewHeader mHeaderView;
75     TaskViewFooter mFooterView;
76     View mActionButtonView;
77     TaskViewCallbacks mCb;
78 
79     // Optimizations
80     ValueAnimator.AnimatorUpdateListener mUpdateDimListener =
81             new ValueAnimator.AnimatorUpdateListener() {
82                 @Override
83                 public void onAnimationUpdate(ValueAnimator animation) {
84                     setTaskProgress((Float) animation.getAnimatedValue());
85                 }
86             };
87 
88 
TaskView(Context context)89     public TaskView(Context context) {
90         this(context, null);
91     }
92 
TaskView(Context context, AttributeSet attrs)93     public TaskView(Context context, AttributeSet attrs) {
94         this(context, attrs, 0);
95     }
96 
TaskView(Context context, AttributeSet attrs, int defStyleAttr)97     public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
98         this(context, attrs, defStyleAttr, 0);
99     }
100 
TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)101     public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
102         super(context, attrs, defStyleAttr, defStyleRes);
103         mConfig = RecentsConfiguration.getInstance();
104         mMaxDimScale = mConfig.taskStackMaxDim / 255f;
105         mClipViewInStack = true;
106         mViewBounds = new AnimateableViewBounds(this, mConfig.taskViewRoundedCornerRadiusPx);
107         setTaskProgress(getTaskProgress());
108         setDim(getDim());
109         if (mConfig.fakeShadows) {
110             setBackground(new FakeShadowDrawable(context.getResources(), mConfig));
111         }
112         setOutlineProvider(mViewBounds);
113     }
114 
115     /** Set callback */
setCallbacks(TaskViewCallbacks cb)116     void setCallbacks(TaskViewCallbacks cb) {
117         mCb = cb;
118     }
119 
120     /** Gets the task */
getTask()121     Task getTask() {
122         return mTask;
123     }
124 
125     /** Returns the view bounds. */
getViewBounds()126     AnimateableViewBounds getViewBounds() {
127         return mViewBounds;
128     }
129 
130     @Override
onFinishInflate()131     protected void onFinishInflate() {
132         // Bind the views
133         mContent = findViewById(R.id.task_view_content);
134         mHeaderView = (TaskViewHeader) findViewById(R.id.task_view_bar);
135         mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail);
136         mThumbnailView.enableTaskBarClip(mHeaderView);
137         mActionButtonView = findViewById(R.id.lock_to_app_fab);
138         mActionButtonView.setOutlineProvider(new ViewOutlineProvider() {
139             @Override
140             public void getOutline(View view, Outline outline) {
141                 // Set the outline to match the FAB background
142                 outline.setOval(0, 0, mActionButtonView.getWidth(), mActionButtonView.getHeight());
143             }
144         });
145         if (mFooterView != null) {
146             mFooterView.setCallbacks(this);
147         }
148     }
149 
150     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)151     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
152         int width = MeasureSpec.getSize(widthMeasureSpec);
153         int height = MeasureSpec.getSize(heightMeasureSpec);
154 
155         int widthWithoutPadding = width - mPaddingLeft - mPaddingRight;
156         int heightWithoutPadding = height - mPaddingTop - mPaddingBottom;
157 
158         // Measure the content
159         mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
160                 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
161 
162         // Measure the bar view, thumbnail, and footer
163         mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
164                 MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
165         if (mFooterView != null) {
166             mFooterView.measure(
167                     MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
168                     MeasureSpec.makeMeasureSpec(mConfig.taskViewLockToAppButtonHeight,
169                             MeasureSpec.EXACTLY));
170         }
171         mActionButtonView.measure(
172                 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.AT_MOST),
173                 MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.AT_MOST));
174         if (mIsFullScreenView) {
175             // Measure the thumbnail height to be the full dimensions
176             mThumbnailView.measure(
177                     MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
178                     MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY));
179         } else {
180             // Measure the thumbnail to be square
181             mThumbnailView.measure(
182                     MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
183                     MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
184         }
185         setMeasuredDimension(width, height);
186         invalidateOutline();
187     }
188 
189     /** Synchronizes this view's properties with the task's transform */
updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration)190     void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) {
191         updateViewPropertiesToTaskTransform(toTransform, duration, null);
192     }
193 
updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration, ValueAnimator.AnimatorUpdateListener updateCallback)194     void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration,
195                                              ValueAnimator.AnimatorUpdateListener updateCallback) {
196         // If we are a full screen view, then only update the Z to keep it in order
197         // XXX: Also update/animate the dim as well
198         if (mIsFullScreenView) {
199             if (!mConfig.fakeShadows &&
200                     toTransform.hasTranslationZChangedFrom(getTranslationZ())) {
201                 setTranslationZ(toTransform.translationZ);
202             }
203             return;
204         }
205 
206         // Apply the transform
207         toTransform.applyToTaskView(this, duration, mConfig.fastOutSlowInInterpolator, false,
208                 !mConfig.fakeShadows, updateCallback);
209 
210         // Update the task progress
211         if (mTaskProgressAnimator != null) {
212             mTaskProgressAnimator.removeAllListeners();
213             mTaskProgressAnimator.cancel();
214         }
215         if (duration <= 0) {
216             setTaskProgress(toTransform.p);
217         } else {
218             mTaskProgressAnimator = ObjectAnimator.ofFloat(this, "taskProgress", toTransform.p);
219             mTaskProgressAnimator.setDuration(duration);
220             mTaskProgressAnimator.addUpdateListener(mUpdateDimListener);
221             mTaskProgressAnimator.start();
222         }
223     }
224 
225     /** Resets this view's properties */
resetViewProperties()226     void resetViewProperties() {
227         setDim(0);
228         TaskViewTransform.reset(this);
229     }
230 
231     /**
232      * When we are un/filtering, this method will set up the transform that we are animating to,
233      * in order to hide the task.
234      */
prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform)235     void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) {
236         // Fade the view out and slide it away
237         toTransform.alpha = 0f;
238         toTransform.translationY += 200;
239         toTransform.translationZ = 0;
240     }
241 
242     /**
243      * When we are un/filtering, this method will setup the transform that we are animating from,
244      * in order to show the task.
245      */
prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform)246     void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) {
247         // Fade the view in
248         fromTransform.alpha = 0f;
249     }
250 
251     /** Prepares this task view for the enter-recents animations.  This is called earlier in the
252      * first layout because the actual animation into recents may take a long time. */
prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, boolean occludesLaunchTarget, int offscreenY)253     void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask,
254                                              boolean occludesLaunchTarget, int offscreenY) {
255         int initialDim = getDim();
256         if (mConfig.launchedFromAppWithScreenshot) {
257             if (isTaskViewLaunchTargetTask) {
258                 // Hide the footer during the transition in, and animate it out afterwards?
259                 if (mFooterView != null) {
260                     mFooterView.animateFooterVisibility(false, 0);
261                 }
262             } else {
263                 // Don't do anything for the side views when animating in
264             }
265 
266         } else if (mConfig.launchedFromAppWithThumbnail) {
267             if (isTaskViewLaunchTargetTask) {
268                 // Hide the action button if it exists
269                 mActionButtonView.setAlpha(0f);
270                 // Set the dim to 0 so we can animate it in
271                 initialDim = 0;
272             } else if (occludesLaunchTarget) {
273                 // Move the task view off screen (below) so we can animate it in
274                 setTranslationY(offscreenY);
275             }
276 
277         } else if (mConfig.launchedFromHome) {
278             // Move the task view off screen (below) so we can animate it in
279             setTranslationY(offscreenY);
280             setTranslationZ(0);
281             setScaleX(1f);
282             setScaleY(1f);
283         }
284         // Apply the current dim
285         setDim(initialDim);
286         // Prepare the thumbnail view alpha
287         mThumbnailView.prepareEnterRecentsAnimation(isTaskViewLaunchTargetTask);
288     }
289 
290     /** Animates this task view as it enters recents */
startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx)291     void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
292         final TaskViewTransform transform = ctx.currentTaskTransform;
293         int startDelay = 0;
294 
295         if (mConfig.launchedFromAppWithScreenshot) {
296             if (mTask.isLaunchTarget) {
297                 Rect taskRect = ctx.currentTaskRect;
298                 int duration = mConfig.taskViewEnterFromHomeDuration * 10;
299                 int windowInsetTop = mConfig.systemInsets.top; // XXX: Should be for the window
300                 float taskScale = ((float) taskRect.width() / getMeasuredWidth()) * transform.scale;
301                 float scaledYOffset = ((1f - taskScale) * getMeasuredHeight()) / 2;
302                 float scaledWindowInsetTop = (int) (taskScale * windowInsetTop);
303                 float scaledTranslationY = taskRect.top + transform.translationY -
304                         (scaledWindowInsetTop + scaledYOffset);
305                 startDelay = mConfig.taskViewEnterFromHomeStaggerDelay;
306 
307                 // Animate the top clip
308                 mViewBounds.animateClipTop(windowInsetTop, duration,
309                         new ValueAnimator.AnimatorUpdateListener() {
310                     @Override
311                     public void onAnimationUpdate(ValueAnimator animation) {
312                         int y = (Integer) animation.getAnimatedValue();
313                         mHeaderView.setTranslationY(y);
314                     }
315                 });
316                 // Animate the bottom or right clip
317                 int size = Math.round((taskRect.width() / taskScale));
318                 if (mConfig.hasHorizontalLayout()) {
319                     mViewBounds.animateClipRight(getMeasuredWidth() - size, duration);
320                 } else {
321                     mViewBounds.animateClipBottom(getMeasuredHeight() - (windowInsetTop + size), duration);
322                 }
323                 // Animate the task bar of the first task view
324                 animate()
325                         .scaleX(taskScale)
326                         .scaleY(taskScale)
327                         .translationY(scaledTranslationY)
328                         .setDuration(duration)
329                         .withEndAction(new Runnable() {
330                             @Override
331                             public void run() {
332                                 setIsFullScreen(false);
333                                 requestLayout();
334 
335                                 // Reset the clip
336                                 mViewBounds.setClipTop(0);
337                                 mViewBounds.setClipBottom(0);
338                                 mViewBounds.setClipRight(0);
339                                 // Reset the bar translation
340                                 mHeaderView.setTranslationY(0);
341                                 // Animate the footer into view (if it is the front most task)
342                                 animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration);
343 
344                                 // Unbind the thumbnail from the screenshot
345                                 RecentsTaskLoader.getInstance().loadTaskData(mTask);
346                                 // Recycle the full screen screenshot
347                                 AlternateRecentsComponent.consumeLastScreenshot();
348 
349                                 mCb.onTaskViewFullScreenTransitionCompleted();
350 
351                                 // Decrement the post animation trigger
352                                 ctx.postAnimationTrigger.decrement();
353                             }
354                         })
355                         .start();
356             } else {
357                 // Animate the footer into view
358                 animateFooterVisibility(true, 0);
359             }
360             ctx.postAnimationTrigger.increment();
361 
362         } else if (mConfig.launchedFromAppWithThumbnail) {
363             if (mTask.isLaunchTarget) {
364                 // Animate the dim/overlay
365                 if (Constants.DebugFlags.App.EnableThumbnailAlphaOnFrontmost) {
366                     // Animate the thumbnail alpha before the dim animation (to prevent updating the
367                     // hardware layer)
368                     mThumbnailView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay,
369                             new Runnable() {
370                                 @Override
371                                 public void run() {
372                                     animateDimToProgress(0, mConfig.taskBarEnterAnimDuration,
373                                             ctx.postAnimationTrigger.decrementOnAnimationEnd());
374                                 }
375                             });
376                 } else {
377                     // Immediately start the dim animation
378                     animateDimToProgress(mConfig.taskBarEnterAnimDelay,
379                             mConfig.taskBarEnterAnimDuration,
380                             ctx.postAnimationTrigger.decrementOnAnimationEnd());
381                 }
382                 ctx.postAnimationTrigger.increment();
383 
384                 // Animate the footer into view
385                 animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration);
386 
387                 // Animate the action button in
388                 mActionButtonView.animate().alpha(1f)
389                         .setStartDelay(mConfig.taskBarEnterAnimDelay)
390                         .setDuration(mConfig.taskBarEnterAnimDuration)
391                         .setInterpolator(mConfig.fastOutLinearInInterpolator)
392                         .withLayer()
393                         .start();
394             } else {
395                 // Animate the task up if it was occluding the launch target
396                 if (ctx.currentTaskOccludesLaunchTarget) {
397                     setTranslationY(transform.translationY + mConfig.taskViewAffiliateGroupEnterOffsetPx);
398                     setAlpha(0f);
399                     animate().alpha(1f)
400                             .translationY(transform.translationY)
401                             .setStartDelay(mConfig.taskBarEnterAnimDelay)
402                             .setUpdateListener(null)
403                             .setInterpolator(mConfig.fastOutSlowInInterpolator)
404                             .setDuration(mConfig.taskViewEnterFromHomeDuration)
405                             .withEndAction(new Runnable() {
406                                 @Override
407                                 public void run() {
408                                     // Decrement the post animation trigger
409                                     ctx.postAnimationTrigger.decrement();
410                                 }
411                             })
412                             .start();
413                     ctx.postAnimationTrigger.increment();
414                 }
415             }
416             startDelay = mConfig.taskBarEnterAnimDelay;
417 
418         } else if (mConfig.launchedFromHome) {
419             // Animate the tasks up
420             int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1);
421             int delay = mConfig.taskViewEnterFromHomeDelay +
422                     frontIndex * mConfig.taskViewEnterFromHomeStaggerDelay;
423 
424             setScaleX(transform.scale);
425             setScaleY(transform.scale);
426             if (!mConfig.fakeShadows) {
427                 animate().translationZ(transform.translationZ);
428             }
429             animate()
430                     .translationY(transform.translationY)
431                     .setStartDelay(delay)
432                     .setUpdateListener(ctx.updateListener)
433                     .setInterpolator(mConfig.quintOutInterpolator)
434                     .setDuration(mConfig.taskViewEnterFromHomeDuration +
435                             frontIndex * mConfig.taskViewEnterFromHomeStaggerDelay)
436                     .withEndAction(new Runnable() {
437                         @Override
438                         public void run() {
439                             // Decrement the post animation trigger
440                             ctx.postAnimationTrigger.decrement();
441                         }
442                     })
443                     .start();
444             ctx.postAnimationTrigger.increment();
445 
446             // Animate the footer into view
447             animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration);
448             startDelay = delay;
449 
450         } else {
451             // Animate the footer into view
452             animateFooterVisibility(true, 0);
453         }
454 
455         // Enable the focus animations from this point onwards so that they aren't affected by the
456         // window transitions
457         postDelayed(new Runnable() {
458             @Override
459             public void run() {
460                 enableFocusAnimations();
461             }
462         }, (startDelay / 2));
463     }
464 
465     /** Animates this task view as it leaves recents by pressing home. */
startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx)466     void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
467         animate()
468                 .translationY(ctx.offscreenTranslationY)
469                 .setStartDelay(0)
470                 .setUpdateListener(null)
471                 .setInterpolator(mConfig.fastOutLinearInInterpolator)
472                 .setDuration(mConfig.taskViewExitToHomeDuration)
473                 .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable())
474                 .start();
475         ctx.postAnimationTrigger.increment();
476     }
477 
478     /** Animates this task view as it exits recents */
startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask, boolean occludesLaunchTarget, boolean lockToTask)479     void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask,
480             boolean occludesLaunchTarget, boolean lockToTask) {
481         if (isLaunchingTask) {
482             // Animate the thumbnail alpha back into full opacity for the window animation out
483             mThumbnailView.startLaunchTaskAnimation(postAnimRunnable);
484 
485             // Animate the dim
486             if (mDim > 0) {
487                 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0);
488                 anim.setDuration(mConfig.taskBarExitAnimDuration);
489                 anim.setInterpolator(mConfig.fastOutLinearInInterpolator);
490                 anim.start();
491             }
492 
493             // Animate the action button away
494             if (!lockToTask) {
495                 float toScale = 0.9f;
496                 mActionButtonView.animate()
497                         .scaleX(toScale)
498                         .scaleY(toScale);
499             }
500             mActionButtonView.animate()
501                     .alpha(0f)
502                     .setStartDelay(0)
503                     .setDuration(mConfig.taskBarExitAnimDuration)
504                     .setInterpolator(mConfig.fastOutLinearInInterpolator)
505                     .withLayer()
506                     .start();
507         } else {
508             // Hide the dismiss button
509             mHeaderView.startLaunchTaskDismissAnimation();
510             // If this is another view in the task grouping and is in front of the launch task,
511             // animate it away first
512             if (occludesLaunchTarget) {
513                 animate().alpha(0f)
514                     .translationY(getTranslationY() + mConfig.taskViewAffiliateGroupEnterOffsetPx)
515                     .setStartDelay(0)
516                     .setUpdateListener(null)
517                     .setInterpolator(mConfig.fastOutLinearInInterpolator)
518                     .setDuration(mConfig.taskBarExitAnimDuration)
519                     .start();
520             }
521         }
522     }
523 
524     /** Animates the deletion of this task view */
startDeleteTaskAnimation(final Runnable r)525     void startDeleteTaskAnimation(final Runnable r) {
526         // Disabling clipping with the stack while the view is animating away
527         setClipViewInStack(false);
528 
529         animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx)
530             .alpha(0f)
531             .setStartDelay(0)
532             .setUpdateListener(null)
533             .setInterpolator(mConfig.fastOutSlowInInterpolator)
534             .setDuration(mConfig.taskViewRemoveAnimDuration)
535             .withEndAction(new Runnable() {
536                 @Override
537                 public void run() {
538                     // We just throw this into a runnable because starting a view property
539                     // animation using layers can cause inconsisten results if we try and
540                     // update the layers while the animation is running.  In some cases,
541                     // the runnabled passed in may start an animation which also uses layers
542                     // so we defer all this by posting this.
543                     r.run();
544 
545                     // Re-enable clipping with the stack (we will reuse this view)
546                     setClipViewInStack(true);
547                 }
548             })
549             .start();
550     }
551 
552     /** Animates this task view if the user does not interact with the stack after a certain time. */
startNoUserInteractionAnimation()553     void startNoUserInteractionAnimation() {
554         mHeaderView.startNoUserInteractionAnimation();
555     }
556 
557     /** Mark this task view that the user does has not interacted with the stack after a certain time. */
setNoUserInteractionState()558     void setNoUserInteractionState() {
559         mHeaderView.setNoUserInteractionState();
560     }
561 
562     /** Dismisses this task. */
dismissTask()563     void dismissTask() {
564         // Animate out the view and call the callback
565         final TaskView tv = this;
566         startDeleteTaskAnimation(new Runnable() {
567             @Override
568             public void run() {
569                 mCb.onTaskViewDismissed(tv);
570             }
571         });
572         // Hide the footer
573         animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration);
574     }
575 
576     /** Sets whether this task view is full screen or not. */
setIsFullScreen(boolean isFullscreen)577     void setIsFullScreen(boolean isFullscreen) {
578         mIsFullScreenView = isFullscreen;
579         mHeaderView.setIsFullscreen(isFullscreen);
580         if (isFullscreen) {
581             // If we are full screen, then disable the bottom outline clip for the footer
582             mViewBounds.setOutlineClipBottom(0);
583         }
584     }
585 
586     /** Returns whether this task view should currently be drawn as a full screen view. */
isFullScreenView()587     boolean isFullScreenView() {
588         return mIsFullScreenView;
589     }
590 
591     /**
592      * Returns whether this view should be clipped, or any views below should clip against this
593      * view.
594      */
shouldClipViewInStack()595     boolean shouldClipViewInStack() {
596         return mClipViewInStack && !mIsFullScreenView && (getVisibility() == View.VISIBLE);
597     }
598 
599     /** Sets whether this view should be clipped, or clipped against. */
setClipViewInStack(boolean clip)600     void setClipViewInStack(boolean clip) {
601         if (clip != mClipViewInStack) {
602             mClipViewInStack = clip;
603             mCb.onTaskViewClipStateChanged(this);
604         }
605     }
606 
607     /** Gets the max footer height. */
getMaxFooterHeight()608     public int getMaxFooterHeight() {
609         if (mFooterView != null) {
610             return mFooterView.mMaxFooterHeight;
611         } else {
612             return 0;
613         }
614     }
615 
616     /** Animates the footer into and out of view. */
animateFooterVisibility(boolean visible, int duration)617     void animateFooterVisibility(boolean visible, int duration) {
618         // Hide the footer if we are a full screen view
619         if (mIsFullScreenView) return;
620         // Hide the footer if the current task can not be locked to
621         if (!mTask.lockToTaskEnabled || !mTask.lockToThisTask) return;
622         // Otherwise, animate the visibility
623         if (mFooterView != null) {
624             mFooterView.animateFooterVisibility(visible, duration);
625         }
626     }
627 
628     /** Sets the current task progress. */
setTaskProgress(float p)629     public void setTaskProgress(float p) {
630         mTaskProgress = p;
631         mViewBounds.setAlpha(p);
632         updateDimFromTaskProgress();
633     }
634 
635     /** Returns the current task progress. */
getTaskProgress()636     public float getTaskProgress() {
637         return mTaskProgress;
638     }
639 
640     /** Returns the current dim. */
setDim(int dim)641     public void setDim(int dim) {
642         mDim = dim;
643         if (mDimAnimator != null) {
644             mDimAnimator.removeAllListeners();
645             mDimAnimator.cancel();
646         }
647         if (mConfig.useHardwareLayers) {
648             // Defer setting hardware layers if we have not yet measured, or there is no dim to draw
649             if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
650                 if (mDimAnimator != null) {
651                     mDimAnimator.removeAllListeners();
652                     mDimAnimator.cancel();
653                 }
654 
655                 int inverse = 255 - mDim;
656                 mDimColorFilter.setColor(Color.argb(0xFF, inverse, inverse, inverse));
657                 mLayerPaint.setColorFilter(mDimColorFilter);
658                 mContent.setLayerType(LAYER_TYPE_HARDWARE, mLayerPaint);
659             }
660         } else {
661             float dimAlpha = mDim / 255.0f;
662             if (mThumbnailView != null) {
663                 mThumbnailView.setDimAlpha(dimAlpha);
664             }
665             if (mHeaderView != null) {
666                 mHeaderView.setDimAlpha(dim);
667             }
668         }
669     }
670 
671     /** Returns the current dim. */
getDim()672     public int getDim() {
673         return mDim;
674     }
675 
676     /** Animates the dim to the task progress. */
animateDimToProgress(int delay, int duration, Animator.AnimatorListener postAnimRunnable)677     void animateDimToProgress(int delay, int duration, Animator.AnimatorListener postAnimRunnable) {
678         // Animate the dim into view as well
679         int toDim = getDimFromTaskProgress();
680         if (toDim != getDim()) {
681             ObjectAnimator anim = ObjectAnimator.ofInt(TaskView.this, "dim", toDim);
682             anim.setStartDelay(delay);
683             anim.setDuration(duration);
684             if (postAnimRunnable != null) {
685                 anim.addListener(postAnimRunnable);
686             }
687             anim.start();
688         }
689     }
690 
691     /** Compute the dim as a function of the scale of this view. */
getDimFromTaskProgress()692     int getDimFromTaskProgress() {
693         float dim = mMaxDimScale * mDimInterpolator.getInterpolation(1f - mTaskProgress);
694         return (int) (dim * 255);
695     }
696 
697     /** Update the dim as a function of the scale of this view. */
updateDimFromTaskProgress()698     void updateDimFromTaskProgress() {
699         setDim(getDimFromTaskProgress());
700     }
701 
702     /**** View focus state ****/
703 
704     /**
705      * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen
706      * if the view is not currently visible, or we are in touch state (where we still want to keep
707      * track of focus).
708      */
setFocusedTask()709     public void setFocusedTask() {
710         mIsFocused = true;
711         if (mFocusAnimationsEnabled) {
712             // Focus the header bar
713             mHeaderView.onTaskViewFocusChanged(true);
714         }
715         // Update the thumbnail alpha with the focus
716         mThumbnailView.onFocusChanged(true);
717         // Call the callback
718         mCb.onTaskViewFocusChanged(this, true);
719         // Workaround, we don't always want it focusable in touch mode, but we want the first task
720         // to be focused after the enter-recents animation, which can be triggered from either touch
721         // or keyboard
722         setFocusableInTouchMode(true);
723         requestFocus();
724         setFocusableInTouchMode(false);
725         invalidate();
726     }
727 
728     /**
729      * Unsets the focused task explicitly.
730      */
unsetFocusedTask()731     void unsetFocusedTask() {
732         mIsFocused = false;
733         if (mFocusAnimationsEnabled) {
734             // Un-focus the header bar
735             mHeaderView.onTaskViewFocusChanged(false);
736         }
737 
738         // Update the thumbnail alpha with the focus
739         mThumbnailView.onFocusChanged(false);
740         // Call the callback
741         mCb.onTaskViewFocusChanged(this, false);
742         invalidate();
743     }
744 
745     /**
746      * Updates the explicitly focused state when the view focus changes.
747      */
748     @Override
onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)749     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
750         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
751         if (!gainFocus) {
752             unsetFocusedTask();
753         }
754     }
755 
756     /**
757      * Returns whether we have explicitly been focused.
758      */
isFocusedTask()759     public boolean isFocusedTask() {
760         return mIsFocused || isFocused();
761     }
762 
763     /** Enables all focus animations. */
enableFocusAnimations()764     void enableFocusAnimations() {
765         boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled;
766         mFocusAnimationsEnabled = true;
767         if (mIsFocused && !wasFocusAnimationsEnabled) {
768             // Re-notify the header if we were focused and animations were not previously enabled
769             mHeaderView.onTaskViewFocusChanged(true);
770         }
771     }
772 
773     /**** TaskCallbacks Implementation ****/
774 
775     /** Binds this task view to the task */
onTaskBound(Task t)776     public void onTaskBound(Task t) {
777         mTask = t;
778         mTask.setCallbacks(this);
779         if (getMeasuredWidth() == 0) {
780             // If we haven't yet measured, we should just set the footer height with any animation
781             animateFooterVisibility(t.lockToThisTask, 0);
782         } else {
783             animateFooterVisibility(t.lockToThisTask, mConfig.taskViewLockToAppLongAnimDuration);
784         }
785         // Hide the action button if lock to app is disabled
786         if (!t.lockToTaskEnabled && mActionButtonView.getVisibility() != View.GONE) {
787             mActionButtonView.setVisibility(View.GONE);
788         }
789     }
790 
791     @Override
onTaskDataLoaded()792     public void onTaskDataLoaded() {
793         if (mThumbnailView != null && mHeaderView != null) {
794             // Bind each of the views to the new task data
795             if (mIsFullScreenView) {
796                 mThumbnailView.bindToScreenshot(AlternateRecentsComponent.getLastScreenshot());
797             } else {
798                 mThumbnailView.rebindToTask(mTask);
799             }
800             mHeaderView.rebindToTask(mTask);
801             // Rebind any listeners
802             mHeaderView.mApplicationIcon.setOnClickListener(this);
803             mHeaderView.mDismissButton.setOnClickListener(this);
804             if (mFooterView != null) {
805                 mFooterView.setOnClickListener(this);
806             }
807             mActionButtonView.setOnClickListener(this);
808             if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
809                 if (mConfig.developerOptionsEnabled) {
810                     mHeaderView.mApplicationIcon.setOnLongClickListener(this);
811                 }
812             }
813         }
814         mTaskDataLoaded = true;
815     }
816 
817     @Override
onTaskDataUnloaded()818     public void onTaskDataUnloaded() {
819         if (mThumbnailView != null && mHeaderView != null) {
820             // Unbind each of the views from the task data and remove the task callback
821             mTask.setCallbacks(null);
822             mThumbnailView.unbindFromTask();
823             mHeaderView.unbindFromTask();
824             // Unbind any listeners
825             mHeaderView.mApplicationIcon.setOnClickListener(null);
826             mHeaderView.mDismissButton.setOnClickListener(null);
827             if (mFooterView != null) {
828                 mFooterView.setOnClickListener(null);
829             }
830             mActionButtonView.setOnClickListener(null);
831             if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
832                 mHeaderView.mApplicationIcon.setOnLongClickListener(null);
833             }
834         }
835         mTaskDataLoaded = false;
836     }
837 
838     /** Enables/disables handling touch on this task view. */
setTouchEnabled(boolean enabled)839     void setTouchEnabled(boolean enabled) {
840         setOnClickListener(enabled ? this : null);
841     }
842 
843     /**** TaskViewFooter.TaskFooterViewCallbacks ****/
844 
845     @Override
onTaskFooterHeightChanged(int height, int maxHeight)846     public void onTaskFooterHeightChanged(int height, int maxHeight) {
847         if (mIsFullScreenView) {
848             // Disable the bottom outline clip when fullscreen
849             mViewBounds.setOutlineClipBottom(0);
850         } else {
851             // Update the bottom clip in our outline provider
852             mViewBounds.setOutlineClipBottom(maxHeight - height);
853         }
854     }
855 
856     /**** View.OnClickListener Implementation ****/
857 
858     @Override
onClick(final View v)859      public void onClick(final View v) {
860         final TaskView tv = this;
861         final boolean delayViewClick = (v != this) && (v != mActionButtonView);
862         if (delayViewClick) {
863             // We purposely post the handler delayed to allow for the touch feedback to draw
864             postDelayed(new Runnable() {
865                 @Override
866                 public void run() {
867                     if (Constants.DebugFlags.App.EnableTaskFiltering && v == mHeaderView.mApplicationIcon) {
868                         mCb.onTaskViewAppIconClicked(tv);
869                     } else if (v == mHeaderView.mDismissButton) {
870                         dismissTask();
871                     }
872                 }
873             }, 125);
874         } else {
875             if (v == mActionButtonView) {
876                 // Reset the translation of the action button before we animate it out
877                 mActionButtonView.setTranslationZ(0f);
878             }
879             mCb.onTaskViewClicked(tv, tv.getTask(),
880                     (v == mFooterView || v == mActionButtonView));
881         }
882     }
883 
884     /**** View.OnLongClickListener Implementation ****/
885 
886     @Override
onLongClick(View v)887     public boolean onLongClick(View v) {
888         if (v == mHeaderView.mApplicationIcon) {
889             mCb.onTaskViewAppInfoClicked(this);
890             return true;
891         }
892         return false;
893     }
894 }
895