Android14 InputManager-焦点窗口的更新

设置焦点时需要

先设置焦点APP

mFo-cusedApp是一个AppWindowToken,在WMS中用来表示当前处于Resume状态的Activity。它是由AMS在开始启动一个Activity时调用WMS的setFocusedApp()函数设置的。

考虑以下应用场景,当用户从Launcher中启动一个Activity之后,在新Activity的窗口显示之前便立刻按下了BACK键。很明显,用户的意图是关闭刚刚启动的Activity,而不是退出Launcher。然而,由于BACK键按得过快,新Activity尚未创建窗口,因此按照之前讨论的焦点窗口的查找条件,Launcher的窗口将会作为焦点窗口而接收BACK键,从而使得Launcher被退出。这与用户的意图是相悖的。为了解决这个问题,WMS要求焦点窗口必须属于mFocusedApp,或者位于mFocu-sedApp的窗口之上。再看添加这个限制之后,上面的应用场景会变成什么样子。当启动新Activity时,尽管其窗口尚未创建,但这个窗口的AppWindowToken已经被添加到mAppTokens列表的顶部,并通过setFocusedApp()设置为mFocusedApp。此时再更新焦点窗口时发现第一个符合焦点条件的窗口属于Launcher,但Launcher的AppWindowToken位于mFocusedApp也就是新Activity之下,因此它不能作为焦点窗口。这样,在新Activity创建窗口之前的一个短暂时间段内,系统处于无焦点窗口的情况。此时到来的BACK键在InputDispatcher中会因找不到目标窗口而触发handleTargetsNotReadyLocked(),从而进入等待重试状态。随后新Activity创建了自己的窗口并添加到WMS里,这时WMS可以成功地将这个窗口作为焦点,并通过IMS.setInputWindows()将其更新到InputDispatcher。这个更新动作会唤醒派发线程立刻对BACK键进行重试,这次重试便找到了新Activity的窗口作为目标,并将事件发送过去,从而正确地体现用户的意图。

    void setLastResumedActivityUncheckLocked(ActivityRecord r, String reason) {

 boolean focusedAppChanged = false;
        if (!getTransitionController().isTransientCollect(r)) {
            focusedAppChanged = r.mDisplayContent.setFocusedApp(r);
            if (focusedAppChanged) {
                mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
                        true /*updateInputWindows*/);
            }
        }
WindowManagerService.java


    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;
    }
RootWindowContainer.java
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
        mTopFocusedAppByProcess.clear();
        boolean changed = false;
        int topFocusedDisplayId = INVALID_DISPLAY;
        // Go through the children in z-order starting at the top-most
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final DisplayContent dc = mChildren.get(i);
            changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows, topFocusedDisplayId);
            final WindowState newFocus = dc.mCurrentFocus;
            if (newFocus != null) {
                final int pidOfNewFocus = newFocus.mSession.mPid;
                if (mTopFocusedAppByProcess.get(pidOfNewFocus) == null) {
                    mTopFocusedAppByProcess.put(pidOfNewFocus, newFocus.mActivityRecord);
                }
                if (topFocusedDisplayId == INVALID_DISPLAY) {
                    topFocusedDisplayId = dc.getDisplayId();
                }
            } else if (topFocusedDisplayId == INVALID_DISPLAY && dc.mFocusedApp != null) {
                // The top-most display that has a focused app should still be the top focused
                // display even when the app window is not ready yet (process not attached or
                // window not added yet).
                topFocusedDisplayId = dc.getDisplayId();
            }
        }
        if (topFocusedDisplayId == INVALID_DISPLAY) {
            topFocusedDisplayId = DEFAULT_DISPLAY;
        }
        if (mTopFocusedDisplayId != topFocusedDisplayId) {
            mTopFocusedDisplayId = topFocusedDisplayId;
            mWmService.mInputManager.setFocusedDisplay(topFocusedDisplayId);
            mWmService.mPolicy.setTopFocusedDisplay(topFocusedDisplayId);
            mWmService.mAccessibilityController.setFocusedDisplay(topFocusedDisplayId);
            ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "New topFocusedDisplayId=%d", topFocusedDisplayId);
        }
        return changed;
    }
