系统版本:基于 android-16.0.0_r4 版本

CarSystemBar:核心类,基于原生 SystemUI 的扩展(部分链路复用),可动态配置位置(上下左右)。

初始化 application 通过 dagger 注入组件,会自动执行 start 方法。

/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.car.systembar;

import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;

import javax.inject.Inject;

/** Corestartable class that is responsible for initializing the system bar controller. */
@SysUISingleton
public class CarSystemBar implements CoreStartable {
    private final CarSystemBarController mCarSystemBarController;

    @Inject
    public CarSystemBar(CarSystemBarController carSystemBarController) {
        mCarSystemBarController = carSystemBarController;
    }

    @Override
    public void start() {
        mCarSystemBarController.init();
    }
}
CarSystemBar

CommandQueue:继承 IStatusBar.Stub,管理 CallBack。

AutoHideControllerImpl:瞬态隐藏控制类(全屏下唤出隐藏)。

在 CarSystemBarControllerImpl 中,负责初始化监听,执行 createSystemBar。

/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.car.systembar;

import static android.content.Intent.ACTION_OVERLAY_CHANGED;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;

import static com.android.systemui.car.Flags.configAwareSystemui;
import static com.android.systemui.car.systembar.CarSystemBarViewController.BUTTON_TYPE_KEYGUARD;
import static com.android.systemui.car.systembar.CarSystemBarViewController.BUTTON_TYPE_NAVIGATION;
import static com.android.systemui.car.systembar.CarSystemBarViewController.BUTTON_TYPE_OCCLUSION;
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT;

import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.StatusBarManager.Disable2Flags;
import android.app.StatusBarManager.DisableFlags;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.PatternMatcher;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController;
import android.view.WindowManager;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.internal.view.AppearanceRegion;
import com.android.systemui.car.CarDeviceProvisionedController;
import com.android.systemui.car.CarDeviceProvisionedListener;
import com.android.systemui.car.displaycompat.ToolbarController;
import com.android.systemui.car.keyguard.KeyguardSystemBarPresenter;
import com.android.systemui.car.wm.scalableui.systemwindow.SystemUiWindow;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;

