Android 7.1 SystemUI--Multi-Window多窗口模式
PhoneStatusBar.java
private View.OnLongClickListener mRecentsLongClickListener = new View.OnLongClickListener() { @Override public boolean onLongClick(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; } };
@Override protected void toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) { 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(new UndockingTaskEvent()); if (metricsUndockAction != -1) { MetricsLogger.action(mContext, metricsUndockAction); } } }
Recents.java
@Override public boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds, int metricsDockAction) { // Ensure the device has been provisioned before allowing the user to interact with // recents if (!isUserSetup()) { return false; } Point realSize = new Point(); 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(new ShowUserToastEvent( R.string.recents_incompatible_app_message, Toast.LENGTH_SHORT)); return false; } } else { return false; } }
RecentsImpl.java
public void dockTopTask(int topTaskId, int dragMode, int stackCreateMode, 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(new DockedTopTaskEvent(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, int createMode, 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. * * @param taskId Id of task to move. * @param createMode The mode the docked stack should be created in if it doesn't exist * already. See * {@link android.app.ActivityManager#DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT} * and * {@link android.app.ActivityManager#DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT} * @param toTop If the task and stack should be moved to the top. * @param animate Whether we should play an animation for the moving the task * @param initialBounds If the docked stack gets created, it will use these bounds for the * docked stack. Pass {@code null} to use default bounds. */ @Override public boolean moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate, Rect initialBounds, boolean moveHomeStackFront) { 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); } return moved; } finally { Binder.restoreCallingIdentity(ident); } } }
ActivityStackSupervisor.java
boolean moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus, String reason, boolean animate, boolean deferResume) { 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. * @param task Task to move. * @param stackId Id of stack to move task to. * @param toTop True if the task should be placed at the top of the stack. * @param forceFocus if focus should be moved to the new stack * @param reason Reason the task is been moved. * @return The stack the task was moved to. */ ActivityStack moveTaskToStackUncheckedLocked( TaskRecord task, int stackId, boolean toTop, boolean forceFocus, 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); return stack; }
WindowManagerService.java
public void moveTaskToStack(int taskId, int stackId, boolean toTop) { 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 void performSurfacePlacement() { if (mDeferDepth > 0) { return; } int loopCount = 6; do { mTraversalScheduled = false; performSurfacePlacementLoop(); mService.mH.removeMessages(DO_TRAVERSAL); loopCount--; } while (mTraversalScheduled && loopCount > 0); mWallpaperActionPending = false; }
}