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