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;
    }

 

}

 

posted @ 2017-09-05 15:16  行走的思想  阅读(2395)  评论(2编辑  收藏  举报