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.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 {
     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 onTaskViewFullScreenTransitionCompleted();
     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 
     89     public TaskView(Context context) {
     90         this(context, null);
     91     }
     92 
     93     public TaskView(Context context, AttributeSet attrs) {
     94         this(context, attrs, 0);
     95     }
     96 
     97     public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
     98         this(context, attrs, defStyleAttr, 0);
     99     }
    100 
    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 */
    116     void setCallbacks(TaskViewCallbacks cb) {
    117         mCb = cb;
    118     }
    119 
    120     /** Gets the task */
    121     Task getTask() {
    122         return mTask;
    123     }
    124 
    125     /** Returns the view bounds. */
    126     AnimateableViewBounds getViewBounds() {
    127         return mViewBounds;
    128     }
    129 
    130     @Override
    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
    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 */
    190     void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) {
    191         updateViewPropertiesToTaskTransform(toTransform, duration, null);
    192     }
    193 
    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 */
    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      */
    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      */
    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. */
    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 */
    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. */
    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 */
    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 */
    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. */
    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. */
    558     void setNoUserInteractionState() {
    559         mHeaderView.setNoUserInteractionState();
    560     }
    561 
    562     /** Dismisses this task. */
    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. */
    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. */
    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      */
    595     boolean shouldClipViewInStack() {
    596         return mClipViewInStack && !mIsFullScreenView && (getVisibility() == View.VISIBLE);
    597     }
    598 
    599     /** Sets whether this view should be clipped, or clipped against. */
    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. */
    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. */
    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. */
    629     public void setTaskProgress(float p) {
    630         mTaskProgress = p;
    631         mViewBounds.setAlpha(p);
    632         updateDimFromTaskProgress();
    633     }
    634 
    635     /** Returns the current task progress. */
    636     public float getTaskProgress() {
    637         return mTaskProgress;
    638     }
    639 
    640     /** Returns the current 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. */
    672     public int getDim() {
    673         return mDim;
    674     }
    675 
    676     /** Animates the dim to the task progress. */
    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. */
    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. */
    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      */
    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      */
    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
    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      */
    759     public boolean isFocusedTask() {
    760         return mIsFocused || isFocused();
    761     }
    762 
    763     /** Enables all focus animations. */
    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 */
    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
    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
    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. */
    839     void setTouchEnabled(boolean enabled) {
    840         setOnClickListener(enabled ? this : null);
    841     }
    842 
    843     /**** TaskViewFooter.TaskFooterViewCallbacks ****/
    844 
    845     @Override
    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
    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
    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