import dagger.Lazy;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/** A single class which controls the system bar views. */
@SysUISingleton
public class CarSystemBarControllerImpl implements CarSystemBarController,
        CommandQueue.Callbacks, ConfigurationController.ConfigurationListener,
        KeyguardSystemBarPresenter {
    private static final boolean DEBUG = Build.IS_ENG || Build.IS_USERDEBUG;

    private static final String TAG = CarSystemBarController.class.getSimpleName();

    private static final String OVERLAY_FILTER_DATA_SCHEME = "package";

    private final Context mContext;
    private final CarSystemBarViewFactory mCarSystemBarViewFactory;
    private final SystemBarConfigs mSystemBarConfigs;
    private final SysuiDarkIconDispatcher mStatusBarIconController;
    private final CarDeviceProvisionedController mCarDeviceProvisionedController;
    private final CommandQueue mCommandQueue;
    private final AutoHideController mAutoHideController;
    private final ButtonSelectionStateListener mButtonSelectionStateListener;
    private final DelayableExecutor mExecutor;
    private final IStatusBarService mBarService;
    private final DisplayTracker mDisplayTracker;
    private final Lazy<KeyguardStateController> mKeyguardStateControllerLazy;
    private final Lazy<PhoneStatusBarPolicy> mIconPolicyLazy;
    private final ConfigurationController mConfigurationController;
    private final CarSystemBarRestartTracker mCarSystemBarRestartTracker;
    private final int mDisplayId;
    @Nullable
    private final ToolbarController mDisplayCompatToolbarController;

    protected final UserTracker mUserTracker;

    // Saved StatusBarManager.DisableFlags
    private int mStatusBarState;
    // Saved StatusBarManager.Disable2Flags
    private int mStatusBarState2;
    private int mLockTaskMode;

    // System bar windows.
    private final Map<String, ViewGroup> mSystemBarWindowMap = new HashMap<>();
    // System bar views.
    private final Map<String, CarSystemBarViewController> mSystemBarViewControllerMap =
            new HashMap<>();
    // If the system bar is attached to the window or not.
    private final Map<String, Boolean> mSystemBarAttachedMap = new HashMap<>();
    // If the system bar is enabled or not.
    private final Map<String, Boolean> mSystemBarEnabledMap = new HashMap<>();
    // Set of View.OnTouchListener on each system bar.
    private final Map<String, Set<View.OnTouchListener>> mBarTouchListenersMap = new HashMap<>();

    // To be attached to the navigation bars such that they can close the notification panel if
    // it's open.
    private boolean mDeviceIsSetUpForUser = true;
    private boolean mIsUserSetupInProgress = false;

    private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0];
    @BarTransitions.TransitionMode
    private int mStatusBarMode;
    @BarTransitions.TransitionMode
    private int mSystemBarMode;
    private boolean mStatusBarTransientShown;
    private boolean mNavBarTransientShown;

    public CarSystemBarControllerImpl(Context context,
            UserTracker userTracker,
            CarSystemBarViewFactory carSystemBarViewFactory,
            SystemBarConfigs systemBarConfigs,
            // TODO(b/156052638): Should not need to inject LightBarController
            LightBarController lightBarController,
            DarkIconDispatcher darkIconDispatcher,
            WindowManager windowManager,
            CarDeviceProvisionedController deviceProvisionedController,
            CommandQueue commandQueue,
            AutoHideController autoHideController,
            ButtonSelectionStateListener buttonSelectionStateListener,
            @Main DelayableExecutor mainExecutor,
            IStatusBarService barService,
            Lazy<KeyguardStateController> keyguardStateControllerLazy,
            Lazy<PhoneStatusBarPolicy> iconPolicyLazy,
            ConfigurationController configurationController,
            CarSystemBarRestartTracker restartTracker,
            DisplayTracker displayTracker,
            @Nullable ToolbarController toolbarController,
            @Main Handler handler) {
        mContext = context;
        mUserTracker = userTracker;
        mCarSystemBarViewFactory = carSystemBarViewFactory;
        mSystemBarConfigs = systemBarConfigs;
        mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
        mCarDeviceProvisionedController = deviceProvisionedController;
        mCommandQueue = commandQueue;
        mAutoHideController = autoHideController;
        mButtonSelectionStateListener = buttonSelectionStateListener;
        mExecutor = mainExecutor;
        mBarService = barService;
        mKeyguardStateControllerLazy = keyguardStateControllerLazy;
        mIconPolicyLazy = iconPolicyLazy;
        mDisplayId = context.getDisplayId();
        mDisplayTracker = displayTracker;
        mConfigurationController = configurationController;
        mCarSystemBarRestartTracker = restartTracker;
        mDisplayCompatToolbarController = toolbarController;
    }

    /**
     * Initializes the SystemBars
     */
    public void init() {

        resetSystemBarConfigs();

        // Connect into the status bar manager service
        mCommandQueue.addCallback(this);

        mAutoHideController.setStatusBar(new AutoHideUiElement() {
            @Override
            public void synchronizeState() {
                // No op.
            }

            @Override
            public boolean isVisible() {
                return mStatusBarTransientShown;
            }

            @Override
            public void hide() {
                clearTransient();
            }
        });

        mAutoHideController.setNavigationBar(new AutoHideUiElement() {
            @Override
            public void synchronizeState() {
                // No op.
            }

            @Override
            public boolean isVisible() {
                return mNavBarTransientShown;
            }

            @Override
            public void hide() {
                clearTransient();
            }
        });

        mDeviceIsSetUpForUser = mCarDeviceProvisionedController.isCurrentUserSetup();
        mIsUserSetupInProgress = mCarDeviceProvisionedController.isCurrentUserSetupInProgress();
        mCarDeviceProvisionedController.addCallback(
                new CarDeviceProvisionedListener() {
                    @Override
                    public void onUserSetupInProgressChanged() {
                        mExecutor.execute(() -> resetSystemBarContentIfNecessary());
                    }

                    @Override
                    public void onUserSetupChanged() {
                        mExecutor.execute(() -> resetSystemBarContentIfNecessary());
                    }

                    @Override
                    public void onUserSwitched() {
                        mExecutor.execute(() -> resetSystemBarContentIfNecessary());
                    }
                });

        mConfigurationController.addCallback(/* listener= */ this);
        registerOverlayChangeBroadcastReceiver();

        createSystemBar();

        TaskStackChangeListeners.getInstance().registerTaskStackListener(
                mButtonSelectionStateListener);
        TaskStackChangeListeners.getInstance().registerTaskStackListener(
                new TaskStackChangeListener() {
                    @Override
                    public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
                        if (mDisplayCompatToolbarController != null) {
                            mDisplayCompatToolbarController.update(taskInfo);
                        }
                    }
                });

        // Lastly, call to the icon policy to install/update all the icons.
        // Must be called on the main thread due to the use of observeForever() in
        // mIconPolicy.init().
        mExecutor.execute(() -> {
            mIconPolicyLazy.get().init();
        });
    }

    /**
     * We register for soft keyboard visibility events such that we can hide the navigation bar
     * giving more screen space to the IME. Note: this is optional and controlled by
     * {@code com.android.internal.R.bool.config_hideNavBarForKeyboard}.
     */
    @Override
    public void setImeWindowStatus(int displayId, int visibility, int backDisposition,
            boolean showImeSwitcher) {
        if (mContext.getDisplayId() != displayId) {
            return;
        }

        boolean isKeyboardVisible = (visibility & InputMethodService.IME_VISIBLE) != 0;

        updateKeyboardVisibility(isKeyboardVisible);
    }

    @Override
    public void onSystemBarAttributesChanged(
            int displayId,
            @WindowInsetsController.Appearance int appearance,
            AppearanceRegion[] appearanceRegions,
            boolean navbarColorManagedByIme,
            @WindowInsetsController.Behavior int behavior,
            @InsetsType int requestedVisibleTypes,
            String packageName,
            LetterboxDetails[] letterboxDetails) {
        if (displayId != mDisplayId) {
            return;
        }
        boolean barModeChanged = updateStatusBarMode(
                mStatusBarTransientShown ? MODE_SEMI_TRANSPARENT : MODE_TRANSPARENT);
        int numStacks = appearanceRegions.length;
        boolean stackAppearancesChanged = mAppearanceRegions.length != numStacks;
        for (int i = 0; i < numStacks && !stackAppearancesChanged; i++) {
            stackAppearancesChanged |= !appearanceRegions[i].equals(mAppearanceRegions[i]);
        }
        if (stackAppearancesChanged || barModeChanged) {
            mAppearanceRegions = appearanceRegions;
            updateStatusBarAppearance();
        }
    }

    @Override
    public void disable(int displayId, @DisableFlags int state1, @Disable2Flags int state2,
            boolean animate) {
        if (displayId != mDisplayId) {
            return;
        }
        setSystemBarStates(state1, state2);
    }

    @Override
    public void showTransient(int displayId, int types, boolean isGestureOnSystemBar) {
        if (displayId != mDisplayId) {
            return;
        }
        if ((types & WindowInsets.Type.statusBars()) != 0) {
            if (!mStatusBarTransientShown) {
                mStatusBarTransientShown = true;
                handleTransientChanged();
            }
        }
        if ((types & WindowInsets.Type.navigationBars()) != 0) {
            if (!mNavBarTransientShown) {
                mNavBarTransientShown = true;
                handleTransientChanged();
            }
        }
    }

    @Override
    public void abortTransient(int displayId, int types) {
        if (displayId != mDisplayId) {
            return;
        }
        if ((types & (WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars())) == 0) {
            return;
        }
        clearTransient();
    }

    @Override
    public void onConfigChanged(Configuration newConfig) {
        // cache the current state
        Map<String, Bundle> cachedSystemBarCurrentState = cacheSystemBarCurrentState();
        resetSystemBarContent(/* isProvisionedStateChange= */ false);
        // retrieve the previous state
        restoreSystemBarSavedState(cachedSystemBarCurrentState);
    }

    private Map<String, Bundle> cacheSystemBarCurrentState() {
        Map<String, Bundle> savedStates = mSystemBarConfigs.getSystemBarNamesByZOrder().stream()
                .collect(HashMap::new,
                        (map, name) -> {
                            Bundle bundle = new Bundle();
                            getBarViewController(name, isDeviceSetupForUser())
                                    .onSaveInstanceState(bundle);
                            map.put(name, bundle);
                        },
                        HashMap::putAll);
        return savedStates;
    }

    private void restoreSystemBarSavedState(Map<String, Bundle> savedStates) {
        mSystemBarConfigs.getSystemBarNamesByZOrder().forEach(name -> {
            getBarViewController(name, isDeviceSetupForUser())
                    .onRestoreInstanceState(savedStates.get(name));
        });
    }

    private void readConfigs() {
        mSystemBarEnabledMap.clear();
        mSystemBarConfigs.getSystemBarNamesByZOrder().forEach(name -> {
            mSystemBarEnabledMap.put(name, mSystemBarConfigs.getEnabledStatusByName(name));
        });
    }

    /** Toggles the right nav bar visibility. */
    @VisibleForTesting
    boolean setWindowVisibility(ViewGroup window, @View.Visibility int visibility) {
        if (window == null) {
            return false;
        }

        if (window.getVisibility() == visibility) {
            return false;
        }

        window.setVisibility(visibility);
        return true;
    }

    /**
     * Sets the system bar states - {@code StatusBarManager.DisableFlags},
     * {@code StatusBarManager.Disable2Flags}, lock task mode. When there is a change in state,
     * and refreshes the system bars.
     *
     * @param state  {@code StatusBarManager.DisableFlags}
     * @param state2 {@code StatusBarManager.Disable2Flags}
     */
    @VisibleForTesting
    void setSystemBarStates(int state, int state2) {
        int diff = (state ^ mStatusBarState) | (state2 ^ mStatusBarState2);
        int lockTaskMode = getLockTaskModeState();
        if (diff == 0 && mLockTaskMode == lockTaskMode) {
            if (DEBUG) {
                Log.d(TAG, "setSystemBarStates(): status bar states unchanged: state: "
                        + state + " state2: " + state2 + " lockTaskMode: " + mLockTaskMode);
            }
            return;
        }
        mStatusBarState = state;
        mStatusBarState2 = state2;
        mLockTaskMode = lockTaskMode;
    }

    @VisibleForTesting
    int getStatusBarState() {
        return mStatusBarState;
    }

    @VisibleForTesting
    int getStatusBarState2() {
        return mStatusBarState2;
    }

    private int getLockTaskModeState() {
        return mContext.getSystemService(ActivityManager.class).getLockTaskModeState();
    }

    private void showAdminSupportDetailsDialog() {
        // TODO(b/205891123): launch AdminSupportDetailsDialog after moving
        // AdminSupportDetailsDialog out of CarSettings since CarSettings is not and should not
        // be allowlisted for lock task mode.
        Toast.makeText(mContext, "This action is unavailable for your profile",
                Toast.LENGTH_LONG).show();
    }

    @VisibleForTesting
    @Nullable
    ViewGroup getBarWindow(String name) {
        Boolean enabled = mSystemBarEnabledMap.get(name);
        return (enabled != null && enabled) ? mCarSystemBarViewFactory.getSystemBarWindow(name)
                : null;
    }

    @VisibleForTesting
    @Nullable
    CarSystemBarViewController getBarViewController(String name, boolean isSetUp) {
        if (mSystemBarEnabledMap.get(name) == null || !mSystemBarEnabledMap.get(name)) {
            return null;
        }

        CarSystemBarViewController viewController = mCarSystemBarViewFactory
                .getSystemBarViewController(name, isSetUp);
        Set<View.OnTouchListener> statusBarTouchListeners = mBarTouchListenersMap.get(name);
        viewController.setSystemBarTouchListeners(
                statusBarTouchListeners != null ? statusBarTouchListeners : new ArraySet<>());

        mSystemBarViewControllerMap.put(name, viewController);
        return viewController;
    }

    @Override
    public void registerBarTouchListener(@NonNull String name, View.OnTouchListener listener) {
        if (mBarTouchListenersMap.get(name) == null) {
            mBarTouchListenersMap.put(name, new ArraySet<>());
        }
        boolean setModified = mBarTouchListenersMap.get(name).add(listener);
        if (setModified && mSystemBarViewControllerMap.get(name) != null) {
            mSystemBarViewControllerMap.get(name)
                    .setSystemBarTouchListeners(mBarTouchListenersMap.get(name));
        }
    }

    /**
     * Shows all of the navigation buttons on the valid instances of {@link CarSystemBarView}.
     */
    @Override
    public void showAllNavigationButtons() {
        showAllNavigationButtons(isDeviceSetupForUser());
    }

    // TODO(b/368407601): can we remove this?
    @VisibleForTesting
    void showAllNavigationButtons(boolean isSetup) {
        checkAllBars(isSetup);
        mSystemBarConfigs.getSystemBarNamesByZOrder().forEach(name -> {
            if (mSystemBarViewControllerMap.get(name) != null) {
                mSystemBarViewControllerMap.get(name)
                        .showButtonsOfType(BUTTON_TYPE_NAVIGATION);
            }
        });
    }

    /**
     * Shows all of the keyguard specific buttons on the valid instances of
     * {@link CarSystemBarView}.
     */
    @Override
    public void showAllKeyguardButtons() {
        showAllKeyguardButtons(isDeviceSetupForUser());
    }

    // TODO(b/368407601): can we remove this?
    @VisibleForTesting
    void showAllKeyguardButtons(boolean isSetUp) {
        checkAllBars(isSetUp);
        mSystemBarConfigs.getSystemBarNamesByZOrder().forEach(name -> {
            if (mSystemBarViewControllerMap.get(name) != null) {
                mSystemBarViewControllerMap.get(name)
                        .showButtonsOfType(BUTTON_TYPE_KEYGUARD);
            }
        });
    }

    /**
     * Shows all of the occlusion state buttons on the valid instances of
     * {@link CarSystemBarView}.
     */
    @Override
    public void showAllOcclusionButtons() {
        showAllOcclusionButtons(isDeviceSetupForUser());
    }

    // TODO(b/368407601): can we remove this?
    @VisibleForTesting
    void showAllOcclusionButtons(boolean isSetUp) {
        checkAllBars(isSetUp);
        mSystemBarConfigs.getSystemBarNamesByZOrder().forEach(name -> {
            if (mSystemBarViewControllerMap.get(name) != null) {
                mSystemBarViewControllerMap.get(name)
                        .showButtonsOfType(BUTTON_TYPE_OCCLUSION);
            }
        });
    }

    private void checkAllBars(boolean isSetUp) {
        mSystemBarViewControllerMap.clear();
        mSystemBarConfigs.getSystemBarNamesByZOrder().forEach(name -> {
            mSystemBarViewControllerMap.put(name, getBarViewController(name, isSetUp));
        });
    }

    /**
     * Invalidate SystemBarConfigs and fetch again from Resources.
     * TODO(): b/260206944, Can remove this after we have a fix for overlaid resources not applied.
     */
    @VisibleForTesting
    void resetSystemBarConfigs() {
        mSystemBarConfigs.resetSystemBarConfigs();
        mCarSystemBarViewFactory.resetSystemBarWindowCache();
        mSystemBarAttachedMap.clear();
        readConfigs();
    }

    protected void updateKeyboardVisibility(boolean isKeyboardVisible) {
        mSystemBarConfigs.getSystemBarNamesByZOrder().forEach(name -> {
            if (mSystemBarConfigs.getHideForKeyboardByName(name)) {
                SystemUiWindow window = mSystemBarConfigs.getWindowForName(name);
                if (window != null) {
                    if (isKeyboardVisible) {
                        window.hide();
                    } else {
                        window.show();
                    }
                }
            }
        });
    }

    protected void createSystemBar() {
        RegisterStatusBarResult result = null;
        try {
            // Register only for Primary User.
            result = mBarService.registerStatusBar(mCommandQueue);

            onSystemBarAttributesChanged(mDisplayId, result.mAppearance, result.mAppearanceRegions,
                    result.mNavbarColorManagedByIme, result.mBehavior,
                    result.mRequestedVisibleTypes,
                    result.mPackageName, result.mLetterboxDetails);

            setImeWindowStatus(mDisplayId, result.mImeWindowVis, result.mImeBackDisposition,
                    result.mShowImeSwitcher);

            // Set up the initial icon state
            int numIcons = result.mIcons.size();
            for (int i = 0; i < numIcons; i++) {
                mCommandQueue.setIcon(result.mIcons.keyAt(i), result.mIcons.valueAt(i));
            }
        } catch (RemoteException ex) {
            ex.rethrowFromSystemServer();
        }

        // Try setting up the initial state of the nav bar if applicable.
        if (result != null) {
            setImeWindowStatus(mDisplayTracker.getDefaultDisplayId(), result.mImeWindowVis,
                    result.mImeBackDisposition, result.mShowImeSwitcher);
        }

        createNavBar();
    }

    protected void createNavBar() {
        buildNavBarWindows();
        buildNavBarContent();
        attachNavBarWindows();
    }

    private void buildNavBarWindows() {
        mSystemBarConfigs.getSystemBarNamesByZOrder().forEach(name -> {
            mSystemBarWindowMap.put(name, getBarWindow(name));
        });

        if (mDisplayCompatToolbarController != null) {
            if (mSystemBarConfigs
                    .isLeftDisplayCompatToolbarEnabled()) {
                mDisplayCompatToolbarController.init(mSystemBarWindowMap.get(LEFT_BAR_NAME));
            } else if (mSystemBarConfigs
                    .isRightDisplayCompatToolbarEnabled()) {
                mDisplayCompatToolbarController.init(mSystemBarWindowMap.get(RIGHT_BAR_NAME));
            }
        }
    }

    private void buildNavBarContent() {
        mSystemBarConfigs.getSystemBarNamesByZOrder().forEach(name -> {
            CarSystemBarViewController viewController = getBarViewController(name,
                    isDeviceSetupForUser());
            ViewGroup systemBarWindow = mSystemBarWindowMap.get(name);
            if (viewController != null && systemBarWindow != null) {
                systemBarWindow.addView(viewController.getView());
            }
        });
    }

    private void attachNavBarWindows() {
        mSystemBarConfigs.getSystemBarNamesByZOrder().forEach(name -> {
            ViewGroup barWindow = mSystemBarWindowMap.get(name);
            boolean isBarAttached = mSystemBarAttachedMap.get(name) != null
                    && mSystemBarAttachedMap.get(name);
            boolean isBarEnabled = mSystemBarConfigs.getEnabledStatusByName(name);
            if (DEBUG) {
                Log.d(TAG, "Name = " + name
                        + ", SystemBarWindow = " + barWindow
                        + ", SystemBarAttached=" + isBarAttached
                        + ", enabled=" + isBarEnabled);
            }
            if (barWindow != null && !isBarAttached && isBarEnabled) {
                SystemUiWindow window = mSystemBarConfigs.getWindowForName(name);
                if (window != null) {
                    window.setRootView(barWindow);
                    mSystemBarAttachedMap.put(name, true);
                }

            }
        });
    }

    private void registerOverlayChangeBroadcastReceiver() {
        if (!configAwareSystemui()) {
            if (DEBUG) {
                Log.d(TAG, "Ignore overlay change for car systemui");
            }
            return;
        }
        IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
        overlayFilter.addDataScheme(OVERLAY_FILTER_DATA_SCHEME);
        overlayFilter.addDataSchemeSpecificPart(mContext.getPackageName(),
                PatternMatcher.PATTERN_LITERAL);
        BroadcastReceiver receiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                mSystemBarAttachedMap.values().forEach(enabled -> {
                    if (enabled) {
                        restartSystemBars();
                    }
                });
            }
        };
        mContext.registerReceiver(receiver, overlayFilter, /* broadcastPermission= */
                null, /* handler= */ null);
    }

    private void resetSystemBarContentIfNecessary() {
        boolean currentUserSetup = mCarDeviceProvisionedController.isCurrentUserSetup();
        boolean currentUserSetupInProgress = mCarDeviceProvisionedController
                .isCurrentUserSetupInProgress();
        if (mIsUserSetupInProgress != currentUserSetupInProgress
                || mDeviceIsSetUpForUser != currentUserSetup) {
            mDeviceIsSetUpForUser = currentUserSetup;
            mIsUserSetupInProgress = currentUserSetupInProgress;
            resetSystemBarContent(/* isProvisionedStateChange= */ true);
        }
    }

    /**
     * Remove all content from navbars and rebuild them. Used to allow for different nav bars
     * before and after the device is provisioned. . Also for change of density and font size.
     */
    private void resetSystemBarContent(boolean isProvisionedStateChange) {
        mCarSystemBarRestartTracker.notifyPendingRestart(/* recreateWindows= */ false,
                isProvisionedStateChange);

        if (!isProvisionedStateChange) {
            mCarSystemBarViewFactory.resetSystemBarViewCache();
        }
        clearSystemBarWindow(/* removeUnusedWindow= */ false);

        buildNavBarContent();
        // If the UI was rebuilt (day/night change or user change) while the keyguard was up we need
        // to correctly respect that state.
        if (mKeyguardStateControllerLazy.get().isShowing()) {
            showAllKeyguardButtons(isDeviceSetupForUser());
        } else {
            showAllNavigationButtons(isDeviceSetupForUser());
        }

        // Upon restarting the Navigation Bar, CarFacetButtonController should immediately apply the
        // selection state that reflects the current task stack.
        mButtonSelectionStateListener.onTaskStackChanged();

        mCarSystemBarRestartTracker.notifyRestartComplete(/* windowRecreated= */ false,
                isProvisionedStateChange);
    }

    private boolean isDeviceSetupForUser() {
        return mDeviceIsSetUpForUser && !mIsUserSetupInProgress;
    }

    private void updateStatusBarAppearance() {
        int numStacks = mAppearanceRegions.length;
        final ArrayList<Rect> lightBarBounds = new ArrayList<>();

        for (int i = 0; i < numStacks; i++) {
            final AppearanceRegion ar = mAppearanceRegions[i];
            if (isLight(ar.getAppearance())) {
                lightBarBounds.add(ar.getBounds());
            }
        }

        // If all stacks are light, all icons become dark.
        if (lightBarBounds.size() == numStacks) {
            mStatusBarIconController.setIconsDarkArea(null);
            mStatusBarIconController.getTransitionsController().setIconsDark(
                    /* dark= */ true, /* animate= */ false);
        } else if (lightBarBounds.isEmpty()) {
            // If no one is light, all icons become white.
            mStatusBarIconController.getTransitionsController().setIconsDark(
                    /* dark= */ false, /* animate= */ false);
        } else {
            // Not the same for every stack, update icons in area only.
            mStatusBarIconController.setIconsDarkArea(lightBarBounds);
            mStatusBarIconController.getTransitionsController().setIconsDark(
                    /* dark= */ true, /* animate= */ false);
        }
    }

    private static boolean isLight(int appearance) {
        return (appearance & APPEARANCE_LIGHT_STATUS_BARS) != 0;
    }

    private void handleTransientChanged() {
        updateStatusBarMode(mStatusBarTransientShown ? MODE_SEMI_TRANSPARENT : MODE_TRANSPARENT);
        updateNavBarMode(mNavBarTransientShown ? MODE_SEMI_TRANSPARENT : MODE_TRANSPARENT);
    }

    // Returns true if the status bar mode has changed.
    private boolean updateStatusBarMode(int barMode) {
        if (mStatusBarMode != barMode) {
            mStatusBarMode = barMode;
            mAutoHideController.touchAutoHide();
            return true;
        }
        return false;
    }

    // Returns true if the nav bar mode has changed.
    private boolean updateNavBarMode(int barMode) {
        if (mSystemBarMode != barMode) {
            mSystemBarMode = barMode;
            mAutoHideController.touchAutoHide();
            return true;
        }
        return false;
    }

    @VisibleForTesting
    void restartSystemBars() {
        mCarSystemBarRestartTracker.notifyPendingRestart(/* recreateWindows= */ true,
                /* provisionedStateChanged= */ false);

        resetSystemBarConfigs();
        clearSystemBarWindow(/* removeUnusedWindow= */ true);
        buildNavBarWindows();
        buildNavBarContent();
        attachNavBarWindows();

        mCarSystemBarRestartTracker.notifyRestartComplete(/* windowRecreated= */ true,
                /* provisionedStateChanged= */ false);
    }

    private void clearSystemBarWindow(boolean removeUnusedWindow) {
        mSystemBarConfigs.getSystemBarNamesByZOrder().forEach(name -> {
            ViewGroup barWindow = getBarWindow(name);
            if (barWindow != null) {
                barWindow.removeAllViews();
                if (removeUnusedWindow) {
                    SystemUiWindow window = mSystemBarConfigs.getWindowForName(name);
                    if (window != null) {
                        window.removeRootViewImmediate();
                    }
                    mSystemBarAttachedMap.put(name, false);
                }
                mSystemBarViewControllerMap.remove(name);
            }
        });
    }

    private void clearTransient() {
        if (mStatusBarTransientShown) {
            mStatusBarTransientShown = false;
            handleTransientChanged();
        }
        if (mNavBarTransientShown) {
            mNavBarTransientShown = false;
            handleTransientChanged();
        }
    }

    @VisibleForTesting
    boolean isStatusBarTransientShown() {
        return mStatusBarTransientShown;
    }

    @VisibleForTesting
    boolean isNavBarTransientShown() {
        return mNavBarTransientShown;
    }
}
CarSystemBarControllerImpl.java

