1 /*
2  * Copyright 2014, 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;
18 
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.os.Bundle;
27 import android.os.UserHandle;
28 import android.support.v4.content.LocalBroadcastManager;
29 import android.view.accessibility.AccessibilityEvent;
30 import android.view.View;
31 import android.widget.TextView;
32 
33 import com.android.managedprovisioning.model.ProvisioningParams;
34 
35 import java.util.ArrayList;
36 
37 /**
38  * This activity starts device owner provisioning:
39  * It downloads a mobile device management application(mdm) from a given url and installs it,
40  * or a given mdm is already present on the device. The mdm is set as the owner of the device so
41  * that it has full control over the device:
42  * TODO: put link here with documentation on how a device owner has control over the device
43  * The mdm can then execute further setup steps.
44  *
45  * <p>
46  * An example use case might be when a company wants to set up a device for a single use case
47  * (such as giving instructions).
48  * </p>
49  *
50  * <p>
51  * Provisioning is triggered by a programmer device that sends required provisioning parameters via
52  * nfc. For an example of a programmer app see:
53  * com.example.android.apis.app.DeviceProvisioningProgrammerSample.
54  * </p>
55  *
56  * <p>
57  * In the unlikely case that this activity is killed the whole provisioning process so far is
58  * repeated. We made sure that all tasks can be done twice without causing any problems.
59  * </p>
60  */
61 public class DeviceOwnerProvisioningActivity extends SetupLayoutActivity {
62     private static final boolean DEBUG = false; // To control logging.
63 
64     private static final String KEY_CANCEL_DIALOG_SHOWN = "cancel_dialog_shown";
65     private static final String KEY_PENDING_INTENTS = "pending_intents";
66 
67     private BroadcastReceiver mServiceMessageReceiver;
68     private TextView mProgressTextView;
69 
70     private ProvisioningParams mParams;
71 
72     // Indicates that the cancel dialog is shown.
73     private boolean mCancelDialogShown = false;
74 
75     // List of intents received while cancel dialog is shown.
76     private ArrayList<Intent> mPendingProvisioningIntents = new ArrayList<Intent>();
77 
78     @Override
onCreate(Bundle savedInstanceState)79     public void onCreate(Bundle savedInstanceState) {
80         super.onCreate(savedInstanceState);
81         if (DEBUG) ProvisionLogger.logd("Device owner provisioning activity ONCREATE");
82 
83         if (savedInstanceState != null) {
84             mCancelDialogShown = savedInstanceState.getBoolean(KEY_CANCEL_DIALOG_SHOWN, false);
85             mPendingProvisioningIntents = savedInstanceState
86                     .getParcelableArrayList(KEY_PENDING_INTENTS);
87         }
88 
89         // Setup the UI.
90         initializeLayoutParams(R.layout.progress, R.string.setup_work_device, true);
91         configureNavigationButtons(NEXT_BUTTON_EMPTY_LABEL, View.INVISIBLE, View.VISIBLE);
92         setTitle(R.string.setup_device_progress);
93 
94         mProgressTextView = (TextView) findViewById(R.id.prog_text);
95         if (mCancelDialogShown) showCancelResetDialog();
96 
97         // Setup broadcast receiver for feedback from service.
98         mServiceMessageReceiver = new ServiceMessageReceiver();
99         IntentFilter filter = new IntentFilter();
100         filter.addAction(DeviceOwnerProvisioningService.ACTION_PROVISIONING_SUCCESS);
101         filter.addAction(DeviceOwnerProvisioningService.ACTION_PROVISIONING_ERROR);
102         filter.addAction(DeviceOwnerProvisioningService.ACTION_PROGRESS_UPDATE);
103         LocalBroadcastManager.getInstance(this).registerReceiver(mServiceMessageReceiver, filter);
104 
105         // Load the ProvisioningParams (from message in Intent).
106         mParams = (ProvisioningParams) getIntent().getParcelableExtra(
107                 ProvisioningParams.EXTRA_PROVISIONING_PARAMS);
108         if (mParams != null) {
109             maybeSetLogoAndMainColor(mParams.mainColor);
110         }
111         startDeviceOwnerProvisioningService();
112     }
113 
startDeviceOwnerProvisioningService()114     private void startDeviceOwnerProvisioningService() {
115         Intent intent = new Intent(this, DeviceOwnerProvisioningService.class);
116         intent.putExtras(getIntent());
117         startService(intent);
118     }
119 
120     class ServiceMessageReceiver extends BroadcastReceiver
121     {
122         @Override
onReceive(Context context, Intent intent)123         public void onReceive(Context context, Intent intent)
124         {
125             if (mCancelDialogShown) {
126 
127                 // Postpone handling the intent.
128                 mPendingProvisioningIntents.add(intent);
129                 return;
130             }
131             handleProvisioningIntent(intent);
132         }
133     }
134 
handleProvisioningIntent(Intent intent)135     private void handleProvisioningIntent(Intent intent) {
136         String action = intent.getAction();
137         if (action.equals(DeviceOwnerProvisioningService.ACTION_PROVISIONING_SUCCESS)) {
138             if (DEBUG) ProvisionLogger.logd("Successfully provisioned");
139             onProvisioningSuccess();
140         } else if (action.equals(DeviceOwnerProvisioningService.ACTION_PROVISIONING_ERROR)) {
141             int errorMessageId = intent.getIntExtra(
142                     DeviceOwnerProvisioningService.EXTRA_USER_VISIBLE_ERROR_ID_KEY,
143                     R.string.device_owner_error_general);
144             boolean factoryResetRequired = intent.getBooleanExtra(
145                     DeviceOwnerProvisioningService.EXTRA_FACTORY_RESET_REQUIRED,
146                     true);
147 
148             if (DEBUG) {
149                 ProvisionLogger.logd("Error reported with code "
150                         + getResources().getString(errorMessageId));
151             }
152             error(errorMessageId, factoryResetRequired);
153         } else if (action.equals(DeviceOwnerProvisioningService.ACTION_PROGRESS_UPDATE)) {
154             int progressMessage = intent.getIntExtra(
155                     DeviceOwnerProvisioningService.EXTRA_PROGRESS_MESSAGE_ID_KEY, -1);
156             if (DEBUG) {
157                 ProvisionLogger.logd("Progress update reported with code "
158                     + getResources().getString(progressMessage));
159             }
160             if (progressMessage >= 0) {
161                 progressUpdate(progressMessage);
162             }
163         }
164     }
165 
166 
onProvisioningSuccess()167     private void onProvisioningSuccess() {
168         stopService(new Intent(this, DeviceOwnerProvisioningService.class));
169         // Note: the DeviceOwnerProvisioningService will stop itself.
170         setResult(Activity.RESULT_OK);
171         finish();
172     }
173 
174     @Override
onBackPressed()175     public void onBackPressed() {
176         if (mCancelDialogShown) {
177             return;
178         }
179 
180         mCancelDialogShown = true;
181         showCancelResetDialog();
182     }
183 
showCancelResetDialog()184     private void showCancelResetDialog() {
185         new AlertDialog.Builder(DeviceOwnerProvisioningActivity.this)
186                 .setCancelable(false)
187                 .setMessage(R.string.device_owner_cancel_message)
188                 .setNegativeButton(R.string.device_owner_cancel_cancel,
189                         new DialogInterface.OnClickListener() {
190                             @Override
191                             public void onClick(DialogInterface dialog, int id) {
192                                 dialog.dismiss();
193                                 handlePendingIntents();
194                                 mCancelDialogShown = false;
195                             }
196                         })
197                 .setPositiveButton(R.string.device_owner_error_reset,
198                         new DialogInterface.OnClickListener() {
199                             @Override
200                             public void onClick(DialogInterface dialog, int id) {
201                                 dialog.dismiss();
202 
203                                 // Factory reset the device.
204                                 mUtils.sendFactoryResetBroadcast(
205                                         DeviceOwnerProvisioningActivity.this,
206                                         "DeviceOwnerProvisioningActivity.showCancelResetDialog()");
207                             }
208                         })
209                 .show();
210     }
211 
handlePendingIntents()212     private void handlePendingIntents() {
213         for (Intent intent : mPendingProvisioningIntents) {
214             if (DEBUG) ProvisionLogger.logd("Handling pending intent " + intent.getAction());
215             handleProvisioningIntent(intent);
216         }
217         mPendingProvisioningIntents.clear();
218     }
219 
progressUpdate(int progressMessage)220     private void progressUpdate(int progressMessage) {
221         mProgressTextView.setText(progressMessage);
222         mProgressTextView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
223     }
224 
error(int dialogMessage, boolean resetRequired)225     private void error(int dialogMessage, boolean resetRequired) {
226         AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this)
227                 .setTitle(R.string.provisioning_error_title)
228                 .setMessage(dialogMessage)
229                 .setCancelable(false);
230         if (resetRequired) {
231             alertBuilder.setPositiveButton(R.string.device_owner_error_reset,
232                     new DialogInterface.OnClickListener() {
233                         @Override
234                         public void onClick(DialogInterface dialog,int id) {
235                             dialog.dismiss();
236 
237                             // Factory reset the device.
238                             Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR);
239                             intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
240                             intent.putExtra(Intent.EXTRA_REASON,
241                                     "DeviceOwnerProvisioningActivity.error()");
242                             sendBroadcast(intent);
243                             stopService(new Intent(DeviceOwnerProvisioningActivity.this,
244                                             DeviceOwnerProvisioningService.class));
245                             setResult(RESULT_CANCELED);
246                             finish();
247                         }
248                     });
249         } else {
250             alertBuilder.setPositiveButton(R.string.device_owner_error_ok,
251                     new DialogInterface.OnClickListener() {
252                         @Override
253                         public void onClick(DialogInterface dialog,int id) {
254                             dialog.dismiss();
255 
256                             // Close activity.
257                             stopService(new Intent(DeviceOwnerProvisioningActivity.this,
258                                             DeviceOwnerProvisioningService.class));
259                             setResult(RESULT_CANCELED);
260                             finish();
261                         }
262                     });
263         }
264         alertBuilder.show();
265     }
266 
267     @Override
onSaveInstanceState(Bundle outState)268     protected void onSaveInstanceState(Bundle outState) {
269         outState.putBoolean(KEY_CANCEL_DIALOG_SHOWN, mCancelDialogShown);
270         outState.putParcelableArrayList(KEY_PENDING_INTENTS, mPendingProvisioningIntents);
271     }
272 
273     @Override
onDestroy()274     public void onDestroy() {
275         if (DEBUG) ProvisionLogger.logd("Device owner provisioning activity ONDESTROY");
276         if (mServiceMessageReceiver != null) {
277             LocalBroadcastManager.getInstance(this).unregisterReceiver(mServiceMessageReceiver);
278             mServiceMessageReceiver = null;
279         }
280         super.onDestroy();
281     }
282 
283     @Override
onRestart()284     protected void onRestart() {
285         if (DEBUG) ProvisionLogger.logd("Device owner provisioning activity ONRESTART");
286         super.onRestart();
287     }
288 
289     @Override
onResume()290     protected void onResume() {
291         if (DEBUG) ProvisionLogger.logd("Device owner provisioning activity ONRESUME");
292         super.onResume();
293     }
294 
295     @Override
onPause()296     protected void onPause() {
297         if (DEBUG) ProvisionLogger.logd("Device owner provisioning activity ONPAUSE");
298         super.onPause();
299     }
300 
301     @Override
onStop()302     protected void onStop() {
303         if (DEBUG) ProvisionLogger.logd("Device owner provisioning activity ONSTOP");
304         super.onStop();
305     }
306 }
307