/** 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); }
/** 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); } } } }
/** ** 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(); }