TaskStackView.java revision f1fbd77cf057e43926f9a0347692611386d09f40
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.app.Activity;
24import android.content.ComponentName;
25import android.content.Context;
26import android.graphics.Canvas;
27import android.graphics.Rect;
28import android.graphics.Region;
29import android.view.LayoutInflater;
30import android.view.MotionEvent;
31import android.view.View;
32import android.widget.FrameLayout;
33import android.widget.OverScroller;
34import com.android.systemui.R;
35import com.android.systemui.recents.Console;
36import com.android.systemui.recents.Constants;
37import com.android.systemui.recents.DozeTrigger;
38import com.android.systemui.recents.RecentsConfiguration;
39import com.android.systemui.recents.ReferenceCountedTrigger;
40import com.android.systemui.recents.Utilities;
41import com.android.systemui.recents.model.RecentsPackageMonitor;
42import com.android.systemui.recents.model.RecentsTaskLoader;
43import com.android.systemui.recents.model.Task;
44import com.android.systemui.recents.model.TaskStack;
45
46import java.util.ArrayList;
47import java.util.HashMap;
48import java.util.Set;
49
50
51/* The visual representation of a task stack view */
52public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
53        TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>,
54        View.OnClickListener, RecentsPackageMonitor.PackageCallbacks {
55
56    /** The TaskView callbacks */
57    interface TaskStackViewCallbacks {
58        public void onTaskLaunched(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
59        public void onTaskAppInfoLaunched(Task t);
60        public void onTaskRemoved(Task t);
61        public void onTaskStackFilterTriggered();
62        public void onTaskStackUnfilterTriggered();
63    }
64
65    RecentsConfiguration mConfig;
66
67    TaskStack mStack;
68    TaskStackViewTouchHandler mTouchHandler;
69    TaskStackViewCallbacks mCb;
70    ViewPool<TaskView, Task> mViewPool;
71    ArrayList<TaskViewTransform> mTaskTransforms = new ArrayList<TaskViewTransform>();
72    DozeTrigger mUIDozeTrigger;
73
74    // The various rects that define the stack view
75    Rect mRect = new Rect();
76    Rect mStackRect = new Rect();
77    Rect mStackRectSansPeek = new Rect();
78    Rect mTaskRect = new Rect();
79
80    // The virtual stack scroll that we use for the card layout
81    int mStackScroll;
82    int mMinScroll;
83    int mMaxScroll;
84    int mStashedScroll;
85    int mFocusedTaskIndex = -1;
86    OverScroller mScroller;
87    ObjectAnimator mScrollAnimator;
88
89    // Optimizations
90    ReferenceCountedTrigger mHwLayersTrigger;
91    int mStackViewsAnimationDuration;
92    boolean mStackViewsDirty = true;
93    boolean mAwaitingFirstLayout = true;
94    boolean mStartEnterAnimationRequestedAfterLayout;
95    ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
96    int[] mTmpVisibleRange = new int[2];
97    Rect mTmpRect = new Rect();
98    Rect mTmpRect2 = new Rect();
99    LayoutInflater mInflater;
100
101    Runnable mReturnAllViewsToPoolRunnable = new Runnable() {
102        @Override
103        public void run() {
104            int childCount = getChildCount();
105            for (int i = childCount - 1; i >= 0; i--) {
106                mViewPool.returnViewToPool((TaskView) getChildAt(i));
107            }
108        }
109    };
110
111    public TaskStackView(Context context, TaskStack stack) {
112        super(context);
113        mConfig = RecentsConfiguration.getInstance();
114        mStack = stack;
115        mStack.setCallbacks(this);
116        mScroller = new OverScroller(context);
117        mTouchHandler = new TaskStackViewTouchHandler(context, this);
118        mViewPool = new ViewPool<TaskView, Task>(context, this);
119        mInflater = LayoutInflater.from(context);
120        mUIDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() {
121            @Override
122            public void run() {
123                // Show the task bar dismiss buttons
124                int childCount = getChildCount();
125                for (int i = 0; i < childCount; i++) {
126                    TaskView tv = (TaskView) getChildAt(i);
127                    tv.startNoUserInteractionAnimation();
128                }
129            }
130        });
131        mHwLayersTrigger = new ReferenceCountedTrigger(getContext(), new Runnable() {
132            @Override
133            public void run() {
134                // Enable hw layers on each of the children
135                int childCount = getChildCount();
136                for (int i = 0; i < childCount; i++) {
137                    TaskView tv = (TaskView) getChildAt(i);
138                    tv.enableHwLayers();
139                }
140            }
141        }, new Runnable() {
142            @Override
143            public void run() {
144                // Disable hw layers on each of the children
145                int childCount = getChildCount();
146                for (int i = 0; i < childCount; i++) {
147                    TaskView tv = (TaskView) getChildAt(i);
148                    tv.disableHwLayers();
149                }
150            }
151        }, new Runnable() {
152            @Override
153            public void run() {
154                new Throwable("Invalid hw layers ref count").printStackTrace();
155                Console.logError(getContext(), "Invalid HW layers ref count");
156            }
157        });
158    }
159
160    /** Sets the callbacks */
161    void setCallbacks(TaskStackViewCallbacks cb) {
162        mCb = cb;
163    }
164
165    /** Requests that the views be synchronized with the model */
166    void requestSynchronizeStackViewsWithModel() {
167        requestSynchronizeStackViewsWithModel(0);
168    }
169    void requestSynchronizeStackViewsWithModel(int duration) {
170        if (Console.Enabled) {
171            Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
172                    "[TaskStackView|requestSynchronize]", "" + duration + "ms", Console.AnsiYellow);
173        }
174        if (!mStackViewsDirty) {
175            invalidate(mStackRect);
176        }
177        if (mAwaitingFirstLayout) {
178            // Skip the animation if we are awaiting first layout
179            mStackViewsAnimationDuration = 0;
180        } else {
181            mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration);
182        }
183        mStackViewsDirty = true;
184    }
185
186    /** Finds the child view given a specific task */
187    private TaskView getChildViewForTask(Task t) {
188        int childCount = getChildCount();
189        for (int i = 0; i < childCount; i++) {
190            TaskView tv = (TaskView) getChildAt(i);
191            if (tv.getTask() == t) {
192                return tv;
193            }
194        }
195        return null;
196    }
197
198    /** Update/get the transform (creates a new TaskViewTransform) */
199    public TaskViewTransform getStackTransform(int indexInStack, int stackScroll) {
200        TaskViewTransform transform = new TaskViewTransform();
201        return getStackTransform(indexInStack, stackScroll, transform);
202    }
203
204    /** Update/get the transform */
205    public TaskViewTransform getStackTransform(int indexInStack, int stackScroll,
206                                               TaskViewTransform transformOut) {
207        // Return early if we have an invalid index
208        if (indexInStack < 0) {
209            transformOut.reset();
210            return transformOut;
211        }
212
213        // Map the items to an continuous position relative to the specified scroll
214        int numPeekCards = Constants.Values.TaskStackView.StackPeekNumCards;
215        float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
216        float peekHeight = Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height();
217        float t = ((indexInStack * overlapHeight) - stackScroll) / overlapHeight;
218        float boundedT = Math.max(t, -(numPeekCards + 1));
219
220        // Set the scale relative to its position
221        int numFrontScaledCards = 3;
222        float minScale = Constants.Values.TaskStackView.StackPeekMinScale;
223        float scaleRange = 1f - minScale;
224        float scaleInc = scaleRange / (numPeekCards + numFrontScaledCards);
225        float scale = Math.max(minScale, Math.min(1f, minScale +
226            ((boundedT + (numPeekCards + 1)) * scaleInc)));
227        float scaleYOffset = ((1f - scale) * mTaskRect.height()) / 2;
228        transformOut.scale = scale;
229
230        // Set the y translation
231        if (boundedT < 0f) {
232            transformOut.translationY = (int) ((Math.max(-numPeekCards, boundedT) /
233                    numPeekCards) * peekHeight - scaleYOffset);
234        } else {
235            transformOut.translationY = (int) (boundedT * overlapHeight - scaleYOffset);
236        }
237
238        // Set the z translation
239        int minZ = mConfig.taskViewTranslationZMinPx;
240        int incZ = mConfig.taskViewTranslationZIncrementPx;
241        transformOut.translationZ = (int) Math.max(minZ, minZ + ((boundedT + numPeekCards) * incZ));
242
243        // Set the alphas
244        transformOut.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f;
245
246        // Update the rect and visibility
247        transformOut.rect.set(mTaskRect);
248        if (t < -(numPeekCards + 1)) {
249            transformOut.visible = false;
250        } else {
251            transformOut.rect.offset(0, transformOut.translationY);
252            Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
253            transformOut.visible = Rect.intersects(mRect, transformOut.rect);
254        }
255        transformOut.t = t;
256        return transformOut;
257    }
258
259    /**
260     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
261     */
262    private void updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
263                                       ArrayList<Task> tasks,
264                                       int stackScroll,
265                                       int[] visibleRangeOut,
266                                       boolean boundTranslationsToRect) {
267        // XXX: Optimization: Use binary search to find the visible range
268
269        int taskTransformCount = taskTransforms.size();
270        int taskCount = tasks.size();
271        int firstVisibleIndex = -1;
272        int lastVisibleIndex = -1;
273
274        // We can reuse the task transforms where possible to reduce object allocation
275        if (taskTransformCount < taskCount) {
276            // If there are less transforms than tasks, then add as many transforms as necessary
277            for (int i = taskTransformCount; i < taskCount; i++) {
278                taskTransforms.add(new TaskViewTransform());
279            }
280        } else if (taskTransformCount > taskCount) {
281            // If there are more transforms than tasks, then just subset the transform list
282            taskTransforms.subList(0, taskCount);
283        }
284
285        // Update the stack transforms
286        for (int i = 0; i < taskCount; i++) {
287            TaskViewTransform transform = getStackTransform(i, stackScroll, taskTransforms.get(i));
288            if (transform.visible) {
289                if (firstVisibleIndex < 0) {
290                    firstVisibleIndex = i;
291                }
292                lastVisibleIndex = i;
293            }
294
295            if (boundTranslationsToRect) {
296                transform.translationY = Math.min(transform.translationY, mRect.bottom);
297            }
298        }
299        if (visibleRangeOut != null) {
300            visibleRangeOut[0] = firstVisibleIndex;
301            visibleRangeOut[1] = lastVisibleIndex;
302        }
303    }
304
305    /**
306     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. This
307     * call is less optimal than calling updateStackTransforms directly.
308     */
309    private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks,
310                                                            int stackScroll,
311                                                            int[] visibleRangeOut,
312                                                            boolean boundTranslationsToRect) {
313        ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>();
314        updateStackTransforms(taskTransforms, tasks, stackScroll, visibleRangeOut,
315                boundTranslationsToRect);
316        return taskTransforms;
317    }
318
319    /** Synchronizes the views with the model */
320    void synchronizeStackViewsWithModel() {
321        if (Console.Enabled) {
322            Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
323                    "[TaskStackView|synchronizeViewsWithModel]",
324                    "mStackViewsDirty: " + mStackViewsDirty, Console.AnsiYellow);
325        }
326        if (mStackViewsDirty) {
327            // XXX: Consider using TaskViewTransform pool to prevent allocations
328            // XXX: Iterate children views, update transforms and remove all that are not visible
329            //      For all remaining tasks, update transforms and if visible add the view
330
331            // Get all the task transforms
332            int[] visibleRange = mTmpVisibleRange;
333            int stackScroll = getStackScroll();
334            ArrayList<Task> tasks = mStack.getTasks();
335            updateStackTransforms(mTaskTransforms, tasks, stackScroll, visibleRange, false);
336
337            // Update the visible state of all the tasks
338            int taskCount = tasks.size();
339            for (int i = 0; i < taskCount; i++) {
340                Task task = tasks.get(i);
341                TaskViewTransform transform = mTaskTransforms.get(i);
342                TaskView tv = getChildViewForTask(task);
343
344                if (transform.visible) {
345                    if (tv == null) {
346                        tv = mViewPool.pickUpViewFromPool(task, task);
347                        // When we are picking up a new view from the view pool, prepare it for any
348                        // following animation by putting it in a reasonable place
349                        if (mStackViewsAnimationDuration > 0 && i != 0) {
350                            int fromIndex = (transform.t < 0) ? (visibleRange[0] - 1) :
351                                    (visibleRange[1] + 1);
352                            tv.updateViewPropertiesToTaskTransform(
353                                    getStackTransform(fromIndex, stackScroll), 0);
354                        }
355                    }
356                } else {
357                    if (tv != null) {
358                        mViewPool.returnViewToPool(tv);
359                    }
360                }
361            }
362
363            // Update all the remaining view children
364            // NOTE: We have to iterate in reverse where because we are removing views directly
365            int childCount = getChildCount();
366            for (int i = childCount - 1; i >= 0; i--) {
367                TaskView tv = (TaskView) getChildAt(i);
368                Task task = tv.getTask();
369                int taskIndex = mStack.indexOfTask(task);
370                if (taskIndex < 0 || !mTaskTransforms.get(taskIndex).visible) {
371                    mViewPool.returnViewToPool(tv);
372                } else {
373                    tv.updateViewPropertiesToTaskTransform(mTaskTransforms.get(taskIndex),
374                            mStackViewsAnimationDuration);
375                }
376            }
377
378            if (Console.Enabled) {
379                Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
380                        "  [TaskStackView|viewChildren]", "" + getChildCount());
381            }
382
383            mStackViewsAnimationDuration = 0;
384            mStackViewsDirty = false;
385        }
386    }
387
388    /** Sets the current stack scroll */
389    public void setStackScroll(int value) {
390        mStackScroll = value;
391        mUIDozeTrigger.poke();
392        requestSynchronizeStackViewsWithModel();
393    }
394    /** Sets the current stack scroll without synchronizing the stack view with the model */
395    public void setStackScrollRaw(int value) {
396        mStackScroll = value;
397        mUIDozeTrigger.poke();
398    }
399    /** Sets the current stack scroll to the initial state when you first enter recents */
400    public void setStackScrollToInitialState() {
401        if (mStack.getTaskCount() > 2) {
402            int initialScroll = mMaxScroll - mTaskRect.height() / 2;
403            setStackScroll(initialScroll);
404        } else {
405            setStackScroll(mMaxScroll);
406        }
407    }
408
409    /**
410     * Returns the scroll to such that the task transform at that index will have t=0. (If the scroll
411     * is not bounded)
412     */
413    int getStackScrollForTaskIndex(int i) {
414        int taskHeight = mTaskRect.height();
415        return (int) (i * Constants.Values.TaskStackView.StackOverlapPct * taskHeight);
416    }
417
418    /** Gets the current stack scroll */
419    public int getStackScroll() {
420        return mStackScroll;
421    }
422
423    /** Animates the stack scroll into bounds */
424    ObjectAnimator animateBoundScroll() {
425        int curScroll = getStackScroll();
426        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
427        if (newScroll != curScroll) {
428            // Enable hw layers on the stack
429            addHwLayersRefCount("animateBoundScroll");
430
431            // Start a new scroll animation
432            animateScroll(curScroll, newScroll, new Runnable() {
433                @Override
434                public void run() {
435                    // Disable hw layers on the stack
436                    decHwLayersRefCount("animateBoundScroll");
437                }
438            });
439        }
440        return mScrollAnimator;
441    }
442
443    /** Animates the stack scroll */
444    void animateScroll(int curScroll, int newScroll, final Runnable postRunnable) {
445        // Abort any current animations
446        abortScroller();
447        abortBoundScrollAnimation();
448
449        mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll);
450        mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll -
451                curScroll, 250));
452        mScrollAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
453        mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
454            @Override
455            public void onAnimationUpdate(ValueAnimator animation) {
456                setStackScroll((Integer) animation.getAnimatedValue());
457            }
458        });
459        mScrollAnimator.addListener(new AnimatorListenerAdapter() {
460            @Override
461            public void onAnimationEnd(Animator animation) {
462                if (postRunnable != null) {
463                    postRunnable.run();
464                }
465                mScrollAnimator.removeAllListeners();
466            }
467        });
468        mScrollAnimator.start();
469    }
470
471    /** Aborts any current stack scrolls */
472    void abortBoundScrollAnimation() {
473        if (mScrollAnimator != null) {
474            mScrollAnimator.cancel();
475        }
476    }
477
478    /** Aborts the scroller and any current fling */
479    void abortScroller() {
480        if (!mScroller.isFinished()) {
481            // Abort the scroller
482            mScroller.abortAnimation();
483            // And disable hw layers on the stack
484            decHwLayersRefCount("flingScroll");
485        }
486    }
487
488    /** Bounds the current scroll if necessary */
489    public boolean boundScroll() {
490        int curScroll = getStackScroll();
491        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
492        if (newScroll != curScroll) {
493            setStackScroll(newScroll);
494            return true;
495        }
496        return false;
497    }
498
499    /**
500     * Bounds the current scroll if necessary, but does not synchronize the stack view with the
501     * model.
502     */
503    public boolean boundScrollRaw() {
504        int curScroll = getStackScroll();
505        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
506        if (newScroll != curScroll) {
507            setStackScrollRaw(newScroll);
508            return true;
509        }
510        return false;
511    }
512
513
514    /** Returns the amount that the scroll is out of bounds */
515    int getScrollAmountOutOfBounds(int scroll) {
516        if (scroll < mMinScroll) {
517            return mMinScroll - scroll;
518        } else if (scroll > mMaxScroll) {
519            return scroll - mMaxScroll;
520        }
521        return 0;
522    }
523
524    /** Returns whether the specified scroll is out of bounds */
525    boolean isScrollOutOfBounds() {
526        return getScrollAmountOutOfBounds(getStackScroll()) != 0;
527    }
528
529    /** Returns whether the task view is in the stack bounds or not */
530    boolean isTaskInStackBounds(TaskView tv) {
531        Rect r = new Rect();
532        tv.getHitRect(r);
533        return r.bottom <= mRect.bottom;
534    }
535
536    /** Updates the min and max virtual scroll bounds */
537    void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
538        // Compute the min and max scroll values
539        int numTasks = Math.max(1, mStack.getTaskCount());
540        int taskHeight = mTaskRect.height();
541        int stackHeight = mStackRectSansPeek.height();
542        int maxScrollHeight = taskHeight + (int) ((numTasks - 1) *
543                Constants.Values.TaskStackView.StackOverlapPct * taskHeight);
544
545        if (numTasks <= 1) {
546            // If there is only one task, then center the task in the stack rect (sans peek)
547            mMinScroll = mMaxScroll = -(stackHeight - taskHeight) / 2;
548        } else {
549            mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight;
550            mMaxScroll = maxScrollHeight - stackHeight;
551        }
552
553        // Debug logging
554        if (Constants.Log.UI.MeasureAndLayout) {
555            Console.log("  [TaskStack|minScroll] " + mMinScroll);
556            Console.log("  [TaskStack|maxScroll] " + mMaxScroll);
557        }
558
559        if (boundScrollToNewMinMax) {
560            boundScroll();
561        }
562    }
563
564    /** Animates a task view in this stack as it launches. */
565    public void animateOnLaunchingTask(TaskView tv, final Runnable r) {
566        // Hide each of the task bar dismiss buttons
567        int childCount = getChildCount();
568        for (int i = 0; i < childCount; i++) {
569            TaskView t = (TaskView) getChildAt(i);
570            if (t == tv) {
571                t.startLaunchTaskAnimation(r, true);
572            } else {
573                t.startLaunchTaskAnimation(null, false);
574            }
575        }
576    }
577
578    /** Focuses the task at the specified index in the stack */
579    void focusTask(int taskIndex, boolean scrollToNewPosition) {
580        if (Console.Enabled) {
581            Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]", "" + taskIndex);
582        }
583        if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
584            mFocusedTaskIndex = taskIndex;
585
586            // Focus the view if possible, otherwise, focus the view after we scroll into position
587            Task t = mStack.getTasks().get(taskIndex);
588            TaskView tv = getChildViewForTask(t);
589            Runnable postScrollRunnable = null;
590            if (tv != null) {
591                tv.setFocusedTask();
592                if (Console.Enabled) {
593                    Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]", "Requesting focus");
594                }
595            } else {
596                postScrollRunnable = new Runnable() {
597                    @Override
598                    public void run() {
599                        Task t = mStack.getTasks().get(mFocusedTaskIndex);
600                        TaskView tv = getChildViewForTask(t);
601                        if (tv != null) {
602                            tv.setFocusedTask();
603                            if (Console.Enabled) {
604                                Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]",
605                                        "Requesting focus after scroll animation");
606                            }
607                        }
608                    }
609                };
610            }
611
612            if (scrollToNewPosition) {
613                // Scroll the view into position
614                int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll,
615                        getStackScrollForTaskIndex(taskIndex)));
616
617                animateScroll(getStackScroll(), newScroll, postScrollRunnable);
618            } else {
619                if (postScrollRunnable != null) {
620                    postScrollRunnable.run();
621                }
622            }
623        }
624    }
625
626    /** Focuses the next task in the stack */
627    void focusNextTask(boolean forward) {
628        if (Console.Enabled) {
629            Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusNextTask]", "" +
630                    mFocusedTaskIndex);
631        }
632
633        // Find the next index to focus
634        int numTasks = mStack.getTaskCount();
635        if (mFocusedTaskIndex < 0) {
636            mFocusedTaskIndex = numTasks - 1;
637        }
638        if (0 <= mFocusedTaskIndex && mFocusedTaskIndex < numTasks) {
639            mFocusedTaskIndex = Math.max(0, Math.min(numTasks - 1,
640                    mFocusedTaskIndex + (forward ? -1 : 1)));
641        }
642        focusTask(mFocusedTaskIndex, true);
643    }
644
645    /** Enables the hw layers and increments the hw layer requirement ref count */
646    void addHwLayersRefCount(String reason) {
647        if (Console.Enabled) {
648            int refCount = mHwLayersTrigger.getCount();
649            Console.log(Constants.Log.UI.HwLayers,
650                    "[TaskStackView|addHwLayersRefCount] refCount: " +
651                            refCount + "->" + (refCount + 1) + " " + reason);
652        }
653        mHwLayersTrigger.increment();
654    }
655
656    /** Decrements the hw layer requirement ref count and disables the hw layers when we don't
657        need them anymore. */
658    void decHwLayersRefCount(String reason) {
659        if (Console.Enabled) {
660            int refCount = mHwLayersTrigger.getCount();
661            Console.log(Constants.Log.UI.HwLayers,
662                    "[TaskStackView|decHwLayersRefCount] refCount: " +
663                            refCount + "->" + (refCount - 1) + " " + reason);
664        }
665        mHwLayersTrigger.decrement();
666    }
667
668    @Override
669    public void computeScroll() {
670        if (mScroller.computeScrollOffset()) {
671            setStackScroll(mScroller.getCurrY());
672            invalidate(mStackRect);
673
674            // If we just finished scrolling, then disable the hw layers
675            if (mScroller.isFinished()) {
676                decHwLayersRefCount("finishedFlingScroll");
677            }
678        }
679    }
680
681    @Override
682    public boolean onInterceptTouchEvent(MotionEvent ev) {
683        return mTouchHandler.onInterceptTouchEvent(ev);
684    }
685
686    @Override
687    public boolean onTouchEvent(MotionEvent ev) {
688        return mTouchHandler.onTouchEvent(ev);
689    }
690
691    @Override
692    public void dispatchDraw(Canvas canvas) {
693        if (Console.Enabled) {
694            Console.log(Constants.Log.UI.Draw, "[TaskStackView|dispatchDraw]", "",
695                    Console.AnsiPurple);
696        }
697        synchronizeStackViewsWithModel();
698        super.dispatchDraw(canvas);
699    }
700
701    @Override
702    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
703        if (Constants.DebugFlags.App.EnableTaskStackClipping) {
704            TaskView tv = (TaskView) child;
705            TaskView nextTv = null;
706            TaskView tmpTv = null;
707            if (tv.shouldClipViewInStack()) {
708                int curIndex = indexOfChild(tv);
709
710                // Find the next view to clip against
711                while (nextTv == null && curIndex < getChildCount()) {
712                    tmpTv = (TaskView) getChildAt(++curIndex);
713                    if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
714                        nextTv = tmpTv;
715                    }
716                }
717
718                // Clip against the next view (if we aren't animating its alpha)
719                if (nextTv != null) {
720                    Rect curRect = tv.getClippingRect(mTmpRect);
721                    Rect nextRect = nextTv.getClippingRect(mTmpRect2);
722                    // The hit rects are relative to the task view, which needs to be offset by
723                    // the system bar height
724                    curRect.offset(0, mConfig.systemInsets.top);
725                    nextRect.offset(0, mConfig.systemInsets.top);
726                    // Compute the clip region
727                    Region clipRegion = new Region();
728                    clipRegion.op(curRect, Region.Op.UNION);
729                    clipRegion.op(nextRect, Region.Op.DIFFERENCE);
730                    // Clip the canvas
731                    int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
732                    canvas.clipRegion(clipRegion);
733                    boolean invalidate = super.drawChild(canvas, child, drawingTime);
734                    canvas.restoreToCount(saveCount);
735                    return invalidate;
736                }
737            }
738        }
739        return super.drawChild(canvas, child, drawingTime);
740    }
741
742    /** Computes the stack and task rects */
743    public void computeRects(int width, int height, int insetLeft, int insetBottom) {
744        // Note: We let the stack view be the full height because we want the cards to go under the
745        //       navigation bar if possible.  However, the stack rects which we use to calculate
746        //       max scroll, etc. need to take the nav bar into account
747
748        // Compute the stack rects
749        mRect.set(0, 0, width, height);
750        mStackRect.set(mRect);
751        mStackRect.left += insetLeft;
752        mStackRect.bottom -= insetBottom;
753
754        int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width());
755        int heightPadding = mConfig.taskStackTopPaddingPx;
756        if (Constants.DebugFlags.App.EnableSearchLayout) {
757            mStackRect.top += heightPadding;
758            mStackRect.left += widthPadding;
759            mStackRect.right -= widthPadding;
760            mStackRect.bottom -= heightPadding;
761        } else {
762            mStackRect.inset(widthPadding, heightPadding);
763        }
764        mStackRectSansPeek.set(mStackRect);
765        mStackRectSansPeek.top += Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height();
766
767        // Compute the task rect
768        int size = mStackRect.width();
769        int left = mStackRect.left + (mStackRect.width() - size) / 2;
770        mTaskRect.set(left, mStackRectSansPeek.top,
771                left + size, mStackRectSansPeek.top + size);
772
773        // Update the scroll bounds
774        updateMinMaxScroll(false);
775    }
776
777    /**
778     * This is called with the size of the space not including the top or right insets, or the
779     * search bar height in portrait (but including the search bar width in landscape, since we want
780     * to draw under it.
781     */
782    @Override
783    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
784        int width = MeasureSpec.getSize(widthMeasureSpec);
785        int height = MeasureSpec.getSize(heightMeasureSpec);
786        if (Console.Enabled) {
787            Console.log(Constants.Log.UI.MeasureAndLayout, "[TaskStackView|measure]",
788                    "width: " + width + " height: " + height +
789                            " awaitingFirstLayout: " + mAwaitingFirstLayout, Console.AnsiGreen);
790        }
791
792        // Compute our stack/task rects
793        Rect taskStackBounds = new Rect();
794        mConfig.getTaskStackBounds(width, height, taskStackBounds);
795        computeRects(width, height, taskStackBounds.left, mConfig.systemInsets.bottom);
796
797        // Debug logging
798        if (Constants.Log.UI.MeasureAndLayout) {
799            Console.log("  [TaskStack|fullRect] " + mRect);
800            Console.log("  [TaskStack|stackRect] " + mStackRect);
801            Console.log("  [TaskStack|stackRectSansPeek] " + mStackRectSansPeek);
802            Console.log("  [TaskStack|taskRect] " + mTaskRect);
803        }
804
805        // If this is the first layout, then scroll to the front of the stack and synchronize the
806        // stack views immediately
807        if (mAwaitingFirstLayout) {
808            setStackScrollToInitialState();
809            requestSynchronizeStackViewsWithModel();
810            synchronizeStackViewsWithModel();
811        }
812
813        // Measure each of the children
814        int childCount = getChildCount();
815        for (int i = 0; i < childCount; i++) {
816            TaskView t = (TaskView) getChildAt(i);
817            t.measure(MeasureSpec.makeMeasureSpec(mTaskRect.width(), MeasureSpec.EXACTLY),
818                    MeasureSpec.makeMeasureSpec(mTaskRect.height(), MeasureSpec.EXACTLY));
819        }
820
821        setMeasuredDimension(width, height);
822    }
823
824    /**
825     * This is called with the size of the space not including the top or right insets, or the
826     * search bar height in portrait (but including the search bar width in landscape, since we want
827     * to draw under it.
828     */
829    @Override
830    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
831        if (Console.Enabled) {
832            Console.log(Constants.Log.UI.MeasureAndLayout, "[TaskStackView|layout]",
833                    "" + new Rect(left, top, right, bottom), Console.AnsiGreen);
834        }
835
836        // Debug logging
837        if (Constants.Log.UI.MeasureAndLayout) {
838            Console.log("  [TaskStack|fullRect] " + mRect);
839            Console.log("  [TaskStack|stackRect] " + mStackRect);
840            Console.log("  [TaskStack|stackRectSansPeek] " + mStackRectSansPeek);
841            Console.log("  [TaskStack|taskRect] " + mTaskRect);
842        }
843
844        // Layout each of the children
845        int childCount = getChildCount();
846        for (int i = 0; i < childCount; i++) {
847            TaskView t = (TaskView) getChildAt(i);
848            t.layout(mTaskRect.left, mStackRectSansPeek.top,
849                    mTaskRect.right, mStackRectSansPeek.top + mTaskRect.height());
850        }
851
852        if (mAwaitingFirstLayout) {
853            // Mark that we have completely the first layout
854            mAwaitingFirstLayout = false;
855
856            // Start dozing
857            mUIDozeTrigger.startDozing();
858
859            // Prepare the first view for its enter animation
860            int offsetTopAlign = -mTaskRect.top;
861            int offscreenY = mRect.bottom - (mTaskRect.top - mRect.top);
862            for (int i = childCount - 1; i >= 0; i--) {
863                TaskView tv = (TaskView) getChildAt(i);
864                tv.prepareEnterRecentsAnimation((i == (getChildCount() - 1)), offsetTopAlign,
865                        offscreenY, mTaskRect);
866            }
867
868            // If the enter animation started already and we haven't completed a layout yet, do the
869            // enter animation now
870            if (mStartEnterAnimationRequestedAfterLayout) {
871                startEnterRecentsAnimation(mStartEnterAnimationContext);
872                mStartEnterAnimationRequestedAfterLayout = false;
873                mStartEnterAnimationContext = null;
874            }
875
876            // Update the focused task index to be the next item to the top task
877            if (mConfig.launchedWithAltTab) {
878                focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
879            }
880        }
881    }
882
883    /** Requests this task stacks to start it's enter-recents animation */
884    public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) {
885        // If we are still waiting to layout, then just defer until then
886        if (mAwaitingFirstLayout) {
887            mStartEnterAnimationRequestedAfterLayout = true;
888            mStartEnterAnimationContext = ctx;
889            return;
890        }
891
892        // Animate all the task views into view
893        ctx.taskRect = mTaskRect;
894        ctx.stackRectSansPeek = mStackRectSansPeek;
895        int childCount = getChildCount();
896        for (int i = childCount - 1; i >= 0; i--) {
897            TaskView tv = (TaskView) getChildAt(i);
898            TaskViewTransform transform = getStackTransform(mStack.indexOfTask(tv.getTask()),
899                    getStackScroll());
900            ctx.stackViewIndex = i;
901            ctx.stackViewCount = childCount;
902            ctx.isFrontMost = (i == (getChildCount() - 1));
903            ctx.transform = transform;
904            tv.startEnterRecentsAnimation(ctx);
905        }
906    }
907
908    /** Requests this task stacks to start it's exit-recents animation. */
909    public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
910        // Animate all the task views into view
911        ctx.offscreenTranslationY = mRect.bottom - (mTaskRect.top - mRect.top);
912        int childCount = getChildCount();
913        for (int i = 0; i < childCount; i++) {
914            TaskView tv = (TaskView) getChildAt(i);
915            tv.startExitToHomeAnimation(ctx);
916        }
917
918        // Add a runnable to the post animation ref counter to clear all the views
919        ctx.postAnimationTrigger.addLastDecrementRunnable(mReturnAllViewsToPoolRunnable);
920    }
921
922    @Override
923    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
924        super.onScrollChanged(l, t, oldl, oldt);
925        requestSynchronizeStackViewsWithModel();
926    }
927
928    public boolean isTransformedTouchPointInView(float x, float y, View child) {
929        return isTransformedTouchPointInView(x, y, child, null);
930    }
931
932    /** Pokes the dozer on user interaction. */
933    void onUserInteraction() {
934        // Poke the doze trigger if it is dozing
935        mUIDozeTrigger.poke();
936    }
937
938    /**** TaskStackCallbacks Implementation ****/
939
940    @Override
941    public void onStackTaskAdded(TaskStack stack, Task t) {
942        requestSynchronizeStackViewsWithModel();
943    }
944
945    @Override
946    public void onStackTaskRemoved(TaskStack stack, Task t) {
947        // Remove the view associated with this task, we can't rely on updateTransforms
948        // to work here because the task is no longer in the list
949        TaskView tv = getChildViewForTask(t);
950        if (tv != null) {
951            mViewPool.returnViewToPool(tv);
952        }
953
954        // Notify the callback that we've removed the task and it can clean up after it
955        mCb.onTaskRemoved(t);
956
957        // Update the min/max scroll and animate other task views into their new positions
958        updateMinMaxScroll(true);
959        int movement = (int) (Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height());
960        requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement));
961
962        // If there are no remaining tasks, then either unfilter the current stack, or just close
963        // the activity if there are no filtered stacks
964        if (mStack.getTaskCount() == 0) {
965            boolean shouldFinishActivity = true;
966            if (mStack.hasFilteredTasks()) {
967                mStack.unfilterTasks();
968                shouldFinishActivity = (mStack.getTaskCount() == 0);
969            }
970            if (shouldFinishActivity) {
971                Activity activity = (Activity) getContext();
972                activity.finish();
973            }
974        }
975    }
976
977    /**
978     * Creates the animations for all the children views that need to be removed or to move views
979     * to their un/filtered position when we are un/filtering a stack, and returns the duration
980     * for these animations.
981     */
982    int getExitTransformsForFilterAnimation(ArrayList<Task> curTasks,
983                        ArrayList<TaskViewTransform> curTaskTransforms,
984                        ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms,
985                        HashMap<TaskView, TaskViewTransform> childViewTransformsOut,
986                        ArrayList<TaskView> childrenToRemoveOut) {
987        // Animate all of the existing views out of view (if they are not in the visible range in
988        // the new stack) or to their final positions in the new stack
989        int offset = 0;
990        int movement = 0;
991        int childCount = getChildCount();
992        for (int i = 0; i < childCount; i++) {
993            TaskView tv = (TaskView) getChildAt(i);
994            Task task = tv.getTask();
995            int taskIndex = tasks.indexOf(task);
996            TaskViewTransform toTransform;
997
998            // If the view is no longer visible, then we should just animate it out
999            boolean willBeInvisible = taskIndex < 0 || !taskTransforms.get(taskIndex).visible;
1000            if (willBeInvisible) {
1001                if (taskIndex < 0) {
1002                    toTransform = curTaskTransforms.get(curTasks.indexOf(task));
1003                } else {
1004                    toTransform = new TaskViewTransform(taskTransforms.get(taskIndex));
1005                }
1006                tv.prepareTaskTransformForFilterTaskVisible(toTransform);
1007                childrenToRemoveOut.add(tv);
1008            } else {
1009                toTransform = taskTransforms.get(taskIndex);
1010                // Use the movement of the visible views to calculate the duration of the animation
1011                movement = Math.max(movement, Math.abs(toTransform.translationY -
1012                        (int) tv.getTranslationY()));
1013            }
1014
1015            toTransform.startDelay = offset * Constants.Values.TaskStackView.FilterStartDelay;
1016            childViewTransformsOut.put(tv, toTransform);
1017            offset++;
1018        }
1019        return mConfig.filteringCurrentViewsAnimDuration;
1020    }
1021
1022    /**
1023     * Creates the animations for all the children views that need to be animated in when we are
1024     * un/filtering a stack, and returns the duration for these animations.
1025     */
1026    int getEnterTransformsForFilterAnimation(ArrayList<Task> tasks,
1027                         ArrayList<TaskViewTransform> taskTransforms,
1028                         HashMap<TaskView, TaskViewTransform> childViewTransformsOut) {
1029        int offset = 0;
1030        int movement = 0;
1031        int taskCount = tasks.size();
1032        for (int i = taskCount - 1; i >= 0; i--) {
1033            Task task = tasks.get(i);
1034            TaskViewTransform toTransform = taskTransforms.get(i);
1035            if (toTransform.visible) {
1036                TaskView tv = getChildViewForTask(task);
1037                if (tv == null) {
1038                    // For views that are not already visible, animate them in
1039                    tv = mViewPool.pickUpViewFromPool(task, task);
1040
1041                    // Compose a new transform to fade and slide the new task in
1042                    TaskViewTransform fromTransform = new TaskViewTransform(toTransform);
1043                    tv.prepareTaskTransformForFilterTaskHidden(fromTransform);
1044                    tv.updateViewPropertiesToTaskTransform(fromTransform, 0);
1045
1046                    toTransform.startDelay = offset * Constants.Values.TaskStackView.FilterStartDelay;
1047                    childViewTransformsOut.put(tv, toTransform);
1048
1049                    // Use the movement of the new views to calculate the duration of the animation
1050                    movement = Math.max(movement,
1051                            Math.abs(toTransform.translationY - fromTransform.translationY));
1052                    offset++;
1053                }
1054            }
1055        }
1056        return mConfig.filteringNewViewsAnimDuration;
1057    }
1058
1059    /** Orchestrates the animations of the current child views and any new views. */
1060    void doFilteringAnimation(ArrayList<Task> curTasks,
1061                              ArrayList<TaskViewTransform> curTaskTransforms,
1062                              final ArrayList<Task> tasks,
1063                              final ArrayList<TaskViewTransform> taskTransforms) {
1064        // Calculate the transforms to animate out all the existing views if they are not in the
1065        // new visible range (or to their final positions in the stack if they are)
1066        final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>();
1067        final HashMap<TaskView, TaskViewTransform> childViewTransforms =
1068                new HashMap<TaskView, TaskViewTransform>();
1069        int duration = getExitTransformsForFilterAnimation(curTasks, curTaskTransforms, tasks,
1070                taskTransforms, childViewTransforms, childrenToRemove);
1071
1072        // If all the current views are in the visible range of the new stack, then don't wait for
1073        // views to animate out and animate all the new views into their place
1074        final boolean unifyNewViewAnimation = childrenToRemove.isEmpty();
1075        if (unifyNewViewAnimation) {
1076            int inDuration = getEnterTransformsForFilterAnimation(tasks, taskTransforms,
1077                    childViewTransforms);
1078            duration = Math.max(duration, inDuration);
1079        }
1080
1081        // Animate all the views to their final transforms
1082        for (final TaskView tv : childViewTransforms.keySet()) {
1083            TaskViewTransform t = childViewTransforms.get(tv);
1084            tv.animate().cancel();
1085            tv.animate()
1086                    .withEndAction(new Runnable() {
1087                        @Override
1088                        public void run() {
1089                            childViewTransforms.remove(tv);
1090                            if (childViewTransforms.isEmpty()) {
1091                                // Return all the removed children to the view pool
1092                                for (TaskView tv : childrenToRemove) {
1093                                    mViewPool.returnViewToPool(tv);
1094                                }
1095
1096                                if (!unifyNewViewAnimation) {
1097                                    // For views that are not already visible, animate them in
1098                                    childViewTransforms.clear();
1099                                    int duration = getEnterTransformsForFilterAnimation(tasks,
1100                                            taskTransforms, childViewTransforms);
1101                                    for (final TaskView tv : childViewTransforms.keySet()) {
1102                                        TaskViewTransform t = childViewTransforms.get(tv);
1103                                        tv.updateViewPropertiesToTaskTransform(t, duration);
1104                                    }
1105                                }
1106                            }
1107                        }
1108                    });
1109            tv.updateViewPropertiesToTaskTransform(t, duration);
1110        }
1111    }
1112
1113    @Override
1114    public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
1115                                Task filteredTask) {
1116        // Stash the scroll and filtered task for us to restore to when we unfilter
1117        mStashedScroll = getStackScroll();
1118
1119        // Calculate the current task transforms
1120        ArrayList<TaskViewTransform> curTaskTransforms =
1121                getStackTransforms(curTasks, getStackScroll(), null, true);
1122
1123        // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
1124        updateMinMaxScroll(false);
1125        float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
1126        setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
1127        boundScrollRaw();
1128
1129        // Compute the transforms of the items in the new stack after setting the new scroll
1130        final ArrayList<Task> tasks = mStack.getTasks();
1131        final ArrayList<TaskViewTransform> taskTransforms =
1132                getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);
1133
1134        // Animate
1135        doFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
1136
1137        // Notify any callbacks
1138        mCb.onTaskStackFilterTriggered();
1139    }
1140
1141    @Override
1142    public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) {
1143        // Calculate the current task transforms
1144        final ArrayList<TaskViewTransform> curTaskTransforms =
1145                getStackTransforms(curTasks, getStackScroll(), null, true);
1146
1147        // Restore the stashed scroll
1148        updateMinMaxScroll(false);
1149        setStackScrollRaw(mStashedScroll);
1150        boundScrollRaw();
1151
1152        // Compute the transforms of the items in the new stack after restoring the stashed scroll
1153        final ArrayList<Task> tasks = mStack.getTasks();
1154        final ArrayList<TaskViewTransform> taskTransforms =
1155                getStackTransforms(tasks, getStackScroll(), null, true);
1156
1157        // Animate
1158        doFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
1159
1160        // Clear the saved vars
1161        mStashedScroll = 0;
1162
1163        // Notify any callbacks
1164        mCb.onTaskStackUnfilterTriggered();
1165    }
1166
1167    /**** ViewPoolConsumer Implementation ****/
1168
1169    @Override
1170    public TaskView createView(Context context) {
1171        if (Console.Enabled) {
1172            Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|createPoolView]");
1173        }
1174        return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
1175    }
1176
1177    @Override
1178    public void prepareViewToEnterPool(TaskView tv) {
1179        Task task = tv.getTask();
1180        tv.resetViewProperties();
1181        if (Console.Enabled) {
1182            Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]",
1183                    tv.getTask() + " tv: " + tv);
1184        }
1185
1186        // Report that this tasks's data is no longer being used
1187        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
1188        loader.unloadTaskData(task);
1189
1190        // Detach the view from the hierarchy
1191        detachViewFromParent(tv);
1192
1193        // Disable hw layers on this view
1194        tv.disableHwLayers();
1195    }
1196
1197    @Override
1198    public void prepareViewToLeavePool(TaskView tv, Task prepareData, boolean isNewView) {
1199        if (Console.Enabled) {
1200            Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]",
1201                    "isNewView: " + isNewView);
1202        }
1203
1204        // Setup and attach the view to the window
1205        Task task = prepareData;
1206        // We try and rebind the task (this MUST be done before the task filled)
1207        tv.onTaskBound(task);
1208        // Request that this tasks's data be filled
1209        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
1210        loader.loadTaskData(task);
1211        // Find the index where this task should be placed in the children
1212        int insertIndex = -1;
1213        int childCount = getChildCount();
1214        for (int i = 0; i < childCount; i++) {
1215            Task tvTask = ((TaskView) getChildAt(i)).getTask();
1216            if (mStack.containsTask(task) && (mStack.indexOfTask(task) < mStack.indexOfTask(tvTask))) {
1217                insertIndex = i;
1218                break;
1219            }
1220        }
1221
1222        // Sanity check, the task view should always be clipping against the stack at this point,
1223        // but just in case, re-enable it here
1224        tv.setClipViewInStack(true);
1225
1226        // If the doze trigger has already fired, then update the state for this task view
1227        if (mUIDozeTrigger.hasTriggered()) {
1228            tv.setNoUserInteractionState();
1229        }
1230
1231        // Add/attach the view to the hierarchy
1232        if (Console.Enabled) {
1233            Console.log(Constants.Log.ViewPool.PoolCallbacks, "  [TaskStackView|insertIndex]",
1234                    "" + insertIndex);
1235        }
1236        if (isNewView) {
1237            addView(tv, insertIndex);
1238
1239            // Set the callbacks and listeners for this new view
1240            tv.setOnClickListener(this);
1241            tv.setCallbacks(this);
1242        } else {
1243            attachViewToParent(tv, insertIndex, tv.getLayoutParams());
1244        }
1245
1246        // Enable hw layers on this view if hw layers are enabled on the stack
1247        if (mHwLayersTrigger.getCount() > 0) {
1248            tv.enableHwLayers();
1249        }
1250    }
1251
1252    @Override
1253    public boolean hasPreferredData(TaskView tv, Task preferredData) {
1254        return (tv.getTask() == preferredData);
1255    }
1256
1257    /**** TaskViewCallbacks Implementation ****/
1258
1259    @Override
1260    public void onTaskIconClicked(TaskView tv) {
1261        if (Console.Enabled) {
1262            Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Icon]",
1263                    tv.getTask() + " is currently filtered: " + mStack.hasFilteredTasks(),
1264                    Console.AnsiCyan);
1265        }
1266        if (Constants.DebugFlags.App.EnableTaskFiltering) {
1267            if (mStack.hasFilteredTasks()) {
1268                mStack.unfilterTasks();
1269            } else {
1270                mStack.filterTasks(tv.getTask());
1271            }
1272        }
1273    }
1274
1275    @Override
1276    public void onTaskAppInfoClicked(TaskView tv) {
1277        if (mCb != null) {
1278            mCb.onTaskAppInfoLaunched(tv.getTask());
1279        }
1280    }
1281
1282    @Override
1283    public void onTaskFocused(TaskView tv) {
1284        // Do nothing
1285    }
1286
1287    @Override
1288    public void onTaskDismissed(TaskView tv) {
1289        Task task = tv.getTask();
1290        // Remove the task from the view
1291        mStack.removeTask(task);
1292    }
1293
1294    /**** View.OnClickListener Implementation ****/
1295
1296    @Override
1297    public void onClick(View v) {
1298        TaskView tv = (TaskView) v;
1299        Task task = tv.getTask();
1300        if (Console.Enabled) {
1301            Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]",
1302                    task + " cb: " + mCb);
1303        }
1304
1305        // Cancel any doze triggers
1306        mUIDozeTrigger.stopDozing();
1307
1308        if (mCb != null) {
1309            mCb.onTaskLaunched(this, tv, mStack, task);
1310        }
1311    }
1312
1313    /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
1314
1315    @Override
1316    public void onComponentRemoved(Set<ComponentName> cns) {
1317        // For other tasks, just remove them directly if they no longer exist
1318        ArrayList<Task> tasks = mStack.getTasks();
1319        for (int i = tasks.size() - 1; i >= 0; i--) {
1320            final Task t = tasks.get(i);
1321            if (cns.contains(t.key.baseIntent.getComponent())) {
1322                TaskView tv = getChildViewForTask(t);
1323                if (tv != null) {
1324                    // For visible children, defer removing the task until after the animation
1325                    tv.startDeleteTaskAnimation(new Runnable() {
1326                        @Override
1327                        public void run() {
1328                            mStack.removeTask(t);
1329                        }
1330                    });
1331                } else {
1332                    // Otherwise, remove the task from the stack immediately
1333                    mStack.removeTask(t);
1334                }
1335            }
1336        }
1337    }
1338}