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.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE; 20 21 import android.accounts.Account; 22 import android.accounts.AccountManager; 23 import android.accounts.AccountManagerFuture; 24 import android.accounts.AuthenticatorException; 25 import android.accounts.OperationCanceledException; 26 import android.app.Activity; 27 import android.app.ActivityManagerNative; 28 import android.app.AlertDialog; 29 import android.app.IActivityManager; 30 import android.app.ProgressDialog; 31 import android.content.BroadcastReceiver; 32 import android.content.ComponentName; 33 import android.content.Context; 34 import android.content.DialogInterface; 35 import android.content.Intent; 36 import android.content.IntentFilter; 37 import android.os.AsyncTask; 38 import android.os.Bundle; 39 import android.os.Handler; 40 import android.os.RemoteException; 41 import android.os.UserHandle; 42 import android.os.UserManager; 43 import android.provider.Settings; 44 import android.support.v4.content.LocalBroadcastManager; 45 import android.view.LayoutInflater; 46 import android.view.View; 47 import android.widget.Button; 48 import android.widget.TextView; 49 50 import java.io.IOException; 51 52 /** 53 * Profile owner provisioning sets up a separate profile on a device whose primary user is already 54 * set up. 55 * 56 * <p> 57 * The typical example is setting up a corporate profile that is controlled by their employer on a 58 * users personal device to keep personal and work data separate. 59 * 60 * <p> 61 * The activity handles the UI for managed profile provisioning and starts the 62 * {@link ProfileOwnerProvisioningService}, which runs through the setup steps in an 63 * async task. 64 */ 65 public class ProfileOwnerProvisioningActivity extends Activity { 66 protected static final String ACTION_CANCEL_PROVISIONING = 67 "com.android.managedprovisioning.CANCEL_PROVISIONING"; 68 69 private BroadcastReceiver mServiceMessageReceiver; 70 71 // Provisioning service started 72 private static final int CANCELSTATUS_PROVISIONING = 1; 73 // Back button pressed during provisioning, confirm dialog showing. 74 private static final int CANCELSTATUS_CONFIRMING = 2; 75 // Cancel confirmed, waiting for the provisioning service to complete. 76 private static final int CANCELSTATUS_CANCELLING = 3; 77 // Cancelling not possible anymore, provisioning already finished succesfully. 78 private static final int CANCELSTATUS_FINALIZING = 4; 79 80 private static final String KEY_CANCELSTATUS= "cancelstatus"; 81 private static final String KEY_PENDING_INTENT = "pending_intent"; 82 83 private int mCancelStatus = CANCELSTATUS_PROVISIONING; 84 private Intent mPendingProvisioningResult = null; 85 private ProgressDialog mCancelProgressDialog = null; 86 private AccountManager mAccountManager; 87 88 @Override onCreate(Bundle savedInstanceState)89 protected void onCreate(Bundle savedInstanceState) { 90 super.onCreate(savedInstanceState); 91 ProvisionLogger.logd("Profile owner provisioning activity ONCREATE"); 92 mAccountManager = (AccountManager) getSystemService(Context.ACCOUNT_SERVICE); 93 94 if (savedInstanceState != null) { 95 mCancelStatus = savedInstanceState.getInt(KEY_CANCELSTATUS, CANCELSTATUS_PROVISIONING); 96 mPendingProvisioningResult = savedInstanceState.getParcelable(KEY_PENDING_INTENT); 97 } 98 99 final LayoutInflater inflater = getLayoutInflater(); 100 View contentView = inflater.inflate(R.layout.progress, null); 101 setContentView(contentView); 102 TextView textView = (TextView) findViewById(R.id.prog_text); 103 if (textView != null) textView.setText(getString(R.string.setting_up_workspace)); 104 105 106 if (mCancelStatus == CANCELSTATUS_CONFIRMING) { 107 showCancelProvisioningDialog(); 108 } else if (mCancelStatus == CANCELSTATUS_CANCELLING) { 109 showCancelProgressDialog(); 110 } 111 } 112 113 114 @Override onResume()115 protected void onResume() { 116 super.onResume(); 117 118 // Setup broadcast receiver for feedback from service. 119 mServiceMessageReceiver = new ServiceMessageReceiver(); 120 IntentFilter filter = new IntentFilter(); 121 filter.addAction(ProfileOwnerProvisioningService.ACTION_PROVISIONING_SUCCESS); 122 filter.addAction(ProfileOwnerProvisioningService.ACTION_PROVISIONING_ERROR); 123 filter.addAction(ProfileOwnerProvisioningService.ACTION_PROVISIONING_CANCELLED); 124 LocalBroadcastManager.getInstance(this).registerReceiver(mServiceMessageReceiver, filter); 125 126 // Start service async to make sure the UI is loaded first. 127 final Handler handler = new Handler(getMainLooper()); 128 handler.post(new Runnable() { 129 @Override 130 public void run() { 131 Intent intent = new Intent(ProfileOwnerProvisioningActivity.this, 132 ProfileOwnerProvisioningService.class); 133 intent.putExtras(getIntent()); 134 startService(intent); 135 } 136 }); 137 } 138 139 class ServiceMessageReceiver extends BroadcastReceiver { 140 @Override onReceive(Context context, Intent intent)141 public void onReceive(Context context, Intent intent) { 142 if (mCancelStatus == CANCELSTATUS_CONFIRMING) { 143 // Store the incoming intent and only process it after the user has responded to 144 // the cancel dialog 145 mPendingProvisioningResult = intent; 146 return; 147 } 148 handleProvisioningResult(intent); 149 } 150 } 151 handleProvisioningResult(Intent intent)152 private void handleProvisioningResult(Intent intent) { 153 String action = intent.getAction(); 154 if (ProfileOwnerProvisioningService.ACTION_PROVISIONING_SUCCESS.equals(action)) { 155 if (mCancelStatus == CANCELSTATUS_CANCELLING) { 156 return; 157 } 158 159 ProvisionLogger.logd("Successfully provisioned." 160 + "Finishing ProfileOwnerProvisioningActivity"); 161 162 Intent pendingIntent = (Intent) intent.getParcelableExtra( 163 ProfileOwnerProvisioningService.EXTRA_PENDING_SUCCESS_INTENT); 164 int serialNumber = intent.getIntExtra( 165 ProfileOwnerProvisioningService.EXTRA_PROFILE_USER_SERIAL_NUMBER, -1); 166 167 int userId = intent.getIntExtra(ProfileOwnerProvisioningService.EXTRA_PROFILE_USER_ID, 168 -1); 169 onProvisioningSuccess(pendingIntent, userId, serialNumber); 170 } else if (ProfileOwnerProvisioningService.ACTION_PROVISIONING_ERROR.equals(action)) { 171 if (mCancelStatus == CANCELSTATUS_CANCELLING){ 172 return; 173 } 174 String errorLogMessage = intent.getStringExtra( 175 ProfileOwnerProvisioningService.EXTRA_LOG_MESSAGE_KEY); 176 ProvisionLogger.logd("Error reported: " + errorLogMessage); 177 error(R.string.managed_provisioning_error_text, errorLogMessage); 178 } if (ProfileOwnerProvisioningService.ACTION_PROVISIONING_CANCELLED.equals(action)) { 179 if (mCancelStatus != CANCELSTATUS_CANCELLING) { 180 return; 181 } 182 mCancelProgressDialog.dismiss(); 183 ProfileOwnerProvisioningActivity.this.setResult(Activity.RESULT_CANCELED); 184 stopService(new Intent(ProfileOwnerProvisioningActivity.this, 185 ProfileOwnerProvisioningService.class)); 186 ProfileOwnerProvisioningActivity.this.finish(); 187 } 188 } 189 190 @Override onBackPressed()191 public void onBackPressed() { 192 if (mCancelStatus == CANCELSTATUS_PROVISIONING) { 193 showCancelProvisioningDialog(); 194 } 195 } 196 showCancelProvisioningDialog()197 private void showCancelProvisioningDialog() { 198 mCancelStatus = CANCELSTATUS_CONFIRMING; 199 AlertDialog alertDialog = new AlertDialog.Builder(this) 200 .setCancelable(false) 201 .setTitle(R.string.profile_owner_cancel_title) 202 .setMessage(R.string.profile_owner_cancel_message) 203 .setNegativeButton(R.string.profile_owner_cancel_cancel, 204 new DialogInterface.OnClickListener() { 205 @Override 206 public void onClick(DialogInterface dialog,int id) { 207 mCancelStatus = CANCELSTATUS_PROVISIONING; 208 if (mPendingProvisioningResult != null) { 209 handleProvisioningResult(mPendingProvisioningResult); 210 } 211 } 212 }) 213 .setPositiveButton(R.string.profile_owner_cancel_ok, 214 new DialogInterface.OnClickListener() { 215 @Override 216 public void onClick(DialogInterface dialog,int id) { 217 confirmCancel(); 218 } 219 }) 220 .create(); 221 alertDialog.show(); 222 } 223 showCancelProgressDialog()224 protected void showCancelProgressDialog() { 225 mCancelProgressDialog = new ProgressDialog(this); 226 mCancelProgressDialog.setMessage(getText(R.string.profile_owner_cancelling)); 227 mCancelProgressDialog.setCancelable(false); 228 mCancelProgressDialog.setCanceledOnTouchOutside(false); 229 mCancelProgressDialog.show(); 230 } 231 error(int resourceId, String logText)232 public void error(int resourceId, String logText) { 233 ProvisionLogger.loge(logText); 234 new AlertDialog.Builder(this) 235 .setTitle(R.string.provisioning_error_title) 236 .setMessage(getString(resourceId)) 237 .setCancelable(false) 238 .setPositiveButton(R.string.device_owner_error_ok, new DialogInterface.OnClickListener() { 239 @Override 240 public void onClick(DialogInterface dialog,int id) { 241 confirmCancel(); 242 } 243 }).show(); 244 } 245 confirmCancel()246 private void confirmCancel() { 247 mCancelStatus = CANCELSTATUS_CANCELLING; 248 Intent intent = new Intent(ProfileOwnerProvisioningActivity.this, 249 ProfileOwnerProvisioningService.class); 250 intent.setAction(ACTION_CANCEL_PROVISIONING); 251 startService(intent); 252 showCancelProgressDialog(); 253 } 254 255 /** 256 * Notify the mdm that provisioning has completed. When the mdm has received the intent, stop 257 * the service and notify the {@link ProfileOwnerProvisioningActivity} so that it can finish itself. 258 */ onProvisioningSuccess(Intent pendingSuccessIntent, int userId, int serialNumber)259 private void onProvisioningSuccess(Intent pendingSuccessIntent, int userId, int serialNumber) { 260 mCancelStatus = CANCELSTATUS_FINALIZING; 261 Settings.Secure.putIntForUser(getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 262 1 /* true- > setup complete */, userId); 263 264 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); 265 UserHandle userHandle = userManager.getUserForSerialNumber(serialNumber); 266 267 // Use an ordered broadcast, so that we only finish when the mdm has received it. 268 // Avoids a lag in the transition between provisioning and the mdm. 269 BroadcastReceiver mdmReceivedSuccessReceiver = new BroadcastReceiver() { 270 @Override 271 public void onReceive(Context context, Intent intent) { 272 ProvisionLogger.logd("ACTION_PROFILE_PROVISIONING_COMPLETE broadcast received by" 273 + " mdm"); 274 ProfileOwnerProvisioningActivity.this.setResult(Activity.RESULT_OK); 275 276 // Now cleanup the primary profile if necessary 277 if (getIntent().hasExtra(EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE)) { 278 ProvisionLogger.logd("Cleaning up account from the primary user."); 279 final Account account = (Account) getIntent().getParcelableExtra( 280 EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE); 281 new AsyncTask<Void, Void, Void>() { 282 @Override 283 protected Void doInBackground(Void... params) { 284 removeAccount(account); 285 return null; 286 } 287 }.execute(); 288 } 289 290 ProfileOwnerProvisioningActivity.this.finish(); 291 stopService(new Intent(ProfileOwnerProvisioningActivity.this, 292 ProfileOwnerProvisioningService.class)); 293 } 294 }; 295 296 sendOrderedBroadcastAsUser(pendingSuccessIntent, userHandle, null, 297 mdmReceivedSuccessReceiver, null, Activity.RESULT_OK, null, null); 298 ProvisionLogger.logd("Provisioning complete broadcast has been sent to user " 299 + userHandle.getIdentifier()); 300 } 301 removeAccount(Account account)302 private void removeAccount(Account account) { 303 try { 304 AccountManagerFuture<Bundle> bundle = mAccountManager.removeAccount(account, 305 this, null /* callback */, null /* handler */); 306 if (bundle.getResult().getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) { 307 ProvisionLogger.logw("Account removed from the primary user."); 308 } else { 309 ProvisionLogger.logw("Could not remove account from the primary user."); 310 } 311 } catch (OperationCanceledException | AuthenticatorException | IOException e) { 312 ProvisionLogger.logw("Exception removing account from the primary user.", e); 313 } 314 } 315 316 @Override onSaveInstanceState(Bundle outState)317 protected void onSaveInstanceState(Bundle outState) { 318 outState.putInt(KEY_CANCELSTATUS, mCancelStatus); 319 outState.putParcelable(KEY_PENDING_INTENT, mPendingProvisioningResult); 320 } 321 322 @Override onPause()323 public void onPause() { 324 LocalBroadcastManager.getInstance(this).unregisterReceiver(mServiceMessageReceiver); 325 super.onPause(); 326 } 327 } 328 329