WindowManagerService的addWindow方法源码解读

2024-03-01 00:28

本文主要是介绍WindowManagerService的addWindow方法源码解读,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

源码链接
https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java;l=9516?q=sanitizeWindowType&hl=zh-cn
addWindow 方法如下:

    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;int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,appOp);if (res != ADD_OKAY) {return res;}WindowState parentWindow = null;final int callingUid = Binder.getCallingUid();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");}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;}if (mWindowMap.containsKey(client.asBinder())) {ProtoLog.w(WM_ERROR, "Window %s is already added", client);return WindowManagerGlobal.ADD_DUPLICATE_ADD;}if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {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;}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;}}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;}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;}int userId = UserHandle.getUserId(session.mUid);if (requestUserId != userId) {try {mAmInternal.handleIncomingUser(callingPid, callingUid, requestUserId,false /*allowAll*/, ALLOW_NON_FULL, null, null);} catch (Exception exp) {ProtoLog.w(WM_ERROR, "Trying to add window with invalid user=%d",requestUserId);return WindowManagerGlobal.ADD_INVALID_USER;}// It's fine to use this userIduserId = requestUserId;}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.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) {if (!unprivilegedAppCanCreateTokenWith(parentWindow, callingUid, type,rootType, attrs.token, attrs.packageName)) {return WindowManagerGlobal.ADD_BAD_APP_TOKEN;}if (hasParent) {// Use existing parent window token for child windows.token = parentWindow.mToken;} else if (mWindowContextListenerController.hasListener(windowContextToken)) {// Respect the window context token if the user provided it.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();}} else if (rootType >= FIRST_APPLICATION_WINDOW&& rootType <= LAST_APPLICATION_WINDOW) {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;} 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;}}} 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;}} 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;}} 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;}} 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;}} 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).attrs.token = null;token = new WindowToken.Builder(this, client.asBinder(), type).setDisplayContent(displayContent).setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow).build();}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;}if (win.getDisplayContent() == null) {ProtoLog.w(WM_ERROR, "Adding window to Display that has been removed.");return WindowManagerGlobal.ADD_INVALID_DISPLAY;}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;}final boolean openInputChannels = (outInputChannel != null&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);if  (openInputChannels) {win.openInputChannel(outInputChannel);}// If adding a toast requires a token for this app we always schedule hiding// toast windows to make sure they don't stick around longer then necessary.// We hide instead of remove such windows as apps aren't prepared to handle// windows being removed under them.//// If the app is older it can add toasts without a token and hence overlay// other apps. To be maximally compatible with these apps we will hide the// window after the toast timeout only if the focused window is from another// UID, otherwise we allow unlimited duration. When a UID looses focus we// schedule hiding all of its toast windows.if (type == TYPE_TOAST) {if (!displayContent.canAddToastWindowForUid(callingUid)) {ProtoLog.w(WM_ERROR, "Adding more than one toast window for UID at a time.");return WindowManagerGlobal.ADD_DUPLICATE_ADD;}// Make sure this happens before we moved focus as one can make the// toast focusable to force it not being hidden after the timeout.// Focusable toasts are always timed out to prevent a focused app to// show a focusable toasts while it has focus which will be kept on// the screen after the activity goes away.if (addToastWindowRequiresToken|| (attrs.flags & FLAG_NOT_FOCUSABLE) == 0|| displayContent.mCurrentFocus == null|| displayContent.mCurrentFocus.mOwnerUid != callingUid) {mH.sendMessageDelayed(mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, win),win.mAttrs.hideTimeoutMilliseconds);}}// Switch to listen to the {@link WindowToken token}'s configuration changes when// adding a window to the window context. Filter sub window type here because the sub// window must be attached to the parent window, which is attached to the window context// created window token.if (!win.isChildWindow()&& mWindowContextListenerController.hasListener(windowContextToken)) {final int windowContextType = mWindowContextListenerController.getWindowType(windowContextToken);final Bundle options = mWindowContextListenerController.getOptions(windowContextToken);if (type != windowContextType) {ProtoLog.w(WM_ERROR, "Window types in WindowContext and"+ " LayoutParams.type should match! Type from LayoutParams is %d,"+ " but type from WindowContext is %d", type, windowContextType);// We allow WindowProviderService to add window other than windowContextType,// but the WindowProviderService won't be associated with the window's// WindowToken.if (!isWindowProviderService(options)) {return WindowManagerGlobal.ADD_INVALID_TYPE;}} else {mWindowContextListenerController.registerWindowContainerListener(windowContextToken, token, callingUid, type, options);}}// From now on, no exceptions or errors allowed!res = ADD_OKAY;if (mUseBLAST) {res |= WindowManagerGlobal.ADD_FLAG_USE_BLAST;}if (displayContent.mCurrentFocus == null) {displayContent.mWinAddedSinceNullFocus.add(win);}if (excludeWindowTypeFromTapOutTask(type)) {displayContent.mTapExcludedWindows.add(win);}win.attach();mWindowMap.put(client.asBinder(), win);win.initAppOpsState();final boolean suspended = mPmInternal.isPackageSuspended(win.getOwningPackage(),UserHandle.getUserId(win.getOwningUid()));win.setHiddenWhileSuspended(suspended);final boolean hideSystemAlertWindows = !mHidingNonSystemOverlayWindows.isEmpty();win.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);boolean imMayMove = true;win.mToken.addWindow(win);displayPolicy.addWindowLw(win, attrs);displayPolicy.setDropInputModePolicy(win, win.mAttrs);if (type == TYPE_APPLICATION_STARTING && activity != null) {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) {displayContent.setInputMethodWindowLocked(win);imMayMove = false;} else if (type == TYPE_INPUT_METHOD_DIALOG) {displayContent.computeImeTarget(true /* updateImeTarget */);imMayMove = false;} else {if (type == TYPE_WALLPAPER) {displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;} else if (win.hasWallpaper()) {displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;} else if (displayContent.mWallpaperController.isBelowWallpaperTarget(win)) {// If there is currently a wallpaper being shown, and// the base layer of the new window is below the current// layer of the target window, then adjust the wallpaper.// This is to avoid a new window being placed between the// wallpaper and its target.displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;}}final WindowStateAnimator winAnimator = win.mWinAnimator;winAnimator.mEnterAnimationPending = true;winAnimator.mEnteringAnimation = true;if (displayPolicy.areSystemBarsForcedConsumedLw()) {res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;}if (displayContent.isInTouchMode()) {res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE;}if (win.mActivityRecord == null || win.mActivityRecord.isClientVisible()) {res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;}displayContent.getInputMonitor().setUpdateInputWindowsNeededLw();boolean focusChanged = false;if (win.canReceiveKeys()) {focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,false /*updateInputWindows*/);if (focusChanged) {imMayMove = false;}}if (imMayMove) {displayContent.computeImeTarget(true /* updateImeTarget */);if (win.isImeOverlayLayeringTarget()) {dispatchImeTargetOverlayVisibilityChanged(client.asBinder(), win.mAttrs.type,win.isVisibleRequestedOrAdding(), false /* removed */);}}// 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*/);ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s"+ ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5));boolean needToSendNewConfiguration =win.isVisibleRequestedOrAdding() && displayContent.updateOrientation();if (win.providesDisplayDecorInsets()) {needToSendNewConfiguration |= displayPolicy.updateDecorInsetsInfo();}if (needToSendNewConfiguration) {displayContent.sendNewConfiguration();}// 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);if (win.mLayoutAttached) {outAttachedFrame.set(win.getParentWindow().getFrame());if (win.mInvGlobalScale != 1f) {outAttachedFrame.scale(win.mInvGlobalScale);}} else {// Make this invalid which indicates a null attached frame.outAttachedFrame.set(0, 0, -1, -1);}outSizeCompatScale[0] = win.getCompatScaleForClient();}Binder.restoreCallingIdentity(origId);return res;}

