• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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