/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.recents.views;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.os.CountDownTimer;
import android.support.v4.graphics.ColorUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.LaunchTaskEvent;
import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;

import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;

/* The task bar view */
public class TaskViewHeader extends FrameLayout
        implements View.OnClickListener, View.OnLongClickListener {

    private static final float HIGHLIGHT_LIGHTNESS_INCREMENT = 0.075f;
    private static final float OVERLAY_LIGHTNESS_INCREMENT = -0.0625f;
    private static final int OVERLAY_REVEAL_DURATION = 250;
    private static final long FOCUS_INDICATOR_INTERVAL_MS = 30;

    /**
     * A color drawable that draws a slight highlight at the top to help it stand out.
     */
    private class HighlightColorDrawable extends Drawable {

        private Paint mHighlightPaint = new Paint();
        private Paint mBackgroundPaint = new Paint();
        private int mColor;
        private float mDimAlpha;

        public HighlightColorDrawable() {
            mBackgroundPaint.setColor(Color.argb(255, 0, 0, 0));
            mBackgroundPaint.setAntiAlias(true);
            mHighlightPaint.setColor(Color.argb(255, 255, 255, 255));
            mHighlightPaint.setAntiAlias(true);
        }

        public void setColorAndDim(int color, float dimAlpha) {
            if (mColor != color || Float.compare(mDimAlpha, dimAlpha) != 0) {
                mColor = color;
                mDimAlpha = dimAlpha;
                if (mShouldDarkenBackgroundColor) {
                    color = getSecondaryColor(color, false /* useLightOverlayColor */);
                }
                mBackgroundPaint.setColor(color);

                ColorUtils.colorToHSL(color, mTmpHSL);
                // TODO: Consider using the saturation of the color to adjust the lightness as well
                mTmpHSL[2] = Math.min(1f,
                        mTmpHSL[2] + HIGHLIGHT_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
                mHighlightPaint.setColor(ColorUtils.HSLToColor(mTmpHSL));

                invalidateSelf();
            }
        }

        @Override
        public void setColorFilter(@Nullable ColorFilter colorFilter) {
            // Do nothing
        }

        @Override
        public void setAlpha(int alpha) {
            // Do nothing
        }

        @Override
        public void draw(Canvas canvas) {
            // Draw the highlight at the top edge (but put the bottom edge just out of view)
            canvas.drawRoundRect(0, 0, mTaskViewRect.width(),
                    2 * Math.max(mHighlightHeight, mCornerRadius),
                    mCornerRadius, mCornerRadius, mHighlightPaint);

            // Draw the background with the rounded corners
            canvas.drawRoundRect(0, mHighlightHeight, mTaskViewRect.width(),
                    getHeight() + mCornerRadius,
                    mCornerRadius, mCornerRadius, mBackgroundPaint);
        }

        @Override
        public int getOpacity() {
            return PixelFormat.OPAQUE;
        }

        public int getColor() {
            return mColor;
        }
    }

    Task mTask;

    // Header views
    ImageView mIconView;
    TextView mTitleView;
    ImageView mMoveTaskButton;
    ImageView mDismissButton;
    FrameLayout mAppOverlayView;
    ImageView mAppIconView;
    ImageView mAppInfoView;
    TextView mAppTitleView;
    ProgressBar mFocusTimerIndicator;

    // Header drawables
    @ViewDebug.ExportedProperty(category="recents")
    Rect mTaskViewRect = new Rect();
    int mHeaderBarHeight;
    int mHeaderButtonPadding;
    int mCornerRadius;
    int mHighlightHeight;
    @ViewDebug.ExportedProperty(category="recents")
    float mDimAlpha;
    Drawable mLightDismissDrawable;
    Drawable mDarkDismissDrawable;
    Drawable mLightFreeformIcon;
    Drawable mDarkFreeformIcon;
    Drawable mLightFullscreenIcon;
    Drawable mDarkFullscreenIcon;
    Drawable mLightInfoIcon;
    Drawable mDarkInfoIcon;
    int mTaskBarViewLightTextColor;
    int mTaskBarViewDarkTextColor;
    int mDisabledTaskBarBackgroundColor;
    int mMoveTaskTargetStackId = INVALID_STACK_ID;

    // Header background
    private HighlightColorDrawable mBackground;
    private HighlightColorDrawable mOverlayBackground;
    private float[] mTmpHSL = new float[3];

    // Header dim, which is only used when task view hardware layers are not used
    private Paint mDimLayerPaint = new Paint();

    // Whether the background color should be darkened to differentiate from the primary color.
    // Used in grid layout.
    private boolean mShouldDarkenBackgroundColor = false;

    private CountDownTimer mFocusTimerCountDown;

    public TaskViewHeader(Context context) {
        this(context, null);
    }

    public TaskViewHeader(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        setWillNotDraw(false);

        // Load the dismiss resources
        Resources res = context.getResources();
        mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light);
        mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark);
        mCornerRadius = Recents.getConfiguration().isGridEnabled ?
                res.getDimensionPixelSize(R.dimen.recents_grid_task_view_rounded_corners_radius) :
                res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
        mHighlightHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight);
        mTaskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color);
        mTaskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color);
        mLightFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_light);
        mDarkFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_dark);
        mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light);
        mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark);
        mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light);
        mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark);
        mDisabledTaskBarBackgroundColor =
                context.getColor(R.color.recents_task_bar_disabled_background_color);

        // Configure the background and dim
        mBackground = new HighlightColorDrawable();
        mBackground.setColorAndDim(Color.argb(255, 0, 0, 0), 0f);
        setBackground(mBackground);
        mOverlayBackground = new HighlightColorDrawable();
        mDimLayerPaint.setColor(Color.argb(255, 0, 0, 0));
        mDimLayerPaint.setAntiAlias(true);
    }

    /**
     * Resets this header along with the TaskView.
     */
    public void reset() {
        hideAppOverlay(true /* immediate */);
    }

    @Override
    protected void onFinishInflate() {
        SystemServicesProxy ssp = Recents.getSystemServices();

        // Initialize the icon and description views
        mIconView = findViewById(R.id.icon);
        mIconView.setOnLongClickListener(this);
        mTitleView = findViewById(R.id.title);
        mDismissButton = findViewById(R.id.dismiss_task);
        if (ssp.hasFreeformWorkspaceSupport()) {
            mMoveTaskButton = findViewById(R.id.move_task);
        }

        onConfigurationChanged();
    }

    /**
     * Programmatically sets the layout params for a header bar layout.  This is necessary because
     * we can't get resources based on the current configuration, but instead need to get them
     * based on the device configuration.
     */
    private void updateLayoutParams(View icon, View title, View secondaryButton, View button) {
        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, mHeaderBarHeight, Gravity.TOP);
        setLayoutParams(lp);
        lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.START);
        icon.setLayoutParams(lp);
        lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL);
        lp.setMarginStart(mHeaderBarHeight);
        lp.setMarginEnd(mMoveTaskButton != null
                ? 2 * mHeaderBarHeight
                : mHeaderBarHeight);
        title.setLayoutParams(lp);
        if (secondaryButton != null) {
            lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.END);
            lp.setMarginEnd(mHeaderBarHeight);
            secondaryButton.setLayoutParams(lp);
            secondaryButton.setPadding(mHeaderButtonPadding, mHeaderButtonPadding,
                    mHeaderButtonPadding, mHeaderButtonPadding);
        }
        lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.END);
        button.setLayoutParams(lp);
        button.setPadding(mHeaderButtonPadding, mHeaderButtonPadding, mHeaderButtonPadding,
                mHeaderButtonPadding);
    }

    /**
     * Update the header view when the configuration changes.
     */
    public void onConfigurationChanged() {
        // Update the dimensions of everything in the header. We do this because we need to use
        // resources for the display, and not the current configuration.
        Resources res = getResources();
        int headerBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(getContext(),
                R.dimen.recents_task_view_header_height,
                R.dimen.recents_task_view_header_height,
                R.dimen.recents_task_view_header_height,
                R.dimen.recents_task_view_header_height_tablet_land,
                R.dimen.recents_task_view_header_height,
                R.dimen.recents_task_view_header_height_tablet_land,
                R.dimen.recents_grid_task_view_header_height);
        int headerButtonPadding = TaskStackLayoutAlgorithm.getDimensionForDevice(getContext(),
                R.dimen.recents_task_view_header_button_padding,
                R.dimen.recents_task_view_header_button_padding,
                R.dimen.recents_task_view_header_button_padding,
                R.dimen.recents_task_view_header_button_padding_tablet_land,
                R.dimen.recents_task_view_header_button_padding,
                R.dimen.recents_task_view_header_button_padding_tablet_land,
                R.dimen.recents_grid_task_view_header_button_padding);
        if (headerBarHeight != mHeaderBarHeight || headerButtonPadding != mHeaderButtonPadding) {
            mHeaderBarHeight = headerBarHeight;
            mHeaderButtonPadding = headerButtonPadding;
            updateLayoutParams(mIconView, mTitleView, mMoveTaskButton, mDismissButton);
            if (mAppOverlayView != null) {
                updateLayoutParams(mAppIconView, mAppTitleView, null, mAppInfoView);
            }
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        // Since we update the position of children based on the width of the parent and this view
        // recompute these changes with the new view size
        onTaskViewSizeChanged(mTaskViewRect.width(), mTaskViewRect.height());
    }

    /**
     * Called when the task view frame changes, allowing us to move the contents of the header
     * to match the frame changes.
     */
    public void onTaskViewSizeChanged(int width, int height) {
        mTaskViewRect.set(0, 0, width, height);

        boolean showTitle = true;
        boolean showMoveIcon = true;
        boolean showDismissIcon = true;
        int rightInset = width - getMeasuredWidth();

        if (mTask != null && mTask.isFreeformTask()) {
            // For freeform tasks, we always show the app icon, and only show the title, move-task
            // icon, and the dismiss icon if there is room
            int appIconWidth = mIconView.getMeasuredWidth();
            int titleWidth = (int) mTitleView.getPaint().measureText(mTask.title);
            int dismissWidth = mDismissButton.getMeasuredWidth();
            int moveTaskWidth = mMoveTaskButton != null
                    ? mMoveTaskButton.getMeasuredWidth()
                    : 0;
            showTitle = width >= (appIconWidth + dismissWidth + moveTaskWidth + titleWidth);
            showMoveIcon = width >= (appIconWidth + dismissWidth + moveTaskWidth);
            showDismissIcon = width >= (appIconWidth + dismissWidth);
        }

        mTitleView.setVisibility(showTitle ? View.VISIBLE : View.INVISIBLE);
        if (mMoveTaskButton != null) {
            mMoveTaskButton.setVisibility(showMoveIcon ? View.VISIBLE : View.INVISIBLE);
            mMoveTaskButton.setTranslationX(rightInset);
        }
        mDismissButton.setVisibility(showDismissIcon ? View.VISIBLE : View.INVISIBLE);
        mDismissButton.setTranslationX(rightInset);

        setLeftTopRightBottom(0, 0, width, getMeasuredHeight());
    }

    @Override
    public void onDrawForeground(Canvas canvas) {
        super.onDrawForeground(canvas);

        // Draw the dim layer with the rounded corners
        canvas.drawRoundRect(0, 0, mTaskViewRect.width(), getHeight() + mCornerRadius,
                mCornerRadius, mCornerRadius, mDimLayerPaint);
    }

    /** Starts the focus timer. */
    public void startFocusTimerIndicator(int duration) {
        if (mFocusTimerIndicator == null) {
            return;
        }

        mFocusTimerIndicator.setVisibility(View.VISIBLE);
        mFocusTimerIndicator.setMax(duration);
        mFocusTimerIndicator.setProgress(duration);
        if (mFocusTimerCountDown != null) {
            mFocusTimerCountDown.cancel();
        }
        mFocusTimerCountDown = new CountDownTimer(duration,
                FOCUS_INDICATOR_INTERVAL_MS) {
            public void onTick(long millisUntilFinished) {
                mFocusTimerIndicator.setProgress((int) millisUntilFinished);
            }

            public void onFinish() {
                // Do nothing
            }
        }.start();
    }

    /** Cancels the focus timer. */
    public void cancelFocusTimerIndicator() {
        if (mFocusTimerIndicator == null) {
            return;
        }

        if (mFocusTimerCountDown != null) {
            mFocusTimerCountDown.cancel();
            mFocusTimerIndicator.setProgress(0);
            mFocusTimerIndicator.setVisibility(View.INVISIBLE);
        }
    }

    /** Only exposed for the workaround for b/27815919. */
    public ImageView getIconView() {
        return mIconView;
    }

    /** Returns the secondary color for a primary color. */
    int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
        int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
        return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f);
    }

    /**
     * Sets the dim alpha, only used when we are not using hardware layers.
     * (see RecentsConfiguration.useHardwareLayers)
     */
    public void setDimAlpha(float dimAlpha) {
        if (Float.compare(mDimAlpha, dimAlpha) != 0) {
            mDimAlpha = dimAlpha;
            mTitleView.setAlpha(1f - dimAlpha);
            updateBackgroundColor(mBackground.getColor(), dimAlpha);
        }
    }

    /**
     * Updates the background and highlight colors for this header.
     */
    private void updateBackgroundColor(int color, float dimAlpha) {
        if (mTask != null) {
            mBackground.setColorAndDim(color, dimAlpha);
            // TODO: Consider using the saturation of the color to adjust the lightness as well
            ColorUtils.colorToHSL(color, mTmpHSL);
            mTmpHSL[2] = Math.min(1f, mTmpHSL[2] + OVERLAY_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
            mOverlayBackground.setColorAndDim(ColorUtils.HSLToColor(mTmpHSL), dimAlpha);
            mDimLayerPaint.setAlpha((int) (dimAlpha * 255));
            invalidate();
        }
    }

    /**
     * Sets whether the background color should be darkened to differentiate from the primary color.
     */
    public void setShouldDarkenBackgroundColor(boolean flag) {
        mShouldDarkenBackgroundColor = flag;
    }

    /**
     * Binds the bar view to the task.
     */
    public void bindToTask(Task t, boolean touchExplorationEnabled, boolean disabledInSafeMode) {
        mTask = t;

        int primaryColor = disabledInSafeMode
                ? mDisabledTaskBarBackgroundColor
                : t.colorPrimary;
        if (mBackground.getColor() != primaryColor) {
            updateBackgroundColor(primaryColor, mDimAlpha);
        }
        if (!mTitleView.getText().toString().equals(t.title)) {
            mTitleView.setText(t.title);
        }
        mTitleView.setContentDescription(t.titleDescription);
        mTitleView.setTextColor(t.useLightOnPrimaryColor ?
                mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
        mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
                mLightDismissDrawable : mDarkDismissDrawable);
        mDismissButton.setContentDescription(t.dismissDescription);
        mDismissButton.setOnClickListener(this);
        mDismissButton.setClickable(false);
        ((RippleDrawable) mDismissButton.getBackground()).setForceSoftware(true);

        // When freeform workspaces are enabled, then update the move-task button depending on the
        // current task
        if (mMoveTaskButton != null) {
            if (t.isFreeformTask()) {
                mMoveTaskTargetStackId = FULLSCREEN_WORKSPACE_STACK_ID;
                mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
                        ? mLightFullscreenIcon
                        : mDarkFullscreenIcon);
            } else {
                mMoveTaskTargetStackId = FREEFORM_WORKSPACE_STACK_ID;
                mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
                        ? mLightFreeformIcon
                        : mDarkFreeformIcon);
            }
            mMoveTaskButton.setOnClickListener(this);
            mMoveTaskButton.setClickable(false);
            ((RippleDrawable) mMoveTaskButton.getBackground()).setForceSoftware(true);
        }

        if (Recents.getDebugFlags().isFastToggleRecentsEnabled()) {
            if (mFocusTimerIndicator == null) {
                mFocusTimerIndicator = (ProgressBar) Utilities.findViewStubById(this,
                        R.id.focus_timer_indicator_stub).inflate();
            }
            mFocusTimerIndicator.getProgressDrawable()
                    .setColorFilter(
                            getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor),
                            PorterDuff.Mode.SRC_IN);
        }

        // In accessibility, a single click on the focused app info button will show it
        if (touchExplorationEnabled) {
            mIconView.setContentDescription(t.appInfoDescription);
            mIconView.setOnClickListener(this);
            mIconView.setClickable(true);
        }
    }

    /**
     * Called when the bound task's data has loaded and this view should update to reflect the
     * changes.
     */
    public void onTaskDataLoaded() {
        if (mTask != null && mTask.icon != null) {
            mIconView.setImageDrawable(mTask.icon);
        }
    }

    /** Unbinds the bar view from the task */
    void unbindFromTask(boolean touchExplorationEnabled) {
        mTask = null;
        mIconView.setImageDrawable(null);
        if (touchExplorationEnabled) {
            mIconView.setClickable(false);
        }
    }

    /** Animates this task bar if the user does not interact with the stack after a certain time. */
    void startNoUserInteractionAnimation() {
        int duration = getResources().getInteger(R.integer.recents_task_enter_from_app_duration);
        mDismissButton.setVisibility(View.VISIBLE);
        mDismissButton.setClickable(true);
        if (mDismissButton.getVisibility() == VISIBLE) {
            mDismissButton.animate()
                    .alpha(1f)
                    .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
                    .setDuration(duration)
                    .start();
        } else {
            mDismissButton.setAlpha(1f);
        }
        if (mMoveTaskButton != null) {
            if (mMoveTaskButton.getVisibility() == VISIBLE) {
                mMoveTaskButton.setVisibility(View.VISIBLE);
                mMoveTaskButton.setClickable(true);
                mMoveTaskButton.animate()
                        .alpha(1f)
                        .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
                        .setDuration(duration)
                        .start();
            } else {
                mMoveTaskButton.setAlpha(1f);
            }
        }
    }

    /**
     * Mark this task view that the user does has not interacted with the stack after a certain
     * time.
     */
    public void setNoUserInteractionState() {
        mDismissButton.setVisibility(View.VISIBLE);
        mDismissButton.animate().cancel();
        mDismissButton.setAlpha(1f);
        mDismissButton.setClickable(true);
        if (mMoveTaskButton != null) {
            mMoveTaskButton.setVisibility(View.VISIBLE);
            mMoveTaskButton.animate().cancel();
            mMoveTaskButton.setAlpha(1f);
            mMoveTaskButton.setClickable(true);
        }
    }

    /**
     * Resets the state tracking that the user has not interacted with the stack after a certain
     * time.
     */
    void resetNoUserInteractionState() {
        mDismissButton.setVisibility(View.INVISIBLE);
        mDismissButton.setAlpha(0f);
        mDismissButton.setClickable(false);
        if (mMoveTaskButton != null) {
            mMoveTaskButton.setVisibility(View.INVISIBLE);
            mMoveTaskButton.setAlpha(0f);
            mMoveTaskButton.setClickable(false);
        }
    }

    @Override
    protected int[] onCreateDrawableState(int extraSpace) {

        // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged.
        // This is to prevent layer trashing when the view is pressed.
        return new int[] {};
    }

    @Override
    public void onClick(View v) {
        if (v == mIconView) {
            // In accessibility, a single click on the focused app info button will show it
            EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
        } else if (v == mDismissButton) {
            TaskView tv = Utilities.findParent(this, TaskView.class);
            tv.dismissTask();

            // Keep track of deletions by the dismiss button
            MetricsLogger.histogram(getContext(), "overview_task_dismissed_source",
                    Constants.Metrics.DismissSourceHeaderButton);
        } else if (v == mMoveTaskButton) {
            TaskView tv = Utilities.findParent(this, TaskView.class);
            EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, null,
                    mMoveTaskTargetStackId, false));
        } else if (v == mAppInfoView) {
            EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
        } else if (v == mAppIconView) {
            hideAppOverlay(false /* immediate */);
        }
    }

    @Override
    public boolean onLongClick(View v) {
        if (v == mIconView) {
            showAppOverlay();
            return true;
        } else if (v == mAppIconView) {
            hideAppOverlay(false /* immediate */);
            return true;
        }
        return false;
    }

    /**
     * Shows the application overlay.
     */
    private void showAppOverlay() {
        // Skip early if the task is invalid
        SystemServicesProxy ssp = Recents.getSystemServices();
        ComponentName cn = mTask.key.getComponent();
        int userId = mTask.key.userId;
        ActivityInfo activityInfo = ssp.getActivityInfo(cn, userId);
        if (activityInfo == null) {
            return;
        }

        // Inflate the overlay if necessary
        if (mAppOverlayView == null) {
            mAppOverlayView = (FrameLayout) Utilities.findViewStubById(this,
                    R.id.app_overlay_stub).inflate();
            mAppOverlayView.setBackground(mOverlayBackground);
            mAppIconView = (ImageView) mAppOverlayView.findViewById(R.id.app_icon);
            mAppIconView.setOnClickListener(this);
            mAppIconView.setOnLongClickListener(this);
            mAppInfoView = (ImageView) mAppOverlayView.findViewById(R.id.app_info);
            mAppInfoView.setOnClickListener(this);
            mAppTitleView = (TextView) mAppOverlayView.findViewById(R.id.app_title);
            updateLayoutParams(mAppIconView, mAppTitleView, null, mAppInfoView);
        }

        // Update the overlay contents for the current app
        mAppTitleView.setText(ssp.getBadgedApplicationLabel(activityInfo.applicationInfo, userId));
        mAppTitleView.setTextColor(mTask.useLightOnPrimaryColor ?
                mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
        mAppIconView.setImageDrawable(ssp.getBadgedApplicationIcon(activityInfo.applicationInfo,
                userId));
        mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor
                ? mLightInfoIcon
                : mDarkInfoIcon);
        mAppOverlayView.setVisibility(View.VISIBLE);

        int x = mIconView.getLeft() + mIconView.getWidth() / 2;
        int y = mIconView.getTop() + mIconView.getHeight() / 2;
        Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, 0,
                getWidth());
        revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
        revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
        revealAnim.start();
    }

    /**
     * Hide the application overlay.
     */
    private void hideAppOverlay(boolean immediate) {
        // Skip if we haven't even loaded the overlay yet
        if (mAppOverlayView == null) {
            return;
        }

        if (immediate) {
            mAppOverlayView.setVisibility(View.GONE);
        } else {
            int x = mIconView.getLeft() + mIconView.getWidth() / 2;
            int y = mIconView.getTop() + mIconView.getHeight() / 2;
            Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y,
                    getWidth(), 0);
            revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
            revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
            revealAnim.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    mAppOverlayView.setVisibility(View.GONE);
                }
            });
            revealAnim.start();
        }
    }
}