CarSystemBarControllerImpl

核心类,实现了CommandQueue.Callbacks,监听系统 SystemBar 相关状态(来自 StatusBarManagerService)。

1、onSystemBarAttributesChanged

image

触发时机:应用窗口焦点变化或主动设置 appearance(原生接口 WindowInsetsController 的 isAppearanceLightStatusBars 跟 isAppearanceLightNavigationBars)。

SystemUI onSystemBarAttributesChanged 接口刷新状态,通过参数 appearance 来判断状态变更。

528 (0x210) = APPEARANCE_LIGHT_STATUS_BARS = 8

536 (0x218) = APPEARANCE_LIGHT_NAVIGATION_BARS = 16

SysuiDarkIconDispatcher 内部维护了一个 DarkReceiver 监听器列表。
Statusbar 上的图标实现了 DarkIconDispatcher.DarkReceiver 接口,并注册到 DarkIconDispatcher 中。
SysuiDarkIconDispatcher 会向所有注册的 DarkReceiver 回调 onDarkChanged(tintAreas, intensity, color),动态改变图标的 Tint 颜色(黑/白切换)。

原生只针对 statusbar 做更新,navbar 不做处理,为保证驾驶过程中的视线一致,所以 NavBar 图标不会因为深浅色而频繁切换,状态主要通过选中态,禁用态。

