Example #1
0
  /** Launches the task that Recents was launched from, if possible */
  public boolean launchPreviousTask() {
    Log.d(TAG, "launchPreviousTask: ");
    // Get the first stack view
    List<TaskStackView> stackViews = getTaskStackViews();
    int stackCount = stackViews.size();
    for (int i = 0; i < stackCount; i++) {
      TaskStackView stackView = stackViews.get(i);
      TaskStack stack = stackView.getStack();
      ArrayList<Task> tasks = stack.getTasks();

      // Find the launch task in the stack
      if (!tasks.isEmpty()) {
        int taskCount = tasks.size();
        for (int j = 0; j < taskCount; j++) {
          if (tasks.get(j).isLaunchTarget) {
            Task task = tasks.get(j);
            TaskView tv = stackView.getChildViewForTask(task);
            onTaskViewClicked(stackView, tv, stack, task, false);
            return true;
          }
        }
      }
    }
    return false;
  }
  /** Launches the task that Recents was launched from, if possible */
  public boolean launchPreviousTask() {
    // Get the first stack view
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
      View child = getChildAt(i);
      if (child != mSearchBar) {
        TaskStackView stackView = (TaskStackView) child;
        TaskStack stack = stackView.mStack;
        ArrayList<Task> tasks = stack.getTasks();

        // Find the launch task in the stack
        if (!tasks.isEmpty()) {
          int taskCount = tasks.size();
          for (int j = 0; j < taskCount; j++) {
            if (tasks.get(j).isLaunchTarget) {
              Task task = tasks.get(j);
              TaskView tv = stackView.getChildViewForTask(task);
              onTaskViewClicked(stackView, tv, stack, task, false);
              return true;
            }
          }
        }
      }
    }
    return false;
  }
 /** ** TaskViewCallbacks Implementation *** */
 @Override
 public void onTaskViewAppIconClicked(TaskView tv) {
   if (Constants.DebugFlags.App.EnableTaskFiltering) {
     if (mStack.hasFilteredTasks()) {
       mStack.unfilterTasks();
     } else {
       mStack.filterTasks(tv.getTask());
     }
   }
 }
  @Override
  public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
    // Rebind the task and request that this task's data be filled into the TaskView
    tv.onTaskBound(task);

    // Mark the launch task as fullscreen
    if (Constants.DebugFlags.App.EnableScreenshotAppTransition && mAwaitingFirstLayout) {
      if (task.isLaunchTarget) {
        tv.setIsFullScreen(true);
      }
    }

    // Load the task data
    RecentsTaskLoader.getInstance().loadTaskData(task);

    // Sanity check, the task view should always be clipping against the stack at this point,
    // but just in case, re-enable it here
    tv.setClipViewInStack(true);

    // If the doze trigger has already fired, then update the state for this task view
    if (mUIDozeTrigger.hasTriggered()) {
      tv.setNoUserInteractionState();
    }

    // If we've finished the start animation, then ensure we always enable the focus animations
    if (mStartEnterAnimationCompleted) {
      tv.enableFocusAnimations();
    }

    // Find the index where this task should be placed in the stack
    int insertIndex = -1;
    int taskIndex = mStack.indexOfTask(task);
    if (taskIndex != -1) {
      int childCount = getChildCount();
      for (int i = 0; i < childCount; i++) {
        Task tvTask = ((TaskView) getChildAt(i)).getTask();
        if (taskIndex < mStack.indexOfTask(tvTask)) {
          insertIndex = i;
          break;
        }
      }
    }

    // Add/attach the view to the hierarchy
    if (isNewView) {
      addView(tv, insertIndex);

      // Set the callbacks and listeners for this new view
      tv.setTouchEnabled(true);
      tv.setCallbacks(this);
    } else {
      attachViewToParent(tv, insertIndex, tv.getLayoutParams());
    }
  }
 @Override
 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
   super.onInitializeAccessibilityEvent(event);
   int childCount = getChildCount();
   if (childCount > 0) {
     TaskView backMostTask = (TaskView) getChildAt(0);
     TaskView frontMostTask = (TaskView) getChildAt(childCount - 1);
     event.setFromIndex(mStack.indexOfTask(backMostTask.getTask()));
     event.setToIndex(mStack.indexOfTask(frontMostTask.getTask()));
     event.setContentDescription(frontMostTask.getTask().activityLabel);
   }
   event.setItemCount(mStack.getTaskCount());
   event.setScrollY(mStackScroller.mScroller.getCurrY());
   event.setMaxScrollY(mStackScroller.progressToScrollRange(mLayoutAlgorithm.mMaxScrollP));
 }
  /** Returns the transition rect for the given task id. */
  TaskViewTransform getThumbnailTransitionTransform(
      TaskStack stack, TaskStackView stackView, int runningTaskId, Task runningTaskOut) {
    // Find the running task in the TaskStack
    Task task = null;
    ArrayList<Task> tasks = stack.getTasks();
    if (runningTaskId != -1) {
      // Otherwise, try and find the task with the
      int taskCount = tasks.size();
      for (int i = taskCount - 1; i >= 0; i--) {
        Task t = tasks.get(i);
        if (t.key.id == runningTaskId) {
          task = t;
          runningTaskOut.copyFrom(t);
          break;
        }
      }
    }
    if (task == null) {
      // If no task is specified or we can not find the task just use the front most one
      task = tasks.get(tasks.size() - 1);
      runningTaskOut.copyFrom(task);
    }

    // Get the transform for the running task
    stackView.getScroller().setStackScrollToInitialState();
    mTmpTransform =
        stackView
            .getStackAlgorithm()
            .getStackTransform(task, stackView.getScroller().getStackScroll(), mTmpTransform, null);
    return mTmpTransform;
  }
 void preloadRecentsInternal() {
   // Preload only the raw task list into a new load plan (which will be consumed by the
   // RecentsActivity) only if there is a task to animate to.
   ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
   MutableBoolean topTaskHome = new MutableBoolean(true);
   RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
   sInstanceLoadPlan = loader.createLoadPlan(mContext);
   if (topTask != null && !mSystemServicesProxy.isRecentsTopMost(topTask, topTaskHome)) {
     sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
     loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
     TaskStack top = sInstanceLoadPlan.getAllTaskStacks().get(0);
     if (top.getTaskCount() > 0) {
       preCacheThumbnailTransitionBitmapAsync(topTask, top, mDummyStackView, topTaskHome.value);
     }
   }
 }
 public TaskStackView(Context context, TaskStack stack) {
   super(context);
   mConfig = RecentsConfiguration.getInstance();
   mStack = stack;
   mStack.setCallbacks(this);
   mViewPool = new ViewPool<TaskView, Task>(context, this);
   mInflater = LayoutInflater.from(context);
   mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(mConfig);
   mFilterAlgorithm = new TaskStackViewFilterAlgorithm(mConfig, this, mViewPool);
   mStackScroller = new TaskStackViewScroller(context, mConfig, mLayoutAlgorithm);
   mStackScroller.setCallbacks(this);
   mTouchHandler = new TaskStackViewTouchHandler(context, this, mConfig, mStackScroller);
   mUIDozeTrigger =
       new DozeTrigger(
           mConfig.taskBarDismissDozeDelaySeconds,
           new Runnable() {
             @Override
             public void run() {
               // Show the task bar dismiss buttons
               int childCount = getChildCount();
               for (int i = 0; i < childCount; i++) {
                 TaskView tv = (TaskView) getChildAt(i);
                 tv.startNoUserInteractionAnimation();
               }
             }
           });
 }
 /** Unfilters any filtered stacks */
 public boolean unfilterFilteredStacks() {
   if (mStacks != null) {
     // Check if there are any filtered stacks and unfilter them before we back out of Recents
     boolean stacksUnfiltered = false;
     int numStacks = mStacks.size();
     for (int i = 0; i < numStacks; i++) {
       TaskStack stack = mStacks.get(i);
       if (stack.hasFilteredTasks()) {
         stack.unfilterTasks();
         stacksUnfiltered = true;
       }
     }
     return stacksUnfiltered;
   }
   return false;
 }
  /** Requests this task stacks to start it's enter-recents animation */
  public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) {
    // If we are still waiting to layout, then just defer until then
    if (mAwaitingFirstLayout) {
      mStartEnterAnimationRequestedAfterLayout = true;
      mStartEnterAnimationContext = ctx;
      return;
    }

    if (mStack.getTaskCount() > 0) {
      // Find the launch target task
      Task launchTargetTask = null;
      int childCount = getChildCount();
      for (int i = childCount - 1; i >= 0; i--) {
        TaskView tv = (TaskView) getChildAt(i);
        Task task = tv.getTask();
        if (task.isLaunchTarget) {
          launchTargetTask = task;
          break;
        }
      }

      // Animate all the task views into view
      for (int i = childCount - 1; i >= 0; i--) {
        TaskView tv = (TaskView) getChildAt(i);
        Task task = tv.getTask();
        ctx.currentTaskTransform = new TaskViewTransform();
        ctx.currentStackViewIndex = i;
        ctx.currentStackViewCount = childCount;
        ctx.currentTaskRect = mLayoutAlgorithm.mTaskRect;
        ctx.currentTaskOccludesLaunchTarget =
            (launchTargetTask != null)
                && launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
        ctx.updateListener = mRequestUpdateClippingListener;
        mLayoutAlgorithm.getStackTransform(
            task, mStackScroller.getStackScroll(), ctx.currentTaskTransform, null);
        tv.startEnterRecentsAnimation(ctx);
      }

      // Add a runnable to the post animation ref counter to clear all the views
      ctx.postAnimationTrigger.addLastDecrementRunnable(
          new Runnable() {
            @Override
            public void run() {
              mStartEnterAnimationCompleted = true;
              // Start dozing
              mUIDozeTrigger.startDozing();
              // Focus the first view if accessibility is enabled
              RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
              SystemServicesProxy ssp = loader.getSystemServicesProxy();
              int childCount = getChildCount();
              if (childCount > 0 && ssp.isTouchExplorationEnabled()) {
                TaskView tv = ((TaskView) getChildAt(childCount - 1));
                tv.requestAccessibilityFocus();
                mPrevAccessibilityFocusedIndex = mStack.indexOfTask(tv.getTask());
              }
            }
          });
    }
  }
  /** Dismisses the focused task. */
  public void dismissFocusedTask() {
    // Return early if there is no focused task index
    if (mFocusedTaskIndex < 0) return;

    Task t = mStack.getTasks().get(mFocusedTaskIndex);
    TaskView tv = getChildViewForTask(t);
    tv.dismissTask();
  }
  /** Handler for the first layout. */
  void onFirstLayout() {
    int offscreenY =
        mLayoutAlgorithm.mViewRect.bottom
            - (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top);

    // Find the launch target task
    Task launchTargetTask = null;
    int childCount = getChildCount();
    for (int i = childCount - 1; i >= 0; i--) {
      TaskView tv = (TaskView) getChildAt(i);
      Task task = tv.getTask();
      if (task.isLaunchTarget) {
        launchTargetTask = task;
        break;
      }
    }

    // Prepare the first view for its enter animation
    for (int i = childCount - 1; i >= 0; i--) {
      TaskView tv = (TaskView) getChildAt(i);
      Task task = tv.getTask();
      boolean occludesLaunchTarget =
          (launchTargetTask != null)
              && launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
      tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget, offscreenY);
    }

    // If the enter animation started already and we haven't completed a layout yet, do the
    // enter animation now
    if (mStartEnterAnimationRequestedAfterLayout) {
      startEnterRecentsAnimation(mStartEnterAnimationContext);
      mStartEnterAnimationRequestedAfterLayout = false;
      mStartEnterAnimationContext = null;
    }

    // When Alt-Tabbing, we scroll to and focus the previous task
    if (mConfig.launchedWithAltTab) {
      if (mConfig.launchedFromHome) {
        focusTask(Math.max(0, mStack.getTaskCount() - 1), false);
      } else {
        focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
      }
    }
  }
  /** Updates the min and max virtual scroll bounds */
  void updateMinMaxScroll(
      boolean boundScrollToNewMinMax, boolean launchedWithAltTab, boolean launchedFromHome) {
    // Compute the min and max scroll values
    mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks(), launchedWithAltTab, launchedFromHome);

    // Debug logging
    if (boundScrollToNewMinMax) {
      mStackScroller.boundScroll();
    }
  }
  /** Focuses the next task in the stack */
  void focusNextTask(boolean forward) {
    // Find the next index to focus
    int numTasks = mStack.getTaskCount();
    if (numTasks == 0) return;

    int nextFocusIndex = numTasks - 1;
    if (0 <= mFocusedTaskIndex && mFocusedTaskIndex < numTasks) {
      nextFocusIndex = Math.max(0, Math.min(numTasks - 1, mFocusedTaskIndex + (forward ? -1 : 1)));
    }
    focusTask(nextFocusIndex, true);
  }
