TaskViewHeader.java revision e693aafe0511c2a7ffc571b22abeefba44046225
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.View;
38import android.view.ViewAnimationUtils;
39import android.view.ViewDebug;
40import android.view.ViewStub;
41import android.widget.FrameLayout;
42import android.widget.ImageView;
43import android.widget.ProgressBar;
44import android.widget.TextView;
45
46import com.android.internal.logging.MetricsLogger;
47import com.android.systemui.Interpolators;
48import com.android.systemui.R;
49import com.android.systemui.recents.Constants;
50import com.android.systemui.recents.Recents;
51import com.android.systemui.recents.events.EventBus;
52import com.android.systemui.recents.events.activity.LaunchTaskEvent;
53import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
54import com.android.systemui.recents.misc.SystemServicesProxy;
55import com.android.systemui.recents.misc.Utilities;
56import com.android.systemui.recents.model.Task;
57
58import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
59import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
60import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
61
62/* The task bar view */
63public class TaskViewHeader extends FrameLayout
64        implements View.OnClickListener, View.OnLongClickListener {
65
66    private static final float HIGHLIGHT_LIGHTNESS_INCREMENT = 0.075f;
67    private static final float OVERLAY_LIGHTNESS_INCREMENT = -0.0625f;
68    private static final int OVERLAY_REVEAL_DURATION = 250;
69    private static final long FOCUS_INDICATOR_INTERVAL_MS = 30;
70
71    /**
72     * A color drawable that draws a slight highlight at the top to help it stand out.
73     */
74    private class HighlightColorDrawable extends Drawable {
75
76        private Paint mHighlightPaint = new Paint();
77        private Paint mBackgroundPaint = new Paint();
78        private int mColor;
79        private float mDimAlpha;
80
81        public HighlightColorDrawable() {
82            mBackgroundPaint.setColor(Color.argb(255, 0, 0, 0));
83            mBackgroundPaint.setAntiAlias(true);
84            mHighlightPaint.setColor(Color.argb(255, 255, 255, 255));
85            mHighlightPaint.setAntiAlias(true);
86        }
87
88        public void setColorAndDim(int color, float dimAlpha) {
89            if (mColor != color || Float.compare(mDimAlpha, dimAlpha) != 0) {
90                mColor = color;
91                mDimAlpha = dimAlpha;
92                mBackgroundPaint.setColor(color);
93
94                ColorUtils.colorToHSL(color, mTmpHSL);
95                // TODO: Consider using the saturation of the color to adjust the lightness as well
96                mTmpHSL[2] = Math.min(1f,
97                        mTmpHSL[2] + HIGHLIGHT_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
98                mHighlightPaint.setColor(ColorUtils.HSLToColor(mTmpHSL));
99
100                invalidateSelf();
101            }
102        }
103
104        @Override
105        public void setColorFilter(@Nullable ColorFilter colorFilter) {
106            // Do nothing
107        }
108
109        @Override
110        public void setAlpha(int alpha) {
111            // Do nothing
112        }
113
114        @Override
115        public void draw(Canvas canvas) {
116            // Draw the highlight at the top edge (but put the bottom edge just out of view)
117            canvas.drawRoundRect(0, 0, mTaskViewRect.width(),
118                    2 * Math.max(mHighlightHeight, mCornerRadius),
119                    mCornerRadius, mCornerRadius, mHighlightPaint);
120
121            // Draw the background with the rounded corners
122            canvas.drawRoundRect(0, mHighlightHeight, mTaskViewRect.width(),
123                    getHeight() + mCornerRadius,
124                    mCornerRadius, mCornerRadius, mBackgroundPaint);
125        }
126
127        @Override
128        public int getOpacity() {
129            return PixelFormat.OPAQUE;
130        }
131
132        public int getColor() {
133            return mColor;
134        }
135    }
136
137    Task mTask;
138
139    // Header views
140    ImageView mIconView;
141    TextView mTitleView;
142    TextView mSubTitleView;
143    ImageView mMoveTaskButton;
144    ImageView mDismissButton;
145    ViewStub mAppOverlayViewStub;
146    FrameLayout mAppOverlayView;
147    ImageView mAppIconView;
148    ImageView mAppInfoView;
149    TextView mAppTitleView;
150    ViewStub mFocusTimerIndicatorStub;
151    ProgressBar mFocusTimerIndicator;
152
153    // Header drawables
154    @ViewDebug.ExportedProperty(category="recents")
155    Rect mTaskViewRect = new Rect();
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        mSubTitleView = (TextView) findViewById(R.id.sub_title);
242        mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
243        if (ssp.hasFreeformWorkspaceSupport()) {
244            mMoveTaskButton = (ImageView) findViewById(R.id.move_task);
245        }
246        mFocusTimerIndicatorStub = (ViewStub) findViewById(R.id.focus_timer_indicator_stub);
247        mAppOverlayViewStub = (ViewStub) findViewById(R.id.app_overlay_stub);
248    }
249
250    @Override
251    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
252        super.onLayout(changed, left, top, right, bottom);
253
254        // Since we update the position of children based on the width of the parent and this view
255        // recompute these changes with the new view size
256        onTaskViewSizeChanged(mTaskViewRect.width(), mTaskViewRect.height());
257    }
258
259    /**
260     * Called when the task view frame changes, allowing us to move the contents of the header
261     * to match the frame changes.
262     */
263    public void onTaskViewSizeChanged(int width, int height) {
264        mTaskViewRect.set(0, 0, width, height);
265
266        boolean showTitle = true;
267        boolean showMoveIcon = true;
268        boolean showDismissIcon = true;
269        int rightInset = width - getMeasuredWidth();
270
271        if (mTask != null && mTask.isFreeformTask()) {
272            // For freeform tasks, we always show the app icon, and only show the title, move-task
273            // icon, and the dismiss icon if there is room
274            int appIconWidth = mIconView.getMeasuredWidth();
275            int titleWidth = (int) mTitleView.getPaint().measureText(mTask.title);
276            int dismissWidth = mDismissButton.getMeasuredWidth();
277            int moveTaskWidth = mMoveTaskButton != null
278                    ? mMoveTaskButton.getMeasuredWidth()
279                    : 0;
280            showTitle = width >= (appIconWidth + dismissWidth + moveTaskWidth + titleWidth);
281            showMoveIcon = width >= (appIconWidth + dismissWidth + moveTaskWidth);
282            showDismissIcon = width >= (appIconWidth + dismissWidth);
283        }
284
285        mTitleView.setVisibility(showTitle ? View.VISIBLE : View.INVISIBLE);
286        if (mMoveTaskButton != null) {
287            mMoveTaskButton.setVisibility(showMoveIcon ? View.VISIBLE : View.INVISIBLE);
288            mMoveTaskButton.setTranslationX(rightInset);
289        }
290        mDismissButton.setVisibility(showDismissIcon ? View.VISIBLE : View.INVISIBLE);
291        mDismissButton.setTranslationX(rightInset);
292    }
293
294    @Override
295    public void onDrawForeground(Canvas canvas) {
296        super.onDrawForeground(canvas);
297
298        // Draw the dim layer with the rounded corners
299        canvas.drawRoundRect(0, 0, mTaskViewRect.width(), getHeight() + mCornerRadius,
300                mCornerRadius, mCornerRadius, mDimLayerPaint);
301    }
302
303    /** Starts the focus timer. */
304    public void startFocusTimerIndicator(int duration) {
305        if (mFocusTimerIndicator == null) {
306            return;
307        }
308
309        mFocusTimerIndicator.setVisibility(View.VISIBLE);
310        mFocusTimerIndicator.setMax(duration);
311        mFocusTimerIndicator.setProgress(duration);
312        if (mFocusTimerCountDown != null) {
313            mFocusTimerCountDown.cancel();
314        }
315        mFocusTimerCountDown = new CountDownTimer(duration,
316                FOCUS_INDICATOR_INTERVAL_MS) {
317            public void onTick(long millisUntilFinished) {
318                mFocusTimerIndicator.setProgress((int) millisUntilFinished);
319            }
320
321            public void onFinish() {
322                // Do nothing
323            }
324        }.start();
325    }
326
327    /** Cancels the focus timer. */
328    public void cancelFocusTimerIndicator() {
329        if (mFocusTimerIndicator == null) {
330            return;
331        }
332
333        if (mFocusTimerCountDown != null) {
334            mFocusTimerCountDown.cancel();
335            mFocusTimerIndicator.setProgress(0);
336            mFocusTimerIndicator.setVisibility(View.INVISIBLE);
337        }
338    }
339
340    /** Returns the secondary color for a primary color. */
341    int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
342        int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
343        return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f);
344    }
345
346    /**
347     * Sets the dim alpha, only used when we are not using hardware layers.
348     * (see RecentsConfiguration.useHardwareLayers)
349     */
350    public void setDimAlpha(float dimAlpha) {
351        if (Float.compare(mDimAlpha, dimAlpha) != 0) {
352            mDimAlpha = dimAlpha;
353            updateBackgroundColor(mBackground.getColor(), dimAlpha);
354        }
355    }
356
357    /**
358     * Updates the background and highlight colors for this header.
359     */
360    private void updateBackgroundColor(int color, float dimAlpha) {
361        if (mTask != null) {
362            mBackground.setColorAndDim(color, dimAlpha);
363            // TODO: Consider using the saturation of the color to adjust the lightness as well
364            ColorUtils.colorToHSL(color, mTmpHSL);
365            mTmpHSL[2] = Math.min(1f, mTmpHSL[2] + OVERLAY_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
366            mOverlayBackground.setColorAndDim(ColorUtils.HSLToColor(mTmpHSL), dimAlpha);
367            mDimLayerPaint.setAlpha((int) (dimAlpha * 255));
368            invalidate();
369        }
370    }
371
372    /** Binds the bar view to the task */
373    public void rebindToTask(Task t, boolean touchExplorationEnabled, boolean disabledInSafeMode) {
374        SystemServicesProxy ssp = Recents.getSystemServices();
375        mTask = t;
376
377        // If an activity icon is defined, then we use that as the primary icon to show in the bar,
378        // otherwise, we fall back to the application icon
379        int primaryColor = disabledInSafeMode
380                ? mDisabledTaskBarBackgroundColor
381                : t.colorPrimary;
382        if (mBackground.getColor() != primaryColor) {
383            updateBackgroundColor(primaryColor, mDimAlpha);
384        }
385        if (t.icon != null) {
386            mIconView.setImageDrawable(t.icon);
387        }
388        if (!mTitleView.getText().toString().equals(t.title)) {
389            mTitleView.setText(t.title);
390        }
391        mTitleView.setContentDescription(t.contentDescription);
392        mTitleView.setTextColor(t.useLightOnPrimaryColor ?
393                mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
394        if (!t.isDockable && ssp.hasDockedTask()) {
395            mSubTitleView.setVisibility(View.VISIBLE);
396            mSubTitleView.setTextColor(t.useLightOnPrimaryColor ?
397                    mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
398        } else {
399            mSubTitleView.setVisibility(View.GONE);
400        }
401        mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
402                mLightDismissDrawable : mDarkDismissDrawable);
403        mDismissButton.setContentDescription(t.dismissDescription);
404
405        // When freeform workspaces are enabled, then update the move-task button depending on the
406        // current task
407        if (mMoveTaskButton != null) {
408            if (t.isFreeformTask()) {
409                mMoveTaskTargetStackId = FULLSCREEN_WORKSPACE_STACK_ID;
410                mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
411                        ? mLightFullscreenIcon
412                        : mDarkFullscreenIcon);
413            } else {
414                mMoveTaskTargetStackId = FREEFORM_WORKSPACE_STACK_ID;
415                mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
416                        ? mLightFreeformIcon
417                        : mDarkFreeformIcon);
418            }
419        }
420
421        if (Recents.getDebugFlags().isFastToggleRecentsEnabled()) {
422            if (mFocusTimerIndicator == null) {
423                mFocusTimerIndicator = (ProgressBar) mFocusTimerIndicatorStub.inflate();
424            }
425            mFocusTimerIndicator.getProgressDrawable()
426                    .setColorFilter(
427                            getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor),
428                            PorterDuff.Mode.SRC_IN);
429        }
430
431        // In accessibility, a single click on the focused app info button will show it
432        if (touchExplorationEnabled) {
433            mIconView.setOnClickListener(this);
434        }
435    }
436
437    /** Unbinds the bar view from the task */
438    void unbindFromTask(boolean touchExplorationEnabled) {
439        mTask = null;
440        mIconView.setImageDrawable(null);
441        if (touchExplorationEnabled) {
442            mIconView.setOnClickListener(null);
443        }
444    }
445
446    /** Animates this task bar if the user does not interact with the stack after a certain time. */
447    void startNoUserInteractionAnimation() {
448        int duration = getResources().getInteger(R.integer.recents_task_enter_from_app_duration);
449        mDismissButton.setOnClickListener(this);
450        mDismissButton.setVisibility(View.VISIBLE);
451        if (mDismissButton.getVisibility() == VISIBLE) {
452            mDismissButton.animate()
453                    .alpha(1f)
454                    .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
455                    .setDuration(duration)
456                    .start();
457        } else {
458            mDismissButton.setAlpha(1f);
459        }
460        if (mMoveTaskButton != null) {
461            if (mMoveTaskButton.getVisibility() == VISIBLE) {
462                mMoveTaskButton.setOnClickListener(this);
463                mMoveTaskButton.setVisibility(View.VISIBLE);
464                mMoveTaskButton.animate()
465                        .alpha(1f)
466                        .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
467                        .setDuration(duration)
468                        .start();
469            } else {
470                mMoveTaskButton.setAlpha(1f);
471            }
472        }
473    }
474
475    /**
476     * Mark this task view that the user does has not interacted with the stack after a certain
477     * time.
478     */
479    void setNoUserInteractionState() {
480        mDismissButton.setVisibility(View.VISIBLE);
481        mDismissButton.animate().cancel();
482        mDismissButton.setAlpha(1f);
483        mDismissButton.setOnClickListener(this);
484        if (mMoveTaskButton != null) {
485            mMoveTaskButton.setVisibility(View.VISIBLE);
486            mMoveTaskButton.animate().cancel();
487            mMoveTaskButton.setAlpha(1f);
488            mMoveTaskButton.setOnClickListener(this);
489        }
490    }
491
492    /**
493     * Resets the state tracking that the user has not interacted with the stack after a certain
494     * time.
495     */
496    void resetNoUserInteractionState() {
497        mDismissButton.setVisibility(View.INVISIBLE);
498        mDismissButton.setAlpha(0f);
499        mDismissButton.setOnClickListener(null);
500        if (mMoveTaskButton != null) {
501            mMoveTaskButton.setVisibility(View.INVISIBLE);
502            mMoveTaskButton.setAlpha(0f);
503            mMoveTaskButton.setOnClickListener(null);
504        }
505    }
506
507    @Override
508    protected int[] onCreateDrawableState(int extraSpace) {
509
510        // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged.
511        // This is to prevent layer trashing when the view is pressed.
512        return new int[] {};
513    }
514
515    @Override
516    public void onClick(View v) {
517        if (v == mIconView) {
518            // In accessibility, a single click on the focused app info button will show it
519            EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
520        } else if (v == mDismissButton) {
521            TaskView tv = Utilities.findParent(this, TaskView.class);
522            tv.dismissTask();
523
524            // Keep track of deletions by the dismiss button
525            MetricsLogger.histogram(getContext(), "overview_task_dismissed_source",
526                    Constants.Metrics.DismissSourceHeaderButton);
527        } else if (v == mMoveTaskButton) {
528            TaskView tv = Utilities.findParent(this, TaskView.class);
529            Rect bounds = mMoveTaskTargetStackId == FREEFORM_WORKSPACE_STACK_ID
530                    ? new Rect(mTaskViewRect)
531                    : new Rect();
532            EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, bounds,
533                    mMoveTaskTargetStackId, false));
534        } else if (v == mAppInfoView) {
535            EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
536        } else if (v == mAppIconView) {
537            hideAppOverlay(false /* immediate */);
538        }
539    }
540
541    @Override
542    public boolean onLongClick(View v) {
543        if (v == mIconView) {
544            showAppOverlay();
545            return true;
546        } else if (v == mAppIconView) {
547            hideAppOverlay(false /* immediate */);
548            return true;
549        }
550        return false;
551    }
552
553    /**
554     * Shows the application overlay.
555     */
556    private void showAppOverlay() {
557        // Skip early if the task is invalid
558        SystemServicesProxy ssp = Recents.getSystemServices();
559        ComponentName cn = mTask.key.getComponent();
560        int userId = mTask.key.userId;
561        ActivityInfo activityInfo = ssp.getActivityInfo(cn, userId);
562        if (activityInfo == null) {
563            return;
564        }
565
566        // Inflate the overlay if necessary
567        if (mAppOverlayView == null) {
568            mAppOverlayView = (FrameLayout) mAppOverlayViewStub.inflate();
569            mAppOverlayView.setBackground(mOverlayBackground);
570            mAppIconView = (ImageView) mAppOverlayView.findViewById(R.id.app_icon);
571            mAppIconView.setOnClickListener(this);
572            mAppIconView.setOnLongClickListener(this);
573            mAppInfoView = (ImageView) mAppOverlayView.findViewById(R.id.app_info);
574            mAppInfoView.setOnClickListener(this);
575            mAppTitleView = (TextView) mAppOverlayView.findViewById(R.id.app_title);
576        }
577
578        // Update the overlay contents for the current app
579        mAppTitleView.setText(ssp.getBadgedApplicationLabel(activityInfo.applicationInfo, userId));
580        mAppTitleView.setTextColor(mTask.useLightOnPrimaryColor ?
581                mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
582        mAppIconView.setImageDrawable(ssp.getBadgedApplicationIcon(activityInfo.applicationInfo,
583                userId));
584        mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor
585                ? mLightInfoIcon
586                : mDarkInfoIcon);
587        mAppOverlayView.setVisibility(View.VISIBLE);
588
589        int x = mIconView.getLeft() + mIconView.getWidth() / 2;
590        int y = mIconView.getTop() + mIconView.getHeight() / 2;
591        Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, 0,
592                getWidth());
593        revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
594        revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
595        revealAnim.start();
596    }
597
598    /**
599     * Hide the application overlay.
600     */
601    private void hideAppOverlay(boolean immediate) {
602        // Skip if we haven't even loaded the overlay yet
603        if (mAppOverlayView == null) {
604            return;
605        }
606
607        if (immediate) {
608            mAppOverlayView.setVisibility(View.GONE);
609        } else {
610            int x = mIconView.getLeft() + mIconView.getWidth() / 2;
611            int y = mIconView.getTop() + mIconView.getHeight() / 2;
612            Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y,
613                    getWidth(), 0);
614            revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
615            revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
616            revealAnim.addListener(new AnimatorListenerAdapter() {
617                @Override
618                public void onAnimationEnd(Animator animation) {
619                    mAppOverlayView.setVisibility(View.GONE);
620                }
621            });
622            revealAnim.start();
623        }
624    }
625}
626