• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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