Example #15
0
 /** Gets the next task in the stack - or if the last - the top task */
 public Task getNextTaskOrTopTask(Task taskToSearch) {
   Log.d(TAG, "getNextTaskOrTopTask: ");
   Task returnTask = null;
   boolean found = false;
   List<TaskStackView> stackViews = getTaskStackViews();
   int stackCount = stackViews.size();
   for (int i = stackCount - 1; i >= 0; --i) {
     TaskStack stack = stackViews.get(i).getStack();
     ArrayList<Task> taskList = stack.getTasks();
     // Iterate the stack views and try and find the focused task
     for (int j = taskList.size() - 1; j >= 0; --j) {
       Task task = taskList.get(j);
       // Return the next task in the line.
       if (found) return task;
       // Remember the first possible task as the top task.
       if (returnTask == null) returnTask = task;
       if (task == taskToSearch) found = true;
     }
   }
   return returnTask;
 }
  /** Focuses the task at the specified index in the stack */
  void focusTask(int taskIndex, boolean scrollToNewPosition) {
    // Return early if the task is already focused
    if (taskIndex == mFocusedTaskIndex) return;

    if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
      mFocusedTaskIndex = taskIndex;

      // Focus the view if possible, otherwise, focus the view after we scroll into position
      Task t = mStack.getTasks().get(taskIndex);
      TaskView tv = getChildViewForTask(t);
      Runnable postScrollRunnable = null;
      if (tv != null) {
        tv.setFocusedTask();
      } else {
        postScrollRunnable =
            new Runnable() {
              @Override
              public void run() {
                Task t = mStack.getTasks().get(mFocusedTaskIndex);
                TaskView tv = getChildViewForTask(t);
                if (tv != null) {
                  tv.setFocusedTask();
                }
              }
            };
      }

      // Scroll the view into position (just center it in the curve)
      if (scrollToNewPosition) {
        float newScroll = mLayoutAlgorithm.getStackScrollForTask(t) - 0.5f;
        newScroll = mStackScroller.getBoundedStackScroll(newScroll);
        mStackScroller.animateScroll(
            mStackScroller.getStackScroll(), newScroll, postScrollRunnable);
      } else {
        if (postScrollRunnable != null) {
          postScrollRunnable.run();
        }
      }
    }
  }
 @Override
 public void onTaskViewDismissed(TaskView tv) {
   Task task = tv.getTask();
   int taskIndex = mStack.indexOfTask(task);
   boolean taskWasFocused = tv.isFocusedTask();
   // Announce for accessibility
   tv.announceForAccessibility(
       getContext()
           .getString(R.string.accessibility_recents_item_dismissed, tv.getTask().activityLabel));
   // Remove the task from the view
   mStack.removeTask(task);
   // If the dismissed task was focused, then we should focus the next task in front
   if (taskWasFocused) {
     ArrayList<Task> tasks = mStack.getTasks();
     int nextTaskIndex = Math.min(tasks.size() - 1, taskIndex);
     if (nextTaskIndex >= 0) {
       Task nextTask = tasks.get(nextTaskIndex);
       TaskView nextTv = getChildViewForTask(nextTask);
       nextTv.setFocusedTask();
     }
   }
 }
  @Override
  public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask) {
    // Remove the view associated with this task, we can't rely on updateTransforms
    // to work here because the task is no longer in the list
    TaskView tv = getChildViewForTask(removedTask);
    if (tv != null) {
      mViewPool.returnViewToPool(tv);
    }

    // Notify the callback that we've removed the task and it can clean up after it
    mCb.onTaskViewDismissed(removedTask);

    // Get the stack scroll of the task to anchor to (since we are removing something, the front
    // most task will be our anchor task)
    Task anchorTask = null;
    float prevAnchorTaskScroll = 0;
    boolean pullStackForward = stack.getTaskCount() > 0;
    if (pullStackForward) {
      anchorTask = mStack.getFrontMostTask();
      prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
    }

    // Update the min/max scroll and animate other task views into their new positions
    updateMinMaxScroll(true, mConfig.launchedWithAltTab, mConfig.launchedFromHome);

    // Offset the stack by as much as the anchor task would otherwise move back
    if (pullStackForward) {
      float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
      mStackScroller.setStackScroll(
          mStackScroller.getStackScroll() + (anchorTaskScroll - prevAnchorTaskScroll));
      mStackScroller.boundScroll();
    }

    // Animate all the tasks into place
    requestSynchronizeStackViewsWithModel(200);

    // Update the new front most task
    if (newFrontMostTask != null) {
      TaskView frontTv = getChildViewForTask(newFrontMostTask);
      if (frontTv != null) {
        frontTv.onTaskBound(newFrontMostTask);
      }
    }

    // If there are no remaining tasks, then either unfilter the current stack, or just close
    // the activity if there are no filtered stacks
    if (mStack.getTaskCount() == 0) {
      boolean shouldFinishActivity = true;
      if (mStack.hasFilteredTasks()) {
        mStack.unfilterTasks();
        shouldFinishActivity = (mStack.getTaskCount() == 0);
      }
      if (shouldFinishActivity) {
        mCb.onAllTaskViewsDismissed();
      }
    }
  }
 /** ** RecentsPackageMonitor.PackageCallbacks Implementation *** */
 @Override
 public void onComponentRemoved(HashSet<ComponentName> cns) {
   // For other tasks, just remove them directly if they no longer exist
   ArrayList<Task> tasks = mStack.getTasks();
   for (int i = tasks.size() - 1; i >= 0; i--) {
     final Task t = tasks.get(i);
     if (cns.contains(t.key.baseIntent.getComponent())) {
       TaskView tv = getChildViewForTask(t);
       if (tv != null) {
         // For visible children, defer removing the task until after the animation
         tv.startDeleteTaskAnimation(
             new Runnable() {
               @Override
               public void run() {
                 mStack.removeTask(t);
               }
             });
       } else {
         // Otherwise, remove the task from the stack immediately
         mStack.removeTask(t);
       }
     }
   }
 }
