TaskViewHeader.java revision 3c107166fc1f719f3b94a706fdf52a23b1820761
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.ViewStub;
40import android.view.animation.AnimationUtils;
41import android.view.animation.Interpolator;
42import android.widget.FrameLayout;
43import android.widget.ImageView;
44import android.widget.ProgressBar;
45import android.widget.TextView;
46import com.android.internal.logging.MetricsLogger;
47import com.android.systemui.R;
48import com.android.systemui.recents.Constants;
49import com.android.systemui.recents.Recents;
50import com.android.systemui.recents.RecentsDebugFlags;
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.125f;
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
79        public HighlightColorDrawable() {
80            mBackgroundPaint.setColor(Color.argb(255, 0, 0, 0));
81            mBackgroundPaint.setAntiAlias(true);
82            mHighlightPaint.setColor(Color.argb(255, 255, 255, 255));
83            mHighlightPaint.setAntiAlias(true);
84        }
85
86        public void setColorAndDim(int color, float dimAlpha) {
87            mBackgroundPaint.setColor(color);
88
89            ColorUtils.colorToHSL(color, mTmpHSL);
90            // TODO: Consider using the saturation of the color to adjust the lightness as well
91            mTmpHSL[2] = Math.min(1f,
92                    mTmpHSL[2] + HIGHLIGHT_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
93            mHighlightPaint.setColor(ColorUtils.HSLToColor(mTmpHSL));
94
95            invalidateSelf();
96        }
97
98        @Override
99        public void setColorFilter(@Nullable ColorFilter colorFilter) {
100            // Do nothing
101        }
102
103        @Override
104        public void setAlpha(int alpha) {
105            // Do nothing
106        }
107
108        @Override
109        public void draw(Canvas canvas) {
110            // Draw the highlight at the top edge (but put the bottom edge just out of view)
111            canvas.drawRoundRect(0, 0, mTaskViewRect.width(),
112                    2 * Math.max(mHighlightHeight, mCornerRadius),
113                    mCornerRadius, mCornerRadius, mHighlightPaint);
114
115            // Draw the background with the rounded corners
116            canvas.drawRoundRect(0, mHighlightHeight, mTaskViewRect.width(),
117                    getHeight() + mCornerRadius,
118                    mCornerRadius, mCornerRadius, mBackgroundPaint);
119        }
120
121        @Override
122        public int getOpacity() {
123            return PixelFormat.OPAQUE;
124        }
125    }
126
127    Task mTask;
128
129    // Header views
130    ImageView mIconView;
131    TextView mTitleView;
132    ImageView mMoveTaskButton;
133    ImageView mDismissButton;
134    ViewStub mAppOverlayViewStub;
135    FrameLayout mAppOverlayView;
136    ImageView mAppIconView;
137    ImageView mAppInfoView;
138    TextView mAppTitleView;
139    ViewStub mFocusTimerIndicatorStub;
140    ProgressBar mFocusTimerIndicator;
141
142    // Header drawables
143    Rect mTaskViewRect = new Rect();
144    int mCornerRadius;
145    int mHighlightHeight;
146    float mDimAlpha;
147    Drawable mLightDismissDrawable;
148    Drawable mDarkDismissDrawable;
149    Drawable mLightFreeformIcon;
150    Drawable mDarkFreeformIcon;
151    Drawable mLightFullscreenIcon;
152    Drawable mDarkFullscreenIcon;
153    Drawable mLightInfoIcon;
154    Drawable mDarkInfoIcon;
155    int mTaskBarViewLightTextColor;
156    int mTaskBarViewDarkTextColor;
157    int mMoveTaskTargetStackId = INVALID_STACK_ID;
158
159    // Header background
160    private HighlightColorDrawable mBackground;
161    private HighlightColorDrawable mOverlayBackground;
162    private float[] mTmpHSL = new float[3];
163
164    // Header dim, which is only used when task view hardware layers are not used
165    private Paint mDimLayerPaint = new Paint();
166
167    Interpolator mFastOutSlowInInterpolator;
168    Interpolator mFastOutLinearInInterpolator;
169    Interpolator mLinearOutSlowInInterpolator;
170
171    private CountDownTimer mFocusTimerCountDown;
172    private long mFocusTimerDuration;
173
174    public TaskViewHeader(Context context) {
175        this(context, null);
176    }
177
178    public TaskViewHeader(Context context, AttributeSet attrs) {
179        this(context, attrs, 0);
180    }
181
182    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) {
183        this(context, attrs, defStyleAttr, 0);
184    }
185
186    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
187        super(context, attrs, defStyleAttr, defStyleRes);
188        setWillNotDraw(false);
189
190        // Load the dismiss resources
191        Resources res = context.getResources();
192        mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light);
193        mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark);
194        mCornerRadius = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
195        mHighlightHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight);
196        mTaskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color);
197        mTaskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color);
198        mLightFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_light);
199        mDarkFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_dark);
200        mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light);
201        mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark);
202        mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light);
203        mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark);
204
205        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
206                com.android.internal.R.interpolator.fast_out_slow_in);
207        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
208                com.android.internal.R.interpolator.fast_out_linear_in);
209        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
210                com.android.internal.R.interpolator.linear_out_slow_in);
211
212        // Configure the background and dim
213        mBackground = new HighlightColorDrawable();
214        mBackground.setColorAndDim(Color.argb(255, 0, 0, 0), 0f);
215        setBackground(mBackground);
216        mOverlayBackground = new HighlightColorDrawable();
217        mDimLayerPaint.setColor(Color.argb(255, 0, 0, 0));
218        mDimLayerPaint.setAntiAlias(true);
219        mFocusTimerDuration = res.getInteger(R.integer.recents_auto_advance_duration);
220    }
221
222    /**
223     * Resets this header along with the TaskView.
224     */
225    public void reset() {
226        hideAppOverlay(true /* immediate */);
227    }
228
229    @Override
230    protected void onFinishInflate() {
231        // Initialize the icon and description views
232        mIconView = (ImageView) findViewById(R.id.icon);
233        mIconView.setClickable(false);
234        mIconView.setOnLongClickListener(this);
235        mTitleView = (TextView) findViewById(R.id.title);
236        mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
237        mDismissButton.setOnClickListener(this);
238        mMoveTaskButton = (ImageView) findViewById(R.id.move_task);
239        mFocusTimerIndicatorStub = (ViewStub) findViewById(R.id.focus_timer_indicator_stub);
240        mAppOverlayViewStub = (ViewStub) findViewById(R.id.app_overlay_stub);
241    }
242
243    /**
244     * Called when the task view frame changes, allowing us to move the contents of the header
245     * to match the frame changes.
246     */
247    public void onTaskViewSizeChanged(int width, int height) {
248        // TODO: Optimize this path
249        mTaskViewRect.set(0, 0, width, height);
250        boolean updateMoveTaskButton = mMoveTaskButton.getVisibility() != View.GONE;
251        boolean isFreeformTask = (mTask != null) && mTask.isFreeformTask();
252        int appIconWidth = mIconView.getMeasuredWidth();
253        int activityDescWidth = (mTask != null)
254                ? (int) mTitleView.getPaint().measureText(mTask.title)
255                : mTitleView.getMeasuredWidth();
256        int dismissIconWidth = mDismissButton.getMeasuredWidth();
257        int moveTaskIconWidth = mMoveTaskButton.getVisibility() == View.VISIBLE
258                ? mMoveTaskButton.getMeasuredWidth()
259                : 0;
260
261        // Priority-wise, we show the activity icon first, the dismiss icon if there is room, the
262        // move-task icon if there is room, and then finally, the activity label if there is room
263        if (isFreeformTask && width < (appIconWidth + dismissIconWidth)) {
264            mTitleView.setVisibility(View.INVISIBLE);
265            if (updateMoveTaskButton) {
266                mMoveTaskButton.setVisibility(View.INVISIBLE);
267            }
268            mDismissButton.setVisibility(View.INVISIBLE);
269        } else if (isFreeformTask && width < (appIconWidth + dismissIconWidth +
270                moveTaskIconWidth)) {
271            mTitleView.setVisibility(View.INVISIBLE);
272            if (updateMoveTaskButton) {
273                mMoveTaskButton.setVisibility(View.INVISIBLE);
274            }
275            mDismissButton.setVisibility(View.VISIBLE);
276        } else if (isFreeformTask && width < (appIconWidth + dismissIconWidth + moveTaskIconWidth +
277                activityDescWidth)) {
278            mTitleView.setVisibility(View.INVISIBLE);
279            if (updateMoveTaskButton) {
280                mMoveTaskButton.setVisibility(View.VISIBLE);
281            }
282            mDismissButton.setVisibility(View.VISIBLE);
283        } else {
284            mTitleView.setVisibility(View.VISIBLE);
285            if (updateMoveTaskButton) {
286                mMoveTaskButton.setVisibility(View.VISIBLE);
287            }
288            mDismissButton.setVisibility(View.VISIBLE);
289        }
290        if (updateMoveTaskButton) {
291            mMoveTaskButton.setTranslationX(width - getMeasuredWidth());
292        }
293        mDismissButton.setTranslationX(width - getMeasuredWidth());
294        invalidate();
295    }
296
297    @Override
298    protected void dispatchDraw(Canvas canvas) {
299        super.dispatchDraw(canvas);
300
301        // Draw the dim layer with the rounded corners
302        canvas.drawRoundRect(0, 0, mTaskViewRect.width(), getHeight() + mCornerRadius,
303                mCornerRadius, mCornerRadius, mDimLayerPaint);
304    }
305
306    /** Starts the focus timer. */
307    public void startFocusTimerIndicator() {
308        if (mFocusTimerIndicator == null) {
309            return;
310        }
311
312        mFocusTimerIndicator.setVisibility(View.VISIBLE);
313        mFocusTimerIndicator.setMax((int) mFocusTimerDuration);
314        if (mFocusTimerCountDown == null) {
315            mFocusTimerCountDown = new CountDownTimer(mFocusTimerDuration,
316                    FOCUS_INDICATOR_INTERVAL_MS) {
317                public void onTick(long millisUntilFinished) {
318                    mFocusTimerIndicator.setProgress((int) millisUntilFinished);
319                }
320
321                public void onFinish() {
322                    mFocusTimerIndicator.setProgress((int) mFocusTimerDuration);
323                }
324            }.start();
325        } else {
326            mFocusTimerCountDown.start();
327        }
328    }
329
330    /** Cancels the focus timer. */
331    public void cancelFocusTimerIndicator() {
332        if (mFocusTimerIndicator == null) {
333            return;
334        }
335
336        if (mFocusTimerCountDown != null) {
337            mFocusTimerCountDown.cancel();
338            mFocusTimerIndicator.setProgress(0);
339            mFocusTimerIndicator.setVisibility(View.INVISIBLE);
340        }
341    }
342
343    /** Returns the secondary color for a primary color. */
344    int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
345        int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
346        return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f);
347    }
348
349    /**
350     * Sets the dim alpha, only used when we are not using hardware layers.
351     * (see RecentsConfiguration.useHardwareLayers)
352     */
353    void setDimAlpha(float dimAlpha) {
354        mDimAlpha = dimAlpha;
355        updateBackgroundColor(dimAlpha);
356        invalidate();
357    }
358
359    /**
360     * Updates the background and highlight colors for this header.
361     */
362    private void updateBackgroundColor(float dimAlpha) {
363        if (mTask != null) {
364            mBackground.setColorAndDim(mTask.colorPrimary, dimAlpha);
365            // TODO: Consider using the saturation of the color to adjust the lightness as well
366            ColorUtils.colorToHSL(mTask.colorPrimary, mTmpHSL);
367            mTmpHSL[2] = Math.min(1f, mTmpHSL[2] + OVERLAY_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
368            mOverlayBackground.setColorAndDim(ColorUtils.HSLToColor(mTmpHSL), dimAlpha);
369            mDimLayerPaint.setAlpha((int) (dimAlpha * 255));
370        }
371    }
372
373    /** Binds the bar view to the task */
374    public void rebindToTask(Task t) {
375        SystemServicesProxy ssp = Recents.getSystemServices();
376        mTask = t;
377
378        // If an activity icon is defined, then we use that as the primary icon to show in the bar,
379        // otherwise, we fall back to the application icon
380        updateBackgroundColor(mDimAlpha);
381        if (t.icon != null) {
382            mIconView.setImageDrawable(t.icon);
383        }
384        if (!mTitleView.getText().toString().equals(t.title)) {
385            mTitleView.setText(t.title);
386        }
387        mTitleView.setContentDescription(t.contentDescription);
388        mTitleView.setTextColor(t.useLightOnPrimaryColor ?
389                mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
390        mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
391                mLightDismissDrawable : mDarkDismissDrawable);
392        mDismissButton.setContentDescription(t.dismissDescription);
393
394        // When freeform workspaces are enabled, then update the move-task button depending on the
395        // current task
396        if (ssp.hasFreeformWorkspaceSupport()) {
397            if (t.isFreeformTask()) {
398                mMoveTaskTargetStackId = FULLSCREEN_WORKSPACE_STACK_ID;
399                mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
400                        ? mLightFullscreenIcon
401                        : mDarkFullscreenIcon);
402            } else {
403                mMoveTaskTargetStackId = FREEFORM_WORKSPACE_STACK_ID;
404                mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
405                        ? mLightFreeformIcon
406                        : mDarkFreeformIcon);
407            }
408            if (mMoveTaskButton.getVisibility() != View.VISIBLE) {
409                mMoveTaskButton.setVisibility(View.VISIBLE);
410            }
411            mMoveTaskButton.setOnClickListener(this);
412        }
413
414        if (Recents.getDebugFlags().isFastToggleIndicatorEnabled()) {
415            if (mFocusTimerIndicator == null) {
416                mFocusTimerIndicator = (ProgressBar) mFocusTimerIndicatorStub.inflate();
417            }
418            mFocusTimerIndicator.getProgressDrawable()
419                    .setColorFilter(
420                            getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor),
421                            PorterDuff.Mode.SRC_IN);
422        }
423
424        // In accessibility, a single click on the focused app info button will show it
425        if (ssp.isTouchExplorationEnabled()) {
426            mIconView.setOnClickListener(this);
427        }
428    }
429
430    /** Unbinds the bar view from the task */
431    void unbindFromTask() {
432        mTask = null;
433        mIconView.setImageDrawable(null);
434        mIconView.setOnClickListener(null);
435        mMoveTaskButton.setOnClickListener(null);
436    }
437
438    /** Animates this task bar if the user does not interact with the stack after a certain time. */
439    void startNoUserInteractionAnimation() {
440        if (mDismissButton.getVisibility() != View.VISIBLE) {
441            mDismissButton.setVisibility(View.VISIBLE);
442            mDismissButton.setAlpha(0f);
443            mDismissButton.animate()
444                    .alpha(1f)
445                    .setStartDelay(0)
446                    .setInterpolator(mFastOutLinearInInterpolator)
447                    .setDuration(getResources().getInteger(
448                            R.integer.recents_task_enter_from_app_duration))
449                    .start();
450        }
451    }
452
453    /**
454     * Mark this task view that the user does has not interacted with the stack after a certain
455     * time.
456     */
457    void setNoUserInteractionState() {
458        if (mDismissButton.getVisibility() != View.VISIBLE) {
459            mDismissButton.animate().cancel();
460            mDismissButton.setVisibility(View.VISIBLE);
461            mDismissButton.setAlpha(1f);
462        }
463    }
464
465    /**
466     * Resets the state tracking that the user has not interacted with the stack after a certain
467     * time.
468     */
469    void resetNoUserInteractionState() {
470        mDismissButton.setVisibility(View.INVISIBLE);
471    }
472
473    @Override
474    protected int[] onCreateDrawableState(int extraSpace) {
475
476        // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged.
477        // This is to prevent layer trashing when the view is pressed.
478        return new int[] {};
479    }
480
481    @Override
482    public void onClick(View v) {
483        if (v == mIconView) {
484            SystemServicesProxy ssp = Recents.getSystemServices();
485            if (ssp.isTouchExplorationEnabled()) {
486                // In accessibility, a single click on the focused app info button will show it
487                EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
488            }
489        } else if (v == mDismissButton) {
490            TaskView tv = Utilities.findParent(this, TaskView.class);
491            tv.dismissTask();
492
493            // Keep track of deletions by the dismiss button
494            MetricsLogger.histogram(getContext(), "overview_task_dismissed_source",
495                    Constants.Metrics.DismissSourceHeaderButton);
496        } else if (v == mMoveTaskButton) {
497            TaskView tv = Utilities.findParent(this, TaskView.class);
498            Rect bounds = mMoveTaskTargetStackId == FREEFORM_WORKSPACE_STACK_ID
499                    ? new Rect(mTaskViewRect)
500                    : new Rect();
501            EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, bounds,
502                    mMoveTaskTargetStackId, false));
503        } else if (v == mAppInfoView) {
504            EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
505        } else if (v == mAppIconView) {
506            hideAppOverlay(false /* immediate */);
507        }
508    }
509
510    @Override
511    public boolean onLongClick(View v) {
512        if (v == mIconView) {
513            showAppOverlay();
514            return true;
515        } else if (v == mAppIconView) {
516            hideAppOverlay(false /* immediate */);
517            return true;
518        }
519        return false;
520    }
521
522    /**
523     * Shows the application overlay.
524     */
525    private void showAppOverlay() {
526        // Skip early if the task is invalid
527        SystemServicesProxy ssp = Recents.getSystemServices();
528        ComponentName cn = mTask.key.getComponent();
529        int userId = mTask.key.userId;
530        ActivityInfo activityInfo = ssp.getActivityInfo(cn, userId);
531        if (activityInfo == null) {
532            return;
533        }
534
535        // Inflate the overlay if necessary
536        if (mAppOverlayView == null) {
537            mAppOverlayView = (FrameLayout) mAppOverlayViewStub.inflate();
538            mAppOverlayView.setBackground(mOverlayBackground);
539            mAppIconView = (ImageView) mAppOverlayView.findViewById(R.id.app_icon);
540            mAppIconView.setOnClickListener(this);
541            mAppIconView.setOnLongClickListener(this);
542            mAppInfoView = (ImageView) mAppOverlayView.findViewById(R.id.app_info);
543            mAppInfoView.setOnClickListener(this);
544            mAppTitleView = (TextView) mAppOverlayView.findViewById(R.id.app_title);
545        }
546
547        // Update the overlay contents for the current app
548        mAppTitleView.setText(ssp.getBadgedApplicationLabel(activityInfo.applicationInfo, userId));
549        mAppTitleView.setTextColor(mTask.useLightOnPrimaryColor ?
550                mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
551        mAppIconView.setImageDrawable(ssp.getBadgedApplicationIcon(activityInfo.applicationInfo,
552                userId));
553        mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor
554                ? mLightInfoIcon
555                : mDarkInfoIcon);
556        mAppOverlayView.setVisibility(View.VISIBLE);
557
558        int x = mIconView.getLeft() + mIconView.getWidth() / 2;
559        int y = mIconView.getTop() + mIconView.getHeight() / 2;
560        Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, 0,
561                getWidth());
562        revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
563        revealAnim.setInterpolator(mLinearOutSlowInInterpolator);
564        revealAnim.start();
565    }
566
567    /**
568     * Hide the application overlay.
569     */
570    private void hideAppOverlay(boolean immediate) {
571        // Skip if we haven't even loaded the overlay yet
572        if (mAppOverlayView == null) {
573            return;
574        }
575
576        if (immediate) {
577            mAppOverlayView.setVisibility(View.GONE);
578        } else {
579            int x = mIconView.getLeft() + mIconView.getWidth() / 2;
580            int y = mIconView.getTop() + mIconView.getHeight() / 2;
581            Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y,
582                    getWidth(), 0);
583            revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
584            revealAnim.setInterpolator(mLinearOutSlowInInterpolator);
585            revealAnim.addListener(new AnimatorListenerAdapter() {
586                @Override
587                public void onAnimationEnd(Animator animation) {
588                    mAppOverlayView.setVisibility(View.GONE);
589                }
590            });
591            revealAnim.start();
592        }
593    }
594}
595