2、disable

触发时机:用户设置引导阶段,车辆行驶限制场景。

<uses-permission android.permission.STATUS_BAR />

用于禁用SystemBar中功能(比如Home,下拉屏等),App 必须是系统应用或拥有特权签名,且在 AndroidManifest.xml 中声明权限。

StatusBarManager statusBarManager = (StatusBarManager) getSystemService(Context.STATUS_BAR_SERVICE);

// 1. 禁用通知栏下拉 (DISABLE_EXPAND) 和 Home / Recent 键
int disableFlags = StatusBarManager.DISABLE_EXPAND
                 | StatusBarManager.DISABLE_HOME
                 | StatusBarManager.DISABLE_RECENT;

// 2. 禁用快捷设置 QuickSettings 或 Notification Shade (disable2)
int disable2Flags = StatusBarManager.DISABLE2_QUICK_SETTINGS
                  | StatusBarManager.DISABLE2_NOTIFICATION_SHADE;

// 3. 调用系统 API 禁用
statusBarManager.disable(disableFlags);
// 如果需要 disable2 功能:
statusBarManager.disable2(disable2Flags);
statusBarManager.disable

或者

// 1. 在 DeviceAdmin / DevicePolicyManager 中将该 package 加入 Allowlist
// 2. 在 Activity 中启动 LockTask 模式
activity.startLockTask();

