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.setupdesign.util;
18 
19 import android.app.Activity;
20 import android.content.Intent;
21 import androidx.annotation.Nullable;
22 import androidx.annotation.StyleRes;
23 import com.google.android.setupcompat.util.WizardManagerHelper;
24 import com.google.android.setupdesign.R;
25 
26 /**
27  * A resolver to resolve the theme from a string or an activity intent, setting options like the
28  * default theme and the oldest supported theme. Apps can share the resolver across the entire
29  * process by calling {@link #setDefault(ThemeResolver)} in {@link
30  * android.app.Application#onCreate()}. If an app needs more granular sharing of the theme default
31  * values, additional instances of {@link ThemeResolver} can be created using the builder.
32  */
33 public class ThemeResolver {
34 
35   @StyleRes private final int defaultTheme;
36   @Nullable private final String oldestSupportedTheme;
37   private final boolean useDayNight;
38   @Nullable private final ThemeSupplier defaultThemeSupplier;
39 
40   @Nullable private static ThemeResolver defaultResolver;
41 
42   /**
43    * Sets the default instance used for the whole process. Can be null to reset the default to the
44    * preset one.
45    */
setDefault(@ullable ThemeResolver resolver)46   public static void setDefault(@Nullable ThemeResolver resolver) {
47     defaultResolver = resolver;
48   }
49 
50   /**
51    * Returns the default instance, which can be changed using {@link #setDefault(ThemeResolver)}.
52    */
getDefault()53   public static ThemeResolver getDefault() {
54     if (defaultResolver == null) {
55       defaultResolver =
56           new ThemeResolver.Builder()
57               .setDefaultTheme(R.style.SudThemeGlif_DayNight)
58               .setUseDayNight(true)
59               .build();
60     }
61     return defaultResolver;
62   }
63 
ThemeResolver( int defaultTheme, @Nullable String oldestSupportedTheme, @Nullable ThemeSupplier defaultThemeSupplier, boolean useDayNight)64   private ThemeResolver(
65       int defaultTheme,
66       @Nullable String oldestSupportedTheme,
67       @Nullable ThemeSupplier defaultThemeSupplier,
68       boolean useDayNight) {
69     this.defaultTheme = defaultTheme;
70     this.oldestSupportedTheme = oldestSupportedTheme;
71     this.defaultThemeSupplier = defaultThemeSupplier;
72     this.useDayNight = useDayNight;
73   }
74 
75   /**
76    * Returns the style for the theme specified in the intent extra. If the specified string theme is
77    * older than the oldest supported theme, the default will be returned instead. Note that the
78    * default theme is returned without processing -- it may not be a DayNight theme even if {@link
79    * #useDayNight} is true.
80    */
81   @StyleRes
resolve(Intent intent)82   public int resolve(Intent intent) {
83     return resolve(
84         intent.getStringExtra(WizardManagerHelper.EXTRA_THEME),
85         /* suppressDayNight= */ WizardManagerHelper.isAnySetupWizard(intent));
86   }
87 
88   /**
89    * Returns the style for the given SetupWizard intent. If the specified intent does not include
90    * the intent extra {@link WizardManagerHelper#EXTRA_THEME}, the default theme will be returned
91    * instead. Note that the default theme is returned without processing -- it may not be a DayNight
92    * theme even if {@link #useDayNight} is true.
93    */
94   @StyleRes
resolve(Intent intent, boolean suppressDayNight)95   public int resolve(Intent intent, boolean suppressDayNight) {
96     return resolve(intent.getStringExtra(WizardManagerHelper.EXTRA_THEME), suppressDayNight);
97   }
98 
99   /**
100    * Returns the style for the given string theme. If the specified string theme is older than the
101    * oldest supported theme, the default will be returned instead. Note that the default theme is
102    * returned without processing -- it may not be a DayNight theme even if {@link #useDayNight} is
103    * true.
104    *
105    * @deprecated Use {@link #resolve(String, boolean)} instead
106    */
107   @Deprecated
108   @StyleRes
resolve(@ullable String theme)109   public int resolve(@Nullable String theme) {
110     return resolve(theme, /* suppressDayNight= */ false);
111   }
112 
113   /**
114    * Returns the style for the given string theme. If the specified string theme is older than the
115    * oldest supported theme, the default will be returned instead. Note that the default theme is
116    * returned without processing -- it may not be a DayNight theme even if {@link #useDayNight} is
117    * true.
118    */
119   @StyleRes
resolve(@ullable String theme, boolean suppressDayNight)120   public int resolve(@Nullable String theme, boolean suppressDayNight) {
121     int themeResource =
122         useDayNight && !suppressDayNight ? getDayNightThemeRes(theme) : getThemeRes(theme);
123     if (themeResource == 0) {
124       if (defaultThemeSupplier != null) {
125         theme = defaultThemeSupplier.getTheme();
126         themeResource =
127             useDayNight && !suppressDayNight ? getDayNightThemeRes(theme) : getThemeRes(theme);
128       }
129       if (themeResource == 0) {
130         return defaultTheme;
131       }
132     }
133 
134     if (oldestSupportedTheme != null && compareThemes(theme, oldestSupportedTheme) < 0) {
135       return defaultTheme;
136     }
137     return themeResource;
138   }
139 
140   /** Reads the theme from the intent, and applies the resolved theme to the activity. */
applyTheme(Activity activity)141   public void applyTheme(Activity activity) {
142     activity.setTheme(
143         resolve(
144             activity.getIntent(),
145             /* suppressDayNight= */ WizardManagerHelper.isAnySetupWizard(activity.getIntent())
146                 && !ThemeHelper.isSetupWizardDayNightEnabled(activity)));
147   }
148 
149   /**
150    * Returns the corresponding DayNight theme resource ID for the given string theme. DayNight
151    * themes are themes that will be either light or dark depending on the system setting. For
152    * example, the string {@link ThemeHelper#THEME_GLIF_LIGHT} will return
153    * {@code @style/SudThemeGlif.DayNight}.
154    */
155   @StyleRes
getDayNightThemeRes(@ullable String theme)156   private static int getDayNightThemeRes(@Nullable String theme) {
157     if (theme != null) {
158       switch (theme) {
159         case ThemeHelper.THEME_GLIF_V3_LIGHT:
160         case ThemeHelper.THEME_GLIF_V3:
161           return R.style.SudThemeGlifV3_DayNight;
162         case ThemeHelper.THEME_GLIF_V2_LIGHT:
163         case ThemeHelper.THEME_GLIF_V2:
164           return R.style.SudThemeGlifV2_DayNight;
165         case ThemeHelper.THEME_GLIF_LIGHT:
166         case ThemeHelper.THEME_GLIF:
167           return R.style.SudThemeGlif_DayNight;
168         case ThemeHelper.THEME_MATERIAL_LIGHT:
169         case ThemeHelper.THEME_MATERIAL:
170           return R.style.SudThemeMaterial_DayNight;
171         default:
172           // fall through
173       }
174     }
175     return 0;
176   }
177 
178   /**
179    * Returns the theme resource ID for the given string theme. For example, the string {@link
180    * ThemeHelper#THEME_GLIF_LIGHT} will return {@code @style/SudThemeGlif.Light}.
181    */
182   @StyleRes
getThemeRes(@ullable String theme)183   private static int getThemeRes(@Nullable String theme) {
184     if (theme != null) {
185       switch (theme) {
186         case ThemeHelper.THEME_GLIF_V3_LIGHT:
187           return R.style.SudThemeGlifV3_Light;
188         case ThemeHelper.THEME_GLIF_V3:
189           return R.style.SudThemeGlifV3;
190         case ThemeHelper.THEME_GLIF_V2_LIGHT:
191           return R.style.SudThemeGlifV2_Light;
192         case ThemeHelper.THEME_GLIF_V2:
193           return R.style.SudThemeGlifV2;
194         case ThemeHelper.THEME_GLIF_LIGHT:
195           return R.style.SudThemeGlif_Light;
196         case ThemeHelper.THEME_GLIF:
197           return R.style.SudThemeGlif;
198         case ThemeHelper.THEME_MATERIAL_LIGHT:
199           return R.style.SudThemeMaterial_Light;
200         case ThemeHelper.THEME_MATERIAL:
201           return R.style.SudThemeMaterial;
202         default:
203           // fall through
204       }
205     }
206     return 0;
207   }
208 
209   /** Compares whether the versions of {@code theme1} and {@code theme2} to check which is newer. */
compareThemes(String theme1, String theme2)210   private static int compareThemes(String theme1, String theme2) {
211     return Integer.valueOf(getThemeVersion(theme1)).compareTo(getThemeVersion(theme2));
212   }
213 
214   /**
215    * Returns the version of the theme. The absolute number of the theme version is not defined, but
216    * a larger number in the version indicates a newer theme.
217    */
getThemeVersion(String theme)218   private static int getThemeVersion(String theme) {
219     if (theme != null) {
220       switch (theme) {
221         case ThemeHelper.THEME_GLIF_V3_LIGHT:
222         case ThemeHelper.THEME_GLIF_V3:
223           return 4;
224         case ThemeHelper.THEME_GLIF_V2_LIGHT:
225         case ThemeHelper.THEME_GLIF_V2:
226           return 3;
227         case ThemeHelper.THEME_GLIF_LIGHT:
228         case ThemeHelper.THEME_GLIF:
229           return 2;
230         case ThemeHelper.THEME_MATERIAL_LIGHT:
231         case ThemeHelper.THEME_MATERIAL:
232           return 1;
233         default:
234           // fall through
235       }
236     }
237     return -1;
238   }
239 
240   /** Builder class for {@link ThemeResolver}. */
241   public static class Builder {
242     private ThemeSupplier defaultThemeSupplier;
243     @StyleRes private int defaultTheme = R.style.SudThemeGlif_DayNight;
244     @Nullable private String oldestSupportedTheme = null;
245     private boolean useDayNight = true;
246 
Builder()247     public Builder() {}
248 
Builder(ThemeResolver themeResolver)249     public Builder(ThemeResolver themeResolver) {
250       this.defaultTheme = themeResolver.defaultTheme;
251       this.oldestSupportedTheme = themeResolver.oldestSupportedTheme;
252       this.useDayNight = themeResolver.useDayNight;
253     }
254 
setDefaultThemeSupplier(ThemeSupplier defaultThemeSupplier)255     public Builder setDefaultThemeSupplier(ThemeSupplier defaultThemeSupplier) {
256       this.defaultThemeSupplier = defaultThemeSupplier;
257       return this;
258     }
259 
setDefaultTheme(@tyleRes int defaultTheme)260     public Builder setDefaultTheme(@StyleRes int defaultTheme) {
261       this.defaultTheme = defaultTheme;
262       return this;
263     }
264 
setOldestSupportedTheme(String oldestSupportedTheme)265     public Builder setOldestSupportedTheme(String oldestSupportedTheme) {
266       this.oldestSupportedTheme = oldestSupportedTheme;
267       return this;
268     }
269 
setUseDayNight(boolean useDayNight)270     public Builder setUseDayNight(boolean useDayNight) {
271       this.useDayNight = useDayNight;
272       return this;
273     }
274 
build()275     public ThemeResolver build() {
276       return new ThemeResolver(
277           defaultTheme, oldestSupportedTheme, defaultThemeSupplier, useDayNight);
278     }
279   }
280 
281   public interface ThemeSupplier {
getTheme()282     String getTheme();
283   }
284 }
285