TaskViewHeader.java revision 217009356efa2b854ab3981dff8d315a0d679c73
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.view.ViewStub;
43import android.widget.FrameLayout;
44import android.widget.ImageView;
45import android.widget.ProgressBar;
46import android.widget.TextView;
47
48import com.android.internal.logging.MetricsLogger;
49import com.android.systemui.Interpolators;
50import com.android.systemui.R;
51import com.android.systemui.recents.Constants;
52import com.android.systemui.recents.Recents;
53import com.android.systemui.recents.events.EventBus;
54import com.android.systemui.recents.events.activity.LaunchTaskEvent;
55import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
56import com.android.systemui.recents.misc.SystemServicesProxy;
57import com.android.systemui.recents.misc.Utilities;
58import com.android.systemui.recents.model.Task;
59
60import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
61import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
62import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
63
64/* The task bar view */
65public 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                mBackgroundPaint.setColor(color);
95
96                ColorUtils.colorToHSL(color, mTmpHSL);
97                // TODO: Consider using the saturation of the color to adjust the lightness as well
98                mTmpHSL[2] = Math.min(1f,
99                        mTmpHSL[2] + HIGHLIGHT_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
100                mHighlightPaint.setColor(ColorUtils.HSLToColor(mTmpHSL));
101
102                invalidateSelf();
103            }
104        }
105
106        @Override
107        public void setColorFilter(@Nullable ColorFilter colorFilter) {
108            // Do nothing
109        }
110
111        @Override
112        public void setAlpha(int alpha) {
113            // Do nothing
114        }
115
116        @Override
117        public void draw(Canvas canvas) {
118            // Draw the highlight at the top edge (but put the bottom edge just out of view)
119            canvas.drawRoundRect(0, 0, mTaskViewRect.width(),
120                    2 * Math.max(mHighlightHeight, mCornerRadius),
121                    mCornerRadius, mCornerRadius, mHighlightPaint);
122
123            // Draw the background with the rounded corners
124            canvas.drawRoundRect(0, mHighlightHeight, mTaskViewRect.width(),
125                    getHeight() + mCornerRadius,
126                    mCornerRadius, mCornerRadius, mBackgroundPaint);
127        }
128
129        @Override
130        public int getOpacity() {
131            return PixelFormat.OPAQUE;
132        }
133
134        public int getColor() {
135            return mColor;
136        }
137    }
138
139    Task mTask;
140
141    // Header views
142    ImageView mIconView;
143    TextView mTitleView;
144    TextView mSubTitleView;
145    ImageView mMoveTaskButton;
146    ImageView mDismissButton;
147    ViewStub mAppOverlayViewStub;
148    FrameLayout mAppOverlayView;
149    ImageView mAppIconView;
150    ImageView mAppInfoView;
151    TextView mAppTitleView;
152    ViewStub mFocusTimerIndicatorStub;
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    private CountDownTimer mFocusTimerCountDown;
186
187    public TaskViewHeader(Context context) {
188        this(context, null);
189    }
190
191    public TaskViewHeader(Context context, AttributeSet attrs) {
192        this(context, attrs, 0);
193    }
194
195    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) {
196        this(context, attrs, defStyleAttr, 0);
197    }
198
199    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
200        super(context, attrs, defStyleAttr, defStyleRes);
201        setWillNotDraw(false);
202
203        // Load the dismiss resources
204        Resources res = context.getResources();
205        mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light);
206        mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark);
207        mCornerRadius = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
208        mHighlightHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight);
209        mTaskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color);
210        mTaskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color);
211        mLightFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_light);
212        mDarkFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_dark);
213        mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light);
214        mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark);
215        mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light);
216        mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark);
217        mDisabledTaskBarBackgroundColor =
218                context.getColor(R.color.recents_task_bar_disabled_background_color);
219
220        // Configure the background and dim
221        mBackground = new HighlightColorDrawable();
222        mBackground.setColorAndDim(Color.argb(255, 0, 0, 0), 0f);
223        setBackground(mBackground);
224        mOverlayBackground = new HighlightColorDrawable();
225        mDimLayerPaint.setColor(Color.argb(255, 0, 0, 0));
226        mDimLayerPaint.setAntiAlias(true);
227    }
228
229    /**
230     * Resets this header along with the TaskView.
231     */
232    public void reset() {
233        hideAppOverlay(true /* immediate */);
234    }
235
236    @Override
237    protected void onFinishInflate() {
238        SystemServicesProxy ssp = Recents.getSystemServices();
239
240        // Initialize the icon and description views
241        mIconView = (ImageView) findViewById(R.id.icon);
242        mIconView.setClickable(false);
243        mIconView.setOnLongClickListener(this);
244        mTitleView = (TextView) findViewById(R.id.title);
245        mSubTitleView = (TextView) findViewById(R.id.sub_title);
246        mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
247        if (ssp.hasFreeformWorkspaceSupport()) {
248            mMoveTaskButton = (ImageView) findViewById(R.id.move_task);
249        }
250        mFocusTimerIndicatorStub = (ViewStub) findViewById(R.id.focus_timer_indicator_stub);
251        mAppOverlayViewStub = (ViewStub) findViewById(R.id.app_overlay_stub);
252
253        // Update the dimensions of everything in the header. We do this because we need to use
254        // resources for the display, and not the current configuration.
255        Resources res = getResources();
256        mHeaderBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(res,
257                R.dimen.recents_task_view_header_height,
258                R.dimen.recents_task_view_header_height,
259                R.dimen.recents_task_view_header_height,
260                R.dimen.recents_task_view_header_height_tablet_land,
261                R.dimen.recents_task_view_header_height,
262                R.dimen.recents_task_view_header_height_tablet_land);
263        mHeaderButtonPadding = TaskStackLayoutAlgorithm.getDimensionForDevice(res,
264                R.dimen.recents_task_view_header_button_padding,
265                R.dimen.recents_task_view_header_button_padding,
266                R.dimen.recents_task_view_header_button_padding,
267                R.dimen.recents_task_view_header_button_padding_tablet_land,
268                R.dimen.recents_task_view_header_button_padding,
269                R.dimen.recents_task_view_header_button_padding_tablet_land);
270        updateLayoutParams(mIconView, findViewById(R.id.title_container), mMoveTaskButton,
271                mDismissButton);
272    }
273
274    /**
275     * Programmatically sets the layout params for a header bar layout.  This is necessary because
276     * we can't get resources based on the current configuration, but instead need to get them
277     * based on the device configuration.
278     */
279    private void updateLayoutParams(View icon, View title, View secondaryButton, View button) {
280        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
281                ViewGroup.LayoutParams.MATCH_PARENT, mHeaderBarHeight, Gravity.TOP);
282        setLayoutParams(lp);
283        lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.START);
284        icon.setLayoutParams(lp);
285        lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
286                ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL);
287        lp.leftMargin = mHeaderBarHeight;
288        lp.rightMargin = mMoveTaskButton != null
289                ? 2 * mHeaderBarHeight
290                : mHeaderBarHeight;
291        title.setLayoutParams(lp);
292        if (secondaryButton != null) {
293            lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.END);
294            lp.rightMargin = mHeaderBarHeight;
295            secondaryButton.setLayoutParams(lp);
296            secondaryButton.setPadding(mHeaderButtonPadding, mHeaderButtonPadding,
297                    mHeaderButtonPadding, mHeaderButtonPadding);
298        }
299        lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.END);
300        button.setLayoutParams(lp);
301        button.setPadding(mHeaderButtonPadding, mHeaderButtonPadding, mHeaderButtonPadding,
302                mHeaderButtonPadding);
303    }
304
305    @Override
306    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
307        super.onLayout(changed, left, top, right, bottom);
308
309        // Since we update the position of children based on the width of the parent and this view
310        // recompute these changes with the new view size
311        onTaskViewSizeChanged(mTaskViewRect.width(), mTaskViewRect.height());
312    }
313
314    /**
315     * Called when the task view frame changes, allowing us to move the contents of the header
316     * to match the frame changes.
317     */
318    public void onTaskViewSizeChanged(int width, int height) {
319        mTaskViewRect.set(0, 0, width, height);
320
321        boolean showTitle = true;
322        boolean showMoveIcon = true;
323        boolean showDismissIcon = true;
324        int rightInset = width - getMeasuredWidth();
325
326        if (mTask != null && mTask.isFreeformTask()) {
327            // For freeform tasks, we always show the app icon, and only show the title, move-task
328            // icon, and the dismiss icon if there is room
329            int appIconWidth = mIconView.getMeasuredWidth();
330            int titleWidth = (int) mTitleView.getPaint().measureText(mTask.title);
331            int dismissWidth = mDismissButton.getMeasuredWidth();
332            int moveTaskWidth = mMoveTaskButton != null
333                    ? mMoveTaskButton.getMeasuredWidth()
334                    : 0;
335            showTitle = width >= (appIconWidth + dismissWidth + moveTaskWidth + titleWidth);
336            showMoveIcon = width >= (appIconWidth + dismissWidth + moveTaskWidth);
337            showDismissIcon = width >= (appIconWidth + dismissWidth);
338        }
339
340        mTitleView.setVisibility(showTitle ? View.VISIBLE : View.INVISIBLE);
341        if (mMoveTaskButton != null) {
342            mMoveTaskButton.setVisibility(showMoveIcon ? View.VISIBLE : View.INVISIBLE);
343            mMoveTaskButton.setTranslationX(rightInset);
344        }
345        mDismissButton.setVisibility(showDismissIcon ? View.VISIBLE : View.INVISIBLE);
346        mDismissButton.setTranslationX(rightInset);
347    }
348
349    @Override
350    public void onDrawForeground(Canvas canvas) {
351        super.onDrawForeground(canvas);
352
353        // Draw the dim layer with the rounded corners
354        canvas.drawRoundRect(0, 0, mTaskViewRect.width(), getHeight() + mCornerRadius,
355                mCornerRadius, mCornerRadius, mDimLayerPaint);
356    }
357
358    /** Starts the focus timer. */
359    public void startFocusTimerIndicator(int duration) {
360        if (mFocusTimerIndicator == null) {
361            return;
362        }
363
364        mFocusTimerIndicator.setVisibility(View.VISIBLE);
365        mFocusTimerIndicator.setMax(duration);
366        mFocusTimerIndicator.setProgress(duration);
367        if (mFocusTimerCountDown != null) {
368            mFocusTimerCountDown.cancel();
369        }
370        mFocusTimerCountDown = new CountDownTimer(duration,
371                FOCUS_INDICATOR_INTERVAL_MS) {
372            public void onTick(long millisUntilFinished) {
373                mFocusTimerIndicator.setProgress((int) millisUntilFinished);
374            }
375
376            public void onFinish() {
377                // Do nothing
378            }
379        }.start();
380    }
381
382    /** Cancels the focus timer. */
383    public void cancelFocusTimerIndicator() {
384        if (mFocusTimerIndicator == null) {
385            return;
386        }
387
388        if (mFocusTimerCountDown != null) {
389            mFocusTimerCountDown.cancel();
390            mFocusTimerIndicator.setProgress(0);
391            mFocusTimerIndicator.setVisibility(View.INVISIBLE);
392        }
393    }
394
395    /** Only exposed for the workaround for b/27815919. */
396    public ImageView getIconView() {
397        return mIconView;
398    }
399
400    /** Returns the secondary color for a primary color. */
401    int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
402        int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
403        return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f);
404    }
405
406    /**
407     * Sets the dim alpha, only used when we are not using hardware layers.
408     * (see RecentsConfiguration.useHardwareLayers)
409     */
410    public void setDimAlpha(float dimAlpha) {
411        if (Float.compare(mDimAlpha, dimAlpha) != 0) {
412            mDimAlpha = dimAlpha;
413            mTitleView.setAlpha(1f - dimAlpha);
414            updateBackgroundColor(mBackground.getColor(), dimAlpha);
415        }
416    }
417
418    /**
419     * Updates the background and highlight colors for this header.
420     */
421    private void updateBackgroundColor(int color, float dimAlpha) {
422        if (mTask != null) {
423            mBackground.setColorAndDim(color, dimAlpha);
424            // TODO: Consider using the saturation of the color to adjust the lightness as well
425            ColorUtils.colorToHSL(color, mTmpHSL);
426            mTmpHSL[2] = Math.min(1f, mTmpHSL[2] + OVERLAY_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
427            mOverlayBackground.setColorAndDim(ColorUtils.HSLToColor(mTmpHSL), dimAlpha);
428            mDimLayerPaint.setAlpha((int) (dimAlpha * 255));
429            invalidate();
430        }
431    }
432
433    /** Binds the bar view to the task */
434    public void rebindToTask(Task t, boolean touchExplorationEnabled, boolean disabledInSafeMode) {
435        SystemServicesProxy ssp = Recents.getSystemServices();
436        mTask = t;
437
438        // If an activity icon is defined, then we use that as the primary icon to show in the bar,
439        // otherwise, we fall back to the application icon
440        int primaryColor = disabledInSafeMode
441                ? mDisabledTaskBarBackgroundColor
442                : t.colorPrimary;
443        if (mBackground.getColor() != primaryColor) {
444            updateBackgroundColor(primaryColor, mDimAlpha);
445        }
446        if (t.icon != null) {
447            mIconView.setImageDrawable(t.icon);
448        }
449        if (!mTitleView.getText().toString().equals(t.title)) {
450            mTitleView.setText(t.title);
451        }
452        mTitleView.setContentDescription(t.contentDescription);
453        mTitleView.setTextColor(t.useLightOnPrimaryColor ?
454                mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
455        if (!t.isDockable && ssp.hasDockedTask()) {
456            mSubTitleView.setVisibility(View.VISIBLE);
457            mSubTitleView.setTextColor(t.useLightOnPrimaryColor ?
458                    mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
459        } else {
460            mSubTitleView.setVisibility(View.GONE);
461        }
462        mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
463                mLightDismissDrawable : mDarkDismissDrawable);
464        mDismissButton.setContentDescription(t.dismissDescription);
465
466        // When freeform workspaces are enabled, then update the move-task button depending on the
467        // current task
468        if (mMoveTaskButton != null) {
469            if (t.isFreeformTask()) {
470                mMoveTaskTargetStackId = FULLSCREEN_WORKSPACE_STACK_ID;
471                mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
472                        ? mLightFullscreenIcon
473                        : mDarkFullscreenIcon);
474            } else {
475                mMoveTaskTargetStackId = FREEFORM_WORKSPACE_STACK_ID;
476                mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
477                        ? mLightFreeformIcon
478                        : mDarkFreeformIcon);
479            }
480        }
481
482        if (Recents.getDebugFlags().isFastToggleRecentsEnabled()) {
483            if (mFocusTimerIndicator == null) {
484                mFocusTimerIndicator = (ProgressBar) mFocusTimerIndicatorStub.inflate();
485            }
486            mFocusTimerIndicator.getProgressDrawable()
487                    .setColorFilter(
488                            getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor),
489                            PorterDuff.Mode.SRC_IN);
490        }
491
492        // In accessibility, a single click on the focused app info button will show it
493        if (touchExplorationEnabled) {
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) mAppOverlayViewStub.inflate();
630            mAppOverlayView.setBackground(mOverlayBackground);
631            mAppIconView = (ImageView) mAppOverlayView.findViewById(R.id.app_icon);
632            mAppIconView.setOnClickListener(this);
633            mAppIconView.setOnLongClickListener(this);
634            mAppInfoView = (ImageView) mAppOverlayView.findViewById(R.id.app_info);
635            mAppInfoView.setOnClickListener(this);
636            mAppTitleView = (TextView) mAppOverlayView.findViewById(R.id.app_title);
637            updateLayoutParams(mAppIconView, mAppTitleView, null, mAppInfoView);
638        }
639
640        // Update the overlay contents for the current app
641        mAppTitleView.setText(ssp.getBadgedApplicationLabel(activityInfo.applicationInfo, userId));
642        mAppTitleView.setTextColor(mTask.useLightOnPrimaryColor ?
643                mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
644        mAppIconView.setImageDrawable(ssp.getBadgedApplicationIcon(activityInfo.applicationInfo,
645                userId));
646        mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor
647                ? mLightInfoIcon
648                : mDarkInfoIcon);
649        mAppOverlayView.setVisibility(View.VISIBLE);
650
651        int x = mIconView.getLeft() + mIconView.getWidth() / 2;
652        int y = mIconView.getTop() + mIconView.getHeight() / 2;
653        Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, 0,
654                getWidth());
655        revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
656        revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
657        revealAnim.start();
658    }
659
660    /**
661     * Hide the application overlay.
662     */
663    private void hideAppOverlay(boolean immediate) {
664        // Skip if we haven't even loaded the overlay yet
665        if (mAppOverlayView == null) {
666            return;
667        }
668
669        if (immediate) {
670            mAppOverlayView.setVisibility(View.GONE);
671        } else {
672            int x = mIconView.getLeft() + mIconView.getWidth() / 2;
673            int y = mIconView.getTop() + mIconView.getHeight() / 2;
674            Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y,
675                    getWidth(), 0);
676            revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
677            revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
678            revealAnim.addListener(new AnimatorListenerAdapter() {
679                @Override
680                public void onAnimationEnd(Animator animation) {
681                    mAppOverlayView.setVisibility(View.GONE);
682                }
683            });
684            revealAnim.start();
685        }
686    }
687}
688