// 退出 LockTask 模式
activity.stopLockTask();
LockTask

 

3、setWindowStatus

触发时机:WMS 判定系统栏窗口状态变化。

用于控制 SystenBar 状态。

4、setImeWindowStatus

image

触发时机:软键盘弹出/收起。

用于更新键盘弹出时相关状态。

5、showTransient

瞬态处理,app侧通过 WindowCompat.getInsetsController 来控制 SystemBar 的状态,可以通过 systemBarsBehavior 的 WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE 标识来开启瞬态模式。

触发时机:transient 的状态取决于原生系统的全局手势,当用户从屏幕边缘滑动时,wms 会通知 SystemUI,发送 showTransient 回调。

image

image

AutoHideController:瞬态触发时,系统通过 handleTransientChanged 方法触发 auto 的 touchAutoHide,用于瞬态的过渡显示。

用户边缘滑动
  → WMS InsetsPolicy.showTransient()
  → StatusBarManagerInternal.showTransient()
  → CommandQueue.Callbacks.showTransient()
  → CarSystemBarControllerImpl.showTransient()
  → mNavBarTransientShown = true
  → handleTransientChanged() → updateNavBarMode() → AutoHideController.touchAutoHide()
  → [超时] AutoHideController.hideTransientBars()
  → CarSystemBarControllerImpl 中 AutoHideUiElement.hide() → clearTransient()

