TaskStackView.java revision e0e45bc26d02e2c6ec505ea006e7487f3a5bddc5
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.recents.views;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.ValueAnimator;
23import android.app.Activity;
24import android.content.ComponentName;
25import android.content.Context;
26import android.graphics.Canvas;
27import android.graphics.Rect;
28import android.graphics.Region;
29import android.util.Pair;
30import android.view.LayoutInflater;
31import android.view.MotionEvent;
32import android.view.VelocityTracker;
33import android.view.View;
34import android.view.ViewConfiguration;
35import android.view.ViewParent;
36import android.widget.FrameLayout;
37import android.widget.OverScroller;
38import com.android.systemui.R;
39import com.android.systemui.recents.Console;
40import com.android.systemui.recents.Constants;
41import com.android.systemui.recents.DozeTrigger;
42import com.android.systemui.recents.RecentsConfiguration;
43import com.android.systemui.recents.RecentsPackageMonitor;
44import com.android.systemui.recents.RecentsTaskLoader;
45import com.android.systemui.recents.ReferenceCountedTrigger;
46import com.android.systemui.recents.Utilities;
47import com.android.systemui.recents.model.Task;
48import com.android.systemui.recents.model.TaskStack;
49
50import java.util.ArrayList;
51import java.util.HashMap;
52import java.util.Set;
53
54
55/* The visual representation of a task stack view */
56public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
57        TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>,
58        View.OnClickListener, RecentsPackageMonitor.PackageCallbacks {
59
60    /** The TaskView callbacks */
61    interface TaskStackViewCallbacks {
62        public void onTaskLaunched(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
63        public void onTaskAppInfoLaunched(Task t);
64        public void onTaskRemoved(Task t);
65    }
66
67    RecentsConfiguration mConfig;
68
69    TaskStack mStack;
70    TaskStackViewTouchHandler mTouchHandler;
71    TaskStackViewCallbacks mCb;
72    ViewPool<TaskView, Task> mViewPool;
73    ArrayList<TaskViewTransform> mTaskTransforms = new ArrayList<TaskViewTransform>();
74    DozeTrigger mDozeTrigger;
75
76    // The various rects that define the stack view
77    Rect mRect = new Rect();
78    Rect mStackRect = new Rect();
79    Rect mStackRectSansPeek = new Rect();
80    Rect mTaskRect = new Rect();
81
82    // The virtual stack scroll that we use for the card layout
83    int mStackScroll;
84    int mMinScroll;
85    int mMaxScroll;
86    int mStashedScroll;
87    int mFocusedTaskIndex = -1;
88    OverScroller mScroller;
89    ObjectAnimator mScrollAnimator;
90
91    // Optimizations
92    ReferenceCountedTrigger mHwLayersTrigger;
93    int mStackViewsAnimationDuration;
94    boolean mStackViewsDirty = true;
95    boolean mAwaitingFirstLayout = true;
96    boolean mStartEnterAnimationRequestedAfterLayout;
97    ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
98    int[] mTmpVisibleRange = new int[2];
99    Rect mTmpRect = new Rect();
100    Rect mTmpRect2 = new Rect();
101    LayoutInflater mInflater;
102
103    public TaskStackView(Context context, TaskStack stack) {
104        super(context);
105        mConfig = RecentsConfiguration.getInstance();
106        mStack = stack;
107        mStack.setCallbacks(this);
108        mScroller = new OverScroller(context);
109        mTouchHandler = new TaskStackViewTouchHandler(context, this);
110        mViewPool = new ViewPool<TaskView, Task>(context, this);
111        mInflater = LayoutInflater.from(context);
112        mDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() {
113            @Override
114            public void run() {
115                // Show the task bar dismiss buttons
116                int childCount = getChildCount();
117                for (int i = 0; i < childCount; i++) {
118                    TaskView tv = (TaskView) getChildAt(i);
119                    tv.startNoUserInteractionAnimation();
120                }
121            }
122        });
123        mHwLayersTrigger = new ReferenceCountedTrigger(getContext(), new Runnable() {
124            @Override
125            public void run() {
126                // Enable hw layers on each of the children
127                int childCount = getChildCount();
128                for (int i = 0; i < childCount; i++) {
129                    TaskView tv = (TaskView) getChildAt(i);
130                    tv.enableHwLayers();
131                }
132            }
133        }, new Runnable() {
134            @Override
135            public void run() {
136                // Disable hw layers on each of the children
137                int childCount = getChildCount();
138                for (int i = 0; i < childCount; i++) {
139                    TaskView tv = (TaskView) getChildAt(i);
140                    tv.disableHwLayers();
141                }
142            }
143        }, new Runnable() {
144            @Override
145            public void run() {
146                new Throwable("Invalid hw layers ref count").printStackTrace();
147                Console.logError(getContext(), "Invalid HW layers ref count");
148            }
149        });
150    }
151
152    /** Sets the callbacks */
153    void setCallbacks(TaskStackViewCallbacks cb) {
154        mCb = cb;
155    }
156
157    /** Requests that the views be synchronized with the model */
158    void requestSynchronizeStackViewsWithModel() {
159        requestSynchronizeStackViewsWithModel(0);
160    }
161    void requestSynchronizeStackViewsWithModel(int duration) {
162        if (Console.Enabled) {
163            Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
164                    "[TaskStackView|requestSynchronize]", "" + duration + "ms", Console.AnsiYellow);
165        }
166        if (!mStackViewsDirty) {
167            invalidate(mStackRect);
168        }
169        if (mAwaitingFirstLayout) {
170            // Skip the animation if we are awaiting first layout
171            mStackViewsAnimationDuration = 0;
172        } else {
173            mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration);
174        }
175        mStackViewsDirty = true;
176    }
177
178    /** Finds the child view given a specific task */
179    private TaskView getChildViewForTask(Task t) {
180        int childCount = getChildCount();
181        for (int i = 0; i < childCount; i++) {
182            TaskView tv = (TaskView) getChildAt(i);
183            if (tv.getTask() == t) {
184                return tv;
185            }
186        }
187        return null;
188    }
189
190    /** Update/get the transform */
191    public TaskViewTransform getStackTransform(int indexInStack, int stackScroll) {
192        TaskViewTransform transform = new TaskViewTransform();
193
194        // Return early if we have an invalid index
195        if (indexInStack < 0) return transform;
196
197        // Map the items to an continuous position relative to the specified scroll
198        int numPeekCards = Constants.Values.TaskStackView.StackPeekNumCards;
199        float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
200        float peekHeight = Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height();
201        float t = ((indexInStack * overlapHeight) - stackScroll) / overlapHeight;
202        float boundedT = Math.max(t, -(numPeekCards + 1));
203
204        // Set the scale relative to its position
205        int numFrontScaledCards = 3;
206        float minScale = Constants.Values.TaskStackView.StackPeekMinScale;
207        float scaleRange = 1f - minScale;
208        float scaleInc = scaleRange / (numPeekCards + numFrontScaledCards);
209        float scale = Math.max(minScale, Math.min(1f, minScale +
210            ((boundedT + (numPeekCards + 1)) * scaleInc)));
211        float scaleYOffset = ((1f - scale) * mTaskRect.height()) / 2;
212        transform.scale = scale;
213
214        // Set the y translation
215        if (boundedT < 0f) {
216            transform.translationY = (int) ((Math.max(-numPeekCards, boundedT) /
217                    numPeekCards) * peekHeight - scaleYOffset);
218        } else {
219            transform.translationY = (int) (boundedT * overlapHeight - scaleYOffset);
220        }
221
222        // Set the z translation
223        int minZ = mConfig.taskViewTranslationZMinPx;
224        int incZ = mConfig.taskViewTranslationZIncrementPx;
225        transform.translationZ = (int) Math.max(minZ, minZ + ((boundedT + numPeekCards) * incZ));
226
227        // Set the alphas
228        transform.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f;
229
230        // Update the rect and visibility
231        transform.rect.set(mTaskRect);
232        if (t < -(numPeekCards + 1)) {
233            transform.visible = false;
234        } else {
235            transform.rect.offset(0, transform.translationY);
236            Utilities.scaleRectAboutCenter(transform.rect, transform.scale);
237            transform.visible = Rect.intersects(mRect, transform.rect);
238        }
239        transform.t = t;
240        return transform;
241    }
242
243    /**
244     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
245     */
246    private void updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
247                                       ArrayList<Task> tasks,
248                                       int stackScroll,
249                                       int[] visibleRangeOut,
250                                       boolean boundTranslationsToRect) {
251        // XXX: Optimization: Use binary search to find the visible range
252
253        int taskCount = tasks.size();
254        int firstVisibleIndex = -1;
255        int lastVisibleIndex = -1;
256        taskTransforms.clear();
257        taskTransforms.ensureCapacity(taskCount);
258        for (int i = 0; i < taskCount; i++) {
259            TaskViewTransform transform = getStackTransform(i, stackScroll);
260            taskTransforms.add(transform);
261            if (transform.visible) {
262                if (firstVisibleIndex < 0) {
263                    firstVisibleIndex = i;
264                }
265                lastVisibleIndex = i;
266            }
267
268            if (boundTranslationsToRect) {
269                transform.translationY = Math.min(transform.translationY, mRect.bottom);
270            }
271        }
272        if (visibleRangeOut != null) {
273            visibleRangeOut[0] = firstVisibleIndex;
274            visibleRangeOut[1] = lastVisibleIndex;
275        }
276    }
277
278    /**
279     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. This
280     * call is less optimal than calling updateStackTransforms directly.
281     */
282    private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks,
283                                                            int stackScroll,
284                                                            int[] visibleRangeOut,
285                                                            boolean boundTranslationsToRect) {
286        ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>();
287        updateStackTransforms(taskTransforms, tasks, stackScroll, visibleRangeOut,
288                boundTranslationsToRect);
289        return taskTransforms;
290    }
291
292    /** Synchronizes the views with the model */
293    void synchronizeStackViewsWithModel() {
294        if (Console.Enabled) {
295            Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
296                    "[TaskStackView|synchronizeViewsWithModel]",
297                    "mStackViewsDirty: " + mStackViewsDirty, Console.AnsiYellow);
298        }
299        if (mStackViewsDirty) {
300            // XXX: Consider using TaskViewTransform pool to prevent allocations
301            // XXX: Iterate children views, update transforms and remove all that are not visible
302            //      For all remaining tasks, update transforms and if visible add the view
303
304            // Get all the task transforms
305            int[] visibleRange = mTmpVisibleRange;
306            int stackScroll = getStackScroll();
307            ArrayList<Task> tasks = mStack.getTasks();
308            updateStackTransforms(mTaskTransforms, tasks, stackScroll, visibleRange, false);
309
310            // Update the visible state of all the tasks
311            int taskCount = tasks.size();
312            for (int i = 0; i < taskCount; i++) {
313                Task task = tasks.get(i);
314                TaskViewTransform transform = mTaskTransforms.get(i);
315                TaskView tv = getChildViewForTask(task);
316
317                if (transform.visible) {
318                    if (tv == null) {
319                        tv = mViewPool.pickUpViewFromPool(task, task);
320                        // When we are picking up a new view from the view pool, prepare it for any
321                        // following animation by putting it in a reasonable place
322                        if (mStackViewsAnimationDuration > 0 && i != 0) {
323                            int fromIndex = (transform.t < 0) ? (visibleRange[0] - 1) :
324                                    (visibleRange[1] + 1);
325                            tv.updateViewPropertiesToTaskTransform(
326                                    getStackTransform(fromIndex, stackScroll), 0);
327                        }
328                    }
329                } else {
330                    if (tv != null) {
331                        mViewPool.returnViewToPool(tv);
332                    }
333                }
334            }
335
336            // Update all the remaining view children
337            // NOTE: We have to iterate in reverse where because we are removing views directly
338            int childCount = getChildCount();
339            for (int i = childCount - 1; i >= 0; i--) {
340                TaskView tv = (TaskView) getChildAt(i);
341                Task task = tv.getTask();
342                int taskIndex = mStack.indexOfTask(task);
343                if (taskIndex < 0 || !mTaskTransforms.get(taskIndex).visible) {
344                    mViewPool.returnViewToPool(tv);
345                } else {
346                    tv.updateViewPropertiesToTaskTransform(mTaskTransforms.get(taskIndex),
347                            mStackViewsAnimationDuration);
348                }
349            }
350
351            if (Console.Enabled) {
352                Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
353                        "  [TaskStackView|viewChildren]", "" + getChildCount());
354            }
355
356            mStackViewsAnimationDuration = 0;
357            mStackViewsDirty = false;
358        }
359    }
360
361    /** Sets the current stack scroll */
362    public void setStackScroll(int value) {
363        mStackScroll = value;
364        requestSynchronizeStackViewsWithModel();
365    }
366    /** Sets the current stack scroll without synchronizing the stack view with the model */
367    public void setStackScrollRaw(int value) {
368        mStackScroll = value;
369    }
370    /** Sets the current stack scroll to the initial state when you first enter recents */
371    public void setStackScrollToInitialState() {
372        if (mStack.getTaskCount() > 2) {
373            int initialScroll = mMaxScroll - mTaskRect.height() / 2;
374            setStackScroll(initialScroll);
375        } else {
376            setStackScroll(mMaxScroll);
377        }
378    }
379
380    /**
381     * Returns the scroll to such that the task transform at that index will have t=0. (If the scroll
382     * is not bounded)
383     */
384    int getStackScrollForTaskIndex(int i) {
385        int taskHeight = mTaskRect.height();
386        return (int) (i * Constants.Values.TaskStackView.StackOverlapPct * taskHeight);
387    }
388
389    /** Gets the current stack scroll */
390    public int getStackScroll() {
391        return mStackScroll;
392    }
393
394    /** Animates the stack scroll into bounds */
395    ObjectAnimator animateBoundScroll() {
396        int curScroll = getStackScroll();
397        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
398        if (newScroll != curScroll) {
399            // Enable hw layers on the stack
400            addHwLayersRefCount("animateBoundScroll");
401
402            // Start a new scroll animation
403            animateScroll(curScroll, newScroll, new Runnable() {
404                @Override
405                public void run() {
406                    // Disable hw layers on the stack
407                    decHwLayersRefCount("animateBoundScroll");
408                }
409            });
410        }
411        return mScrollAnimator;
412    }
413
414    /** Animates the stack scroll */
415    void animateScroll(int curScroll, int newScroll, final Runnable postRunnable) {
416        // Abort any current animations
417        abortScroller();
418        abortBoundScrollAnimation();
419
420        mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll);
421        mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll -
422                curScroll, 250));
423        mScrollAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
424        mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
425            @Override
426            public void onAnimationUpdate(ValueAnimator animation) {
427                setStackScroll((Integer) animation.getAnimatedValue());
428            }
429        });
430        mScrollAnimator.addListener(new AnimatorListenerAdapter() {
431            @Override
432            public void onAnimationEnd(Animator animation) {
433                if (postRunnable != null) {
434                    postRunnable.run();
435                }
436                mScrollAnimator.removeAllListeners();
437            }
438        });
439        mScrollAnimator.start();
440    }
441
442    /** Aborts any current stack scrolls */
443    void abortBoundScrollAnimation() {
444        if (mScrollAnimator != null) {
445            mScrollAnimator.cancel();
446        }
447    }
448
449    /** Aborts the scroller and any current fling */
450    void abortScroller() {
451        if (!mScroller.isFinished()) {
452            // Abort the scroller
453            mScroller.abortAnimation();
454            // And disable hw layers on the stack
455            decHwLayersRefCount("flingScroll");
456        }
457    }
458
459    /** Bounds the current scroll if necessary */
460    public boolean boundScroll() {
461        int curScroll = getStackScroll();
462        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
463        if (newScroll != curScroll) {
464            setStackScroll(newScroll);
465            return true;
466        }
467        return false;
468    }
469
470    /**
471     * Bounds the current scroll if necessary, but does not synchronize the stack view with the
472     * model.
473     */
474    public boolean boundScrollRaw() {
475        int curScroll = getStackScroll();
476        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
477        if (newScroll != curScroll) {
478            setStackScrollRaw(newScroll);
479            return true;
480        }
481        return false;
482    }
483
484
485    /** Returns the amount that the scroll is out of bounds */
486    int getScrollAmountOutOfBounds(int scroll) {
487        if (scroll < mMinScroll) {
488            return mMinScroll - scroll;
489        } else if (scroll > mMaxScroll) {
490            return scroll - mMaxScroll;
491        }
492        return 0;
493    }
494
495    /** Returns whether the specified scroll is out of bounds */
496    boolean isScrollOutOfBounds() {
497        return getScrollAmountOutOfBounds(getStackScroll()) != 0;
498    }
499
500    /** Returns whether the task view is in the stack bounds or not */
501    boolean isTaskInStackBounds(TaskView tv) {
502        Rect r = new Rect();
503        tv.getHitRect(r);
504        return r.bottom <= mRect.bottom;
505    }
506
507    /** Updates the min and max virtual scroll bounds */
508    void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
509        // Compute the min and max scroll values
510        int numTasks = Math.max(1, mStack.getTaskCount());
511        int taskHeight = mTaskRect.height();
512        int stackHeight = mStackRectSansPeek.height();
513        int maxScrollHeight = taskHeight + (int) ((numTasks - 1) *
514                Constants.Values.TaskStackView.StackOverlapPct * taskHeight);
515
516        if (numTasks <= 1) {
517            // If there is only one task, then center the task in the stack rect (sans peek)
518            mMinScroll = mMaxScroll = -(stackHeight - taskHeight) / 2;
519        } else {
520            mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight;
521            mMaxScroll = maxScrollHeight - stackHeight;
522        }
523
524        // Debug logging
525        if (Constants.Log.UI.MeasureAndLayout) {
526            Console.log("  [TaskStack|minScroll] " + mMinScroll);
527            Console.log("  [TaskStack|maxScroll] " + mMaxScroll);
528        }
529
530        if (boundScrollToNewMinMax) {
531            boundScroll();
532        }
533    }
534
535    /** Animates a task view in this stack as it launches. */
536    public void animateOnLaunchingTask(TaskView tv, final Runnable r) {
537        // Hide each of the task bar dismiss buttons
538        int childCount = getChildCount();
539        for (int i = 0; i < childCount; i++) {
540            TaskView t = (TaskView) getChildAt(i);
541            if (t == tv) {
542                t.startLaunchTaskAnimation(r, true);
543            } else {
544                t.startLaunchTaskAnimation(null, false);
545            }
546        }
547    }
548
549    /** Focuses the task at the specified index in the stack */
550    void focusTask(int taskIndex, boolean scrollToNewPosition) {
551        if (Console.Enabled) {
552            Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]", "" + taskIndex);
553        }
554        if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
555            mFocusedTaskIndex = taskIndex;
556
557            // Focus the view if possible, otherwise, focus the view after we scroll into position
558            Task t = mStack.getTasks().get(taskIndex);
559            TaskView tv = getChildViewForTask(t);
560            Runnable postScrollRunnable = null;
561            if (tv != null) {
562                tv.setFocusedTask();
563                if (Console.Enabled) {
564                    Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]", "Requesting focus");
565                }
566            } else {
567                postScrollRunnable = new Runnable() {
568                    @Override
569                    public void run() {
570                        Task t = mStack.getTasks().get(mFocusedTaskIndex);
571                        TaskView tv = getChildViewForTask(t);
572                        if (tv != null) {
573                            tv.setFocusedTask();
574                            if (Console.Enabled) {
575                                Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]",
576                                        "Requesting focus after scroll animation");
577                            }
578                        }
579                    }
580                };
581            }
582
583            if (scrollToNewPosition) {
584                // Scroll the view into position
585                int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll,
586                        getStackScrollForTaskIndex(taskIndex)));
587
588                animateScroll(getStackScroll(), newScroll, postScrollRunnable);
589            } else {
590                if (postScrollRunnable != null) {
591                    postScrollRunnable.run();
592                }
593            }
594        }
595    }
596
597    /** Focuses the next task in the stack */
598    void focusNextTask(boolean forward) {
599        if (Console.Enabled) {
600            Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusNextTask]", "" +
601                    mFocusedTaskIndex);
602        }
603
604        // Find the next index to focus
605        int numTasks = mStack.getTaskCount();
606        if (mFocusedTaskIndex < 0) {
607            mFocusedTaskIndex = numTasks - 1;
608        }
609        if (0 <= mFocusedTaskIndex && mFocusedTaskIndex < numTasks) {
610            mFocusedTaskIndex = Math.max(0, Math.min(numTasks - 1,
611                    mFocusedTaskIndex + (forward ? -1 : 1)));
612        }
613        focusTask(mFocusedTaskIndex, true);
614    }
615
616    /** Enables the hw layers and increments the hw layer requirement ref count */
617    void addHwLayersRefCount(String reason) {
618        if (Console.Enabled) {
619            int refCount = mHwLayersTrigger.getCount();
620            Console.log(Constants.Log.UI.HwLayers,
621                    "[TaskStackView|addHwLayersRefCount] refCount: " +
622                            refCount + "->" + (refCount + 1) + " " + reason);
623        }
624        mHwLayersTrigger.increment();
625    }
626
627    /** Decrements the hw layer requirement ref count and disables the hw layers when we don't
628        need them anymore. */
629    void decHwLayersRefCount(String reason) {
630        if (Console.Enabled) {
631            int refCount = mHwLayersTrigger.getCount();
632            Console.log(Constants.Log.UI.HwLayers,
633                    "[TaskStackView|decHwLayersRefCount] refCount: " +
634                            refCount + "->" + (refCount - 1) + " " + reason);
635        }
636        mHwLayersTrigger.decrement();
637    }
638
639    @Override
640    public void computeScroll() {
641        if (mScroller.computeScrollOffset()) {
642            setStackScroll(mScroller.getCurrY());
643            invalidate(mStackRect);
644
645            // If we just finished scrolling, then disable the hw layers
646            if (mScroller.isFinished()) {
647                decHwLayersRefCount("finishedFlingScroll");
648            }
649        }
650    }
651
652    @Override
653    public boolean onInterceptTouchEvent(MotionEvent ev) {
654        return mTouchHandler.onInterceptTouchEvent(ev);
655    }
656
657    @Override
658    public boolean onTouchEvent(MotionEvent ev) {
659        return mTouchHandler.onTouchEvent(ev);
660    }
661
662    @Override
663    public void dispatchDraw(Canvas canvas) {
664        if (Console.Enabled) {
665            Console.log(Constants.Log.UI.Draw, "[TaskStackView|dispatchDraw]", "",
666                    Console.AnsiPurple);
667        }
668        synchronizeStackViewsWithModel();
669        super.dispatchDraw(canvas);
670    }
671
672    @Override
673    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
674        if (Constants.DebugFlags.App.EnableTaskStackClipping) {
675            TaskView tv = (TaskView) child;
676            TaskView nextTv = null;
677            TaskView tmpTv = null;
678            if (tv.shouldClipViewInStack()) {
679                int curIndex = indexOfChild(tv);
680
681                // Find the next view to clip against
682                while (nextTv == null && curIndex < getChildCount()) {
683                    tmpTv = (TaskView) getChildAt(++curIndex);
684                    if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
685                        nextTv = tmpTv;
686                    }
687                }
688
689                // Clip against the next view (if we aren't animating its alpha)
690                if (nextTv != null) {
691                    Rect curRect = tv.getClippingRect(mTmpRect);
692                    Rect nextRect = nextTv.getClippingRect(mTmpRect2);
693                    // The hit rects are relative to the task view, which needs to be offset by
694                    // the system bar height
695                    curRect.offset(0, mConfig.systemInsets.top);
696                    nextRect.offset(0, mConfig.systemInsets.top);
697                    // Compute the clip region
698                    Region clipRegion = new Region();
699                    clipRegion.op(curRect, Region.Op.UNION);
700                    clipRegion.op(nextRect, Region.Op.DIFFERENCE);
701                    // Clip the canvas
702                    int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
703                    canvas.clipRegion(clipRegion);
704                    boolean invalidate = super.drawChild(canvas, child, drawingTime);
705                    canvas.restoreToCount(saveCount);
706                    return invalidate;
707                }
708            }
709        }
710        return super.drawChild(canvas, child, drawingTime);
711    }
712
713    /** Computes the stack and task rects */
714    public void computeRects(int width, int height, int insetLeft, int insetBottom) {
715        // Note: We let the stack view be the full height because we want the cards to go under the
716        //       navigation bar if possible.  However, the stack rects which we use to calculate
717        //       max scroll, etc. need to take the nav bar into account
718
719        // Compute the stack rects
720        mRect.set(0, 0, width, height);
721        mStackRect.set(mRect);
722        mStackRect.left += insetLeft;
723        mStackRect.bottom -= insetBottom;
724
725        int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width());
726        int heightPadding = mConfig.taskStackTopPaddingPx;
727        if (Constants.DebugFlags.App.EnableSearchLayout) {
728            mStackRect.top += heightPadding;
729            mStackRect.left += widthPadding;
730            mStackRect.right -= widthPadding;
731            mStackRect.bottom -= heightPadding;
732        } else {
733            mStackRect.inset(widthPadding, heightPadding);
734        }
735        mStackRectSansPeek.set(mStackRect);
736        mStackRectSansPeek.top += Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height();
737
738        // Compute the task rect
739        int size = mStackRect.width();
740        int left = mStackRect.left + (mStackRect.width() - size) / 2;
741        mTaskRect.set(left, mStackRectSansPeek.top,
742                left + size, mStackRectSansPeek.top + size);
743
744        // Update the scroll bounds
745        updateMinMaxScroll(false);
746    }
747
748    /**
749     * This is called with the size of the space not including the top or right insets, or the
750     * search bar height in portrait (but including the search bar width in landscape, since we want
751     * to draw under it.
752     */
753    @Override
754    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
755        int width = MeasureSpec.getSize(widthMeasureSpec);
756        int height = MeasureSpec.getSize(heightMeasureSpec);
757        if (Console.Enabled) {
758            Console.log(Constants.Log.UI.MeasureAndLayout, "[TaskStackView|measure]",
759                    "width: " + width + " height: " + height +
760                            " awaitingFirstLayout: " + mAwaitingFirstLayout, Console.AnsiGreen);
761        }
762
763        // Compute our stack/task rects
764        Rect taskStackBounds = new Rect();
765        mConfig.getTaskStackBounds(width, height, taskStackBounds);
766        computeRects(width, height, taskStackBounds.left, mConfig.systemInsets.bottom);
767
768        // Debug logging
769        if (Constants.Log.UI.MeasureAndLayout) {
770            Console.log("  [TaskStack|fullRect] " + mRect);
771            Console.log("  [TaskStack|stackRect] " + mStackRect);
772            Console.log("  [TaskStack|stackRectSansPeek] " + mStackRectSansPeek);
773            Console.log("  [TaskStack|taskRect] " + mTaskRect);
774        }
775
776        // If this is the first layout, then scroll to the front of the stack and synchronize the
777        // stack views immediately
778        if (mAwaitingFirstLayout) {
779            setStackScrollToInitialState();
780            requestSynchronizeStackViewsWithModel();
781            synchronizeStackViewsWithModel();
782        }
783
784        // Measure each of the children
785        int childCount = getChildCount();
786        for (int i = 0; i < childCount; i++) {
787            TaskView t = (TaskView) getChildAt(i);
788            t.measure(MeasureSpec.makeMeasureSpec(mTaskRect.width(), MeasureSpec.EXACTLY),
789                    MeasureSpec.makeMeasureSpec(mTaskRect.height(), MeasureSpec.EXACTLY));
790        }
791
792        setMeasuredDimension(width, height);
793    }
794
795    /**
796     * This is called with the size of the space not including the top or right insets, or the
797     * search bar height in portrait (but including the search bar width in landscape, since we want
798     * to draw under it.
799     */
800    @Override
801    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
802        if (Console.Enabled) {
803            Console.log(Constants.Log.UI.MeasureAndLayout, "[TaskStackView|layout]",
804                    "" + new Rect(left, top, right, bottom), Console.AnsiGreen);
805        }
806
807        // Debug logging
808        if (Constants.Log.UI.MeasureAndLayout) {
809            Console.log("  [TaskStack|fullRect] " + mRect);
810            Console.log("  [TaskStack|stackRect] " + mStackRect);
811            Console.log("  [TaskStack|stackRectSansPeek] " + mStackRectSansPeek);
812            Console.log("  [TaskStack|taskRect] " + mTaskRect);
813        }
814
815        // Layout each of the children
816        int childCount = getChildCount();
817        for (int i = 0; i < childCount; i++) {
818            TaskView t = (TaskView) getChildAt(i);
819            t.layout(mTaskRect.left, mStackRectSansPeek.top,
820                    mTaskRect.right, mStackRectSansPeek.top + mTaskRect.height());
821        }
822
823        if (mAwaitingFirstLayout) {
824            // Mark that we have completely the first layout
825            mAwaitingFirstLayout = false;
826
827            // Start dozing
828            mDozeTrigger.startDozing();
829
830            // Prepare the first view for its enter animation
831            int offsetTopAlign = -mTaskRect.top;
832            int offscreenY = mRect.bottom - (mTaskRect.top - mRect.top);
833            for (int i = childCount - 1; i >= 0; i--) {
834                TaskView tv = (TaskView) getChildAt(i);
835                tv.prepareEnterRecentsAnimation((i == (getChildCount() - 1)), offsetTopAlign,
836                        offscreenY, mTaskRect);
837            }
838
839            // If the enter animation started already and we haven't completed a layout yet, do the
840            // enter animation now
841            if (mStartEnterAnimationRequestedAfterLayout) {
842                startEnterRecentsAnimation(mStartEnterAnimationContext);
843                mStartEnterAnimationRequestedAfterLayout = false;
844                mStartEnterAnimationContext = null;
845            }
846
847            // Update the focused task index to be the next item to the top task
848            if (mConfig.launchedWithAltTab) {
849                focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
850            }
851        }
852    }
853
854    /** Requests this task stacks to start it's enter-recents animation */
855    public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) {
856        // If we are still waiting to layout, then just defer until then
857        if (mAwaitingFirstLayout) {
858            mStartEnterAnimationRequestedAfterLayout = true;
859            mStartEnterAnimationContext = ctx;
860            return;
861        }
862
863        // Animate all the task views into view
864        ctx.taskRect = mTaskRect;
865        ctx.stackRectSansPeek = mStackRectSansPeek;
866        int childCount = getChildCount();
867        for (int i = childCount - 1; i >= 0; i--) {
868            TaskView tv = (TaskView) getChildAt(i);
869            TaskViewTransform transform = getStackTransform(mStack.indexOfTask(tv.getTask()),
870                    getStackScroll());
871            ctx.stackViewIndex = i;
872            ctx.stackViewCount = childCount;
873            ctx.isFrontMost = (i == (getChildCount() - 1));
874            ctx.transform = transform;
875            tv.startEnterRecentsAnimation(ctx);
876        }
877    }
878
879    /** Requests this task stacks to start it's exit-recents animation. */
880    public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
881        // Animate all the task views into view
882        ctx.offscreenTranslationY = mRect.bottom - (mTaskRect.top - mRect.top);
883        int childCount = getChildCount();
884        for (int i = 0; i < childCount; i++) {
885            TaskView tv = (TaskView) getChildAt(i);
886            tv.startExitToHomeAnimation(ctx);
887        }
888    }
889
890    @Override
891    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
892        super.onScrollChanged(l, t, oldl, oldt);
893        requestSynchronizeStackViewsWithModel();
894    }
895
896    public boolean isTransformedTouchPointInView(float x, float y, View child) {
897        return isTransformedTouchPointInView(x, y, child, null);
898    }
899
900    /** Pokes the dozer on user interaction. */
901    void onUserInteraction() {
902        // If the dozer is not running, then either we have not yet laid out, or it has already
903        // fallen asleep, so just let it rest.
904        if (mDozeTrigger.isDozing()) {
905            mDozeTrigger.poke();
906        }
907    }
908
909    /**** TaskStackCallbacks Implementation ****/
910
911    @Override
912    public void onStackTaskAdded(TaskStack stack, Task t) {
913        requestSynchronizeStackViewsWithModel();
914    }
915
916    @Override
917    public void onStackTaskRemoved(TaskStack stack, Task t) {
918        // Remove the view associated with this task, we can't rely on updateTransforms
919        // to work here because the task is no longer in the list
920        int childCount = getChildCount();
921        for (int i = childCount - 1; i >= 0; i--) {
922            TaskView tv = (TaskView) getChildAt(i);
923            if (tv.getTask() == t) {
924                mViewPool.returnViewToPool(tv);
925                break;
926            }
927        }
928
929        // Update the min/max scroll and animate other task views into their new positions
930        updateMinMaxScroll(true);
931        int movement = (int) (Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height());
932        requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement));
933
934        // If there are no remaining tasks, then either unfilter the current stack, or just close
935        // the activity if there are no filtered stacks
936        if (mStack.getTaskCount() == 0) {
937            boolean shouldFinishActivity = true;
938            if (mStack.hasFilteredTasks()) {
939                mStack.unfilterTasks();
940                shouldFinishActivity = (mStack.getTaskCount() == 0);
941            }
942            if (shouldFinishActivity) {
943                Activity activity = (Activity) getContext();
944                activity.finish();
945            }
946        }
947    }
948
949    /**
950     * Creates the animations for all the children views that need to be removed or to move views
951     * to their un/filtered position when we are un/filtering a stack, and returns the duration
952     * for these animations.
953     */
954    int getExitTransformsForFilterAnimation(ArrayList<Task> curTasks,
955                        ArrayList<TaskViewTransform> curTaskTransforms,
956                        ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms,
957                        HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut,
958                        ArrayList<TaskView> childrenToRemoveOut) {
959        // Animate all of the existing views out of view (if they are not in the visible range in
960        // the new stack) or to their final positions in the new stack
961        int offset = 0;
962        int movement = 0;
963        int childCount = getChildCount();
964        for (int i = 0; i < childCount; i++) {
965            TaskView tv = (TaskView) getChildAt(i);
966            Task task = tv.getTask();
967            int taskIndex = tasks.indexOf(task);
968            TaskViewTransform toTransform;
969
970            // If the view is no longer visible, then we should just animate it out
971            boolean willBeInvisible = taskIndex < 0 || !taskTransforms.get(taskIndex).visible;
972            if (willBeInvisible) {
973                if (taskIndex < 0) {
974                    toTransform = curTaskTransforms.get(curTasks.indexOf(task));
975                } else {
976                    toTransform = new TaskViewTransform(taskTransforms.get(taskIndex));
977                }
978                tv.prepareTaskTransformForFilterTaskVisible(toTransform);
979                childrenToRemoveOut.add(tv);
980            } else {
981                toTransform = taskTransforms.get(taskIndex);
982                // Use the movement of the visible views to calculate the duration of the animation
983                movement = Math.max(movement, Math.abs(toTransform.translationY -
984                        (int) tv.getTranslationY()));
985            }
986
987            int startDelay = offset *
988                    Constants.Values.TaskStackView.FilterStartDelay;
989            childViewTransformsOut.put(tv, new Pair(startDelay, toTransform));
990            offset++;
991        }
992        return mConfig.filteringCurrentViewsAnimDuration;
993    }
994
995    /**
996     * Creates the animations for all the children views that need to be animated in when we are
997     * un/filtering a stack, and returns the duration for these animations.
998     */
999    int getEnterTransformsForFilterAnimation(ArrayList<Task> tasks,
1000                         ArrayList<TaskViewTransform> taskTransforms,
1001                         HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut) {
1002        int offset = 0;
1003        int movement = 0;
1004        int taskCount = tasks.size();
1005        for (int i = taskCount - 1; i >= 0; i--) {
1006            Task task = tasks.get(i);
1007            TaskViewTransform toTransform = taskTransforms.get(i);
1008            if (toTransform.visible) {
1009                TaskView tv = getChildViewForTask(task);
1010                if (tv == null) {
1011                    // For views that are not already visible, animate them in
1012                    tv = mViewPool.pickUpViewFromPool(task, task);
1013
1014                    // Compose a new transform to fade and slide the new task in
1015                    TaskViewTransform fromTransform = new TaskViewTransform(toTransform);
1016                    tv.prepareTaskTransformForFilterTaskHidden(fromTransform);
1017                    tv.updateViewPropertiesToTaskTransform(fromTransform, 0);
1018
1019                    int startDelay = offset *
1020                            Constants.Values.TaskStackView.FilterStartDelay;
1021                    childViewTransformsOut.put(tv, new Pair(startDelay, toTransform));
1022
1023                    // Use the movement of the new views to calculate the duration of the animation
1024                    movement = Math.max(movement,
1025                            Math.abs(toTransform.translationY - fromTransform.translationY));
1026                    offset++;
1027                }
1028            }
1029        }
1030        return mConfig.filteringNewViewsAnimDuration;
1031    }
1032
1033    /** Orchestrates the animations of the current child views and any new views. */
1034    void doFilteringAnimation(ArrayList<Task> curTasks,
1035                              ArrayList<TaskViewTransform> curTaskTransforms,
1036                              final ArrayList<Task> tasks,
1037                              final ArrayList<TaskViewTransform> taskTransforms) {
1038        // Calculate the transforms to animate out all the existing views if they are not in the
1039        // new visible range (or to their final positions in the stack if they are)
1040        final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>();
1041        final HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransforms =
1042                new HashMap<TaskView, Pair<Integer, TaskViewTransform>>();
1043        int duration = getExitTransformsForFilterAnimation(curTasks, curTaskTransforms, tasks,
1044                taskTransforms, childViewTransforms, childrenToRemove);
1045
1046        // If all the current views are in the visible range of the new stack, then don't wait for
1047        // views to animate out and animate all the new views into their place
1048        final boolean unifyNewViewAnimation = childrenToRemove.isEmpty();
1049        if (unifyNewViewAnimation) {
1050            int inDuration = getEnterTransformsForFilterAnimation(tasks, taskTransforms,
1051                    childViewTransforms);
1052            duration = Math.max(duration, inDuration);
1053        }
1054
1055        // Animate all the views to their final transforms
1056        for (final TaskView tv : childViewTransforms.keySet()) {
1057            Pair<Integer, TaskViewTransform> t = childViewTransforms.get(tv);
1058            tv.animate().cancel();
1059            tv.animate()
1060                    .setStartDelay(t.first)
1061                    .withEndAction(new Runnable() {
1062                        @Override
1063                        public void run() {
1064                            childViewTransforms.remove(tv);
1065                            if (childViewTransforms.isEmpty()) {
1066                                // Return all the removed children to the view pool
1067                                for (TaskView tv : childrenToRemove) {
1068                                    mViewPool.returnViewToPool(tv);
1069                                }
1070
1071                                if (!unifyNewViewAnimation) {
1072                                    // For views that are not already visible, animate them in
1073                                    childViewTransforms.clear();
1074                                    int duration = getEnterTransformsForFilterAnimation(tasks,
1075                                            taskTransforms, childViewTransforms);
1076                                    for (final TaskView tv : childViewTransforms.keySet()) {
1077                                        Pair<Integer, TaskViewTransform> t = childViewTransforms.get(tv);
1078                                        tv.animate().setStartDelay(t.first);
1079                                        tv.updateViewPropertiesToTaskTransform(t.second, duration);
1080                                    }
1081                                }
1082                            }
1083                        }
1084                    });
1085            tv.updateViewPropertiesToTaskTransform(t.second, duration);
1086        }
1087    }
1088
1089    @Override
1090    public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
1091                                Task filteredTask) {
1092        // Stash the scroll and filtered task for us to restore to when we unfilter
1093        mStashedScroll = getStackScroll();
1094
1095        // Calculate the current task transforms
1096        ArrayList<TaskViewTransform> curTaskTransforms =
1097                getStackTransforms(curTasks, getStackScroll(), null, true);
1098
1099        // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
1100        updateMinMaxScroll(false);
1101        float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
1102        setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
1103        boundScrollRaw();
1104
1105        // Compute the transforms of the items in the new stack after setting the new scroll
1106        final ArrayList<Task> tasks = mStack.getTasks();
1107        final ArrayList<TaskViewTransform> taskTransforms =
1108                getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);
1109
1110        // Animate
1111        doFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
1112    }
1113
1114    @Override
1115    public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) {
1116        // Calculate the current task transforms
1117        final ArrayList<TaskViewTransform> curTaskTransforms =
1118                getStackTransforms(curTasks, getStackScroll(), null, true);
1119
1120        // Restore the stashed scroll
1121        updateMinMaxScroll(false);
1122        setStackScrollRaw(mStashedScroll);
1123        boundScrollRaw();
1124
1125        // Compute the transforms of the items in the new stack after restoring the stashed scroll
1126        final ArrayList<Task> tasks = mStack.getTasks();
1127        final ArrayList<TaskViewTransform> taskTransforms =
1128                getStackTransforms(tasks, getStackScroll(), null, true);
1129
1130        // Animate
1131        doFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
1132
1133        // Clear the saved vars
1134        mStashedScroll = 0;
1135    }
1136
1137    /**** ViewPoolConsumer Implementation ****/
1138
1139    @Override
1140    public TaskView createView(Context context) {
1141        if (Console.Enabled) {
1142            Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|createPoolView]");
1143        }
1144        return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
1145    }
1146
1147    @Override
1148    public void prepareViewToEnterPool(TaskView tv) {
1149        Task task = tv.getTask();
1150        tv.resetViewProperties();
1151        if (Console.Enabled) {
1152            Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]",
1153                    tv.getTask() + " tv: " + tv);
1154        }
1155
1156        // Report that this tasks's data is no longer being used
1157        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
1158        loader.unloadTaskData(task);
1159
1160        // Detach the view from the hierarchy
1161        detachViewFromParent(tv);
1162
1163        // Disable hw layers on this view
1164        tv.disableHwLayers();
1165    }
1166
1167    @Override
1168    public void prepareViewToLeavePool(TaskView tv, Task prepareData, boolean isNewView) {
1169        if (Console.Enabled) {
1170            Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]",
1171                    "isNewView: " + isNewView);
1172        }
1173
1174        // Setup and attach the view to the window
1175        Task task = prepareData;
1176        // We try and rebind the task (this MUST be done before the task filled)
1177        tv.onTaskBound(task);
1178        // Request that this tasks's data be filled
1179        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
1180        loader.loadTaskData(task);
1181
1182        // Find the index where this task should be placed in the children
1183        int insertIndex = -1;
1184        int childCount = getChildCount();
1185        for (int i = 0; i < childCount; i++) {
1186            Task tvTask = ((TaskView) getChildAt(i)).getTask();
1187            if (mStack.containsTask(task) && (mStack.indexOfTask(task) < mStack.indexOfTask(tvTask))) {
1188                insertIndex = i;
1189                break;
1190            }
1191        }
1192
1193        // Sanity check, the task view should always be clipping against the stack at this point,
1194        // but just in case, re-enable it here
1195        tv.setClipViewInStack(true);
1196
1197        // If the doze trigger has already fired, then update the state for this task view
1198        if (mDozeTrigger.hasTriggered()) {
1199            tv.setNoUserInteractionState();
1200        }
1201
1202        // Add/attach the view to the hierarchy
1203        if (Console.Enabled) {
1204            Console.log(Constants.Log.ViewPool.PoolCallbacks, "  [TaskStackView|insertIndex]",
1205                    "" + insertIndex);
1206        }
1207        if (isNewView) {
1208            addView(tv, insertIndex);
1209
1210            // Set the callbacks and listeners for this new view
1211            tv.setOnClickListener(this);
1212            tv.setCallbacks(this);
1213        } else {
1214            attachViewToParent(tv, insertIndex, tv.getLayoutParams());
1215        }
1216
1217        // Enable hw layers on this view if hw layers are enabled on the stack
1218        if (mHwLayersTrigger.getCount() > 0) {
1219            tv.enableHwLayers();
1220        }
1221    }
1222
1223    @Override
1224    public boolean hasPreferredData(TaskView tv, Task preferredData) {
1225        return (tv.getTask() == preferredData);
1226    }
1227
1228    /**** TaskViewCallbacks Implementation ****/
1229
1230    @Override
1231    public void onTaskIconClicked(TaskView tv) {
1232        if (Console.Enabled) {
1233            Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Icon]",
1234                    tv.getTask() + " is currently filtered: " + mStack.hasFilteredTasks(),
1235                    Console.AnsiCyan);
1236        }
1237        if (Constants.DebugFlags.App.EnableTaskFiltering) {
1238            if (mStack.hasFilteredTasks()) {
1239                mStack.unfilterTasks();
1240            } else {
1241                mStack.filterTasks(tv.getTask());
1242            }
1243        }
1244    }
1245
1246    @Override
1247    public void onTaskAppInfoClicked(TaskView tv) {
1248        if (mCb != null) {
1249            mCb.onTaskAppInfoLaunched(tv.getTask());
1250        }
1251    }
1252
1253    @Override
1254    public void onTaskFocused(TaskView tv) {
1255        // Do nothing
1256    }
1257
1258    @Override
1259    public void onTaskDismissed(TaskView tv) {
1260        Task task = tv.getTask();
1261        // Remove the task from the view
1262        mStack.removeTask(task);
1263        // Notify the callback that we've removed the task and it can clean up after it
1264        mCb.onTaskRemoved(task);
1265    }
1266
1267    /**** View.OnClickListener Implementation ****/
1268
1269    @Override
1270    public void onClick(View v) {
1271        TaskView tv = (TaskView) v;
1272        Task task = tv.getTask();
1273        if (Console.Enabled) {
1274            Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]",
1275                    task + " cb: " + mCb);
1276        }
1277
1278        if (mCb != null) {
1279            mCb.onTaskLaunched(this, tv, mStack, task);
1280        }
1281    }
1282
1283    /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
1284
1285    @Override
1286    public void onComponentRemoved(Set<ComponentName> cns) {
1287        // For other tasks, just remove them directly if they no longer exist
1288        ArrayList<Task> tasks = mStack.getTasks();
1289        for (int i = tasks.size() - 1; i >= 0; i--) {
1290            final Task t = tasks.get(i);
1291            if (cns.contains(t.key.baseIntent.getComponent())) {
1292                TaskView tv = getChildViewForTask(t);
1293                if (tv != null) {
1294                    // For visible children, defer removing the task until after the animation
1295                    tv.startDeleteTaskAnimation(new Runnable() {
1296                        @Override
1297                        public void run() {
1298                            mStack.removeTask(t);
1299                        }
1300                    });
1301                } else {
1302                    // Otherwise, remove the task from the stack immediately
1303                    mStack.removeTask(t);
1304                }
1305            }
1306        }
1307    }
1308}
1309
1310/* Handles touch events */
1311class TaskStackViewTouchHandler implements SwipeHelper.Callback {
1312    static int INACTIVE_POINTER_ID = -1;
1313
1314    TaskStackView mSv;
1315    VelocityTracker mVelocityTracker;
1316
1317    boolean mIsScrolling;
1318
1319    int mInitialMotionX, mInitialMotionY;
1320    int mLastMotionX, mLastMotionY;
1321    int mActivePointerId = INACTIVE_POINTER_ID;
1322    TaskView mActiveTaskView = null;
1323
1324    int mTotalScrollMotion;
1325    int mMinimumVelocity;
1326    int mMaximumVelocity;
1327    // The scroll touch slop is used to calculate when we start scrolling
1328    int mScrollTouchSlop;
1329    // The page touch slop is used to calculate when we start swiping
1330    float mPagingTouchSlop;
1331
1332    SwipeHelper mSwipeHelper;
1333    boolean mInterceptedBySwipeHelper;
1334
1335    public TaskStackViewTouchHandler(Context context, TaskStackView sv) {
1336        ViewConfiguration configuration = ViewConfiguration.get(context);
1337        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
1338        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
1339        mScrollTouchSlop = configuration.getScaledTouchSlop();
1340        mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
1341        mSv = sv;
1342
1343
1344        float densityScale = context.getResources().getDisplayMetrics().density;
1345        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop);
1346        mSwipeHelper.setMinAlpha(1f);
1347    }
1348
1349    /** Velocity tracker helpers */
1350    void initOrResetVelocityTracker() {
1351        if (mVelocityTracker == null) {
1352            mVelocityTracker = VelocityTracker.obtain();
1353        } else {
1354            mVelocityTracker.clear();
1355        }
1356    }
1357    void initVelocityTrackerIfNotExists() {
1358        if (mVelocityTracker == null) {
1359            mVelocityTracker = VelocityTracker.obtain();
1360        }
1361    }
1362    void recycleVelocityTracker() {
1363        if (mVelocityTracker != null) {
1364            mVelocityTracker.recycle();
1365            mVelocityTracker = null;
1366        }
1367    }
1368
1369    /** Returns the view at the specified coordinates */
1370    TaskView findViewAtPoint(int x, int y) {
1371        int childCount = mSv.getChildCount();
1372        for (int i = childCount - 1; i >= 0; i--) {
1373            TaskView tv = (TaskView) mSv.getChildAt(i);
1374            if (tv.getVisibility() == View.VISIBLE) {
1375                if (mSv.isTransformedTouchPointInView(x, y, tv)) {
1376                    return tv;
1377                }
1378            }
1379        }
1380        return null;
1381    }
1382
1383    /** Touch preprocessing for handling below */
1384    public boolean onInterceptTouchEvent(MotionEvent ev) {
1385        if (Console.Enabled) {
1386            Console.log(Constants.Log.UI.TouchEvents,
1387                    "[TaskStackViewTouchHandler|interceptTouchEvent]",
1388                    Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
1389        }
1390
1391        // Return early if we have no children
1392        boolean hasChildren = (mSv.getChildCount() > 0);
1393        if (!hasChildren) {
1394            return false;
1395        }
1396
1397        // Pass through to swipe helper if we are swiping
1398        mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
1399        if (mInterceptedBySwipeHelper) {
1400            return true;
1401        }
1402
1403        boolean wasScrolling = !mSv.mScroller.isFinished() ||
1404                (mSv.mScrollAnimator != null && mSv.mScrollAnimator.isRunning());
1405        int action = ev.getAction();
1406        switch (action & MotionEvent.ACTION_MASK) {
1407            case MotionEvent.ACTION_DOWN: {
1408                // Save the touch down info
1409                mInitialMotionX = mLastMotionX = (int) ev.getX();
1410                mInitialMotionY = mLastMotionY = (int) ev.getY();
1411                mActivePointerId = ev.getPointerId(0);
1412                mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
1413                // Stop the current scroll if it is still flinging
1414                mSv.abortScroller();
1415                mSv.abortBoundScrollAnimation();
1416                // Initialize the velocity tracker
1417                initOrResetVelocityTracker();
1418                mVelocityTracker.addMovement(ev);
1419                // Check if the scroller is finished yet
1420                mIsScrolling = !mSv.mScroller.isFinished();
1421                break;
1422            }
1423            case MotionEvent.ACTION_MOVE: {
1424                if (mActivePointerId == INACTIVE_POINTER_ID) break;
1425
1426                int activePointerIndex = ev.findPointerIndex(mActivePointerId);
1427                int y = (int) ev.getY(activePointerIndex);
1428                int x = (int) ev.getX(activePointerIndex);
1429                if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
1430                    // Save the touch move info
1431                    mIsScrolling = true;
1432                    // Initialize the velocity tracker if necessary
1433                    initVelocityTrackerIfNotExists();
1434                    mVelocityTracker.addMovement(ev);
1435                    // Disallow parents from intercepting touch events
1436                    final ViewParent parent = mSv.getParent();
1437                    if (parent != null) {
1438                        parent.requestDisallowInterceptTouchEvent(true);
1439                    }
1440                    // Enable HW layers
1441                    mSv.addHwLayersRefCount("stackScroll");
1442                }
1443
1444                mLastMotionX = x;
1445                mLastMotionY = y;
1446                break;
1447            }
1448            case MotionEvent.ACTION_CANCEL:
1449            case MotionEvent.ACTION_UP: {
1450                // Animate the scroll back if we've cancelled
1451                mSv.animateBoundScroll();
1452                // Disable HW layers
1453                if (mIsScrolling) {
1454                    mSv.decHwLayersRefCount("stackScroll");
1455                }
1456                // Reset the drag state and the velocity tracker
1457                mIsScrolling = false;
1458                mActivePointerId = INACTIVE_POINTER_ID;
1459                mActiveTaskView = null;
1460                mTotalScrollMotion = 0;
1461                recycleVelocityTracker();
1462                break;
1463            }
1464        }
1465
1466        return wasScrolling || mIsScrolling;
1467    }
1468
1469    /** Handles touch events once we have intercepted them */
1470    public boolean onTouchEvent(MotionEvent ev) {
1471        if (Console.Enabled) {
1472            Console.log(Constants.Log.UI.TouchEvents,
1473                    "[TaskStackViewTouchHandler|touchEvent]",
1474                    Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
1475        }
1476
1477        // Short circuit if we have no children
1478        boolean hasChildren = (mSv.getChildCount() > 0);
1479        if (!hasChildren) {
1480            return false;
1481        }
1482
1483        // Pass through to swipe helper if we are swiping
1484        if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
1485            return true;
1486        }
1487
1488        // Update the velocity tracker
1489        initVelocityTrackerIfNotExists();
1490        mVelocityTracker.addMovement(ev);
1491
1492        int action = ev.getAction();
1493        switch (action & MotionEvent.ACTION_MASK) {
1494            case MotionEvent.ACTION_DOWN: {
1495                // Save the touch down info
1496                mInitialMotionX = mLastMotionX = (int) ev.getX();
1497                mInitialMotionY = mLastMotionY = (int) ev.getY();
1498                mActivePointerId = ev.getPointerId(0);
1499                mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
1500                // Stop the current scroll if it is still flinging
1501                mSv.abortScroller();
1502                mSv.abortBoundScrollAnimation();
1503                // Initialize the velocity tracker
1504                initOrResetVelocityTracker();
1505                mVelocityTracker.addMovement(ev);
1506                // Disallow parents from intercepting touch events
1507                final ViewParent parent = mSv.getParent();
1508                if (parent != null) {
1509                    parent.requestDisallowInterceptTouchEvent(true);
1510                }
1511                break;
1512            }
1513            case MotionEvent.ACTION_POINTER_DOWN: {
1514                final int index = ev.getActionIndex();
1515                mActivePointerId = ev.getPointerId(index);
1516                mLastMotionX = (int) ev.getX(index);
1517                mLastMotionY = (int) ev.getY(index);
1518                break;
1519            }
1520            case MotionEvent.ACTION_MOVE: {
1521                if (mActivePointerId == INACTIVE_POINTER_ID) break;
1522
1523                int activePointerIndex = ev.findPointerIndex(mActivePointerId);
1524                int x = (int) ev.getX(activePointerIndex);
1525                int y = (int) ev.getY(activePointerIndex);
1526                int yTotal = Math.abs(y - mInitialMotionY);
1527                int deltaY = mLastMotionY - y;
1528                if (!mIsScrolling) {
1529                    if (yTotal > mScrollTouchSlop) {
1530                        mIsScrolling = true;
1531                        // Initialize the velocity tracker
1532                        initOrResetVelocityTracker();
1533                        mVelocityTracker.addMovement(ev);
1534                        // Disallow parents from intercepting touch events
1535                        final ViewParent parent = mSv.getParent();
1536                        if (parent != null) {
1537                            parent.requestDisallowInterceptTouchEvent(true);
1538                        }
1539                        // Enable HW layers
1540                        mSv.addHwLayersRefCount("stackScroll");
1541                    }
1542                }
1543                if (mIsScrolling) {
1544                    int curStackScroll = mSv.getStackScroll();
1545                    int overScrollAmount = mSv.getScrollAmountOutOfBounds(curStackScroll + deltaY);
1546                    if (overScrollAmount != 0) {
1547                        // Bound the overscroll to a fixed amount, and inversely scale the y-movement
1548                        // relative to how close we are to the max overscroll
1549                        float maxOverScroll = mSv.mTaskRect.height() / 3f;
1550                        deltaY = Math.round(deltaY * (1f - (Math.min(maxOverScroll, overScrollAmount)
1551                                / maxOverScroll)));
1552                    }
1553                    mSv.setStackScroll(curStackScroll + deltaY);
1554                    if (mSv.isScrollOutOfBounds()) {
1555                        mVelocityTracker.clear();
1556                    }
1557                }
1558                mLastMotionX = x;
1559                mLastMotionY = y;
1560                mTotalScrollMotion += Math.abs(deltaY);
1561                break;
1562            }
1563            case MotionEvent.ACTION_UP: {
1564                final VelocityTracker velocityTracker = mVelocityTracker;
1565                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1566                int velocity = (int) velocityTracker.getYVelocity(mActivePointerId);
1567
1568                if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {
1569                    // Enable HW layers on the stack
1570                    mSv.addHwLayersRefCount("flingScroll");
1571                    // XXX: Make this animation a function of the velocity AND distance
1572                    int overscrollRange = (int) (Math.min(1f,
1573                            Math.abs((float) velocity / mMaximumVelocity)) *
1574                            Constants.Values.TaskStackView.TaskStackOverscrollRange);
1575
1576                    if (Console.Enabled) {
1577                        Console.log(Constants.Log.UI.TouchEvents,
1578                                "[TaskStackViewTouchHandler|fling]",
1579                                "scroll: " + mSv.getStackScroll() + " velocity: " + velocity +
1580                                        " maxVelocity: " + mMaximumVelocity +
1581                                        " overscrollRange: " + overscrollRange,
1582                                Console.AnsiGreen);
1583                    }
1584
1585                    // Fling scroll
1586                    mSv.mScroller.fling(0, mSv.getStackScroll(),
1587                            0, -velocity,
1588                            0, 0,
1589                            mSv.mMinScroll, mSv.mMaxScroll,
1590                            0, overscrollRange);
1591                    // Invalidate to kick off computeScroll
1592                    mSv.invalidate(mSv.mStackRect);
1593                } else if (mSv.isScrollOutOfBounds()) {
1594                    // Animate the scroll back into bounds
1595                    // XXX: Make this animation a function of the velocity OR distance
1596                    mSv.animateBoundScroll();
1597                }
1598
1599                if (mIsScrolling) {
1600                    // Disable HW layers
1601                    mSv.decHwLayersRefCount("stackScroll");
1602                }
1603                mActivePointerId = INACTIVE_POINTER_ID;
1604                mIsScrolling = false;
1605                mTotalScrollMotion = 0;
1606                recycleVelocityTracker();
1607                break;
1608            }
1609            case MotionEvent.ACTION_POINTER_UP: {
1610                int pointerIndex = ev.getActionIndex();
1611                int pointerId = ev.getPointerId(pointerIndex);
1612                if (pointerId == mActivePointerId) {
1613                    // Select a new active pointer id and reset the motion state
1614                    final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
1615                    mActivePointerId = ev.getPointerId(newPointerIndex);
1616                    mLastMotionX = (int) ev.getX(newPointerIndex);
1617                    mLastMotionY = (int) ev.getY(newPointerIndex);
1618                    mVelocityTracker.clear();
1619                }
1620                break;
1621            }
1622            case MotionEvent.ACTION_CANCEL: {
1623                if (mIsScrolling) {
1624                    // Disable HW layers
1625                    mSv.decHwLayersRefCount("stackScroll");
1626                }
1627                if (mSv.isScrollOutOfBounds()) {
1628                    // Animate the scroll back into bounds
1629                    // XXX: Make this animation a function of the velocity OR distance
1630                    mSv.animateBoundScroll();
1631                }
1632                mActivePointerId = INACTIVE_POINTER_ID;
1633                mIsScrolling = false;
1634                mTotalScrollMotion = 0;
1635                recycleVelocityTracker();
1636                break;
1637            }
1638        }
1639        return true;
1640    }
1641
1642    /**** SwipeHelper Implementation ****/
1643
1644    @Override
1645    public View getChildAtPosition(MotionEvent ev) {
1646        return findViewAtPoint((int) ev.getX(), (int) ev.getY());
1647    }
1648
1649    @Override
1650    public boolean canChildBeDismissed(View v) {
1651        return true;
1652    }
1653
1654    @Override
1655    public void onBeginDrag(View v) {
1656        // Enable HW layers
1657        mSv.addHwLayersRefCount("swipeBegin");
1658        // Disable clipping with the stack while we are swiping
1659        TaskView tv = (TaskView) v;
1660        tv.setClipViewInStack(false);
1661        // Disallow parents from intercepting touch events
1662        final ViewParent parent = mSv.getParent();
1663        if (parent != null) {
1664            parent.requestDisallowInterceptTouchEvent(true);
1665        }
1666    }
1667
1668    @Override
1669    public void onSwipeChanged(View v, float delta) {
1670        // Do nothing
1671    }
1672
1673    @Override
1674    public void onChildDismissed(View v) {
1675        TaskView tv = (TaskView) v;
1676        mSv.onTaskDismissed(tv);
1677
1678        // Re-enable clipping with the stack (we will reuse this view)
1679        tv.setClipViewInStack(true);
1680
1681        // Disable HW layers
1682        mSv.decHwLayersRefCount("swipeComplete");
1683    }
1684
1685    @Override
1686    public void onSnapBackCompleted(View v) {
1687        // Re-enable clipping with the stack
1688        TaskView tv = (TaskView) v;
1689        tv.setClipViewInStack(true);
1690    }
1691
1692    @Override
1693    public void onDragCancelled(View v) {
1694        // Disable HW layers
1695        mSv.decHwLayersRefCount("swipeCancelled");
1696    }
1697}
1698