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