6、abortTransient

触发场景:窗口切换等。

清除瞬态,假设处于常显,此时系统需要永久显示时用于中断瞬态,防止状态异常。

如果需要主动清除,使用 WindowManagerService.hideTransientBars,但不会触发 abort。

7、showToast

触发时机:Toast.makeText().show()

由于 Toast 定位是低优先级提示,但会造成驾驶分心导致安全问题,所以为了考虑提示的泛滥,Toast 提示不在由应用决定,统一在 SystemUI 管理

可以通过配置 config_restrictedToastsPackageNameAllowList 来进行配置,或者是系统应用。

SystemBar 创建流程

车机系统中,SystemBar 默认颜色固定。

<?xml version='1.0' encoding='UTF-8'?>
<!-- Copyright (C) 2025 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="?oemColorSurface" />
</shape>
nav_bar_background
<?xml version='1.0' encoding='UTF-8'?>
<!-- Copyright (C) 2025 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="?oemColorSurface" />
</shape>
status_bar_background

默认色为系统的 Surface 基础色(通常为不透明深色/灰色)。

image

可以通过平台 RRO (Runtime Resource Overlay) 将 drawable/nav_bar_background 替换(packages/apps/Car/SystemUI/samples/SystemBarNavBarTranslucent)。

SystemBarConfigsImpl:

原生路径布局参考:

