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
17package com.android.managedprovisioning;
18
19import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE;
20import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
21
22import android.accounts.Account;
23import android.accounts.AccountManager;
24import android.accounts.AccountManagerFuture;
25import android.accounts.AuthenticatorException;
26import android.accounts.OperationCanceledException;
27import android.app.Activity;
28import android.app.AlertDialog;
29import android.app.ProgressDialog;
30import android.app.admin.DevicePolicyManager;
31import android.content.BroadcastReceiver;
32import android.content.Context;
33import android.content.DialogInterface;
34import android.content.Intent;
35import android.content.IntentFilter;
36import android.os.AsyncTask;
37import android.os.Bundle;
38import android.os.ConditionVariable;
39import android.os.Handler;
40import android.os.UserHandle;
41import android.os.UserManager;
42import android.provider.Settings;
43import android.support.v4.content.LocalBroadcastManager;
44import android.view.View;
45import android.widget.TextView;
46
47import com.android.managedprovisioning.model.ProvisioningParams;
48
49import java.io.IOException;
50import 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 */
65public 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
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.setting_up_workspace, true);
104        setTitle(R.string.setup_profile_progress);
105
106        if (mCancelStatus == STATUS_CANCEL_CONFIRMING) {
107            showCancelProvisioningDialog();
108        } else if (mCancelStatus == STATUS_CANCELLING) {
109            showCancelProgressDialog();
110        }
111        mParams = (ProvisioningParams) getIntent().getParcelableExtra(
112                ProvisioningParams.EXTRA_PROVISIONING_PARAMS);
113        if (mParams != null) {
114            maybeSetLogoAndMainColor(mParams.mainColor);
115        }
116    }
117
118    @Override
119    protected void onResume() {
120        super.onResume();
121
122        // Setup broadcast receiver for feedback from service.
123        mServiceMessageReceiver = new ServiceMessageReceiver();
124        IntentFilter filter = new IntentFilter();
125        filter.addAction(ProfileOwnerProvisioningService.ACTION_PROVISIONING_SUCCESS);
126        filter.addAction(ProfileOwnerProvisioningService.ACTION_PROVISIONING_ERROR);
127        filter.addAction(ProfileOwnerProvisioningService.ACTION_PROVISIONING_CANCELLED);
128        LocalBroadcastManager.getInstance(this).registerReceiver(mServiceMessageReceiver, filter);
129
130        // Start service async to make sure the UI is loaded first.
131        final Handler handler = new Handler(getMainLooper());
132        handler.post(new Runnable() {
133            @Override
134            public void run() {
135                Intent intent = new Intent(ProfileOwnerProvisioningActivity.this,
136                        ProfileOwnerProvisioningService.class);
137                intent.putExtras(getIntent());
138                startService(intent);
139            }
140        });
141    }
142
143    class ServiceMessageReceiver extends BroadcastReceiver {
144        @Override
145        public void onReceive(Context context, Intent intent) {
146            if (mCancelStatus == STATUS_CANCEL_CONFIRMING) {
147                // Store the incoming intent and only process it after the user has responded to
148                // the cancel dialog
149                mPendingProvisioningResult = intent;
150                return;
151            }
152            handleProvisioningResult(intent);
153        }
154    }
155
156    private void handleProvisioningResult(Intent intent) {
157        String action = intent.getAction();
158        if (ProfileOwnerProvisioningService.ACTION_PROVISIONING_SUCCESS.equals(action)) {
159            if (mCancelStatus == STATUS_CANCELLING) {
160                return;
161            }
162
163            ProvisionLogger.logd("Successfully provisioned."
164                    + "Finishing ProfileOwnerProvisioningActivity");
165
166            onProvisioningSuccess();
167        } else if (ProfileOwnerProvisioningService.ACTION_PROVISIONING_ERROR.equals(action)) {
168            if (mCancelStatus == STATUS_CANCELLING){
169                return;
170            }
171            String errorLogMessage = intent.getStringExtra(
172                    ProfileOwnerProvisioningService.EXTRA_LOG_MESSAGE_KEY);
173            ProvisionLogger.logd("Error reported: " + errorLogMessage);
174            error(R.string.managed_provisioning_error_text, errorLogMessage);
175            // Note that this will be reported as a canceled action
176            mCancelStatus = STATUS_FINALIZING;
177        } else if (ProfileOwnerProvisioningService.ACTION_PROVISIONING_CANCELLED.equals(action)) {
178            if (mCancelStatus != STATUS_CANCELLING) {
179                return;
180            }
181            mCancelProgressDialog.dismiss();
182            onProvisioningAborted();
183        }
184    }
185
186    private void onProvisioningAborted() {
187        stopService(new Intent(this, ProfileOwnerProvisioningService.class));
188        setResult(Activity.RESULT_CANCELED);
189        finish();
190    }
191
192    @Override
193    public void onBackPressed() {
194        if (mCancelStatus == STATUS_PROVISIONING) {
195            mCancelStatus = STATUS_CANCEL_CONFIRMING;
196            showCancelProvisioningDialog();
197        } else {
198            super.onBackPressed();
199        }
200    }
201
202    private void showCancelProvisioningDialog() {
203        AlertDialog alertDialog = new AlertDialog.Builder(this)
204                .setCancelable(false)
205                .setMessage(R.string.profile_owner_cancel_message)
206                .setNegativeButton(R.string.profile_owner_cancel_cancel,
207                        new DialogInterface.OnClickListener() {
208                            @Override
209                            public void onClick(DialogInterface dialog,int id) {
210                                mCancelStatus = STATUS_PROVISIONING;
211                                if (mPendingProvisioningResult != null) {
212                                    handleProvisioningResult(mPendingProvisioningResult);
213                                }
214                            }
215                        })
216                .setPositiveButton(R.string.profile_owner_cancel_ok,
217                        new DialogInterface.OnClickListener() {
218                            @Override
219                            public void onClick(DialogInterface dialog,int id) {
220                                confirmCancel();
221                            }
222                        })
223                .create();
224        alertDialog.show();
225    }
226
227    protected void showCancelProgressDialog() {
228        mCancelProgressDialog = new ProgressDialog(this);
229        mCancelProgressDialog.setMessage(getText(R.string.profile_owner_cancelling));
230        mCancelProgressDialog.setCancelable(false);
231        mCancelProgressDialog.setCanceledOnTouchOutside(false);
232        mCancelProgressDialog.show();
233    }
234
235    public void error(int resourceId, String logText) {
236        ProvisionLogger.loge(logText);
237        new AlertDialog.Builder(this)
238                .setTitle(R.string.provisioning_error_title)
239                .setMessage(resourceId)
240                .setCancelable(false)
241                .setPositiveButton(R.string.device_owner_error_ok,
242                        new DialogInterface.OnClickListener() {
243                            @Override
244                            public void onClick(DialogInterface dialog,int id) {
245                                onProvisioningAborted();
246                            }
247                        })
248                .show();
249    }
250
251    private void confirmCancel() {
252        if (mCancelStatus != STATUS_CANCEL_CONFIRMING) {
253            // Can only cancel if provisioning hasn't finished at this point.
254            return;
255        }
256        mCancelStatus = STATUS_CANCELLING;
257        Intent intent = new Intent(ProfileOwnerProvisioningActivity.this,
258                ProfileOwnerProvisioningService.class);
259        intent.setAction(ACTION_CANCEL_PROVISIONING);
260        startService(intent);
261        showCancelProgressDialog();
262    }
263
264    /**
265     * Finish activity and stop service.
266     */
267    private void onProvisioningSuccess() {
268        mCancelStatus = STATUS_FINALIZING;
269        stopService(new Intent(this, ProfileOwnerProvisioningService.class));
270        setResult(Activity.RESULT_OK);
271        finish();
272    }
273
274    @Override
275    protected void onSaveInstanceState(Bundle outState) {
276        outState.putInt(KEY_STATUS, mCancelStatus);
277        outState.putParcelable(KEY_PENDING_INTENT, mPendingProvisioningResult);
278    }
279
280    @Override
281    public void onPause() {
282        LocalBroadcastManager.getInstance(this).unregisterReceiver(mServiceMessageReceiver);
283        super.onPause();
284    }
285}
286