PhoneStatusBar.java
private View.OnLongClickListener mRecentsLongClickListener = newView.OnLongClickListener() {
@Overridepublic booleanonLongClick(View v) {if (mRecents == null || !ActivityManager.supportsMultiWindow()|| !getComponent(Divider.class).getView().getSnapAlgorithm()
.isSplitScreenFeasible()) {return false;
}
toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);return true;
}
};
@Overrideprotected void toggleSplitScreenMode(int metricsDockAction, intmetricsUndockAction) {if (mRecents == null) {return;
}int dockSide =WindowManagerProxy.getInstance().getDockSide();if (dockSide ==WindowManager.DOCKED_INVALID) {//进入分屏
mRecents.dockTopTask(NavigationBarGestureHelper.DRAG_MODE_NONE,
ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT,null, metricsDockAction);
}else{//退出分屏
EventBus.getDefault().send(newUndockingTaskEvent());if (metricsUndockAction != -1) {
MetricsLogger.action(mContext, metricsUndockAction);
}
}
}
Recents.java
@Overridepublic boolean dockTopTask(int dragMode, intstackCreateMode, Rect initialBounds,intmetricsDockAction) {//Ensure the device has been provisioned before allowing the user to interact with//recents
if (!isUserSetup()) {return false;
}
Point realSize= newPoint();if (initialBounds == null) {
mContext.getSystemService(DisplayManager.class).getDisplay(Display.DEFAULT_DISPLAY)
.getRealSize(realSize);
initialBounds= new Rect(0, 0, realSize.x, realSize.y);
}int currentUser =sSystemServicesProxy.getCurrentUser();
SystemServicesProxy ssp=Recents.getSystemServices();
ActivityManager.RunningTaskInfo runningTask=ssp.getRunningTask();boolean screenPinningActive =ssp.isScreenPinningActive();boolean isRunningTaskInHomeStack = runningTask != null &&SystemServicesProxy.isHomeStack(runningTask.stackId);if (runningTask != null && !isRunningTaskInHomeStack && !screenPinningActive) {
logDockAttempt(mContext, runningTask.topActivity, runningTask.resizeMode);
// 可分屏if(runningTask.isDockable) {if (metricsDockAction != -1) {
MetricsLogger.action(mContext, metricsDockAction,
runningTask.topActivity.flattenToShortString());
}if(sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.dockTopTask(runningTask.id, dragMode, stackCreateMode, initialBounds);
}else{if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks=mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);if (callbacks != null) {try{
callbacks.dockTopTask(runningTask.id, dragMode, stackCreateMode,
initialBounds);
}catch(RemoteException e) {
Log.e(TAG,"Callback failed", e);
}
}else{
Log.e(TAG,"No SystemUI callbacks found for user: " +currentUser);
}
}
}
mDraggingInRecentsCurrentUser=currentUser;return true;
}else{
// 不支持分屏
EventBus.getDefault().send(newShowUserToastEvent(
R.string.recents_incompatible_app_message, Toast.LENGTH_SHORT));return false;
}
}else{return false;
}
}
RecentsImpl.java
public void dockTopTask(int topTaskId, intdragMode,intstackCreateMode, Rect initialBounds) {
SystemServicesProxy ssp=Recents.getSystemServices();//Make sure we inform DividerView before we actually start the activity so we can change//the resize mode already.
if(ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
EventBus.getDefault().send(newDockedTopTaskEvent(dragMode, initialBounds));
showRecents(false /*triggeredFromAltTab*/,
dragMode==NavigationBarGestureHelper.DRAG_MODE_RECENTS,false /*animate*/,true /*launchedWhileDockingTask*/,false /*fromHome*/,
DividerView.INVALID_RECENTS_GROW_TARGET);
}
}
SystemServicesProxy.java
/**Docks an already resumed task to the side of the screen.*/
public boolean moveTaskToDockedStack(int taskId, intcreateMode, Rect initialBounds) {if (mIam == null) {return false;
}try{return mIam.moveTaskToDockedStack(taskId, createMode, true /*onTop*/,false /*animate*/, initialBounds, true /*moveHomeStackFront*/);
}catch(RemoteException e) {
e.printStackTrace();
}return false;
}
mIam 是 IActivityManager 对象
ActivityManagerService.java
/*** Moves the input task to the docked stack.
*
*@paramtaskId Id of task to move.
*@paramcreateMode The mode the docked stack should be created in if it doesn't exist
* already. See
* {@linkandroid.app.ActivityManager#DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT}
* and
* {@linkandroid.app.ActivityManager#DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT}
*@paramtoTop If the task and stack should be moved to the top.
*@paramanimate Whether we should play an animation for the moving the task
*@paraminitialBounds If the docked stack gets created, it will use these bounds for the
* docked stack. Pass {@codenull} to use default bounds.*/@Overridepublic boolean moveTaskToDockedStack(int taskId, int createMode, boolean toTop, booleananimate,
Rect initialBounds,booleanmoveHomeStackFront) {
enforceCallingPermission(MANAGE_ACTIVITY_STACKS,"moveTaskToDockedStack()");synchronized (this) {long ident =Binder.clearCallingIdentity();try{if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToDockedStack: moving task=" +taskId+ " to createMode=" + createMode + " toTop=" +toTop);
mWindowManager.setDockedStackCreateState(createMode, initialBounds);final boolean moved =mStackSupervisor.moveTaskToStackLocked(
taskId, DOCKED_STACK_ID, toTop,!FORCE_FOCUS, "moveTaskToDockedStack",
animate, DEFER_RESUME);if(moved) {if(moveHomeStackFront) {
mStackSupervisor.moveHomeStackToFront("moveTaskToDockedStack");
}
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
}returnmoved;
}finally{
Binder.restoreCallingIdentity(ident);
}
}
}
ActivityStackSupervisor.java
boolean moveTaskToStackLocked(int taskId, int stackId, boolean toTop, booleanforceFocus,
String reason,boolean animate, booleandeferResume) {final TaskRecord task =anyTaskForIdLocked(taskId);if (task == null) {
Slog.w(TAG,"moveTaskToStack: no task for id=" +taskId);return false;
}if (task.stack != null && task.stack.mStackId ==stackId) {//You are already in the right stack silly...
Slog.i(TAG, "moveTaskToStack: taskId=" + taskId + " already in stackId=" +stackId);return true;
}if (stackId == FREEFORM_WORKSPACE_STACK_ID && !mService.mSupportsFreeformWindowManagement) {throw new IllegalArgumentException("moveTaskToStack:"
+ "Attempt to move task " + taskId + " to unsupported freeform stack");
}final ActivityRecord topActivity =task.getTopActivity();final int sourceStackId = task.stack != null ?task.stack.mStackId : INVALID_STACK_ID;final boolean mightReplaceWindow =StackId.replaceWindowsOnTaskMove(sourceStackId, stackId)&& topActivity != null;if(mightReplaceWindow) {//We are about to relaunch the activity because its configuration changed due to//being maximized, i.e. size change. The activity will first remove the old window//and then add a new one. This call will tell window manager about this, so it can//preserve the old window until the new one is drawn. This prevents having a gap//between the removal and addition, in which no window is visible. We also want the//entrance of the new window to be properly animated.//Note here we always set the replacing window first, as the flags might be needed//during the relaunch. If we end up not doing any relaunch, we clear the flags later.
mWindowManager.setReplacingWindow(topActivity.appToken, animate);
}
mWindowManager.deferSurfaceLayout();final int preferredLaunchStackId =stackId;boolean kept = true;try{final ActivityStack stack =moveTaskToStackUncheckedLocked(
task, stackId, toTop, forceFocus, reason+ " moveTaskToStack");
stackId=stack.mStackId;if (!animate) {
stack.mNoAnimActivities.add(topActivity);
}//We might trigger a configuration change. Save the current task bounds for freezing.
mWindowManager.prepareFreezingTaskBounds(stack.mStackId);//Make sure the task has the appropriate bounds/size for the stack it is in.
if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
kept=resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM,!mightReplaceWindow, deferResume);
}else if (stackId ==FREEFORM_WORKSPACE_STACK_ID) {
Rect bounds=task.getLaunchBounds();if (bounds == null) {
stack.layoutTaskInStack(task,null);
bounds=task.mBounds;
}
kept= resizeTaskLocked(task, bounds, RESIZE_MODE_FORCED, !mightReplaceWindow,
deferResume);
}else if (stackId == DOCKED_STACK_ID || stackId ==PINNED_STACK_ID) {
kept=resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM,!mightReplaceWindow, deferResume);
}
}finally{
mWindowManager.continueSurfaceLayout();
}if(mightReplaceWindow) {//If we didn't actual do a relaunch (indicated by kept==true meaning we kept the old//window), we need to clear the replace window settings. Otherwise, we schedule a//timeout to remove the old window if the replacing window is not coming in time.
mWindowManager.scheduleClearReplacingWindowIfNeeded(topActivity.appToken, !kept);
}if (!deferResume) {//The task might have already been running and its visibility needs to be synchronized with//the visibility of the stack / windows.
ensureActivitiesVisibleLocked(null, 0, !mightReplaceWindow);
resumeFocusedStackTopActivityLocked();
}
handleNonResizableTaskIfNeeded(task, preferredLaunchStackId, stackId);return (preferredLaunchStackId ==stackId);
}
final ActivityStack stack = moveTaskToStackUncheckedLocked(
task, stackId, toTop, forceFocus, reason + " moveTaskToStack");
/*** Moves the specified task record to the input stack id.
* WARNING: This method performs an unchecked/raw move of the task and
* can leave the system in an unstable state if used incorrectly.
* Use {@link#moveTaskToStackLocked} to perform safe task movement to a stack.
*@paramtask Task to move.
*@paramstackId Id of stack to move task to.
*@paramtoTop True if the task should be placed at the top of the stack.
*@paramforceFocus if focus should be moved to the new stack
*@paramreason Reason the task is been moved.
*@returnThe stack the task was moved to.*/ActivityStack moveTaskToStackUncheckedLocked(
TaskRecord task,int stackId, boolean toTop, booleanforceFocus, String reason) {if (StackId.isMultiWindowStack(stackId) && !mService.mSupportsMultiWindow) {throw new IllegalStateException("moveTaskToStackUncheckedLocked: Device doesn't "
+ "support multi-window task=" + task + " to stackId=" +stackId);
}final ActivityRecord r =task.topRunningActivityLocked();final ActivityStack prevStack =task.stack;final boolean wasFocused = isFocusedStack(prevStack) && (topRunningActivityLocked() ==r);final boolean wasResumed = prevStack.mResumedActivity ==r;//In some cases the focused stack isn't the front stack. E.g. pinned stack.//Whenever we are moving the top activity from the front stack we want to make sure to move//the stack to the front.
final boolean wasFront =isFrontStack(prevStack)&& (prevStack.topRunningActivityLocked() ==r);if (stackId == DOCKED_STACK_ID && !task.isResizeable()) {//We don't allow moving a unresizeable task to the docked stack since the docked//stack is used for split-screen mode and will cause things like the docked divider to//show up. We instead leave the task in its current stack or move it to the fullscreen//stack if it isn't currently in a stack.
stackId = (prevStack != null) ?prevStack.mStackId : FULLSCREEN_WORKSPACE_STACK_ID;
Slog.w(TAG,"Can not move unresizeable task=" +task+ " to docked stack. Moving to stackId=" + stackId + " instead.");
}if (stackId ==FREEFORM_WORKSPACE_STACK_ID&&mService.mUserController.shouldConfirmCredentials(task.userId)) {
stackId= (prevStack != null) ?prevStack.mStackId : FULLSCREEN_WORKSPACE_STACK_ID;
Slog.w(TAG,"Can not move locked profile task=" +task+ " to freeform stack. Moving to stackId=" + stackId + " instead.");
}//Temporarily disable resizeablility of task we are moving. We don't want it to be resized//if a docked stack is created below which will lead to the stack we are moving from and//its resizeable tasks being resized.
task.mTemporarilyUnresizable = true;final ActivityStack stack =getStack(stackId, CREATE_IF_NEEDED, toTop);
task.mTemporarilyUnresizable= false;
mWindowManager.moveTaskToStack(task.taskId, stack.mStackId, toTop);
stack.addTask(task, toTop, reason);//If the task had focus before (or we're requested to move focus),//move focus to the new stack by moving the stack to the front.
stack.moveToFrontAndResumeStateIfNeeded(
r, forceFocus|| wasFocused ||wasFront, wasResumed, reason);returnstack;
}
WindowManagerService.java
public void moveTaskToStack(int taskId, int stackId, booleantoTop) {synchronized(mWindowMap) {if (DEBUG_STACK) Slog.i(TAG_WM, "moveTaskToStack: moving taskId=" +taskId+ " to stackId=" + stackId + " at " + (toTop ? "top" : "bottom"));
Task task=mTaskIdToTask.get(taskId);if (task == null) {if (DEBUG_STACK) Slog.i(TAG_WM, "moveTaskToStack: could not find taskId=" +taskId);return;
}
TaskStack stack=mStackIdToStack.get(stackId);if (stack == null) {if (DEBUG_STACK) Slog.i(TAG_WM, "moveTaskToStack: could not find stackId=" +stackId);return;
}
task.moveTaskToStack(stack, toTop);final DisplayContent displayContent =stack.getDisplayContent();
displayContent.layoutNeeded= true;
mWindowPlacerLocked.performSurfacePlacement();
}
}
WindowSurfacePlacer.java
/**
* Positions windows and their surfaces.
*
* It sets positions of windows by calculating their frames and then applies this by positioning
* surfaces according to these frames. Z layer is still assigned withing WindowManagerService.
*/
class WindowSurfacePlacer {
final voidperformSurfacePlacement() {if (mDeferDepth > 0) {return;
}int loopCount = 6;do{
mTraversalScheduled= false;
performSurfacePlacementLoop();
mService.mH.removeMessages(DO_TRAVERSAL);
loopCount--;
}while (mTraversalScheduled && loopCount > 0);
mWallpaperActionPending= false;
}
}