系统版本:基于 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(); } }
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
核心类,实现了CommandQueue.Callbacks,监听系统 SystemBar 相关状态(来自 StatusBarManagerService)。
1、onSystemBarAttributesChanged

触发时机:应用窗口焦点变化或主动设置 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);
或者
// 1. 在 DeviceAdmin / DevicePolicyManager 中将该 package 加入 Allowlist // 2. 在 Activity 中启动 LockTask 模式 activity.startLockTask(); // 退出 LockTask 模式 activity.stopLockTask();
3、setWindowStatus
触发时机:WMS 判定系统栏窗口状态变化。
用于控制 SystenBar 状态。
4、setImeWindowStatus

触发时机:软键盘弹出/收起。
用于更新键盘弹出时相关状态。
5、showTransient
瞬态处理,app侧通过 WindowCompat.getInsetsController 来控制 SystemBar 的状态,可以通过 systemBarsBehavior 的 WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE 标识来开启瞬态模式。
触发时机:transient 的状态取决于原生系统的全局手势,当用户从屏幕边缘滑动时,wms 会通知 SystemUI,发送 showTransient 回调。


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>
<?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>
默认色为系统的 Surface 基础色(通常为不透明深色/灰色)。

可以通过平台 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>
<?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>
<!-- 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>
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
/* * 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 } } }
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 }
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)
}
全屏显示适配:
WindowCompat.getInsetsController(window, window.decorView).apply {
hide(WindowInsetsCompat.Type.systemBars())
}
瞬态显示适配:
WindowCompat.getInsetsController(window, window.decorView).apply { hide(WindowInsetsCompat.Type.systemBars()) systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE }
沉浸态适配(EdgeToEdge):
androidx.activity.enableEdgeToEdge() // WindowCompat.enableEdgeToEdge(window) // WindowCompat.setDecorFitsSystemWindows(window, false)
Android 15(API 35)默认开启,但为了兼容旧版本设备和保持全局视觉一致性,建议调用。
浙公网安备 33010602011771号