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.android.setupwizardlib.util; 18 19 import android.app.Activity; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.res.Resources.Theme; 23 import android.os.Build.VERSION; 24 import android.os.Build.VERSION_CODES; 25 import android.provider.Settings; 26 import androidx.annotation.Nullable; 27 import androidx.annotation.StyleRes; 28 import androidx.annotation.VisibleForTesting; 29 import java.util.Arrays; 30 31 /** 32 * Helper to interact with Wizard Manager in setup wizard, which should be used when a screen is 33 * shown inside the setup flow. This includes things like parsing extras passed by Wizard Manager, 34 * and invoking Wizard Manager to start the next action. 35 */ 36 public class WizardManagerHelper { 37 38 private static final String ACTION_NEXT = "com.android.wizard.NEXT"; 39 40 // EXTRA_SCRIPT_URI and EXTRA_ACTION_ID are used in setup wizard in versions before M and are 41 // kept for backwards compatibility. 42 @VisibleForTesting static final String EXTRA_SCRIPT_URI = "scriptUri"; 43 @VisibleForTesting static final String EXTRA_ACTION_ID = "actionId"; 44 45 @VisibleForTesting static final String EXTRA_WIZARD_BUNDLE = "wizardBundle"; 46 private static final String EXTRA_RESULT_CODE = "com.android.setupwizard.ResultCode"; 47 @VisibleForTesting static final String EXTRA_IS_FIRST_RUN = "firstRun"; 48 @VisibleForTesting static final String EXTRA_IS_DEFERRED_SETUP = "deferredSetup"; 49 @VisibleForTesting static final String EXTRA_IS_PRE_DEFERRED_SETUP = "preDeferredSetup"; 50 @VisibleForTesting public static final String EXTRA_IS_SETUP_FLOW = "isSetupFlow"; 51 52 public static final String EXTRA_THEME = "theme"; 53 public static final String EXTRA_USE_IMMERSIVE_MODE = "useImmersiveMode"; 54 55 public static final String SETTINGS_GLOBAL_DEVICE_PROVISIONED = "device_provisioned"; 56 public static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete"; 57 58 public static final String THEME_HOLO = "holo"; 59 public static final String THEME_HOLO_LIGHT = "holo_light"; 60 public static final String THEME_MATERIAL = "material"; 61 public static final String THEME_MATERIAL_LIGHT = "material_light"; 62 63 /** 64 * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the dark variant of the theme 65 * used in setup wizard for Nougat MR1. 66 */ 67 public static final String THEME_GLIF = "glif"; 68 69 /** 70 * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the default theme used in 71 * setup wizard for Nougat MR1. 72 */ 73 public static final String THEME_GLIF_LIGHT = "glif_light"; 74 75 /** 76 * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the dark variant of the theme 77 * used in setup wizard for O DR. 78 */ 79 public static final String THEME_GLIF_V2 = "glif_v2"; 80 81 /** 82 * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the default theme used in 83 * setup wizard for O DR. 84 */ 85 public static final String THEME_GLIF_V2_LIGHT = "glif_v2_light"; 86 87 /** 88 * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the dark variant of the theme 89 * used in setup wizard for P. 90 */ 91 public static final String THEME_GLIF_V3 = "glif_v3"; 92 93 /** 94 * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the default theme used in 95 * setup wizard for P. 96 */ 97 public static final String THEME_GLIF_V3_LIGHT = "glif_v3_light"; 98 99 /** 100 * Get an intent that will invoke the next step of setup wizard. 101 * 102 * @param originalIntent The original intent that was used to start the step, usually via {@link 103 * android.app.Activity#getIntent()}. 104 * @param resultCode The result code of the step. See {@link ResultCodes}. 105 * @return A new intent that can be used with {@link 106 * android.app.Activity#startActivityForResult(Intent, int)} to start the next step of the 107 * setup flow. 108 */ getNextIntent(Intent originalIntent, int resultCode)109 public static Intent getNextIntent(Intent originalIntent, int resultCode) { 110 return getNextIntent(originalIntent, resultCode, null); 111 } 112 113 /** 114 * Get an intent that will invoke the next step of setup wizard. 115 * 116 * @param originalIntent The original intent that was used to start the step, usually via {@link 117 * android.app.Activity#getIntent()}. 118 * @param resultCode The result code of the step. See {@link ResultCodes}. 119 * @param data An intent containing extra result data. 120 * @return A new intent that can be used with {@link 121 * android.app.Activity#startActivityForResult(Intent, int)} to start the next step of the 122 * setup flow. 123 */ getNextIntent(Intent originalIntent, int resultCode, Intent data)124 public static Intent getNextIntent(Intent originalIntent, int resultCode, Intent data) { 125 Intent intent = new Intent(ACTION_NEXT); 126 copyWizardManagerExtras(originalIntent, intent); 127 intent.putExtra(EXTRA_RESULT_CODE, resultCode); 128 if (data != null && data.getExtras() != null) { 129 intent.putExtras(data.getExtras()); 130 } 131 intent.putExtra(EXTRA_THEME, originalIntent.getStringExtra(EXTRA_THEME)); 132 133 return intent; 134 } 135 136 /** 137 * Copy the internal extras used by setup wizard from one intent to another. For low-level use 138 * only, such as when using {@link Intent#FLAG_ACTIVITY_FORWARD_RESULT} to relay to another 139 * intent. 140 * 141 * @param srcIntent Intent to get the wizard manager extras from. 142 * @param dstIntent Intent to copy the wizard manager extras to. 143 */ copyWizardManagerExtras(Intent srcIntent, Intent dstIntent)144 public static void copyWizardManagerExtras(Intent srcIntent, Intent dstIntent) { 145 dstIntent.putExtra(EXTRA_WIZARD_BUNDLE, srcIntent.getBundleExtra(EXTRA_WIZARD_BUNDLE)); 146 for (String key : 147 Arrays.asList( 148 EXTRA_IS_FIRST_RUN, 149 EXTRA_IS_DEFERRED_SETUP, 150 EXTRA_IS_PRE_DEFERRED_SETUP, 151 EXTRA_IS_SETUP_FLOW)) { 152 dstIntent.putExtra(key, srcIntent.getBooleanExtra(key, false)); 153 } 154 155 for (String key : Arrays.asList(EXTRA_THEME, EXTRA_SCRIPT_URI, EXTRA_ACTION_ID)) { 156 dstIntent.putExtra(key, srcIntent.getStringExtra(key)); 157 } 158 } 159 160 /** 161 * Check whether an intent is intended to be used within the setup wizard flow. 162 * 163 * @param intent The intent to be checked, usually from {@link android.app.Activity#getIntent()}. 164 * @return true if the intent passed in was intended to be used with setup wizard. 165 */ isSetupWizardIntent(Intent intent)166 public static boolean isSetupWizardIntent(Intent intent) { 167 return intent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false); 168 } 169 170 /** 171 * Checks whether the current user has completed Setup Wizard. This is true if the current user 172 * has gone through Setup Wizard. The current user may or may not be the device owner and the 173 * device owner may have already completed setup wizard. 174 * 175 * @param context The context to retrieve the settings. 176 * @return true if the current user has completed Setup Wizard. 177 * @see #isDeviceProvisioned(android.content.Context) 178 */ isUserSetupComplete(Context context)179 public static boolean isUserSetupComplete(Context context) { 180 if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { 181 return Settings.Secure.getInt( 182 context.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) 183 == 1; 184 } else { 185 // For versions below JB MR1, there are no user profiles. Just return the global device 186 // provisioned state. 187 return Settings.Secure.getInt( 188 context.getContentResolver(), SETTINGS_GLOBAL_DEVICE_PROVISIONED, 0) 189 == 1; 190 } 191 } 192 193 /** 194 * Checks whether the device is provisioned. This means that the device has gone through Setup 195 * Wizard at least once. Note that the user can still be in Setup Wizard even if this is true, for 196 * a secondary user profile triggered through Settings > Add account. 197 * 198 * @param context The context to retrieve the settings. 199 * @return true if the device is provisioned. 200 * @see #isUserSetupComplete(android.content.Context) 201 */ isDeviceProvisioned(Context context)202 public static boolean isDeviceProvisioned(Context context) { 203 if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { 204 return Settings.Global.getInt( 205 context.getContentResolver(), SETTINGS_GLOBAL_DEVICE_PROVISIONED, 0) 206 == 1; 207 } else { 208 return Settings.Secure.getInt( 209 context.getContentResolver(), SETTINGS_GLOBAL_DEVICE_PROVISIONED, 0) 210 == 1; 211 } 212 } 213 214 /** 215 * Checks whether an intent is running in the deferred setup wizard flow. 216 * 217 * @param originalIntent The original intent that was used to start the step, usually via {@link 218 * android.app.Activity#getIntent()}. 219 * @return true if the intent passed in was running in deferred setup wizard. 220 */ isDeferredSetupWizard(Intent originalIntent)221 public static boolean isDeferredSetupWizard(Intent originalIntent) { 222 return originalIntent != null && originalIntent.getBooleanExtra(EXTRA_IS_DEFERRED_SETUP, false); 223 } 224 225 /** 226 * Checks whether an intent is running in "pre-deferred" setup wizard flow. 227 * 228 * @param originalIntent The original intent that was used to start the step, usually via {@link 229 * android.app.Activity#getIntent()}. 230 * @return true if the intent passed in was running in "pre-deferred" setup wizard. 231 */ isPreDeferredSetupWizard(Intent originalIntent)232 public static boolean isPreDeferredSetupWizard(Intent originalIntent) { 233 return originalIntent != null 234 && originalIntent.getBooleanExtra(EXTRA_IS_PRE_DEFERRED_SETUP, false); 235 } 236 237 /** 238 * Checks the intent whether the extra indicates that the light theme should be used or not. If 239 * the theme is not specified in the intent, or the theme specified is unknown, the value def will 240 * be returned. Note that day-night themes are not taken into account by this method. 241 * 242 * @param intent The intent used to start the activity, which the theme extra will be read from. 243 * @param def The default value if the theme is not specified. 244 * @return True if the activity started by the given intent should use light theme. 245 */ isLightTheme(Intent intent, boolean def)246 public static boolean isLightTheme(Intent intent, boolean def) { 247 final String theme = intent.getStringExtra(EXTRA_THEME); 248 return isLightTheme(theme, def); 249 } 250 251 /** 252 * Checks whether {@code theme} represents a light or dark theme. If the theme specified is 253 * unknown, the value def will be returned. Note that day-night themes are not taken into account 254 * by this method. 255 * 256 * @param theme The theme as specified from an intent sent from setup wizard. 257 * @param def The default value if the theme is not known. 258 * @return True if {@code theme} represents a light theme. 259 */ isLightTheme(String theme, boolean def)260 public static boolean isLightTheme(String theme, boolean def) { 261 if (THEME_HOLO_LIGHT.equals(theme) 262 || THEME_MATERIAL_LIGHT.equals(theme) 263 || THEME_GLIF_LIGHT.equals(theme) 264 || THEME_GLIF_V2_LIGHT.equals(theme) 265 || THEME_GLIF_V3_LIGHT.equals(theme)) { 266 return true; 267 } else if (THEME_HOLO.equals(theme) 268 || THEME_MATERIAL.equals(theme) 269 || THEME_GLIF.equals(theme) 270 || THEME_GLIF_V2.equals(theme) 271 || THEME_GLIF_V3.equals(theme)) { 272 return false; 273 } else { 274 return def; 275 } 276 } 277 278 /** 279 * Gets the theme style resource defined by this library for the theme specified in the given 280 * intent. For example, for THEME_GLIF_LIGHT, the theme @style/SuwThemeGlif.Light is returned. 281 * 282 * @param intent The intent passed by setup wizard, or one with the theme propagated along using 283 * {@link #copyWizardManagerExtras(Intent, Intent)}. 284 * @return The style corresponding to the theme in the given intent, or {@code defaultTheme} if 285 * the given theme is not recognized. 286 * @see #getThemeRes(String, int) 287 * @deprecated it is recommended to use {@link ThemeResolver} which allows setting the default 288 * theme in one place and applying it to multiple screens. 289 */ 290 @Deprecated getThemeRes(Intent intent, @StyleRes int defaultTheme)291 public static @StyleRes int getThemeRes(Intent intent, @StyleRes int defaultTheme) { 292 return new ThemeResolver.Builder(ThemeResolver.getDefault()) 293 .setDefaultTheme(defaultTheme) 294 .setUseDayNight(false) 295 .build() 296 .resolve(intent); 297 } 298 299 /** 300 * Gets the theme style resource defined by this library for the theme specified in the given 301 * intent. For example, for THEME_GLIF_LIGHT, the theme @style/SuwThemeGlif.Light is returned. 302 * 303 * @param intent The intent passed by setup wizard, or one with the theme propagated along using 304 * {@link #copyWizardManagerExtras(Intent, Intent)}. 305 * @return The style corresponding to the theme in the given intent, or {@code defaultTheme} if 306 * the given theme is not recognized. Return the {@code defaultTheme} if the specified theme 307 * is older than the oldest supported one. 308 * @see #getThemeRes(String, int) 309 * @deprecated it is recommended to use {@link ThemeResolver} which allows setting the default 310 * theme and oldest supported theme in one place and applying it to multiple screens. 311 */ 312 @Deprecated getThemeRes( Intent intent, @StyleRes int defaultTheme, @Nullable String oldestSupportedTheme)313 public static @StyleRes int getThemeRes( 314 Intent intent, @StyleRes int defaultTheme, @Nullable String oldestSupportedTheme) { 315 return new ThemeResolver.Builder(ThemeResolver.getDefault()) 316 .setDefaultTheme(defaultTheme) 317 .setUseDayNight(false) 318 .setOldestSupportedTheme(oldestSupportedTheme) 319 .build() 320 .resolve(intent); 321 } 322 323 /** 324 * Gets the theme style resource defined by this library for the given theme name. For example, 325 * for THEME_GLIF_LIGHT, the theme @style/SuwThemeGlif.Light is returned. 326 * 327 * <p>If you require extra theme attributes but want to ensure forward compatibility with new 328 * themes added here, consider overriding {@link android.app.Activity#onApplyThemeResource} in 329 * your activity and call {@link Theme#applyStyle(int, boolean)} using your theme overlay. 330 * 331 * <pre>{@code 332 * protected void onApplyThemeResource(Theme theme, int resid, boolean first) { 333 * super.onApplyThemeResource(theme, resid, first); 334 * theme.applyStyle(R.style.MyThemeOverlay, true); 335 * } 336 * }</pre> 337 * 338 * @param theme The string representation of the theme. 339 * @return The style corresponding to the given {@code theme}, or {@code defaultTheme} if the 340 * given theme is not recognized. 341 * @deprecated it is recommended to use {@link ThemeResolver} which allows setting the default 342 * theme in one place and applying it to multiple screens. 343 */ 344 @Deprecated getThemeRes( String theme, @StyleRes int defaultTheme, @Nullable String oldestSupportedTheme)345 public static @StyleRes int getThemeRes( 346 String theme, @StyleRes int defaultTheme, @Nullable String oldestSupportedTheme) { 347 return new ThemeResolver.Builder(ThemeResolver.getDefault()) 348 .setDefaultTheme(defaultTheme) 349 .setUseDayNight(false) 350 .setOldestSupportedTheme(oldestSupportedTheme) 351 .build() 352 .resolve(theme); 353 } 354 355 /** 356 * Gets the theme style resource defined by this library for the given theme name. For example, 357 * for THEME_GLIF_LIGHT, the theme @style/SuwThemeGlif.Light is returned. 358 * 359 * <p>If you require extra theme attributes but want to ensure forward compatibility with new 360 * themes added here, consider overriding {@link android.app.Activity#onApplyThemeResource} in 361 * your activity and call {@link Theme#applyStyle(int, boolean)} using your theme overlay. 362 * 363 * <pre>{@code 364 * protected void onApplyThemeResource(Theme theme, int resid, boolean first) { 365 * super.onApplyThemeResource(theme, resid, first); 366 * theme.applyStyle(R.style.MyThemeOverlay, true); 367 * } 368 * }</pre> 369 * 370 * @param theme The string representation of the theme. 371 * @return The style corresponding to the given {@code theme}, or {@code defaultTheme} if the 372 * given theme is not recognized. 373 * @deprecated it is recommended to use {@link ThemeResolver} which allows setting the default 374 * theme in one place and applying it to multiple screens. 375 */ 376 @Deprecated getThemeRes(@ullable String theme, @StyleRes int defaultTheme)377 public static @StyleRes int getThemeRes(@Nullable String theme, @StyleRes int defaultTheme) { 378 return new ThemeResolver.Builder(ThemeResolver.getDefault()) 379 .setDefaultTheme(defaultTheme) 380 .setUseDayNight(false) 381 .build() 382 .resolve(theme); 383 } 384 385 /** 386 * Reads the theme from the intent, and applies the theme to the activity as resolved by {@link 387 * ThemeResolver#getDefault()}. 388 * 389 * <p>If you require extra theme attributes, consider overriding {@link 390 * android.app.Activity#onApplyThemeResource} in your activity and call {@link 391 * Theme#applyStyle(int, boolean)} using your theme overlay. 392 * 393 * <pre>{@code 394 * protected void onApplyThemeResource(Theme theme, int resid, boolean first) { 395 * super.onApplyThemeResource(theme, resid, first); 396 * theme.applyStyle(R.style.MyThemeOverlay, true); 397 * } 398 * }</pre> 399 * 400 * @param activity the activity to get the intent from and apply the resulting theme to. 401 */ applyTheme(Activity activity)402 public static void applyTheme(Activity activity) { 403 ThemeResolver.getDefault().applyTheme(activity); 404 } 405 } 406