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