TaskStackView.java revision a433fa9c17772f563163ff7db177d091d6aebd5b
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.recents.views;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.ValueAnimator;
23import android.content.ComponentName;
24import android.content.Context;
25import android.graphics.Canvas;
26import android.graphics.Rect;
27import android.graphics.Region;
28import android.os.SystemClock;
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.Constants;
36import com.android.systemui.recents.RecentsConfiguration;
37import com.android.systemui.recents.misc.Console;
38import com.android.systemui.recents.misc.DozeTrigger;
39import com.android.systemui.recents.misc.ReferenceCountedTrigger;
40import com.android.systemui.recents.misc.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 onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
59        public void onTaskViewAppInfoClicked(Task t);
60        public void onTaskViewDismissed(Task t);
61        public void onAllTaskViewsDismissed();
62        public void onTaskStackFilterTriggered();
63        public void onTaskStackUnfilterTriggered();
64    }
65
66    RecentsConfiguration mConfig;
67
68    TaskStack mStack;
69    TaskStackViewLayoutAlgorithm mStackAlgorithm;
70    TaskStackViewFilterAlgorithm mFilterAlgorithm;
71    TaskStackViewTouchHandler mTouchHandler;
72    TaskStackViewCallbacks mCb;
73    ViewPool<TaskView, Task> mViewPool;
74    ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>();
75    DozeTrigger mUIDozeTrigger;
76
77    // The virtual stack scroll that we use for the card layout
78    int mStackScroll;
79    int mMinScroll;
80    int mMaxScroll;
81    int mStashedScroll;
82    int mFocusedTaskIndex = -1;
83    OverScroller mScroller;
84    ObjectAnimator mScrollAnimator;
85
86    // Optimizations
87    ReferenceCountedTrigger mHwLayersTrigger;
88    int mStackViewsAnimationDuration;
89    boolean mStackViewsDirty = true;
90    boolean mAwaitingFirstLayout = true;
91    boolean mStartEnterAnimationRequestedAfterLayout;
92    ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
93    int[] mTmpVisibleRange = new int[2];
94    Rect mTmpRect = new Rect();
95    Rect mTmpRect2 = new Rect();
96    LayoutInflater mInflater;
97
98    Runnable mReturnAllViewsToPoolRunnable = new Runnable() {
99        @Override
100        public void run() {
101            int childCount = getChildCount();
102            for (int i = childCount - 1; i >= 0; i--) {
103                mViewPool.returnViewToPool((TaskView) getChildAt(i));
104            }
105        }
106    };
107
108    public TaskStackView(Context context, TaskStack stack) {
109        super(context);
110        mConfig = RecentsConfiguration.getInstance();
111        mStack = stack;
112        mStack.setCallbacks(this);
113        mScroller = new OverScroller(context);
114        mTouchHandler = new TaskStackViewTouchHandler(context, this);
115        mViewPool = new ViewPool<TaskView, Task>(context, this);
116        mInflater = LayoutInflater.from(context);
117        mStackAlgorithm = new TaskStackViewLayoutAlgorithm(mConfig);
118        mFilterAlgorithm = new TaskStackViewFilterAlgorithm(mConfig, this, mViewPool);
119        mUIDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() {
120            @Override
121            public void run() {
122                // Show the task bar dismiss buttons
123                int childCount = getChildCount();
124                for (int i = 0; i < childCount; i++) {
125                    TaskView tv = (TaskView) getChildAt(i);
126                    tv.startNoUserInteractionAnimation();
127                }
128            }
129        });
130        mHwLayersTrigger = new ReferenceCountedTrigger(getContext(), new Runnable() {
131            @Override
132            public void run() {
133                // Enable hw layers on each of the children
134                int childCount = getChildCount();
135                for (int i = 0; i < childCount; i++) {
136                    TaskView tv = (TaskView) getChildAt(i);
137                    tv.enableHwLayers();
138                }
139            }
140        }, new Runnable() {
141            @Override
142            public void run() {
143                // Disable hw layers on each of the children
144                int childCount = getChildCount();
145                for (int i = 0; i < childCount; i++) {
146                    TaskView tv = (TaskView) getChildAt(i);
147                    tv.disableHwLayers();
148                }
149            }
150        }, new Runnable() {
151            @Override
152            public void run() {
153                new Throwable("Invalid hw layers ref count").printStackTrace();
154                Console.logError(getContext(), "Invalid HW layers ref count");
155            }
156        });
157    }
158
159    /** Sets the callbacks */
160    void setCallbacks(TaskStackViewCallbacks cb) {
161        mCb = cb;
162    }
163
164    /** Requests that the views be synchronized with the model */
165    void requestSynchronizeStackViewsWithModel() {
166        requestSynchronizeStackViewsWithModel(0);
167    }
168    void requestSynchronizeStackViewsWithModel(int duration) {
169        if (Console.Enabled) {
170            Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
171                    "[TaskStackView|requestSynchronize]", "" + duration + "ms", Console.AnsiYellow);
172        }
173        if (!mStackViewsDirty) {
174            invalidate(mStackAlgorithm.mStackRect);
175        }
176        if (mAwaitingFirstLayout) {
177            // Skip the animation if we are awaiting first layout
178            mStackViewsAnimationDuration = 0;
179        } else {
180            mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration);
181        }
182        mStackViewsDirty = true;
183    }
184
185    /** Returns a mapping of child view to Task. */
186    HashMap<Task, TaskView> getTaskChildViewMap() {
187        HashMap<Task, TaskView> taskViewMap = new HashMap<Task, TaskView>();
188        int childCount = getChildCount();
189        for (int i = 0; i < childCount; i++) {
190            TaskView tv = (TaskView) getChildAt(i);
191            taskViewMap.put(tv.getTask(), tv);
192        }
193        return taskViewMap;
194    }
195
196    /** Finds the child view given a specific task. */
197    TaskView getChildViewForTask(Task t) {
198        int childCount = getChildCount();
199        for (int i = 0; i < childCount; i++) {
200            TaskView tv = (TaskView) getChildAt(i);
201            if (tv.getTask() == t) {
202                return tv;
203            }
204        }
205        return null;
206    }
207
208    /** Returns the stack algorithm for this task stack. */
209    public TaskStackViewLayoutAlgorithm getStackAlgorithm() {
210        return mStackAlgorithm;
211    }
212
213    /**
214     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
215     */
216    private void updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
217                                       ArrayList<Task> tasks,
218                                       int stackScroll,
219                                       int[] visibleRangeOut,
220                                       boolean boundTranslationsToRect) {
221        // XXX: We should be intelligent about where to look for the visible stack range using the
222        //      current stack scroll.
223
224        int taskTransformCount = taskTransforms.size();
225        int taskCount = tasks.size();
226        int frontMostVisibleIndex = -1;
227        int backMostVisibleIndex = -1;
228
229        // We can reuse the task transforms where possible to reduce object allocation
230        if (taskTransformCount < taskCount) {
231            // If there are less transforms than tasks, then add as many transforms as necessary
232            for (int i = taskTransformCount; i < taskCount; i++) {
233                taskTransforms.add(new TaskViewTransform());
234            }
235        } else if (taskTransformCount > taskCount) {
236            // If there are more transforms than tasks, then just subset the transform list
237            taskTransforms.subList(0, taskCount);
238        }
239
240        // Update the stack transforms
241        for (int i = taskCount - 1; i >= 0; i--) {
242            TaskViewTransform transform = mStackAlgorithm.getStackTransform(tasks.get(i), stackScroll, taskTransforms.get(i));
243            if (transform.visible) {
244                if (frontMostVisibleIndex < 0) {
245                    frontMostVisibleIndex = i;
246                }
247                backMostVisibleIndex = i;
248            } else {
249                if (backMostVisibleIndex != -1) {
250                    // We've reached the end of the visible range, so going down the rest of the
251                    // stack, we can just reset the transforms accordingly
252                    while (i >= 0) {
253                        taskTransforms.get(i).reset();
254                        i--;
255                    }
256                    break;
257                }
258            }
259
260            if (boundTranslationsToRect) {
261                transform.translationY = Math.min(transform.translationY,
262                        mStackAlgorithm.mRect.bottom);
263            }
264        }
265        if (visibleRangeOut != null) {
266            visibleRangeOut[0] = frontMostVisibleIndex;
267            visibleRangeOut[1] = backMostVisibleIndex;
268            if (Console.Enabled) {
269                Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
270                        "[TaskStackView|updateStackTransforms]",
271                        "Back: " + backMostVisibleIndex + " Front: " + frontMostVisibleIndex);
272            }
273        }
274    }
275
276    /**
277     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. This
278     * call is less optimal than calling updateStackTransforms directly.
279     */
280    private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks,
281                                                            int stackScroll,
282                                                            int[] visibleRangeOut,
283                                                            boolean boundTranslationsToRect) {
284        ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>();
285        updateStackTransforms(taskTransforms, tasks, stackScroll, visibleRangeOut,
286                boundTranslationsToRect);
287        return taskTransforms;
288    }
289
290    /** Synchronizes the views with the model */
291    void synchronizeStackViewsWithModel() {
292        if (Console.Enabled) {
293            Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
294                    "[TaskStackView|synchronizeViewsWithModel]",
295                    "mStackViewsDirty: " + mStackViewsDirty, Console.AnsiYellow);
296        }
297        if (mStackViewsDirty) {
298            // Get all the task transforms
299            ArrayList<Task> tasks = mStack.getTasks();
300            int stackScroll = getStackScroll();
301            int[] visibleRange = mTmpVisibleRange;
302            updateStackTransforms(mCurrentTaskTransforms, tasks, stackScroll, visibleRange, false);
303            TaskViewTransform tmpTransform = new TaskViewTransform();
304            TaskStack.GroupTaskIndex gti = new TaskStack.GroupTaskIndex();
305
306            // Return all the invisible children to the pool
307            HashMap<Task, TaskView> taskChildViewMap = getTaskChildViewMap();
308            int childCount = getChildCount();
309            for (int i = childCount - 1; i >= 0; i--) {
310                TaskView tv = (TaskView) getChildAt(i);
311                Task task = tv.getTask();
312                int taskIndex = mStack.indexOfTask(task);
313                if (taskIndex < visibleRange[1] || taskIndex > visibleRange[0]) {
314                    taskChildViewMap.remove(task);
315                    mViewPool.returnViewToPool(tv);
316                }
317            }
318
319            // Pick up all the newly visible children and update all the existing children
320            boolean isValidVisibleRange = visibleRange[0] != -1 && visibleRange[1] != -1;
321            for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) {
322                Task task = tasks.get(i);
323                TaskViewTransform transform = mCurrentTaskTransforms.get(i);
324                TaskView tv = taskChildViewMap.get(task);
325                int taskIndex = mStack.indexOfTask(task);
326
327                if (tv == null) {
328                    tv = mViewPool.pickUpViewFromPool(task, task);
329                    if (mStackViewsAnimationDuration > 0) {
330                        // For items in the list, put them in start animating them from the
331                        // approriate ends of the list where they are expected to appear
332                        if (transform.t < 0) {
333                            tmpTransform = mStackAlgorithm.getStackTransform(tasks.get(0), stackScroll, tmpTransform);
334                        } else {
335                            tmpTransform = mStackAlgorithm.getStackTransform(tasks.get(Math.min(tasks.size() - 1, visibleRange[0] + 1)),
336                                    stackScroll, tmpTransform);
337                        }
338                        tv.updateViewPropertiesToTaskTransform(tmpTransform, 0);
339                    }
340                }
341
342                // Update and animate the task into place
343                tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex),
344                        mStackViewsAnimationDuration);
345            }
346
347            if (Console.Enabled) {
348                Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
349                        "  [TaskStackView|viewChildren]", "" + getChildCount());
350            }
351
352            mStackViewsAnimationDuration = 0;
353            mStackViewsDirty = false;
354        }
355    }
356
357    /** Sets the current stack scroll */
358    public void setStackScroll(int value) {
359        mStackScroll = value;
360        mUIDozeTrigger.poke();
361        requestSynchronizeStackViewsWithModel();
362    }
363    /** Sets the current stack scroll without synchronizing the stack view with the model */
364    public void setStackScrollRaw(int value) {
365        mStackScroll = value;
366        mUIDozeTrigger.poke();
367    }
368    /** Sets the current stack scroll to the initial state when you first enter recents */
369    public void setStackScrollToInitialState() {
370        setStackScroll(getInitialStackScroll());
371    }
372    /** Computes the initial stack scroll for the stack. */
373    int getInitialStackScroll() {
374        if (mStack.getTaskCount() > 2) {
375            return mMaxScroll - mStackAlgorithm.mTaskRect.height() / 2;
376        }
377        return mMaxScroll;
378    }
379
380    /** Gets the current stack scroll */
381    public int getStackScroll() {
382        return mStackScroll;
383    }
384
385    /** Animates the stack scroll into bounds */
386    ObjectAnimator animateBoundScroll() {
387        int curScroll = getStackScroll();
388        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
389        if (newScroll != curScroll) {
390            // Enable hw layers on the stack
391            addHwLayersRefCount("animateBoundScroll");
392
393            // Start a new scroll animation
394            animateScroll(curScroll, newScroll, new Runnable() {
395                @Override
396                public void run() {
397                    // Disable hw layers on the stack
398                    decHwLayersRefCount("animateBoundScroll");
399                }
400            });
401        }
402        return mScrollAnimator;
403    }
404
405    /** Animates the stack scroll */
406    void animateScroll(int curScroll, int newScroll, final Runnable postRunnable) {
407        // Abort any current animations
408        abortScroller();
409        abortBoundScrollAnimation();
410
411        mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll);
412        mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll -
413                curScroll, 250));
414        mScrollAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
415        mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
416            @Override
417            public void onAnimationUpdate(ValueAnimator animation) {
418                setStackScroll((Integer) animation.getAnimatedValue());
419            }
420        });
421        mScrollAnimator.addListener(new AnimatorListenerAdapter() {
422            @Override
423            public void onAnimationEnd(Animator animation) {
424                if (postRunnable != null) {
425                    postRunnable.run();
426                }
427                mScrollAnimator.removeAllListeners();
428            }
429        });
430        mScrollAnimator.start();
431    }
432
433    /** Aborts any current stack scrolls */
434    void abortBoundScrollAnimation() {
435        if (mScrollAnimator != null) {
436            mScrollAnimator.cancel();
437        }
438    }
439
440    /** Aborts the scroller and any current fling */
441    void abortScroller() {
442        if (!mScroller.isFinished()) {
443            // Abort the scroller
444            mScroller.abortAnimation();
445            // And disable hw layers on the stack
446            decHwLayersRefCount("flingScroll");
447        }
448    }
449
450    /** Bounds the current scroll if necessary */
451    public boolean boundScroll() {
452        int curScroll = getStackScroll();
453        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
454        if (newScroll != curScroll) {
455            setStackScroll(newScroll);
456            return true;
457        }
458        return false;
459    }
460
461    /**
462     * Bounds the current scroll if necessary, but does not synchronize the stack view with the
463     * model.
464     */
465    public boolean boundScrollRaw() {
466        int curScroll = getStackScroll();
467        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
468        if (newScroll != curScroll) {
469            setStackScrollRaw(newScroll);
470            return true;
471        }
472        return false;
473    }
474
475
476    /** Returns the amount that the scroll is out of bounds */
477    int getScrollAmountOutOfBounds(int scroll) {
478        if (scroll < mMinScroll) {
479            return mMinScroll - scroll;
480        } else if (scroll > mMaxScroll) {
481            return scroll - mMaxScroll;
482        }
483        return 0;
484    }
485
486    /** Returns whether the specified scroll is out of bounds */
487    boolean isScrollOutOfBounds() {
488        return getScrollAmountOutOfBounds(getStackScroll()) != 0;
489    }
490
491    /** Updates the min and max virtual scroll bounds */
492    void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
493        // Compute the min and max scroll values
494        mStackAlgorithm.computeMinMaxScroll(mStack.getTasks());
495        mMinScroll = mStackAlgorithm.mMinScroll;
496        mMaxScroll = mStackAlgorithm.mMaxScroll;
497
498        // Debug logging
499        if (Constants.Log.UI.MeasureAndLayout) {
500            Console.log("  [TaskStack|minScroll] " + mMinScroll);
501            Console.log("  [TaskStack|maxScroll] " + mMaxScroll);
502        }
503
504        if (boundScrollToNewMinMax) {
505            boundScroll();
506        }
507    }
508
509    /** Animates a task view in this stack as it launches. */
510    public void animateOnLaunchingTask(TaskView tv, final Runnable r) {
511        // Hide each of the task bar dismiss buttons
512        int childCount = getChildCount();
513        for (int i = 0; i < childCount; i++) {
514            TaskView t = (TaskView) getChildAt(i);
515            if (t == tv) {
516                t.startLaunchTaskAnimation(r, true);
517            } else {
518                t.startLaunchTaskAnimation(null, false);
519            }
520        }
521    }
522
523    /** Focuses the task at the specified index in the stack */
524    void focusTask(int taskIndex, boolean scrollToNewPosition) {
525        if (Console.Enabled) {
526            Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]", "" + taskIndex);
527        }
528        if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
529            mFocusedTaskIndex = taskIndex;
530
531            // Focus the view if possible, otherwise, focus the view after we scroll into position
532            Task t = mStack.getTasks().get(taskIndex);
533            TaskView tv = getChildViewForTask(t);
534            Runnable postScrollRunnable = null;
535            if (tv != null) {
536                tv.setFocusedTask();
537                if (Console.Enabled) {
538                    Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]", "Requesting focus");
539                }
540            } else {
541                postScrollRunnable = new Runnable() {
542                    @Override
543                    public void run() {
544                        Task t = mStack.getTasks().get(mFocusedTaskIndex);
545                        TaskView tv = getChildViewForTask(t);
546                        if (tv != null) {
547                            tv.setFocusedTask();
548                            if (Console.Enabled) {
549                                Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]",
550                                        "Requesting focus after scroll animation");
551                            }
552                        }
553                    }
554                };
555            }
556
557            if (scrollToNewPosition) {
558                // Scroll the view into position
559                int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll,
560                        mStackAlgorithm.getStackScrollForTaskIndex(t)));
561
562                animateScroll(getStackScroll(), newScroll, postScrollRunnable);
563            } else {
564                if (postScrollRunnable != null) {
565                    postScrollRunnable.run();
566                }
567            }
568        }
569    }
570
571    /** Focuses the next task in the stack */
572    void focusNextTask(boolean forward) {
573        if (Console.Enabled) {
574            Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusNextTask]", "" +
575                    mFocusedTaskIndex);
576        }
577
578        // Find the next index to focus
579        int numTasks = mStack.getTaskCount();
580        if (mFocusedTaskIndex < 0) {
581            mFocusedTaskIndex = numTasks - 1;
582        }
583        if (0 <= mFocusedTaskIndex && mFocusedTaskIndex < numTasks) {
584            mFocusedTaskIndex = Math.max(0, Math.min(numTasks - 1,
585                    mFocusedTaskIndex + (forward ? -1 : 1)));
586        }
587        focusTask(mFocusedTaskIndex, true);
588    }
589
590    /** Enables the hw layers and increments the hw layer requirement ref count */
591    void addHwLayersRefCount(String reason) {
592        if (Console.Enabled) {
593            int refCount = mHwLayersTrigger.getCount();
594            Console.log(Constants.Log.UI.HwLayers,
595                    "[TaskStackView|addHwLayersRefCount] refCount: " +
596                            refCount + "->" + (refCount + 1) + " " + reason);
597        }
598        mHwLayersTrigger.increment();
599    }
600
601    /** Decrements the hw layer requirement ref count and disables the hw layers when we don't
602        need them anymore. */
603    void decHwLayersRefCount(String reason) {
604        if (Console.Enabled) {
605            int refCount = mHwLayersTrigger.getCount();
606            Console.log(Constants.Log.UI.HwLayers,
607                    "[TaskStackView|decHwLayersRefCount] refCount: " +
608                            refCount + "->" + (refCount - 1) + " " + reason);
609        }
610        mHwLayersTrigger.decrement();
611    }
612
613    @Override
614    public boolean onInterceptTouchEvent(MotionEvent ev) {
615        return mTouchHandler.onInterceptTouchEvent(ev);
616    }
617
618    @Override
619    public boolean onTouchEvent(MotionEvent ev) {
620        return mTouchHandler.onTouchEvent(ev);
621    }
622
623    @Override
624    public void computeScroll() {
625        if (mScroller.computeScrollOffset()) {
626            setStackScroll(mScroller.getCurrY());
627            invalidate(mStackAlgorithm.mStackRect);
628
629            // If we just finished scrolling, then disable the hw layers
630            if (mScroller.isFinished()) {
631                decHwLayersRefCount("finishedFlingScroll");
632            }
633        }
634    }
635
636    @Override
637    public void dispatchDraw(Canvas canvas) {
638        if (Console.Enabled) {
639            Console.log(Constants.Log.UI.Draw, "[TaskStackView|dispatchDraw]", "",
640                    Console.AnsiPurple);
641        }
642        synchronizeStackViewsWithModel();
643        super.dispatchDraw(canvas);
644    }
645
646    @Override
647    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
648        if (Constants.DebugFlags.App.EnableTaskStackClipping) {
649            TaskView tv = (TaskView) child;
650            TaskView nextTv = null;
651            TaskView tmpTv = null;
652            if (tv.shouldClipViewInStack()) {
653                int curIndex = indexOfChild(tv);
654
655                // Find the next view to clip against
656                while (nextTv == null && curIndex < getChildCount()) {
657                    tmpTv = (TaskView) getChildAt(++curIndex);
658                    if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
659                        nextTv = tmpTv;
660                    }
661                }
662
663                // Clip against the next view (if we aren't animating its alpha)
664                if (nextTv != null) {
665                    Rect curRect = tv.getClippingRect(mTmpRect);
666                    Rect nextRect = nextTv.getClippingRect(mTmpRect2);
667                    // The hit rects are relative to the task view, which needs to be offset by
668                    // the system bar height
669                    curRect.offset(0, mConfig.systemInsets.top);
670                    nextRect.offset(0, mConfig.systemInsets.top);
671                    // Compute the clip region
672                    Region clipRegion = new Region();
673                    clipRegion.op(curRect, Region.Op.UNION);
674                    clipRegion.op(nextRect, Region.Op.DIFFERENCE);
675                    // Clip the canvas
676                    int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
677                    canvas.clipRegion(clipRegion);
678                    boolean invalidate = super.drawChild(canvas, child, drawingTime);
679                    canvas.restoreToCount(saveCount);
680                    return invalidate;
681                }
682            }
683        }
684        return super.drawChild(canvas, child, drawingTime);
685    }
686
687    /** Computes the stack and task rects */
688    public void computeRects(int width, int height, int insetLeft, int insetBottom) {
689        // Compute the rects in the stack algorithm
690        mStackAlgorithm.computeRects(mStack.getTasks(), width, height, insetLeft, insetBottom);
691
692        // Update the scroll bounds
693        updateMinMaxScroll(false);
694    }
695
696    /**
697     * This is called with the size of the space not including the top or right insets, or the
698     * search bar height in portrait (but including the search bar width in landscape, since we want
699     * to draw under it.
700     */
701    @Override
702    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
703        int width = MeasureSpec.getSize(widthMeasureSpec);
704        int height = MeasureSpec.getSize(heightMeasureSpec);
705        if (Console.Enabled) {
706            Console.log(Constants.Log.UI.MeasureAndLayout, "[TaskStackView|measure]",
707                    "width: " + width + " height: " + height +
708                            " awaitingFirstLayout: " + mAwaitingFirstLayout, Console.AnsiGreen);
709        }
710
711        // Compute our stack/task rects
712        Rect taskStackBounds = new Rect();
713        mConfig.getTaskStackBounds(width, height, taskStackBounds);
714        computeRects(width, height, taskStackBounds.left, mConfig.systemInsets.bottom);
715
716        // Debug logging
717        if (Constants.Log.UI.MeasureAndLayout) {
718            Console.log("  [TaskStack|fullRect] " + mStackAlgorithm.mRect);
719            Console.log("  [TaskStack|stackRect] " + mStackAlgorithm.mStackRect);
720            Console.log("  [TaskStack|stackRectSansPeek] " + mStackAlgorithm.mStackRectSansPeek);
721            Console.log("  [TaskStack|taskRect] " + mStackAlgorithm.mTaskRect);
722        }
723
724        // If this is the first layout, then scroll to the front of the stack and synchronize the
725        // stack views immediately
726        if (mAwaitingFirstLayout) {
727            setStackScrollToInitialState();
728            requestSynchronizeStackViewsWithModel();
729            synchronizeStackViewsWithModel();
730        }
731
732        // Measure each of the children
733        int childCount = getChildCount();
734        for (int i = 0; i < childCount; i++) {
735            TaskView t = (TaskView) getChildAt(i);
736            t.measure(MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(), MeasureSpec.EXACTLY),
737                    MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height(), MeasureSpec.EXACTLY));
738        }
739
740        setMeasuredDimension(width, height);
741    }
742
743    /**
744     * This is called with the size of the space not including the top or right insets, or the
745     * search bar height in portrait (but including the search bar width in landscape, since we want
746     * to draw under it.
747     */
748    @Override
749    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
750        if (Console.Enabled) {
751            Console.log(Constants.Log.UI.MeasureAndLayout, "[TaskStackView|layout]",
752                    "" + new Rect(left, top, right, bottom), Console.AnsiGreen);
753        }
754
755        // Debug logging
756        if (Constants.Log.UI.MeasureAndLayout) {
757            Console.log("  [TaskStack|fullRect] " + mStackAlgorithm.mRect);
758            Console.log("  [TaskStack|stackRect] " + mStackAlgorithm.mStackRect);
759            Console.log("  [TaskStack|stackRectSansPeek] " + mStackAlgorithm.mStackRectSansPeek);
760            Console.log("  [TaskStack|taskRect] " + mStackAlgorithm.mTaskRect);
761        }
762
763        // Layout each of the children
764        int childCount = getChildCount();
765        for (int i = 0; i < childCount; i++) {
766            TaskView t = (TaskView) getChildAt(i);
767            t.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mStackRectSansPeek.top,
768                    mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mStackRectSansPeek.top +
769                    mStackAlgorithm.mTaskRect.height());
770        }
771
772        if (mAwaitingFirstLayout) {
773            // Mark that we have completely the first layout
774            mAwaitingFirstLayout = false;
775
776            // Prepare the first view for its enter animation
777            int offsetTopAlign = -mStackAlgorithm.mTaskRect.top;
778            int offscreenY = mStackAlgorithm.mRect.bottom -
779                    (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mRect.top);
780            for (int i = childCount - 1; i >= 0; i--) {
781                TaskView tv = (TaskView) getChildAt(i);
782                tv.prepareEnterRecentsAnimation((i == (getChildCount() - 1)), offsetTopAlign,
783                        offscreenY, mStackAlgorithm.mTaskRect);
784            }
785
786            // If the enter animation started already and we haven't completed a layout yet, do the
787            // enter animation now
788            if (mStartEnterAnimationRequestedAfterLayout) {
789                startEnterRecentsAnimation(mStartEnterAnimationContext);
790                mStartEnterAnimationRequestedAfterLayout = false;
791                mStartEnterAnimationContext = null;
792            }
793
794            // Update the focused task index to be the next item to the top task
795            if (mConfig.launchedWithAltTab) {
796                // When alt-tabbing, we focus the next previous task
797                focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
798            }
799        }
800    }
801
802    /** Requests this task stacks to start it's enter-recents animation */
803    public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) {
804        // If we are still waiting to layout, then just defer until then
805        if (mAwaitingFirstLayout) {
806            mStartEnterAnimationRequestedAfterLayout = true;
807            mStartEnterAnimationContext = ctx;
808            return;
809        }
810
811        if (mStack.getTaskCount() > 0) {
812            if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
813                // Find the target task with the specified id
814                ArrayList<Task> tasks = mStack.getTasks();
815                Task targetTask = null;
816                int targetTaskId = mConfig.launchedToTaskId;
817                if (targetTaskId != -1) {
818                    int taskCount = tasks.size();
819                    for (int i = 0; i < taskCount; i++) {
820                        Task t = tasks.get(i);
821                        if (t.key.id == targetTaskId) {
822                            targetTask = t;
823                            break;
824                        }
825                    }
826                }
827
828                // Find the group and task index of the target task
829                if (targetTask != null) {
830                    ctx.targetTaskTransform = new TaskViewTransform();
831                    mStackAlgorithm.getStackTransform(targetTask, getStackScroll(), ctx.targetTaskTransform);
832                    Rect taskStackBounds = new Rect();
833                    mConfig.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds);
834                    ctx.targetTaskTransform.rect.offset(taskStackBounds.left, taskStackBounds.top);
835                }
836            }
837
838            // Animate all the task views into view
839            int childCount = getChildCount();
840            for (int i = childCount - 1; i >= 0; i--) {
841                TaskView tv = (TaskView) getChildAt(i);
842                ctx.currentTaskTransform = new TaskViewTransform();
843                ctx.currentStackViewIndex = i;
844                ctx.currentStackViewCount = childCount;
845                ctx.isCurrentTaskFrontMost = (i == (getChildCount() - 1));
846                mStackAlgorithm.getStackTransform(tv.getTask(), getStackScroll(), ctx.currentTaskTransform);
847                tv.startEnterRecentsAnimation(ctx);
848            }
849
850            // Add a runnable to the post animation ref counter to clear all the views
851            ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
852                @Override
853                public void run() {
854                    // Start dozing
855                    mUIDozeTrigger.startDozing();
856                }
857            });
858        }
859    }
860
861    /** Requests this task stacks to start it's exit-recents animation. */
862    public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
863        // Animate all the task views into view
864        ctx.offscreenTranslationY = mStackAlgorithm.mRect.bottom -
865                (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mRect.top);
866        int childCount = getChildCount();
867        for (int i = 0; i < childCount; i++) {
868            TaskView tv = (TaskView) getChildAt(i);
869            tv.startExitToHomeAnimation(ctx);
870        }
871
872        // Add a runnable to the post animation ref counter to clear all the views
873        ctx.postAnimationTrigger.addLastDecrementRunnable(mReturnAllViewsToPoolRunnable);
874    }
875
876    @Override
877    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
878        super.onScrollChanged(l, t, oldl, oldt);
879        requestSynchronizeStackViewsWithModel();
880    }
881
882    public boolean isTransformedTouchPointInView(float x, float y, View child) {
883        return isTransformedTouchPointInView(x, y, child, null);
884    }
885
886    /** Pokes the dozer on user interaction. */
887    void onUserInteraction() {
888        // Poke the doze trigger if it is dozing
889        mUIDozeTrigger.poke();
890    }
891
892    /** Disables handling touch on this task view. */
893    void setTouchOnTaskView(TaskView tv, boolean enabled) {
894        tv.setOnClickListener(enabled ? this : null);
895    }
896
897    /**** TaskStackCallbacks Implementation ****/
898
899    @Override
900    public void onStackTaskAdded(TaskStack stack, Task t) {
901        // Update the task offsets
902        mStackAlgorithm.updateTaskOffsets(mStack.getTasks());
903
904        requestSynchronizeStackViewsWithModel();
905    }
906
907    @Override
908    public void onStackTaskRemoved(TaskStack stack, Task t) {
909        // Update the task offsets
910        mStackAlgorithm.updateTaskOffsets(mStack.getTasks());
911
912        // Remove the view associated with this task, we can't rely on updateTransforms
913        // to work here because the task is no longer in the list
914        TaskView tv = getChildViewForTask(t);
915        if (tv != null) {
916            mViewPool.returnViewToPool(tv);
917        }
918
919        // Notify the callback that we've removed the task and it can clean up after it
920        mCb.onTaskViewDismissed(t);
921
922        // Update the min/max scroll and animate other task views into their new positions
923        updateMinMaxScroll(true);
924        int movement = (int) mStackAlgorithm.getTaskOverlapHeight();
925        requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement));
926
927        // If there are no remaining tasks, then either unfilter the current stack, or just close
928        // the activity if there are no filtered stacks
929        if (mStack.getTaskCount() == 0) {
930            boolean shouldFinishActivity = true;
931            if (mStack.hasFilteredTasks()) {
932                mStack.unfilterTasks();
933                shouldFinishActivity = (mStack.getTaskCount() == 0);
934            }
935            if (shouldFinishActivity) {
936                mCb.onAllTaskViewsDismissed();
937            }
938        }
939    }
940
941    @Override
942    public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
943                                Task filteredTask) {
944        // Stash the scroll and filtered task for us to restore to when we unfilter
945        mStashedScroll = getStackScroll();
946
947        // Calculate the current task transforms
948        ArrayList<TaskViewTransform> curTaskTransforms =
949                getStackTransforms(curTasks, getStackScroll(), null, true);
950
951        // Update the task offsets
952        mStackAlgorithm.updateTaskOffsets(mStack.getTasks());
953
954        // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
955        updateMinMaxScroll(false);
956        float overlapHeight = mStackAlgorithm.getTaskOverlapHeight();
957        setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
958        boundScrollRaw();
959
960        // Compute the transforms of the items in the new stack after setting the new scroll
961        final ArrayList<Task> tasks = mStack.getTasks();
962        final ArrayList<TaskViewTransform> taskTransforms =
963                getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);
964
965        // Animate
966        mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
967
968        // Notify any callbacks
969        mCb.onTaskStackFilterTriggered();
970    }
971
972    @Override
973    public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) {
974        // Calculate the current task transforms
975        final ArrayList<TaskViewTransform> curTaskTransforms =
976                getStackTransforms(curTasks, getStackScroll(), null, true);
977
978        // Update the task offsets
979        mStackAlgorithm.updateTaskOffsets(mStack.getTasks());
980
981        // Restore the stashed scroll
982        updateMinMaxScroll(false);
983        setStackScrollRaw(mStashedScroll);
984        boundScrollRaw();
985
986        // Compute the transforms of the items in the new stack after restoring the stashed scroll
987        final ArrayList<Task> tasks = mStack.getTasks();
988        final ArrayList<TaskViewTransform> taskTransforms =
989                getStackTransforms(tasks, getStackScroll(), null, true);
990
991        // Animate
992        mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
993
994        // Clear the saved vars
995        mStashedScroll = 0;
996
997        // Notify any callbacks
998        mCb.onTaskStackUnfilterTriggered();
999    }
1000
1001    /**** ViewPoolConsumer Implementation ****/
1002
1003    @Override
1004    public TaskView createView(Context context) {
1005        if (Console.Enabled) {
1006            Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|createPoolView]");
1007        }
1008        return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
1009    }
1010
1011    @Override
1012    public void prepareViewToEnterPool(TaskView tv) {
1013        Task task = tv.getTask();
1014        if (Console.Enabled) {
1015            Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]",
1016                    tv.getTask() + " tv: " + tv);
1017        }
1018
1019        // Report that this tasks's data is no longer being used
1020        RecentsTaskLoader.getInstance().unloadTaskData(task);
1021
1022        // Detach the view from the hierarchy
1023        detachViewFromParent(tv);
1024
1025        // Disable HW layers
1026        tv.disableHwLayers();
1027
1028        // Reset the view properties
1029        tv.resetViewProperties();
1030    }
1031
1032    @Override
1033    public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
1034        if (Console.Enabled) {
1035            Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]",
1036                    "isNewView: " + isNewView);
1037        }
1038
1039        // Rebind the task and request that this task's data be filled into the TaskView
1040        tv.onTaskBound(task);
1041        RecentsTaskLoader.getInstance().loadTaskData(task);
1042
1043        // Sanity check, the task view should always be clipping against the stack at this point,
1044        // but just in case, re-enable it here
1045        tv.setClipViewInStack(true);
1046
1047        // If the doze trigger has already fired, then update the state for this task view
1048        if (mUIDozeTrigger.hasTriggered()) {
1049            tv.setNoUserInteractionState();
1050        }
1051
1052        // Find the index where this task should be placed in the stack
1053        int insertIndex = -1;
1054        int taskIndex = mStack.indexOfTask(task);
1055        if (taskIndex != -1) {
1056            int childCount = getChildCount();
1057            for (int i = 0; i < childCount; i++) {
1058                Task tvTask = ((TaskView) getChildAt(i)).getTask();
1059                if (taskIndex < mStack.indexOfTask(tvTask)) {
1060                    insertIndex = i;
1061                    break;
1062                }
1063            }
1064        }
1065
1066        // Add/attach the view to the hierarchy
1067        if (Console.Enabled) {
1068            Console.log(Constants.Log.ViewPool.PoolCallbacks, "  [TaskStackView|insertIndex]",
1069                    "" + insertIndex);
1070        }
1071        if (isNewView) {
1072            addView(tv, insertIndex);
1073
1074            // Set the callbacks and listeners for this new view
1075            setTouchOnTaskView(tv, true);
1076            tv.setCallbacks(this);
1077        } else {
1078            attachViewToParent(tv, insertIndex, tv.getLayoutParams());
1079        }
1080
1081        // Enable hw layers on this view if hw layers are enabled on the stack
1082        if (mHwLayersTrigger.getCount() > 0) {
1083            tv.enableHwLayers();
1084        }
1085    }
1086
1087    @Override
1088    public boolean hasPreferredData(TaskView tv, Task preferredData) {
1089        return (tv.getTask() == preferredData);
1090    }
1091
1092    /**** TaskViewCallbacks Implementation ****/
1093
1094    @Override
1095    public void onTaskViewAppIconClicked(TaskView tv) {
1096        if (Console.Enabled) {
1097            Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Icon]",
1098                    tv.getTask() + " is currently filtered: " + mStack.hasFilteredTasks(),
1099                    Console.AnsiCyan);
1100        }
1101        if (Constants.DebugFlags.App.EnableTaskFiltering) {
1102            if (mStack.hasFilteredTasks()) {
1103                mStack.unfilterTasks();
1104            } else {
1105                mStack.filterTasks(tv.getTask());
1106            }
1107        }
1108    }
1109
1110    @Override
1111    public void onTaskViewAppInfoClicked(TaskView tv) {
1112        if (mCb != null) {
1113            mCb.onTaskViewAppInfoClicked(tv.getTask());
1114        }
1115    }
1116
1117    @Override
1118    public void onTaskViewDismissed(TaskView tv) {
1119        Task task = tv.getTask();
1120        // Remove the task from the view
1121        mStack.removeTask(task);
1122    }
1123
1124    /**** View.OnClickListener Implementation ****/
1125
1126    @Override
1127    public void onClick(View v) {
1128        TaskView tv = (TaskView) v;
1129        Task task = tv.getTask();
1130        if (Console.Enabled) {
1131            Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]",
1132                    task + " cb: " + mCb);
1133        }
1134
1135        // Cancel any doze triggers
1136        mUIDozeTrigger.stopDozing();
1137
1138        if (mCb != null) {
1139            mCb.onTaskViewClicked(this, tv, mStack, task);
1140        }
1141    }
1142
1143    /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
1144
1145    @Override
1146    public void onComponentRemoved(Set<ComponentName> cns) {
1147        // For other tasks, just remove them directly if they no longer exist
1148        ArrayList<Task> tasks = mStack.getTasks();
1149        for (int i = tasks.size() - 1; i >= 0; i--) {
1150            final Task t = tasks.get(i);
1151            if (cns.contains(t.key.baseIntent.getComponent())) {
1152                TaskView tv = getChildViewForTask(t);
1153                if (tv != null) {
1154                    // For visible children, defer removing the task until after the animation
1155                    tv.startDeleteTaskAnimation(new Runnable() {
1156                        @Override
1157                        public void run() {
1158                            mStack.removeTask(t);
1159                        }
1160                    });
1161                } else {
1162                    // Otherwise, remove the task from the stack immediately
1163                    mStack.removeTask(t);
1164                }
1165            }
1166        }
1167    }
1168}