addWindow 是 WindowManagerService 类中的一个公开方法,它的作用是将一个窗口添加到窗口管理系统中,以便在显示器上显示和管理。这个方法的逻辑大致如下:

  • 参数:这个方法接收十五个参数,分别是:
    • session:一个 Session 类型的对象,表示调用者的会话。
    • client:一个 IWindow 类型的对象,表示要添加的窗口的接口。
    • seq:一个 int 类型的值,表示要添加的窗口的序列号。
    • attrs:一个 WindowManager.LayoutParams 类型的对象,表示要添加的窗口的布局参数。
    • viewVisibility:一个 int 类型的值,表示要添加的窗口的可见性。
    • displayId:一个 int 类型的值,表示要添加的窗口所在的显示器的 ID。
    • outContentInsets:一个 Rect 类型的对象,表示一个输出参数,用于返回要添加的窗口的内容区域的内边距。
    • outStableInsets:一个 Rect 类型的对象,表示一个输出参数,用于返回要添加的窗口的稳定区域的内边距。
    • outOutsets:一个 Rect 类型的对象,表示一个输出参数,用于返回要添加的窗口的外边距。
    • outInputChannel:一个 InputChannel 类型的对象,表示一个输出参数,用于返回要添加的窗口的输入通道。
  • 返回值:这个方法返回一个 int 类型的值,表示添加窗口的结果,可能的值有:
    • ADD_OKAY:表示添加窗口成功。
    • ADD_BAD_APP_TOKEN:表示添加窗口失败,因为应用窗口的令牌无效。
    • ADD_BAD_SUBWINDOW_TOKEN:表示添加窗口失败,因为子窗口的令牌无效。
    • ADD_DUPLICATE_ADD:表示添加窗口失败,因为窗口已经存在。
    • ADD_STARTING_NOT_NEEDED:表示添加窗口失败,因为启动窗口不需要。
    • ADD_MULTIPLE_SINGLETON:表示添加窗口失败,因为单例窗口重复。
    • ADD_PERMISSION_DENIED:表示添加窗口失败,因为权限不足。
    • ADD_INVALID_DISPLAY:表示添加窗口失败,因为显示器不存在。
    • ADD_INVALID_TYPE:表示添加窗口失败,因为窗口类型不合法。
  • 逻辑:这个方法的逻辑是:
    • 首先,检查调用者的权限,如果没有 SYSTEM_ALERT_WINDOW 权限,且窗口类型是系统警告窗口,那么返回 ADD_PERMISSION_DENIED。
    • 然后,检查调用者的应用操作,如果有应用操作,且应用操作被拒绝,那么返回 ADD_PERMISSION_DENIED。
    • 接着,检查窗口类型,如果窗口类型是 TYPE_APPLICATION_STARTING,且不需要显示启动窗口,那么返回 ADD_STARTING_NOT_NEEDED。
    • 然后,检查显示器,如果显示器不存在,那么返回 ADD_INVALID_DISPLAY。
    • 接着,检查窗口,如果窗口已经存在,那么返回 ADD_DUPLICATE_ADD。
    • 然后,检查窗口令牌,如果窗口令牌无效,那么返回 ADD_BAD_APP_TOKEN 或 ADD_BAD_SUBWINDOW_TOKEN。
    • 接着,检查窗口类型,如果窗口类型不合法,那么返回 ADD_INVALID_TYPE。
    • 然后,创建一个 WindowState 对象,表示要添加的窗口的状态,这个对象的构造方法接收十六个参数,分别是 this, session, client, windowToken, parentWindow, appOp[0], seq, attrs, viewVisibility, displayContent, mPolicy, mService, mAnimator, mRoot, mInputManager, mAccessibilityController。
    • 接着,调用 mPolicy 的 prepareAddWindowLw 方法,将 win 和 attrs 作为参数传入,这个方法用于在添加窗口之前,根据策略调整窗口的布局参数。
    • 然后,调用 mPolicy 的 addWindowLw 方法,将 win 作为参数传入,这个方法用于在添加窗口时,根据策略返回窗口的层级。
    • 接着,调用 win 的 attach 方法,将 windowToken, mDisplayManagerInternal, mInputManager 作为参数传入,这个方法用于将窗口附加到窗口令牌和显示管理器和输入管理器上。
    • 然后,调用 win 的 computeFrameLw 方法,这个方法用于计算窗口的框架区域和内容区域和稳定区域和外边距等。
    • 接着,将 win 的内容区域的内边距和稳定区域的内边距和外边距分别赋值给 outContentInsets, outStableInsets, outOutsets 参数,用于返回给调用者。
    • 然后,调用 win 的 openInputChannel 方法,将 outInputChannel 作为参数传入,这个方法用于打开窗口的输入通道,并返回给调用者。
    • 接着,调用 mInputManager 的 addInputWindowHandle 方法,将 win 的输入窗口句柄作为参数传入,这个方法用于将窗口的输入窗口句柄添加到输入管理器中。
    • 然后,调用 mPolicy 的 getContentInsetHintLw 方法,将 win 的布局参数和窗口类型和窗口的内容区域的宽度和高度作为参数传入,这个方法用于根据策略返回窗口的内容区域的内边距的提示。
    • 接着,调用 mRoot 的 addWindow 方法,将 win 作为参数传入,这个方法用于将窗口添加到窗口树中,并更新窗口的可见性和焦点和动画等。
    • 最后,返回 ADD_OKAY,表示添加窗口成功。

