1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.car.systembar; 18 19 import android.annotation.IntDef; 20 import android.content.res.Resources; 21 import android.graphics.PixelFormat; 22 import android.util.ArrayMap; 23 import android.util.ArraySet; 24 import android.util.Log; 25 import android.view.Gravity; 26 import android.view.InsetsState; 27 import android.view.ViewGroup; 28 import android.view.WindowManager; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.systemui.R; 32 import com.android.systemui.car.notification.BottomNotificationPanelViewMediator; 33 import com.android.systemui.car.notification.TopNotificationPanelViewMediator; 34 import com.android.systemui.dagger.SysUISingleton; 35 import com.android.systemui.dagger.qualifiers.Main; 36 37 import java.lang.annotation.ElementType; 38 import java.lang.annotation.Target; 39 import java.util.ArrayList; 40 import java.util.Comparator; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.Set; 44 45 import javax.inject.Inject; 46 47 /** 48 * Reads configs for system bars for each side (TOP, BOTTOM, LEFT, and RIGHT) and returns the 49 * corresponding {@link android.view.WindowManager.LayoutParams} per the configuration. 50 */ 51 @SysUISingleton 52 public class SystemBarConfigs { 53 54 private static final String TAG = SystemBarConfigs.class.getSimpleName(); 55 // The z-order from which system bars will start to appear on top of HUN's. 56 private static final int HUN_ZORDER = 10; 57 58 @IntDef(value = {TOP, BOTTOM, LEFT, RIGHT}) 59 @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) 60 private @interface SystemBarSide { 61 } 62 63 public static final int TOP = 0; 64 public static final int BOTTOM = 1; 65 public static final int LEFT = 2; 66 public static final int RIGHT = 3; 67 68 /* 69 NOTE: The elements' order in the map below must be preserved as-is since the correct 70 corresponding values are obtained by the index. 71 */ 72 private static final int[] BAR_TYPE_MAP = { 73 InsetsState.ITYPE_STATUS_BAR, 74 InsetsState.ITYPE_NAVIGATION_BAR, 75 InsetsState.ITYPE_CLIMATE_BAR, 76 InsetsState.ITYPE_EXTRA_NAVIGATION_BAR 77 }; 78 79 private static final Map<@SystemBarSide Integer, Integer> BAR_GRAVITY_MAP = new ArrayMap<>(); 80 private static final Map<@SystemBarSide Integer, String> BAR_TITLE_MAP = new ArrayMap<>(); 81 private static final Map<@SystemBarSide Integer, Integer> BAR_GESTURE_MAP = new ArrayMap<>(); 82 83 private final Resources mResources; 84 private final Map<@SystemBarSide Integer, SystemBarConfig> mSystemBarConfigMap = 85 new ArrayMap<>(); 86 private final List<@SystemBarSide Integer> mSystemBarSidesByZOrder = new ArrayList<>(); 87 88 private boolean mTopNavBarEnabled; 89 private boolean mBottomNavBarEnabled; 90 private boolean mLeftNavBarEnabled; 91 private boolean mRightNavBarEnabled; 92 93 @Inject SystemBarConfigs(@ain Resources resources)94 public SystemBarConfigs(@Main Resources resources) { 95 mResources = resources; 96 97 populateMaps(); 98 readConfigs(); 99 100 checkEnabledBarsHaveUniqueBarTypes(); 101 checkAllOverlappingBarsHaveDifferentZOrders(); 102 checkSystemBarEnabledForNotificationPanel(); 103 checkHideBottomBarForKeyboardConfigSync(); 104 105 setInsetPaddingsForOverlappingCorners(); 106 sortSystemBarSidesByZOrder(); 107 } 108 getLayoutParamsBySide(@ystemBarSide int side)109 protected WindowManager.LayoutParams getLayoutParamsBySide(@SystemBarSide int side) { 110 return mSystemBarConfigMap.get(side) != null 111 ? mSystemBarConfigMap.get(side).getLayoutParams() : null; 112 } 113 getEnabledStatusBySide(@ystemBarSide int side)114 protected boolean getEnabledStatusBySide(@SystemBarSide int side) { 115 switch (side) { 116 case TOP: 117 return mTopNavBarEnabled; 118 case BOTTOM: 119 return mBottomNavBarEnabled; 120 case LEFT: 121 return mLeftNavBarEnabled; 122 case RIGHT: 123 return mRightNavBarEnabled; 124 default: 125 return false; 126 } 127 } 128 getHideForKeyboardBySide(@ystemBarSide int side)129 protected boolean getHideForKeyboardBySide(@SystemBarSide int side) { 130 return mSystemBarConfigMap.get(side) != null 131 && mSystemBarConfigMap.get(side).getHideForKeyboard(); 132 } 133 insetSystemBar(@ystemBarSide int side, CarSystemBarView view)134 protected void insetSystemBar(@SystemBarSide int side, CarSystemBarView view) { 135 if (mSystemBarConfigMap.get(side) == null) return; 136 137 int[] paddings = mSystemBarConfigMap.get(side).getPaddings(); 138 view.setPadding(paddings[2], paddings[0], paddings[3], paddings[1]); 139 } 140 getSystemBarSidesByZOrder()141 protected List<Integer> getSystemBarSidesByZOrder() { 142 return mSystemBarSidesByZOrder; 143 } 144 145 @VisibleForTesting updateInsetPaddings(@ystemBarSide int side, Map<@SystemBarSide Integer, Boolean> barVisibilities)146 void updateInsetPaddings(@SystemBarSide int side, 147 Map<@SystemBarSide Integer, Boolean> barVisibilities) { 148 SystemBarConfig currentConfig = mSystemBarConfigMap.get(side); 149 150 if (currentConfig == null) return; 151 152 if (isHorizontalBar(side)) { 153 if (mLeftNavBarEnabled && currentConfig.getZOrder() < mSystemBarConfigMap.get( 154 LEFT).getZOrder()) { 155 currentConfig.setPaddingBySide(LEFT, 156 barVisibilities.get(LEFT) ? mSystemBarConfigMap.get(LEFT).getGirth() : 0); 157 } 158 if (mRightNavBarEnabled && currentConfig.getZOrder() < mSystemBarConfigMap.get( 159 RIGHT).getZOrder()) { 160 currentConfig.setPaddingBySide(RIGHT, 161 barVisibilities.get(RIGHT) ? mSystemBarConfigMap.get(RIGHT).getGirth() : 0); 162 } 163 } 164 if (isVerticalBar(side)) { 165 if (mTopNavBarEnabled && currentConfig.getZOrder() < mSystemBarConfigMap.get( 166 TOP).getZOrder()) { 167 currentConfig.setPaddingBySide(TOP, 168 barVisibilities.get(TOP) ? mSystemBarConfigMap.get(TOP).getGirth() : 0); 169 } 170 if (mBottomNavBarEnabled && currentConfig.getZOrder() < mSystemBarConfigMap.get( 171 BOTTOM).getZOrder()) { 172 currentConfig.setPaddingBySide(BOTTOM, 173 barVisibilities.get(BOTTOM) ? mSystemBarConfigMap.get(BOTTOM).getGirth() 174 : 0); 175 } 176 } 177 } 178 179 @VisibleForTesting getHunZOrder()180 static int getHunZOrder() { 181 return HUN_ZORDER; 182 } 183 populateMaps()184 private static void populateMaps() { 185 BAR_GRAVITY_MAP.put(TOP, Gravity.TOP); 186 BAR_GRAVITY_MAP.put(BOTTOM, Gravity.BOTTOM); 187 BAR_GRAVITY_MAP.put(LEFT, Gravity.LEFT); 188 BAR_GRAVITY_MAP.put(RIGHT, Gravity.RIGHT); 189 190 BAR_TITLE_MAP.put(TOP, "TopCarSystemBar"); 191 BAR_TITLE_MAP.put(BOTTOM, "BottomCarSystemBar"); 192 BAR_TITLE_MAP.put(LEFT, "LeftCarSystemBar"); 193 BAR_TITLE_MAP.put(RIGHT, "RightCarSystemBar"); 194 195 BAR_GESTURE_MAP.put(TOP, InsetsState.ITYPE_TOP_MANDATORY_GESTURES); 196 BAR_GESTURE_MAP.put(BOTTOM, InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES); 197 BAR_GESTURE_MAP.put(LEFT, InsetsState.ITYPE_LEFT_MANDATORY_GESTURES); 198 BAR_GESTURE_MAP.put(RIGHT, InsetsState.ITYPE_RIGHT_MANDATORY_GESTURES); 199 } 200 readConfigs()201 private void readConfigs() { 202 mTopNavBarEnabled = mResources.getBoolean(R.bool.config_enableTopSystemBar); 203 mBottomNavBarEnabled = mResources.getBoolean(R.bool.config_enableBottomSystemBar); 204 mLeftNavBarEnabled = mResources.getBoolean(R.bool.config_enableLeftSystemBar); 205 mRightNavBarEnabled = mResources.getBoolean(R.bool.config_enableRightSystemBar); 206 207 if (mTopNavBarEnabled) { 208 SystemBarConfig topBarConfig = 209 new SystemBarConfigBuilder() 210 .setSide(TOP) 211 .setGirth(mResources.getDimensionPixelSize( 212 R.dimen.car_top_system_bar_height)) 213 .setBarType(mResources.getInteger(R.integer.config_topSystemBarType)) 214 .setZOrder(mResources.getInteger(R.integer.config_topSystemBarZOrder)) 215 .setHideForKeyboard(mResources.getBoolean( 216 R.bool.config_hideTopSystemBarForKeyboard)) 217 .build(); 218 mSystemBarConfigMap.put(TOP, topBarConfig); 219 } 220 221 if (mBottomNavBarEnabled) { 222 SystemBarConfig bottomBarConfig = 223 new SystemBarConfigBuilder() 224 .setSide(BOTTOM) 225 .setGirth(mResources.getDimensionPixelSize( 226 R.dimen.car_bottom_system_bar_height)) 227 .setBarType(mResources.getInteger(R.integer.config_bottomSystemBarType)) 228 .setZOrder( 229 mResources.getInteger(R.integer.config_bottomSystemBarZOrder)) 230 .setHideForKeyboard(mResources.getBoolean( 231 R.bool.config_hideBottomSystemBarForKeyboard)) 232 .build(); 233 mSystemBarConfigMap.put(BOTTOM, bottomBarConfig); 234 } 235 236 if (mLeftNavBarEnabled) { 237 SystemBarConfig leftBarConfig = 238 new SystemBarConfigBuilder() 239 .setSide(LEFT) 240 .setGirth(mResources.getDimensionPixelSize( 241 R.dimen.car_left_system_bar_width)) 242 .setBarType(mResources.getInteger(R.integer.config_leftSystemBarType)) 243 .setZOrder(mResources.getInteger(R.integer.config_leftSystemBarZOrder)) 244 .setHideForKeyboard(mResources.getBoolean( 245 R.bool.config_hideLeftSystemBarForKeyboard)) 246 .build(); 247 mSystemBarConfigMap.put(LEFT, leftBarConfig); 248 } 249 250 if (mRightNavBarEnabled) { 251 SystemBarConfig rightBarConfig = 252 new SystemBarConfigBuilder() 253 .setSide(RIGHT) 254 .setGirth(mResources.getDimensionPixelSize( 255 R.dimen.car_right_system_bar_width)) 256 .setBarType(mResources.getInteger(R.integer.config_rightSystemBarType)) 257 .setZOrder(mResources.getInteger(R.integer.config_rightSystemBarZOrder)) 258 .setHideForKeyboard(mResources.getBoolean( 259 R.bool.config_hideRightSystemBarForKeyboard)) 260 .build(); 261 mSystemBarConfigMap.put(RIGHT, rightBarConfig); 262 } 263 } 264 checkEnabledBarsHaveUniqueBarTypes()265 private void checkEnabledBarsHaveUniqueBarTypes() throws RuntimeException { 266 Set<Integer> barTypesUsed = new ArraySet<>(); 267 int enabledNavBarCount = mSystemBarConfigMap.size(); 268 269 for (SystemBarConfig systemBarConfig : mSystemBarConfigMap.values()) { 270 barTypesUsed.add(systemBarConfig.getBarType()); 271 } 272 273 // The number of bar types used cannot be fewer than that of enabled system bars. 274 if (barTypesUsed.size() < enabledNavBarCount) { 275 throw new RuntimeException("Each enabled system bar must have a unique bar type. Check " 276 + "the configuration in config.xml"); 277 } 278 } 279 checkAllOverlappingBarsHaveDifferentZOrders()280 private void checkAllOverlappingBarsHaveDifferentZOrders() { 281 checkOverlappingBarsHaveDifferentZOrders(TOP, LEFT); 282 checkOverlappingBarsHaveDifferentZOrders(TOP, RIGHT); 283 checkOverlappingBarsHaveDifferentZOrders(BOTTOM, LEFT); 284 checkOverlappingBarsHaveDifferentZOrders(BOTTOM, RIGHT); 285 } 286 checkSystemBarEnabledForNotificationPanel()287 private void checkSystemBarEnabledForNotificationPanel() throws RuntimeException { 288 String notificationPanelMediatorName = 289 mResources.getString(R.string.config_notificationPanelViewMediator); 290 if (notificationPanelMediatorName == null) { 291 return; 292 } 293 294 Class<?> notificationPanelMediatorUsed = null; 295 try { 296 notificationPanelMediatorUsed = Class.forName(notificationPanelMediatorName); 297 } catch (ClassNotFoundException e) { 298 e.printStackTrace(); 299 } 300 301 if (!mTopNavBarEnabled && TopNotificationPanelViewMediator.class.isAssignableFrom( 302 notificationPanelMediatorUsed)) { 303 throw new RuntimeException( 304 "Top System Bar must be enabled to use " + notificationPanelMediatorName); 305 } 306 307 if (!mBottomNavBarEnabled && BottomNotificationPanelViewMediator.class.isAssignableFrom( 308 notificationPanelMediatorUsed)) { 309 throw new RuntimeException("Bottom System Bar must be enabled to use " 310 + notificationPanelMediatorName); 311 } 312 } 313 checkHideBottomBarForKeyboardConfigSync()314 private void checkHideBottomBarForKeyboardConfigSync() throws RuntimeException { 315 if (mBottomNavBarEnabled) { 316 boolean actual = mResources.getBoolean(R.bool.config_hideBottomSystemBarForKeyboard); 317 boolean expected = mResources.getBoolean( 318 com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard); 319 320 if (actual != expected) { 321 throw new RuntimeException("config_hideBottomSystemBarForKeyboard must not be " 322 + "overlaid directly and should always refer to" 323 + "config_automotiveHideNavBarForKeyboard. However, their values " 324 + "currently do not sync. Set config_hideBottomSystemBarForKeyguard to " 325 + "@*android:bool/config_automotiveHideNavBarForKeyboard. To change its " 326 + "value, overlay config_automotiveHideNavBarForKeyboard in " 327 + "framework/base/core/res/res."); 328 } 329 } 330 } 331 setInsetPaddingsForOverlappingCorners()332 private void setInsetPaddingsForOverlappingCorners() { 333 Map<@SystemBarSide Integer, Boolean> systemBarVisibilityOnInit = 334 getSystemBarsVisibilityOnInit(); 335 updateInsetPaddings(TOP, systemBarVisibilityOnInit); 336 updateInsetPaddings(BOTTOM, systemBarVisibilityOnInit); 337 updateInsetPaddings(LEFT, systemBarVisibilityOnInit); 338 updateInsetPaddings(RIGHT, systemBarVisibilityOnInit); 339 } 340 sortSystemBarSidesByZOrder()341 private void sortSystemBarSidesByZOrder() { 342 List<SystemBarConfig> systemBarsByZOrder = new ArrayList<>(mSystemBarConfigMap.values()); 343 344 systemBarsByZOrder.sort(new Comparator<SystemBarConfig>() { 345 @Override 346 public int compare(SystemBarConfig o1, SystemBarConfig o2) { 347 return o1.getZOrder() - o2.getZOrder(); 348 } 349 }); 350 351 systemBarsByZOrder.forEach(systemBarConfig -> { 352 mSystemBarSidesByZOrder.add(systemBarConfig.getSide()); 353 }); 354 } 355 356 @InsetsState.InternalInsetsType getSystemBarTypeBySide(@ystemBarSide int side)357 private int getSystemBarTypeBySide(@SystemBarSide int side) { 358 return mSystemBarConfigMap.get(side) != null 359 ? mSystemBarConfigMap.get(side).getBarType() : null; 360 } 361 362 // On init, system bars are visible as long as they are enabled. getSystemBarsVisibilityOnInit()363 private Map<@SystemBarSide Integer, Boolean> getSystemBarsVisibilityOnInit() { 364 ArrayMap<@SystemBarSide Integer, Boolean> visibilityMap = new ArrayMap<>(); 365 visibilityMap.put(TOP, mTopNavBarEnabled); 366 visibilityMap.put(BOTTOM, mBottomNavBarEnabled); 367 visibilityMap.put(LEFT, mLeftNavBarEnabled); 368 visibilityMap.put(RIGHT, mRightNavBarEnabled); 369 return visibilityMap; 370 } 371 checkOverlappingBarsHaveDifferentZOrders(@ystemBarSide int horizontalSide, @SystemBarSide int verticalSide)372 private void checkOverlappingBarsHaveDifferentZOrders(@SystemBarSide int horizontalSide, 373 @SystemBarSide int verticalSide) { 374 375 if (isVerticalBar(horizontalSide) || isHorizontalBar(verticalSide)) { 376 Log.w(TAG, "configureBarPaddings: Returning immediately since the horizontal and " 377 + "vertical sides were not provided correctly."); 378 return; 379 } 380 381 SystemBarConfig horizontalBarConfig = mSystemBarConfigMap.get(horizontalSide); 382 SystemBarConfig verticalBarConfig = mSystemBarConfigMap.get(verticalSide); 383 384 if (verticalBarConfig != null && horizontalBarConfig != null) { 385 int horizontalBarZOrder = horizontalBarConfig.getZOrder(); 386 int verticalBarZOrder = verticalBarConfig.getZOrder(); 387 388 if (horizontalBarZOrder == verticalBarZOrder) { 389 throw new RuntimeException( 390 BAR_TITLE_MAP.get(horizontalSide) + " " + BAR_TITLE_MAP.get(verticalSide) 391 + " have the same Z-Order, and so their placing order cannot be " 392 + "determined. Determine which bar should be placed on top of the " 393 + "other bar and change the Z-order in config.xml accordingly." 394 ); 395 } 396 } 397 } 398 isHorizontalBar(@ystemBarSide int side)399 private static boolean isHorizontalBar(@SystemBarSide int side) { 400 return side == TOP || side == BOTTOM; 401 } 402 isVerticalBar(@ystemBarSide int side)403 private static boolean isVerticalBar(@SystemBarSide int side) { 404 return side == LEFT || side == RIGHT; 405 } 406 407 private static final class SystemBarConfig { 408 private final int mSide; 409 private final int mBarType; 410 private final int mGirth; 411 private final int mZOrder; 412 private final boolean mHideForKeyboard; 413 414 private int[] mPaddings = new int[]{0, 0, 0, 0}; 415 SystemBarConfig(@ystemBarSide int side, int barType, int girth, int zOrder, boolean hideForKeyboard)416 private SystemBarConfig(@SystemBarSide int side, int barType, int girth, int zOrder, 417 boolean hideForKeyboard) { 418 mSide = side; 419 mBarType = barType; 420 mGirth = girth; 421 mZOrder = zOrder; 422 mHideForKeyboard = hideForKeyboard; 423 } 424 getSide()425 private int getSide() { 426 return mSide; 427 } 428 getBarType()429 private int getBarType() { 430 return mBarType; 431 } 432 getGirth()433 private int getGirth() { 434 return mGirth; 435 } 436 getZOrder()437 private int getZOrder() { 438 return mZOrder; 439 } 440 getHideForKeyboard()441 private boolean getHideForKeyboard() { 442 return mHideForKeyboard; 443 } 444 getPaddings()445 private int[] getPaddings() { 446 return mPaddings; 447 } 448 getLayoutParams()449 private WindowManager.LayoutParams getLayoutParams() { 450 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 451 isHorizontalBar(mSide) ? ViewGroup.LayoutParams.MATCH_PARENT : mGirth, 452 isHorizontalBar(mSide) ? mGirth : ViewGroup.LayoutParams.MATCH_PARENT, 453 mapZOrderToBarType(mZOrder), 454 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 455 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 456 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 457 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 458 PixelFormat.TRANSLUCENT); 459 lp.setTitle(BAR_TITLE_MAP.get(mSide)); 460 lp.providesInsetsTypes = new int[]{BAR_TYPE_MAP[mBarType], BAR_GESTURE_MAP.get(mSide)}; 461 lp.setFitInsetsTypes(0); 462 lp.windowAnimations = 0; 463 lp.gravity = BAR_GRAVITY_MAP.get(mSide); 464 return lp; 465 } 466 mapZOrderToBarType(int zOrder)467 private int mapZOrderToBarType(int zOrder) { 468 return zOrder >= HUN_ZORDER ? WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL 469 : WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL; 470 } 471 setPaddingBySide(@ystemBarSide int side, int padding)472 private void setPaddingBySide(@SystemBarSide int side, int padding) { 473 mPaddings[side] = padding; 474 } 475 } 476 477 private static final class SystemBarConfigBuilder { 478 private int mSide; 479 private int mBarType; 480 private int mGirth; 481 private int mZOrder; 482 private boolean mHideForKeyboard; 483 setSide(@ystemBarSide int side)484 private SystemBarConfigBuilder setSide(@SystemBarSide int side) { 485 mSide = side; 486 return this; 487 } 488 setBarType(int type)489 private SystemBarConfigBuilder setBarType(int type) { 490 mBarType = type; 491 return this; 492 } 493 setGirth(int girth)494 private SystemBarConfigBuilder setGirth(int girth) { 495 mGirth = girth; 496 return this; 497 } 498 setZOrder(int zOrder)499 private SystemBarConfigBuilder setZOrder(int zOrder) { 500 mZOrder = zOrder; 501 return this; 502 } 503 setHideForKeyboard(boolean hide)504 private SystemBarConfigBuilder setHideForKeyboard(boolean hide) { 505 mHideForKeyboard = hide; 506 return this; 507 } 508 build()509 private SystemBarConfig build() { 510 return new SystemBarConfig(mSide, mBarType, mGirth, mZOrder, mHideForKeyboard); 511 } 512 } 513 } 514