/** 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;
  }
  /** Prepares the header bar layout. */
  void reloadHeaderBarLayout() {
    Resources res = mContext.getResources();
    mWindowRect = mSystemServicesProxy.getWindowRect();
    mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
    mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
    mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
    mConfig = RecentsConfiguration.reinitialize(mContext, mSystemServicesProxy);
    mConfig.updateOnConfigurationChange();
    Rect searchBarBounds = new Rect();
    // Try and pre-emptively bind the search widget on startup to ensure that we
    // have the right thumbnail bounds to animate to.
    // Note: We have to reload the widget id before we get the task stack bounds below
    if (mSystemServicesProxy.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
      mConfig.getSearchBarBounds(
          mWindowRect.width(), mWindowRect.height(), mStatusBarHeight, searchBarBounds);
    }
    mConfig.getAvailableTaskStackBounds(
        mWindowRect.width(),
        mWindowRect.height(),
        mStatusBarHeight,
        (mConfig.hasTransposedNavBar ? mNavBarWidth : 0),
        searchBarBounds,
        mTaskStackBounds);
    if (mConfig.isLandscape && mConfig.hasTransposedNavBar) {
      mSystemInsets.set(0, mStatusBarHeight, mNavBarWidth, 0);
    } else {
      mSystemInsets.set(0, mStatusBarHeight, 0, mNavBarHeight);
    }

    // Inflate the header bar layout so that we can rebind and draw it for the transition
    TaskStack stack = new TaskStack();
    mDummyStackView = new TaskStackView(mContext, stack);
    TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
    Rect taskStackBounds = new Rect(mTaskStackBounds);
    taskStackBounds.bottom -= mSystemInsets.bottom;
    algo.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds);
    Rect taskViewSize = algo.getUntransformedTaskViewSize();
    int taskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
    synchronized (mHeaderBarLock) {
      mHeaderBar =
          (TaskViewHeader) mInflater.inflate(R.layout.recents_task_view_header, null, false);
      mHeaderBar.measure(
          View.MeasureSpec.makeMeasureSpec(taskViewSize.width(), View.MeasureSpec.EXACTLY),
          View.MeasureSpec.makeMeasureSpec(taskBarHeight, View.MeasureSpec.EXACTLY));
      mHeaderBar.layout(0, 0, taskViewSize.width(), taskBarHeight);
    }
  }
  /** ** TaskStackView.TaskStackCallbacks Implementation *** */
  @Override
  public void onTaskViewClicked(
      final TaskStackView stackView,
      final TaskView tv,
      final TaskStack stack,
      final Task task,
      final boolean lockToTask) {
    // Notify any callbacks of the launching of a new task
    if (mCb != null) {
      mCb.onTaskViewClicked();
    }
    // Skip if we are already launching tasks
    if (mAlreadyLaunchingTask) {
      return;
    }
    mAlreadyLaunchingTask = true;

    // 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) {
      Bitmap b;
      if (tv != null) {
        // Disable any focused state before we draw the header
        if (tv.isFocusedTask()) {
          tv.unsetFocusedTask();
        }

        float scale = tv.getScaleX();
        int fromHeaderWidth = (int) (tv.mHeaderView.getMeasuredWidth() * scale);
        int fromHeaderHeight = (int) (tv.mHeaderView.getMeasuredHeight() * scale);
        b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight, Bitmap.Config.ARGB_8888);
        if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
          b.eraseColor(0xFFff0000);
        } else {
          Canvas c = new Canvas(b);
          c.scale(tv.getScaleX(), tv.getScaleY());
          tv.mHeaderView.draw(c);
          c.setBitmap(null);
        }
      } else {
        // Notify the system to skip the thumbnail layer by using an ALPHA_8 bitmap
        b = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
      }
      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() {
                          ssp.lockCurrentTask();
                        }
                      },
                      350);
                  mTriggered = true;
                }
              }
            };
      }
      opts =
          ActivityOptions.makeThumbnailAspectScaleUpAnimation(
              sourceView,
              b,
              offsetX,
              offsetY,
              transform.rect.width(),
              transform.rect.height(),
              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) {
                  ssp.lockCurrentTask();
                }
              } else {
                // Dismiss the task and return the user to home if we fail to
                // launch the task
                onTaskViewDismissed(task);
                if (mCb != null) {
                  mCb.onTaskLaunchFailed();
                }
              }
            }
          }
        };

    // Launch the app right away if there is no task view, otherwise, animate the icon out first
    if (tv == null) {
      post(launchRunnable);
    } else {
      if (!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);
        postDelayed(launchRunnable, 17);
      }
    }
  }
Beispiel #4
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();
      }
    }
  }