总结:addWindow 方法是用于将一个窗口添加到窗口管理系统中的公开方法,它接收十五个参数,返回一个 int 类型的值,表示添加窗口的结果,它的逻辑是先检查调用者的权限和应用操作和窗口类型和显示器和窗口令牌等,然后创建一个 WindowState 对象,表示要添加的窗口的状态,然后根据策略调整和返回窗口的布局参数和层级,然后将窗口附加到窗口令牌和显示管理器和输入管理器上,然后计算和返回窗口的区域和边距,然后打开和返回窗口的输入通道,然后将窗口的输入窗口句柄添加到输入管理器中,然后返回窗口的内容区域的内边距的提示,然后将窗口添加到窗口树中,并更新窗口的相关状态。

private boolean unprivilegedAppCanCreateTokenWith(WindowState parentWindow,int callingUid, int type, int rootType, IBinder tokenForLog, String packageName) {if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {ProtoLog.w(WM_ERROR, "Attempted to add application window with unknown token "+ "%s.  Aborting.", tokenForLog);return false;}if (rootType == TYPE_INPUT_METHOD) {ProtoLog.w(WM_ERROR, "Attempted to add input method window with unknown token "+ "%s.  Aborting.", tokenForLog);return false;}if (rootType == TYPE_VOICE_INTERACTION) {ProtoLog.w(WM_ERROR,"Attempted to add voice interaction window with unknown token "+ "%s.  Aborting.", tokenForLog);return false;}if (rootType == TYPE_WALLPAPER) {ProtoLog.w(WM_ERROR, "Attempted to add wallpaper window with unknown token "+ "%s.  Aborting.", tokenForLog);return false;}if (rootType == TYPE_QS_DIALOG) {ProtoLog.w(WM_ERROR, "Attempted to add QS dialog window with unknown token "+ "%s.  Aborting.", tokenForLog);return false;}if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {ProtoLog.w(WM_ERROR,"Attempted to add Accessibility overlay window with unknown token "+ "%s.  Aborting.", tokenForLog);return false;}if (type == TYPE_TOAST) {// Apps targeting SDK above N MR1 cannot arbitrary add toast windows.if (doesAddToastWindowRequireToken(packageName, callingUid, parentWindow)) {ProtoLog.w(WM_ERROR, "Attempted to add a toast window with unknown token "+ "%s.  Aborting.", tokenForLog);return false;}}return true;}

unprivilegedAppCanCreateTokenWith,它的作用是判断一个没有特权的应用是否可以使用指定的窗口类型和窗口令牌创建一个窗口。这个方法的逻辑大致如下:

  • 参数:这个方法接收六个参数,分别是:
    • parentWindow:一个 WindowState 类型的对象,表示要创建的窗口的父窗口。
    • callingUid:一个 int 类型的值,表示调用者的用户 ID。
    • type:一个 int 类型的值,表示要创建的窗口的类型。
    • rootType:一个 int 类型的值,表示要创建的窗口的根类型,即窗口令牌对应的窗口类型。
    • tokenForLog:一个 IBinder 类型的对象,表示要创建的窗口的令牌,用于打印日志。
    • packageName:一个 String 类型的对象,表示调用者的包名。
  • 返回值:这个方法返回一个 boolean 类型的值,表示是否可以创建窗口。
  • 逻辑:这个方法的逻辑是:
    • 首先,检查根类型,如果根类型是应用窗口的范围,那么打印一条错误日志,表示尝试使用未知的令牌添加应用窗口,并返回 false。
    • 然后,检查根类型,如果根类型是以下几种特殊的窗口类型,那么打印一条错误日志,表示尝试使用未知的令牌添加这种类型的窗口,并返回 false。这些窗口类型分别是:
      • TYPE_INPUT_METHOD:输入法窗口,用于显示输入法的界面。
      • TYPE_VOICE_INTERACTION:语音交互窗口,用于显示语音助手的界面。
      • TYPE_WALLPAPER:壁纸窗口,用于显示桌面壁纸。
      • TYPE_QS_DIALOG:快速设置对话框窗口,用于显示快速设置的面板。
      • TYPE_ACCESSIBILITY_OVERLAY:辅助功能覆盖窗口,用于显示辅助功能的界面。
    • 接着,检查窗口类型,如果窗口类型是 TYPE_TOAST,即吐司窗口,用于显示短暂的提示信息,那么还需要检查调用者的包名和用户 ID 和父窗口,是否需要令牌才能添加吐司窗口,如果需要,那么打印一条错误日志,表示尝试使用未知的令牌添加吐司窗口,并返回 false。
    • 最后,如果没有遇到以上的情况,那么返回 true,表示可以创建窗口。

总结:unprivilegedAppCanCreateTokenWith 方法是用于判断一个没有特权的应用是否可以创建窗口的私有方法,它根据窗口类型和窗口令牌和调用者的信息进行判断,如果不符合条件,就打印错误日志并返回 false,否则返回 true。

 boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");boolean changed = mRoot.updateFocusedWindowLocked(mode, updateInputWindows);Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);return changed;}

updateFocusedWindowLocked,它的作用是更新系统中的焦点窗口,即当前正在与用户交互的窗口。这个方法的逻辑大致如下:

  • 参数:这个方法接收两个参数,分别是:
    • mode:一个 int 类型的值,表示更新焦点窗口时所处的阶段,共有五个值,分别表示正常更新、在分配窗口层级之前、在放置表面过程中、在放置表面之前、在移除焦点窗口后。
    • updateInputWindows:一个 boolean 类型的值,表示是否同步更新输入窗口,即能接收输入事件的窗口。
  • 返回值:这个方法返回一个 boolean 类型的值,表示焦点窗口是否发生了变化。
  • 逻辑:这个方法的逻辑是:
    • 首先,调用 Trace 的 traceBegin 方法,将 TRACE_TAG_WINDOW_MANAGER 和 “wmUpdateFocus” 作为参数传入,这个方法用于开始追踪一个事件,用于性能分析¹。
    • 然后,调用 mRoot 的 updateFocusedWindowLocked 方法,将 mode 和 updateInputWindows 作为参数传入,这个方法用于更新根窗口容器中的焦点窗口,并返回是否发生了变化,将结果赋值给 changed 变量。
    • 接着,调用 Trace 的 traceEnd 方法,将 TRACE_TAG_WINDOW_MANAGER 作为参数传入,这个方法用于结束追踪一个事件,用于性能分析¹。
    • 最后,返回 changed 变量,表示焦点窗口是否发生了变化。

总结:updateFocusedWindowLocked 方法是用于更新系统中的焦点窗口的公开方法,它根据不同的阶段和是否同步更新输入窗口,调用根窗口容器的方法,然后返回焦点窗口是否发生了变化,并且在开始和结束时进行性能追踪。

    void dispatchImeTargetOverlayVisibilityChanged(@NonNull IBinder token,@WindowManager.LayoutParams.WindowType int windowType, boolean visible,boolean removed) {if (mImeTargetChangeListener != null) {if (DEBUG_INPUT_METHOD) {Slog.d(TAG, "onImeTargetOverlayVisibilityChanged, win=" + mWindowMap.get(token)+ ", type=" + ViewDebug.intToString(WindowManager.LayoutParams.class,"type", windowType) + "visible=" + visible + ", removed=" + removed);}mH.post(() -> mImeTargetChangeListener.onImeTargetOverlayVisibilityChanged(token,windowType, visible, removed));}}

dispatchImeTargetOverlayVisibilityChanged,它的作用是分发输入法目标覆盖层可见性变化的事件,即当一个窗口在输入法窗口上方显示或隐藏时,通知输入法目标变化的监听器。这个方法的逻辑大致如下:

  • 参数:这个方法接收四个参数,分别是:
    • token:一个 IBinder 类型的对象,表示输入法目标覆盖层窗口的令牌。
    • windowType:一个 int 类型的值,表示输入法目标覆盖层窗口的类型。
    • visible:一个 boolean 类型的值,表示输入法目标覆盖层窗口是否可见。
    • removed:一个 boolean 类型的值,表示输入法目标覆盖层窗口是否被移除。
  • 逻辑:这个方法的逻辑是:
    • 首先,检查 mImeTargetChangeListener 变量是否为空,这是一个 OnImeTargetChangeListener 类型的变量,用于存储输入法目标变化的监听器。
    • 然后,如果不为空,那么执行以下操作:
      • 如果 DEBUG_INPUT_METHOD 常量为 true,这是一个 boolean 类型的常量,用于控制是否打印输入法相关的调试日志,那么调用 Slog 的 d 方法,将 TAG, “onImeTargetOverlayVisibilityChanged, win=” + mWindowMap.get(token) + “, type=” + ViewDebug.intToString(WindowManager.LayoutParams.class, “type”, windowType) + “visible=” + visible + “, removed=” + removed 作为参数传入,这个方法用于打印一条调试级别的日志,显示输入法目标覆盖层窗口的信息。
      • 然后,调用 mH 的 post 方法,将一个 lambda 表达式作为参数传入,这个方法用于在主线程上执行一个任务。
      • 最后,这个 lambda 表达式的操作是调用 mImeTargetChangeListener 的 onImeTargetOverlayVisibilityChanged 方法,将 token, windowType, visible, removed 作为参数传入,这个方法用于通知输入法目标变化的监听器,输入法目标覆盖层窗口的可见性发生了变化。

总结:dispatchImeTargetOverlayVisibilityChanged 方法是用于分发输入法目标覆盖层可见性变化的事件的公开方法,它根据输入法目标覆盖层窗口的令牌、类型、可见性和移除状态,通知输入法目标变化的监听器,并且在调试模式下打印相关的日志。

private void getInsetsSourceControls(WindowState win, InsetsSourceControl.Array outArray) {final InsetsSourceControl[] controls =win.getDisplayContent().getInsetsStateController().getControlsForDispatch(win);if (controls != null) {final int length = controls.length;final InsetsSourceControl[] outControls = new InsetsSourceControl[length];for (int i = 0; i < length; i++) {// We will leave the critical section before returning the leash to the client,// so we need to copy the leash to prevent others release the one that we are// about to return.if (controls[i] != null) {// This source control is an extra copy if the client is not local. By setting// PARCELABLE_WRITE_RETURN_VALUE, the leash will be released at the end of// SurfaceControl.writeToParcel.outControls[i] = new InsetsSourceControl(controls[i]);outControls[i].setParcelableFlags(PARCELABLE_WRITE_RETURN_VALUE);}}outArray.set(outControls);}}

getInsetsSourceControls,它的作用是获取一个窗口的插图源控制器,即用于控制窗口的插图区域的可见性和行为的对象。这个方法的逻辑大致如下:

  • 参数:这个方法接收两个参数,分别是:
    • win:一个 WindowState 类型的对象,表示要获取插图源控制器的窗口。
    • outArray:一个 InsetsSourceControl.Array 类型的对象,表示一个输出参数,用于返回插图源控制器的数组。
  • 逻辑:这个方法的逻辑是:
    • 首先,调用 win 的 getDisplayContent 方法,得到窗口所在的显示内容(DisplayContent),然后调用它的 getInsetsStateController 方法,得到它的插图状态控制器(InsetsStateController),然后调用它的 getControlsForDispatch 方法,将 win 作为参数传入,得到它的插图源控制器的数组(InsetsSourceControl[]),并赋值给 controls 变量。
    • 然后,检查 controls 变量是否为空,如果不为空,那么执行以下操作:
      • 声明一个 int 类型的变量,用于存储 controls 数组的长度,并赋值为 controls.length。
      • 声明一个 InsetsSourceControl 类型的数组,用于存储输出的插图源控制器,并初始化为和 controls 数组一样的长度。
      • 遍历 controls 数组的每个元素,执行以下操作:
        • 如果当前元素不为空,那么执行以下操作:
          • 创建一个新的 InsetsSourceControl 对象,用于复制当前元素,并赋值给输出数组的对应位置。
          • 调用输出数组的当前元素的 setParcelableFlags 方法,将 PARCELABLE_WRITE_RETURN_VALUE 作为参数传入,这个方法用于设置可序列化的标志,表示在 SurfaceControl.writeToParcel 方法结束时,释放当前元素的绳索(leash),即用于控制表面的对象。
      • 调用 outArray 的 set 方法,将输出数组作为参数传入,这个方法用于将输出数组赋值给 outArray 对象。

总结:getInsetsSourceControls 方法是用于获取一个窗口的插图源控制器的私有方法,它根据窗口的显示内容和插图状态,得到插图源控制器的数组,然后复制并设置可序列化的标志,然后返回给输出参数。

这篇关于WindowManagerService的addWindow方法源码解读的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/760589

相关文章

Java实现任务管理器性能网络监控数据的方法详解

《Java实现任务管理器性能网络监控数据的方法详解》在现代操作系统中,任务管理器是一个非常重要的工具,用于监控和管理计算机的运行状态,包括CPU使用率、内存占用等,对于开发者和系统管理员来说,了解这些... 目录引言一、背景知识二、准备工作1. Maven依赖2. Gradle依赖三、代码实现四、代码详解五

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2

Redis与缓存解读

《Redis与缓存解读》文章介绍了Redis作为缓存层的优势和缺点,并分析了六种缓存更新策略,包括超时剔除、先删缓存再更新数据库、旁路缓存、先更新数据库再删缓存、先更新数据库再更新缓存、读写穿透和异步... 目录缓存缓存优缺点缓存更新策略超时剔除先删缓存再更新数据库旁路缓存(先更新数据库,再删缓存)先更新数

Java调用Python代码的几种方法小结

《Java调用Python代码的几种方法小结》Python语言有丰富的系统管理、数据处理、统计类软件包,因此从java应用中调用Python代码的需求很常见、实用,本文介绍几种方法从java调用Pyt... 目录引言Java core使用ProcessBuilder使用Java脚本引擎总结引言python

Apache Tomcat服务器版本号隐藏的几种方法

《ApacheTomcat服务器版本号隐藏的几种方法》本文主要介绍了ApacheTomcat服务器版本号隐藏的几种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需... 目录1. 隐藏HTTP响应头中的Server信息编辑 server.XML 文件2. 修China编程改错误

Java中switch-case结构的使用方法举例详解

《Java中switch-case结构的使用方法举例详解》:本文主要介绍Java中switch-case结构使用的相关资料,switch-case结构是Java中处理多个分支条件的一种有效方式,它... 目录前言一、switch-case结构的基本语法二、使用示例三、注意事项四、总结前言对于Java初学者

使用Python实现大文件切片上传及断点续传的方法

《使用Python实现大文件切片上传及断点续传的方法》本文介绍了使用Python实现大文件切片上传及断点续传的方法,包括功能模块划分(获取上传文件接口状态、临时文件夹状态信息、切片上传、切片合并)、整... 目录概要整体架构流程技术细节获取上传文件状态接口获取临时文件夹状态信息接口切片上传功能文件合并功能小

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步

Oracle Expdp按条件导出指定表数据的方法实例

《OracleExpdp按条件导出指定表数据的方法实例》:本文主要介绍Oracle的expdp数据泵方式导出特定机构和时间范围的数据,并通过parfile文件进行条件限制和配置,文中通过代码介绍... 目录1.场景描述 2.方案分析3.实验验证 3.1 parfile文件3.2 expdp命令导出4.总结

更改docker默认数据目录的方法步骤

《更改docker默认数据目录的方法步骤》本文主要介绍了更改docker默认数据目录的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1.查看docker是否存在并停止该服务2.挂载镜像并安装rsync便于备份3.取消挂载备份和迁