1 /*
2  * Copyright (C) 2015 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.setupdesign.util;
18 
19 import android.app.Activity;
20 import android.content.Context;
21 import android.content.Intent;
22 import androidx.annotation.NonNull;
23 import androidx.annotation.StyleRes;
24 import com.google.android.setupcompat.PartnerCustomizationLayout;
25 import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
26 import com.google.android.setupcompat.util.BuildCompatUtils;
27 import com.google.android.setupcompat.util.Logger;
28 import com.google.android.setupcompat.util.WizardManagerHelper;
29 import com.google.android.setupdesign.R;
30 import java.util.Objects;
31 
32 /** The helper class holds the constant names of themes and util functions */
33 public final class ThemeHelper {
34 
35   private static final Logger LOG = new Logger("ThemeHelper");
36 
37   /**
38    * Passed in a setup wizard intent as {@link WizardManagerHelper#EXTRA_THEME}. This is the dark
39    * variant of the theme used in setup wizard for Nougat MR1.
40    */
41   public static final String THEME_GLIF = "glif";
42 
43   /**
44    * Passed in a setup wizard intent as {@link WizardManagerHelper#EXTRA_THEME}. This is the default
45    * theme used in setup wizard for Nougat MR1.
46    */
47   public static final String THEME_GLIF_LIGHT = "glif_light";
48 
49   /**
50    * Passed in a setup wizard intent as {@link WizardManagerHelper#EXTRA_THEME}. This is the dark
51    * variant of the theme used in setup wizard for O DR.
52    */
53   public static final String THEME_GLIF_V2 = "glif_v2";
54 
55   /**
56    * Passed in a setup wizard intent as {@link WizardManagerHelper#EXTRA_THEME}. This is the default
57    * theme used in setup wizard for O DR.
58    */
59   public static final String THEME_GLIF_V2_LIGHT = "glif_v2_light";
60 
61   /**
62    * Passed in a setup wizard intent as {@link WizardManagerHelper#EXTRA_THEME}. This is the dark
63    * variant of the theme used in setup wizard for P.
64    */
65   public static final String THEME_GLIF_V3 = "glif_v3";
66 
67   /**
68    * Passed in a setup wizard intent as {@link WizardManagerHelper#EXTRA_THEME}. This is the default
69    * theme used in setup wizard for P.
70    */
71   public static final String THEME_GLIF_V3_LIGHT = "glif_v3_light";
72 
73   /**
74    * Placeholder, not avirailed yet.
75    */
76   public static final String THEME_GLIF_V4 = "glif_v4";
77 
78   /**
79    * Placeholder, not avirailed yet.
80    */
81   public static final String THEME_GLIF_V4_LIGHT = "glif_v4_light";
82 
83   public static final String THEME_HOLO = "holo";
84   public static final String THEME_HOLO_LIGHT = "holo_light";
85   public static final String THEME_MATERIAL = "material";
86   public static final String THEME_MATERIAL_LIGHT = "material_light";
87 
88   /**
89    * Checks the intent whether the extra indicates that the light theme should be used or not. If
90    * the theme is not specified in the intent, or the theme specified is unknown, the value def will
91    * be returned. Note that day-night themes are not taken into account by this method.
92    *
93    * @param intent The intent used to start the activity, which the theme extra will be read from.
94    * @param def The default value if the theme is not specified.
95    * @return True if the activity started by the given intent should use light theme.
96    */
isLightTheme(Intent intent, boolean def)97   public static boolean isLightTheme(Intent intent, boolean def) {
98     final String theme = intent.getStringExtra(WizardManagerHelper.EXTRA_THEME);
99     return isLightTheme(theme, def);
100   }
101 
102   /**
103    * Checks whether {@code theme} represents a light or dark theme. If the theme specified is
104    * unknown, the value def will be returned. Note that day-night themes are not taken into account
105    * by this method.
106    *
107    * @param theme The theme as specified from an intent sent from setup wizard.
108    * @param def The default value if the theme is not known.
109    * @return True if {@code theme} represents a light theme.
110    */
isLightTheme(String theme, boolean def)111   public static boolean isLightTheme(String theme, boolean def) {
112     if (THEME_HOLO_LIGHT.equals(theme)
113         || THEME_MATERIAL_LIGHT.equals(theme)
114         || THEME_GLIF_LIGHT.equals(theme)
115         || THEME_GLIF_V2_LIGHT.equals(theme)
116         || THEME_GLIF_V3_LIGHT.equals(theme)
117         || THEME_GLIF_V4_LIGHT.equals(theme)) {
118       return true;
119     } else if (THEME_HOLO.equals(theme)
120         || THEME_MATERIAL.equals(theme)
121         || THEME_GLIF.equals(theme)
122         || THEME_GLIF_V2.equals(theme)
123         || THEME_GLIF_V3.equals(theme)
124         || THEME_GLIF_V4.equals(theme)) {
125       return false;
126     } else {
127       return def;
128     }
129   }
130 
131   /**
132    * Reads the theme from the intent, and applies the theme to the activity as resolved by {@link
133    * ThemeResolver#getDefault()}.
134    *
135    * <p>If you require extra theme attributes, consider overriding {@link
136    * android.app.Activity#onApplyThemeResource} in your activity and call {@link
137    * android.content.res.Resources.Theme#applyStyle(int, boolean)} using your theme overlay.
138    *
139    * <pre>{@code
140    * protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
141    *     super.onApplyThemeResource(theme, resid, first);
142    *     theme.applyStyle(R.style.MyThemeOverlay, true);
143    * }
144    * }</pre>
145    *
146    * @param activity the activity to get the intent from and apply the resulting theme to.
147    */
applyTheme(Activity activity)148   public static void applyTheme(Activity activity) {
149     ThemeResolver.getDefault().applyTheme(activity);
150   }
151 
152   /**
153    * Checks whether SetupWizard supports the DayNight theme during setup flow; if it returns false,
154    * setup flow is always light theme.
155    *
156    * @return true if the SetupWizard is listening to system DayNight theme setting.
157    */
isSetupWizardDayNightEnabled(@onNull Context context)158   public static boolean isSetupWizardDayNightEnabled(@NonNull Context context) {
159     return PartnerConfigHelper.isSetupWizardDayNightEnabled(context);
160   }
161 
162   /**
163    * Returns true if the partner provider of SetupWizard is ready to support more partner configs.
164    */
shouldApplyExtendedPartnerConfig(@onNull Context context)165   public static boolean shouldApplyExtendedPartnerConfig(@NonNull Context context) {
166     return PartnerConfigHelper.shouldApplyExtendedPartnerConfig(context);
167   }
168 
169   /**
170    * Returns {@code true} if the partner provider of SetupWizard is ready to support dynamic color.
171    */
isSetupWizardDynamicColorEnabled(@onNull Context context)172   public static boolean isSetupWizardDynamicColorEnabled(@NonNull Context context) {
173     return PartnerConfigHelper.isSetupWizardDynamicColorEnabled(context);
174   }
175 
176   /** Returns {@code true} if this {@code context} should apply dynamic color. */
shouldApplyDynamicColor(@onNull Context context)177   public static boolean shouldApplyDynamicColor(@NonNull Context context) {
178     return shouldApplyExtendedPartnerConfig(context) && isSetupWizardDynamicColorEnabled(context);
179   }
180 
181   /**
182    * Returns a theme resource id if the {@link com.google.android.setupdesign.GlifLayout} should
183    * apply dynamic color.
184    *
185    * <p>Otherwise returns {@code 0}.
186    */
187   @StyleRes
getDynamicColorTheme(@onNull Context context)188   public static int getDynamicColorTheme(@NonNull Context context) {
189     @StyleRes int resId = 0;
190 
191     Activity activity;
192     try {
193       activity = PartnerCustomizationLayout.lookupActivityFromContext(context);
194     } catch (IllegalArgumentException ex) {
195       LOG.e(Objects.requireNonNull(ex.getMessage()));
196       return resId;
197     }
198 
199     boolean isSetupFlow = WizardManagerHelper.isAnySetupWizard(activity.getIntent());
200     boolean isDayNightEnabled = isSetupWizardDayNightEnabled(context);
201 
202     if (isSetupFlow) {
203       // return theme for inside setup flow
204       resId =
205           isDayNightEnabled
206               ? R.style.SudDynamicColorThemeGlifV3_DayNight
207               : R.style.SudDynamicColorThemeGlifV3_Light;
208     } else {
209       // return theme for outside setup flow
210       resId =
211           isDayNightEnabled
212               ? R.style.SudFullDynamicColorThemeGlifV3_DayNight
213               : R.style.SudFullDynamicColorThemeGlifV3_Light;
214       LOG.atInfo(
215           "Return "
216               + (isDayNightEnabled
217                   ? "SudFullDynamicColorThemeGlifV3_DayNight"
218                   : "SudFullDynamicColorThemeGlifV3_Light"));
219     }
220 
221     LOG.atDebug(
222         "Gets the dynamic accentColor: [Light] "
223             + colorIntToHex(context, R.color.sud_dynamic_color_accent_glif_v3_light)
224             + ", "
225             + (BuildCompatUtils.isAtLeastS()
226                 ? colorIntToHex(context, android.R.color.system_accent1_600)
227                 : "n/a")
228             + ", [Dark] "
229             + colorIntToHex(context, R.color.sud_dynamic_color_accent_glif_v3_dark)
230             + ", "
231             + (BuildCompatUtils.isAtLeastS()
232                 ? colorIntToHex(context, android.R.color.system_accent1_100)
233                 : "n/a"));
234 
235     return resId;
236   }
237 
238   /** Returns {@code true} if the dynamic color is set. */
trySetDynamicColor(@onNull Context context)239   public static boolean trySetDynamicColor(@NonNull Context context) {
240     if (!shouldApplyExtendedPartnerConfig(context)) {
241       LOG.w("SetupWizard does not supports the extended partner configs.");
242       return false;
243     }
244 
245     if (!isSetupWizardDynamicColorEnabled(context)) {
246       LOG.w("SetupWizard does not support the dynamic color or supporting status unknown.");
247       return false;
248     }
249 
250     Activity activity;
251     try {
252       activity = PartnerCustomizationLayout.lookupActivityFromContext(context);
253     } catch (IllegalArgumentException ex) {
254       LOG.e(Objects.requireNonNull(ex.getMessage()));
255       return false;
256     }
257 
258     @StyleRes int resId = getDynamicColorTheme(context);
259     if (resId != 0) {
260       activity.setTheme(resId);
261     } else {
262       LOG.w("Error occurred on getting dynamic color theme.");
263       return false;
264     }
265 
266     return true;
267   }
268 
colorIntToHex(Context context, int colorInt)269   private static String colorIntToHex(Context context, int colorInt) {
270     return String.format("#%06X", (0xFFFFFF & context.getResources().getColor(colorInt)));
271   }
272 
ThemeHelper()273   private ThemeHelper() {}
274 }
275