<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2020 The Android Open Source Project.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<com.android.systemui.car.systembar.CarSystemBarView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/nav_bar_background"
    android:gravity="center"
    android:orientation="horizontal">

    <LinearLayout
        android:id="@+id/nav_buttons"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="@dimen/car_nav_buttons_margin"
        android:layoutDirection="ltr"
        android:gravity="center">

        <com.android.systemui.car.systembar.CarSystemBarButton
            android:id="@+id/home"
            android:contentDescription="@string/system_bar_home_label"
            style="@style/SystemBarButton"
            systemui:componentNames="@string/config_homeComponentName"
            systemui:highlightWhenSelected="true"
            systemui:icon="@drawable/car_ic_home"
            systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
            systemui:systemBarDisableFlags="home"
            systemui:controller="com.android.systemui.car.systembar.HomeButtonController"/>

        <com.android.systemui.car.systembar.CarSystemBarButton
            android:id="@+id/passenger_home"
            android:contentDescription="@string/system_bar_home_label"
            android:visibility="gone"
            style="@style/SystemBarButton"
            systemui:highlightWhenSelected="true"
            systemui:icon="@drawable/car_ic_home"
            systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
            systemui:systemBarDisableFlags="home"
            systemui:controller="com.android.systemui.car.systembar.PassengerHomeButtonController"/>

        <Space
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"/>

        <com.android.systemui.car.hvac.TemperatureControlView
            android:id="@+id/driver_hvac"
            style="@style/TemperatureControlView"
            systemui:hvacAreaId="49"
            systemui:controller="com.android.systemui.car.hvac.TemperatureControlViewController">
            <include layout="@layout/adjustable_temperature_view"/>
        </com.android.systemui.car.hvac.TemperatureControlView>

        <Space
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"/>

        <com.android.systemui.car.systembar.AppGridButton
            android:id="@+id/grid_nav"
            android:contentDescription="@string/system_bar_applications_label"
            style="@style/SystemBarButton"
            systemui:componentNames="@string/config_appGridComponentName"
            systemui:highlightWhenSelected="true"
            systemui:icon="@drawable/car_ic_apps"
            systemui:intent="@string/system_bar_app_drawer_intent"
            systemui:clearBackStack="@bool/config_enableClearBackStack"
            systemui:systemBarDisableFlags="home"/>

        <com.android.systemui.car.systembar.element.layout.CarSystemBarFrameLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="@dimen/dock_container_margin"
            systemui:controller="com.android.systemui.car.systembar.DockViewControllerWrapper">
            <com.android.car.docklib.view.DockView
                android:id="@+id/dock"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
        </com.android.systemui.car.systembar.element.layout.CarSystemBarFrameLayout>

        <com.android.systemui.car.systembar.CarSystemBarButton
            android:id="@+id/control_center_nav"
            android:contentDescription="@string/system_bar_control_center_label"
            style="@style/SystemBarButton"
            android:visibility="gone"
            systemui:highlightWhenSelected="true"
            systemui:icon="@drawable/car_ic_control_center"
            systemui:intent="intent:#Intent;action=android.intent.action.MAIN;package=com.android.car.multidisplay.controlcenter;component=com.android.car.multidisplay.controlcenter/.ControlCenterActivity;B.BOTTOM_BAR_LAUNCH=true;end"
            systemui:componentNames="com.android.car.multidisplay.controlcenter/.ControlCenterActivity"
            systemui:controller="com.android.systemui.car.systembar.ControlCenterButtonController"/>

        <Space
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"/>

        <com.android.systemui.car.hvac.TemperatureControlView
            android:id="@+id/passenger_hvac"
            style="@style/TemperatureControlView"
            systemui:hvacAreaId="68"
            systemui:controller="com.android.systemui.car.hvac.TemperatureControlViewController">
            <include layout="@layout/adjustable_temperature_view"/>
        </com.android.systemui.car.hvac.TemperatureControlView>

        <Space
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"/>

        <com.android.systemui.car.systembar.AssistantButton
            android:id="@+id/assistant"
            android:contentDescription="@string/system_bar_assistant_label"
            style="@style/SystemBarButton"
            systemui:highlightWhenSelected="true"
            systemui:icon="@drawable/ic_mic_light"/>

    </LinearLayout>

    <LinearLayout
        android:id="@+id/lock_screen_nav_buttons"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:layoutDirection="ltr"
        android:paddingEnd="@dimen/car_keyline_1"
        android:paddingStart="@dimen/car_keyline_1"
        android:visibility="gone"/>

    <LinearLayout
        android:id="@+id/occlusion_buttons"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:layoutDirection="ltr"
        android:paddingEnd="@dimen/car_keyline_1"
        android:paddingStart="@dimen/car_keyline_1"
        android:visibility="gone">
        <com.android.systemui.car.systembar.CarSystemBarButton
            android:id="@+id/home"
            android:contentDescription="@string/system_bar_home_label"
            style="@style/SystemBarButton"
            systemui:componentNames="@string/config_homeComponentName"
            systemui:highlightWhenSelected="true"
            systemui:icon="@drawable/car_ic_home"
            systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
            systemui:systemBarDisableFlags="home"
            systemui:controller="com.android.systemui.car.systembar.HomeButtonController"/>
    </LinearLayout>
</com.android.systemui.car.systembar.CarSystemBarView>
packages/apps/Car/SystemUI/res/layout/car_bottom_system_bar.xml
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2018 The Android Open Source Project
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License
  -->

<com.android.systemui.car.systembar.CarSystemBarView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:id="@+id/car_top_bar"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/status_bar_background"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layoutDirection="ltr">

        <include layout="@layout/qc_status_icons_horizontal"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_centerVertical="true"
            android:layout_alignParentStart="true" />

        <FrameLayout
            android:id="@+id/clock_container"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:paddingStart="@dimen/car_padding_2"
            android:paddingEnd="@dimen/car_padding_2"
            android:layout_centerInParent="true">
            <com.android.systemui.statusbar.policy.Clock
                android:id="@+id/clock"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:elevation="5dp"
                android:singleLine="true"
                style="@style/SystemBarClockText"
                systemui:amPmStyle="gone"
                />
        </FrameLayout>

        <include layout="@layout/read_only_status_icons"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@id/clock_container"/>

        <com.android.systemui.car.systembar.CarSystemBarButton
            android:id="@+id/notifications"
            android:contentDescription="@string/system_bar_notifications_label"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            style="@style/TopBarButton"
            android:layout_toLeftOf="@id/share_to_app_privacy_chip"
            systemui:highlightWhenSelected="true"
            systemui:icon="@drawable/car_ic_notification_dock"
            systemui:longIntent="intent:#Intent;action=com.android.car.bugreport.action.BUG_REPORT;end"
            systemui:systemBarDisableFlags="notificationIcons"
            systemui:controller="com.android.systemui.car.notification.NotificationButtonController"/>

        <include layout="@layout/share_to_app_privacy_chip"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_centerVertical="true"
            android:layout_toLeftOf="@id/cast_to_other_device_privacy_chip" />

        <include layout="@layout/cast_to_other_device_privacy_chip"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_centerVertical="true"
            android:layout_toLeftOf="@id/camera_privacy_chip" />

        <include layout="@layout/camera_privacy_chip"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_centerVertical="true"
            android:layout_toLeftOf="@id/mic_privacy_chip" />

        <include layout="@layout/mic_privacy_chip"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_centerVertical="true"
            android:layout_toLeftOf="@id/user_name_container"
            android:contentDescription="@string/system_bar_mic_privacy_chip" />

        <include layout="@layout/user_name_container"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_alignParentEnd="true" />
    </RelativeLayout>
</com.android.systemui.car.systembar.CarSystemBarView>
packages/apps/Car/SystemUI/res/layout/car_top_system_bar.xml
    <!-- Configure which system bars should be displayed. -->
    <bool name="config_enableTopSystemBar">true</bool>
    <bool name="config_enableLeftSystemBar">false</bool>
    <bool name="config_enableRightSystemBar">false</bool>
    <bool name="config_enableBottomSystemBar">true</bool>

    <!-- Configure the type of each system bar. -->
    <!--    STATUS_BAR = 0-->
    <!--    NAVIGATION_BAR = 1-->
    <integer name="config_topSystemBarType">0</integer>
    <integer name="config_leftSystemBarType">0</integer>
    <integer name="config_rightSystemBarType">1</integer>
    <integer name="config_bottomSystemBarType">1</integer>
config.xml

init 中 调用 readConfigs() 根据系统配置来创建 InternalSystemBarWindow,然后存储到 mSystemBars map 集合中。

InternalSystemBarWindow:

继承自 SystemBarWindow 接口,接口持有 SystemBarConfiguration,用于持有 SystemBar 相关配置信息。

/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.systemui.car.wm.scalableui.systemwindow

import com.android.systemui.car.wm.scalableui.configuration.SystemBarConfiguration

