/** This is called with the full size of the window since we are handling our own insets. */ @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { Log.d(TAG, "onLayout: "); // Get the search bar bounds so that we lay it out if (mSearchBar != null) { Rect searchBarSpaceBounds = new Rect(); mConfig.getSearchBarBounds( getMeasuredWidth(), getMeasuredHeight(), mConfig.systemInsets.top, searchBarSpaceBounds); mSearchBar.layout( searchBarSpaceBounds.left, searchBarSpaceBounds.top, searchBarSpaceBounds.right, searchBarSpaceBounds.bottom); } // Layout each TaskStackView with the full width and height of the window since the // transition view is a child of that stack view List<TaskStackView> stackViews = getTaskStackViews(); int stackCount = stackViews.size(); for (int i = 0; i < stackCount; i++) { TaskStackView stackView = stackViews.get(i); if (stackView.getVisibility() != GONE) { stackView.layout( left, top, left + stackView.getMeasuredWidth(), top + stackView.getMeasuredHeight()); } } }
/** 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; }
/** Set/get the bsp root node */ public void setTaskStacks(ArrayList<TaskStack> stacks) { // Remove all TaskStackViews (but leave the search bar) int childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { View v = getChildAt(i); if (v != mSearchBar) { removeViewAt(i); } } // Create and add all the stacks for this partition of space. mStacks = stacks; int numStacks = mStacks.size(); for (int i = 0; i < numStacks; i++) { TaskStack stack = mStacks.get(i); TaskStackView stackView = new TaskStackView(getContext(), stack); stackView.setCallbacks(this); // Enable debug mode drawing if (mConfig.debugModeEnabled) { stackView.setDebugOverlay(mDebugOverlay); } addView(stackView); } // Reset the launched state mAlreadyLaunchingTask = false; }
/** This is called with the full size of the window since we are handling our own insets. */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); // Get the search bar bounds and measure the search bar layout if (mSearchBar != null) { Rect searchBarSpaceBounds = new Rect(); mConfig.getSearchBarBounds(width, height, mConfig.systemInsets.top, searchBarSpaceBounds); mSearchBar.measure( MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY)); } Rect taskStackBounds = new Rect(); mConfig.getTaskStackBounds( width, height, mConfig.systemInsets.top, mConfig.systemInsets.right, taskStackBounds); // Measure each TaskStackView with the full width and height of the window since the // transition view is a child of that stack view int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (child != mSearchBar && child.getVisibility() != GONE) { TaskStackView tsv = (TaskStackView) child; // Set the insets to be the top/left inset + search bounds tsv.setStackInsetRect(taskStackBounds); tsv.measure(widthMeasureSpec, heightMeasureSpec); } } setMeasuredDimension(width, height); }
/** 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; }
/** 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; }
/** Final callback after Recents is finally hidden. */ public void onRecentsHidden() { // Notify each task stack view List<TaskStackView> stackViews = getTaskStackViews(); int stackCount = stackViews.size(); for (int i = 0; i < stackCount; i++) { TaskStackView stackView = stackViews.get(i); stackView.onRecentsHidden(); } }
/** Notifies each task view of the user interaction. */ public void onUserInteraction() { Log.d(TAG, "onUserInteraction: "); // 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); stackView.onUserInteraction(); } }
/** Requests all task stacks to start their enter-recents animation */ public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (child != mSearchBar) { TaskStackView stackView = (TaskStackView) child; stackView.startEnterRecentsAnimation(ctx); } } }
/** ** RecentsPackageMonitor.PackageCallbacks Implementation *** */ @Override public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) { // Propagate this event down to each task stack view List<TaskStackView> stackViews = getTaskStackViews(); int stackCount = stackViews.size(); for (int i = 0; i < stackCount; i++) { TaskStackView stackView = stackViews.get(i); stackView.onPackagesChanged(monitor, packageName, userId); } }
/** Notifies each task view of the user interaction. */ public void onUserInteraction() { // 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; stackView.onUserInteraction(); } } }
/** ** RecentsPackageMonitor.PackageCallbacks Implementation *** */ @Override public void onComponentRemoved(HashSet<ComponentName> cns) { // Propagate this event down to each task stack view int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (child != mSearchBar) { TaskStackView stackView = (TaskStackView) child; stackView.onComponentRemoved(cns); } } }
/** Focuses the next task in the first stack view */ public void focusNextTask(boolean forward) { // 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; stackView.focusNextTask(forward); break; } } }
/** Dismisses the focused task. */ public void dismissFocusedTask() { // 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; stackView.dismissFocusedTask(); break; } } }
/** Requests all task stacks to start their exit-recents animation */ public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (child != mSearchBar) { TaskStackView stackView = (TaskStackView) child; stackView.startExitToHomeAnimation(ctx); } } // Notify of the exit animation mCb.onExitToHomeAnimationTriggered(); }
/** Requests all task stacks to start their enter-recents animation */ public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) { Log.d(TAG, "startEnterRecentsAnimation: "); // We have to increment/decrement the post animation trigger in case there are no children // to ensure that it runs ctx.postAnimationTrigger.increment(); List<TaskStackView> stackViews = getTaskStackViews(); int stackCount = stackViews.size(); for (int i = 0; i < stackCount; i++) { TaskStackView stackView = stackViews.get(i); stackView.startEnterRecentsAnimation(ctx); } ctx.postAnimationTrigger.decrement(); }
/** * Caches the header thumbnail used for a window animation asynchronously into {@link * #mThumbnailTransitionBitmapCache}. */ void preCacheThumbnailTransitionBitmapAsync( ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView, boolean isTopTaskHome) { preloadIcon(topTask); // Update the destination rect mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome); final Task toTask = new Task(); final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView, topTask.id, toTask); new AsyncTask<Void, Void, Bitmap>() { @Override protected Bitmap doInBackground(Void... params) { return drawThumbnailTransitionBitmap(toTask, toTransform); } @Override protected void onPostExecute(Bitmap bitmap) { mThumbnailTransitionBitmapCache = bitmap; mThumbnailTransitionBitmapCacheKey = toTask; } }.execute(); }
/** This is called with the full size of the window since we are handling our own insets. */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Log.d(TAG, "onMeasure: "); int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); // Get the search bar bounds and measure the search bar layout Rect searchBarSpaceBounds = new Rect(); if (mSearchBar != null) { mConfig.getSearchBarBounds(width, height, mConfig.systemInsets.top, searchBarSpaceBounds); mSearchBar.measure( MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY)); } Rect taskStackBounds = new Rect(); mConfig.getAvailableTaskStackBounds( width, height, mConfig.systemInsets.top, mConfig.systemInsets.right, searchBarSpaceBounds, taskStackBounds); // Measure each TaskStackView with the full width and height of the window since the // transition view is a child of that stack view List<TaskStackView> stackViews = getTaskStackViews(); List<Rect> stackViewsBounds = mLayoutAlgorithm.computeStackRects(stackViews, taskStackBounds); int stackCount = stackViews.size(); for (int i = 0; i < stackCount; i++) { TaskStackView stackView = stackViews.get(i); if (stackView.getVisibility() != GONE) { // We are going to measure the TaskStackView with the whole RecentsView dimensions, // but the actual stack is going to be inset to the bounds calculated by the layout // algorithm stackView.setStackInsetRect(stackViewsBounds.get(i)); stackView.measure(widthMeasureSpec, heightMeasureSpec); } } setMeasuredDimension(width, height); }
/** Launches a given task. */ public boolean launchTask(Task task) { Log.d(TAG, "launchTask: "); // 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(); // Iterate the stack views and try and find the given task. List<TaskView> taskViews = stackView.getTaskViews(); int taskViewCount = taskViews.size(); for (int j = 0; j < taskViewCount; j++) { TaskView tv = taskViews.get(j); if (tv.getTask() == task) { onTaskViewClicked(stackView, tv, stack, task, false); return true; } } } return false; }
/** Launches the focused task from the first stack if possible */ public boolean launchFocusedTask() { // 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; // Iterate the stack views and try and find the focused task int taskCount = stackView.getChildCount(); for (int j = 0; j < taskCount; j++) { TaskView tv = (TaskView) stackView.getChildAt(j); Task task = tv.getTask(); if (tv.isFocusedTask()) { onTaskViewClicked(stackView, tv, stack, task, false); return true; } } } } return false; }
/** 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); } }
/** Set/get the bsp root node */ public void setTaskStacks(ArrayList<TaskStack> stacks) { Log.d(TAG, "setTaskStacks: "); int numStacks = stacks.size(); // Remove all/extra stack views int numTaskStacksToKeep = 0; // Keep no tasks if we are recreating the layout if (mConfig.launchedReuseTaskStackViews) { numTaskStacksToKeep = Math.min(mTaskStackViews.size(), numStacks); } for (int i = mTaskStackViews.size() - 1; i >= numTaskStacksToKeep; i--) { removeView(mTaskStackViews.remove(i)); } // Update the stack views that we are keeping for (int i = 0; i < numTaskStacksToKeep; i++) { TaskStackView tsv = mTaskStackViews.get(i); // If onRecentsHidden is not triggered, we need to the stack view again here tsv.reset(); tsv.setStack(stacks.get(i)); } // Add remaining/recreate stack views mStacks = stacks; for (int i = mTaskStackViews.size(); i < numStacks; i++) { TaskStack stack = stacks.get(i); TaskStackView stackView = new TaskStackView(getContext(), stack); stackView.setCallbacks(this); addView(stackView); mTaskStackViews.add(stackView); } // Enable debug mode drawing on all the stacks if necessary if (mConfig.debugModeEnabled) { for (int i = mTaskStackViews.size() - 1; i >= 0; i--) { TaskStackView stackView = mTaskStackViews.get(i); stackView.setDebugOverlay(mDebugOverlay); } } // Trigger a new layout requestLayout(); }
/** ** 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); } } }
/** 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(); }
/** ** 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(); } } }