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