Android14 SystemUI 默认横屏时打开竖屏应用后系统变为默认竖屏(关闭屏幕自动旋转)

178 阅读3分钟

背景:在平板设备上,当锁定平板设备为横屏时,然后打开一个强制竖屏的应用时,发现平板设备变为默认竖屏了, 下面分析一下原因

查看当前显示方向:

adb shell settings get system user_rotation

通过分析代码发现在锁定屏幕方向时,打开竖屏应用后再设备竖屏显示应用后SystemUI 会将系统 user_rotation 设置为 0 , 即竖屏模式

看一下SystemUI 是如何修改user_rotation 的, 我们的入手点是 NavBarHelper , 因为在 NavBarHelper 中向 WindowManagerService 中注册了一个监听,监听屏幕方向

frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java

private final IWindowManager mWm;
@Inject
public NavBarHelper(
        IWindowManager wm,
 ) {
    mWm = wm;
}

NavBarHelper 初始化的时候设置 WindowManagerServiceIWindowManager binder调用, 看一下 vm 是在哪里注入的 frameworks/base/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java

@Module
public class FrameworkServicesModule {
    @Provides
    @Singleton
    static IWindowManager provideIWindowManager() {
        return WindowManagerGlobal.getWindowManagerService();
    }
}

这向NavBarHelper注入了一个 IWindowManager

在回到 NavBarHelper 中在 setupOnFirstBar() 方法中设置显示方向变化的观察回调

private void setupOnFirstBar() {
    // Setup display rotation watcher
    try {
        mWm.watchRotation(mRotationWatcher, mDefaultDisplayId);
    } catch (Exception e) {
        Log.w(TAG, "Failed to register rotation watcher", e);
    }
}

mRotationWatcher 这个实现也在 NavBarHelper 中, 具体代码如下:

private final IRotationWatcher mRotationWatcher = new IRotationWatcher.Stub() {
    @Override
    public void onRotationChanged(final int rotation) {
        // 省略部分代码
        // We need this to be scheduled as early as possible to beat the redrawing of
        // window in response to the orientation change.
        mHandler.postAtFrontOfQueue(() -> {
            mRotationWatcherRotation = rotation;
            dispatchRotationChanged(rotation);
        });
    }
};

private void dispatchRotationChanged(int rotation) {
    for (NavbarTaskbarStateUpdater listener : mStateListeners) {
        listener.updateRotationWatcherState(rotation);
    }
}

在接收到显示变化回调后, 会通过d ispatchRotationChanged 方法把状态分发出去, 下面看一下在 NavBarHelper 中添加监听的方法

public void registerNavTaskStateUpdater(NavbarTaskbarStateUpdater listener) {
    mStateListeners.add(listener);
    // 省略部分代码
}

整理一下上面的代码,主要逻辑是 NavBarHelper 通过想 WindowManagerService 添加显示方向变化监听,在显示方向发生变化时进行分发

下面看一下在显示发生变化事会分发到哪里。查看源码发现会分发的 NavigationBarNavigationBar 在创建时会把 NavBarHelper 注入, 在 onInt 方法中调用 NavBarHelperregisterNavTaskStateUpdater 方法设置监听 frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java

public void onInit() {

    // This currently MUST be called after mHomeButtonLongPressDurationMs is initialized since
    // the registration callbacks will trigger code that uses it
    mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
    // 省略部分代码
}
    
private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater =
    new NavBarHelper.NavbarTaskbarStateUpdater() {
        // 省略部分代码
        @Override
        public void updateRotationWatcherState(int rotation) {
            if (mIsOnDefaultDisplay && mView != null) {
                mView.getRotationButtonController().onRotationWatcherChanged(rotation);
                if (mView.needsReorient(rotation)) {
                    repositionNavigationBar(rotation);
                }
            }
        }
    };

在接收到方向变化回调时调用了 RotationButtonController 中的 onRotationWatcherChanged 方法,下面看一下 RotationButtonController 中的实现 frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java

public static final int NATURAL_ROTATION = Surface.ROTATION_0; // ROTATION_0 = 0

public void onRotationWatcherChanged(int rotation) {
    if (!mListenersRegistered) {
        // Ignore if not registered
        return;
    }

    // If the screen rotation changes while locked, potentially update lock to flow with
    // new screen rotation and hide any showing suggestions.
    boolean rotationLocked = isRotationLocked();
    // The isVisible check makes the rotation button disappear when we are not locked
    // (e.g. for tabletop auto-rotate).
    if (rotationLocked || mRotationButton.isVisible()) {
        // Do not allow a change in rotation to set user rotation when docked.
        if (shouldOverrideUserLockPrefs(rotation) && rotationLocked && !mDocked) {
            setRotationLockedAtAngle(rotation);
        }
        setRotateSuggestionButtonState(false /* visible */, true /* forced */);
    }
}

private boolean shouldOverrideUserLockPrefs(final int rotation) {
    if (mSkipOverrideUserLockPrefsOnce) {
        mSkipOverrideUserLockPrefsOnce = false;
        return false;
    }
    // Only override user prefs when returning to the natural rotation (normally portrait).
    // Don't let apps that force landscape or 180 alter user lock.
    return rotation == NATURAL_ROTATION;
}

public void setRotationLockedAtAngle(int rotationSuggestion) {
    RotationPolicy.setRotationLockAtAngle(mContext, /* enabled= */ isRotationLocked(),
            /* rotation= */ rotationSuggestion);
}

上面代码执行完后,就会将系统显示方向设置为竖屏显示(user_rotation = 0), 需要注意的地方是 shouldOverrideUserLockPrefs 方法,只有当 rotation 为 0 时,才会返回true , 也就是,只有当 rotation 为 0 的时候才会调用 setRotationLockedAtAngle 方法设置显示方向为竖屏, 当 rotation 为非 0 的时候, 不会调用 setRotationLockedAtAngle 方法设置显示方向, 也就解释了为平板默认设置为横屏,打开一个竖屏应用后,系统变为默认竖屏了。