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