TaskStackView.java revision 37c8d8ef855aacb074ee0b702a46dbc62b7551a2
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.app.ActivityManager;
25import android.content.Context;
26import android.graphics.Canvas;
27import android.graphics.Rect;
28import android.graphics.Region;
29import android.view.LayoutInflater;
30import android.view.MotionEvent;
31import android.view.VelocityTracker;
32import android.view.View;
33import android.view.ViewConfiguration;
34import android.view.ViewParent;
35import android.widget.FrameLayout;
36import android.widget.OverScroller;
37import com.android.systemui.recents.Console;
38import com.android.systemui.recents.Constants;
39import com.android.systemui.recents.RecentsConfiguration;
40import com.android.systemui.recents.RecentsTaskLoader;
41import com.android.systemui.recents.Utilities;
42import com.android.systemui.recents.model.Task;
43import com.android.systemui.recents.model.TaskStack;
44import com.android.systemui.R;
45
46import java.util.ArrayList;
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        View.OnClickListener {
53
54    /** The TaskView callbacks */
55    interface TaskStackViewCallbacks {
56        public void onTaskLaunched(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
57    }
58
59    TaskStack mStack;
60    TaskStackViewTouchHandler mTouchHandler;
61    TaskStackViewCallbacks mCb;
62    ViewPool<TaskView, Task> mViewPool;
63
64    // The various rects that define the stack view
65    Rect mRect = new Rect();
66    Rect mStackRect = new Rect();
67    Rect mStackRectSansPeek = new Rect();
68    Rect mTaskRect = new Rect();
69
70    // The virtual stack scroll that we use for the card layout
71    int mStackScroll;
72    int mMinScroll;
73    int mMaxScroll;
74    OverScroller mScroller;
75    ObjectAnimator mScrollAnimator;
76
77    // Optimizations
78    int mHwLayersRefCount;
79    int mStackViewsAnimationDuration;
80    boolean mStackViewsDirty = true;
81    boolean mAwaitingFirstLayout = true;
82    LayoutInflater mInflater;
83
84    public TaskStackView(Context context, TaskStack stack) {
85        super(context);
86        mStack = stack;
87        mStack.setCallbacks(this);
88        mScroller = new OverScroller(context);
89        mTouchHandler = new TaskStackViewTouchHandler(context, this);
90        mViewPool = new ViewPool<TaskView, Task>(context, this);
91        mInflater = LayoutInflater.from(context);
92    }
93
94    /** Sets the callbacks */
95    void setCallbacks(TaskStackViewCallbacks cb) {
96        mCb = cb;
97    }
98
99    /** Requests that the views be synchronized with the model */
100    void requestSynchronizeStackViewsWithModel() {
101        requestSynchronizeStackViewsWithModel(0);
102    }
103    void requestSynchronizeStackViewsWithModel(int duration) {
104        Console.log(Constants.DebugFlags.TaskStack.SynchronizeViewsWithModel,
105                "[TaskStackView|requestSynchronize]", "", Console.AnsiYellow);
106        if (!mStackViewsDirty) {
107            invalidate();
108        }
109        if (mAwaitingFirstLayout) {
110            // Skip the animation if we are awaiting first layout
111            mStackViewsAnimationDuration = 0;
112        } else {
113            mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration);
114        }
115        mStackViewsDirty = true;
116    }
117
118    // XXX: Optimization: Use a mapping of Task -> View
119    private TaskView getChildViewForTask(Task t) {
120        int childCount = getChildCount();
121        for (int i = 0; i < childCount; i++) {
122            TaskView tv = (TaskView) getChildAt(i);
123            if (tv.getTask() == t) {
124                return tv;
125            }
126        }
127        return null;
128    }
129
130    /** Update/get the transform */
131    public TaskViewTransform getStackTransform(int indexInStack) {
132        TaskViewTransform transform = new TaskViewTransform();
133
134        // Map the items to an continuous position relative to the current scroll
135        int numPeekCards = Constants.Values.TaskStackView.StackPeekNumCards;
136        float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
137        float peekHeight = Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height();
138        float t = ((indexInStack * overlapHeight) - getStackScroll()) / overlapHeight;
139        float boundedT = Math.max(t, -(numPeekCards + 1));
140
141        // Set the scale relative to its position
142        float minScale = Constants.Values.TaskStackView.StackPeekMinScale;
143        float scaleRange = 1f - minScale;
144        float scaleInc = scaleRange / numPeekCards;
145        float scale = Math.max(minScale, Math.min(1f, 1f + (boundedT * scaleInc)));
146        float scaleYOffset = ((1f - scale) * mTaskRect.height()) / 2;
147        transform.scale = scale;
148
149        // Set the translation
150        if (boundedT < 0f) {
151            transform.translationY = (int) ((Math.max(-numPeekCards, boundedT) /
152                    numPeekCards) * peekHeight - scaleYOffset);
153        } else {
154            transform.translationY = (int) (boundedT * overlapHeight - scaleYOffset);
155        }
156
157        // Update the rect and visibility
158        transform.rect.set(mTaskRect);
159        if (t < -(numPeekCards + 1)) {
160            transform.visible = false;
161        } else {
162            transform.rect.offset(0, transform.translationY);
163            Utilities.scaleRectAboutCenter(transform.rect, scale);
164            transform.visible = Rect.intersects(mRect, transform.rect);
165        }
166        transform.t = t;
167        return transform;
168    }
169
170    /** Synchronizes the views with the model */
171    void synchronizeStackViewsWithModel() {
172        Console.log(Constants.DebugFlags.TaskStack.SynchronizeViewsWithModel,
173                "[TaskStackView|synchronizeViewsWithModel]",
174                "mStackViewsDirty: " + mStackViewsDirty, Console.AnsiYellow);
175        if (mStackViewsDirty) {
176
177            // XXX: Optimization: Use binary search to find the visible range
178            // XXX: Optimize to not call getStackTransform() so many times
179            // XXX: Consider using TaskViewTransform pool to prevent allocations
180            // XXX: Iterate children views, update transforms and remove all that are not visible
181            //      For all remaining tasks, update transforms and if visible add the view
182
183            // Update the visible state of all the tasks
184            ArrayList<Task> tasks = mStack.getTasks();
185            int taskCount = tasks.size();
186            for (int i = 0; i < taskCount; i++) {
187                Task task = tasks.get(i);
188                TaskViewTransform transform = getStackTransform(i);
189                TaskView tv = getChildViewForTask(task);
190
191                if (transform.visible) {
192                    if (tv == null) {
193                        tv = mViewPool.pickUpViewFromPool(task, task);
194                        // When we are picking up a new view from the view pool, prepare it for any
195                        // following animation by putting it in a reasonable place
196                        if (mStackViewsAnimationDuration > 0 && i != 0) {
197                            // XXX: We have to animate when filtering, etc. Maybe we should have a
198                            //      runnable that ensures that tasks are animated in a special way
199                            //      when they are entering the scene?
200                            int fromIndex = (transform.t < 0) ? (i - 1) : (i + 1);
201                            tv.updateViewPropertiesFromTask(null, getStackTransform(fromIndex), 0);
202                        }
203                    }
204                } else {
205                    if (tv != null) {
206                        mViewPool.returnViewToPool(tv);
207                    }
208                }
209            }
210
211            // Update all the current view children
212            // NOTE: We have to iterate in reverse where because we are removing views directly
213            int childCount = getChildCount();
214            for (int i = childCount - 1; i >= 0; i--) {
215                TaskView tv = (TaskView) getChildAt(i);
216                Task task = tv.getTask();
217                TaskViewTransform transform = getStackTransform(mStack.indexOfTask(task));
218                if (!transform.visible) {
219                    mViewPool.returnViewToPool(tv);
220                } else {
221                    tv.updateViewPropertiesFromTask(null, transform, mStackViewsAnimationDuration);
222                }
223            }
224
225            Console.log(Constants.DebugFlags.TaskStack.SynchronizeViewsWithModel,
226                    "  [TaskStackView|viewChildren]", "" + getChildCount());
227
228            mStackViewsAnimationDuration = 0;
229            mStackViewsDirty = false;
230        }
231    }
232
233    /** Sets the current stack scroll */
234    public void setStackScroll(int value) {
235        mStackScroll = value;
236        requestSynchronizeStackViewsWithModel();
237    }
238
239    /** Gets the current stack scroll */
240    public int getStackScroll() {
241        return mStackScroll;
242    }
243
244    /** Animates the stack scroll into bounds */
245    ObjectAnimator animateBoundScroll(int duration) {
246        int curScroll = getStackScroll();
247        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
248        if (newScroll != curScroll) {
249            // Enable hw layers on the stack
250            addHwLayersRefCount("animateBoundScroll");
251
252            // Abort any current animations
253            abortScroller();
254            if (mScrollAnimator != null) {
255                mScrollAnimator.cancel();
256                mScrollAnimator.removeAllListeners();
257            }
258
259            // Start a new scroll animation
260            mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll);
261            mScrollAnimator.setDuration(duration);
262            mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
263                @Override
264                public void onAnimationUpdate(ValueAnimator animation) {
265                    setStackScroll((Integer) animation.getAnimatedValue());
266                }
267            });
268            mScrollAnimator.addListener(new AnimatorListenerAdapter() {
269                @Override
270                public void onAnimationEnd(Animator animation) {
271                    // Disable hw layers on the stack
272                    decHwLayersRefCount("animateBoundScroll");
273                }
274            });
275            mScrollAnimator.start();
276        }
277        return mScrollAnimator;
278    }
279
280    /** Aborts any current stack scrolls */
281    void abortBoundScrollAnimation() {
282        if (mScrollAnimator != null) {
283            mScrollAnimator.cancel();
284        }
285    }
286
287    void abortScroller() {
288        if (!mScroller.isFinished()) {
289            // Abort the scroller
290            mScroller.abortAnimation();
291            // And disable hw layers on the stack
292            decHwLayersRefCount("flingScroll");
293        }
294    }
295
296    /** Bounds the current scroll if necessary */
297    public boolean boundScroll() {
298        int curScroll = getStackScroll();
299        int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
300        if (newScroll != curScroll) {
301            setStackScroll(newScroll);
302            return true;
303        }
304        return false;
305    }
306
307    /** Returns whether the current scroll is out of bounds */
308    boolean isScrollOutOfBounds() {
309        return (getStackScroll() < 0) || (getStackScroll() > mMaxScroll);
310    }
311
312    /** Updates the min and max virtual scroll bounds */
313    void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
314        // Compute the min and max scroll values
315        int numTasks = Math.max(1, mStack.getTaskCount());
316        int taskHeight = mTaskRect.height();
317        int stackHeight = mStackRectSansPeek.height();
318        int maxScrollHeight = taskHeight + (int) ((numTasks - 1) *
319                Constants.Values.TaskStackView.StackOverlapPct * taskHeight);
320        mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight;
321        mMaxScroll = maxScrollHeight - stackHeight;
322
323        // Debug logging
324        if (Constants.DebugFlags.UI.MeasureAndLayout) {
325            Console.log("  [TaskStack|minScroll] " + mMinScroll);
326            Console.log("  [TaskStack|maxScroll] " + mMaxScroll);
327        }
328
329        if (boundScrollToNewMinMax) {
330            boundScroll();
331        }
332    }
333
334    /** Enables the hw layers and increments the hw layer requirement ref count */
335    void addHwLayersRefCount(String reason) {
336        Console.log(Constants.DebugFlags.UI.HwLayers,
337                "[TaskStackView|addHwLayersRefCount] refCount: " +
338                        mHwLayersRefCount + "->" + (mHwLayersRefCount + 1) + " " + reason);
339        if (mHwLayersRefCount == 0) {
340            // Enable hw layers on each of the children
341            int childCount = getChildCount();
342            for (int i = 0; i < childCount; i++) {
343                TaskView tv = (TaskView) getChildAt(i);
344                tv.enableHwLayers();
345            }
346        }
347        mHwLayersRefCount++;
348    }
349
350    /** Decrements the hw layer requirement ref count and disables the hw layers when we don't
351        need them anymore. */
352    void decHwLayersRefCount(String reason) {
353        Console.log(Constants.DebugFlags.UI.HwLayers,
354                "[TaskStackView|decHwLayersRefCount] refCount: " +
355                        mHwLayersRefCount + "->" + (mHwLayersRefCount - 1) + " " + reason);
356        mHwLayersRefCount--;
357        if (mHwLayersRefCount == 0) {
358            // Disable hw layers on each of the children
359            int childCount = getChildCount();
360            for (int i = 0; i < childCount; i++) {
361                TaskView tv = (TaskView) getChildAt(i);
362                tv.disableHwLayers();
363            }
364        } else if (mHwLayersRefCount < 0) {
365            new Throwable("Invalid hw layers ref count").printStackTrace();
366            Console.logError(getContext(), "Invalid HW layers ref count");
367        }
368    }
369
370    @Override
371    public void computeScroll() {
372        if (mScroller.computeScrollOffset()) {
373            setStackScroll(mScroller.getCurrY());
374            invalidate();
375
376            // If we just finished scrolling, then disable the hw layers
377            if (mScroller.isFinished()) {
378                decHwLayersRefCount("finishedFlingScroll");
379            }
380        }
381    }
382
383    @Override
384    public boolean onInterceptTouchEvent(MotionEvent ev) {
385        return mTouchHandler.onInterceptTouchEvent(ev);
386    }
387
388    @Override
389    public boolean onTouchEvent(MotionEvent ev) {
390        return mTouchHandler.onTouchEvent(ev);
391    }
392
393    @Override
394    public void dispatchDraw(Canvas canvas) {
395        Console.log(Constants.DebugFlags.UI.Draw, "[TaskStackView|dispatchDraw]", "",
396                Console.AnsiPurple);
397        synchronizeStackViewsWithModel();
398        super.dispatchDraw(canvas);
399    }
400
401    @Override
402    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
403        if (Constants.DebugFlags.App.EnableTaskStackClipping) {
404            TaskView tv = (TaskView) child;
405            TaskView nextTv = null;
406            int curIndex = indexOfChild(tv);
407            if (curIndex < (getChildCount() - 1)) {
408                // Clip against the next view (if we aren't animating its alpha)
409                nextTv = (TaskView) getChildAt(curIndex + 1);
410                if (nextTv.getAlpha() == 1f) {
411                    Rect curRect = tv.getClippingRect(Utilities.tmpRect, false);
412                    Rect nextRect = nextTv.getClippingRect(Utilities.tmpRect2, true);
413                    RecentsConfiguration config = RecentsConfiguration.getInstance();
414                    // The hit rects are relative to the task view, which needs to be offset by the
415                    // system bar height
416                    curRect.offset(0, config.systemInsets.top);
417                    nextRect.offset(0, config.systemInsets.top);
418                    // Compute the clip region
419                    Region clipRegion = new Region();
420                    clipRegion.op(curRect, Region.Op.UNION);
421                    clipRegion.op(nextRect, Region.Op.DIFFERENCE);
422                    // Clip the canvas
423                    int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
424                    canvas.clipRegion(clipRegion);
425                    boolean invalidate = super.drawChild(canvas, child, drawingTime);
426                    canvas.restoreToCount(saveCount);
427                    return invalidate;
428                }
429            }
430        }
431        return super.drawChild(canvas, child, drawingTime);
432    }
433
434    /** Computes the stack and task rects */
435    public void computeRects(int width, int height, int insetBottom) {
436        // Note: We let the stack view be the full height because we want the cards to go under the
437        //       navigation bar if possible.  However, the stack rects which we use to calculate
438        //       max scroll, etc. need to take the nav bar into account
439
440        // Compute the stack rects
441        mRect.set(0, 0, width, height);
442        mStackRect.set(mRect);
443        mStackRect.bottom -= insetBottom;
444
445        int smallestDimension = Math.min(width, height);
446        int padding = (int) (Constants.Values.TaskStackView.StackPaddingPct * smallestDimension / 2f);
447        mStackRect.inset(padding, padding);
448        mStackRectSansPeek.set(mStackRect);
449        mStackRectSansPeek.top += Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height();
450
451        // Compute the task rect
452        int minHeight = (int) (mStackRect.height() -
453                (Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height()));
454        int size = Math.min(minHeight, Math.min(mStackRect.width(), mStackRect.height()));
455        int centerX = mStackRect.centerX();
456        mTaskRect.set(centerX - size / 2, mStackRectSansPeek.top,
457                centerX + size / 2, mStackRectSansPeek.top + size);
458
459        // Update the scroll bounds
460        updateMinMaxScroll(false);
461    }
462
463    @Override
464    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
465        int width = MeasureSpec.getSize(widthMeasureSpec);
466        int height = MeasureSpec.getSize(heightMeasureSpec);
467        Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[TaskStackView|measure]",
468                "width: " + width + " height: " + height +
469                " awaitingFirstLayout: " + mAwaitingFirstLayout, Console.AnsiGreen);
470
471        // Compute our stack/task rects
472        RecentsConfiguration config = RecentsConfiguration.getInstance();
473        computeRects(width, height, config.systemInsets.bottom);
474
475        // Debug logging
476        if (Constants.DebugFlags.UI.MeasureAndLayout) {
477            Console.log("  [TaskStack|fullRect] " + mRect);
478            Console.log("  [TaskStack|stackRect] " + mStackRect);
479            Console.log("  [TaskStack|stackRectSansPeek] " + mStackRectSansPeek);
480            Console.log("  [TaskStack|taskRect] " + mTaskRect);
481        }
482
483        // If this is the first layout, then scroll to the front of the stack and synchronize the
484        // stack views immediately
485        if (mAwaitingFirstLayout) {
486            setStackScroll(mMaxScroll);
487            requestSynchronizeStackViewsWithModel();
488            synchronizeStackViewsWithModel();
489
490            // Animate the icon of the first task view
491            if (Constants.Values.TaskView.AnimateFrontTaskIconOnEnterRecents) {
492                TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
493                if (tv != null) {
494                    tv.animateOnEnterRecents();
495                }
496            }
497        }
498
499        // Measure each of the children
500        int childCount = getChildCount();
501        for (int i = 0; i < childCount; i++) {
502            TaskView t = (TaskView) getChildAt(i);
503            t.measure(MeasureSpec.makeMeasureSpec(mTaskRect.width(), MeasureSpec.EXACTLY),
504                    MeasureSpec.makeMeasureSpec(mTaskRect.height(), MeasureSpec.EXACTLY));
505        }
506
507        setMeasuredDimension(width, height);
508    }
509
510    @Override
511    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
512        Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[TaskStackView|layout]",
513                "" + new Rect(left, top, right, bottom), Console.AnsiGreen);
514
515        // Debug logging
516        if (Constants.DebugFlags.UI.MeasureAndLayout) {
517            Console.log("  [TaskStack|fullRect] " + mRect);
518            Console.log("  [TaskStack|stackRect] " + mStackRect);
519            Console.log("  [TaskStack|stackRectSansPeek] " + mStackRectSansPeek);
520            Console.log("  [TaskStack|taskRect] " + mTaskRect);
521        }
522
523        // Layout each of the children
524        int childCount = getChildCount();
525        for (int i = 0; i < childCount; i++) {
526            TaskView t = (TaskView) getChildAt(i);
527            t.layout(mTaskRect.left, mStackRectSansPeek.top,
528                    mTaskRect.right, mStackRectSansPeek.top + mTaskRect.height());
529        }
530
531        if (!mAwaitingFirstLayout) {
532            requestSynchronizeStackViewsWithModel();
533        } else {
534            mAwaitingFirstLayout = false;
535        }
536    }
537
538    @Override
539    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
540        super.onScrollChanged(l, t, oldl, oldt);
541        requestSynchronizeStackViewsWithModel();
542    }
543
544    public boolean isTransformedTouchPointInView(float x, float y, View child) {
545        return isTransformedTouchPointInView(x, y, child, null);
546    }
547
548    /**** TaskStackCallbacks Implementation ****/
549
550    @Override
551    public void onStackTaskAdded(TaskStack stack, Task t) {
552        requestSynchronizeStackViewsWithModel();
553    }
554
555    @Override
556    public void onStackTaskRemoved(TaskStack stack, Task t) {
557        // Remove the view associated with this task, we can't rely on updateTransforms
558        // to work here because the task is no longer in the list
559        int childCount = getChildCount();
560        for (int i = childCount - 1; i >= 0; i--) {
561            TaskView tv = (TaskView) getChildAt(i);
562            if (tv.getTask() == t) {
563                mViewPool.returnViewToPool(tv);
564                break;
565            }
566        }
567
568        updateMinMaxScroll(true);
569        requestSynchronizeStackViewsWithModel(Constants.Values.TaskStackView.Animation.TaskRemovedReshuffleDuration);
570    }
571
572    @Override
573    public void onStackFiltered(TaskStack stack) {
574        requestSynchronizeStackViewsWithModel();
575    }
576
577    @Override
578    public void onStackUnfiltered(TaskStack stack) {
579        requestSynchronizeStackViewsWithModel();
580    }
581
582    /**** ViewPoolConsumer Implementation ****/
583
584    @Override
585    public TaskView createView(Context context) {
586        Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, "[TaskStackView|createPoolView]");
587        return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
588    }
589
590    @Override
591    public void prepareViewToEnterPool(TaskView tv) {
592        Task task = tv.getTask();
593        tv.resetViewProperties();
594        Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]",
595                tv.getTask() + " tv: " + tv);
596
597        // Report that this tasks's data is no longer being used
598        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
599        loader.unloadTaskData(task);
600
601        // Detach the view from the hierarchy
602        detachViewFromParent(tv);
603
604        // Disable hw layers on this view
605        tv.disableHwLayers();
606    }
607
608    @Override
609    public void prepareViewToLeavePool(TaskView tv, Task prepareData, boolean isNewView) {
610        Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]",
611                "isNewView: " + isNewView);
612
613        // Setup and attach the view to the window
614        Task task = prepareData;
615        // We try and rebind the task (this MUST be done before the task filled)
616        tv.onTaskBound(task);
617        // Request that this tasks's data be filled
618        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
619        loader.loadTaskData(task);
620
621        // Find the index where this task should be placed in the children
622        int insertIndex = -1;
623        int childCount = getChildCount();
624        for (int i = 0; i < childCount; i++) {
625            Task tvTask = ((TaskView) getChildAt(i)).getTask();
626            if (mStack.containsTask(task) && (mStack.indexOfTask(task) < mStack.indexOfTask(tvTask))) {
627                insertIndex = i;
628                break;
629            }
630        }
631
632        // Add/attach the view to the hierarchy
633        Console.log(Constants.DebugFlags.ViewPool.PoolCallbacks, "  [TaskStackView|insertIndex]",
634                "" + insertIndex);
635        if (isNewView) {
636            addView(tv, insertIndex);
637
638            // Set the callbacks and listeners for this new view
639            tv.setOnClickListener(this);
640            tv.setCallbacks(this);
641        } else {
642            attachViewToParent(tv, insertIndex, tv.getLayoutParams());
643        }
644
645        // Enable hw layers on this view if hw layers are enabled on the stack
646        if (mHwLayersRefCount > 0) {
647            tv.enableHwLayers();
648        }
649    }
650
651    @Override
652    public boolean hasPreferredData(TaskView tv, Task preferredData) {
653        return (tv.getTask() == preferredData);
654    }
655
656    /**** TaskViewCallbacks Implementation ****/
657
658    @Override
659    public void onTaskIconClicked(TaskView tv) {
660        Console.log(Constants.DebugFlags.UI.ClickEvents, "[TaskStack|Clicked|Icon]",
661                tv.getTask() + " is currently filtered: " + mStack.hasFilteredTasks(),
662                Console.AnsiCyan);
663        if (Constants.DebugFlags.App.EnableTaskFiltering) {
664            if (mStack.hasFilteredTasks()) {
665                mStack.unfilterTasks();
666            } else {
667                mStack.filterTasks(tv.getTask());
668            }
669        } else {
670            Console.logError(getContext(), "Task Filtering TBD");
671        }
672    }
673
674    /**** View.OnClickListener Implementation ****/
675
676    @Override
677    public void onClick(View v) {
678        TaskView tv = (TaskView) v;
679        Task task = tv.getTask();
680        Console.log(Constants.DebugFlags.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]",
681                task + " cb: " + mCb);
682
683        if (mCb != null) {
684            mCb.onTaskLaunched(this, tv, mStack, task);
685        }
686    }
687}
688
689/* Handles touch events */
690class TaskStackViewTouchHandler implements SwipeHelper.Callback {
691    static int INACTIVE_POINTER_ID = -1;
692
693    TaskStackView mSv;
694    VelocityTracker mVelocityTracker;
695
696    boolean mIsScrolling;
697
698    int mInitialMotionX, mInitialMotionY;
699    int mLastMotionX, mLastMotionY;
700    int mActivePointerId = INACTIVE_POINTER_ID;
701    TaskView mActiveTaskView = null;
702
703    int mTotalScrollMotion;
704    int mMinimumVelocity;
705    int mMaximumVelocity;
706    // The scroll touch slop is used to calculate when we start scrolling
707    int mScrollTouchSlop;
708    // The page touch slop is used to calculate when we start swiping
709    float mPagingTouchSlop;
710
711    SwipeHelper mSwipeHelper;
712    boolean mInterceptedBySwipeHelper;
713
714    public TaskStackViewTouchHandler(Context context, TaskStackView sv) {
715        ViewConfiguration configuration = ViewConfiguration.get(context);
716        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
717        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
718        mScrollTouchSlop = configuration.getScaledTouchSlop();
719        mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
720        mSv = sv;
721
722
723        float densityScale = context.getResources().getDisplayMetrics().density;
724        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop);
725        mSwipeHelper.setMinAlpha(1f);
726    }
727
728    /** Velocity tracker helpers */
729    void initOrResetVelocityTracker() {
730        if (mVelocityTracker == null) {
731            mVelocityTracker = VelocityTracker.obtain();
732        } else {
733            mVelocityTracker.clear();
734        }
735    }
736    void initVelocityTrackerIfNotExists() {
737        if (mVelocityTracker == null) {
738            mVelocityTracker = VelocityTracker.obtain();
739        }
740    }
741    void recycleVelocityTracker() {
742        if (mVelocityTracker != null) {
743            mVelocityTracker.recycle();
744            mVelocityTracker = null;
745        }
746    }
747
748    /** Returns the view at the specified coordinates */
749    TaskView findViewAtPoint(int x, int y) {
750        int childCount = mSv.getChildCount();
751        for (int i = childCount - 1; i >= 0; i--) {
752            TaskView tv = (TaskView) mSv.getChildAt(i);
753            if (tv.getVisibility() == View.VISIBLE) {
754                if (mSv.isTransformedTouchPointInView(x, y, tv)) {
755                    return tv;
756                }
757            }
758        }
759        return null;
760    }
761
762    /** Touch preprocessing for handling below */
763    public boolean onInterceptTouchEvent(MotionEvent ev) {
764        Console.log(Constants.DebugFlags.UI.TouchEvents,
765                "[TaskStackViewTouchHandler|interceptTouchEvent]",
766                Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
767
768        // Return early if we have no children
769        boolean hasChildren = (mSv.getChildCount() > 0);
770        if (!hasChildren) {
771            return false;
772        }
773
774        // Pass through to swipe helper if we are swiping
775        mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
776        if (mInterceptedBySwipeHelper) {
777            return true;
778        }
779
780        boolean wasScrolling = !mSv.mScroller.isFinished() ||
781                (mSv.mScrollAnimator != null && mSv.mScrollAnimator.isRunning());
782        int action = ev.getAction();
783        switch (action & MotionEvent.ACTION_MASK) {
784            case MotionEvent.ACTION_DOWN: {
785                // Save the touch down info
786                mInitialMotionX = mLastMotionX = (int) ev.getX();
787                mInitialMotionY = mLastMotionY = (int) ev.getY();
788                mActivePointerId = ev.getPointerId(0);
789                mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
790                // Stop the current scroll if it is still flinging
791                mSv.abortScroller();
792                mSv.abortBoundScrollAnimation();
793                // Initialize the velocity tracker
794                initOrResetVelocityTracker();
795                mVelocityTracker.addMovement(ev);
796                // Check if the scroller is finished yet
797                mIsScrolling = !mSv.mScroller.isFinished();
798                break;
799            }
800            case MotionEvent.ACTION_MOVE: {
801                if (mActivePointerId == INACTIVE_POINTER_ID) break;
802
803                int activePointerIndex = ev.findPointerIndex(mActivePointerId);
804                int y = (int) ev.getY(activePointerIndex);
805                int x = (int) ev.getX(activePointerIndex);
806                if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
807                    // Save the touch move info
808                    mIsScrolling = true;
809                    // Initialize the velocity tracker if necessary
810                    initVelocityTrackerIfNotExists();
811                    mVelocityTracker.addMovement(ev);
812                    // Disallow parents from intercepting touch events
813                    final ViewParent parent = mSv.getParent();
814                    if (parent != null) {
815                        parent.requestDisallowInterceptTouchEvent(true);
816                    }
817                    // Enable HW layers
818                    mSv.addHwLayersRefCount("stackScroll");
819                }
820
821                mLastMotionX = x;
822                mLastMotionY = y;
823                break;
824            }
825            case MotionEvent.ACTION_CANCEL:
826            case MotionEvent.ACTION_UP: {
827                // Animate the scroll back if we've cancelled
828                mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration);
829                // Disable HW layers
830                if (mIsScrolling) {
831                    mSv.decHwLayersRefCount("stackScroll");
832                }
833                // Reset the drag state and the velocity tracker
834                mIsScrolling = false;
835                mActivePointerId = INACTIVE_POINTER_ID;
836                mActiveTaskView = null;
837                mTotalScrollMotion = 0;
838                recycleVelocityTracker();
839                break;
840            }
841        }
842
843        return wasScrolling || mIsScrolling;
844    }
845
846    /** Handles touch events once we have intercepted them */
847    public boolean onTouchEvent(MotionEvent ev) {
848        Console.log(Constants.DebugFlags.TaskStack.SynchronizeViewsWithModel,
849                "[TaskStackViewTouchHandler|touchEvent]",
850                Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
851
852        // Short circuit if we have no children
853        boolean hasChildren = (mSv.getChildCount() > 0);
854        if (!hasChildren) {
855            return false;
856        }
857
858        // Pass through to swipe helper if we are swiping
859        if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
860            return true;
861        }
862
863        // Update the velocity tracker
864        initVelocityTrackerIfNotExists();
865        mVelocityTracker.addMovement(ev);
866
867        int action = ev.getAction();
868        switch (action & MotionEvent.ACTION_MASK) {
869            case MotionEvent.ACTION_DOWN: {
870                // Save the touch down info
871                mInitialMotionX = mLastMotionX = (int) ev.getX();
872                mInitialMotionY = mLastMotionY = (int) ev.getY();
873                mActivePointerId = ev.getPointerId(0);
874                mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
875                // Stop the current scroll if it is still flinging
876                mSv.abortScroller();
877                mSv.abortBoundScrollAnimation();
878                // Initialize the velocity tracker
879                initOrResetVelocityTracker();
880                mVelocityTracker.addMovement(ev);
881                // Disallow parents from intercepting touch events
882                final ViewParent parent = mSv.getParent();
883                if (parent != null) {
884                    parent.requestDisallowInterceptTouchEvent(true);
885                }
886                break;
887            }
888            case MotionEvent.ACTION_POINTER_DOWN: {
889                final int index = ev.getActionIndex();
890                mActivePointerId = ev.getPointerId(index);
891                mLastMotionX = (int) ev.getX(index);
892                mLastMotionY = (int) ev.getY(index);
893                break;
894            }
895            case MotionEvent.ACTION_MOVE: {
896                if (mActivePointerId == INACTIVE_POINTER_ID) break;
897
898                int activePointerIndex = ev.findPointerIndex(mActivePointerId);
899                int x = (int) ev.getX(activePointerIndex);
900                int y = (int) ev.getY(activePointerIndex);
901                int deltaY = mLastMotionY - y;
902                if (!mIsScrolling) {
903                    if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
904                        mIsScrolling = true;
905                        // Initialize the velocity tracker
906                        initOrResetVelocityTracker();
907                        mVelocityTracker.addMovement(ev);
908                        // Disallow parents from intercepting touch events
909                        final ViewParent parent = mSv.getParent();
910                        if (parent != null) {
911                            parent.requestDisallowInterceptTouchEvent(true);
912                        }
913                        // Enable HW layers
914                        mSv.addHwLayersRefCount("stackScroll");
915                    }
916                }
917                if (mIsScrolling) {
918                    mSv.setStackScroll(mSv.getStackScroll() + deltaY);
919                    if (mSv.isScrollOutOfBounds()) {
920                        mVelocityTracker.clear();
921                    }
922                }
923                mLastMotionX = x;
924                mLastMotionY = y;
925                mTotalScrollMotion += Math.abs(deltaY);
926                break;
927            }
928            case MotionEvent.ACTION_UP: {
929                final VelocityTracker velocityTracker = mVelocityTracker;
930                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
931                int velocity = (int) velocityTracker.getYVelocity(mActivePointerId);
932
933                if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {
934                    // Enable HW layers on the stack
935                    mSv.addHwLayersRefCount("flingScroll");
936                    int overscrollRange = (int) (Math.min(1f,
937                            Math.abs((float) velocity / mMaximumVelocity)) *
938                            Constants.Values.TaskStackView.TaskStackOverscrollRange);
939
940                    Console.log(Constants.DebugFlags.UI.TouchEvents,
941                            "[TaskStackViewTouchHandler|fling]",
942                            "scroll: " + mSv.getStackScroll() + " velocity: " + velocity +
943                                    " maxVelocity: " + mMaximumVelocity +
944                                    " overscrollRange: " + overscrollRange,
945                            Console.AnsiGreen);
946
947                    // Fling scroll
948                    mSv.mScroller.fling(0, mSv.getStackScroll(),
949                            0, -velocity,
950                            0, 0,
951                            mSv.mMinScroll, mSv.mMaxScroll,
952                            0, overscrollRange);
953                    // Invalidate to kick off computeScroll
954                    mSv.invalidate();
955                } else if (mSv.isScrollOutOfBounds()) {
956                    // Animate the scroll back into bounds
957                    // XXX: Make this animation a function of the velocity OR distance
958                    mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration);
959                }
960
961                if (mIsScrolling) {
962                    // Disable HW layers
963                    mSv.decHwLayersRefCount("stackScroll");
964                }
965                mActivePointerId = INACTIVE_POINTER_ID;
966                mIsScrolling = false;
967                mTotalScrollMotion = 0;
968                recycleVelocityTracker();
969                break;
970            }
971            case MotionEvent.ACTION_POINTER_UP: {
972                int pointerIndex = ev.getActionIndex();
973                int pointerId = ev.getPointerId(pointerIndex);
974                if (pointerId == mActivePointerId) {
975                    // Select a new active pointer id and reset the motion state
976                    final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
977                    mActivePointerId = ev.getPointerId(newPointerIndex);
978                    mLastMotionX = (int) ev.getX(newPointerIndex);
979                    mLastMotionY = (int) ev.getY(newPointerIndex);
980                    mVelocityTracker.clear();
981                }
982                break;
983            }
984            case MotionEvent.ACTION_CANCEL: {
985                if (mIsScrolling) {
986                    // Disable HW layers
987                    mSv.decHwLayersRefCount("stackScroll");
988                }
989                if (mSv.isScrollOutOfBounds()) {
990                    // Animate the scroll back into bounds
991                    // XXX: Make this animation a function of the velocity OR distance
992                    mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration);
993                }
994                mActivePointerId = INACTIVE_POINTER_ID;
995                mIsScrolling = false;
996                mTotalScrollMotion = 0;
997                recycleVelocityTracker();
998                break;
999            }
1000        }
1001        return true;
1002    }
1003
1004    /**** SwipeHelper Implementation ****/
1005
1006    @Override
1007    public View getChildAtPosition(MotionEvent ev) {
1008        return findViewAtPoint((int) ev.getX(), (int) ev.getY());
1009    }
1010
1011    @Override
1012    public boolean canChildBeDismissed(View v) {
1013        return true;
1014    }
1015
1016    @Override
1017    public void onBeginDrag(View v) {
1018        // Enable HW layers
1019        mSv.addHwLayersRefCount("swipeBegin");
1020        // Disallow parents from intercepting touch events
1021        final ViewParent parent = mSv.getParent();
1022        if (parent != null) {
1023            parent.requestDisallowInterceptTouchEvent(true);
1024        }
1025    }
1026
1027    @Override
1028    public void onChildDismissed(View v) {
1029        TaskView tv = (TaskView) v;
1030        Task task = tv.getTask();
1031        Activity activity = (Activity) mSv.getContext();
1032
1033        // Remove the task from the view
1034        mSv.mStack.removeTask(task);
1035
1036        // Remove any stored data from the loader
1037        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
1038        loader.deleteTaskData(task);
1039
1040        // Remove the task from activity manager
1041        final ActivityManager am = (ActivityManager)
1042                activity.getSystemService(Context.ACTIVITY_SERVICE);
1043        if (am != null) {
1044            am.removeTask(tv.getTask().key.id,
1045                    ActivityManager.REMOVE_TASK_KILL_PROCESS);
1046        }
1047
1048        // If there are no remaining tasks, then just close the activity
1049        if (mSv.mStack.getTaskCount() == 0) {
1050            activity.finish();
1051        }
1052
1053        // Disable HW layers
1054        mSv.decHwLayersRefCount("swipeComplete");
1055    }
1056
1057    @Override
1058    public void onSnapBackCompleted(View v) {
1059        // Do Nothing
1060    }
1061
1062    @Override
1063    public void onDragCancelled(View v) {
1064        // Disable HW layers
1065        mSv.decHwLayersRefCount("swipeCancelled");
1066    }
1067}
1068