Android14 WMS-窗口添加流程(二)-Server端

Android14 WMS-窗口添加流程(一)-Client端-CSDN博客

本文接着上文"Android14 WMS-窗口添加流程(一)-Client端"往下讲。也就是WindowManagerService#addWindow流程。

目录

一. WindowManagerService#addWindow

标志1:mPolicy.checkAddPermission

标志2:getDisplayContentOrCreate

[标志3: mWindowMap](#标志3: mWindowMap)

二:窗口类型检查

三:新建WindowToken

[标志1 WindowToken](#标志1 WindowToken)

[四: 新建WindowState](#四: 新建WindowState)

五:adjustWindowParamsLw

六:窗口ADD_OKAY后续流程


整个流程如下

一. WindowManagerService#addWindow

http://aospxref.com/android-14.0.0_r2/xref/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java#1431

java 复制代码
    public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
            int displayId, int requestUserId, @InsetsType int requestedVisibleTypes,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
            float[] outSizeCompatScale) {
        outActiveControls.set(null);
        int[] appOp = new int[1];
//权限检查
        final boolean isRoundedCornerOverlay = (attrs.privateFlags
                & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
//标志1
        int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
                appOp);
        if (res != ADD_OKAY) {
            return res;
        }
//父window
        WindowState parentWindow = null;
//发起者Uid
        final int callingUid = Binder.getCallingUid();
//发起者Pid
        final int callingPid = Binder.getCallingPid();
        final long origId = Binder.clearCallingIdentity();
//窗口类型
        final int type = attrs.type;

        synchronized (mGlobalLock) {
            if (!mDisplayReady) {
                throw new IllegalStateException("Display has not been initialialized");
            }
//一个DisplayContent对应一个绘制屏幕
//标志2
            final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);

            if (displayContent == null) {
                ProtoLog.w(WM_ERROR, "Attempted to add window to a display that does "
                        + "not exist: %d. Aborting.", displayId);
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
            if (!displayContent.hasAccess(session.mUid)) {
                ProtoLog.w(WM_ERROR,
                        "Attempted to add window to a display for which the application "
                                + "does not have access: %d.  Aborting.",
                        displayContent.getDisplayId());
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
//是否已经添加对应的窗口, 去重判断
//标志3
            if (mWindowMap.containsKey(client.asBinder())) {
                ProtoLog.w(WM_ERROR, "Window %s is already added", client);
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }

由于此方法太长,所以我会分开去讲

标志1:mPolicy.checkAddPermission

mPolicy定义如下,如果此方法返回ADD_OKAY,则代码无权限问题,否则直接返回。

复制代码
    WindowManagerPolicy mPolicy;
------------------------------------------
        int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName, appOp);
        if (res != ADD_OKAY) {
            return res;
        }

WindowManagerPolicy.java - OpenGrok cross reference for /frameworks/base/services/core/java/com/android/server/policy/WindowManagerPolicy.java

复制代码
    /**当add window的时候检查权限
     * Check permissions when adding a window.
     *
     * @param type The window type--窗口类型
     * @param isRoundedCornerOverlay {@code true} to indicate the adding window is
     *           round corner overlay. 指示要add的窗口为圆角叠加层。
     * @param packageName package name  包名
     * @param outAppOp First element will be filled with the app op corresponding to this window, or OP_NONE.第一个元素将填充与此窗口对应的应用操作,或OP_NONE。
     *
     * @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed;
     *      else an error code, usually
     *      {@link WindowManagerGlobal#ADD_PERMISSION_DENIED}, to abort the add.
     *//返回是否有权限添加,有则返回ADD_OKAY
     * @see WindowManager.LayoutParams#PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY
     */
    int checkAddPermission(int type, boolean isRoundedCornerOverlay, String packageName, int[] outAppOp);

这里只是声明,没有实现,具体实现如下

PhoneWindowManager.java - OpenGrok cross reference for /frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java

java 复制代码
    public int checkAddPermission(int type, boolean isRoundedCornerOverlay, String packageName,
            int[] outAppOp) {
//如果是圆角覆盖并且无PERMISSION_GRANTED权限,也无法添加window
        if (isRoundedCornerOverlay && mContext.checkCallingOrSelfPermission(INTERNAL_SYSTEM_WINDOW)
                != PERMISSION_GRANTED) {
            return ADD_PERMISSION_DENIED;
        }

        outAppOp[0] = AppOpsManager.OP_NONE;
//如果窗口类型不在系统划分的三大窗口范围内,则返回不合法的type--ADD_INVALID_TYPE
        if (!((type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW)
                || (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW)
                || (type >= FIRST_SYSTEM_WINDOW && type <= LAST_SYSTEM_WINDOW))) {
            return WindowManagerGlobal.ADD_INVALID_TYPE;
        }
//如果窗口类型不是系统窗口类型,比如是APP窗口类型或者是子窗口类型,系统最大窗口类型为LAST_SYSTEM_WINDOW,则可以添加,返回ADD_OKAY
        if (type < FIRST_SYSTEM_WINDOW || type > LAST_SYSTEM_WINDOW) {
            // Window manager will make sure these are okay.
            return ADD_OKAY;
        }
//如果窗口类型不是alert window, alert window有好几种,可以查看这个方法
        if (!isSystemAlertWindowType(type)) {
            switch (type) {
                case TYPE_TOAST:
                    // Only apps that target older than O SDK can add window without a token, after
                    // that we require a token so apps cannot add toasts directly as the token is
                    // added by the notification system.
                    // Window manager does the checking for this.
                    outAppOp[0] = OP_TOAST_WINDOW;
                    return ADD_OKAY;
                case TYPE_INPUT_METHOD:
                case TYPE_WALLPAPER:
                case TYPE_PRESENTATION:
                case TYPE_PRIVATE_PRESENTATION:
                case TYPE_VOICE_INTERACTION:
                case TYPE_ACCESSIBILITY_OVERLAY:
                case TYPE_QS_DIALOG:
                case TYPE_NAVIGATION_BAR_PANEL:
                    // The window manager will check these.
                    return ADD_OKAY;
            }

            return (mContext.checkCallingOrSelfPermission(INTERNAL_SYSTEM_WINDOW)
                    == PERMISSION_GRANTED) ? ADD_OKAY : ADD_PERMISSION_DENIED;
        }

        // Things get a little more interesting for alert windows...
        outAppOp[0] = OP_SYSTEM_ALERT_WINDOW;

        final int callingUid = Binder.getCallingUid();
        // system processes will be automatically granted privilege to draw
//如果是系统进程,则直接允许
        if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
            return ADD_OKAY;
        }

...
    }

标志2:getDisplayContentOrCreate

复制代码
            final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);

此方法定义如下,其作用是**"Get existing {@link DisplayContent} or create a new one if the display is registered in DisplayManager."**,即"获取现有的DisplayContent,如果display已在 DisplayManager 中注册,则创建一个新DisplayContent".

仅当尚未创建与刚刚添加到 DisplayManager 的display相对应的DisplayContent时,才应使用此选项。这通常意味着此方法的调用是从 Activity 或窗口管理器外部启动的。

java 复制代码
    /**
     * Get existing {@link DisplayContent} or create a new one if the display is registered in
     * DisplayManager.
     *
     * NOTE: This should only be used in cases when there is a chance that a {@link DisplayContent}
     * that corresponds to a display just added to DisplayManager has not yet been created. This
     * usually means that the call of this method was initiated from outside of Activity or Window
     * Manager. In most cases the regular getter should be used.
     * @param displayId The preferred display Id.
     * @param token The window token associated with the window we are trying to get display for.
     *              if not null then the display of the window token will be returned. Set to null
     *              is there isn't an a token associated with the request.
     * @see RootWindowContainer#getDisplayContent(int)
     */
    private DisplayContent getDisplayContentOrCreate(int displayId, IBinder token) {
        if (token != null) {
//WM中相关联的窗口集合
//tag1
            final WindowToken wToken = mRoot.getWindowToken(token);
            if (wToken != null) {
//tag2
                return wToken.getDisplayContent();
            }
        }
//tag3
        return mRoot.getDisplayContentOrCreate(displayId);
    }

这块涉及到Container,我们来看看继承关系

复制代码
设备的Root WindowContainer,一个设备只有一个RootWindowContainer
class RootWindowContainer extends WindowContainer<DisplayContent>
        implements DisplayManager.DisplayListener {

一个设备只有一个RootWindowContainer, 因为它的赋值是在WMS的实例化中,WMS实例在全局只有一个,是从SystemServer中发起的。

复制代码
    private WindowManagerService(Context context, InputManagerService inputManager, boolean showBootMsgs, WindowManagerPolicy policy, ActivityTaskManagerService atm,DisplayWindowSettingsProvider displayWindowSettingsProvider, Supplier<SurfaceControl.Transaction> transactionFactory, Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {
...
        http://aospxref.com/android-14.0.0_r2/xref/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java#mRoot = new http://aospxref.com/android-14.0.0_r2/s?defs=RootWindowContainer&project=frameworks(this);

复制代码
一系列相关window的集合
通常,这是一个AppWindowToken(AppWindowToken就是activity),它是用于显示窗口的 Activity的句柄。
这个主要用于表示窗口的令牌(Token)信息,主要负责管理窗口的一些属性和行为,通过WindowToken,WMS可以对窗口进行布局,层级排序,焦点管理,输入事件分发等操作。
class WindowToken extends WindowContainer<WindowState> {
复制代码
此外ActivityRecord继承WindowToken,一个ActivityRecord代表一个activity
final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {

复制代码
WindowContainer作为窗口层级结构中的基本单元,负责承载一个窗口的所有信息和状态。每个窗口容器都对应一个窗口,并包含了窗口的配置信息、绘制信息、动画状态等。
WindowContainer还提供了与窗口相关的操作接口,如设置窗口属性、绘制窗口内容等。
在源码层面,WindowContainer的实现通常涉及到多个类和接口。
例如,WindowContainer类可能包含了一些与窗口容器相关的属性和方法,如窗口的位置、大小、背景色等。此外,WindowContainer还可能与其他组件进行交互,如与SurfaceFlinger进行渲染交互,与InputManager进行输入事件处理等。
class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
        implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable, InsetsControlTarget {
...
一系列子级window container集合。此集合按 z 顺序排列,因为子项显示在屏幕上,最上面的window container位于列表的尾部。
mChildren.add和mChildren.remove操作都在WindowContainer.java中
    protected final WindowList<E> mChildren = new WindowList<E>();
复制代码
WindowList介绍如下: 一个 ArrayList,存储WindowContainer中的子window container
class WindowList<E> extends ArrayList<E> {
添加到集合首位
    void addFirst(E e) {
        add(0, e);
    }
选择最后一位
    E peekLast() {
        return size() > 0 ? get(size() - 1) : null;
    }
选择第一位
    E peekFirst() {
        return size() > 0 ? get(0) : null;
    }
}

复制代码
包含具有重写配置并按层次结构组织的类的通用逻辑。
public abstract class ConfigurationContainer<E extends ConfigurationContainer> {

回归正题,来看看tag1处mRoot.getWindowToken(token); 刚刚已经讲过mChildren是一系列子级window container集合,那我们通过binder对此集合遍历,找到binder对应的windowtoken,然后返回。

复制代码
    /** Returns the window token for the input binder if it exist in the system.*/
    WindowToken getWindowToken(IBinder binder) {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final DisplayContent dc = mChildren.get(i);
            final WindowToken wtoken = dc.getWindowToken(binder);
            if (wtoken != null) {
                return wtoken;
            }
        }
        return null;
    }
复制代码
用于跟踪特定 Display 的 WindowStates 和其他相关内容的 辅助类。
DisplayContent 用于管理屏幕,一块屏幕对应一个 DisplayContent 对象,虽然手机只有一个显示屏,但是可以创建多个 DisplayContent 对象
class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.DisplayContentInfo {

标志3: mWindowMap

mWindowMap保存了每个WindowState和客户端窗口的映射关系,客户端应用请求窗口操作时,通过mWindowMap查询到对应的WindowState

复制代码
    /** Mapping from an IWindow IBinder to the server's Window object. */
    final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();

二:窗口类型检查

java 复制代码
如果是子窗口类型
            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
先找到它的父窗口,子窗口需要依附在父窗口上,如果父窗口为null,则返回ADD_BAD_SUBWINDOW_TOKEN
                parentWindow = windowForClientLocked(null, attrs.token, false);
                if (parentWindow == null) {
                    ProtoLog.w(WM_ERROR, "Attempted to add window with token that is not a window: "
                            + "%s.  Aborting.", attrs.token);
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
如果父窗口也是一个子窗口,也直接返回ADD_BAD_SUBWINDOW_TOKEN
                if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    ProtoLog.w(WM_ERROR, "Attempted to add window with token that is a sub-window: "
                            + "%s.  Aborting.", attrs.token);
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
            }
如果窗口是保密类型,但displayContent不是保密类型,则返回ADD_PERMISSION_DENIED
            if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
                ProtoLog.w(WM_ERROR,
                        "Attempted to add private presentation window to a non-private display.  "
                                + "Aborting.");
                return WindowManagerGlobal.ADD_PERMISSION_DENIED;
            }
如果窗口是保密类型,但displayContent的屏幕是公开演示显示器,返回ADD_INVALID_DISPLAY
            if (type == TYPE_PRESENTATION && !displayContent.getDisplay().isPublicPresentation()) {
                ProtoLog.w(WM_ERROR,
                        "Attempted to add presentation window to a non-suitable display.  "
                                + "Aborting.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }

三:新建WindowToken

java 复制代码
            ActivityRecord activity = null;
            final boolean hasParent = parentWindow != null;
            // Use existing parent window token for child windows since they go in the same token
            // as there parent window so we can apply the same policy on them.
子窗口使用现有的父窗口令牌,因为它们与父窗口使用相同的令牌,因此我们可以对它们应用相同的策略。
根据客户端传来的token获取windowToken
            WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);
            // If this is a child window, we want to apply the same type checking rules as the
            // parent window type.
如果这是一个子窗口,我们希望应用与父窗口类型相同的类型检查规则。
            final int rootType = hasParent ? parentWindow.mAttrs.type : type;

            boolean addToastWindowRequiresToken = false;

            final IBinder windowContextToken = attrs.mWindowContextToken;

            if (token == null) {如果token为空
                if (!unprivilegedAppCanCreateTokenWith(parentWindow, callingUid, type,
                        rootType, attrs.token, attrs.packageName)) {
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (hasParent) {如果有父级window
                    // Use existing parent window token for child windows.
                    token = parentWindow.mToken;则直接用父级token给所有的子windows
                } else if (mWindowContextListenerController.hasListener(windowContextToken)) {
                    // Respect the window context token if the user provided it.
如果用户提供了窗口上下文令牌,则用windowContextToken
                    final IBinder binder = attrs.token != null ? attrs.token : windowContextToken;
                    final Bundle options = mWindowContextListenerController
                            .getOptions(windowContextToken);
                    token = new WindowToken.Builder(this, binder, type)
                            .setDisplayContent(displayContent)
                            .setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
                            .setRoundedCornerOverlay(isRoundedCornerOverlay)
                            .setFromClientToken(true)
                            .setOptions(options)
                            .build();
                } else {
                    final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
                    token = new WindowToken.Builder(this, binder, type)
                            .setDisplayContent(displayContent)
                            .setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
                            .setRoundedCornerOverlay(isRoundedCornerOverlay)
                            .build();
                }
如果是APP类型窗口
            } else if (rootType >= FIRST_APPLICATION_WINDOW
                    && rootType <= LAST_APPLICATION_WINDOW) {
通过token获取ActivityRecord
                activity = token.asActivityRecord();
                if (activity == null) {
                    ProtoLog.w(WM_ERROR, "Attempted to add window with non-application token "
                            + ".%s Aborting.", token);
                    return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
                } else if (activity.getParent() == null) {
                    ProtoLog.w(WM_ERROR, "Attempted to add window with exiting application token "
                            + ".%s Aborting.", token);
                    return WindowManagerGlobal.ADD_APP_EXITING;
窗口类型为starting window
                } else if (type == TYPE_APPLICATION_STARTING) {
                    if (activity.mStartingWindow != null) {
                        ProtoLog.w(WM_ERROR, "Attempted to add starting window to "
                                + "token with already existing starting window");
                        return WindowManagerGlobal.ADD_DUPLICATE_ADD;
                    }
                    if (activity.mStartingData == null) {
                        ProtoLog.w(WM_ERROR, "Attempted to add starting window to "
                                + "token but already cleaned");
                        return WindowManagerGlobal.ADD_DUPLICATE_ADD;
                    }
                }
窗口类型为input window
            } else if (rootType == TYPE_INPUT_METHOD) {
                if (token.windowType != TYPE_INPUT_METHOD) {
                    ProtoLog.w(WM_ERROR, "Attempted to add input method window with bad token "
                            + "%s.  Aborting.", attrs.token);
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
窗口类型为voice window
            } else if (rootType == TYPE_VOICE_INTERACTION) {
                if (token.windowType != TYPE_VOICE_INTERACTION) {
                    ProtoLog.w(WM_ERROR, "Attempted to add voice interaction window with bad token "
                            + "%s.  Aborting.", attrs.token);
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
窗口类型为壁纸
            } else if (rootType == TYPE_WALLPAPER) {
                if (token.windowType != TYPE_WALLPAPER) {
                    ProtoLog.w(WM_ERROR, "Attempted to add wallpaper window with bad token "
                            + "%s.  Aborting.", attrs.token);
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
窗口类型为辅助功能 OVERLAY
            } else if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
                if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) {
                    ProtoLog.w(WM_ERROR,
                            "Attempted to add Accessibility overlay window with bad token "
                                    + "%s.  Aborting.", attrs.token);
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
窗口类型为Toast类型
            } else if (type == TYPE_TOAST) {
                // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
                addToastWindowRequiresToken = doesAddToastWindowRequireToken(attrs.packageName,
                        callingUid, parentWindow);
                if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) {
                    ProtoLog.w(WM_ERROR, "Attempted to add a toast window with bad token "
                            + "%s.  Aborting.", attrs.token);
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_QS_DIALOG) {
                if (token.windowType != TYPE_QS_DIALOG) {
                    ProtoLog.w(WM_ERROR, "Attempted to add QS dialog window with bad token "
                            + "%s.  Aborting.", attrs.token);
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
通过token获取到的ActivityRecord不为空
            } else if (token.asActivityRecord() != null) {
                ProtoLog.w(WM_ERROR, "Non-null activity for system window of rootType=%d",
                        rootType);
                // It is not valid to use an app token with other system types; we will
                // instead make a new token for it (as if null had been passed in for the token).
将应用令牌用于其他系统类型是无效的;
相反,我们将为它创建一个新的令牌(就好像已经为令牌传入了 null 一样)。
                attrs.token = null;
创建WindowToken
标志1
                token = new WindowToken.Builder(this, client.asBinder(), type)
                        .setDisplayContent(displayContent)
                        .setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
                        .build();
            }

标志1 WindowToken

通常,这是一个AppWindowToken(AppWindowToken就是activity),它是用于显示窗口的 Activity的句柄。
这个主要用于表示窗口的令牌(Token)信息,主要负责管理窗口的一些属性和行为,通过WindowToken,WMS可以对窗口进行布局,层级排序,焦点管理,输入事件分发等操作。

token = new WindowToken.Builder(this, client.asBinder(), type)
.setDisplayContent(displayContent)
.setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
.build(); //build()就能创建出来WindowToken对象

复制代码
class WindowToken extends WindowContainer<WindowState> {
    protected WindowToken(WindowManagerService service, IBinder _token, int type,
boolean persistOnEmpty, DisplayContent dc, boolean ownerCanManageAppTokens) {
        this(service, _token, type, persistOnEmpty, dc, ownerCanManageAppTokens,
false /* roundedCornerOverlay */, false /* fromClientToken */, null /* options */);
    }

    protected WindowToken(WindowManagerService service, IBinder _token, int type,
boolean persistOnEmpty, DisplayContent dc, boolean ownerCanManageAppTokens,
boolean roundedCornerOverlay, boolean fromClientToken, @Nullable Bundle options) {
        super(service);
        token = _token;
        windowType = type;
        mOptions = options;
        mPersistOnEmpty = persistOnEmpty;
        mOwnerCanManageAppTokens = ownerCanManageAppTokens;
        mRoundedCornerOverlay = roundedCornerOverlay;
        mFromClientToken = fromClientToken;
        if (dc != null) {
            dc.addWindowToken(token, this);
        }
    }---------------------------------------------
    static class Builder {
        private final WindowManagerService mService;
        private final IBinder mToken;
        @WindowType
        private final int mType;

        private boolean mPersistOnEmpty;
        private DisplayContent mDisplayContent;
        private boolean mOwnerCanManageAppTokens;
        @Nullable
        private Bundle mOptions;

        Builder(WindowManagerService service, IBinder token, int type) {
            mService = service;
            mToken = token;
            mType = type;
        }

        /** Sets the {@link DisplayContent} to be associated. */
        Builder setDisplayContent(DisplayContent dc) {
            mDisplayContent = dc;
            return this;
        }

        /** @see WindowToken#mOwnerCanManageAppTokens */
        Builder setOwnerCanManageAppTokens(boolean ownerCanManageAppTokens) {
            mOwnerCanManageAppTokens = ownerCanManageAppTokens;
            return this;
        }

        WindowToken build() {
            return new WindowToken(mService, mToken, mType, mPersistOnEmpty, mDisplayContent, mOwnerCanManageAppTokens, mRoundedCornerOverlay, mFromClientToken, mOptions);
        }
    }

四: 新建WindowState

java 复制代码
创建WindowState
            final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], attrs, viewVisibility, session.mUid, userId,
                    session.mCanAddInternalSystemWindow);
            if (win.mDeathRecipient == null) {
                // Client has apparently died, so there is no reason to
                // continue.
                ProtoLog.w(WM_ERROR, "Adding window client %s"
                        + " that is dead, aborting.", client.asBinder());
                return WindowManagerGlobal.ADD_APP_EXITING;
            }
WindowState对应的DisplayContent为空则没有对应的屏幕去显示window,所以报错
            if (win.getDisplayContent() == null) {
                ProtoLog.w(WM_ERROR, "Adding window to Display that has been removed.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }

WindowState表示一个窗口的所有属性,一个WindowState对应一个窗口,借用一张图来表示,也就解释了为什么WindowToken是一系列Window的集合的容器了。

WindowToken--"Container of a set of related windows in the window manager"

复制代码
/** A window in the window manager. */
class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState,
        InsetsControlTarget, InputTarget {

五:adjustWindowParamsLw

java 复制代码
获取DisplayPolicy
            final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
调整窗口参数
            displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
            attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
            attrs.inputFeatures = sanitizeSpyWindow(attrs.inputFeatures, win.getName(), callingUid,
                    callingPid);
            win.setRequestedVisibleTypes(requestedVisibleTypes);
检查窗口是否可以添加至系统,主要是检查权限相关
            res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
            if (res != ADD_OKAY) {
                return res;
            }
复制代码
/**提供要显示的UI的基本行为和状态的策略。
 * The policy that provides the basic behaviors and states of a display to show UI.
 */
public class DisplayPolicy {

在DisplayPolicy实例化中有关于手势相关的,比如下拉状态栏,左滑返回这种。

DisplayPolicy还可以调整布局相关

adjustWindowParamsLw作用:根据客户端的布局参数调整布局。 允许策略执行某些操作,例如确保特定类型的窗口不能采用输入焦点。

java 复制代码
    public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams attrs) {
        switch (attrs.type) {
            case TYPE_SYSTEM_OVERLAY:
            case TYPE_SECURE_SYSTEM_OVERLAY:
如果窗口类型是这个,则给窗口flag添加如下参数
                // These types of windows can't receive input events.
                attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
                attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
                break;
            case TYPE_WALLPAPER:
如果是壁纸类型,则设置让窗口总是可以扩展到刘海区域中
                // Dreams and wallpapers don't have an app window token and can thus not be
                // letterboxed. Hence always let them extend under the cutout.
                attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
                break;
            case TYPE_TOAST:
...
                attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
                break;

            case TYPE_BASE_APPLICATION:
...
                break;
        }
...
    }

layoutInDisplayCutoutMode的默认值是LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT。

|-------------------------------------------|-------|--------------------------------------------|
| layoutInDisplayCutoutMode | Value | 含义 |
| LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT | 0 | 这是默认行为,在竖屏模式下,内容会呈现到刘海区域中;但在横屏模式下,内容会显示黑边。 |
| LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES | 1 | 在竖屏模式和横屏模式下,内容都会呈现到刘海区域中 |
| LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER | 2 | 内容从不呈现到刘海区域中 |
| LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS | 3 | 窗口总是可以扩展到刘海区域中 |

六:窗口ADD_OKAY后续流程

java 复制代码
            final boolean openInputChannels = (outInputChannel != null
                    && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
            if  (openInputChannels) {
打开input channel
                win.openInputChannel(outInputChannel);
            }
...
            // From now on, no exceptions or errors allowed!
能走到这里那就说明这个窗口可以添加,没有问题
            res = ADD_OKAY;
请求创建一个BLAST (Buffer as LayerState)层的标志。
如果没有指定,客户端将接收一个BufferQueue层。
            if (mUseBLAST) {
                res |= WindowManagerGlobal.ADD_FLAG_USE_BLAST;
            }
...
//    void attach() {
//        if (DEBUG) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
//        mSession.windowAddedLocked();
//    }
将这个windowstate即这个窗口更新到Session中的 mNumWindow中
            win.attach();
mWindowMap更新,前面有讲过mWindowMap作用
保存 IWindow IBinder和windowstate的匹配
//"Mapping from an IWindow IBinder to the server's Window object."
            mWindowMap.put(client.asBinder(), win);
            win.initAppOpsState();
当前window对应的token添加windowstate
            win.mToken.addWindow(win);
策略更新window及其对应的窗口属性
            displayPolicy.addWindowLw(win, attrs);
            displayPolicy.setDropInputModePolicy(win, win.mAttrs);
            if (type == TYPE_APPLICATION_STARTING && activity != null) {
activityrecord添加starting window窗口
                activity.attachStartingWindow(win);
                ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s",
                        activity, win);
            } else if (type == TYPE_INPUT_METHOD
                    // IME window is always touchable.
                    // Ignore non-touchable windows e.g. Stylus InkWindow.java.
                    && (win.getAttrs().flags & FLAG_NOT_TOUCHABLE) == 0) {
如果是输入法,且可触摸,则给这个display设置输入法
                displayContent.setInputMethodWindowLocked(win);
                imMayMove = false;
            } else if (type == TYPE_INPUT_METHOD_DIALOG) {
如果是输入法dialog,则计算输入法目标
                displayContent.computeImeTarget(true /* updateImeTarget */);
                imMayMove = false;
...
窗口可以接受输入事件
            if (win.canReceiveKeys()) {
更新焦点窗口
                focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
                        false /*updateInputWindows*/);
                if (focusChanged) {
                    imMayMove = false;
                }
            }
...
            // Don't do layout here, the window must call
            // relayout to be displayed, so we'll do it there.
            win.getParent().assignChildLayers();
窗口焦点更新了,所以当前输入焦点窗口也要重新设置
            if (focusChanged) {
                displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
                        false /*updateInputWindows*/);
            }
更新输入窗口
            displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
...
            // This window doesn't have a frame yet. Don't let this window cause the insets change.
            displayContent.getInsetsStateController().updateAboveInsetsState(
                    false /* notifyInsetsChanged */);

            outInsetsState.set(win.getCompatInsetsState(), true /* copySources */);
            getInsetsSourceControls(win, outActiveControls);
...
        return res;
    }
相关推荐
幻雨様4 小时前
UE5多人MOBA+GAS 45、制作冲刺技能
android·ue5
Jerry说前后端6 小时前
Android 数据可视化开发:从技术选型到性能优化
android·信息可视化·性能优化
Meteors.6 小时前
Android约束布局(ConstraintLayout)常用属性
android
alexhilton7 小时前
玩转Shader之学会如何变形画布
android·kotlin·android jetpack
whysqwhw11 小时前
安卓图片性能优化技巧
android
风往哪边走11 小时前
自定义底部筛选弹框
android
Yyyy48212 小时前
MyCAT基础概念
android
Android轮子哥12 小时前
尝试解决 Android 适配最后一公里
android
雨白13 小时前
OkHttp 源码解析:enqueue 非同步流程与 Dispatcher 调度
android
风往哪边走14 小时前
自定义仿日历组件弹框
android