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 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
21 
22 import android.accounts.Account;
23 import android.accounts.AccountManager;
24 import android.accounts.AccountManagerFuture;
25 import android.accounts.AuthenticatorException;
26 import android.accounts.OperationCanceledException;
27 import android.app.Activity;
28 import android.app.AlertDialog;
29 import android.app.ProgressDialog;
30 import android.app.admin.DevicePolicyManager;
31 import android.content.BroadcastReceiver;
32 import android.content.Context;
33 import android.content.DialogInterface;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.os.AsyncTask;
37 import android.os.Bundle;
38 import android.os.ConditionVariable;
39 import android.os.Handler;
40 import android.os.UserHandle;
41 import android.os.UserManager;
42 import android.provider.Settings;
43 import android.support.v4.content.LocalBroadcastManager;
44 import android.view.View;
45 import android.widget.TextView;
46 
47 import com.android.managedprovisioning.model.ProvisioningParams;
48 
49 import java.io.IOException;
50 import java.util.concurrent.ExecutionException;
51 
52 /**
53  * Profile owner provisioning sets up a separate profile on a device whose primary user is already
54  * set up or being 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 SetupLayoutActivity {
66     protected static final String ACTION_CANCEL_PROVISIONING =
67             "com.android.managedprovisioning.CANCEL_PROVISIONING";
68 
69     private BroadcastReceiver mServiceMessageReceiver;
70 
71     private static final int BROADCAST_TIMEOUT = 2 * 60 * 1000;
72 
73     // Provisioning service started
74     private static final int STATUS_PROVISIONING = 1;
75     // Back button pressed during provisioning, confirm dialog showing.
76     private static final int STATUS_CANCEL_CONFIRMING = 2;
77     // Cancel confirmed, waiting for the provisioning service to complete.
78     private static final int STATUS_CANCELLING = 3;
79     // Cancelling not possible anymore, provisioning already finished successfully.
80     private static final int STATUS_FINALIZING = 4;
81 
82     private static final String KEY_STATUS= "status";
83     private static final String KEY_PENDING_INTENT = "pending_intent";
84 
85     private int mCancelStatus = STATUS_PROVISIONING;
86     private Intent mPendingProvisioningResult = null;
87     private ProgressDialog mCancelProgressDialog = null;
88     private AccountManager mAccountManager;
89 
90     private ProvisioningParams mParams;
91 
92     @Override
onCreate(Bundle savedInstanceState)93     protected void onCreate(Bundle savedInstanceState) {
94         super.onCreate(savedInstanceState);
95         ProvisionLogger.logd("Profile owner provisioning activity ONCREATE");
96         mAccountManager = (AccountManager) getSystemService(Context.ACCOUNT_SERVICE);
97 
98         if (savedInstanceState != null) {
99             mCancelStatus = savedInstanceState.getInt(KEY_STATUS, STATUS_PROVISIONING);
100             mPendingProvisioningResult = savedInstanceState.getParcelable(KEY_PENDING_INTENT);
101         }
102 
103         initializeLayoutParams(R.layout.progress, R.string.setup_work_profile, true);
104         configureNavigationButtons(NEXT_BUTTON_EMPTY_LABEL, View.INVISIBLE, View.VISIBLE);
105         setTitle(R.string.setup_profile_progress);
106 
107         TextView textView = (TextView) findViewById(R.id.prog_text);
108         if (textView != null) textView.setText(R.string.setting_up_workspace);
109 
110         if (mCancelStatus == STATUS_CANCEL_CONFIRMING) {
111             showCancelProvisioningDialog();
112         } else if (mCancelStatus == STATUS_CANCELLING) {
113             showCancelProgressDialog();
114         }
115         mParams = (ProvisioningParams) getIntent().getParcelableExtra(
116                 ProvisioningParams.EXTRA_PROVISIONING_PARAMS);
117         if (mParams != null) {
118             maybeSetLogoAndMainColor(mParams.mainColor);
119         }
120     }
121 
122     @Override
onResume()123     protected void onResume() {
124         super.onResume();
125 
126         // Setup broadcast receiver for feedback from service.
127         mServiceMessageReceiver = new ServiceMessageReceiver();
128         IntentFilter filter = new IntentFilter();
129         filter.addAction(ProfileOwnerProvisioningService.ACTION_PROVISIONING_SUCCESS);
130         filter.addAction(ProfileOwnerProvisioningService.ACTION_PROVISIONING_ERROR);
131         filter.addAction(ProfileOwnerProvisioningService.ACTION_PROVISIONING_CANCELLED);
132         LocalBroadcastManager.getInstance(this).registerReceiver(mServiceMessageReceiver, filter);
133 
134         // Start service async to make sure the UI is loaded first.
135         final Handler handler = new Handler(getMainLooper());
136         handler.post(new Runnable() {
137             @Override
138             public void run() {
139                 Intent intent = new Intent(ProfileOwnerProvisioningActivity.this,
140                         ProfileOwnerProvisioningService.class);
141                 intent.putExtras(getIntent());
142                 startService(intent);
143             }
144         });
145     }
146 
147     class ServiceMessageReceiver extends BroadcastReceiver {
148         @Override
onReceive(Context context, Intent intent)149         public void onReceive(Context context, Intent intent) {
150             if (mCancelStatus == STATUS_CANCEL_CONFIRMING) {
151                 // Store the incoming intent and only process it after the user has responded to
152                 // the cancel dialog
153                 mPendingProvisioningResult = intent;
154                 return;
155             }
156             handleProvisioningResult(intent);
157         }
158     }
159 
handleProvisioningResult(Intent intent)160     private void handleProvisioningResult(Intent intent) {
161         String action = intent.getAction();
162         if (ProfileOwnerProvisioningService.ACTION_PROVISIONING_SUCCESS.equals(action)) {
163             if (mCancelStatus == STATUS_CANCELLING) {
164                 return;
165             }
166 
167             ProvisionLogger.logd("Successfully provisioned."
168                     + "Finishing ProfileOwnerProvisioningActivity");
169 
170             onProvisioningSuccess();
171         } else if (ProfileOwnerProvisioningService.ACTION_PROVISIONING_ERROR.equals(action)) {
172             if (mCancelStatus == STATUS_CANCELLING){
173                 return;
174             }
175             String errorLogMessage = intent.getStringExtra(
176                     ProfileOwnerProvisioningService.EXTRA_LOG_MESSAGE_KEY);
177             ProvisionLogger.logd("Error reported: " + errorLogMessage);
178             error(R.string.managed_provisioning_error_text, errorLogMessage);
179             // Note that this will be reported as a canceled action
180             mCancelStatus = STATUS_FINALIZING;
181         } else if (ProfileOwnerProvisioningService.ACTION_PROVISIONING_CANCELLED.equals(action)) {
182             if (mCancelStatus != STATUS_CANCELLING) {
183                 return;
184             }
185             mCancelProgressDialog.dismiss();
186             onProvisioningAborted();
187         }
188     }
189 
onProvisioningAborted()190     private void onProvisioningAborted() {
191         stopService(new Intent(this, ProfileOwnerProvisioningService.class));
192         setResult(Activity.RESULT_CANCELED);
193         finish();
194     }
195 
196     @Override
onBackPressed()197     public void onBackPressed() {
198         if (mCancelStatus == STATUS_PROVISIONING) {
199             mCancelStatus = STATUS_CANCEL_CONFIRMING;
200             showCancelProvisioningDialog();
201         } else {
202             super.onBackPressed();
203         }
204     }
205 
showCancelProvisioningDialog()206     private void showCancelProvisioningDialog() {
207         AlertDialog alertDialog = new AlertDialog.Builder(this)
208                 .setCancelable(false)
209                 .setMessage(R.string.profile_owner_cancel_message)
210                 .setNegativeButton(R.string.profile_owner_cancel_cancel,
211                         new DialogInterface.OnClickListener() {
212                             @Override
213                             public void onClick(DialogInterface dialog,int id) {
214                                 mCancelStatus = STATUS_PROVISIONING;
215                                 if (mPendingProvisioningResult != null) {
216                                     handleProvisioningResult(mPendingProvisioningResult);
217                                 }
218                             }
219                         })
220                 .setPositiveButton(R.string.profile_owner_cancel_ok,
221                         new DialogInterface.OnClickListener() {
222                             @Override
223                             public void onClick(DialogInterface dialog,int id) {
224                                 confirmCancel();
225                             }
226                         })
227                 .create();
228         alertDialog.show();
229     }
230 
showCancelProgressDialog()231     protected void showCancelProgressDialog() {
232         mCancelProgressDialog = new ProgressDialog(this);
233         mCancelProgressDialog.setMessage(getText(R.string.profile_owner_cancelling));
234         mCancelProgressDialog.setCancelable(false);
235         mCancelProgressDialog.setCanceledOnTouchOutside(false);
236         mCancelProgressDialog.show();
237     }
238 
error(int resourceId, String logText)239     public void error(int resourceId, String logText) {
240         ProvisionLogger.loge(logText);
241         new AlertDialog.Builder(this)
242                 .setTitle(R.string.provisioning_error_title)
243                 .setMessage(resourceId)
244                 .setCancelable(false)
245                 .setPositiveButton(R.string.device_owner_error_ok,
246                         new DialogInterface.OnClickListener() {
247                             @Override
248                             public void onClick(DialogInterface dialog,int id) {
249                                 onProvisioningAborted();
250                             }
251                         })
252                 .show();
253     }
254 
confirmCancel()255     private void confirmCancel() {
256         if (mCancelStatus != STATUS_CANCEL_CONFIRMING) {
257             // Can only cancel if provisioning hasn't finished at this point.
258             return;
259         }
260         mCancelStatus = STATUS_CANCELLING;
261         Intent intent = new Intent(ProfileOwnerProvisioningActivity.this,
262                 ProfileOwnerProvisioningService.class);
263         intent.setAction(ACTION_CANCEL_PROVISIONING);
264         startService(intent);
265         showCancelProgressDialog();
266     }
267 
268     /**
269      * Finish activity and stop service.
270      */
onProvisioningSuccess()271     private void onProvisioningSuccess() {
272         mBackButton.setVisibility(View.INVISIBLE);
273 
274         mCancelStatus = STATUS_FINALIZING;
275         stopService(new Intent(this, ProfileOwnerProvisioningService.class));
276         setResult(Activity.RESULT_OK);
277         finish();
278     }
279 
280     @Override
onSaveInstanceState(Bundle outState)281     protected void onSaveInstanceState(Bundle outState) {
282         outState.putInt(KEY_STATUS, mCancelStatus);
283         outState.putParcelable(KEY_PENDING_INTENT, mPendingProvisioningResult);
284     }
285 
286     @Override
onPause()287     public void onPause() {
288         LocalBroadcastManager.getInstance(this).unregisterReceiver(mServiceMessageReceiver);
289         super.onPause();
290     }
291 }
292