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.Canvas;
     23 import android.graphics.Matrix;
     24 import android.graphics.Rect;
     25 import android.os.Bundle;
     26 import android.view.LayoutInflater;
     27 import android.view.MotionEvent;
     28 import android.view.View;
     29 import android.view.accessibility.AccessibilityEvent;
     30 import android.view.accessibility.AccessibilityNodeInfo;
     31 import android.widget.FrameLayout;
     32 
     33 import com.android.internal.logging.MetricsLogger;
     34 import com.android.systemui.R;
     35 import com.android.systemui.recents.Constants;
     36 import com.android.systemui.recents.RecentsConfiguration;
     37 import com.android.systemui.recents.misc.DozeTrigger;
     38 import com.android.systemui.recents.misc.SystemServicesProxy;
     39 import com.android.systemui.recents.misc.Utilities;
     40 import com.android.systemui.recents.model.RecentsPackageMonitor;
     41 import com.android.systemui.recents.model.RecentsTaskLoader;
     42 import com.android.systemui.recents.model.Task;
     43 import com.android.systemui.recents.model.TaskStack;
     44 import com.android.systemui.statusbar.DismissView;
     45 
     46 import java.util.ArrayList;
     47 import java.util.Collections;
     48 import java.util.HashMap;
     49 import java.util.HashSet;
     50 import java.util.Iterator;
     51 import java.util.List;
     52 
     53 
     54 /* The visual representation of a task stack view */
     55 public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
     56         TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks,
     57         ViewPool.ViewPoolConsumer<TaskView, Task>, RecentsPackageMonitor.PackageCallbacks {
     58 
     59     /** The TaskView callbacks */
     60     interface TaskStackViewCallbacks {
     61         public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
     62                                       boolean lockToTask);
     63         public void onTaskViewAppInfoClicked(Task t);
     64         public void onTaskViewDismissed(Task t);
     65         public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks);
     66         public void onTaskStackFilterTriggered();
     67         public void onTaskStackUnfilterTriggered();
     68 
     69         public void onTaskResize(Task t);
     70     }
     71     RecentsConfiguration mConfig;
     72 
     73     TaskStack mStack;
     74     TaskStackViewLayoutAlgorithm mLayoutAlgorithm;
     75     TaskStackViewFilterAlgorithm mFilterAlgorithm;
     76     TaskStackViewScroller mStackScroller;
     77     TaskStackViewTouchHandler mTouchHandler;
     78     TaskStackViewCallbacks mCb;
     79     ViewPool<TaskView, Task> mViewPool;
     80     ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>();
     81     DozeTrigger mUIDozeTrigger;
     82     DebugOverlayView mDebugOverlay;
     83     Rect mTaskStackBounds = new Rect();
     84     DismissView mDismissAllButton;
     85     boolean mDismissAllButtonAnimating;
     86     int mFocusedTaskIndex = -1;
     87     int mPrevAccessibilityFocusedIndex = -1;
     88     // Optimizations
     89     int mStackViewsAnimationDuration;
     90     boolean mStackViewsDirty = true;
     91     boolean mStackViewsClipDirty = true;
     92     boolean mAwaitingFirstLayout = true;
     93     boolean mStartEnterAnimationRequestedAfterLayout;
     94     boolean mStartEnterAnimationCompleted;
     95     ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
     96     int[] mTmpVisibleRange = new int[2];
     97     float[] mTmpCoord = new float[2];
     98     Matrix mTmpMatrix = new Matrix();
     99     Rect mTmpRect = new Rect();
    100     TaskViewTransform mTmpTransform = new TaskViewTransform();
    101     HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<Task, TaskView>();
    102     ArrayList<TaskView> mTaskViews = new ArrayList<TaskView>();
    103     List<TaskView> mImmutableTaskViews = new ArrayList<TaskView>();
    104     LayoutInflater mInflater;
    105     boolean mLayersDisabled;
    106 
    107     // A convenience update listener to request updating clipping of tasks
    108     ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
    109             new ValueAnimator.AnimatorUpdateListener() {
    110         @Override
    111         public void onAnimationUpdate(ValueAnimator animation) {
    112             requestUpdateStackViewsClip();
    113         }
    114     };
    115 
    116     public TaskStackView(Context context, TaskStack stack) {
    117         super(context);
    118         // Set the stack first
    119         setStack(stack);
    120         mConfig = RecentsConfiguration.getInstance();
    121         mViewPool = new ViewPool<TaskView, Task>(context, this);
    122         mInflater = LayoutInflater.from(context);
    123         mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(mConfig);
    124         mFilterAlgorithm = new TaskStackViewFilterAlgorithm(mConfig, this, mViewPool);
    125         mStackScroller = new TaskStackViewScroller(context, mConfig, mLayoutAlgorithm);
    126         mStackScroller.setCallbacks(this);
    127         mTouchHandler = new TaskStackViewTouchHandler(context, this, mConfig, mStackScroller);
    128         mUIDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() {
    129             @Override
    130             public void run() {
    131                 // Show the task bar dismiss buttons
    132                 List<TaskView> taskViews = getTaskViews();
    133                 int taskViewCount = taskViews.size();
    134                 for (int i = 0; i < taskViewCount; i++) {
    135                     TaskView tv = taskViews.get(i);
    136                     tv.startNoUserInteractionAnimation();
    137                 }
    138             }
    139         });
    140         setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
    141     }
    142 
    143     /** Sets the callbacks */
    144     void setCallbacks(TaskStackViewCallbacks cb) {
    145         mCb = cb;
    146     }
    147 
    148     /** Sets the task stack */
    149     void setStack(TaskStack stack) {
    150         // Set the new stack
    151         mStack = stack;
    152         if (mStack != null) {
    153             mStack.setCallbacks(this);
    154         }
    155         // Layout again with the new stack
    156         requestLayout();
    157     }
    158 
    159     /** Returns the task stack. */
    160     TaskStack getStack() {
    161         return mStack;
    162     }
    163 
    164     /** Sets the debug overlay */
    165     public void setDebugOverlay(DebugOverlayView overlay) {
    166         mDebugOverlay = overlay;
    167     }
    168 
    169     /** Updates the list of task views */
    170     void updateTaskViewsList() {
    171         mTaskViews.clear();
    172         int childCount = getChildCount();
    173         for (int i = 0; i < childCount; i++) {
    174             View v = getChildAt(i);
    175             if (v instanceof TaskView) {
    176                 mTaskViews.add((TaskView) v);
    177             }
    178         }
    179         mImmutableTaskViews = Collections.unmodifiableList(mTaskViews);
    180     }
    181 
    182     /** Gets the list of task views */
    183     List<TaskView> getTaskViews() {
    184         return mImmutableTaskViews;
    185     }
    186 
    187     /** Resets this TaskStackView for reuse. */
    188     void reset() {
    189         // Reset the focused task
    190         resetFocusedTask();
    191 
    192         // Return all the views to the pool
    193         List<TaskView> taskViews = getTaskViews();
    194         int taskViewCount = taskViews.size();
    195         for (int i = taskViewCount - 1; i >= 0; i--) {
    196             mViewPool.returnViewToPool(taskViews.get(i));
    197         }
    198 
    199         // Mark each task view for relayout
    200         if (mViewPool != null) {
    201             Iterator<TaskView> iter = mViewPool.poolViewIterator();
    202             if (iter != null) {
    203                 while (iter.hasNext()) {
    204                     TaskView tv = iter.next();
    205                     tv.reset();
    206                 }
    207             }
    208         }
    209 
    210         // Reset the stack state
    211         mStack.reset();
    212         mStackViewsDirty = true;
    213         mStackViewsClipDirty = true;
    214         mAwaitingFirstLayout = true;
    215         mPrevAccessibilityFocusedIndex = -1;
    216         if (mUIDozeTrigger != null) {
    217             mUIDozeTrigger.stopDozing();
    218             mUIDozeTrigger.resetTrigger();
    219         }
    220         mStackScroller.reset();
    221     }
    222 
    223     /** Requests that the views be synchronized with the model */
    224     void requestSynchronizeStackViewsWithModel() {
    225         requestSynchronizeStackViewsWithModel(0);
    226     }
    227     void requestSynchronizeStackViewsWithModel(int duration) {
    228         if (!mStackViewsDirty) {
    229             invalidate();
    230             mStackViewsDirty = true;
    231         }
    232         if (mAwaitingFirstLayout) {
    233             // Skip the animation if we are awaiting first layout
    234             mStackViewsAnimationDuration = 0;
    235         } else {
    236             mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration);
    237         }
    238     }
    239 
    240     /** Requests that the views clipping be updated. */
    241     void requestUpdateStackViewsClip() {
    242         if (!mStackViewsClipDirty) {
    243             invalidate();
    244             mStackViewsClipDirty = true;
    245         }
    246     }
    247 
    248     /** Finds the child view given a specific task. */
    249     public TaskView getChildViewForTask(Task t) {
    250         List<TaskView> taskViews = getTaskViews();
    251         int taskViewCount = taskViews.size();
    252         for (int i = 0; i < taskViewCount; i++) {
    253             TaskView tv = taskViews.get(i);
    254             if (tv.getTask() == t) {
    255                 return tv;
    256             }
    257         }
    258         return null;
    259     }
    260 
    261     /** Returns the stack algorithm for this task stack. */
    262     public TaskStackViewLayoutAlgorithm getStackAlgorithm() {
    263         return mLayoutAlgorithm;
    264     }
    265 
    266     /**
    267      * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
    268      */
    269     private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
    270                                        ArrayList<Task> tasks,
    271                                        float stackScroll,
    272                                        int[] visibleRangeOut,
    273                                        boolean boundTranslationsToRect) {
    274         int taskTransformCount = taskTransforms.size();
    275         int taskCount = tasks.size();
    276         int frontMostVisibleIndex = -1;
    277         int backMostVisibleIndex = -1;
    278 
    279         // We can reuse the task transforms where possible to reduce object allocation
    280         if (taskTransformCount < taskCount) {
    281             // If there are less transforms than tasks, then add as many transforms as necessary
    282             for (int i = taskTransformCount; i < taskCount; i++) {
    283                 taskTransforms.add(new TaskViewTransform());
    284             }
    285         } else if (taskTransformCount > taskCount) {
    286             // If there are more transforms than tasks, then just subset the transform list
    287             taskTransforms.subList(0, taskCount);
    288         }
    289 
    290         // Update the stack transforms
    291         TaskViewTransform prevTransform = null;
    292         for (int i = taskCount - 1; i >= 0; i--) {
    293             TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(tasks.get(i),
    294                     stackScroll, taskTransforms.get(i), prevTransform);
    295             if (transform.visible) {
    296                 if (frontMostVisibleIndex < 0) {
    297                     frontMostVisibleIndex = i;
    298                 }
    299                 backMostVisibleIndex = i;
    300             } else {
    301                 if (backMostVisibleIndex != -1) {
    302                     // We've reached the end of the visible range, so going down the rest of the
    303                     // stack, we can just reset the transforms accordingly
    304                     while (i >= 0) {
    305                         taskTransforms.get(i).reset();
    306                         i--;
    307                     }
    308                     break;
    309                 }
    310             }
    311 
    312             if (boundTranslationsToRect) {
    313                 transform.translationY = Math.min(transform.translationY,
    314                         mLayoutAlgorithm.mViewRect.bottom);
    315             }
    316             prevTransform = transform;
    317         }
    318         if (visibleRangeOut != null) {
    319             visibleRangeOut[0] = frontMostVisibleIndex;
    320             visibleRangeOut[1] = backMostVisibleIndex;
    321         }
    322         return frontMostVisibleIndex != -1 && backMostVisibleIndex != -1;
    323     }
    324 
    325     /** Synchronizes the views with the model */
    326     boolean synchronizeStackViewsWithModel() {
    327         if (mStackViewsDirty) {
    328             RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
    329             SystemServicesProxy ssp = loader.getSystemServicesProxy();
    330 
    331             // Get all the task transforms
    332             ArrayList<Task> tasks = mStack.getTasks();
    333             float stackScroll = mStackScroller.getStackScroll();
    334             int[] visibleRange = mTmpVisibleRange;
    335             boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks,
    336                     stackScroll, visibleRange, false);
    337             if (mDebugOverlay != null) {
    338                 mDebugOverlay.setText("vis[" + visibleRange[1] + "-" + visibleRange[0] + "]");
    339             }
    340 
    341             // Inflate and add the dismiss button if necessary
    342             if (Constants.DebugFlags.App.EnableDismissAll && mDismissAllButton == null) {
    343                 mDismissAllButton = (DismissView)
    344                         mInflater.inflate(R.layout.recents_dismiss_button, this, false);
    345                 mDismissAllButton.setOnButtonClickListener(new View.OnClickListener() {
    346                     @Override
    347                     public void onClick(View v) {
    348                         mStack.removeAllTasks();
    349                     }
    350                 });
    351                 addView(mDismissAllButton, 0);
    352             }
    353 
    354             // Return all the invisible children to the pool
    355             mTmpTaskViewMap.clear();
    356             List<TaskView> taskViews = getTaskViews();
    357             int taskViewCount = taskViews.size();
    358             boolean reaquireAccessibilityFocus = false;
    359             for (int i = taskViewCount - 1; i >= 0; i--) {
    360                 TaskView tv = taskViews.get(i);
    361                 Task task = tv.getTask();
    362                 int taskIndex = mStack.indexOfTask(task);
    363                 if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) {
    364                     mTmpTaskViewMap.put(task, tv);
    365                 } else {
    366                     mViewPool.returnViewToPool(tv);
    367                     reaquireAccessibilityFocus |= (i == mPrevAccessibilityFocusedIndex);
    368 
    369                     // Hide the dismiss button if the front most task is invisible
    370                     if (task == mStack.getFrontMostTask()) {
    371                         hideDismissAllButton(null);
    372                     }
    373                 }
    374             }
    375 
    376             // Pick up all the newly visible children and update all the existing children
    377             for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) {
    378                 Task task = tasks.get(i);
    379                 TaskViewTransform transform = mCurrentTaskTransforms.get(i);
    380                 TaskView tv = mTmpTaskViewMap.get(task);
    381                 int taskIndex = mStack.indexOfTask(task);
    382 
    383                 if (tv == null) {
    384                     tv = mViewPool.pickUpViewFromPool(task, task);
    385                     if (mLayersDisabled) {
    386                         tv.disableLayersForOneFrame();
    387                     }
    388                     if (mStackViewsAnimationDuration > 0) {
    389                         // For items in the list, put them in start animating them from the
    390                         // approriate ends of the list where they are expected to appear
    391                         if (Float.compare(transform.p, 0f) <= 0) {
    392                             mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpTransform, null);
    393                         } else {
    394                             mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpTransform, null);
    395                         }
    396                         tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0);
    397                     }
    398 
    399                     // If we show the front most task view then ensure that the dismiss button
    400                     // is visible too.
    401                     if (!mAwaitingFirstLayout && (task == mStack.getFrontMostTask())) {
    402                         showDismissAllButton();
    403                     }
    404                 }
    405 
    406                 // Animate the task into place
    407                 tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex),
    408                         mStackViewsAnimationDuration, mRequestUpdateClippingListener);
    409 
    410                 // Request accessibility focus on the next view if we removed the task
    411                 // that previously held accessibility focus
    412                 if (reaquireAccessibilityFocus) {
    413                     taskViews = getTaskViews();
    414                     taskViewCount = taskViews.size();
    415                     if (taskViewCount > 0 && ssp.isTouchExplorationEnabled() &&
    416                             mPrevAccessibilityFocusedIndex != -1) {
    417                         TaskView atv = taskViews.get(taskViewCount - 1);
    418                         int indexOfTask = mStack.indexOfTask(atv.getTask());
    419                         if (mPrevAccessibilityFocusedIndex != indexOfTask) {
    420                             tv.requestAccessibilityFocus();
    421                             mPrevAccessibilityFocusedIndex = indexOfTask;
    422                         }
    423                     }
    424                 }
    425             }
    426 
    427             // Reset the request-synchronize params
    428             mStackViewsAnimationDuration = 0;
    429             mStackViewsDirty = false;
    430             mStackViewsClipDirty = true;
    431             return true;
    432         }
    433         return false;
    434     }
    435 
    436     /** Updates the clip for each of the task views. */
    437     void clipTaskViews() {
    438         // Update the clip on each task child
    439         List<TaskView> taskViews = getTaskViews();
    440         int taskViewCount = taskViews.size();
    441         for (int i = 0; i < taskViewCount - 1; i++) {
    442             TaskView tv = taskViews.get(i);
    443             TaskView nextTv = null;
    444             TaskView tmpTv = null;
    445             int clipBottom = 0;
    446             if (tv.shouldClipViewInStack()) {
    447                 // Find the next view to clip against
    448                 int nextIndex = i;
    449                 while (nextIndex < (taskViewCount - 1)) {
    450                     tmpTv = taskViews.get(++nextIndex);
    451                     if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
    452                         nextTv = tmpTv;
    453                         break;
    454                     }
    455                 }
    456 
    457                 // Clip against the next view, this is just an approximation since we are
    458                 // stacked and we can make assumptions about the visibility of the this
    459                 // task relative to the ones in front of it.
    460                 if (nextTv != null) {
    461                     // Map the top edge of next task view into the local space of the current
    462                     // task view to find the clip amount in local space
    463                     mTmpCoord[0] = mTmpCoord[1] = 0;
    464                     Utilities.mapCoordInDescendentToSelf(nextTv, this, mTmpCoord, false);
    465                     Utilities.mapCoordInSelfToDescendent(tv, this, mTmpCoord, mTmpMatrix);
    466                     clipBottom = (int) Math.floor(tv.getMeasuredHeight() - mTmpCoord[1]
    467                             - nextTv.getPaddingTop() - 1);
    468                 }
    469             }
    470             tv.getViewBounds().setClipBottom(clipBottom);
    471         }
    472         if (taskViewCount > 0) {
    473             // The front most task should never be clipped
    474             TaskView tv = taskViews.get(taskViewCount - 1);
    475             tv.getViewBounds().setClipBottom(0);
    476         }
    477         mStackViewsClipDirty = false;
    478     }
    479 
    480     /** The stack insets to apply to the stack contents */
    481     public void setStackInsetRect(Rect r) {
    482         mTaskStackBounds.set(r);
    483     }
    484 
    485     /** Updates the min and max virtual scroll bounds */
    486     void updateMinMaxScroll(boolean boundScrollToNewMinMax, boolean launchedWithAltTab,
    487             boolean launchedFromHome) {
    488         // Compute the min and max scroll values
    489         mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks(), launchedWithAltTab, launchedFromHome);
    490 
    491         // Debug logging
    492         if (boundScrollToNewMinMax) {
    493             mStackScroller.boundScroll();
    494         }
    495     }
    496 
    497     /** Returns the scroller. */
    498     public TaskStackViewScroller getScroller() {
    499         return mStackScroller;
    500     }
    501 
    502     /** Focuses the task at the specified index in the stack */
    503     void focusTask(int taskIndex, boolean scrollToNewPosition, final boolean animateFocusedState) {
    504         // Return early if the task is already focused
    505         if (taskIndex == mFocusedTaskIndex) return;
    506 
    507         if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
    508             mFocusedTaskIndex = taskIndex;
    509             mPrevAccessibilityFocusedIndex = taskIndex;
    510 
    511             // Focus the view if possible, otherwise, focus the view after we scroll into position
    512             final Task t = mStack.getTasks().get(mFocusedTaskIndex);
    513             Runnable postScrollRunnable = new Runnable() {
    514                 @Override
    515                 public void run() {
    516                     TaskView tv = getChildViewForTask(t);
    517                     if (tv != null) {
    518                         tv.setFocusedTask(animateFocusedState);
    519                         tv.requestAccessibilityFocus();
    520                     }
    521                 }
    522             };
    523 
    524             // Scroll the view into position (just center it in the curve)
    525             if (scrollToNewPosition) {
    526                 float newScroll = mLayoutAlgorithm.getStackScrollForTask(t) - 0.5f;
    527                 newScroll = mStackScroller.getBoundedStackScroll(newScroll);
    528                 mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, postScrollRunnable);
    529             } else {
    530                 if (postScrollRunnable != null) {
    531                     postScrollRunnable.run();
    532                 }
    533             }
    534 
    535         }
    536     }
    537 
    538     /**
    539      * Ensures that there is a task focused, if nothing is focused, then we will use the task
    540      * at the center of the visible stack.
    541      */
    542     public boolean ensureFocusedTask(boolean findClosestToCenter) {
    543         if (mFocusedTaskIndex < 0) {
    544             List<TaskView> taskViews = getTaskViews();
    545             int taskViewCount = taskViews.size();
    546             if (findClosestToCenter) {
    547                 // If there is no task focused, then find the task that is closes to the center
    548                 // of the screen and use that as the currently focused task
    549                 int x = mLayoutAlgorithm.mStackVisibleRect.centerX();
    550                 int y = mLayoutAlgorithm.mStackVisibleRect.centerY();
    551                 for (int i = taskViewCount - 1; i >= 0; i--) {
    552                     TaskView tv = taskViews.get(i);
    553                     tv.getHitRect(mTmpRect);
    554                     if (mTmpRect.contains(x, y)) {
    555                         mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
    556                         mPrevAccessibilityFocusedIndex = mFocusedTaskIndex;
    557                         break;
    558                     }
    559                 }
    560             }
    561             // If we can't find the center task, then use the front most index
    562             if (mFocusedTaskIndex < 0 && taskViewCount > 0) {
    563                 TaskView tv = taskViews.get(taskViewCount - 1);
    564                 mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
    565                 mPrevAccessibilityFocusedIndex = mFocusedTaskIndex;
    566             }
    567         }
    568         return mFocusedTaskIndex >= 0;
    569     }
    570 
    571     /**
    572      * Focuses the next task in the stack.
    573      * @param animateFocusedState determines whether to actually draw the highlight along with
    574      *                            the change in focus, as well as whether to scroll to fit the
    575      *                            task into view.
    576      */
    577     public void focusNextTask(boolean forward, boolean animateFocusedState) {
    578         // Find the next index to focus
    579         int numTasks = mStack.getTaskCount();
    580         if (numTasks == 0) return;
    581 
    582         int direction = (forward ? -1 : 1);
    583         int newIndex = mFocusedTaskIndex + direction;
    584         if (newIndex >= 0 && newIndex <= (numTasks - 1)) {
    585             newIndex = Math.max(0, Math.min(numTasks - 1, newIndex));
    586             focusTask(newIndex, true, animateFocusedState);
    587         }
    588     }
    589 
    590     /** Dismisses the focused task. */
    591     public void dismissFocusedTask() {
    592         // Return early if the focused task index is invalid
    593         if (mFocusedTaskIndex < 0 || mFocusedTaskIndex >= mStack.getTaskCount()) {
    594             mFocusedTaskIndex = -1;
    595             return;
    596         }
    597 
    598         Task t = mStack.getTasks().get(mFocusedTaskIndex);
    599         TaskView tv = getChildViewForTask(t);
    600         tv.dismissTask();
    601     }
    602 
    603     /** Resets the focused task. */
    604     void resetFocusedTask() {
    605         if ((0 <= mFocusedTaskIndex) && (mFocusedTaskIndex < mStack.getTaskCount())) {
    606             Task t = mStack.getTasks().get(mFocusedTaskIndex);
    607             TaskView tv = getChildViewForTask(t);
    608             if (tv != null) {
    609                 tv.unsetFocusedTask();
    610             }
    611         }
    612         mFocusedTaskIndex = -1;
    613         mPrevAccessibilityFocusedIndex = -1;
    614     }
    615 
    616     @Override
    617     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    618         super.onInitializeAccessibilityEvent(event);
    619         List<TaskView> taskViews = getTaskViews();
    620         int taskViewCount = taskViews.size();
    621         if (taskViewCount > 0) {
    622             TaskView backMostTask = taskViews.get(0);
    623             TaskView frontMostTask = taskViews.get(taskViewCount - 1);
    624             event.setFromIndex(mStack.indexOfTask(backMostTask.getTask()));
    625             event.setToIndex(mStack.indexOfTask(frontMostTask.getTask()));
    626             event.setContentDescription(frontMostTask.getTask().activityLabel);
    627         }
    628         event.setItemCount(mStack.getTaskCount());
    629         event.setScrollY(mStackScroller.mScroller.getCurrY());
    630         event.setMaxScrollY(mStackScroller.progressToScrollRange(mLayoutAlgorithm.mMaxScrollP));
    631     }
    632 
    633     @Override
    634     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    635         super.onInitializeAccessibilityNodeInfo(info);
    636         List<TaskView> taskViews = getTaskViews();
    637         int taskViewCount = taskViews.size();
    638         if (taskViewCount > 1 && mPrevAccessibilityFocusedIndex != -1) {
    639             info.setScrollable(true);
    640             if (mPrevAccessibilityFocusedIndex > 0) {
    641                 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
    642             }
    643             if (mPrevAccessibilityFocusedIndex < mStack.getTaskCount() - 1) {
    644                 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
    645             }
    646         }
    647     }
    648 
    649     @Override
    650     public CharSequence getAccessibilityClassName() {
    651         return TaskStackView.class.getName();
    652     }
    653 
    654     @Override
    655     public boolean performAccessibilityAction(int action, Bundle arguments) {
    656         if (super.performAccessibilityAction(action, arguments)) {
    657             return true;
    658         }
    659         if (ensureFocusedTask(false)) {
    660             switch (action) {
    661                 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
    662                     if (mPrevAccessibilityFocusedIndex > 0) {
    663                         focusNextTask(true, false);
    664                         return true;
    665                     }
    666                 }
    667                 break;
    668                 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
    669                     if (mPrevAccessibilityFocusedIndex < mStack.getTaskCount() - 1) {
    670                         focusNextTask(false, false);
    671                         return true;
    672                     }
    673                 }
    674                 break;
    675             }
    676         }
    677         return false;
    678     }
    679 
    680     @Override
    681     public boolean onInterceptTouchEvent(MotionEvent ev) {
    682         return mTouchHandler.onInterceptTouchEvent(ev);
    683     }
    684 
    685     @Override
    686     public boolean onTouchEvent(MotionEvent ev) {
    687         return mTouchHandler.onTouchEvent(ev);
    688     }
    689 
    690     @Override
    691     public boolean onGenericMotionEvent(MotionEvent ev) {
    692         return mTouchHandler.onGenericMotionEvent(ev);
    693     }
    694 
    695     /** Returns the region that touch gestures can be started in. */
    696     Rect getTouchableRegion() {
    697         return mTaskStackBounds;
    698     }
    699 
    700     @Override
    701     public void computeScroll() {
    702         mStackScroller.computeScroll();
    703         // Synchronize the views
    704         synchronizeStackViewsWithModel();
    705         clipTaskViews();
    706         updateDismissButtonPosition();
    707         // Notify accessibility
    708         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
    709     }
    710 
    711     /** Computes the stack and task rects */
    712     public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds,
    713             boolean launchedWithAltTab, boolean launchedFromHome) {
    714         // Compute the rects in the stack algorithm
    715         mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds);
    716 
    717         // Update the scroll bounds
    718         updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome);
    719     }
    720 
    721     /**
    722      * This is ONLY used from AlternateRecentsComponent to update the dummy stack view for purposes
    723      * of getting the task rect to animate to.
    724      */
    725     public void updateMinMaxScrollForStack(TaskStack stack, boolean launchedWithAltTab,
    726             boolean launchedFromHome) {
    727         mStack = stack;
    728         updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome);
    729     }
    730 
    731     /**
    732      * Computes the maximum number of visible tasks and thumbnails.  Requires that
    733      * updateMinMaxScrollForStack() is called first.
    734      */
    735     public TaskStackViewLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
    736         return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks());
    737     }
    738 
    739     /**
    740      * This is called with the full window width and height to allow stack view children to
    741      * perform the full screen transition down.
    742      */
    743     @Override
    744     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    745         int width = MeasureSpec.getSize(widthMeasureSpec);
    746         int height = MeasureSpec.getSize(heightMeasureSpec);
    747 
    748         // Compute our stack/task rects
    749         Rect taskStackBounds = new Rect(mTaskStackBounds);
    750         taskStackBounds.bottom -= mConfig.systemInsets.bottom;
    751         computeRects(width, height, taskStackBounds, mConfig.launchedWithAltTab,
    752                 mConfig.launchedFromHome);
    753 
    754         // If this is the first layout, then scroll to the front of the stack and synchronize the
    755         // stack views immediately to load all the views
    756         if (mAwaitingFirstLayout) {
    757             mStackScroller.setStackScrollToInitialState();
    758             requestSynchronizeStackViewsWithModel();
    759             synchronizeStackViewsWithModel();
    760         }
    761 
    762         // Measure each of the TaskViews
    763         List<TaskView> taskViews = getTaskViews();
    764         int taskViewCount = taskViews.size();
    765         for (int i = 0; i < taskViewCount; i++) {
    766             TaskView tv = taskViews.get(i);
    767             if (tv.getBackground() != null) {
    768                 tv.getBackground().getPadding(mTmpRect);
    769             } else {
    770                 mTmpRect.setEmpty();
    771             }
    772             tv.measure(
    773                 MeasureSpec.makeMeasureSpec(
    774                         mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,
    775                         MeasureSpec.EXACTLY),
    776                 MeasureSpec.makeMeasureSpec(
    777                         mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom,
    778                         MeasureSpec.EXACTLY));
    779         }
    780 
    781         // Measure the dismiss button
    782         if (mDismissAllButton != null) {
    783             int taskRectWidth = mLayoutAlgorithm.mTaskRect.width();
    784             mDismissAllButton.measure(
    785                     MeasureSpec.makeMeasureSpec(taskRectWidth, MeasureSpec.EXACTLY),
    786                     MeasureSpec.makeMeasureSpec(mConfig.dismissAllButtonSizePx, MeasureSpec.EXACTLY));
    787         }
    788 
    789         setMeasuredDimension(width, height);
    790     }
    791 
    792     /**
    793      * This is called with the size of the space not including the top or right insets, or the
    794      * search bar height in portrait (but including the search bar width in landscape, since we want
    795      * to draw under it.
    796      */
    797     @Override
    798     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    799         // Layout each of the children
    800         List<TaskView> taskViews = getTaskViews();
    801         int taskViewCount = taskViews.size();
    802         for (int i = 0; i < taskViewCount; i++) {
    803             TaskView tv = taskViews.get(i);
    804             if (tv.getBackground() != null) {
    805                 tv.getBackground().getPadding(mTmpRect);
    806             } else {
    807                 mTmpRect.setEmpty();
    808             }
    809             tv.layout(mLayoutAlgorithm.mTaskRect.left - mTmpRect.left,
    810                     mLayoutAlgorithm.mTaskRect.top - mTmpRect.top,
    811                     mLayoutAlgorithm.mTaskRect.right + mTmpRect.right,
    812                     mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom);
    813         }
    814 
    815         // Layout the dismiss button at the top of the screen, and just translate it accordingly
    816         // when synchronizing the views with the model to attach it to the bottom of the front-most
    817         // task view
    818         if (mDismissAllButton != null) {
    819             mDismissAllButton.layout(mLayoutAlgorithm.mTaskRect.left, 0,
    820                     mLayoutAlgorithm.mTaskRect.left + mDismissAllButton.getMeasuredWidth(),
    821                     mDismissAllButton.getMeasuredHeight());
    822         }
    823 
    824         if (mAwaitingFirstLayout) {
    825             mAwaitingFirstLayout = false;
    826             onFirstLayout();
    827         }
    828     }
    829 
    830     /** Handler for the first layout. */
    831     void onFirstLayout() {
    832         int offscreenY = mLayoutAlgorithm.mViewRect.bottom -
    833                 (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top);
    834 
    835         // Find the launch target task
    836         Task launchTargetTask = null;
    837         List<TaskView> taskViews = getTaskViews();
    838         int taskViewCount = taskViews.size();
    839         for (int i = taskViewCount - 1; i >= 0; i--) {
    840             TaskView tv = taskViews.get(i);
    841             Task task = tv.getTask();
    842             if (task.isLaunchTarget) {
    843                 launchTargetTask = task;
    844                 break;
    845             }
    846         }
    847 
    848         // Prepare the first view for its enter animation
    849         for (int i = taskViewCount - 1; i >= 0; i--) {
    850             TaskView tv = taskViews.get(i);
    851             Task task = tv.getTask();
    852             boolean occludesLaunchTarget = (launchTargetTask != null) &&
    853                     launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
    854             tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget, offscreenY);
    855         }
    856 
    857         // If the enter animation started already and we haven't completed a layout yet, do the
    858         // enter animation now
    859         if (mStartEnterAnimationRequestedAfterLayout) {
    860             startEnterRecentsAnimation(mStartEnterAnimationContext);
    861             mStartEnterAnimationRequestedAfterLayout = false;
    862             mStartEnterAnimationContext = null;
    863         }
    864 
    865         // When Alt-Tabbing, focus the previous task (but leave the animation until we finish the
    866         // enter animation).
    867         if (mConfig.launchedWithAltTab) {
    868             if (mConfig.launchedFromAppWithThumbnail) {
    869                 focusTask(Math.max(0, mStack.getTaskCount() - 2), false,
    870                         mConfig.launchedHasConfigurationChanged);
    871             } else {
    872                 focusTask(Math.max(0, mStack.getTaskCount() - 1), false,
    873                         mConfig.launchedHasConfigurationChanged);
    874             }
    875         }
    876 
    877         // Start dozing
    878         if (!mConfig.multiStackEnabled) {
    879             mUIDozeTrigger.startDozing();
    880         }
    881     }
    882 
    883     /** Requests this task stacks to start it's enter-recents animation */
    884     public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) {
    885         // If we are still waiting to layout, then just defer until then
    886         if (mAwaitingFirstLayout) {
    887             mStartEnterAnimationRequestedAfterLayout = true;
    888             mStartEnterAnimationContext = ctx;
    889             return;
    890         }
    891 
    892         if (mStack.getTaskCount() > 0) {
    893             // Find the launch target task
    894             Task launchTargetTask = null;
    895             List<TaskView> taskViews = getTaskViews();
    896             int taskViewCount = taskViews.size();
    897             for (int i = taskViewCount - 1; i >= 0; i--) {
    898                 TaskView tv = taskViews.get(i);
    899                 Task task = tv.getTask();
    900                 if (task.isLaunchTarget) {
    901                     launchTargetTask = task;
    902                     break;
    903                 }
    904             }
    905 
    906             // Animate all the task views into view
    907             for (int i = taskViewCount - 1; i >= 0; i--) {
    908                 TaskView tv = taskViews.get(i);
    909                 Task task = tv.getTask();
    910                 ctx.currentTaskTransform = new TaskViewTransform();
    911                 ctx.currentStackViewIndex = i;
    912                 ctx.currentStackViewCount = taskViewCount;
    913                 ctx.currentTaskRect = mLayoutAlgorithm.mTaskRect;
    914                 ctx.currentTaskOccludesLaunchTarget = (launchTargetTask != null) &&
    915                         launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
    916                 ctx.updateListener = mRequestUpdateClippingListener;
    917                 mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), ctx.currentTaskTransform, null);
    918                 tv.startEnterRecentsAnimation(ctx);
    919             }
    920 
    921             // Add a runnable to the post animation ref counter to clear all the views
    922             ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
    923                 @Override
    924                 public void run() {
    925                     mStartEnterAnimationCompleted = true;
    926                     // Poke the dozer to restart the trigger after the animation completes
    927                     mUIDozeTrigger.poke();
    928 
    929                     RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
    930                     SystemServicesProxy ssp = loader.getSystemServicesProxy();
    931                     List<TaskView> taskViews = getTaskViews();
    932                     int taskViewCount = taskViews.size();
    933                     if (taskViewCount > 0) {
    934                         // Focus the first view if accessibility is enabled
    935                         if (ssp.isTouchExplorationEnabled()) {
    936                             TaskView tv = taskViews.get(taskViewCount - 1);
    937                             tv.requestAccessibilityFocus();
    938                             mPrevAccessibilityFocusedIndex = mStack.indexOfTask(tv.getTask());
    939                         }
    940                     }
    941 
    942                     // Start the focus animation when alt-tabbing
    943                     ArrayList<Task> tasks = mStack.getTasks();
    944                     if (mConfig.launchedWithAltTab && !mConfig.launchedHasConfigurationChanged &&
    945                             0 <= mFocusedTaskIndex && mFocusedTaskIndex < tasks.size()) {
    946                         TaskView tv = getChildViewForTask(tasks.get(mFocusedTaskIndex));
    947                         if (tv != null) {
    948                             tv.setFocusedTask(true);
    949                         }
    950                     }
    951 
    952                     // Show the dismiss button
    953                     showDismissAllButton();
    954                 }
    955             });
    956         }
    957     }
    958 
    959     /** Requests this task stack to start it's exit-recents animation. */
    960     public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
    961         // Stop any scrolling
    962         mStackScroller.stopScroller();
    963         mStackScroller.stopBoundScrollAnimation();
    964         // Animate all the task views out of view
    965         ctx.offscreenTranslationY = mLayoutAlgorithm.mViewRect.bottom -
    966                 (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top);
    967         // Animate the dismiss-all button
    968         hideDismissAllButton(null);
    969 
    970         List<TaskView> taskViews = getTaskViews();
    971         int taskViewCount = taskViews.size();
    972         for (int i = 0; i < taskViewCount; i++) {
    973             TaskView tv = taskViews.get(i);
    974             tv.startExitToHomeAnimation(ctx);
    975         }
    976     }
    977 
    978     /** Requests this task stack to start it's dismiss-all animation. */
    979     public void startDismissAllAnimation(final Runnable postAnimationRunnable) {
    980         // Clear the focused task
    981         resetFocusedTask();
    982         // Animate the dismiss-all button
    983         hideDismissAllButton(new Runnable() {
    984             @Override
    985             public void run() {
    986                 List<TaskView> taskViews = getTaskViews();
    987                 int taskViewCount = taskViews.size();
    988                 int count = 0;
    989                 for (int i = taskViewCount - 1; i >= 0; i--) {
    990                     TaskView tv = taskViews.get(i);
    991                     tv.startDeleteTaskAnimation(i > 0 ? null : postAnimationRunnable, count * 50);
    992                     count++;
    993                 }
    994             }
    995         });
    996     }
    997 
    998     /** Animates a task view in this stack as it launches. */
    999     public void startLaunchTaskAnimation(TaskView tv, Runnable r, boolean lockToTask) {
   1000         Task launchTargetTask = tv.getTask();
   1001         List<TaskView> taskViews = getTaskViews();
   1002         int taskViewCount = taskViews.size();
   1003         for (int i = 0; i < taskViewCount; i++) {
   1004             TaskView t = taskViews.get(i);
   1005             if (t == tv) {
   1006                 t.setClipViewInStack(false);
   1007                 t.startLaunchTaskAnimation(r, true, true, lockToTask);
   1008             } else {
   1009                 boolean occludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(t.getTask(),
   1010                         launchTargetTask);
   1011                 t.startLaunchTaskAnimation(null, false, occludesLaunchTarget, lockToTask);
   1012             }
   1013         }
   1014     }
   1015 
   1016     /** Shows the dismiss button */
   1017     void showDismissAllButton() {
   1018         if (mDismissAllButton == null) return;
   1019 
   1020         if (mDismissAllButtonAnimating || mDismissAllButton.getVisibility() != View.VISIBLE ||
   1021                 Float.compare(mDismissAllButton.getAlpha(), 0f) == 0) {
   1022             mDismissAllButtonAnimating = true;
   1023             mDismissAllButton.setVisibility(View.VISIBLE);
   1024             mDismissAllButton.showClearButton();
   1025             mDismissAllButton.findViewById(R.id.dismiss_text).setAlpha(1f);
   1026             mDismissAllButton.setAlpha(0f);
   1027             mDismissAllButton.animate()
   1028                     .alpha(1f)
   1029                     .setDuration(250)
   1030                     .withEndAction(new Runnable() {
   1031                         @Override
   1032                         public void run() {
   1033                             mDismissAllButtonAnimating = false;
   1034                         }
   1035                     })
   1036                     .start();
   1037         }
   1038     }
   1039 
   1040     /** Hides the dismiss button */
   1041     void hideDismissAllButton(final Runnable postAnimRunnable) {
   1042         if (mDismissAllButton == null) return;
   1043 
   1044         mDismissAllButtonAnimating = true;
   1045         mDismissAllButton.animate()
   1046                 .alpha(0f)
   1047                 .setDuration(200)
   1048                 .withEndAction(new Runnable() {
   1049                     @Override
   1050                     public void run() {
   1051                         mDismissAllButtonAnimating = false;
   1052                         mDismissAllButton.setVisibility(View.GONE);
   1053                         if (postAnimRunnable != null) {
   1054                             postAnimRunnable.run();
   1055                         }
   1056                     }
   1057                 })
   1058                 .start();
   1059     }
   1060 
   1061     /** Updates the dismiss button position */
   1062     void updateDismissButtonPosition() {
   1063         if (mDismissAllButton == null) return;
   1064 
   1065         // Update the position of the clear-all button to hang it off the first task view
   1066         if (mStack.getTaskCount() > 0) {
   1067             mTmpCoord[0] = mTmpCoord[1] = 0;
   1068             TaskView tv = getChildViewForTask(mStack.getFrontMostTask());
   1069             TaskViewTransform transform = mCurrentTaskTransforms.get(mStack.getTaskCount() - 1);
   1070             if (tv != null && transform.visible) {
   1071                 Utilities.mapCoordInDescendentToSelf(tv, this, mTmpCoord, false);
   1072                 mDismissAllButton.setTranslationY(mTmpCoord[1] + (tv.getScaleY() * tv.getHeight()));
   1073                 mDismissAllButton.setTranslationX(-(mLayoutAlgorithm.mStackRect.width() -
   1074                         transform.rect.width()) / 2f);
   1075             }
   1076         }
   1077     }
   1078 
   1079     /** Final callback after Recents is finally hidden. */
   1080     void onRecentsHidden() {
   1081         reset();
   1082     }
   1083 
   1084     public boolean isTransformedTouchPointInView(float x, float y, View child) {
   1085         return isTransformedTouchPointInView(x, y, child, null);
   1086     }
   1087 
   1088     /** Pokes the dozer on user interaction. */
   1089     void onUserInteraction() {
   1090         // Poke the doze trigger if it is dozing
   1091         mUIDozeTrigger.poke();
   1092     }
   1093 
   1094     @Override
   1095     protected void dispatchDraw(Canvas canvas) {
   1096         mLayersDisabled = false;
   1097         super.dispatchDraw(canvas);
   1098     }
   1099 
   1100     public void disableLayersForOneFrame() {
   1101         mLayersDisabled = true;
   1102         List<TaskView> taskViews = getTaskViews();
   1103         for (int i = 0; i < taskViews.size(); i++) {
   1104             taskViews.get(i).disableLayersForOneFrame();
   1105         }
   1106     }
   1107 
   1108     /**** TaskStackCallbacks Implementation ****/
   1109 
   1110     @Override
   1111     public void onStackTaskAdded(TaskStack stack, Task t) {
   1112         requestSynchronizeStackViewsWithModel();
   1113     }
   1114 
   1115     @Override
   1116     public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask) {
   1117         // Remove the view associated with this task, we can't rely on updateTransforms
   1118         // to work here because the task is no longer in the list
   1119         TaskView tv = getChildViewForTask(removedTask);
   1120         if (tv != null) {
   1121             mViewPool.returnViewToPool(tv);
   1122         }
   1123 
   1124         // Get the stack scroll of the task to anchor to (since we are removing something, the front
   1125         // most task will be our anchor task)
   1126         Task anchorTask = null;
   1127         float prevAnchorTaskScroll = 0;
   1128         boolean pullStackForward = stack.getTaskCount() > 0;
   1129         if (pullStackForward) {
   1130             anchorTask = mStack.getFrontMostTask();
   1131             prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
   1132         }
   1133 
   1134         // Update the min/max scroll and animate other task views into their new positions
   1135         updateMinMaxScroll(true, mConfig.launchedWithAltTab, mConfig.launchedFromHome);
   1136 
   1137         // Offset the stack by as much as the anchor task would otherwise move back
   1138         if (pullStackForward) {
   1139             float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
   1140             mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll
   1141                     - prevAnchorTaskScroll));
   1142             mStackScroller.boundScroll();
   1143         }
   1144 
   1145         // Animate all the tasks into place
   1146         requestSynchronizeStackViewsWithModel(200);
   1147 
   1148         // Update the new front most task
   1149         if (newFrontMostTask != null) {
   1150             TaskView frontTv = getChildViewForTask(newFrontMostTask);
   1151             if (frontTv != null) {
   1152                 frontTv.onTaskBound(newFrontMostTask);
   1153                 frontTv.fadeInActionButton(0, mConfig.taskViewEnterFromAppDuration);
   1154             }
   1155         }
   1156 
   1157         // If there are no remaining tasks, then either unfilter the current stack, or just close
   1158         // the activity if there are no filtered stacks
   1159         if (mStack.getTaskCount() == 0) {
   1160             boolean shouldFinishActivity = true;
   1161             if (mStack.hasFilteredTasks()) {
   1162                 mStack.unfilterTasks();
   1163                 shouldFinishActivity = (mStack.getTaskCount() == 0);
   1164             }
   1165             if (shouldFinishActivity) {
   1166                 mCb.onAllTaskViewsDismissed(null);
   1167             }
   1168         } else {
   1169             // Fade the dismiss button back in
   1170             showDismissAllButton();
   1171         }
   1172 
   1173         // Notify the callback that we've removed the task and it can clean up after it. Note, we
   1174         // do this after onAllTaskViewsDismissed() is called, to allow the home activity to be
   1175         // started before the call to remove the task.
   1176         mCb.onTaskViewDismissed(removedTask);
   1177     }
   1178 
   1179     @Override
   1180     public void onStackAllTasksRemoved(TaskStack stack, final ArrayList<Task> removedTasks) {
   1181         // Announce for accessibility
   1182         String msg = getContext().getString(R.string.accessibility_recents_all_items_dismissed);
   1183         announceForAccessibility(msg);
   1184 
   1185         startDismissAllAnimation(new Runnable() {
   1186             @Override
   1187             public void run() {
   1188                 // Notify that all tasks have been removed
   1189                 mCb.onAllTaskViewsDismissed(removedTasks);
   1190             }
   1191         });
   1192     }
   1193 
   1194     @Override
   1195     public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
   1196                                 Task filteredTask) {
   1197         /*
   1198         // Stash the scroll and filtered task for us to restore to when we unfilter
   1199         mStashedScroll = getStackScroll();
   1200 
   1201         // Calculate the current task transforms
   1202         ArrayList<TaskViewTransform> curTaskTransforms =
   1203                 getStackTransforms(curTasks, getStackScroll(), null, true);
   1204 
   1205         // Update the task offsets
   1206         mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks());
   1207 
   1208         // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
   1209         updateMinMaxScroll(false);
   1210         float overlapHeight = mLayoutAlgorithm.getTaskOverlapHeight();
   1211         setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
   1212         boundScrollRaw();
   1213 
   1214         // Compute the transforms of the items in the new stack after setting the new scroll
   1215         final ArrayList<Task> tasks = mStack.getTasks();
   1216         final ArrayList<TaskViewTransform> taskTransforms =
   1217                 getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);
   1218 
   1219         // Animate
   1220         mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
   1221 
   1222         // Notify any callbacks
   1223         mCb.onTaskStackFilterTriggered();
   1224         */
   1225     }
   1226 
   1227     @Override
   1228     public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) {
   1229         /*
   1230         // Calculate the current task transforms
   1231         final ArrayList<TaskViewTransform> curTaskTransforms =
   1232                 getStackTransforms(curTasks, getStackScroll(), null, true);
   1233 
   1234         // Update the task offsets
   1235         mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks());
   1236 
   1237         // Restore the stashed scroll
   1238         updateMinMaxScroll(false);
   1239         setStackScrollRaw(mStashedScroll);
   1240         boundScrollRaw();
   1241 
   1242         // Compute the transforms of the items in the new stack after restoring the stashed scroll
   1243         final ArrayList<Task> tasks = mStack.getTasks();
   1244         final ArrayList<TaskViewTransform> taskTransforms =
   1245                 getStackTransforms(tasks, getStackScroll(), null, true);
   1246 
   1247         // Animate
   1248         mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
   1249 
   1250         // Clear the saved vars
   1251         mStashedScroll = 0;
   1252 
   1253         // Notify any callbacks
   1254         mCb.onTaskStackUnfilterTriggered();
   1255         */
   1256     }
   1257 
   1258     /**** ViewPoolConsumer Implementation ****/
   1259 
   1260     @Override
   1261     public TaskView createView(Context context) {
   1262         return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
   1263     }
   1264 
   1265     @Override
   1266     public void prepareViewToEnterPool(TaskView tv) {
   1267         Task task = tv.getTask();
   1268 
   1269         // Clear the accessibility focus for that view
   1270         if (tv.isAccessibilityFocused()) {
   1271             tv.clearAccessibilityFocus();
   1272         }
   1273 
   1274         // Report that this tasks's data is no longer being used
   1275         RecentsTaskLoader.getInstance().unloadTaskData(task);
   1276 
   1277         // Detach the view from the hierarchy
   1278         detachViewFromParent(tv);
   1279         // Update the task views list after removing the task view
   1280         updateTaskViewsList();
   1281 
   1282         // Reset the view properties
   1283         tv.resetViewProperties();
   1284 
   1285         // Reset the clip state of the task view
   1286         tv.setClipViewInStack(false);
   1287     }
   1288 
   1289     @Override
   1290     public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
   1291         // It is possible for a view to be returned to the view pool before it is laid out,
   1292         // which means that we will need to relayout the view when it is first used next.
   1293         boolean requiresRelayout = tv.getWidth() <= 0 && !isNewView;
   1294 
   1295         // Rebind the task and request that this task's data be filled into the TaskView
   1296         tv.onTaskBound(task);
   1297 
   1298         // Load the task data
   1299         RecentsTaskLoader.getInstance().loadTaskData(task);
   1300 
   1301         // If the doze trigger has already fired, then update the state for this task view
   1302         if (mConfig.multiStackEnabled || mUIDozeTrigger.hasTriggered()) {
   1303             tv.setNoUserInteractionState();
   1304         }
   1305 
   1306         // If we've finished the start animation, then ensure we always enable the focus animations
   1307         if (mStartEnterAnimationCompleted) {
   1308             tv.enableFocusAnimations();
   1309         }
   1310 
   1311         // Find the index where this task should be placed in the stack
   1312         int insertIndex = -1;
   1313         int taskIndex = mStack.indexOfTask(task);
   1314         if (taskIndex != -1) {
   1315 
   1316             List<TaskView> taskViews = getTaskViews();
   1317             int taskViewCount = taskViews.size();
   1318             for (int i = 0; i < taskViewCount; i++) {
   1319                 Task tvTask = taskViews.get(i).getTask();
   1320                 if (taskIndex < mStack.indexOfTask(tvTask)) {
   1321                     // Offset by 1 if we have a dismiss-all button
   1322                     insertIndex = i + (Constants.DebugFlags.App.EnableDismissAll ? 1 : 0);
   1323                     break;
   1324                 }
   1325             }
   1326         }
   1327 
   1328         // Add/attach the view to the hierarchy
   1329         if (isNewView) {
   1330             addView(tv, insertIndex);
   1331         } else {
   1332             attachViewToParent(tv, insertIndex, tv.getLayoutParams());
   1333             if (requiresRelayout) {
   1334                 tv.requestLayout();
   1335             }
   1336         }
   1337         // Update the task views list after adding the new task view
   1338         updateTaskViewsList();
   1339 
   1340         // Set the new state for this view, including the callbacks and view clipping
   1341         tv.setCallbacks(this);
   1342         tv.setTouchEnabled(true);
   1343         tv.setClipViewInStack(true);
   1344     }
   1345 
   1346     @Override
   1347     public boolean hasPreferredData(TaskView tv, Task preferredData) {
   1348         return (tv.getTask() == preferredData);
   1349     }
   1350 
   1351     /**** TaskViewCallbacks Implementation ****/
   1352 
   1353     @Override
   1354     public void onTaskViewAppIconClicked(TaskView tv) {
   1355         if (Constants.DebugFlags.App.EnableTaskFiltering) {
   1356             if (mStack.hasFilteredTasks()) {
   1357                 mStack.unfilterTasks();
   1358             } else {
   1359                 mStack.filterTasks(tv.getTask());
   1360             }
   1361         }
   1362     }
   1363 
   1364     @Override
   1365     public void onTaskViewAppInfoClicked(TaskView tv) {
   1366         if (mCb != null) {
   1367             mCb.onTaskViewAppInfoClicked(tv.getTask());
   1368 
   1369             // Keep track of app-info invocations
   1370             MetricsLogger.count(getContext(), "overview_app_info", 1);
   1371         }
   1372     }
   1373 
   1374     @Override
   1375     public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) {
   1376         // Cancel any doze triggers
   1377         mUIDozeTrigger.stopDozing();
   1378 
   1379         if (mCb != null) {
   1380             mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask);
   1381         }
   1382     }
   1383 
   1384     @Override
   1385     public void onTaskViewDismissed(TaskView tv) {
   1386         Task task = tv.getTask();
   1387         int taskIndex = mStack.indexOfTask(task);
   1388         boolean taskWasFocused = tv.isFocusedTask();
   1389         // Announce for accessibility
   1390         tv.announceForAccessibility(getContext().getString(R.string.accessibility_recents_item_dismissed,
   1391                 tv.getTask().activityLabel));
   1392         // Remove the task from the view
   1393         mStack.removeTask(task);
   1394         // If the dismissed task was focused, then we should focus the new task in the same index
   1395         if (taskWasFocused) {
   1396             ArrayList<Task> tasks = mStack.getTasks();
   1397             int nextTaskIndex = Math.min(tasks.size() - 1, taskIndex - 1);
   1398             if (nextTaskIndex >= 0) {
   1399                 Task nextTask = tasks.get(nextTaskIndex);
   1400                 TaskView nextTv = getChildViewForTask(nextTask);
   1401                 if (nextTv != null) {
   1402                     // Focus the next task, and only animate the visible state if we are launched
   1403                     // from Alt-Tab
   1404                     nextTv.setFocusedTask(mConfig.launchedWithAltTab);
   1405                 }
   1406             }
   1407         }
   1408     }
   1409 
   1410     @Override
   1411     public void onTaskViewClipStateChanged(TaskView tv) {
   1412         if (!mStackViewsDirty) {
   1413             invalidate();
   1414         }
   1415     }
   1416 
   1417     @Override
   1418     public void onTaskViewFocusChanged(TaskView tv, boolean focused) {
   1419         if (focused) {
   1420             mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
   1421         }
   1422     }
   1423 
   1424     @Override
   1425     public void onTaskResize(TaskView tv) {
   1426         if (mCb != null) {
   1427             mCb.onTaskResize(tv.getTask());
   1428         }
   1429     }
   1430 
   1431     /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
   1432 
   1433     @Override
   1434     public void onScrollChanged(float p) {
   1435         mUIDozeTrigger.poke();
   1436         requestSynchronizeStackViewsWithModel();
   1437         postInvalidateOnAnimation();
   1438     }
   1439 
   1440     /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
   1441 
   1442     @Override
   1443     public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) {
   1444         // Compute which components need to be removed
   1445         HashSet<ComponentName> removedComponents = monitor.computeComponentsRemoved(
   1446                 mStack.getTaskKeys(), packageName, userId);
   1447 
   1448         // For other tasks, just remove them directly if they no longer exist
   1449         ArrayList<Task> tasks = mStack.getTasks();
   1450         for (int i = tasks.size() - 1; i >= 0; i--) {
   1451             final Task t = tasks.get(i);
   1452             if (removedComponents.contains(t.key.baseIntent.getComponent())) {
   1453                 TaskView tv = getChildViewForTask(t);
   1454                 if (tv != null) {
   1455                     // For visible children, defer removing the task until after the animation
   1456                     tv.startDeleteTaskAnimation(new Runnable() {
   1457                         @Override
   1458                         public void run() {
   1459                             mStack.removeTask(t);
   1460                         }
   1461                     }, 0);
   1462                 } else {
   1463                     // Otherwise, remove the task from the stack immediately
   1464                     mStack.removeTask(t);
   1465                 }
   1466             }
   1467         }
   1468     }
   1469 }
   1470