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