DisplayContent.java
 boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows,
            int topFocusedDisplayId) {
        // Don't re-assign focus automatically away from a should-keep-focus app window.
        // `findFocusedWindow` will always grab the transient-launch app since it is "on top" which
        // would create a mismatch, so just early-out here.
        if (mCurrentFocus != null && mTransitionController.shouldKeepFocus(mCurrentFocus)
                // This is only keeping focus, so don't early-out if the focused-app has been
                // explicitly changed (eg. via setFocusedTask).
                && mFocusedApp != null && mCurrentFocus.isDescendantOf(mFocusedApp)
                && mCurrentFocus.isVisible() && mCurrentFocus.isFocusable()) {
            ProtoLog.v(WM_DEBUG_FOCUS, "Current transition prevents automatic focus change");
            return false;
        }
        WindowState newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);
        if (mCurrentFocus == newFocus) {
            return false;
        }
        boolean imWindowChanged = false;
        final WindowState imWindow = mInputMethodWindow;
        if (imWindow != null) {
            final WindowState prevTarget = mImeLayeringTarget;
            final WindowState newTarget = computeImeTarget(true /* updateImeTarget*/);
            imWindowChanged = prevTarget != newTarget;

            if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
                    && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {
                assignWindowLayers(false /* setLayoutNeeded */);
            }

            if (imWindowChanged) {
                mWmService.mWindowsChanged = true;
                setLayoutNeeded();
                newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);
            }
        }

        ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "Changing focus from %s to %s displayId=%d Callers=%s",
                mCurrentFocus, newFocus, getDisplayId(), Debug.getCallers(4));
        final WindowState oldFocus = mCurrentFocus;
        mCurrentFocus = newFocus;

        if (newFocus != null) {
            mWinAddedSinceNullFocus.clear();
            mWinRemovedSinceNullFocus.clear();

            if (newFocus.canReceiveKeys()) {
                // Displaying a window implicitly causes dispatching to be unpaused.
                // This is to protect against bugs if someone pauses dispatching but
                // forgets to resume.
                newFocus.mToken.paused = false;
            }
        }

        getDisplayPolicy().focusChangedLw(oldFocus, newFocus);
        mAtmService.mBackNavigationController.onFocusChanged(newFocus);

        if (imWindowChanged && oldFocus != mInputMethodWindow) {
            // Focus of the input method window changed. Perform layout if needed.
            if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
                performLayout(true /*initial*/,  updateInputWindows);
            } else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) {
                // Client will do the layout, but we need to assign layers
                // for handleNewWindowLocked() below.
                assignWindowLayers(false /* setLayoutNeeded */);
            }
        }

        if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
            // If we defer assigning layers, then the caller is responsible for doing this part.
            getInputMonitor().setInputFocusLw(newFocus, updateInputWindows);
        }

        adjustForImeIfNeeded();
        updateKeepClearAreas();

        // We may need to schedule some toast windows to be removed. The toasts for an app that
        // does not have input focus are removed within a timeout to prevent apps to redress
        // other apps' UI.
        scheduleToastWindowsTimeoutIfNeededLocked(oldFocus, newFocus);

        if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
            pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
        }

        // Notify the accessibility manager for the change so it has the windows before the newly
        // focused one starts firing events.
        // TODO(b/151179149) investigate what info accessibility service needs before input can
        // dispatch focus to clients.
        if (mWmService.mAccessibilityController.hasCallbacks()) {
            mWmService.mH.sendMessage(PooledLambda.obtainMessage(
                    this::updateAccessibilityOnWindowFocusChanged,
                    mWmService.mAccessibilityController));
        }

        return true;
    }

updateFocusedWindowLocked

首先寻找焦点窗口findFocusedWindowIfNeeded

然后将焦点窗口设置到input :setInputFocusLw

寻找焦点窗口

