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.AnimatorListenerAdapter;
     21 import android.annotation.Nullable;
     22 import android.content.ComponentName;
     23 import android.content.Context;
     24 import android.content.pm.ActivityInfo;
     25 import android.content.res.Resources;
     26 import android.graphics.Canvas;
     27 import android.graphics.Color;
     28 import android.graphics.ColorFilter;
     29 import android.graphics.Paint;
     30 import android.graphics.PixelFormat;
     31 import android.graphics.PorterDuff;
     32 import android.graphics.Rect;
     33 import android.graphics.drawable.Drawable;
     34 import android.graphics.drawable.RippleDrawable;
     35 import android.os.CountDownTimer;
     36 import android.support.v4.graphics.ColorUtils;
     37 import android.util.AttributeSet;
     38 import android.view.Gravity;
     39 import android.view.View;
     40 import android.view.ViewAnimationUtils;
     41 import android.view.ViewDebug;
     42 import android.view.ViewGroup;
     43 import android.widget.FrameLayout;
     44 import android.widget.ImageView;
     45 import android.widget.ProgressBar;
     46 import android.widget.TextView;
     47 
     48 import com.android.internal.logging.MetricsLogger;
     49 import com.android.systemui.Interpolators;
     50 import com.android.systemui.R;
     51 import com.android.systemui.recents.Constants;
     52 import com.android.systemui.recents.Recents;
     53 import com.android.systemui.recents.events.EventBus;
     54 import com.android.systemui.recents.events.activity.LaunchTaskEvent;
     55 import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
     56 import com.android.systemui.recents.misc.SystemServicesProxy;
     57 import com.android.systemui.recents.misc.Utilities;
     58 import com.android.systemui.recents.model.Task;
     59 
     60 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
     61 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
     62 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
     63 
     64 /* The task bar view */
     65 public class TaskViewHeader extends FrameLayout
     66         implements View.OnClickListener, View.OnLongClickListener {
     67 
     68     private static final float HIGHLIGHT_LIGHTNESS_INCREMENT = 0.075f;
     69     private static final float OVERLAY_LIGHTNESS_INCREMENT = -0.0625f;
     70     private static final int OVERLAY_REVEAL_DURATION = 250;
     71     private static final long FOCUS_INDICATOR_INTERVAL_MS = 30;
     72 
     73     /**
     74      * A color drawable that draws a slight highlight at the top to help it stand out.
     75      */
     76     private class HighlightColorDrawable extends Drawable {
     77 
     78         private Paint mHighlightPaint = new Paint();
     79         private Paint mBackgroundPaint = new Paint();
     80         private int mColor;
     81         private float mDimAlpha;
     82 
     83         public HighlightColorDrawable() {
     84             mBackgroundPaint.setColor(Color.argb(255, 0, 0, 0));
     85             mBackgroundPaint.setAntiAlias(true);
     86             mHighlightPaint.setColor(Color.argb(255, 255, 255, 255));
     87             mHighlightPaint.setAntiAlias(true);
     88         }
     89 
     90         public void setColorAndDim(int color, float dimAlpha) {
     91             if (mColor != color || Float.compare(mDimAlpha, dimAlpha) != 0) {
     92                 mColor = color;
     93                 mDimAlpha = dimAlpha;
     94                 if (mShouldDarkenBackgroundColor) {
     95                     color = getSecondaryColor(color, false /* useLightOverlayColor */);
     96                 }
     97                 mBackgroundPaint.setColor(color);
     98 
     99                 ColorUtils.colorToHSL(color, mTmpHSL);
    100                 // TODO: Consider using the saturation of the color to adjust the lightness as well
    101                 mTmpHSL[2] = Math.min(1f,
    102                         mTmpHSL[2] + HIGHLIGHT_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
    103                 mHighlightPaint.setColor(ColorUtils.HSLToColor(mTmpHSL));
    104 
    105                 invalidateSelf();
    106             }
    107         }
    108 
    109         @Override
    110         public void setColorFilter(@Nullable ColorFilter colorFilter) {
    111             // Do nothing
    112         }
    113 
    114         @Override
    115         public void setAlpha(int alpha) {
    116             // Do nothing
    117         }
    118 
    119         @Override
    120         public void draw(Canvas canvas) {
    121             // Draw the highlight at the top edge (but put the bottom edge just out of view)
    122             canvas.drawRoundRect(0, 0, mTaskViewRect.width(),
    123                     2 * Math.max(mHighlightHeight, mCornerRadius),
    124                     mCornerRadius, mCornerRadius, mHighlightPaint);
    125 
    126             // Draw the background with the rounded corners
    127             canvas.drawRoundRect(0, mHighlightHeight, mTaskViewRect.width(),
    128                     getHeight() + mCornerRadius,
    129                     mCornerRadius, mCornerRadius, mBackgroundPaint);
    130         }
    131 
    132         @Override
    133         public int getOpacity() {
    134             return PixelFormat.OPAQUE;
    135         }
    136 
    137         public int getColor() {
    138             return mColor;
    139         }
    140     }
    141 
    142     Task mTask;
    143 
    144     // Header views
    145     ImageView mIconView;
    146     TextView mTitleView;
    147     ImageView mMoveTaskButton;
    148     ImageView mDismissButton;
    149     FrameLayout mAppOverlayView;
    150     ImageView mAppIconView;
    151     ImageView mAppInfoView;
    152     TextView mAppTitleView;
    153     ProgressBar mFocusTimerIndicator;
    154 
    155     // Header drawables
    156     @ViewDebug.ExportedProperty(category="recents")
    157     Rect mTaskViewRect = new Rect();
    158     int mHeaderBarHeight;
    159     int mHeaderButtonPadding;
    160     int mCornerRadius;
    161     int mHighlightHeight;
    162     @ViewDebug.ExportedProperty(category="recents")
    163     float mDimAlpha;
    164     Drawable mLightDismissDrawable;
    165     Drawable mDarkDismissDrawable;
    166     Drawable mLightFreeformIcon;
    167     Drawable mDarkFreeformIcon;
    168     Drawable mLightFullscreenIcon;
    169     Drawable mDarkFullscreenIcon;
    170     Drawable mLightInfoIcon;
    171     Drawable mDarkInfoIcon;
    172     int mTaskBarViewLightTextColor;
    173     int mTaskBarViewDarkTextColor;
    174     int mDisabledTaskBarBackgroundColor;
    175     int mMoveTaskTargetStackId = INVALID_STACK_ID;
    176 
    177     // Header background
    178     private HighlightColorDrawable mBackground;
    179     private HighlightColorDrawable mOverlayBackground;
    180     private float[] mTmpHSL = new float[3];
    181 
    182     // Header dim, which is only used when task view hardware layers are not used
    183     private Paint mDimLayerPaint = new Paint();
    184 
    185     // Whether the background color should be darkened to differentiate from the primary color.
    186     // Used in grid layout.
    187     private boolean mShouldDarkenBackgroundColor = false;
    188 
    189     private CountDownTimer mFocusTimerCountDown;
    190 
    191     public TaskViewHeader(Context context) {
    192         this(context, null);
    193     }
    194 
    195     public TaskViewHeader(Context context, AttributeSet attrs) {
    196         this(context, attrs, 0);
    197     }
    198 
    199     public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) {
    200         this(context, attrs, defStyleAttr, 0);
    201     }
    202 
    203     public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    204         super(context, attrs, defStyleAttr, defStyleRes);
    205         setWillNotDraw(false);
    206 
    207         // Load the dismiss resources
    208         Resources res = context.getResources();
    209         mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light);
    210         mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark);
    211         mCornerRadius = Recents.getConfiguration().isGridEnabled ?
    212                 res.getDimensionPixelSize(R.dimen.recents_grid_task_view_rounded_corners_radius) :
    213                 res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
    214         mHighlightHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight);
    215         mTaskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color);
    216         mTaskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color);
    217         mLightFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_light);
    218         mDarkFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_dark);
    219         mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light);
    220         mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark);
    221         mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light);
    222         mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark);
    223         mDisabledTaskBarBackgroundColor =
    224                 context.getColor(R.color.recents_task_bar_disabled_background_color);
    225 
    226         // Configure the background and dim
    227         mBackground = new HighlightColorDrawable();
    228         mBackground.setColorAndDim(Color.argb(255, 0, 0, 0), 0f);
    229         setBackground(mBackground);
    230         mOverlayBackground = new HighlightColorDrawable();
    231         mDimLayerPaint.setColor(Color.argb(255, 0, 0, 0));
    232         mDimLayerPaint.setAntiAlias(true);
    233     }
    234 
    235     /**
    236      * Resets this header along with the TaskView.
    237      */
    238     public void reset() {
    239         hideAppOverlay(true /* immediate */);
    240     }
    241 
    242     @Override
    243     protected void onFinishInflate() {
    244         SystemServicesProxy ssp = Recents.getSystemServices();
    245 
    246         // Initialize the icon and description views
    247         mIconView = findViewById(R.id.icon);
    248         mIconView.setOnLongClickListener(this);
    249         mTitleView = findViewById(R.id.title);
    250         mDismissButton = findViewById(R.id.dismiss_task);
    251         if (ssp.hasFreeformWorkspaceSupport()) {
    252             mMoveTaskButton = findViewById(R.id.move_task);
    253         }
    254 
    255         onConfigurationChanged();
    256     }
    257 
    258     /**
    259      * Programmatically sets the layout params for a header bar layout.  This is necessary because
    260      * we can't get resources based on the current configuration, but instead need to get them
    261      * based on the device configuration.
    262      */
    263     private void updateLayoutParams(View icon, View title, View secondaryButton, View button) {
    264         FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
    265                 ViewGroup.LayoutParams.MATCH_PARENT, mHeaderBarHeight, Gravity.TOP);
    266         setLayoutParams(lp);
    267         lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.START);
    268         icon.setLayoutParams(lp);
    269         lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
    270                 ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL);
    271         lp.setMarginStart(mHeaderBarHeight);
    272         lp.setMarginEnd(mMoveTaskButton != null
    273                 ? 2 * mHeaderBarHeight
    274                 : mHeaderBarHeight);
    275         title.setLayoutParams(lp);
    276         if (secondaryButton != null) {
    277             lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.END);
    278             lp.setMarginEnd(mHeaderBarHeight);
    279             secondaryButton.setLayoutParams(lp);
    280             secondaryButton.setPadding(mHeaderButtonPadding, mHeaderButtonPadding,
    281                     mHeaderButtonPadding, mHeaderButtonPadding);
    282         }
    283         lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.END);
    284         button.setLayoutParams(lp);
    285         button.setPadding(mHeaderButtonPadding, mHeaderButtonPadding, mHeaderButtonPadding,
    286                 mHeaderButtonPadding);
    287     }
    288 
    289     /**
    290      * Update the header view when the configuration changes.
    291      */
    292     public void onConfigurationChanged() {
    293         // Update the dimensions of everything in the header. We do this because we need to use
    294         // resources for the display, and not the current configuration.
    295         Resources res = getResources();
    296         int headerBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(getContext(),
    297                 R.dimen.recents_task_view_header_height,
    298                 R.dimen.recents_task_view_header_height,
    299                 R.dimen.recents_task_view_header_height,
    300                 R.dimen.recents_task_view_header_height_tablet_land,
    301                 R.dimen.recents_task_view_header_height,
    302                 R.dimen.recents_task_view_header_height_tablet_land,
    303                 R.dimen.recents_grid_task_view_header_height);
    304         int headerButtonPadding = TaskStackLayoutAlgorithm.getDimensionForDevice(getContext(),
    305                 R.dimen.recents_task_view_header_button_padding,
    306                 R.dimen.recents_task_view_header_button_padding,
    307                 R.dimen.recents_task_view_header_button_padding,
    308                 R.dimen.recents_task_view_header_button_padding_tablet_land,
    309                 R.dimen.recents_task_view_header_button_padding,
    310                 R.dimen.recents_task_view_header_button_padding_tablet_land,
    311                 R.dimen.recents_grid_task_view_header_button_padding);
    312         if (headerBarHeight != mHeaderBarHeight || headerButtonPadding != mHeaderButtonPadding) {
    313             mHeaderBarHeight = headerBarHeight;
    314             mHeaderButtonPadding = headerButtonPadding;
    315             updateLayoutParams(mIconView, mTitleView, mMoveTaskButton, mDismissButton);
    316             if (mAppOverlayView != null) {
    317                 updateLayoutParams(mAppIconView, mAppTitleView, null, mAppInfoView);
    318             }
    319         }
    320     }
    321 
    322     @Override
    323     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    324         super.onLayout(changed, left, top, right, bottom);
    325 
    326         // Since we update the position of children based on the width of the parent and this view
    327         // recompute these changes with the new view size
    328         onTaskViewSizeChanged(mTaskViewRect.width(), mTaskViewRect.height());
    329     }
    330 
    331     /**
    332      * Called when the task view frame changes, allowing us to move the contents of the header
    333      * to match the frame changes.
    334      */
    335     public void onTaskViewSizeChanged(int width, int height) {
    336         mTaskViewRect.set(0, 0, width, height);
    337 
    338         boolean showTitle = true;
    339         boolean showMoveIcon = true;
    340         boolean showDismissIcon = true;
    341         int rightInset = width - getMeasuredWidth();
    342 
    343         if (mTask != null && mTask.isFreeformTask()) {
    344             // For freeform tasks, we always show the app icon, and only show the title, move-task
    345             // icon, and the dismiss icon if there is room
    346             int appIconWidth = mIconView.getMeasuredWidth();
    347             int titleWidth = (int) mTitleView.getPaint().measureText(mTask.title);
    348             int dismissWidth = mDismissButton.getMeasuredWidth();
    349             int moveTaskWidth = mMoveTaskButton != null
    350                     ? mMoveTaskButton.getMeasuredWidth()
    351                     : 0;
    352             showTitle = width >= (appIconWidth + dismissWidth + moveTaskWidth + titleWidth);
    353             showMoveIcon = width >= (appIconWidth + dismissWidth + moveTaskWidth);
    354             showDismissIcon = width >= (appIconWidth + dismissWidth);
    355         }
    356 
    357         mTitleView.setVisibility(showTitle ? View.VISIBLE : View.INVISIBLE);
    358         if (mMoveTaskButton != null) {
    359             mMoveTaskButton.setVisibility(showMoveIcon ? View.VISIBLE : View.INVISIBLE);
    360             mMoveTaskButton.setTranslationX(rightInset);
    361         }
    362         mDismissButton.setVisibility(showDismissIcon ? View.VISIBLE : View.INVISIBLE);
    363         mDismissButton.setTranslationX(rightInset);
    364 
    365         setLeftTopRightBottom(0, 0, width, getMeasuredHeight());
    366     }
    367 
    368     @Override
    369     public void onDrawForeground(Canvas canvas) {
    370         super.onDrawForeground(canvas);
    371 
    372         // Draw the dim layer with the rounded corners
    373         canvas.drawRoundRect(0, 0, mTaskViewRect.width(), getHeight() + mCornerRadius,
    374                 mCornerRadius, mCornerRadius, mDimLayerPaint);
    375     }
    376 
    377     /** Starts the focus timer. */
    378     public void startFocusTimerIndicator(int duration) {
    379         if (mFocusTimerIndicator == null) {
    380             return;
    381         }
    382 
    383         mFocusTimerIndicator.setVisibility(View.VISIBLE);
    384         mFocusTimerIndicator.setMax(duration);
    385         mFocusTimerIndicator.setProgress(duration);
    386         if (mFocusTimerCountDown != null) {
    387             mFocusTimerCountDown.cancel();
    388         }
    389         mFocusTimerCountDown = new CountDownTimer(duration,
    390                 FOCUS_INDICATOR_INTERVAL_MS) {
    391             public void onTick(long millisUntilFinished) {
    392                 mFocusTimerIndicator.setProgress((int) millisUntilFinished);
    393             }
    394 
    395             public void onFinish() {
    396                 // Do nothing
    397             }
    398         }.start();
    399     }
    400 
    401     /** Cancels the focus timer. */
    402     public void cancelFocusTimerIndicator() {
    403         if (mFocusTimerIndicator == null) {
    404             return;
    405         }
    406 
    407         if (mFocusTimerCountDown != null) {
    408             mFocusTimerCountDown.cancel();
    409             mFocusTimerIndicator.setProgress(0);
    410             mFocusTimerIndicator.setVisibility(View.INVISIBLE);
    411         }
    412     }
    413 
    414     /** Only exposed for the workaround for b/27815919. */
    415     public ImageView getIconView() {
    416         return mIconView;
    417     }
    418 
    419     /** Returns the secondary color for a primary color. */
    420     int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
    421         int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
    422         return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f);
    423     }
    424 
    425     /**
    426      * Sets the dim alpha, only used when we are not using hardware layers.
    427      * (see RecentsConfiguration.useHardwareLayers)
    428      */
    429     public void setDimAlpha(float dimAlpha) {
    430         if (Float.compare(mDimAlpha, dimAlpha) != 0) {
    431             mDimAlpha = dimAlpha;
    432             mTitleView.setAlpha(1f - dimAlpha);
    433             updateBackgroundColor(mBackground.getColor(), dimAlpha);
    434         }
    435     }
    436 
    437     /**
    438      * Updates the background and highlight colors for this header.
    439      */
    440     private void updateBackgroundColor(int color, float dimAlpha) {
    441         if (mTask != null) {
    442             mBackground.setColorAndDim(color, dimAlpha);
    443             // TODO: Consider using the saturation of the color to adjust the lightness as well
    444             ColorUtils.colorToHSL(color, mTmpHSL);
    445             mTmpHSL[2] = Math.min(1f, mTmpHSL[2] + OVERLAY_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
    446             mOverlayBackground.setColorAndDim(ColorUtils.HSLToColor(mTmpHSL), dimAlpha);
    447             mDimLayerPaint.setAlpha((int) (dimAlpha * 255));
    448             invalidate();
    449         }
    450     }
    451 
    452     /**
    453      * Sets whether the background color should be darkened to differentiate from the primary color.
    454      */
    455     public void setShouldDarkenBackgroundColor(boolean flag) {
    456         mShouldDarkenBackgroundColor = flag;
    457     }
    458 
    459     /**
    460      * Binds the bar view to the task.
    461      */
    462     public void bindToTask(Task t, boolean touchExplorationEnabled, boolean disabledInSafeMode) {
    463         mTask = t;
    464 
    465         int primaryColor = disabledInSafeMode
    466                 ? mDisabledTaskBarBackgroundColor
    467                 : t.colorPrimary;
    468         if (mBackground.getColor() != primaryColor) {
    469             updateBackgroundColor(primaryColor, mDimAlpha);
    470         }
    471         if (!mTitleView.getText().toString().equals(t.title)) {
    472             mTitleView.setText(t.title);
    473         }
    474         mTitleView.setContentDescription(t.titleDescription);
    475         mTitleView.setTextColor(t.useLightOnPrimaryColor ?
    476                 mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
    477         mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
    478                 mLightDismissDrawable : mDarkDismissDrawable);
    479         mDismissButton.setContentDescription(t.dismissDescription);
    480         mDismissButton.setOnClickListener(this);
    481         mDismissButton.setClickable(false);
    482         ((RippleDrawable) mDismissButton.getBackground()).setForceSoftware(true);
    483 
    484         // When freeform workspaces are enabled, then update the move-task button depending on the
    485         // current task
    486         if (mMoveTaskButton != null) {
    487             if (t.isFreeformTask()) {
    488                 mMoveTaskTargetStackId = FULLSCREEN_WORKSPACE_STACK_ID;
    489                 mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
    490                         ? mLightFullscreenIcon
    491                         : mDarkFullscreenIcon);
    492             } else {
    493                 mMoveTaskTargetStackId = FREEFORM_WORKSPACE_STACK_ID;
    494                 mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
    495                         ? mLightFreeformIcon
    496                         : mDarkFreeformIcon);
    497             }
    498             mMoveTaskButton.setOnClickListener(this);
    499             mMoveTaskButton.setClickable(false);
    500             ((RippleDrawable) mMoveTaskButton.getBackground()).setForceSoftware(true);
    501         }
    502 
    503         if (Recents.getDebugFlags().isFastToggleRecentsEnabled()) {
    504             if (mFocusTimerIndicator == null) {
    505                 mFocusTimerIndicator = (ProgressBar) Utilities.findViewStubById(this,
    506                         R.id.focus_timer_indicator_stub).inflate();
    507             }
    508             mFocusTimerIndicator.getProgressDrawable()
    509                     .setColorFilter(
    510                             getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor),
    511                             PorterDuff.Mode.SRC_IN);
    512         }
    513 
    514         // In accessibility, a single click on the focused app info button will show it
    515         if (touchExplorationEnabled) {
    516             mIconView.setContentDescription(t.appInfoDescription);
    517             mIconView.setOnClickListener(this);
    518             mIconView.setClickable(true);
    519         }
    520     }
    521 
    522     /**
    523      * Called when the bound task's data has loaded and this view should update to reflect the
    524      * changes.
    525      */
    526     public void onTaskDataLoaded() {
    527         if (mTask != null && mTask.icon != null) {
    528             mIconView.setImageDrawable(mTask.icon);
    529         }
    530     }
    531 
    532     /** Unbinds the bar view from the task */
    533     void unbindFromTask(boolean touchExplorationEnabled) {
    534         mTask = null;
    535         mIconView.setImageDrawable(null);
    536         if (touchExplorationEnabled) {
    537             mIconView.setClickable(false);
    538         }
    539     }
    540 
    541     /** Animates this task bar if the user does not interact with the stack after a certain time. */
    542     void startNoUserInteractionAnimation() {
    543         int duration = getResources().getInteger(R.integer.recents_task_enter_from_app_duration);
    544         mDismissButton.setVisibility(View.VISIBLE);
    545         mDismissButton.setClickable(true);
    546         if (mDismissButton.getVisibility() == VISIBLE) {
    547             mDismissButton.animate()
    548                     .alpha(1f)
    549                     .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
    550                     .setDuration(duration)
    551                     .start();
    552         } else {
    553             mDismissButton.setAlpha(1f);
    554         }
    555         if (mMoveTaskButton != null) {
    556             if (mMoveTaskButton.getVisibility() == VISIBLE) {
    557                 mMoveTaskButton.setVisibility(View.VISIBLE);
    558                 mMoveTaskButton.setClickable(true);
    559                 mMoveTaskButton.animate()
    560                         .alpha(1f)
    561                         .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
    562                         .setDuration(duration)
    563                         .start();
    564             } else {
    565                 mMoveTaskButton.setAlpha(1f);
    566             }
    567         }
    568     }
    569 
    570     /**
    571      * Mark this task view that the user does has not interacted with the stack after a certain
    572      * time.
    573      */
    574     public void setNoUserInteractionState() {
    575         mDismissButton.setVisibility(View.VISIBLE);
    576         mDismissButton.animate().cancel();
    577         mDismissButton.setAlpha(1f);
    578         mDismissButton.setClickable(true);
    579         if (mMoveTaskButton != null) {
    580             mMoveTaskButton.setVisibility(View.VISIBLE);
    581             mMoveTaskButton.animate().cancel();
    582             mMoveTaskButton.setAlpha(1f);
    583             mMoveTaskButton.setClickable(true);
    584         }
    585     }
    586 
    587     /**
    588      * Resets the state tracking that the user has not interacted with the stack after a certain
    589      * time.
    590      */
    591     void resetNoUserInteractionState() {
    592         mDismissButton.setVisibility(View.INVISIBLE);
    593         mDismissButton.setAlpha(0f);
    594         mDismissButton.setClickable(false);
    595         if (mMoveTaskButton != null) {
    596             mMoveTaskButton.setVisibility(View.INVISIBLE);
    597             mMoveTaskButton.setAlpha(0f);
    598             mMoveTaskButton.setClickable(false);
    599         }
    600     }
    601 
    602     @Override
    603     protected int[] onCreateDrawableState(int extraSpace) {
    604 
    605         // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged.
    606         // This is to prevent layer trashing when the view is pressed.
    607         return new int[] {};
    608     }
    609 
    610     @Override
    611     public void onClick(View v) {
    612         if (v == mIconView) {
    613             // In accessibility, a single click on the focused app info button will show it
    614             EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
    615         } else if (v == mDismissButton) {
    616             TaskView tv = Utilities.findParent(this, TaskView.class);
    617             tv.dismissTask();
    618 
    619             // Keep track of deletions by the dismiss button
    620             MetricsLogger.histogram(getContext(), "overview_task_dismissed_source",
    621                     Constants.Metrics.DismissSourceHeaderButton);
    622         } else if (v == mMoveTaskButton) {
    623             TaskView tv = Utilities.findParent(this, TaskView.class);
    624             EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, null,
    625                     mMoveTaskTargetStackId, false));
    626         } else if (v == mAppInfoView) {
    627             EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
    628         } else if (v == mAppIconView) {
    629             hideAppOverlay(false /* immediate */);
    630         }
    631     }
    632 
    633     @Override
    634     public boolean onLongClick(View v) {
    635         if (v == mIconView) {
    636             showAppOverlay();
    637             return true;
    638         } else if (v == mAppIconView) {
    639             hideAppOverlay(false /* immediate */);
    640             return true;
    641         }
    642         return false;
    643     }
    644 
    645     /**
    646      * Shows the application overlay.
    647      */
    648     private void showAppOverlay() {
    649         // Skip early if the task is invalid
    650         SystemServicesProxy ssp = Recents.getSystemServices();
    651         ComponentName cn = mTask.key.getComponent();
    652         int userId = mTask.key.userId;
    653         ActivityInfo activityInfo = ssp.getActivityInfo(cn, userId);
    654         if (activityInfo == null) {
    655             return;
    656         }
    657 
    658         // Inflate the overlay if necessary
    659         if (mAppOverlayView == null) {
    660             mAppOverlayView = (FrameLayout) Utilities.findViewStubById(this,
    661                     R.id.app_overlay_stub).inflate();
    662             mAppOverlayView.setBackground(mOverlayBackground);
    663             mAppIconView = (ImageView) mAppOverlayView.findViewById(R.id.app_icon);
    664             mAppIconView.setOnClickListener(this);
    665             mAppIconView.setOnLongClickListener(this);
    666             mAppInfoView = (ImageView) mAppOverlayView.findViewById(R.id.app_info);
    667             mAppInfoView.setOnClickListener(this);
    668             mAppTitleView = (TextView) mAppOverlayView.findViewById(R.id.app_title);
    669             updateLayoutParams(mAppIconView, mAppTitleView, null, mAppInfoView);
    670         }
    671 
    672         // Update the overlay contents for the current app
    673         mAppTitleView.setText(ssp.getBadgedApplicationLabel(activityInfo.applicationInfo, userId));
    674         mAppTitleView.setTextColor(mTask.useLightOnPrimaryColor ?
    675                 mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
    676         mAppIconView.setImageDrawable(ssp.getBadgedApplicationIcon(activityInfo.applicationInfo,
    677                 userId));
    678         mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor
    679                 ? mLightInfoIcon
    680                 : mDarkInfoIcon);
    681         mAppOverlayView.setVisibility(View.VISIBLE);
    682 
    683         int x = mIconView.getLeft() + mIconView.getWidth() / 2;
    684         int y = mIconView.getTop() + mIconView.getHeight() / 2;
    685         Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, 0,
    686                 getWidth());
    687         revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
    688         revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
    689         revealAnim.start();
    690     }
    691 
    692     /**
    693      * Hide the application overlay.
    694      */
    695     private void hideAppOverlay(boolean immediate) {
    696         // Skip if we haven't even loaded the overlay yet
    697         if (mAppOverlayView == null) {
    698             return;
    699         }
    700 
    701         if (immediate) {
    702             mAppOverlayView.setVisibility(View.GONE);
    703         } else {
    704             int x = mIconView.getLeft() + mIconView.getWidth() / 2;
    705             int y = mIconView.getTop() + mIconView.getHeight() / 2;
    706             Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y,
    707                     getWidth(), 0);
    708             revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
    709             revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
    710             revealAnim.addListener(new AnimatorListenerAdapter() {
    711                 @Override
    712                 public void onAnimationEnd(Animator animation) {
    713                     mAppOverlayView.setVisibility(View.GONE);
    714                 }
    715             });
    716             revealAnim.start();
    717         }
    718     }
    719 }
    720