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.google.android.setupcompat.util;
18 
19 import android.app.Activity;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.os.Build;
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.VisibleForTesting;
28 import java.util.Arrays;
29 
30 /**
31  * Helper to interact with Wizard Manager in setup wizard, which should be used when a screen is
32  * shown inside the setup flow. This includes things like parsing extras passed by Wizard Manager,
33  * and invoking Wizard Manager to start the next action.
34  */
35 public final class WizardManagerHelper {
36 
37   @VisibleForTesting public static final String ACTION_NEXT = "com.android.wizard.NEXT";
38 
39   // EXTRA_SCRIPT_URI and EXTRA_ACTION_ID are used in setup wizard in versions before M and are
40   // kept for backwards compatibility.
41   @VisibleForTesting static final String EXTRA_SCRIPT_URI = "scriptUri";
42   @VisibleForTesting static final String EXTRA_ACTION_ID = "actionId";
43 
44   @VisibleForTesting static final String EXTRA_WIZARD_BUNDLE = "wizardBundle";
45   private static final String EXTRA_RESULT_CODE = "com.android.setupwizard.ResultCode";
46 
47   /** Extra for notifying an Activity that it is inside the first SetupWizard flow or not. */
48   public static final String EXTRA_IS_FIRST_RUN = "firstRun";
49 
50   /** Extra for notifying an Activity that it is inside the Deferred SetupWizard flow or not. */
51   public static final String EXTRA_IS_DEFERRED_SETUP = "deferredSetup";
52 
53   /** Extra for notifying an Activity that it is inside the "Pre-Deferred Setup" flow. */
54   public static final String EXTRA_IS_PRE_DEFERRED_SETUP = "preDeferredSetup";
55 
56   /** Extra for notifying an Activity that it is inside the "Portal Setup" flow. */
57   public static final String EXTRA_IS_PORTAL_SETUP = "portalSetup";
58 
59   /**
60    * Extra for notifying an Activity that it is inside the any setup flow.
61    *
62    * <p>Apps that target API levels below {@link android.os.Build.VERSION_CODES#Q} is able to
63    * determine whether Activity is inside the any setup flow by one of {@link #EXTRA_IS_FIRST_RUN},
64    * {@link #EXTRA_IS_DEFERRED_SETUP}, and {@link #EXTRA_IS_PRE_DEFERRED_SETUP} is true.
65    */
66   public static final String EXTRA_IS_SETUP_FLOW = "isSetupFlow";
67 
68   public static final String EXTRA_THEME = "theme";
69   public static final String EXTRA_USE_IMMERSIVE_MODE = "useImmersiveMode";
70 
71   public static final String SETTINGS_GLOBAL_DEVICE_PROVISIONED = "device_provisioned";
72   public static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
73 
74   /**
75    * Gets an intent that will invoke the next step of setup wizard.
76    *
77    * @param originalIntent The original intent that was used to start the step, usually via {@link
78    *     Activity#getIntent()}.
79    * @param resultCode The result code of the step. See {@link ResultCodes}.
80    * @return A new intent that can be used with {@link Activity#startActivityForResult(Intent, int)}
81    *     to start the next step of the setup flow.
82    */
getNextIntent(Intent originalIntent, int resultCode)83   public static Intent getNextIntent(Intent originalIntent, int resultCode) {
84     return getNextIntent(originalIntent, resultCode, null);
85   }
86 
87   /**
88    * Gets an intent that will invoke the next step of setup wizard.
89    *
90    * @param originalIntent The original intent that was used to start the step, usually via {@link
91    *     Activity#getIntent()}.
92    * @param resultCode The result code of the step. See {@link ResultCodes}.
93    * @param data An intent containing extra result data.
94    * @return A new intent that can be used with {@link Activity#startActivityForResult(Intent, int)}
95    *     to start the next step of the setup flow.
96    */
getNextIntent(Intent originalIntent, int resultCode, Intent data)97   public static Intent getNextIntent(Intent originalIntent, int resultCode, Intent data) {
98     Intent intent = new Intent(ACTION_NEXT);
99     copyWizardManagerExtras(originalIntent, intent);
100     intent.putExtra(EXTRA_RESULT_CODE, resultCode);
101     if (data != null && data.getExtras() != null) {
102       intent.putExtras(data.getExtras());
103     }
104     intent.putExtra(EXTRA_THEME, originalIntent.getStringExtra(EXTRA_THEME));
105 
106     return intent;
107   }
108 
109   /**
110    * Copies the internal extras used by setup wizard from one intent to another. For low-level use
111    * only, such as when using {@link Intent#FLAG_ACTIVITY_FORWARD_RESULT} to relay to another
112    * intent.
113    *
114    * @param srcIntent Intent to get the wizard manager extras from.
115    * @param dstIntent Intent to copy the wizard manager extras to.
116    */
copyWizardManagerExtras(Intent srcIntent, Intent dstIntent)117   public static void copyWizardManagerExtras(Intent srcIntent, Intent dstIntent) {
118     dstIntent.putExtra(EXTRA_WIZARD_BUNDLE, srcIntent.getBundleExtra(EXTRA_WIZARD_BUNDLE));
119     for (String key :
120         Arrays.asList(
121             EXTRA_IS_FIRST_RUN,
122             EXTRA_IS_DEFERRED_SETUP,
123             EXTRA_IS_PRE_DEFERRED_SETUP,
124             EXTRA_IS_PORTAL_SETUP,
125             EXTRA_IS_SETUP_FLOW)) {
126       dstIntent.putExtra(key, srcIntent.getBooleanExtra(key, false));
127     }
128 
129     for (String key : Arrays.asList(EXTRA_THEME, EXTRA_SCRIPT_URI, EXTRA_ACTION_ID)) {
130       dstIntent.putExtra(key, srcIntent.getStringExtra(key));
131     }
132   }
133 
134   /** @deprecated Use {@link isInitialSetupWizard} instead. */
135   @Deprecated
isSetupWizardIntent(Intent intent)136   public static boolean isSetupWizardIntent(Intent intent) {
137     return intent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false);
138   }
139 
140   /**
141    * Checks whether the current user has completed Setup Wizard. This is true if the current user
142    * has gone through Setup Wizard. The current user may or may not be the device owner and the
143    * device owner may have already completed setup wizard.
144    *
145    * @param context The context to retrieve the settings.
146    * @return true if the current user has completed Setup Wizard.
147    * @see #isDeviceProvisioned(Context)
148    */
isUserSetupComplete(Context context)149   public static boolean isUserSetupComplete(Context context) {
150     if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
151       return Settings.Secure.getInt(
152               context.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0)
153           == 1;
154     } else {
155       // For versions below JB MR1, there are no user profiles. Just return the global device
156       // provisioned state.
157       return Settings.Secure.getInt(
158               context.getContentResolver(), SETTINGS_GLOBAL_DEVICE_PROVISIONED, 0)
159           == 1;
160     }
161   }
162 
163   /**
164    * Checks whether the device is provisioned. This means that the device has gone through Setup
165    * Wizard at least once. Note that the user can still be in Setup Wizard even if this is true, for
166    * a secondary user profile triggered through Settings > Add account.
167    *
168    * @param context The context to retrieve the settings.
169    * @return true if the device is provisioned.
170    * @see #isUserSetupComplete(Context)
171    */
isDeviceProvisioned(Context context)172   public static boolean isDeviceProvisioned(Context context) {
173     if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
174       return Settings.Global.getInt(
175               context.getContentResolver(), SETTINGS_GLOBAL_DEVICE_PROVISIONED, 0)
176           == 1;
177     } else {
178       return Settings.Secure.getInt(
179               context.getContentResolver(), SETTINGS_GLOBAL_DEVICE_PROVISIONED, 0)
180           == 1;
181     }
182   }
183 
184   /**
185    * Checks whether an intent is running in the deferred setup wizard flow.
186    *
187    * @param originalIntent The original intent that was used to start the step, usually via {@link
188    *     Activity#getIntent()}.
189    * @return true if the intent passed in was running in deferred setup wizard.
190    */
isDeferredSetupWizard(Intent originalIntent)191   public static boolean isDeferredSetupWizard(Intent originalIntent) {
192     return originalIntent != null && originalIntent.getBooleanExtra(EXTRA_IS_DEFERRED_SETUP, false);
193   }
194 
195   /**
196    * Checks whether an intent is running in "pre-deferred" setup wizard flow.
197    *
198    * @param originalIntent The original intent that was used to start the step, usually via {@link
199    *     Activity#getIntent()}.
200    * @return true if the intent passed in was running in "pre-deferred" setup wizard.
201    */
isPreDeferredSetupWizard(Intent originalIntent)202   public static boolean isPreDeferredSetupWizard(Intent originalIntent) {
203     return originalIntent != null
204         && originalIntent.getBooleanExtra(EXTRA_IS_PRE_DEFERRED_SETUP, false);
205   }
206 
207   /**
208    * Checks whether an intent is is running in the initial setup wizard flow.
209    *
210    * @param intent The intent to be checked, usually from {@link Activity#getIntent()}.
211    * @return true if the intent passed in was intended to be used with setup wizard.
212    */
isInitialSetupWizard(Intent intent)213   public static boolean isInitialSetupWizard(Intent intent) {
214     return intent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false);
215   }
216 
217   /**
218    * Returns true if the intent passed in indicates that it is running in any setup wizard flow,
219    * including initial setup and deferred setup etc.
220    *
221    * @param originalIntent The original intent that was used to start the step, usually via {@link
222    *     Activity#getIntent()}.
223    */
isAnySetupWizard(@ullable Intent originalIntent)224   public static boolean isAnySetupWizard(@Nullable Intent originalIntent) {
225     if (originalIntent == null) {
226       return false;
227     }
228 
229     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
230       return originalIntent.getBooleanExtra(EXTRA_IS_SETUP_FLOW, false);
231     } else {
232       return isInitialSetupWizard(originalIntent)
233           || isPreDeferredSetupWizard(originalIntent)
234           || isDeferredSetupWizard(originalIntent);
235     }
236   }
237 
WizardManagerHelper()238   private WizardManagerHelper() {}
239 }
240