TaskStackView.java revision d6c3db5e5a0a4087b46dd2cdac8e7f90695dd8b8
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.view.LayoutInflater;
28import android.view.MotionEvent;
29import android.view.View;
30import android.widget.FrameLayout;
31import android.widget.OverScroller;
32import com.android.systemui.R;
33import com.android.systemui.recents.Constants;
34import com.android.systemui.recents.RecentsConfiguration;
35import com.android.systemui.recents.misc.Console;
36import com.android.systemui.recents.misc.DozeTrigger;
37import com.android.systemui.recents.misc.ReferenceCountedTrigger;
38import com.android.systemui.recents.misc.Utilities;
39import com.android.systemui.recents.model.RecentsPackageMonitor;
40import com.android.systemui.recents.model.RecentsTaskLoader;
41import com.android.systemui.recents.model.Task;
42import com.android.systemui.recents.model.TaskStack;
43
44import java.util.ArrayList;
45import java.util.HashMap;
46import java.util.HashSet;
47
48
49/* The visual representation of a task stack view */
50public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
51        TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>,
52        RecentsPackageMonitor.PackageCallbacks {
53
54    /** The TaskView callbacks */
55    interface TaskStackViewCallbacks {
56        public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
57                                      boolean lockToTask);
58        public void onTaskViewAppInfoClicked(Task t);
59        public void onTaskViewDismissed(Task t);
60        public void onAllTaskViewsDismissed();
61        public void onTaskStackFilterTriggered();
62        public void onTaskStackUnfilterTriggered();
63    }
64
65    RecentsConfiguration mConfig;
66
67    TaskStack mStack;
68    TaskStackViewLayoutAlgorithm mStackAlgorithm;
69    TaskStackViewFilterAlgorithm mFilterAlgorithm;
70    TaskStackViewTouchHandler mTouchHandler;
71    TaskStackViewCallbacks mCb;
72    ViewPool<TaskView, Task> mViewPool;
73    ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>();
74    DozeTrigger mUIDozeTrigger;
75    DebugOverlayView mDebugOverlay;
76    Rect mTaskStackBounds = 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    boolean mEnableStackClipping = true;
87
88    // Optimizations
89    ReferenceCountedTrigger mHwLayersTrigger;
90    int mStackViewsAnimationDuration;
91    boolean mStackViewsDirty = true;
92    boolean mAwaitingFirstLayout = true;
93    boolean mStartEnterAnimationRequestedAfterLayout;
94    ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
95    int[] mTmpVisibleRange = new int[2];
96    Rect mTmpRect = new Rect();
97    Rect mTmpRect2 = new Rect();
98    TaskViewTransform mTmpTransform = new TaskViewTransform();
99    HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<Task, TaskView>();
100    LayoutInflater mInflater;
101
102    // A convenience runnable to return all views to the pool
103    Runnable mReturnAllViewsToPoolRunnable = new Runnable() {
104        @Override
105        public void run() {
106            int childCount = getChildCount();
107            for (int i = childCount - 1; i >= 0; i--) {
108                TaskView tv = (TaskView) getChildAt(i);
109                mViewPool.returnViewToPool(tv);
110                // Also hide the view since we don't need it anymore
111                tv.setVisibility(View.INVISIBLE);
112            }
113        }
114    };
115
116    public TaskStackView(Context context, TaskStack stack) {
117        super(context);
118        mConfig = RecentsConfiguration.getInstance();
119        mStack = stack;
120        mStack.setCallbacks(this);
121        mScroller = new OverScroller(context);
122        mTouchHandler = new TaskStackViewTouchHandler(context, this);
123        mViewPool = new ViewPool<TaskView, Task>(context, this);
124        mInflater = LayoutInflater.from(context);
125        mStackAlgorithm = new TaskStackViewLayoutAlgorithm(mConfig);
126        mFilterAlgorithm = new TaskStackViewFilterAlgorithm(mConfig, this, mViewPool);
127        mUIDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() {
128            @Override
129            public void run() {
130                // Show the task bar dismiss buttons
131                int childCount = getChildCount();
132                for (int i = 0; i < childCount; i++) {
133                    TaskView tv = (TaskView) getChildAt(i);
134                    tv.startNoUserInteractionAnimation();
135                }
136            }
137        });
138    }
139
140    /** Sets the callbacks */
141    void setCallbacks(TaskStackViewCallbacks cb) {
142        mCb = cb;
143    }
144
145    /** Sets the debug overlay */
146    public void setDebugOverlay(DebugOverlayView overlay) {
147        mDebugOverlay = overlay;
148    }
149
150    /** Requests that the views be synchronized with the model */
151    void requestSynchronizeStackViewsWithModel() {
152        requestSynchronizeStackViewsWithModel(0);
153    }
154    void requestSynchronizeStackViewsWithModel(int duration) {
155        if (!mStackViewsDirty) {
156            invalidate(mStackAlgorithm.mStackRect);
157            mStackViewsDirty = true;
158        }
159        if (mAwaitingFirstLayout) {
160            // Skip the animation if we are awaiting first layout
161            mStackViewsAnimationDuration = 0;
162        } else {
163            mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration);
164        }
165    }
166
167    /** Finds the child view given a specific task. */
168    public TaskView getChildViewForTask(Task t) {
169        int childCount = getChildCount();
170        for (int i = 0; i < childCount; i++) {
171            TaskView tv = (TaskView) getChildAt(i);
172            if (tv.getTask() == t) {
173                return tv;
174            }
175        }
176        return null;
177    }
178
179    /** Returns the stack algorithm for this task stack. */
180    public TaskStackViewLayoutAlgorithm getStackAlgorithm() {
181        return mStackAlgorithm;
182    }
183
184    /**
185     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
186     */
187    private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
188                                       ArrayList<Task> tasks,
189                                       int stackScroll,
190                                       int[] visibleRangeOut,
191                                       boolean boundTranslationsToRect) {
192        // XXX: We should be intelligent about wheee to look for the visible stack range using the
193        //      current stack scroll.
194        // XXX: We should log extra cases like the ones below where we don't expect to hit very often
195        // XXX: Print out approximately how many indices we have to go through to find the first visible transform
196
197        int taskTransformCount = taskTransforms.size();
198        int taskCount = tasks.size();
199        int frontMostVisibleIndex = -1;
200        int backMostVisibleIndex = -1;
201
202
203
204        // We can reuse the task transforms where possible to reduce object allocation
205        if (taskTransformCount < taskCount) {
206            // If there are less transforms than tasks, then add as many transforms as necessary
207            for (int i = taskTransformCount; i < taskCount; i++) {
208                taskTransforms.add(new TaskViewTransform());
209            }
210        } else if (taskTransformCount > taskCount) {
211            // If there are more transforms than tasks, then just subset the transform list
212            taskTransforms.subList(0, taskCount);
213        }
214
215        // Update the stack transforms
216        for (int i = taskCount - 1; i >= 0; i--) {
217            TaskViewTransform transform = mStackAlgorithm.getStackTransform(tasks.get(i), stackScroll, taskTransforms.get(i));
218            if (transform.visible) {
219                if (frontMostVisibleIndex < 0) {
220                    frontMostVisibleIndex = i;
221                }
222                backMostVisibleIndex = i;
223            } else {
224                if (backMostVisibleIndex != -1) {
225                    // We've reached the end of the visible range, so going down the rest of the
226                    // stack, we can just reset the transforms accordingly
227                    while (i >= 0) {
228                        taskTransforms.get(i).reset();
229                        i--;
230                    }
231                    break;
232                }
233            }
234
235            if (boundTranslationsToRect) {
236                transform.translationY = Math.min(transform.translationY,
237                        mStackAlgorithm.mViewRect.bottom);
238            }
239        }
240        if (visibleRangeOut != null) {
241            visibleRangeOut[0] = frontMostVisibleIndex;
242            visibleRangeOut[1] = backMostVisibleIndex;
243        }
244        return frontMostVisibleIndex != -1 && backMostVisibleIndex != -1;
245    }
246
247    /**
248     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. This
249     * call is less optimal than calling updateStackTransforms directly.
250     */
251    private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks,
252                                                            int stackScroll,
253                                                            int[] visibleRangeOut,
254                                                            boolean boundTranslationsToRect) {
255        ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>();
256        updateStackTransforms(taskTransforms, tasks, stackScroll, visibleRangeOut,
257                boundTranslationsToRect);
258        return taskTransforms;
259    }
260
261    /** Synchronizes the views with the model */
262    boolean synchronizeStackViewsWithModel() {
263        if (mStackViewsDirty) {
264            // Get all the task transforms
265            ArrayList<Task> tasks = mStack.getTasks();
266            int stackScroll = getStackScroll();
267            int[] visibleRange = mTmpVisibleRange;
268            boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks, stackScroll, visibleRange, false);
269
270            // Return all the invisible children to the pool
271            mTmpTaskViewMap.clear();
272            int childCount = getChildCount();
273            for (int i = childCount - 1; i >= 0; i--) {
274                TaskView tv = (TaskView) getChildAt(i);
275                Task task = tv.getTask();
276                int taskIndex = mStack.indexOfTask(task);
277                if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) {
278                    mTmpTaskViewMap.put(task, tv);
279                } else {
280                    mViewPool.returnViewToPool(tv);
281                }
282            }
283
284            // Pick up all the newly visible children and update all the existing children
285            for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) {
286                Task task = tasks.get(i);
287                TaskViewTransform transform = mCurrentTaskTransforms.get(i);
288                TaskView tv = mTmpTaskViewMap.get(task);
289                int taskIndex = mStack.indexOfTask(task);
290
291                if (tv == null) {
292                    tv = mViewPool.pickUpViewFromPool(task, task);
293                    if (mStackViewsAnimationDuration > 0) {
294                        // For items in the list, put them in start animating them from the
295                        // approriate ends of the list where they are expected to appear
296                        if (transform.t < 0) {
297                            mTmpTransform = mStackAlgorithm.getStackTransform(tasks.get(0), stackScroll, mTmpTransform);
298                        } else {
299                            int nextTaskStackScroll = mStackAlgorithm.getStackScrollForTaskIndex(task, 1);
300                            mStackAlgorithm.getStackTransform(nextTaskStackScroll, stackScroll, mTmpTransform);
301                        }
302                        tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0);
303                    }
304                }
305
306                // Animate the task into place
307                tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex),
308                        mStackViewsAnimationDuration);
309            }
310
311            // Reset the request-synchronize params
312            mStackViewsAnimationDuration = 0;
313            mStackViewsDirty = false;
314            return true;
315        }
316        return false;
317    }
318
319    /** Updates the clip for each of the task views. */
320    void clipTaskViews() {
321        // Update the clip on each task child
322        if (Constants.DebugFlags.App.EnableTaskStackClipping && mEnableStackClipping) {
323            int childCount = getChildCount();
324            for (int i = 0; i < childCount - 1; i++) {
325                TaskView tv = (TaskView) getChildAt(i);
326                TaskView nextTv = null;
327                TaskView tmpTv = null;
328                int clipBottom = 0;
329                if (tv.shouldClipViewInStack()) {
330                    // Find the next view to clip against
331                    int nextIndex = i;
332                    while (nextIndex < getChildCount()) {
333                        tmpTv = (TaskView) getChildAt(++nextIndex);
334                        if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
335                            nextTv = tmpTv;
336                            break;
337                        }
338                    }
339
340                    // Clip against the next view, this is just an approximation since we are
341                    // stacked and we can make assumptions about the visibility of the this
342                    // task relative to the ones in front of it.
343                    if (nextTv != null) {
344                        // We calculate the bottom clip independent of the footer (since we animate
345                        // that)
346                        int scaledMaxFooterHeight = (int) (tv.getMaxFooterHeight() * tv.getScaleX());
347                        tv.getHitRect(mTmpRect);
348                        nextTv.getHitRect(mTmpRect2);
349                        clipBottom = (mTmpRect.bottom - scaledMaxFooterHeight - mTmpRect2.top);
350                    }
351                }
352                tv.getViewBounds().setClipBottom(clipBottom);
353            }
354            if (getChildCount() > 0) {
355                // The front most task should never be clipped
356                TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
357                tv.getViewBounds().setClipBottom(0);
358            }
359        }
360    }
361
362    /** Enables/Disables clipping of the tasks in the stack. */
363    void setStackClippingEnabled(boolean stackClippingEnabled) {
364        if (!stackClippingEnabled) {
365            int childCount = getChildCount();
366            for (int i = 0; i < childCount; i++) {
367                TaskView tv = (TaskView) getChildAt(i);
368                tv.getViewBounds().setClipBottom(0);
369            }
370        }
371        mEnableStackClipping = stackClippingEnabled;
372    }
373
374    /** The stack insets to apply to the stack contents */
375    public void setStackInsetRect(Rect r) {
376        mTaskStackBounds.set(r);
377    }
378
379    /** Sets the current stack scroll */
380    public void setStackScroll(int value) {
381        mStackScroll = value;
382        mUIDozeTrigger.poke();
383        requestSynchronizeStackViewsWithModel();
384    }
385
386    /** Sets the current stack scroll without synchronizing the stack view with the model */
387    public void setStackScrollRaw(int value) {
388        mStackScroll = value;
389        mUIDozeTrigger.poke();
390    }
391    /** Sets the current stack scroll to the initial state when you first enter recents */
392    public void setStackScrollToInitialState() {
393        setStackScroll(getInitialStackScroll());
394    }
395    /** Computes the initial stack scroll for the stack. */
396    int getInitialStackScroll() {
397        if (mStack.getTaskCount() > 2) {
398            return Math.max(mMinScroll, mMaxScroll - (int) (mStackAlgorithm.mTaskRect.height() * (3f/4f)));
399        }
400        return mMaxScroll;
401    }
402
403    /** Gets the current stack scroll */
404    public int getStackScroll() {
405        return mStackScroll;
406    }
407
408    /** Animates the stack scroll into bounds */
409    ObjectAnimator animateBoundScroll() {
410        int curScroll = getStackScroll();
411        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
412        if (newScroll != curScroll) {
413            // Start a new scroll animation
414            animateScroll(curScroll, newScroll, null);
415        }
416        return mScrollAnimator;
417    }
418
419    /** Animates the stack scroll */
420    void animateScroll(int curScroll, int newScroll, final Runnable postRunnable) {
421        // Abort any current animations
422        abortScroller();
423        abortBoundScrollAnimation();
424
425        mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll);
426        mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll -
427                curScroll, 250));
428        mScrollAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
429        mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
430            @Override
431            public void onAnimationUpdate(ValueAnimator animation) {
432                setStackScroll((Integer) animation.getAnimatedValue());
433            }
434        });
435        mScrollAnimator.addListener(new AnimatorListenerAdapter() {
436            @Override
437            public void onAnimationEnd(Animator animation) {
438                if (postRunnable != null) {
439                    postRunnable.run();
440                }
441                mScrollAnimator.removeAllListeners();
442            }
443        });
444        mScrollAnimator.start();
445    }
446
447    /** Aborts any current stack scrolls */
448    void abortBoundScrollAnimation() {
449        if (mScrollAnimator != null) {
450            mScrollAnimator.cancel();
451        }
452    }
453
454    /** Aborts the scroller and any current fling */
455    void abortScroller() {
456        if (!mScroller.isFinished()) {
457            // Abort the scroller
458            mScroller.abortAnimation();
459        }
460    }
461
462    /** Bounds the current scroll if necessary */
463    public boolean boundScroll() {
464        int curScroll = getStackScroll();
465        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
466        if (newScroll != curScroll) {
467            setStackScroll(newScroll);
468            return true;
469        }
470        return false;
471    }
472
473    /**
474     * Bounds the current scroll if necessary, but does not synchronize the stack view with the
475     * model.
476     */
477    public boolean boundScrollRaw() {
478        int curScroll = getStackScroll();
479        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
480        if (newScroll != curScroll) {
481            setStackScrollRaw(newScroll);
482            return true;
483        }
484        return false;
485    }
486
487
488    /** Returns the amount that the scroll is out of bounds */
489    int getScrollAmountOutOfBounds(int scroll) {
490        if (scroll < mMinScroll) {
491            return mMinScroll - scroll;
492        } else if (scroll > mMaxScroll) {
493            return scroll - mMaxScroll;
494        }
495        return 0;
496    }
497
498    /** Returns whether the specified scroll is out of bounds */
499    boolean isScrollOutOfBounds() {
500        return getScrollAmountOutOfBounds(mStackScroll) != 0;
501    }
502
503    /** Updates the min and max virtual scroll bounds */
504    void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
505        // Compute the min and max scroll values
506        mStackAlgorithm.computeMinMaxScroll(mStack.getTasks());
507        mMinScroll = mStackAlgorithm.mMinScroll;
508        mMaxScroll = mStackAlgorithm.mMaxScroll;
509
510        // Debug logging
511        if (boundScrollToNewMinMax) {
512            boundScroll();
513        }
514    }
515
516    /** Animates a task view in this stack as it launches. */
517    public void animateOnLaunchingTask(TaskView tv, final Runnable r) {
518        // Hide each of the task bar dismiss buttons
519        int childCount = getChildCount();
520        for (int i = 0; i < childCount; i++) {
521            TaskView t = (TaskView) getChildAt(i);
522            if (t == tv) {
523                t.startLaunchTaskAnimation(r, true);
524            } else {
525                t.startLaunchTaskAnimation(null, false);
526            }
527        }
528    }
529
530    /** Focuses the task at the specified index in the stack */
531    void focusTask(int taskIndex, boolean scrollToNewPosition) {
532        if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
533            mFocusedTaskIndex = taskIndex;
534
535            // Focus the view if possible, otherwise, focus the view after we scroll into position
536            Task t = mStack.getTasks().get(taskIndex);
537            TaskView tv = getChildViewForTask(t);
538            Runnable postScrollRunnable = null;
539            if (tv != null) {
540                tv.setFocusedTask();
541            } else {
542                postScrollRunnable = new Runnable() {
543                    @Override
544                    public void run() {
545                        Task t = mStack.getTasks().get(mFocusedTaskIndex);
546                        TaskView tv = getChildViewForTask(t);
547                        if (tv != null) {
548                            tv.setFocusedTask();
549                        }
550                    }
551                };
552            }
553
554            if (scrollToNewPosition) {
555                // Scroll the view into position
556                int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll,
557                        mStackAlgorithm.getStackScrollForTaskIndex(t)));
558
559                animateScroll(getStackScroll(), newScroll, postScrollRunnable);
560            } else {
561                if (postScrollRunnable != null) {
562                    postScrollRunnable.run();
563                }
564            }
565        }
566    }
567
568    /** Focuses the next task in the stack */
569    void focusNextTask(boolean forward) {
570        // Find the next index to focus
571        int numTasks = mStack.getTaskCount();
572        if (mFocusedTaskIndex < 0) {
573            mFocusedTaskIndex = numTasks - 1;
574        }
575        if (0 <= mFocusedTaskIndex && mFocusedTaskIndex < numTasks) {
576            mFocusedTaskIndex = Math.max(0, Math.min(numTasks - 1,
577                    mFocusedTaskIndex + (forward ? -1 : 1)));
578        }
579        focusTask(mFocusedTaskIndex, true);
580    }
581
582    @Override
583    public boolean onInterceptTouchEvent(MotionEvent ev) {
584        return mTouchHandler.onInterceptTouchEvent(ev);
585    }
586
587    @Override
588    public boolean onTouchEvent(MotionEvent ev) {
589        return mTouchHandler.onTouchEvent(ev);
590    }
591
592    @Override
593    public void computeScroll() {
594        if (mScroller.computeScrollOffset()) {
595            setStackScroll(mScroller.getCurrY());
596            invalidate();
597        }
598    }
599
600    @Override
601    public void dispatchDraw(Canvas canvas) {
602        if (synchronizeStackViewsWithModel()) {
603            clipTaskViews();
604        }
605        super.dispatchDraw(canvas);
606    }
607
608    /** Computes the stack and task rects */
609    public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) {
610        // Compute the rects in the stack algorithm
611        mStackAlgorithm.computeRects(mStack.getTasks(), windowWidth, windowHeight, taskStackBounds);
612
613        // Update the scroll bounds
614        updateMinMaxScroll(false);
615    }
616
617    /**
618     * This is called with the full window width and height to allow stack view children to
619     * perform the full screen transition down.
620     */
621    @Override
622    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
623        int width = MeasureSpec.getSize(widthMeasureSpec);
624        int height = MeasureSpec.getSize(heightMeasureSpec);
625
626        // Compute our stack/task rects
627        Rect taskStackBounds = new Rect(mTaskStackBounds);
628        taskStackBounds.bottom -= mConfig.systemInsets.bottom;
629        computeRects(width, height, taskStackBounds);
630
631        // If this is the first layout, then scroll to the front of the stack and synchronize the
632        // stack views immediately to load all the views
633        if (mAwaitingFirstLayout) {
634            setStackScrollToInitialState();
635            requestSynchronizeStackViewsWithModel();
636            synchronizeStackViewsWithModel();
637
638            // Find the first task and mark it as full screen
639            if (mConfig.launchedFromAppWithScreenshot) {
640                int childCount = getChildCount();
641                for (int i = 0; i < childCount; i++) {
642                    TaskView tv = (TaskView) getChildAt(i);
643                    if (tv.getTask().isLaunchTarget) {
644                        tv.setIsFullScreen(true);
645                        break;
646                    }
647                }
648            }
649        }
650
651        // Measure each of the TaskViews
652        int childCount = getChildCount();
653        for (int i = 0; i < childCount; i++) {
654            TaskView tv = (TaskView) getChildAt(i);
655            if (tv.isFullScreenView()) {
656                tv.measure(widthMeasureSpec, heightMeasureSpec);
657            } else {
658                tv.measure(
659                    MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(),
660                            MeasureSpec.EXACTLY),
661                    MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height() +
662                            tv.getMaxFooterHeight(), MeasureSpec.EXACTLY));
663            }
664        }
665
666        setMeasuredDimension(width, height);
667    }
668
669    /**
670     * This is called with the size of the space not including the top or right insets, or the
671     * search bar height in portrait (but including the search bar width in landscape, since we want
672     * to draw under it.
673     */
674    @Override
675    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
676        // Layout each of the children
677        int childCount = getChildCount();
678        for (int i = 0; i < childCount; i++) {
679            TaskView tv = (TaskView) getChildAt(i);
680            if (tv.isFullScreenView()) {
681                tv.layout(left, top, left + tv.getMeasuredWidth(), top + tv.getMeasuredHeight());
682            } else {
683                tv.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mTaskRect.top,
684                        mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mTaskRect.bottom +
685                                tv.getMaxFooterHeight());
686            }
687        }
688
689        if (mAwaitingFirstLayout) {
690            mAwaitingFirstLayout = false;
691            onFirstLayout();
692        }
693    }
694
695    /** Handler for the first layout. */
696    void onFirstLayout() {
697        // Prepare the first view for its enter animation
698        int offscreenY = mStackAlgorithm.mViewRect.bottom -
699                (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mViewRect.top);
700        int childCount = getChildCount();
701        for (int i = childCount - 1; i >= 0; i--) {
702            TaskView tv = (TaskView) getChildAt(i);
703            tv.prepareEnterRecentsAnimation(tv.getTask().isLaunchTarget, offscreenY);
704        }
705
706        // If the enter animation started already and we haven't completed a layout yet, do the
707        // enter animation now
708        if (mStartEnterAnimationRequestedAfterLayout) {
709            startEnterRecentsAnimation(mStartEnterAnimationContext);
710            mStartEnterAnimationRequestedAfterLayout = false;
711            mStartEnterAnimationContext = null;
712        }
713
714        // Update the focused task index to be the next item to the top task
715        if (mConfig.launchedWithAltTab) {
716            // When alt-tabbing, we focus the next previous task
717            focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
718        }
719    }
720
721    /** Requests this task stacks to start it's enter-recents animation */
722    public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) {
723        // If we are still waiting to layout, then just defer until then
724        if (mAwaitingFirstLayout) {
725            mStartEnterAnimationRequestedAfterLayout = true;
726            mStartEnterAnimationContext = ctx;
727            return;
728        }
729
730        if (mStack.getTaskCount() > 0) {
731            // Animate all the task views into view
732            int childCount = getChildCount();
733            for (int i = childCount - 1; i >= 0; i--) {
734                TaskView tv = (TaskView) getChildAt(i);
735                Task task = tv.getTask();
736                ctx.currentTaskTransform = new TaskViewTransform();
737                ctx.currentStackViewIndex = i;
738                ctx.currentStackViewCount = childCount;
739                ctx.currentTaskRect = mStackAlgorithm.mTaskRect;
740                mStackAlgorithm.getStackTransform(task, getStackScroll(), ctx.currentTaskTransform);
741                tv.startEnterRecentsAnimation(ctx);
742            }
743
744            // Add a runnable to the post animation ref counter to clear all the views
745            ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
746                @Override
747                public void run() {
748                    // Start dozing
749                    mUIDozeTrigger.startDozing();
750                    // Request an update of the task views after the animation in to
751                    // relayout the fullscreen view back to its normal size
752                    if (mConfig.launchedFromAppWithScreenshot) {
753                        requestSynchronizeStackViewsWithModel();
754                    }
755                }
756            });
757        }
758    }
759
760    /** Requests this task stacks to start it's exit-recents animation. */
761    public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
762        // Animate all the task views into view
763        ctx.offscreenTranslationY = mStackAlgorithm.mViewRect.bottom -
764                (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mViewRect.top);
765        int childCount = getChildCount();
766        for (int i = 0; i < childCount; i++) {
767            TaskView tv = (TaskView) getChildAt(i);
768            tv.startExitToHomeAnimation(ctx);
769        }
770
771        // Add a runnable to the post animation ref counter to clear all the views
772        ctx.postAnimationTrigger.addLastDecrementRunnable(mReturnAllViewsToPoolRunnable);
773    }
774
775    @Override
776    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
777        super.onScrollChanged(l, t, oldl, oldt);
778        requestSynchronizeStackViewsWithModel();
779    }
780
781    public boolean isTransformedTouchPointInView(float x, float y, View child) {
782        return isTransformedTouchPointInView(x, y, child, null);
783    }
784
785    /** Pokes the dozer on user interaction. */
786    void onUserInteraction() {
787        // Poke the doze trigger if it is dozing
788        mUIDozeTrigger.poke();
789    }
790
791    /**** TaskStackCallbacks Implementation ****/
792
793    @Override
794    public void onStackTaskAdded(TaskStack stack, Task t) {
795        // Update the task offsets
796        mStackAlgorithm.updateTaskOffsets(mStack.getTasks());
797
798        requestSynchronizeStackViewsWithModel();
799    }
800
801    @Override
802    public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask) {
803        // Update the task offsets
804        mStackAlgorithm.updateTaskOffsets(mStack.getTasks());
805
806        // Remove the view associated with this task, we can't rely on updateTransforms
807        // to work here because the task is no longer in the list
808        TaskView tv = getChildViewForTask(removedTask);
809        if (tv != null) {
810            mViewPool.returnViewToPool(tv);
811        }
812
813        // Notify the callback that we've removed the task and it can clean up after it
814        mCb.onTaskViewDismissed(removedTask);
815
816        // Update the min/max scroll and animate other task views into their new positions
817        updateMinMaxScroll(true);
818        int movement = (int) mStackAlgorithm.getTaskOverlapHeight();
819        requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement));
820
821        // Update the new front most task
822        if (newFrontMostTask != null) {
823            TaskView frontTv = getChildViewForTask(newFrontMostTask);
824            if (frontTv != null) {
825                frontTv.onTaskBound(newFrontMostTask);
826            }
827        }
828
829        // If there are no remaining tasks, then either unfilter the current stack, or just close
830        // the activity if there are no filtered stacks
831        if (mStack.getTaskCount() == 0) {
832            boolean shouldFinishActivity = true;
833            if (mStack.hasFilteredTasks()) {
834                mStack.unfilterTasks();
835                shouldFinishActivity = (mStack.getTaskCount() == 0);
836            }
837            if (shouldFinishActivity) {
838                mCb.onAllTaskViewsDismissed();
839            }
840        }
841    }
842
843    @Override
844    public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
845                                Task filteredTask) {
846        // Stash the scroll and filtered task for us to restore to when we unfilter
847        mStashedScroll = getStackScroll();
848
849        // Calculate the current task transforms
850        ArrayList<TaskViewTransform> curTaskTransforms =
851                getStackTransforms(curTasks, getStackScroll(), null, true);
852
853        // Update the task offsets
854        mStackAlgorithm.updateTaskOffsets(mStack.getTasks());
855
856        // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
857        updateMinMaxScroll(false);
858        float overlapHeight = mStackAlgorithm.getTaskOverlapHeight();
859        setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
860        boundScrollRaw();
861
862        // Compute the transforms of the items in the new stack after setting the new scroll
863        final ArrayList<Task> tasks = mStack.getTasks();
864        final ArrayList<TaskViewTransform> taskTransforms =
865                getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);
866
867        // Animate
868        mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
869
870        // Notify any callbacks
871        mCb.onTaskStackFilterTriggered();
872    }
873
874    @Override
875    public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) {
876        // Calculate the current task transforms
877        final ArrayList<TaskViewTransform> curTaskTransforms =
878                getStackTransforms(curTasks, getStackScroll(), null, true);
879
880        // Update the task offsets
881        mStackAlgorithm.updateTaskOffsets(mStack.getTasks());
882
883        // Restore the stashed scroll
884        updateMinMaxScroll(false);
885        setStackScrollRaw(mStashedScroll);
886        boundScrollRaw();
887
888        // Compute the transforms of the items in the new stack after restoring the stashed scroll
889        final ArrayList<Task> tasks = mStack.getTasks();
890        final ArrayList<TaskViewTransform> taskTransforms =
891                getStackTransforms(tasks, getStackScroll(), null, true);
892
893        // Animate
894        mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
895
896        // Clear the saved vars
897        mStashedScroll = 0;
898
899        // Notify any callbacks
900        mCb.onTaskStackUnfilterTriggered();
901    }
902
903    /**** ViewPoolConsumer Implementation ****/
904
905    @Override
906    public TaskView createView(Context context) {
907        return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
908    }
909
910    @Override
911    public void prepareViewToEnterPool(TaskView tv) {
912        Task task = tv.getTask();
913
914        // Report that this tasks's data is no longer being used
915        RecentsTaskLoader.getInstance().unloadTaskData(task);
916
917        // Detach the view from the hierarchy
918        detachViewFromParent(tv);
919
920        // Reset the view properties
921        tv.resetViewProperties();
922    }
923
924    @Override
925    public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
926        // Rebind the task and request that this task's data be filled into the TaskView
927        tv.onTaskBound(task);
928        RecentsTaskLoader.getInstance().loadTaskData(task);
929
930        // Sanity check, the task view should always be clipping against the stack at this point,
931        // but just in case, re-enable it here
932        tv.setClipViewInStack(true);
933
934        // If the doze trigger has already fired, then update the state for this task view
935        if (mUIDozeTrigger.hasTriggered()) {
936            tv.setNoUserInteractionState();
937        }
938
939        // Find the index where this task should be placed in the stack
940        int insertIndex = -1;
941        int taskIndex = mStack.indexOfTask(task);
942        if (taskIndex != -1) {
943            int childCount = getChildCount();
944            for (int i = 0; i < childCount; i++) {
945                Task tvTask = ((TaskView) getChildAt(i)).getTask();
946                if (taskIndex < mStack.indexOfTask(tvTask)) {
947                    insertIndex = i;
948                    break;
949                }
950            }
951        }
952
953        // Add/attach the view to the hierarchy
954        if (isNewView) {
955            addView(tv, insertIndex);
956
957            // Set the callbacks and listeners for this new view
958            tv.setTouchEnabled(true);
959            tv.setCallbacks(this);
960        } else {
961            attachViewToParent(tv, insertIndex, tv.getLayoutParams());
962        }
963    }
964
965    @Override
966    public boolean hasPreferredData(TaskView tv, Task preferredData) {
967        return (tv.getTask() == preferredData);
968    }
969
970    /**** TaskViewCallbacks Implementation ****/
971
972    @Override
973    public void onTaskViewAppIconClicked(TaskView tv) {
974        if (Constants.DebugFlags.App.EnableTaskFiltering) {
975            if (mStack.hasFilteredTasks()) {
976                mStack.unfilterTasks();
977            } else {
978                mStack.filterTasks(tv.getTask());
979            }
980        }
981    }
982
983    @Override
984    public void onTaskViewAppInfoClicked(TaskView tv) {
985        if (mCb != null) {
986            mCb.onTaskViewAppInfoClicked(tv.getTask());
987        }
988    }
989
990    @Override
991    public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) {
992        // Cancel any doze triggers
993        mUIDozeTrigger.stopDozing();
994
995        if (mCb != null) {
996            mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask);
997        }
998    }
999
1000    @Override
1001    public void onTaskViewDismissed(TaskView tv) {
1002        Task task = tv.getTask();
1003        // Remove the task from the view
1004        mStack.removeTask(task);
1005    }
1006
1007    @Override
1008    public void onTaskViewClipStateChanged(TaskView tv) {
1009        invalidate(mStackAlgorithm.mStackRect);
1010    }
1011
1012    /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
1013
1014    @Override
1015    public void onComponentRemoved(HashSet<ComponentName> cns) {
1016        // For other tasks, just remove them directly if they no longer exist
1017        ArrayList<Task> tasks = mStack.getTasks();
1018        for (int i = tasks.size() - 1; i >= 0; i--) {
1019            final Task t = tasks.get(i);
1020            if (cns.contains(t.key.baseIntent.getComponent())) {
1021                TaskView tv = getChildViewForTask(t);
1022                if (tv != null) {
1023                    // For visible children, defer removing the task until after the animation
1024                    tv.startDeleteTaskAnimation(new Runnable() {
1025                        @Override
1026                        public void run() {
1027                            mStack.removeTask(t);
1028                        }
1029                    });
1030                } else {
1031                    // Otherwise, remove the task from the stack immediately
1032                    mStack.removeTask(t);
1033                }
1034            }
1035        }
1036    }
1037}