@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(); } } }
/** 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); } }
/** 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()); } } }); } }
/** 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(); } }
/** Computes the stack and task rects */ public void computeRects( int windowWidth, int windowHeight, Rect taskStackBounds, boolean launchedWithAltTab, boolean launchedFromHome) { // Compute the rects in the stack algorithm mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds); // Update the scroll bounds updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome); }
/** Initializes the Recents. */ @ProxyFromPrimaryToCurrentUser @Override public void start() { if (sInstance == null) { sInstance = this; } RecentsTaskLoader.initialize(mContext); mInflater = LayoutInflater.from(mContext); mSystemServicesProxy = new SystemServicesProxy(mContext); mHandler = new Handler(); mTaskStackBounds = new Rect(); mAppWidgetHost = new RecentsAppWidgetHost(mContext, Constants.Values.App.AppWidgetHostId); // Register the task stack listener mTaskStackListener = new TaskStackListenerImpl(mHandler); mSystemServicesProxy.registerTaskStackListener(mTaskStackListener); // Only the owner has the callback to update the SysUI visibility flags, so all non-owner // instances of AlternateRecentsComponent needs to notify the owner when the visibility // changes. if (mSystemServicesProxy.isForegroundUserOwner()) { mProxyBroadcastReceiver = new RecentsOwnerEventProxyReceiver(); IntentFilter filter = new IntentFilter(); filter.addAction(Recents.ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER); filter.addAction(Recents.ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER); mContext.registerReceiverAsUser( mProxyBroadcastReceiver, UserHandle.CURRENT, filter, null, mHandler); } // Initialize some static datastructures TaskStackViewLayoutAlgorithm.initializeCurve(); // Load the header bar layout reloadHeaderBarLayout(); // When we start, preload the data associated with the previous recent tasks. // We can use a new plan since the caches will be the same. RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); loader.preloadTasks(plan, true /* isTopTaskHome */); RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize(); launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize(); launchOpts.onlyLoadForCache = true; loader.loadTasks(mContext, plan, launchOpts); putComponent(Recents.class, this); }
/** 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(); } } } }
/** 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; }
/** Gets the stack transforms of a list of tasks, and returns the visible range of tasks. */ private boolean updateStackTransforms( ArrayList<TaskViewTransform> taskTransforms, ArrayList<Task> tasks, float stackScroll, int[] visibleRangeOut, boolean boundTranslationsToRect) { // XXX: We should be intelligent about where to look for the visible stack range using the // current stack scroll. // XXX: We should log extra cases like the ones below where we don't expect to hit very often // XXX: Print out approximately how many indices we have to go through to find the first visible // transform int taskTransformCount = taskTransforms.size(); int taskCount = tasks.size(); int frontMostVisibleIndex = -1; int backMostVisibleIndex = -1; // We can reuse the task transforms where possible to reduce object allocation if (taskTransformCount < taskCount) { // If there are less transforms than tasks, then add as many transforms as necessary for (int i = taskTransformCount; i < taskCount; i++) { taskTransforms.add(new TaskViewTransform()); } } else if (taskTransformCount > taskCount) { // If there are more transforms than tasks, then just subset the transform list taskTransforms.subList(0, taskCount); } // Update the stack transforms TaskViewTransform prevTransform = null; for (int i = taskCount - 1; i >= 0; i--) { TaskViewTransform transform = mLayoutAlgorithm.getStackTransform( tasks.get(i), stackScroll, taskTransforms.get(i), prevTransform); if (transform.visible) { if (frontMostVisibleIndex < 0) { frontMostVisibleIndex = i; } backMostVisibleIndex = i; } else { if (backMostVisibleIndex != -1) { // We've reached the end of the visible range, so going down the rest of the // stack, we can just reset the transforms accordingly while (i >= 0) { taskTransforms.get(i).reset(); i--; } break; } } if (boundTranslationsToRect) { transform.translationY = Math.min(transform.translationY, mLayoutAlgorithm.mViewRect.bottom); } prevTransform = transform; } if (visibleRangeOut != null) { visibleRangeOut[0] = frontMostVisibleIndex; visibleRangeOut[1] = backMostVisibleIndex; } return frontMostVisibleIndex != -1 && backMostVisibleIndex != -1; }