Example #20
0
  /** ** TaskStackView.TaskStackCallbacks Implementation *** */
  @Override
  public void onTaskViewClicked(
      final TaskStackView stackView,
      final TaskView tv,
      final TaskStack stack,
      final Task task,
      final boolean lockToTask) {
    Log.d(TAG, "onTaskViewClicked: ");
    // Notify any callbacks of the launching of a new task
    if (mCb != null) {
      mCb.onTaskViewClicked();
    }

    // Upfront the processing of the thumbnail
    TaskViewTransform transform = new TaskViewTransform();
    View sourceView;
    int offsetX = 0;
    int offsetY = 0;
    float stackScroll = stackView.getScroller().getStackScroll();
    if (tv == null) {
      // If there is no actual task view, then use the stack view as the source view
      // and then offset to the expected transform rect, but bound this to just
      // outside the display rect (to ensure we don't animate from too far away)
      sourceView = stackView;
      transform =
          stackView.getStackAlgorithm().getStackTransform(task, stackScroll, transform, null);
      offsetX = transform.rect.left;
      offsetY = mConfig.displayRect.height();
    } else {
      sourceView = tv.mThumbnailView;
      transform =
          stackView.getStackAlgorithm().getStackTransform(task, stackScroll, transform, null);
    }

    // Compute the thumbnail to scale up from
    final SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
    ActivityOptions opts = null;
    if (task.thumbnail != null && task.thumbnail.getWidth() > 0 && task.thumbnail.getHeight() > 0) {
      ActivityOptions.OnAnimationStartedListener animStartedListener = null;
      if (lockToTask) {
        animStartedListener =
            new ActivityOptions.OnAnimationStartedListener() {
              boolean mTriggered = false;

              @Override
              public void onAnimationStarted() {
                if (!mTriggered) {
                  postDelayed(
                      new Runnable() {
                        @Override
                        public void run() {
                          mCb.onScreenPinningRequest();
                        }
                      },
                      350);
                  mTriggered = true;
                }
              }
            };
      }
      if (tv != null) {
        postDrawHeaderThumbnailTransitionRunnable(
            tv, offsetX, offsetY, transform, animStartedListener);
      }
      if (mConfig.multiStackEnabled) {
        opts =
            ActivityOptions.makeCustomAnimation(
                sourceView.getContext(),
                R.anim.recents_from_unknown_enter,
                R.anim.recents_from_unknown_exit,
                sourceView.getHandler(),
                animStartedListener);
      } else {
        opts =
            ActivityOptions.makeThumbnailAspectScaleUpAnimation(
                sourceView,
                Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8).createAshmemBitmap(),
                offsetX,
                offsetY,
                transform.rect.width(),
                transform.rect.height(),
                sourceView.getHandler(),
                animStartedListener);
      }
    }

    final ActivityOptions launchOpts = opts;
    final Runnable launchRunnable =
        new Runnable() {
          @Override
          public void run() {
            if (task.isActive) {
              // Bring an active task to the foreground
              ssp.moveTaskToFront(task.key.id, launchOpts);
            } else {
              if (ssp.startActivityFromRecents(
                  getContext(), task.key.id, task.activityLabel, launchOpts)) {
                if (launchOpts == null && lockToTask) {
                  mCb.onScreenPinningRequest();
                }
              } else {
                // Dismiss the task and return the user to home if we fail to
                // launch the task
                onTaskViewDismissed(task);
                if (mCb != null) {
                  mCb.onTaskLaunchFailed();
                }

                // Keep track of failed launches
                MetricsLogger.count(getContext(), "overview_task_launch_failed", 1);
              }
            }
          }
        };

    // Keep track of the index of the task launch
    int taskIndexFromFront = 0;
    int taskIndex = stack.indexOfTask(task);
    if (taskIndex > -1) {
      taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
    }
    MetricsLogger.histogram(getContext(), "overview_task_launch_index", taskIndexFromFront);

    // Launch the app right away if there is no task view, otherwise, animate the icon out first
    if (tv == null) {
      launchRunnable.run();
    } else {
      if (task.group != null && !task.group.isFrontMostTask(task)) {
        // For affiliated tasks that are behind other tasks, we must animate the front cards
        // out of view before starting the task transition
        stackView.startLaunchTaskAnimation(tv, launchRunnable, lockToTask);
      } else {
        // Otherwise, we can start the task transition immediately
        stackView.startLaunchTaskAnimation(tv, null, lockToTask);
        launchRunnable.run();
      }
    }
  }
 @Override
 public void onTaskViewFocusChanged(TaskView tv, boolean focused) {
   if (focused) {
     mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
   }
 }
  /** Synchronizes the views with the model */
  boolean synchronizeStackViewsWithModel() {
    if (mStackViewsDirty) {
      RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
      SystemServicesProxy ssp = loader.getSystemServicesProxy();

      // Get all the task transforms
      ArrayList<Task> tasks = mStack.getTasks();
      float stackScroll = mStackScroller.getStackScroll();
      int[] visibleRange = mTmpVisibleRange;
      boolean isValidVisibleRange =
          updateStackTransforms(mCurrentTaskTransforms, tasks, stackScroll, visibleRange, false);
      if (mDebugOverlay != null) {
        mDebugOverlay.setText("vis[" + visibleRange[1] + "-" + visibleRange[0] + "]");
      }

      // Return all the invisible children to the pool
      mTmpTaskViewMap.clear();
      int childCount = getChildCount();
      for (int i = childCount - 1; i >= 0; i--) {
        TaskView tv = (TaskView) getChildAt(i);
        Task task = tv.getTask();
        int taskIndex = mStack.indexOfTask(task);
        if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) {
          mTmpTaskViewMap.put(task, tv);
        } else {
          mViewPool.returnViewToPool(tv);
        }
      }

      // Pick up all the newly visible children and update all the existing children
      for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) {
        Task task = tasks.get(i);
        TaskViewTransform transform = mCurrentTaskTransforms.get(i);
        TaskView tv = mTmpTaskViewMap.get(task);
        int taskIndex = mStack.indexOfTask(task);

        if (tv == null) {
          tv = mViewPool.pickUpViewFromPool(task, task);

          if (mStackViewsAnimationDuration > 0) {
            // For items in the list, put them in start animating them from the
            // approriate ends of the list where they are expected to appear
            if (Float.compare(transform.p, 0f) <= 0) {
              mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpTransform, null);
            } else {
              mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpTransform, null);
            }
            tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0);
          }
        }

        // Animate the task into place
        tv.updateViewPropertiesToTaskTransform(
            mCurrentTaskTransforms.get(taskIndex),
            mStackViewsAnimationDuration,
            mRequestUpdateClippingListener);

        // Request accessibility focus on the next view if we removed the task
        // that previously held accessibility focus
        childCount = getChildCount();
        if (childCount > 0 && ssp.isTouchExplorationEnabled()) {
          TaskView atv = (TaskView) getChildAt(childCount - 1);
          int indexOfTask = mStack.indexOfTask(atv.getTask());
          if (mPrevAccessibilityFocusedIndex != indexOfTask) {
            tv.requestAccessibilityFocus();
            mPrevAccessibilityFocusedIndex = indexOfTask;
          }
        }
      }

      // Reset the request-synchronize params
      mStackViewsAnimationDuration = 0;
      mStackViewsDirty = false;
      mStackViewsClipDirty = true;
      return true;
    }
    return false;
  }
  void showRelativeAffiliatedTask(boolean showNextTask) {
    // Return early if there is no focused stack
    int focusedStackId = mSystemServicesProxy.getFocusedStack();
    TaskStack focusedStack = null;
    RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
    RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
    loader.preloadTasks(plan, true /* isTopTaskHome */);
    if (mConfig.multiStackEnabled) {
      if (focusedStackId < 0) return;
      focusedStack = plan.getTaskStack(focusedStackId);
    } else {
      focusedStack = plan.getAllTaskStacks().get(0);
    }

    // Return early if there are no tasks in the focused stack
    if (focusedStack == null || focusedStack.getTaskCount() == 0) return;

    ActivityManager.RunningTaskInfo runningTask = mSystemServicesProxy.getTopMostTask();
    // Return early if there is no running task (can't determine affiliated tasks in this case)
    if (runningTask == null) return;
    // Return early if the running task is in the home stack (optimization)
    if (mSystemServicesProxy.isInHomeStack(runningTask.id)) return;

    // Find the task in the recents list
    ArrayList<Task> tasks = focusedStack.getTasks();
    Task toTask = null;
    ActivityOptions launchOpts = null;
    int taskCount = tasks.size();
    int numAffiliatedTasks = 0;
    for (int i = 0; i < taskCount; i++) {
      Task task = tasks.get(i);
      if (task.key.id == runningTask.id) {
        TaskGrouping group = task.group;
        Task.TaskKey toTaskKey;
        if (showNextTask) {
          toTaskKey = group.getNextTaskInGroup(task);
          launchOpts =
              ActivityOptions.makeCustomAnimation(
                  mContext,
                  R.anim.recents_launch_next_affiliated_task_target,
                  R.anim.recents_launch_next_affiliated_task_source);
        } else {
          toTaskKey = group.getPrevTaskInGroup(task);
          launchOpts =
              ActivityOptions.makeCustomAnimation(
                  mContext,
                  R.anim.recents_launch_prev_affiliated_task_target,
                  R.anim.recents_launch_prev_affiliated_task_source);
        }
        if (toTaskKey != null) {
          toTask = focusedStack.findTaskWithId(toTaskKey.id);
        }
        numAffiliatedTasks = group.getTaskCount();
        break;
      }
    }

    // Return early if there is no next task
    if (toTask == null) {
      if (numAffiliatedTasks > 1) {
        if (showNextTask) {
          mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
              ActivityOptions.makeCustomInPlaceAnimation(
                  mContext, R.anim.recents_launch_next_affiliated_task_bounce));
        } else {
          mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
              ActivityOptions.makeCustomInPlaceAnimation(
                  mContext, R.anim.recents_launch_prev_affiliated_task_bounce));
        }
      }
      return;
    }

    // Keep track of actually launched affiliated tasks
    MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);

    // Launch the task
    if (toTask.isActive) {
      // Bring an active task to the foreground
      mSystemServicesProxy.moveTaskToFront(toTask.key.id, launchOpts);
    } else {
      mSystemServicesProxy.startActivityFromRecents(
          mContext, toTask.key.id, toTask.activityLabel, launchOpts);
    }
  }
  /** Starts the recents activity */
  void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome) {
    RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
    RecentsConfiguration.reinitialize(mContext, mSystemServicesProxy);

    if (sInstanceLoadPlan == null) {
      // Create a new load plan if onPreloadRecents() was never triggered
      sInstanceLoadPlan = loader.createLoadPlan(mContext);
    }

    // Temporarily skip the transition (use a dummy fade) if multi stack is enabled.
    // For multi-stack we need to figure out where each of the tasks are going.
    if (mConfig.multiStackEnabled) {
      loader.preloadTasks(sInstanceLoadPlan, true);
      ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks();
      TaskStack stack = stacks.get(0);
      mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, true);
      TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
          mDummyStackView.computeStackVisibilityReport();
      ActivityOptions opts = getUnknownTransitionActivityOptions();
      startAlternateRecentsActivity(
          topTask,
          opts,
          true /* fromHome */,
          false /* fromSearchHome */,
          false /* fromThumbnail */,
          stackVr);
      return;
    }

    if (!sInstanceLoadPlan.hasTasks()) {
      loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
    }
    ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks();
    TaskStack stack = stacks.get(0);

    // Prepare the dummy stack for the transition
    mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome);
    TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
        mDummyStackView.computeStackVisibilityReport();
    boolean hasRecentTasks = stack.getTaskCount() > 0;
    boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;

    if (useThumbnailTransition) {

      // Try starting with a thumbnail transition
      ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack, mDummyStackView);
      if (opts != null) {
        startAlternateRecentsActivity(
            topTask,
            opts,
            false /* fromHome */,
            false /* fromSearchHome */,
            true /* fromThumbnail */,
            stackVr);
      } else {
        // Fall through below to the non-thumbnail transition
        useThumbnailTransition = false;
      }
    }

    if (!useThumbnailTransition) {
      // If there is no thumbnail transition, but is launching from home into recents, then
      // use a quick home transition and do the animation from home
      if (hasRecentTasks) {
        String homeActivityPackage = mSystemServicesProxy.getHomeActivityPackageName();
        String searchWidgetPackage =
            Prefs.getString(mContext, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null);

        // Determine whether we are coming from a search owned home activity
        boolean fromSearchHome =
            (homeActivityPackage != null) && homeActivityPackage.equals(searchWidgetPackage);
        ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
        startAlternateRecentsActivity(
            topTask, opts, true /* fromHome */, fromSearchHome, false /* fromThumbnail */, stackVr);
      } else {
        // Otherwise we do the normal fade from an unknown source
        ActivityOptions opts = getUnknownTransitionActivityOptions();
        startAlternateRecentsActivity(
            topTask,
            opts,
            true /* fromHome */,
            false /* fromSearchHome */,
            false /* fromThumbnail */,
            stackVr);
      }
    }
    mLastToggleTime = SystemClock.elapsedRealtime();
  }