Home | History | Annotate | Download | only in managedprovisioning
      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
     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
    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
    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 
    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
    191     public void onBackPressed() {
    192         if (mCancelStatus == CANCELSTATUS_PROVISIONING) {
    193             showCancelProvisioningDialog();
    194         }
    195     }
    196 
    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 
    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 
    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 
    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      */
    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 
    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
    317     protected void onSaveInstanceState(Bundle outState) {
    318         outState.putInt(KEY_CANCELSTATUS, mCancelStatus);
    319         outState.putParcelable(KEY_PENDING_INTENT, mPendingProvisioningResult);
    320     }
    321 
    322     @Override
    323     public void onPause() {
    324         LocalBroadcastManager.getInstance(this).unregisterReceiver(mServiceMessageReceiver);
    325         super.onPause();
    326     }
    327 }
    328 
    329