TaskView.java revision 480dd72daf927283997bdb4060091299add66832
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.animation.ObjectAnimator;
22import android.animation.ValueAnimator;
23import android.content.Context;
24import android.graphics.Color;
25import android.graphics.Paint;
26import android.graphics.PorterDuff;
27import android.graphics.PorterDuffColorFilter;
28import android.graphics.Rect;
29import android.util.AttributeSet;
30import android.view.View;
31import android.view.animation.AccelerateInterpolator;
32import android.widget.FrameLayout;
33import com.android.systemui.R;
34import com.android.systemui.recents.AlternateRecentsComponent;
35import com.android.systemui.recents.Constants;
36import com.android.systemui.recents.RecentsConfiguration;
37import com.android.systemui.recents.model.Task;
38
39/* A task view */
40public class TaskView extends FrameLayout implements Task.TaskCallbacks,
41        TaskFooterView.TaskFooterViewCallbacks, View.OnClickListener, View.OnLongClickListener {
42    /** The TaskView callbacks */
43    interface TaskViewCallbacks {
44        public void onTaskViewAppIconClicked(TaskView tv);
45        public void onTaskViewAppInfoClicked(TaskView tv);
46        public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask);
47        public void onTaskViewDismissed(TaskView tv);
48        public void onTaskViewClipStateChanged(TaskView tv);
49    }
50
51    RecentsConfiguration mConfig;
52
53    int mDim;
54    int mMaxDim;
55    AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator();
56    PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.MULTIPLY);
57
58    Task mTask;
59    boolean mTaskDataLoaded;
60    boolean mIsFocused;
61    boolean mIsFullScreenView;
62    boolean mClipViewInStack;
63    AnimateableViewBounds mViewBounds;
64    Paint mLayerPaint = new Paint();
65
66    TaskThumbnailView mThumbnailView;
67    TaskBarView mBarView;
68    TaskFooterView mFooterView;
69    TaskViewCallbacks mCb;
70
71    // Optimizations
72    ValueAnimator.AnimatorUpdateListener mUpdateDimListener =
73            new ValueAnimator.AnimatorUpdateListener() {
74                @Override
75                public void onAnimationUpdate(ValueAnimator animation) {
76                    updateDimOverlayFromScale();
77                }
78            };
79
80
81    public TaskView(Context context) {
82        this(context, null);
83    }
84
85    public TaskView(Context context, AttributeSet attrs) {
86        this(context, attrs, 0);
87    }
88
89    public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
90        this(context, attrs, defStyleAttr, 0);
91    }
92
93    public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
94        super(context, attrs, defStyleAttr, defStyleRes);
95        mConfig = RecentsConfiguration.getInstance();
96        mMaxDim = mConfig.taskStackMaxDim;
97        mClipViewInStack = true;
98        mViewBounds = new AnimateableViewBounds(this, mConfig.taskViewRoundedCornerRadiusPx);
99        setOutlineProvider(mViewBounds);
100        setDim(getDim());
101    }
102
103    /** Set callback */
104    void setCallbacks(TaskViewCallbacks cb) {
105        mCb = cb;
106    }
107
108    /** Gets the task */
109    Task getTask() {
110        return mTask;
111    }
112
113    /** Returns the view bounds. */
114    AnimateableViewBounds getViewBounds() {
115        return mViewBounds;
116    }
117
118    @Override
119    protected void onFinishInflate() {
120        // Bind the views
121        mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
122        mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
123        mFooterView = (TaskFooterView) findViewById(R.id.lock_to_app);
124        if (mConfig.lockToAppEnabled) {
125            mFooterView.setCallbacks(this);
126        } else {
127            mFooterView.setVisibility(View.GONE);
128        }
129    }
130
131    @Override
132    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
133        int width = MeasureSpec.getSize(widthMeasureSpec);
134        int height = MeasureSpec.getSize(heightMeasureSpec);
135
136        // Measure the bar view, thumbnail, and footer
137        mBarView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
138                MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
139        mFooterView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
140                MeasureSpec.makeMeasureSpec(mConfig.taskViewLockToAppButtonHeight,
141                        MeasureSpec.EXACTLY));
142        if (mIsFullScreenView) {
143            // Measure the thumbnail height to be the full dimensions
144            mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
145                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
146        } else {
147            // Measure the thumbnail to be square
148            mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
149                    MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY));
150        }
151        setMeasuredDimension(width, height);
152        invalidateOutline();
153    }
154
155    /** Synchronizes this view's properties with the task's transform */
156    void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) {
157        // Update the bar view
158        mBarView.updateViewPropertiesToTaskTransform(toTransform, duration);
159
160        // If we are a full screen view, then only update the Z to keep it in order
161        // XXX: Also update/animate the dim as well
162        if (mIsFullScreenView) {
163            if (Constants.DebugFlags.App.EnableShadows &&
164                    toTransform.hasTranslationZChangedFrom(getTranslationZ())) {
165                setTranslationZ(toTransform.translationZ);
166            }
167            return;
168        }
169
170        // Apply the transform
171        toTransform.applyToTaskView(this, duration, mConfig.fastOutSlowInInterpolator, false,
172                mUpdateDimListener);
173    }
174
175    /** Resets this view's properties */
176    void resetViewProperties() {
177        setDim(0);
178        TaskViewTransform.reset(this);
179    }
180
181    /**
182     * When we are un/filtering, this method will set up the transform that we are animating to,
183     * in order to hide the task.
184     */
185    void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) {
186        // Fade the view out and slide it away
187        toTransform.alpha = 0f;
188        toTransform.translationY += 200;
189        toTransform.translationZ = 0;
190    }
191
192    /**
193     * When we are un/filtering, this method will setup the transform that we are animating from,
194     * in order to show the task.
195     */
196    void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) {
197        // Fade the view in
198        fromTransform.alpha = 0f;
199    }
200
201    /** Prepares this task view for the enter-recents animations.  This is called earlier in the
202     * first layout because the actual animation into recents may take a long time. */
203    public void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, int offscreenY) {
204        if (mConfig.launchedFromAppWithScreenshot) {
205            if (isTaskViewLaunchTargetTask) {
206                // Also hide the front most task bar view so we can animate it in
207                // mBarView.prepareEnterRecentsAnimation();
208            } else {
209                // Don't do anything for the side views when animating in
210            }
211
212        } else if (mConfig.launchedFromAppWithThumbnail) {
213            if (isTaskViewLaunchTargetTask) {
214                // Hide the front most task bar view so we can animate it in
215                mBarView.prepareEnterRecentsAnimation();
216                // Set the dim to 0 so we can animate it in
217                setDim(0);
218            }
219
220        } else if (mConfig.launchedFromHome) {
221            // Move the task view off screen (below) so we can animate it in
222            setTranslationY(offscreenY);
223            if (Constants.DebugFlags.App.EnableShadows) {
224                setTranslationZ(0);
225            }
226            setScaleX(1f);
227            setScaleY(1f);
228        }
229    }
230
231    /** Animates this task view as it enters recents */
232    public void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
233        TaskViewTransform transform = ctx.currentTaskTransform;
234        Rect taskRect = ctx.currentTaskRect;
235
236        if (mConfig.launchedFromAppWithScreenshot) {
237            if (mTask.isLaunchTarget) {
238                int duration = mConfig.taskViewEnterFromHomeDuration * 5;
239                int windowInsetTop = mConfig.systemInsets.top; // XXX: Should be for the window
240                float taskScale = ((float) taskRect.width() / getMeasuredWidth()) * transform.scale;
241                float taskTranslationY = taskRect.top + transform.translationY - windowInsetTop;
242
243                // Animate the top clip
244                mViewBounds.animateClipTop(windowInsetTop, duration,
245                        new ValueAnimator.AnimatorUpdateListener() {
246                    @Override
247                    public void onAnimationUpdate(ValueAnimator animation) {
248                        int y = (Integer) animation.getAnimatedValue();
249                        mBarView.setTranslationY(y);
250                    }
251                });
252                // Animate the bottom or right clip
253                int size = Math.round((taskRect.width() / taskScale));
254                if (mConfig.hasHorizontalLayout()) {
255                    mViewBounds.animateClipRight(getMeasuredWidth() - size, duration);
256                } else {
257                    mViewBounds.animateClipBottom(getMeasuredHeight() - (windowInsetTop + size), duration);
258                }
259                animate()
260                        .scaleX(taskScale)
261                        .scaleY(taskScale)
262                        .translationY(taskTranslationY)
263                        .setDuration(duration)
264                        .withEndAction(new Runnable() {
265                            @Override
266                            public void run() {
267                                // Animate the task bar of the first task view
268                                mBarView.startEnterRecentsAnimation(0, mThumbnailView.enableTaskBarClipAsRunnable(mBarView));
269                                // Animate the footer into view (if it is the front most task)
270                                animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration);
271                                // Decrement the post animation trigger
272                                ctx.postAnimationTrigger.decrement();
273
274                                // XXX Request layout and only start hte next animation after the next
275                                // layout
276
277                                setIsFullScreen(false);
278                                mThumbnailView.unbindFromScreenshot();
279
280                                // Recycle the full screen screenshot
281                                AlternateRecentsComponent.consumeLastScreenshot();
282                            }
283                        })
284                        .start();
285            } else {
286                // Otherwise, just enable the thumbnail clip
287                mThumbnailView.enableTaskBarClip(mBarView);
288
289                // Animate the footer into view
290                animateFooterVisibility(true, 0);
291            }
292            ctx.postAnimationTrigger.increment();
293
294        } else if (mConfig.launchedFromAppWithThumbnail) {
295            if (mTask.isLaunchTarget) {
296                // Animate the task bar of the first task view
297                mBarView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay,
298                        mThumbnailView.enableTaskBarClipAsRunnable(mBarView));
299
300                // Animate the dim into view as well
301                ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", getDimOverlayFromScale());
302                anim.setStartDelay(mConfig.taskBarEnterAnimDelay);
303                anim.setDuration(mConfig.taskBarEnterAnimDuration);
304                anim.setInterpolator(mConfig.fastOutLinearInInterpolator);
305                anim.addListener(new AnimatorListenerAdapter() {
306                    @Override
307                    public void onAnimationEnd(Animator animation) {
308                        // Decrement the post animation trigger
309                        ctx.postAnimationTrigger.decrement();
310                    }
311                });
312                anim.start();
313                ctx.postAnimationTrigger.increment();
314
315                // Animate the footer into view
316                animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration);
317            } else {
318                mThumbnailView.enableTaskBarClip(mBarView);
319            }
320
321        } else if (mConfig.launchedFromHome) {
322            // Animate the tasks up
323            int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1);
324            int delay = mConfig.taskBarEnterAnimDelay +
325                    frontIndex * mConfig.taskViewEnterFromHomeDelay;
326            if (Constants.DebugFlags.App.EnableShadows) {
327                animate().translationZ(transform.translationZ);
328            }
329            animate()
330                    .scaleX(transform.scale)
331                    .scaleY(transform.scale)
332                    .translationY(transform.translationY)
333                    .setStartDelay(delay)
334                    .setUpdateListener(null)
335                    .setInterpolator(mConfig.quintOutInterpolator)
336                    .setDuration(mConfig.taskViewEnterFromHomeDuration)
337                    .withEndAction(new Runnable() {
338                        @Override
339                        public void run() {
340                            mThumbnailView.enableTaskBarClip(mBarView);
341                            // Decrement the post animation trigger
342                            ctx.postAnimationTrigger.decrement();
343                        }
344                    })
345                    .start();
346            ctx.postAnimationTrigger.increment();
347
348            // Animate the footer into view
349            animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration);
350
351        } else {
352            // Otherwise, just enable the thumbnail clip
353            mThumbnailView.enableTaskBarClip(mBarView);
354
355            // Animate the footer into view
356            animateFooterVisibility(true, 0);
357        }
358    }
359
360    /** Animates this task view as it leaves recents by pressing home. */
361    public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
362        animate()
363                .translationY(ctx.offscreenTranslationY)
364                .setStartDelay(0)
365                .setUpdateListener(null)
366                .setInterpolator(mConfig.fastOutLinearInInterpolator)
367                .setDuration(mConfig.taskViewExitToHomeDuration)
368                .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable())
369                .start();
370        ctx.postAnimationTrigger.increment();
371    }
372
373    /** Animates this task view as it exits recents */
374    public void startLaunchTaskAnimation(final Runnable r, boolean isLaunchingTask) {
375        if (isLaunchingTask) {
376            // Disable the thumbnail clip and animate the bar out
377            mBarView.startLaunchTaskAnimation(mThumbnailView.disableTaskBarClipAsRunnable(), r);
378
379            // Animate the dim
380            if (mDim > 0) {
381                ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0);
382                anim.setDuration(mConfig.taskBarExitAnimDuration);
383                anim.setInterpolator(mConfig.fastOutLinearInInterpolator);
384                anim.start();
385            }
386        } else {
387            // Hide the dismiss button
388            mBarView.startLaunchTaskDismissAnimation();
389        }
390    }
391
392    /** Animates the deletion of this task view */
393    public void startDeleteTaskAnimation(final Runnable r) {
394        // Disabling clipping with the stack while the view is animating away
395        setClipViewInStack(false);
396
397        animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx)
398            .alpha(0f)
399            .setStartDelay(0)
400            .setUpdateListener(null)
401            .setInterpolator(mConfig.fastOutSlowInInterpolator)
402            .setDuration(mConfig.taskViewRemoveAnimDuration)
403            .withEndAction(new Runnable() {
404                @Override
405                public void run() {
406                    // We just throw this into a runnable because starting a view property
407                    // animation using layers can cause inconsisten results if we try and
408                    // update the layers while the animation is running.  In some cases,
409                    // the runnabled passed in may start an animation which also uses layers
410                    // so we defer all this by posting this.
411                    r.run();
412
413                    // Re-enable clipping with the stack (we will reuse this view)
414                    setClipViewInStack(true);
415                }
416            })
417            .start();
418    }
419
420    /** Animates this task view if the user does not interact with the stack after a certain time. */
421    public void startNoUserInteractionAnimation() {
422        mBarView.startNoUserInteractionAnimation();
423    }
424
425    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
426    public void setNoUserInteractionState() {
427        mBarView.setNoUserInteractionState();
428    }
429
430    /** Sets whether this task view is full screen or not. */
431    void setIsFullScreen(boolean isFullscreen) {
432        mIsFullScreenView = isFullscreen;
433        mBarView.setIsFullscreen(isFullscreen);
434        if (isFullscreen) {
435            // If we are full screen, then disable the bottom outline clip for the footer
436            mViewBounds.setOutlineClipBottom(0);
437        }
438    }
439
440    /** Returns whether this task view should currently be drawn as a full screen view. */
441    boolean isFullScreenView() {
442        return mIsFullScreenView;
443    }
444
445    /**
446     * Returns whether this view should be clipped, or any views below should clip against this
447     * view.
448     */
449    boolean shouldClipViewInStack() {
450        return mClipViewInStack && !mIsFullScreenView && (getVisibility() == View.VISIBLE);
451    }
452
453    /** Sets whether this view should be clipped, or clipped against. */
454    void setClipViewInStack(boolean clip) {
455        if (clip != mClipViewInStack) {
456            mClipViewInStack = clip;
457            mCb.onTaskViewClipStateChanged(this);
458        }
459    }
460
461    /** Gets the max footer height. */
462    public int getMaxFooterHeight() {
463        if (mConfig.lockToAppEnabled) {
464            return mFooterView.mMaxFooterHeight;
465        } else {
466            return 0;
467        }
468    }
469
470    /** Animates the footer into and out of view. */
471    void animateFooterVisibility(boolean visible, int duration) {
472        // Hide the footer if we are a full screen view
473        if (mIsFullScreenView) return;
474        // Hide the footer if the current task can not be locked to
475        if (!mTask.lockToTaskEnabled || !mTask.lockToThisTask) return;
476        // Otherwise, animate the visibility
477        mFooterView.animateFooterVisibility(visible, duration);
478    }
479
480    /** Returns the current dim. */
481    public void setDim(int dim) {
482        mDim = dim;
483        int inverse = 255 - mDim;
484        mDimColorFilter.setColor(Color.argb(0xFF, inverse, inverse, inverse));
485        mLayerPaint.setColorFilter(mDimColorFilter);
486        setLayerType(LAYER_TYPE_HARDWARE, mLayerPaint);
487    }
488
489    /** Returns the current dim. */
490    public int getDim() {
491        return mDim;
492    }
493
494    /** Compute the dim as a function of the scale of this view. */
495    int getDimOverlayFromScale() {
496        float minScale = TaskStackViewLayoutAlgorithm.StackPeekMinScale;
497        float scaleRange = 1f - minScale;
498        float dim = (1f - getScaleX()) / scaleRange;
499        dim = mDimInterpolator.getInterpolation(Math.min(dim, 1f));
500        return Math.max(0, Math.min(mMaxDim, (int) (dim * 255)));
501    }
502
503    /** Update the dim as a function of the scale of this view. */
504    void updateDimOverlayFromScale() {
505        setDim(getDimOverlayFromScale());
506    }
507
508    /**** View focus state ****/
509
510    /**
511     * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen
512     * if the view is not currently visible, or we are in touch state (where we still want to keep
513     * track of focus).
514     */
515    public void setFocusedTask() {
516        mIsFocused = true;
517        // Workaround, we don't always want it focusable in touch mode, but we want the first task
518        // to be focused after the enter-recents animation, which can be triggered from either touch
519        // or keyboard
520        setFocusableInTouchMode(true);
521        requestFocus();
522        setFocusableInTouchMode(false);
523        invalidate();
524    }
525
526    /**
527     * Updates the explicitly focused state when the view focus changes.
528     */
529    @Override
530    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
531        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
532        if (!gainFocus) {
533            mIsFocused = false;
534            invalidate();
535        }
536    }
537
538    /**
539     * Returns whether we have explicitly been focused.
540     */
541    public boolean isFocusedTask() {
542        return mIsFocused || isFocused();
543    }
544
545    /**** TaskCallbacks Implementation ****/
546
547    /** Binds this task view to the task */
548    public void onTaskBound(Task t) {
549        mTask = t;
550        mTask.setCallbacks(this);
551        if (getMeasuredWidth() == 0) {
552            // If we haven't yet measured, we should just set the footer height with any animation
553            animateFooterVisibility(t.lockToThisTask, 0);
554        } else {
555            animateFooterVisibility(t.lockToThisTask, mConfig.taskViewLockToAppLongAnimDuration);
556        }
557    }
558
559    @Override
560    public void onTaskDataLoaded() {
561        if (mThumbnailView != null && mBarView != null) {
562            // Bind each of the views to the new task data
563            if (mIsFullScreenView) {
564                mThumbnailView.bindToScreenshot(AlternateRecentsComponent.getLastScreenshot());
565            } else {
566                mThumbnailView.rebindToTask(mTask);
567            }
568            mBarView.rebindToTask(mTask);
569            // Rebind any listeners
570            if (Constants.DebugFlags.App.EnableTaskFiltering) {
571                mBarView.mApplicationIcon.setOnClickListener(this);
572            }
573            mBarView.mDismissButton.setOnClickListener(this);
574            mFooterView.setOnClickListener(this);
575            if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
576                if (mConfig.developerOptionsEnabled) {
577                    mBarView.mApplicationIcon.setOnLongClickListener(this);
578                }
579            }
580        }
581        mTaskDataLoaded = true;
582    }
583
584    @Override
585    public void onTaskDataUnloaded() {
586        if (mThumbnailView != null && mBarView != null) {
587            // Unbind each of the views from the task data and remove the task callback
588            mTask.setCallbacks(null);
589            mThumbnailView.unbindFromTask();
590            mBarView.unbindFromTask();
591            // Unbind any listeners
592            if (Constants.DebugFlags.App.EnableTaskFiltering) {
593                mBarView.mApplicationIcon.setOnClickListener(null);
594            }
595            mBarView.mDismissButton.setOnClickListener(null);
596            mFooterView.setOnClickListener(null);
597            if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
598                mBarView.mApplicationIcon.setOnLongClickListener(null);
599            }
600        }
601        mTaskDataLoaded = false;
602    }
603
604    /** Enables/disables handling touch on this task view. */
605    void setTouchEnabled(boolean enabled) {
606        setOnClickListener(enabled ? this : null);
607    }
608
609    /**** TaskFooterView.TaskFooterViewCallbacks ****/
610
611    @Override
612    public void onTaskFooterHeightChanged(int height, int maxHeight) {
613        if (mIsFullScreenView) {
614            // Disable the bottom outline clip when fullscreen
615            mViewBounds.setOutlineClipBottom(0);
616        } else {
617            // Update the bottom clip in our outline provider
618            mViewBounds.setOutlineClipBottom(maxHeight - height);
619        }
620    }
621
622    /**** View.OnClickListener Implementation ****/
623
624    @Override
625     public void onClick(final View v) {
626        // We purposely post the handler delayed to allow for the touch feedback to draw
627        final TaskView tv = this;
628        postDelayed(new Runnable() {
629            @Override
630            public void run() {
631                if (v == mBarView.mApplicationIcon) {
632                    mCb.onTaskViewAppIconClicked(tv);
633                } else if (v == mBarView.mDismissButton) {
634                    // Animate out the view and call the callback
635                    startDeleteTaskAnimation(new Runnable() {
636                        @Override
637                        public void run() {
638                            mCb.onTaskViewDismissed(tv);
639                        }
640                    });
641                    // Hide the footer
642                    tv.animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration);
643                } else if (v == tv || v == mFooterView) {
644                    mCb.onTaskViewClicked(tv, tv.getTask(), (v == mFooterView));
645                }
646            }
647        }, 125);
648    }
649
650    /**** View.OnLongClickListener Implementation ****/
651
652    @Override
653    public boolean onLongClick(View v) {
654        if (v == mBarView.mApplicationIcon) {
655            mCb.onTaskViewAppInfoClicked(this);
656            return true;
657        }
658        return false;
659    }
660}
661