Android 7.0 SystemUI(2)--Multi-Window多窗口模式

从Android7.0开始,Google原生加入了应用多窗口支持,相关特性可查看

多窗口支持


本文涉及到的代码

frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
frameworks/base/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java 
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java 
frameworks/base/core/java/android/app/ActivityManager.java
frameworks/base/packages/SystemUI/src/com/android/systemui/recents/Recents.java 
frameworks/base/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java 
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java.java

SystemUI在启动过程中会启动Divider,主要是管理分屏分割线的

frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
   private final Class[] SERVICES = new Class[] {
            com.android.systemui.tuner.TunerService.class,
            com.android.systemui.keyguard.KeyguardViewMediator.class,
            com.android.systemui.recents.Recents.class,
            com.android.systemui.volume.VolumeUI.class,
            Divider.class,
            com.android.systemui.statusbar.SystemBars.class,
            com.android.systemui.usb.StorageNotification.class,
            com.android.systemui.power.PowerUI.class,
            com.android.systemui.media.RingtonePlayer.class,
            com.android.systemui.keyboard.KeyboardUI.class,
            com.android.systemui.tv.pip.PipUI.class,
            com.android.systemui.shortcut.ShortcutKeyDispatcher.class
    };


该类比较简单

frameworks/base/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
/**
 * Controls the docked stack divider.
 */
public class Divider extends SystemUI {
    private DividerWindowManager mWindowManager;
    private DividerView mView;
    ...
    private DockDividerVisibilityListener mDockDividerVisibilityListener;
    ...
    private ForcedResizableInfoActivityController mForcedResizableController;

主要有几个比较重要的对象
DividerWindowManager--主要是封装了 WindowManager

frameworks/base/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
/**
 * Manages the window parameters of the docked stack divider.
 */
public class DividerWindowManager {

    private static final String WINDOW_TITLE = "DockedStackDivider";

    private final WindowManager mWindowManager;
    private WindowManager.LayoutParams mLp;
    private View mView;

    public DividerWindowManager(Context ctx) {
        mWindowManager = ctx.getSystemService(WindowManager.class);
    }

    public void add(View view, int width, int height) {
        mLp = new WindowManager.LayoutParams(
                width, height, TYPE_DOCK_DIVIDER,
                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL
                        | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
                PixelFormat.TRANSLUCENT);
        mLp.setTitle(WINDOW_TITLE);
        mLp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
        view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
        mWindowManager.addView(view, mLp);
        mView = view;
    }

    public void remove() {
        if (mView != null) {
            mWindowManager.removeView(mView);
        }
        mView = null;
    }
DividerView就是分屏分割线的view

Android 7.0 SystemUI(2)--Multi-Window多窗口模式_第1张图片

DockDividerVisibilityListener是用来回调控制DividerView状态的(包括)

通过长按多任务按键可触发多窗口模式进入分屏状态

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/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);
            }
        }
    }

dockTopTask方法第二个参数是标示应用进入分屏后显示的区域

主要有两种模式

frameworks/base/core/java/android/app/ActivityManager.java
//横屏时在上方显示或者竖屏时在左边显示
public static final int DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT = 0;

//横屏时在下方显示或者竖屏时在右边显示
public static final int DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT = 1;

进入到dockTopTask方法中

frameworks/base/packages/SystemUI/src/com/android/systemui/recents/Recents.java 
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 {
			//应用不支持分屏
                Toast.makeText(mContext, R.string.recents_incompatible_app_message,
                        Toast.LENGTH_SHORT).show();
                return false;
            }
        } else {
            return false;
        }
    }
从这里可以看到需要满足以下条件才能进入分屏

当前正在显示的界面不是launcher或者recentsactivity(launcher和recentsactivity都是在HOME_STACK_ID上),即在launcher或者recentsactivity界面长按多任务键是不会进入分屏模式的,另外还需要满足当前不在画中画模式。

应用支持分屏时调用

frameworks/base/packages/SystemUI/src/com/android/systemui/recents/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);
        }
    }
	
frameworks/base/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
 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;
    }
最终会进入到ams的moveTaskToDockedStack中。

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java 
       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);
				//移动task
                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);
            }
        }
    }

首先会检查调用者是否有android.permission.MANAGE_ACTIVITY_STACKS权限,如果有权限这会调用mStackSupervisor.moveTaskToStackLocked开始移动task
frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java
   boolean moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus,
            String reason, boolean animate, boolean deferResume) {
			//taskId要移动的task的id,先根据id找到TaskRecord
        final TaskRecord task = anyTaskForIdLocked(taskId);
        ...
		//stackId为DOCKED_STACK_ID,如果TaskRecord的id已经是DOCKED_STACK_ID,说明该task已经在DOCKED_STACK_ID上,就不需要再去移动了
        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;
        }

       ...
	   
        try {
            final ActivityStack stack = moveTaskToStackUncheckedLocked(
                    task, stackId, toTop, forceFocus, reason + " moveTaskToStack");
            ...
			//这里stackId = DOCKED_STACK_ID
            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();
        }

       ...

        handleNonResizableTaskIfNeeded(task, preferredLaunchStackId, stackId);

        return (preferredLaunchStackId == stackId);
    }

先根据taskid找到taskrecord 

frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java
  ActivityStack moveTaskToStackUncheckedLocked(
            TaskRecord task, int stackId, boolean toTop, boolean forceFocus, String reason) {


        ...
		//创建DOCKED_STACK_ID的ActivityStack
        final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, toTop);
        task.mTemporarilyUnresizable = false;
		//wm端也要做相应的移动
        mWindowManager.moveTaskToStack(task.taskId, stack.mStackId, toTop);
		//将要移动的taskrecord放到DOCKED_STACK_ID的ActivityStack
        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;
    }

这里返回一个DOCKED_STACK_ID的ActivityStack。

我们知道一个Activity在Ams会有一个ActivityRecord的对象,对应在WMS会有一个WindowState来保存该activity的显示窗口显示状态,通过一个IApplicationToken对象进行关联。
在ams中一组有关联的ActivityRecord组成了TaskRecord,对应的在WMS中有Task,他们通过taskid来一一对应。如下图

Android 7.0 SystemUI(2)--Multi-Window多窗口模式_第2张图片

所以分屏时wms端也要做相应的移动。

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java.java
 public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
        synchronized (mWindowMap) {
            ...
            Task task = mTaskIdToTask.get(taskId);
            ...
            TaskStack stack = mStackIdToStack.get(stackId);
            ...
            task.moveTaskToStack(stack, toTop);
            final DisplayContent displayContent = stack.getDisplayContent();
            displayContent.layoutNeeded = true;
            mWindowPlacerLocked.performSurfacePlacement();
        }
    }

移动完task后,最后performSurfacePlacement开始去显示出来。



 
  

你可能感兴趣的:(systemui)