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