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