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