@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();
      }
    }
  }
  /** 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());
              }
            }
          });
    }
  }
  /** 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);
      }
    }
  }
  /** 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);
  }
 @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));
 }
 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);
     }
   }
 }
  /** 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();
        }
      }
    }
  }
예제 #8
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();
      }
    }
  }
  /** 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();
  }
  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);
    }
  }