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;
    }
相关推荐
戏谑29 分钟前
Android 常用布局
android·view
拭心12 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
带电的小王14 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡15 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道15 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库16 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道16 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
MuYe17 小时前
Android Hook - 动态加载so库
android
居居飒17 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
Henry_He20 小时前
桌面列表小部件不能点击的问题分析
android