/**
 * An implementation of [SystemUiWindow] specifically for system bars.
 */
interface SystemBarWindow : SystemUiWindow {
    /**
     * Returns the [SystemBarConfiguration] for this window.
     */
    fun getConfiguration(): SystemBarConfiguration
}

/**
 * Pre-defined HUN z order
 */
const val HUN_Z_ORDER: Int = 10
SystemBarWindow.kt
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.car.wm.scalableui.configuration

import android.os.Bundle
import com.android.car.scalableui.loader.xml.SystemBarTagXmlParser
import com.android.car.scalableui.loader.xml.SystemBarTagXmlParser.TYPE_NAVIGATION
import com.android.systemui.car.systembar.CarSystemBarController.NAVIGATION_BAR
import com.android.systemui.car.systembar.CarSystemBarController.STATUS_BAR
import com.android.systemui.car.wm.scalableui.panel.panelupdates.PanelUpdateConsumer
import com.android.systemui.car.wm.scalableui.systemwindow.HUN_Z_ORDER
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.util.Optional

/**
 * An object that stores configuration values for system bars.
 */
class SystemBarConfiguration(
    /** a [Bundle] that contains all of the configuration values */
    val configuration: Bundle,
    /** System bar name/id **/
    val name: String,
    /** System bar {@link InsetsFrameProvider}'s index **/
    val index: Int,
    /** WindowInsets.Type.mandatorySystemGestures {@link InsetsFrameProvider}'s index **/
    val mandatorySystemGestureIndexOffset: Int
) {

    @AssistedInject
    constructor(
        consumer: Optional<PanelUpdateConsumer>,
        @Assisted name: String,
        @Assisted("index") index: Int,
        @Assisted("indexOffset") mandatorySystemGestureIndexOffset: Int
    ) : this(getMetadata(consumer, name), name, index, mandatorySystemGestureIndexOffset)

    val type: Int
        /**
         * @return System bar type name
         */
        get() = when (configuration.getString(SystemBarTagXmlParser.TYPE_ATTRIBUTE)) {
            TYPE_NAVIGATION -> {
                NAVIGATION_BAR
            }
            else -> {
                STATUS_BAR
            }
        }

    val zOrder: Int
        /**
         * @return the relative Z-order of the SystemBar
         */
        get() = configuration.getInt(SystemBarTagXmlParser.BAR_Z_ORDER_ATTRIBUTE)

    val isAboveHun: Boolean
        /**
         * @return `true` if SystemBar should be displayed above HUN
         */
        get() = HUN_Z_ORDER >= zOrder

    val isHiddenForKeyboard: Boolean
        /**
         * @return `true` if this system bar should be hidden when keyboard is visible.
         */
        get() = configuration.getBoolean(SystemBarTagXmlParser.HIDE_FOR_KEYBOARD_ATTRIBUTE)

    @AssistedFactory
    interface Factory {
        fun create(
            @Assisted id: String,
            @Assisted("index") index: Int,
            @Assisted("indexOffset") mandatorySystemGestureIndexOffset: Int
        ): SystemBarConfiguration
    }

    companion object {
        private fun getMetadata(consumer: Optional<PanelUpdateConsumer>, id: String): Bundle {
            if (consumer.isEmpty) {
                throw IllegalStateException("PanelUpdateConsumer must be present")
            }
            val metadata = consumer.get().getPanelControllerMetadata(id)
            checkNotNull(metadata) { "PanelControllerMetadata must be present" }
            return metadata.configurations
        }
    }
}
SystemBarConfiguration.kt

attachNavBarWindows()

CarSystemBarControllerImpl 中在 init 时调用了 createSystemBar() → attachNavBarWindows()

通过 SystemBarConfigs 从集合中拿到具体 InternalSystemBarWindow,调用 SystemUiWindow 将 SystemBar 持有。

这里将布局跟逻辑控制分离,无论后续 SystemBar 怎么调整,都不用变更状态控制的逻辑(比如直接更换成 换成 Scalable UI 布局)。

SystemBar 适配

在 Android 15 (API 35) 中,官方明确将 Window.setNavigationBarColor() 和 setStatusBarColor() 标记为过时(Deprecated)。

官方推行 Edge-to-Edge(边到边/全屏沉浸式) 规范,默认为透明,navigationBar 的背景将交给 app 适配,应用自己管理被系统栏遮挡区域的内容与视觉效果。

SystemUI 中的 SystemBar 位于独立系统窗口,层级在应用窗口之上,应用不需要管理,系统会在 Activity 打开时,DecorView 会获取窗口标识,如果该窗口开启了系统栏绘制标记(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS),且没有将导航栏背景设为全透明,DecorView 会自动在应用窗口动态创建一个 View(R.id.navigationBarBackground)。

图标颜色适配:

WindowCompat.getInsetsController(window, window.decorView).apply {
            isAppearanceLightStatusBars = true // false是深色(白色图标),true是浅色(黑色图标)
            isAppearanceLightNavigationBars = true
        }
isAppearanceLightStatusBars

StatusBra:由 app 的 WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars 控制。

注意不要遗漏了 onConfigurationChanged。

isAppearanceLightStatusBars → WMS/StatusBarManagerService → CommandQueue → onSystemBarAttributesChanged() → updateStatusBarAppearance() → setIconsDark

NavBar:跟随系统深浅色切换。

透明背景适配:

window.decorView.findViewById<View>(android.R.id.navigationBarBackground)?.apply {
                setBackgroundColor(Color.TRANSPARENT)
        }
navigationBarBackground

全屏显示适配:

WindowCompat.getInsetsController(window, window.decorView).apply {
            hide(WindowInsetsCompat.Type.systemBars())
        }
hide systemBar

瞬态显示适配:

WindowCompat.getInsetsController(window, window.decorView).apply {
            hide(WindowInsetsCompat.Type.systemBars())
            systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
        }
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE

沉浸态适配(EdgeToEdge):

androidx.activity.enableEdgeToEdge()
// WindowCompat.enableEdgeToEdge(window)
// WindowCompat.setDecorFitsSystemWindows(window, false)
enableEdgeToEdge

Android 15(API 35)默认开启,但为了兼容旧版本设备和保持全局视觉一致性,建议调用。

posted on 2026-11-09 15:38  翻滚的咸鱼  阅读(19)  评论(0)    收藏  举报