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