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