1 /*
2  * Copyright 2019, 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.managedprovisioning.provisioning;
18 
19 import android.annotation.IntDef;
20 import android.app.Activity;
21 import android.app.DialogFragment;
22 import android.content.Intent;
23 import android.os.Bundle;
24 
25 import androidx.annotation.VisibleForTesting;
26 
27 import com.android.managedprovisioning.ManagedProvisioningScreens;
28 import com.android.managedprovisioning.R;
29 import com.android.managedprovisioning.common.DialogBuilder;
30 import com.android.managedprovisioning.common.ProvisionLogger;
31 import com.android.managedprovisioning.common.SettingsFacade;
32 import com.android.managedprovisioning.common.SetupGlifLayoutActivity;
33 import com.android.managedprovisioning.common.SimpleDialog;
34 import com.android.managedprovisioning.common.ThemeHelper;
35 import com.android.managedprovisioning.common.Utils;
36 import com.android.managedprovisioning.model.ProvisioningParams;
37 import com.android.managedprovisioning.util.LazyStringResource;
38 
39 import com.google.android.setupcompat.logging.ScreenKey;
40 import com.google.android.setupcompat.logging.SetupMetric;
41 import com.google.android.setupcompat.logging.SetupMetricsLogger;
42 import com.google.android.setupcompat.util.WizardManagerHelper;
43 
44 import java.lang.annotation.Retention;
45 import java.lang.annotation.RetentionPolicy;
46 
47 /**
48  * Abstract class for provisioning activity.
49  *
50  * <p>This activity registers for updates of the provisioning process from the
51  * {@link ProvisioningManager}. It shows progress updates as provisioning progresses and handles
52  * showing of cancel and error dialogs.</p>
53  */
54 // TODO(b/123288153): Rearrange provisioning activity, manager, controller classes.
55 public abstract class AbstractProvisioningActivity extends SetupGlifLayoutActivity
56         implements SimpleDialog.SimpleDialogListener, ProvisioningManagerCallback {
57 
58     private static final String KEY_ACTIVITY_STATE = "activity-state";
59 
60     static final int STATE_PROVISIONING_INTIIALIZING = 1;
61     static final int STATE_PROVISIONING_STARTED = 2;
62     static final int STATE_PROVISIONING_COMPLETED = 3;
63     static final int STATE_PROVISIONING_FINALIZED = 4;
64     static final int SETUP_METRIC_DEFAULT_ERROR_CODE = -1;
65     protected ScreenKey mScreenKey;
66     protected String setupMetricScreenName= "DefaultScreenName";
67 
68 
69     @Retention(RetentionPolicy.SOURCE)
70     @IntDef({STATE_PROVISIONING_INTIIALIZING,
71             STATE_PROVISIONING_STARTED,
72             STATE_PROVISIONING_COMPLETED,
73             STATE_PROVISIONING_FINALIZED})
74     private @interface ProvisioningState {}
75 
76     @VisibleForTesting static final String ERROR_DIALOG_OK = "ErrorDialogOk";
77     @VisibleForTesting static final String ERROR_DIALOG_RESET = "ErrorDialogReset";
78     @VisibleForTesting static final String CANCEL_PROVISIONING_DIALOG_OK
79             = "CancelProvisioningDialogOk";
80     @VisibleForTesting static final String CANCEL_PROVISIONING_DIALOG_RESET
81             = "CancelProvisioningDialogReset";
82 
83     protected ProvisioningParams mParams;
84     protected @ProvisioningState int mState;
85 
86     @VisibleForTesting
AbstractProvisioningActivity( Utils utils, SettingsFacade settingsFacade, ThemeHelper themeHelper)87     protected AbstractProvisioningActivity(
88             Utils utils, SettingsFacade settingsFacade, ThemeHelper themeHelper) {
89         super(utils, settingsFacade, themeHelper);
90     }
91 
92     // Lazily initialize ProvisioningManager, since we can't call in ProvisioningManager.getInstance
93     // in constructor as base context is not available in constructor.
getProvisioningManager()94     protected abstract ProvisioningManagerInterface getProvisioningManager();
95     // Show the dialog when user press back button while provisioning.
decideCancelProvisioningDialog()96     protected abstract void decideCancelProvisioningDialog();
97 
98     @Override
onCreate(Bundle savedInstanceState)99     protected void onCreate(Bundle savedInstanceState) {
100         // initialize params so they're accessible for prechecks in onCreate
101         mParams = getIntent().getParcelableExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS);
102         mScreenKey =  ScreenKey.of(setupMetricScreenName, this);
103         super.onCreate(savedInstanceState);
104 
105         if (savedInstanceState != null) {
106             mState = savedInstanceState.getInt(KEY_ACTIVITY_STATE,
107                     STATE_PROVISIONING_INTIIALIZING);
108         } else {
109             mState = STATE_PROVISIONING_INTIIALIZING;
110         }
111 
112         if (mState == STATE_PROVISIONING_INTIIALIZING) {
113             getProvisioningManager().maybeStartProvisioning(mParams);
114             mState = STATE_PROVISIONING_STARTED;
115         }
116     }
117 
118     @Override
onSaveInstanceState(Bundle outState)119     protected void onSaveInstanceState(Bundle outState) {
120         super.onSaveInstanceState(outState);
121         outState.putInt(KEY_ACTIVITY_STATE, mState);
122     }
123 
124     @Override
onResume()125     protected void onResume() {
126         super.onResume();
127         if (!isAnyDialogAdded()) {
128             getProvisioningManager().registerListener(this);
129         }
130     }
131 
isAnyDialogAdded()132     private boolean isAnyDialogAdded() {
133         return isDialogAdded(ERROR_DIALOG_OK)
134                 || isDialogAdded(ERROR_DIALOG_RESET)
135                 || isDialogAdded(CANCEL_PROVISIONING_DIALOG_OK)
136                 || isDialogAdded(CANCEL_PROVISIONING_DIALOG_RESET);
137     }
138 
139     @Override
onPause()140     public void onPause() {
141         getProvisioningManager().unregisterListener(this);
142         super.onPause();
143     }
144 
145     @Override
onBackPressed()146     public void onBackPressed() {
147         decideCancelProvisioningDialog();
148     }
149 
showCancelProvisioningDialog(boolean resetRequired)150     protected void showCancelProvisioningDialog(boolean resetRequired) {
151         if (resetRequired) {
152             showDialog(
153                     mUtils.createCancelProvisioningResetDialogBuilder(
154                             getApplicationContext()), CANCEL_PROVISIONING_DIALOG_RESET);
155         } else {
156             showDialog(mUtils.createCancelProvisioningDialogBuilder(),
157                     CANCEL_PROVISIONING_DIALOG_OK);
158         }
159     }
160 
startResetActivity()161     protected void startResetActivity() {
162         final Intent intent =
163             new Intent(this, getActivityForScreen(ManagedProvisioningScreens.RESET_DEVICE));
164         WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent);
165         getTransitionHelper().startActivityWithTransition(this, intent);
166     }
167 
168     @Override
error(int titleId, int messageId, boolean resetRequired)169     public void error(int titleId, int messageId, boolean resetRequired) {
170         if (messageId == R.string.fully_managed_device_unsupported_DPC_in_headless_mode_subheader) {
171             ProvisionLogger.logd("This admin app does not support fully managed mode on " +
172                 "headless system user devices");
173             startResetActivity();
174             return;
175         }
176         SimpleDialog.Builder dialogBuilder = new SimpleDialog.Builder()
177                 .setTitle(titleId)
178                 .setMessage(messageId)
179                 .setCancelable(false)
180                 .setPositiveButtonMessage(resetRequired
181                         ? R.string.reset : android.R.string.ok);
182 
183         showDialog(dialogBuilder, resetRequired ? ERROR_DIALOG_RESET : ERROR_DIALOG_OK);
184     }
185 
186     @Override
error(int titleId, String errorMessage, boolean resetRequired)187     public void error(int titleId, String errorMessage, boolean resetRequired) {
188         if (errorMessage.equals(getString(R.string.fully_managed_device_unsupported_DPC_in_headless_mode_subheader))) {
189             ProvisionLogger.logd("This admin app does not support fully managed mode on " +
190                 "headless system user devices");
191             startResetActivity();
192             return;
193         }
194         SimpleDialog.Builder dialogBuilder =
195                 new SimpleDialog.Builder()
196                         .setTitle(titleId)
197                         .setMessage(LazyStringResource.of(errorMessage))
198                         .setCancelable(false)
199                         .setPositiveButtonMessage(
200                                 resetRequired ? R.string.reset : android.R.string.ok);
201 
202         showDialog(dialogBuilder, resetRequired ? ERROR_DIALOG_RESET : ERROR_DIALOG_OK);
203     }
204 
205     @Override
showDialog(DialogBuilder builder, String tag)206     protected void showDialog(DialogBuilder builder, String tag) {
207         SetupMetricsLogger.logMetrics(this, mScreenKey,
208                 SetupMetric.ofError(setupMetricScreenName + ": " + tag,
209                         SETUP_METRIC_DEFAULT_ERROR_CODE ));
210         // Whenever a dialog is shown, stop listening for further updates
211         getProvisioningManager().unregisterListener(this);
212         super.showDialog(builder, tag);
213     }
214 
onProvisioningAborted()215     private void onProvisioningAborted() {
216         setResult(Activity.RESULT_CANCELED);
217         getTransitionHelper().finishActivity(this);
218     }
219 
220     @Override
onNegativeButtonClick(DialogFragment dialog)221     public void onNegativeButtonClick(DialogFragment dialog) {
222         switch (dialog.getTag()) {
223             case CANCEL_PROVISIONING_DIALOG_OK:
224             case CANCEL_PROVISIONING_DIALOG_RESET:
225                 dialog.dismiss();
226                 break;
227             default:
228                 SimpleDialog.throwButtonClickHandlerNotImplemented(dialog);
229         }
230         getProvisioningManager().registerListener(this);
231     }
232 
233     @Override
onPositiveButtonClick(DialogFragment dialog)234     public void onPositiveButtonClick(DialogFragment dialog) {
235         switch (dialog.getTag()) {
236             case CANCEL_PROVISIONING_DIALOG_OK:
237                 getProvisioningManager().cancelProvisioning();
238                 onProvisioningAborted();
239                 break;
240             case CANCEL_PROVISIONING_DIALOG_RESET:
241                 getUtils().factoryReset(this, "Provisioning cancelled by user");
242                 onProvisioningAborted();
243                 break;
244             case ERROR_DIALOG_OK:
245                 onProvisioningAborted();
246                 break;
247             case ERROR_DIALOG_RESET:
248                 getUtils().factoryReset(this, "Error during provisioning");
249                 onProvisioningAborted();
250                 break;
251             default:
252                 SimpleDialog.throwButtonClickHandlerNotImplemented(dialog);
253         }
254     }
255 }
256