ManagedProvisioningActivity.java revision 14eeef9ff6b961f4a746f2953dde1529ad27bc6a
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_DEVICE_ADMIN;
20import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEFAULT_MANAGED_PROFILE_NAME;
21import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
22import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TOKEN;
23
24import android.app.Activity;
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.content.pm.ApplicationInfo;
30import android.content.pm.PackageManager;
31import android.content.pm.PackageManager.NameNotFoundException;
32import android.content.pm.UserInfo;
33import android.graphics.drawable.Drawable;
34import android.os.Bundle;
35import android.os.UserManager;
36import android.text.TextUtils;
37import android.view.LayoutInflater;
38import android.view.View;
39import android.widget.ImageView;
40import android.widget.TextView;
41import android.widget.Button;
42
43import com.android.managedprovisioning.UserConsentSaver;
44
45import java.util.List;
46
47/**
48 * Managed provisioning sets up a separate profile on a device whose primary user is already set up.
49 * The typical example is setting up a corporate profile that is controlled by their employer on a
50 * users personal device to keep personal and work data separate.
51 *
52 * The activity handles the input validation and UI for managed profile provisioning.
53 * and starts the {@link ManagedProvisioningService}, which runs through the setup steps in an
54 * async task.
55 */
56// TODO: Proper error handling to report back to the user and potentially the mdm.
57public class ManagedProvisioningActivity extends Activity {
58
59    // TODO remove these when the new constant values are in use in all relevant places.
60    protected static final String EXTRA_LEGACY_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME =
61            "deviceAdminPackageName";
62    protected static final String EXTRA_LEGACY_PROVISIONING_DEFAULT_MANAGED_PROFILE_NAME =
63            "defaultManagedProfileName";
64
65    protected static final int ENCRYPT_DEVICE_REQUEST_CODE = 2;
66
67    private String mMdmPackageName;
68    private int mToken;
69    private BroadcastReceiver mServiceMessageReceiver;
70
71    private View mMainTextView;
72    private View mProgressView;
73
74    @Override
75    protected void onCreate(Bundle savedInstanceState) {
76        super.onCreate(savedInstanceState);
77
78        ProvisionLogger.logd("Managed provisioning activity ONCREATE");
79
80        PackageManager pm = getPackageManager();
81        if (!pm.hasSystemFeature(PackageManager.FEATURE_MANAGED_PROFILES)) {
82            showErrorAndClose(R.string.managed_provisioning_not_supported,
83                    "Exiting managed provisioning, managed profiles feature is not available");
84            return;
85        }
86
87        // Setup broadcast receiver for feedback from service.
88        mServiceMessageReceiver = new ServiceMessageReceiver();
89        IntentFilter filter = new IntentFilter();
90        filter.addAction(ManagedProvisioningService.ACTION_PROVISIONING_SUCCESS);
91        filter.addAction(ManagedProvisioningService.ACTION_PROVISIONING_ERROR);
92        registerReceiver(mServiceMessageReceiver, filter);
93
94        // Initialize member variables from the intent, stop if the intent wasn't valid.
95        try {
96            initialize(getIntent());
97        } catch (ManagedProvisioningFailedException e) {
98            showErrorAndClose(R.string.managed_provisioning_error_text, e.getMessage());
99            return;
100        }
101
102        final LayoutInflater inflater = getLayoutInflater();
103        final View contentView = inflater.inflate(R.layout.user_consent, null);
104        final View mMainTextView = contentView.findViewById(R.id.main_text_container);
105        final View mProgressView = contentView.findViewById(R.id.progress_container);
106        setContentView(contentView);
107        setMdmIcon(mMdmPackageName, contentView);
108
109        // Don't continue if a managed profile already exists
110        if (alreadyHasManagedProfile()) {
111            showErrorAndClose(R.string.managed_profile_already_present,
112                    "The device already has a managed profile, nothing to do.");
113        } else {
114
115            // If we previously received an intent confirming user consent, skip the user consent.
116            // Otherwise wait for the user to consent.
117            if (UserConsentSaver.hasUserConsented(this, mMdmPackageName, mToken)) {
118                checkEncryptedAndStartProvisioningService();
119            } else {
120                Button positiveButton = (Button) contentView.findViewById(R.id.positive_button);
121                positiveButton.setOnClickListener(new View.OnClickListener() {
122                    @Override
123                    public void onClick(View v) {
124                        checkEncryptedAndStartProvisioningService();
125                    }
126                });
127            }
128        }
129    }
130
131    class ServiceMessageReceiver extends BroadcastReceiver {
132        @Override
133        public void onReceive(Context context, Intent intent) {
134            String action = intent.getAction();
135            if (action.equals(ManagedProvisioningService.ACTION_PROVISIONING_SUCCESS)) {
136                ProvisionLogger.logd("Successfully provisioned");
137                finish();
138                return;
139            } else if (action.equals(ManagedProvisioningService.ACTION_PROVISIONING_ERROR)) {
140                String errorLogMessage = intent.getStringExtra(
141                        ManagedProvisioningService.EXTRA_LOG_MESSAGE_KEY);
142                ProvisionLogger.logd("Error reported: " + errorLogMessage);
143                showErrorAndClose(R.string.managed_provisioning_error_text, errorLogMessage);
144                return;
145            }
146        }
147    }
148
149    private void setMdmIcon(String packageName, View contentView) {
150        if (packageName != null) {
151            PackageManager pm = getPackageManager();
152            try {
153                ApplicationInfo ai = pm.getApplicationInfo(packageName, /* default flags */ 0);
154                if (ai != null) {
155                    Drawable packageIcon = pm.getApplicationIcon(packageName);
156                    ImageView imageView = (ImageView) contentView.findViewById(R.id.mdm_icon_view);
157                    imageView.setImageDrawable(packageIcon);
158
159                    String appLabel = pm.getApplicationLabel(ai).toString();
160                    TextView deviceManagerName = (TextView) contentView
161                            .findViewById(R.id.device_manager_name);
162                    deviceManagerName.setText(appLabel);
163                }
164            } catch (PackageManager.NameNotFoundException e) {
165                // Package does not exist, ignore. Should never happen.
166                ProvisionLogger.loge("Package does not exist. Should never happen.");
167            }
168        }
169    }
170
171    /**
172     * Checks if all required provisioning parameters are provided.
173     * Does not check for extras that are optional such as the email address.
174     *
175     * @param intent The intent that started provisioning
176     */
177    private void initialize(Intent intent) throws ManagedProvisioningFailedException {
178
179        // Validate package name and check if the package is installed
180        mMdmPackageName = getMdmPackageName(intent);
181        if (TextUtils.isEmpty(mMdmPackageName)) {
182            throw new ManagedProvisioningFailedException("Missing intent extra: "
183                    + EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
184        } else {
185            try {
186                this.getPackageManager().getPackageInfo(mMdmPackageName, 0);
187            } catch (NameNotFoundException e) {
188                throw new ManagedProvisioningFailedException("Mdm "+ mMdmPackageName
189                        + " is not installed. " + e);
190            }
191        }
192
193        // Validate the provided device admin component.
194        if (intent.getParcelableExtra(EXTRA_DEVICE_ADMIN) == null) {
195            throw new ManagedProvisioningFailedException("Missing intent extra: "
196                    + EXTRA_DEVICE_ADMIN);
197        }
198
199        // Validate the default profile name.
200        if (TextUtils.isEmpty(getDefaultManagedProfileName(intent))) {
201            throw new ManagedProvisioningFailedException("Missing intent extra: "
202                    + EXTRA_PROVISIONING_DEFAULT_MANAGED_PROFILE_NAME);
203        }
204
205        // The token will be empty if the user has not previously consented.
206        mToken = intent.getIntExtra(EXTRA_PROVISIONING_TOKEN, UserConsentSaver.NO_TOKEN_RECEIVED);
207    }
208
209    private String getMdmPackageName(Intent intent) {
210        String name = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
211        if (TextUtils.isEmpty(name)) {
212            name = intent.getStringExtra(EXTRA_LEGACY_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
213        }
214        return name;
215    }
216
217    private String getDefaultManagedProfileName(Intent intent) {
218        String name = intent.getStringExtra(EXTRA_PROVISIONING_DEFAULT_MANAGED_PROFILE_NAME);
219        if (TextUtils.isEmpty(name)) {
220            name = intent.getStringExtra(EXTRA_LEGACY_PROVISIONING_DEFAULT_MANAGED_PROFILE_NAME);
221        }
222        return name;
223    }
224
225    @Override
226    public void onBackPressed() {
227        // TODO: Handle this graciously by stopping the provisioning flow and cleaning up.
228    }
229
230    @Override
231    public void onDestroy() {
232        unregisterReceiver(mServiceMessageReceiver);
233        super.onDestroy();
234    }
235
236    /**
237     * If the device is encrypted start the service which does the provisioning, otherwise ask for
238     * user consent to encrypt the device.
239     */
240    private void checkEncryptedAndStartProvisioningService() {
241        if (EncryptDeviceActivity.isDeviceEncrypted()) {
242            mProgressView.setVisibility(View.VISIBLE);
243            mMainTextView.setVisibility(View.GONE);
244
245            Intent intent = new Intent(this, ManagedProvisioningService.class);
246            intent.putExtras(getIntent());
247            startService(intent);
248        } else {
249            Bundle resumeExtras = getIntent().getExtras();
250            resumeExtras.putString(EncryptDeviceActivity.EXTRA_RESUME_TARGET,
251                    EncryptDeviceActivity.TARGET_PROFILE_OWNER);
252            Intent encryptIntent = new Intent(this, EncryptDeviceActivity.class)
253                    .putExtra(EncryptDeviceActivity.EXTRA_RESUME, resumeExtras);
254            startActivityForResult(encryptIntent, ENCRYPT_DEVICE_REQUEST_CODE);
255            // Continue in onActivityResult or after reboot.
256        }
257    }
258
259    @Override
260    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
261        if (requestCode == ENCRYPT_DEVICE_REQUEST_CODE) {
262            if (resultCode == RESULT_CANCELED) {
263                // Move back to user consent.
264                if (UserConsentSaver.hasUserConsented(this, mMdmPackageName, mToken)) {
265                    checkEncryptedAndStartProvisioningService();
266                }
267            }
268        }
269    }
270
271    public void showErrorAndClose(int resourceId, String logText) {
272        ProvisionLogger.loge(logText);
273        new ManagedProvisioningErrorDialog(getString(resourceId))
274              .show(getFragmentManager(), "ErrorDialogFragment");
275    }
276
277    boolean alreadyHasManagedProfile() {
278        UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
279        List<UserInfo> profiles = userManager.getProfiles(getUserId());
280        for (UserInfo userInfo : profiles) {
281            if (userInfo.isManagedProfile()) return true;
282        }
283        return false;
284    }
285
286    /**
287     * Exception thrown when the managed provisioning has failed completely.
288     *
289     * We're using a custom exception to avoid catching subsequent exceptions that might be
290     * significant.
291     */
292    private class ManagedProvisioningFailedException extends Exception {
293      public ManagedProvisioningFailedException(String message) {
294          super(message);
295      }
296    }
297}
298
299