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