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 string theme. If the specified string theme is older than the
90    * oldest supported theme, the default will be returned instead. Note that the default theme is
91    * returned without processing -- it may not be a DayNight theme even if {@link #useDayNight} is
92    * true.
93    *
94    * @deprecated Use {@link #resolve(String, boolean)} instead
95    */
96   @Deprecated
97   @StyleRes
resolve(@ullable String theme)98   public int resolve(@Nullable String theme) {
99     return resolve(theme, /* suppressDayNight= */ false);
100   }
101 
102   /**
103    * Returns the style for the given string theme. If the specified string theme is older than the
104    * oldest supported theme, the default will be returned instead. Note that the default theme is
105    * returned without processing -- it may not be a DayNight theme even if {@link #useDayNight} is
106    * true.
107    */
108   @StyleRes
resolve(@ullable String theme, boolean suppressDayNight)109   public int resolve(@Nullable String theme, boolean suppressDayNight) {
110     int themeResource =
111         useDayNight && !suppressDayNight ? getDayNightThemeRes(theme) : getThemeRes(theme);
112     if (themeResource == 0) {
113       if (defaultThemeSupplier != null) {
114         theme = defaultThemeSupplier.getTheme();
115         themeResource =
116             useDayNight && !suppressDayNight ? getDayNightThemeRes(theme) : getThemeRes(theme);
117       }
118       if (themeResource == 0) {
119         return defaultTheme;
120       }
121     }
122 
123     if (oldestSupportedTheme != null && compareThemes(theme, oldestSupportedTheme) < 0) {
124       return defaultTheme;
125     }
126     return themeResource;
127   }
128 
129   /** Reads the theme from the intent, and applies the resolved theme to the activity. */
applyTheme(Activity activity)130   public void applyTheme(Activity activity) {
131     activity.setTheme(resolve(activity.getIntent()));
132   }
133 
134   /**
135    * Returns the corresponding DayNight theme resource ID for the given string theme. DayNight
136    * themes are themes that will be either light or dark depending on the system setting. For
137    * example, the string {@link ThemeHelper#THEME_GLIF_LIGHT} will return
138    * {@code @style/SudThemeGlif.DayNight}.
139    */
140   @StyleRes
getDayNightThemeRes(@ullable String theme)141   private static int getDayNightThemeRes(@Nullable String theme) {
142     if (theme != null) {
143       switch (theme) {
144         case ThemeHelper.THEME_GLIF_V3_LIGHT:
145         case ThemeHelper.THEME_GLIF_V3:
146           return R.style.SudThemeGlifV3_DayNight;
147         case ThemeHelper.THEME_GLIF_V2_LIGHT:
148         case ThemeHelper.THEME_GLIF_V2:
149           return R.style.SudThemeGlifV2_DayNight;
150         case ThemeHelper.THEME_GLIF_LIGHT:
151         case ThemeHelper.THEME_GLIF:
152           return R.style.SudThemeGlif_DayNight;
153         case ThemeHelper.THEME_MATERIAL_LIGHT:
154         case ThemeHelper.THEME_MATERIAL:
155           return R.style.SudThemeMaterial_DayNight;
156         default:
157           // fall through
158       }
159     }
160     return 0;
161   }
162 
163   /**
164    * Returns the theme resource ID for the given string theme. For example, the string {@link
165    * ThemeHelper#THEME_GLIF_LIGHT} will return {@code @style/SudThemeGlif.Light}.
166    */
167   @StyleRes
getThemeRes(@ullable String theme)168   private static int getThemeRes(@Nullable String theme) {
169     if (theme != null) {
170       switch (theme) {
171         case ThemeHelper.THEME_GLIF_V3_LIGHT:
172           return R.style.SudThemeGlifV3_Light;
173         case ThemeHelper.THEME_GLIF_V3:
174           return R.style.SudThemeGlifV3;
175         case ThemeHelper.THEME_GLIF_V2_LIGHT:
176           return R.style.SudThemeGlifV2_Light;
177         case ThemeHelper.THEME_GLIF_V2:
178           return R.style.SudThemeGlifV2;
179         case ThemeHelper.THEME_GLIF_LIGHT:
180           return R.style.SudThemeGlif_Light;
181         case ThemeHelper.THEME_GLIF:
182           return R.style.SudThemeGlif;
183         case ThemeHelper.THEME_MATERIAL_LIGHT:
184           return R.style.SudThemeMaterial_Light;
185         case ThemeHelper.THEME_MATERIAL:
186           return R.style.SudThemeMaterial;
187         default:
188           // fall through
189       }
190     }
191     return 0;
192   }
193 
194   /** Compares whether the versions of {@code theme1} and {@code theme2} to check which is newer. */
compareThemes(String theme1, String theme2)195   private static int compareThemes(String theme1, String theme2) {
196     return Integer.valueOf(getThemeVersion(theme1)).compareTo(getThemeVersion(theme2));
197   }
198 
199   /**
200    * Returns the version of the theme. The absolute number of the theme version is not defined, but
201    * a larger number in the version indicates a newer theme.
202    */
getThemeVersion(String theme)203   private static int getThemeVersion(String theme) {
204     if (theme != null) {
205       switch (theme) {
206         case ThemeHelper.THEME_GLIF_V3_LIGHT:
207         case ThemeHelper.THEME_GLIF_V3:
208           return 4;
209         case ThemeHelper.THEME_GLIF_V2_LIGHT:
210         case ThemeHelper.THEME_GLIF_V2:
211           return 3;
212         case ThemeHelper.THEME_GLIF_LIGHT:
213         case ThemeHelper.THEME_GLIF:
214           return 2;
215         case ThemeHelper.THEME_MATERIAL_LIGHT:
216         case ThemeHelper.THEME_MATERIAL:
217           return 1;
218         default:
219           // fall through
220       }
221     }
222     return -1;
223   }
224 
225   /** Builder class for {@link ThemeResolver}. */
226   public static class Builder {
227     private ThemeSupplier defaultThemeSupplier;
228     @StyleRes private int defaultTheme = R.style.SudThemeGlif_DayNight;
229     @Nullable private String oldestSupportedTheme = null;
230     private boolean useDayNight = true;
231 
Builder()232     public Builder() {}
233 
Builder(ThemeResolver themeResolver)234     public Builder(ThemeResolver themeResolver) {
235       this.defaultTheme = themeResolver.defaultTheme;
236       this.oldestSupportedTheme = themeResolver.oldestSupportedTheme;
237       this.useDayNight = themeResolver.useDayNight;
238     }
239 
setDefaultThemeSupplier(ThemeSupplier defaultThemeSupplier)240     public Builder setDefaultThemeSupplier(ThemeSupplier defaultThemeSupplier) {
241       this.defaultThemeSupplier = defaultThemeSupplier;
242       return this;
243     }
244 
setDefaultTheme(@tyleRes int defaultTheme)245     public Builder setDefaultTheme(@StyleRes int defaultTheme) {
246       this.defaultTheme = defaultTheme;
247       return this;
248     }
249 
setOldestSupportedTheme(String oldestSupportedTheme)250     public Builder setOldestSupportedTheme(String oldestSupportedTheme) {
251       this.oldestSupportedTheme = oldestSupportedTheme;
252       return this;
253     }
254 
setUseDayNight(boolean useDayNight)255     public Builder setUseDayNight(boolean useDayNight) {
256       this.useDayNight = useDayNight;
257       return this;
258     }
259 
build()260     public ThemeResolver build() {
261       return new ThemeResolver(
262           defaultTheme, oldestSupportedTheme, defaultThemeSupplier, useDayNight);
263     }
264   }
265 
266   public interface ThemeSupplier {
getTheme()267     String getTheme();
268   }
269 }
270