DisplayContent.java

    WindowState findFocusedWindowIfNeeded(int topFocusedDisplayId) {
        return (hasOwnFocus() || topFocusedDisplayId == INVALID_DISPLAY)
                    ? findFocusedWindow() : null;
    }

    /**
     * Find the focused window of this DisplayContent. The search takes the state of the display
     * content into account
     * @return The focused window, null if none was found.
     */
    WindowState findFocusedWindow() {
        mTmpWindow = null;

        // mFindFocusedWindow will populate mTmpWindow with the new focused window when found.
        forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */);

        if (mTmpWindow == null) {
            ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: No focusable windows, display=%d",
                    getDisplayId());
            return null;
        }
        return mTmpWindow;
    }
 private final ToBooleanFunction<WindowState> mFindFocusedWindow = w -> {
        final ActivityRecord focusedApp = mFocusedApp;
        ProtoLog.v(WM_DEBUG_FOCUS, "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s",
                w, w.mAttrs.flags, w.canReceiveKeys(),
                w.canReceiveKeysReason(false /* fromUserTouch */));

        if (!w.canReceiveKeys()) {
            return false;
        }

        // When switching the app task, we keep the IME window visibility for better
        // transitioning experiences.
        // However, in case IME created a child window or the IME selection dialog without
        // dismissing during the task switching to keep the window focus because IME window has
        // higher window hierarchy, we don't give it focus if the next IME layering target
        // doesn't request IME visible.
        if (w.mIsImWindow && w.isChildWindow() && (mImeLayeringTarget == null
                || !mImeLayeringTarget.isRequestedVisible(ime()))) {
            return false;
        }
        if (w.mAttrs.type == TYPE_INPUT_METHOD_DIALOG && mImeLayeringTarget != null
                && !mImeLayeringTarget.isRequestedVisible(ime())
                && !mImeLayeringTarget.isVisibleRequested()) {
            return false;
        }

        final ActivityRecord activity = w.mActivityRecord;

        if (focusedApp == null) {
            ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,
                    "findFocusedWindow: focusedApp=null using new focus @ %s", w);
            mTmpWindow = w;
            return true;
        }

        if (!focusedApp.windowsAreFocusable()) {
            // Current focused app windows aren't focusable...
            ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: focusedApp windows not"
                    + " focusable using new focus @ %s", w);
            mTmpWindow = w;
            return true;
        }

        // Descend through all of the app tokens and find the first that either matches
        // win.mActivityRecord (return win) or mFocusedApp (return null).
        if (activity != null && w.mAttrs.type != TYPE_APPLICATION_STARTING) {
            if (focusedApp.compareTo(activity) > 0) {
                // App root task below focused app root task. No focus for you!!!
                ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,
                        "findFocusedWindow: Reached focused app=%s", focusedApp);
                mTmpWindow = null;
                return true;
            }

            // If the candidate activity is currently being embedded in the focused task, the
            // activity cannot be focused unless it is on the same TaskFragment as the focusedApp's.
            TaskFragment parent = activity.getTaskFragment();
            if (parent != null && parent.isEmbedded()) {
                if (activity.getTask() == focusedApp.getTask()
                        && activity.getTaskFragment() != focusedApp.getTaskFragment()) {
                    return false;
                }
            }
        }

        ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: Found new focus @ %s", w);
        mTmpWindow = w;
        return true;
    };

该方法中,将依次根据如下条件获得焦点窗口:

  • 如果WindowState不能接收Input事件,则不能作为焦点窗口;
    1. 如果没有前台Activity,则当前WindowState作为焦点窗口返回;
    2. 如果前台Activity是不可获焦状态,则当前WindowState作为焦点窗口返回;
    3. 如果当前WindowState由ActivityRecord管理,且该WindowState不是Staring Window类型,那么当前台Activity在当前WindowState所属Activity之上时,不存在焦点窗口;
    4. 处于focusedApp之下的窗口不能成为焦点窗口
    5. 如果以上条件都不满足,则当前WindowState作为焦点窗口返回;
  • 由此可以得出影响窗口焦点的动作有以下几个:
  • □窗口的增删操作。
  • □窗口次序的调整。
  • □Activity的启动与退出。
  • □DisplayContent的增删操作。
  • 因此当这些动作发生时,WMS都会触发对updateFocusedWindowLocked()的调用,并更新窗口的布局信息到输入系统,以实现按键事件正确派发。
  •     public boolean canReceiveKeys(boolean fromUserTouch) {
            if (mActivityRecord != null && mTransitionController.shouldKeepFocus(mActivityRecord)) {
                // During transient launch, the transient-hide windows are not visibleRequested
                // or on-top but are kept focusable and thus can receive keys.
                return true;
            }
            final boolean canReceiveKeys = isVisibleRequestedOrAdding()
                    && (mViewVisibility == View.VISIBLE) && !mRemoveOnExit
                    && ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)
                    && (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))
                    // can it receive touches
                    && (mActivityRecord == null || mActivityRecord.getTask() == null
                            || !mActivityRecord.getTask().getRootTask().shouldIgnoreInput());
    
            if (!canReceiveKeys) {
                return false;
            }
            // Do not allow untrusted virtual display to receive keys unless user intentionally
            // touches the display.
            return fromUserTouch || getDisplayContent().isOnTop()
                    || getDisplayContent().isTrusted();
        }

    如果一个WindowState可以接受Input事件,需要同时满足多个条件:

    1. isVisibleRequestedOrAdding方法为true,表示该WindowState可见或处于添加过程中:
    2. mViewVisibility属性为View.VISIBLE,表示客户端View可见;
    3. mRemoveOnExit为false,表示WindowState的退出动画不存在;
    4. mAttrs.flags中不存在FLAG_NOT_FOCUSABLE标记,该标记如果设置,表示该窗口为不可获焦窗口;
    5. mActivityRecord为null或者mActivityRecord可获焦;
    6. cantReceiveTouchInput()方法为false,表示可以接受Touch事件。
    boolean isVisibleRequestedOrAdding() {
        final ActivityRecord atoken = mActivityRecord;
        return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
                && isVisibleByPolicy() && !isParentWindowHidden()
                && (atoken == null || atoken.isVisibleRequested())
                && !mAnimatingExit && !mDestroying;
    }

然后将焦点窗口设置到input :setInputFocusLw

查看Android14 InputManager-InputWindow的更新过程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值