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