1 /*
2  * Copyright (C) 2018 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.google.android.setupcompat.template;
18 
19 import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
20 
21 import android.content.Context;
22 import android.content.res.TypedArray;
23 import android.graphics.Color;
24 import android.os.Build;
25 import android.os.Build.VERSION;
26 import android.os.Build.VERSION_CODES;
27 import android.util.AttributeSet;
28 import android.view.View;
29 import android.view.Window;
30 import androidx.annotation.AttrRes;
31 import androidx.annotation.NonNull;
32 import androidx.annotation.Nullable;
33 import androidx.annotation.VisibleForTesting;
34 import com.google.android.setupcompat.PartnerCustomizationLayout;
35 import com.google.android.setupcompat.R;
36 import com.google.android.setupcompat.internal.TemplateLayout;
37 import com.google.android.setupcompat.partnerconfig.PartnerConfig;
38 import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
39 import com.google.android.setupcompat.util.SystemBarHelper;
40 
41 /**
42  * A {@link Mixin} for setting and getting background color and window compatible with light theme
43  * of system navigation bar.
44  */
45 public class SystemNavBarMixin implements Mixin {
46 
47   private final TemplateLayout templateLayout;
48   @Nullable private final Window windowOfActivity;
49   @VisibleForTesting final boolean applyPartnerResources;
50   @VisibleForTesting final boolean useFullDynamicColor;
51   private int sucSystemNavBarBackgroundColor = 0;
52 
53   /**
54    * Creates a mixin for managing the system navigation bar.
55    *
56    * @param layout The layout this Mixin belongs to.
57    * @param window The window this activity of Mixin belongs to.*
58    */
SystemNavBarMixin(@onNull TemplateLayout layout, @Nullable Window window)59   public SystemNavBarMixin(@NonNull TemplateLayout layout, @Nullable Window window) {
60     this.templateLayout = layout;
61     this.windowOfActivity = window;
62     this.applyPartnerResources =
63         layout instanceof PartnerCustomizationLayout
64             && ((PartnerCustomizationLayout) layout).shouldApplyPartnerResource();
65 
66     this.useFullDynamicColor =
67         layout instanceof PartnerCustomizationLayout
68             && ((PartnerCustomizationLayout) layout).useFullDynamicColor();
69   }
70 
71   /**
72    * Creates a mixin for managing the system navigation bar.
73    *
74    * @param attrs XML attributes given to the layout.
75    * @param defStyleAttr The default style attribute as given to the constructor of the layout.
76    */
applyPartnerCustomizations(@ullable AttributeSet attrs, @AttrRes int defStyleAttr)77   public void applyPartnerCustomizations(@Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
78     // Support updating system navigation bar background color and is light system navigation bar
79     // from O.
80     if (Build.VERSION.SDK_INT >= VERSION_CODES.O_MR1) {
81       TypedArray a =
82           templateLayout
83               .getContext()
84               .obtainStyledAttributes(attrs, R.styleable.SucSystemNavBarMixin, defStyleAttr, 0);
85       sucSystemNavBarBackgroundColor =
86           a.getColor(R.styleable.SucSystemNavBarMixin_sucSystemNavBarBackgroundColor, 0);
87       setSystemNavBarBackground(sucSystemNavBarBackgroundColor);
88       setLightSystemNavBar(
89           a.getBoolean(
90               R.styleable.SucSystemNavBarMixin_sucLightSystemNavBar, isLightSystemNavBar()));
91 
92       // Support updating system navigation bar divider color from P.
93       if (VERSION.SDK_INT >= VERSION_CODES.P) {
94         // get fallback value from theme
95         int[] navBarDividerColorAttr = new int[] {android.R.attr.navigationBarDividerColor};
96         TypedArray typedArray =
97             templateLayout.getContext().obtainStyledAttributes(navBarDividerColorAttr);
98         int defaultColor = typedArray.getColor(/* index= */ 0, /* defValue= */ 0);
99         int sucSystemNavBarDividerColor =
100             a.getColor(R.styleable.SucSystemNavBarMixin_sucSystemNavBarDividerColor, defaultColor);
101         setSystemNavBarDividerColor(sucSystemNavBarDividerColor);
102         typedArray.recycle();
103       }
104       a.recycle();
105     }
106   }
107 
108   /**
109    * Sets the background color of navigation bar. The color will be overridden by partner resource
110    * if the activity is running in setup wizard flow.
111    *
112    * @param color The background color of navigation bar.
113    */
setSystemNavBarBackground(int color)114   public void setSystemNavBarBackground(int color) {
115     if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && windowOfActivity != null) {
116       if (applyPartnerResources) {
117         // If full dynamic color enabled which means this activity is running outside of setup
118         // flow, the colors should refer to R.style.SudFullDynamicColorThemeGlifV3.
119         if (!useFullDynamicColor) {
120           Context context = templateLayout.getContext();
121           color =
122               PartnerConfigHelper.get(context)
123                   .getColor(context, PartnerConfig.CONFIG_NAVIGATION_BAR_BG_COLOR);
124         }
125       }
126       windowOfActivity.setNavigationBarColor(color);
127     }
128   }
129 
130   /** Returns the background color of navigation bar. */
getSystemNavBarBackground()131   public int getSystemNavBarBackground() {
132     if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && windowOfActivity != null) {
133       return windowOfActivity.getNavigationBarColor();
134     }
135     return Color.BLACK;
136   }
137 
138   /**
139    * Sets the navigation bar to draw in a mode that is compatible with light or dark navigation bar
140    * backgrounds. The navigation bar drawing mode will be overridden by partner resource if the
141    * activity is running in setup wizard flow.
142    *
143    * @param isLight true means compatible with light theme, otherwise compatible with dark theme
144    */
145 
setLightSystemNavBar(boolean isLight)146   public void setLightSystemNavBar(boolean isLight) {
147     if (Build.VERSION.SDK_INT >= VERSION_CODES.O && windowOfActivity != null) {
148       if (applyPartnerResources) {
149         Context context = templateLayout.getContext();
150         isLight =
151             PartnerConfigHelper.get(context)
152                 .getBoolean(context, PartnerConfig.CONFIG_LIGHT_NAVIGATION_BAR, false);
153       }
154       if (isLight) {
155         windowOfActivity
156             .getDecorView()
157             .setSystemUiVisibility(
158                 windowOfActivity.getDecorView().getSystemUiVisibility()
159                     | SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
160       } else {
161         windowOfActivity
162             .getDecorView()
163             .setSystemUiVisibility(
164                 windowOfActivity.getDecorView().getSystemUiVisibility()
165                     & ~SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
166       }
167     }
168   }
169 
170   /**
171    * Returns true if the navigation bar icon should be drawn on light background, false if the icons
172    * should be drawn light-on-dark.
173    */
isLightSystemNavBar()174   public boolean isLightSystemNavBar() {
175     if (Build.VERSION.SDK_INT >= VERSION_CODES.O && windowOfActivity != null) {
176       return (windowOfActivity.getDecorView().getSystemUiVisibility()
177               & SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR)
178           == SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
179     }
180     return true;
181   }
182 
183   /**
184    * Sets the divider color of navigation bar. The color will be overridden by partner resource if
185    * the activity is running in setup wizard flow.
186    *
187    * @param color the default divider color of navigation bar
188    */
setSystemNavBarDividerColor(int color)189   public void setSystemNavBarDividerColor(int color) {
190     if (Build.VERSION.SDK_INT >= VERSION_CODES.P && windowOfActivity != null) {
191       if (applyPartnerResources) {
192         Context context = templateLayout.getContext();
193         // Do nothing if the old version partner provider did not contain the new config.
194         if (PartnerConfigHelper.get(context)
195             .isPartnerConfigAvailable(PartnerConfig.CONFIG_NAVIGATION_BAR_DIVIDER_COLOR)) {
196           color =
197               PartnerConfigHelper.get(context)
198                   .getColor(context, PartnerConfig.CONFIG_NAVIGATION_BAR_DIVIDER_COLOR);
199         }
200       }
201       windowOfActivity.setNavigationBarDividerColor(color);
202     }
203   }
204 
205   /**
206    * Hides the navigation bar, make the color of the status and navigation bars transparent, and
207    * specify {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} flag so that the content is laid-out
208    * behind the transparent status bar. This is commonly used with {@link
209    * android.app.Activity#getWindow()} to make the navigation and status bars follow the Setup
210    * Wizard style.
211    *
212    * <p>This will only take effect in versions Lollipop or above. Otherwise this is a no-op.
213    */
hideSystemBars(final Window window)214   public void hideSystemBars(final Window window) {
215     if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
216       SystemBarHelper.addVisibilityFlag(window, SystemBarHelper.DEFAULT_IMMERSIVE_FLAGS);
217       SystemBarHelper.addImmersiveFlagsToDecorView(window, SystemBarHelper.DEFAULT_IMMERSIVE_FLAGS);
218 
219       // Also set the navigation bar and status bar to transparent color. Note that this
220       // doesn't work if android.R.boolean.config_enableTranslucentDecor is false.
221       window.setNavigationBarColor(Color.TRANSPARENT);
222       window.setStatusBarColor(Color.TRANSPARENT);
223     }
224   }
225 
226   /**
227    * Reverts the actions of hideSystemBars. Note that this will remove the system UI visibility
228    * flags regardless of whether it is originally present. The status bar color is reset to
229    * transparent, thus it will show the status bar color set by StatusBarMixin.
230    *
231    * <p>This will only take effect in versions Lollipop or above. Otherwise this is a no-op.
232    */
showSystemBars(final Window window, final Context context)233   public void showSystemBars(final Window window, final Context context) {
234     if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
235       SystemBarHelper.removeVisibilityFlag(window, SystemBarHelper.DEFAULT_IMMERSIVE_FLAGS);
236       SystemBarHelper.removeImmersiveFlagsFromDecorView(
237           window, SystemBarHelper.DEFAULT_IMMERSIVE_FLAGS);
238 
239       if (context != null) {
240         if (applyPartnerResources) {
241           int partnerNavigationBarColor =
242               PartnerConfigHelper.get(context)
243                   .getColor(context, PartnerConfig.CONFIG_NAVIGATION_BAR_BG_COLOR);
244           window.setStatusBarColor(Color.TRANSPARENT);
245           window.setNavigationBarColor(partnerNavigationBarColor);
246         } else {
247           // noinspection AndroidLintInlinedApi
248           TypedArray typedArray =
249               context.obtainStyledAttributes(
250                   new int[] {android.R.attr.statusBarColor, android.R.attr.navigationBarColor});
251           int statusBarColor = typedArray.getColor(0, 0);
252           int navigationBarColor = typedArray.getColor(1, 0);
253           if (templateLayout instanceof PartnerCustomizationLayout) {
254             if (VERSION.SDK_INT >= VERSION_CODES.M) {
255               statusBarColor = Color.TRANSPARENT;
256             }
257             if (VERSION.SDK_INT >= VERSION_CODES.O_MR1) {
258               navigationBarColor = sucSystemNavBarBackgroundColor;
259             }
260           }
261           window.setStatusBarColor(statusBarColor);
262           window.setNavigationBarColor(navigationBarColor);
263           typedArray.recycle();
264         }
265       }
266     }
267   }
268 }
269