/* * 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 static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.InsetsState.containsType; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT; import android.app.StatusBarManager.Disable2Flags; import android.app.StatusBarManager.DisableFlags; import android.content.Context; import android.inputmethodservice.InputMethodService; import android.os.IBinder; import android.os.RemoteException; import android.view.Display; import android.view.InsetsVisibilities; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsetsController; import android.view.WindowManager; import androidx.annotation.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.RegisterStatusBarResult; import com.android.internal.view.AppearanceRegion; import com.android.systemui.SystemUI; import com.android.systemui.car.CarDeviceProvisionedController; import com.android.systemui.car.CarDeviceProvisionedListener; import com.android.systemui.car.hvac.HvacController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; 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.BarTransitions; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy; import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.DelayableExecutor; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.concurrent.Executor; import javax.inject.Inject; import dagger.Lazy; /** Navigation bars customized for the automotive use case. */ public class CarSystemBar extends SystemUI implements CommandQueue.Callbacks { private final CarSystemBarController mCarSystemBarController; private final SysuiDarkIconDispatcher mStatusBarIconController; private final WindowManager mWindowManager; private final CarDeviceProvisionedController mCarDeviceProvisionedController; private final CommandQueue mCommandQueue; private final AutoHideController mAutoHideController; private final ButtonSelectionStateListener mButtonSelectionStateListener; private final DelayableExecutor mExecutor; private final Executor mUiBgExecutor; private final IStatusBarService mBarService; private final Lazy mKeyguardStateControllerLazy; private final Lazy mIconPolicyLazy; private final HvacController mHvacController; private final int mDisplayId; private final SystemBarConfigs mSystemBarConfigs; private StatusBarSignalPolicy mSignalPolicy; private ActivityManagerWrapper mActivityManagerWrapper; // If the nav bar should be hidden when the soft keyboard is visible. private boolean mHideTopBarForKeyboard; private boolean mHideLeftBarForKeyboard; private boolean mHideRightBarForKeyboard; private boolean mHideBottomBarForKeyboard; private boolean mBottomNavBarVisible; // Nav bar views. private ViewGroup mTopSystemBarWindow; private ViewGroup mBottomSystemBarWindow; private ViewGroup mLeftSystemBarWindow; private ViewGroup mRightSystemBarWindow; private CarSystemBarView mTopSystemBarView; private CarSystemBarView mBottomSystemBarView; private CarSystemBarView mLeftSystemBarView; private CarSystemBarView mRightSystemBarView; // 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; @Inject public CarSystemBar(Context context, CarSystemBarController carSystemBarController, // 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, @UiBackground Executor uiBgExecutor, IStatusBarService barService, Lazy keyguardStateControllerLazy, Lazy iconPolicyLazy, StatusBarSignalPolicy signalPolicy, HvacController hvacController, SystemBarConfigs systemBarConfigs ) { super(context); mCarSystemBarController = carSystemBarController; mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher; mWindowManager = windowManager; mCarDeviceProvisionedController = deviceProvisionedController; mCommandQueue = commandQueue; mAutoHideController = autoHideController; mButtonSelectionStateListener = buttonSelectionStateListener; mExecutor = mainExecutor; mUiBgExecutor = uiBgExecutor; mBarService = barService; mKeyguardStateControllerLazy = keyguardStateControllerLazy; mIconPolicyLazy = iconPolicyLazy; mHvacController = hvacController; mSystemBarConfigs = systemBarConfigs; mSignalPolicy = signalPolicy; mDisplayId = context.getDisplayId(); } @Override public void start() { // Set initial state. mHideTopBarForKeyboard = mSystemBarConfigs.getHideForKeyboardBySide(SystemBarConfigs.TOP); mHideBottomBarForKeyboard = mSystemBarConfigs.getHideForKeyboardBySide( SystemBarConfigs.BOTTOM); mHideLeftBarForKeyboard = mSystemBarConfigs.getHideForKeyboardBySide(SystemBarConfigs.LEFT); mHideRightBarForKeyboard = mSystemBarConfigs.getHideForKeyboardBySide( SystemBarConfigs.RIGHT); mBottomNavBarVisible = false; // Connect into the status bar manager service mCommandQueue.addCallback(this); RegisterStatusBarResult result = null; try { result = mBarService.registerStatusBar(mCommandQueue); } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } onSystemBarAttributesChanged(mDisplayId, result.mAppearance, result.mAppearanceRegions, result.mNavbarColorManagedByIme, result.mBehavior, result.mRequestedVisibilities, result.mPackageName); // StatusBarManagerService has a back up of IME token and it's restored here. setImeWindowStatus(mDisplayId, result.mImeToken, 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)); } 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(() -> restartNavBarsIfNecessary()); } @Override public void onUserSetupChanged() { mExecutor.execute(() -> restartNavBarsIfNecessary()); } @Override public void onUserSwitched() { mExecutor.execute(() -> restartNavBarsIfNecessary()); } }); createSystemBar(result); mActivityManagerWrapper = ActivityManagerWrapper.getInstance(); mActivityManagerWrapper.registerTaskStackListener(mButtonSelectionStateListener); mActivityManagerWrapper.registerTaskStackListener(new TaskStackChangeListener() { @Override public void onLockTaskModeChanged(int mode) { mCarSystemBarController.refreshSystemBarByLockTaskFeatures(); } }); // 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(); }); } private void restartNavBarsIfNecessary() { boolean currentUserSetup = mCarDeviceProvisionedController.isCurrentUserSetup(); boolean currentUserSetupInProgress = mCarDeviceProvisionedController .isCurrentUserSetupInProgress(); if (mIsUserSetupInProgress != currentUserSetupInProgress || mDeviceIsSetUpForUser != currentUserSetup) { mDeviceIsSetUpForUser = currentUserSetup; mIsUserSetupInProgress = currentUserSetupInProgress; restartNavBars(); } } /** * 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 restartNavBars() { // remove and reattach all components such that we don't keep a reference to unused ui // elements mCarSystemBarController.removeAll(); if (mTopSystemBarWindow != null) { mTopSystemBarWindow.removeAllViews(); mHvacController.unregisterViews(mTopSystemBarView); mTopSystemBarView = null; } if (mBottomSystemBarWindow != null) { mBottomSystemBarWindow.removeAllViews(); mHvacController.unregisterViews(mBottomSystemBarView); mBottomSystemBarView = null; } if (mLeftSystemBarWindow != null) { mLeftSystemBarWindow.removeAllViews(); mHvacController.unregisterViews(mLeftSystemBarView); mLeftSystemBarView = null; } if (mRightSystemBarWindow != null) { mRightSystemBarWindow.removeAllViews(); mHvacController.unregisterViews(mRightSystemBarView); mRightSystemBarView = null; } 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()) { mCarSystemBarController.showAllKeyguardButtons(isDeviceSetupForUser()); } else { mCarSystemBarController.showAllNavigationButtons(isDeviceSetupForUser()); } // Upon restarting the Navigation Bar, CarFacetButtonController should immediately apply the // selection state that reflects the current task stack. mButtonSelectionStateListener.onTaskStackChanged(); } private boolean isDeviceSetupForUser() { return mDeviceIsSetUpForUser && !mIsUserSetupInProgress; } private void createSystemBar(RegisterStatusBarResult result) { buildNavBarWindows(); buildNavBarContent(); attachNavBarWindows(); // Try setting up the initial state of the nav bar if applicable. if (result != null) { setImeWindowStatus(Display.DEFAULT_DISPLAY, result.mImeToken, result.mImeWindowVis, result.mImeBackDisposition, result.mShowImeSwitcher); } } private void buildNavBarWindows() { mTopSystemBarWindow = mCarSystemBarController.getTopWindow(); mBottomSystemBarWindow = mCarSystemBarController.getBottomWindow(); mLeftSystemBarWindow = mCarSystemBarController.getLeftWindow(); mRightSystemBarWindow = mCarSystemBarController.getRightWindow(); } private void buildNavBarContent() { mTopSystemBarView = mCarSystemBarController.getTopBar(isDeviceSetupForUser()); if (mTopSystemBarView != null) { mSystemBarConfigs.insetSystemBar(SystemBarConfigs.TOP, mTopSystemBarView); mHvacController.registerHvacViews(mTopSystemBarView); mTopSystemBarWindow.addView(mTopSystemBarView); } mBottomSystemBarView = mCarSystemBarController.getBottomBar(isDeviceSetupForUser()); if (mBottomSystemBarView != null) { mSystemBarConfigs.insetSystemBar(SystemBarConfigs.BOTTOM, mBottomSystemBarView); mHvacController.registerHvacViews(mBottomSystemBarView); mBottomSystemBarWindow.addView(mBottomSystemBarView); } mLeftSystemBarView = mCarSystemBarController.getLeftBar(isDeviceSetupForUser()); if (mLeftSystemBarView != null) { mSystemBarConfigs.insetSystemBar(SystemBarConfigs.LEFT, mLeftSystemBarView); mHvacController.registerHvacViews(mLeftSystemBarView); mLeftSystemBarWindow.addView(mLeftSystemBarView); } mRightSystemBarView = mCarSystemBarController.getRightBar(isDeviceSetupForUser()); if (mRightSystemBarView != null) { mSystemBarConfigs.insetSystemBar(SystemBarConfigs.RIGHT, mRightSystemBarView); mHvacController.registerHvacViews(mRightSystemBarView); mRightSystemBarWindow.addView(mRightSystemBarView); } } private void attachNavBarWindows() { mSystemBarConfigs.getSystemBarSidesByZOrder().forEach(this::attachNavBarBySide); } private void attachNavBarBySide(int side) { switch (side) { case SystemBarConfigs.TOP: if (mTopSystemBarWindow != null) { mWindowManager.addView(mTopSystemBarWindow, mSystemBarConfigs.getLayoutParamsBySide(SystemBarConfigs.TOP)); } break; case SystemBarConfigs.BOTTOM: if (mBottomSystemBarWindow != null && !mBottomNavBarVisible) { mBottomNavBarVisible = true; mWindowManager.addView(mBottomSystemBarWindow, mSystemBarConfigs.getLayoutParamsBySide(SystemBarConfigs.BOTTOM)); } break; case SystemBarConfigs.LEFT: if (mLeftSystemBarWindow != null) { mWindowManager.addView(mLeftSystemBarWindow, mSystemBarConfigs.getLayoutParamsBySide(SystemBarConfigs.LEFT)); } break; case SystemBarConfigs.RIGHT: if (mRightSystemBarWindow != null) { mWindowManager.addView(mRightSystemBarWindow, mSystemBarConfigs.getLayoutParamsBySide(SystemBarConfigs.RIGHT)); } break; default: return; } } /** * 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_automotiveHideNavBarForKeyboard}. */ @Override public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher) { if (mContext.getDisplayId() != displayId) { return; } boolean isKeyboardVisible = (vis & InputMethodService.IME_VISIBLE) != 0; if (mHideTopBarForKeyboard) { mCarSystemBarController.setTopWindowVisibility( isKeyboardVisible ? View.GONE : View.VISIBLE); } if (mHideBottomBarForKeyboard) { mCarSystemBarController.setBottomWindowVisibility( isKeyboardVisible ? View.GONE : View.VISIBLE); } if (mHideLeftBarForKeyboard) { mCarSystemBarController.setLeftWindowVisibility( isKeyboardVisible ? View.GONE : View.VISIBLE); } if (mHideRightBarForKeyboard) { mCarSystemBarController.setRightWindowVisibility( isKeyboardVisible ? View.GONE : View.VISIBLE); } } @Override public void onSystemBarAttributesChanged( int displayId, @WindowInsetsController.Appearance int appearance, AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, @WindowInsetsController.Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName) { 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(); } mCarSystemBarController.refreshSystemBarByLockTaskFeatures(); } @Override public void disable(int displayId, @DisableFlags int state1, @Disable2Flags int state2, boolean animate) { if (displayId != mDisplayId) { return; } mCarSystemBarController.setStatusBarState(state1); } private void updateStatusBarAppearance() { int numStacks = mAppearanceRegions.length; int numLightStacks = 0; // We can only have maximum one light stack. int indexLightStack = -1; for (int i = 0; i < numStacks; i++) { if (isLight(mAppearanceRegions[i].getAppearance())) { numLightStacks++; indexLightStack = i; } } // If all stacks are light, all icons become dark. if (numLightStacks == numStacks) { mStatusBarIconController.setIconsDarkArea(null); mStatusBarIconController.getTransitionsController().setIconsDark( /* dark= */ true, /* animate= */ false); } else if (numLightStacks == 0) { // 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( mAppearanceRegions[indexLightStack].getBounds()); mStatusBarIconController.getTransitionsController().setIconsDark( /* dark= */ true, /* animate= */ false); } } private static boolean isLight(int appearance) { return (appearance & APPEARANCE_LIGHT_STATUS_BARS) != 0; } @Override public void showTransient(int displayId, int[] types) { if (displayId != mDisplayId) { return; } if (containsType(types, ITYPE_STATUS_BAR)) { if (!mStatusBarTransientShown) { mStatusBarTransientShown = true; handleTransientChanged(); } } if (containsType(types, ITYPE_NAVIGATION_BAR)) { if (!mNavBarTransientShown) { mNavBarTransientShown = true; handleTransientChanged(); } } } @Override public void abortTransient(int displayId, int[] types) { if (displayId != mDisplayId) { return; } if (!containsType(types, ITYPE_STATUS_BAR) && !containsType(types, ITYPE_NAVIGATION_BAR)) { return; } clearTransient(); } private void clearTransient() { if (mStatusBarTransientShown) { mStatusBarTransientShown = false; handleTransientChanged(); } if (mNavBarTransientShown) { mNavBarTransientShown = false; handleTransientChanged(); } } @VisibleForTesting boolean isStatusBarTransientShown() { return mStatusBarTransientShown; } @VisibleForTesting boolean isNavBarTransientShown() { return mNavBarTransientShown; } @VisibleForTesting void setSignalPolicy(StatusBarSignalPolicy signalPolicy) { mSignalPolicy = signalPolicy; } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.print(" mTaskStackListener="); pw.println(mButtonSelectionStateListener); pw.print(" mBottomSystemBarView="); pw.println(mBottomSystemBarView); } 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; } }