ManagedProvisioningActivity.java revision d90263a6f503f2350b031de05cff3d854c6212c9
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_ADMIN_EXTRAS_BUNDLE;
20import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
21import static com.android.managedprovisioning.EncryptDeviceActivity.EXTRA_RESUME;
22import static com.android.managedprovisioning.EncryptDeviceActivity.EXTRA_RESUME_TARGET;
23import static com.android.managedprovisioning.EncryptDeviceActivity.TARGET_PROFILE_OWNER;
24
25import android.app.Activity;
26import android.app.AlertDialog;
27import android.content.BroadcastReceiver;
28import android.content.ComponentName;
29import android.content.Context;
30import android.content.DialogInterface;
31import android.content.Intent;
32import android.content.IntentFilter;
33import android.content.pm.ApplicationInfo;
34import android.content.pm.PackageManager;
35import android.content.pm.ResolveInfo;
36import android.content.pm.PackageManager.NameNotFoundException;
37import android.content.pm.UserInfo;
38import android.graphics.drawable.Drawable;
39import android.os.Build;
40import android.os.Bundle;
41import android.os.PersistableBundle;
42import android.os.Process;
43import android.os.SystemProperties;
44import android.os.UserHandle;
45import android.os.UserManager;
46import android.support.v4.content.LocalBroadcastManager;
47import android.text.TextUtils;
48import android.view.LayoutInflater;
49import android.view.View;
50import android.widget.ImageView;
51import android.widget.TextView;
52import android.widget.Button;
53
54import java.util.List;
55
56/**
57 * Managed provisioning sets up a separate profile on a device whose primary user is already set up.
58 * The typical example is setting up a corporate profile that is controlled by their employer on a
59 * users personal device to keep personal and work data separate.
60 *
61 * The activity handles the input validation and UI for managed profile provisioning.
62 * and starts the {@link ManagedProvisioningService}, which runs through the setup steps in an
63 * async task.
64 */
65// TODO: Proper error handling to report back to the user and potentially the mdm.
66public class ManagedProvisioningActivity extends Activity {
67
68    private static final String MANAGE_USERS_PERMISSION = "android.permission.MANAGE_USERS";
69
70    // Note: must match the constant defined in HomeSettings
71    private static final String EXTRA_SUPPORT_MANAGED_PROFILES = "support_managed_profiles";
72
73    protected static final String EXTRA_USER_HAS_CONSENTED_PROVISIONING =
74            "com.android.managedprovisioning.EXTRA_USER_HAS_CONSENTED_PROVISIONING";
75
76    // Aliases to start managed provisioning with and without MANAGE_USERS permission
77    protected static final ComponentName ALIAS_CHECK_CALLER =
78            new ComponentName("com.android.managedprovisioning",
79                    "com.android.managedprovisioning.ManagedProvisioningActivity");
80
81    protected static final ComponentName ALIAS_NO_CHECK_CALLER =
82            new ComponentName("com.android.managedprovisioning",
83                    "com.android.managedprovisioning.ManagedProvisioningActivityNoCallerCheck");
84
85    protected static final int ENCRYPT_DEVICE_REQUEST_CODE = 2;
86    protected static final int CHANGE_LAUNCHER_REQUEST_CODE = 1;
87
88    private String mMdmPackageName;
89    private BroadcastReceiver mServiceMessageReceiver;
90
91    private View mContentView;
92    private View mMainTextView;
93    private View mProgressView;
94
95    @Override
96    protected void onCreate(Bundle savedInstanceState) {
97        super.onCreate(savedInstanceState);
98
99        ProvisionLogger.logd("Managed provisioning activity ONCREATE");
100
101        final LayoutInflater inflater = getLayoutInflater();
102        mContentView = inflater.inflate(R.layout.user_consent, null);
103        mMainTextView = mContentView.findViewById(R.id.main_text_container);
104        mProgressView = mContentView.findViewById(R.id.progress_container);
105        setContentView(mContentView);
106
107        // Check whether system has the required managed profile feature.
108        if (!systemHasManagedProfileFeature()) {
109            showErrorAndClose(R.string.managed_provisioning_not_supported,
110                    "Exiting managed provisioning, managed profiles feature is not available");
111            return;
112        }
113        if (Process.myUserHandle().getIdentifier() != UserHandle.USER_OWNER) {
114            showErrorAndClose(R.string.user_is_not_owner,
115                    "Exiting managed provisioning, calling user is not owner.");
116            return;
117        }
118
119        // Setup broadcast receiver for feedback from service.
120        mServiceMessageReceiver = new ServiceMessageReceiver();
121        IntentFilter filter = new IntentFilter();
122        filter.addAction(ManagedProvisioningService.ACTION_PROVISIONING_SUCCESS);
123        filter.addAction(ManagedProvisioningService.ACTION_PROVISIONING_ERROR);
124        LocalBroadcastManager.getInstance(this).registerReceiver(mServiceMessageReceiver, filter);
125
126        // Initialize member variables from the intent, stop if the intent wasn't valid.
127        try {
128            initialize(getIntent());
129        } catch (ManagedProvisioningFailedException e) {
130            showErrorAndClose(R.string.managed_provisioning_error_text, e.getMessage());
131            return;
132        }
133
134        setMdmIcon(mMdmPackageName, mContentView);
135
136        // If the caller started us via ALIAS_NO_CHECK_CALLER then they must have permission to
137        // MANAGE_USERS since it is a restricted intent. Otherwise, check the calling package.
138        boolean hasManageUsersPermission = (getComponentName().equals(ALIAS_NO_CHECK_CALLER));
139        if (!hasManageUsersPermission) {
140            // Calling package has to equal the requested device admin package or has to be system.
141            String callingPackage = getCallingPackage();
142            if (callingPackage == null) {
143                showErrorAndClose(R.string.managed_provisioning_error_text,
144                        "Calling package is null. " +
145                        "Was startActivityForResult used to start this activity?");
146                return;
147            }
148            if (!callingPackage.equals(mMdmPackageName)
149                    && !packageHasManageUsersPermission(callingPackage)) {
150                showErrorAndClose(R.string.managed_provisioning_error_text, "Permission denied, "
151                        + "calling package tried to set a different package as profile owner. "
152                        + "The system MANAGE_USERS permission is required.");
153                return;
154            }
155        }
156
157        // If there is already a managed profile, allow the user to cancel or delete it.
158        int existingManagedProfileUserId = alreadyHasManagedProfile();
159        if (existingManagedProfileUserId != -1) {
160            showManagedProfileExistsDialog(existingManagedProfileUserId);
161        } else {
162            showStartProvisioningScreen();
163        }
164    }
165
166    private void showStartProvisioningScreen() {
167        Button positiveButton = (Button) mContentView.findViewById(R.id.positive_button);
168        positiveButton.setOnClickListener(new View.OnClickListener() {
169            @Override
170            public void onClick(View v) {
171                checkEncryptedAndStartProvisioningService();
172            }
173        });
174    }
175
176    private boolean packageHasManageUsersPermission(String pkg) {
177        int packagePermission = this.getPackageManager()
178                .checkPermission(MANAGE_USERS_PERMISSION, pkg);
179        if (packagePermission == PackageManager.PERMISSION_GRANTED) {
180            return true;
181        } else {
182            return false;
183        }
184    }
185
186    private boolean systemHasManagedProfileFeature() {
187        PackageManager pm = getPackageManager();
188        return pm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
189    }
190
191    private boolean currentLauncherSupportsManagedProfiles() {
192        Intent intent = new Intent(Intent.ACTION_MAIN);
193        intent.addCategory(Intent.CATEGORY_HOME);
194
195        PackageManager pm = getPackageManager();
196        ResolveInfo launcherResolveInfo
197                = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
198        try {
199            ApplicationInfo launcherAppInfo = getPackageManager().getApplicationInfo(
200                    launcherResolveInfo.activityInfo.packageName, 0 /* default flags */);
201            return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion);
202        } catch (PackageManager.NameNotFoundException e) {
203            return false;
204        }
205    }
206
207    private boolean versionNumberAtLeastL(int versionNumber) {
208        return versionNumber >= Build.VERSION_CODES.L;
209    }
210
211    class ServiceMessageReceiver extends BroadcastReceiver {
212        @Override
213        public void onReceive(Context context, Intent intent) {
214            String action = intent.getAction();
215            if (action.equals(ManagedProvisioningService.ACTION_PROVISIONING_SUCCESS)) {
216                ProvisionLogger.logd("Successfully provisioned");
217                ManagedProvisioningActivity.this.setResult(Activity.RESULT_OK);
218                ManagedProvisioningActivity.this.finish();
219                return;
220            } else if (action.equals(ManagedProvisioningService.ACTION_PROVISIONING_ERROR)) {
221                String errorLogMessage = intent.getStringExtra(
222                        ManagedProvisioningService.EXTRA_LOG_MESSAGE_KEY);
223                ProvisionLogger.logd("Error reported: " + errorLogMessage);
224                showErrorAndClose(R.string.managed_provisioning_error_text, errorLogMessage);
225                return;
226            }
227        }
228    }
229
230    private void setMdmIcon(String packageName, View contentView) {
231        if (packageName != null) {
232            PackageManager pm = getPackageManager();
233            try {
234                ApplicationInfo ai = pm.getApplicationInfo(packageName, /* default flags */ 0);
235                if (ai != null) {
236                    Drawable packageIcon = pm.getApplicationIcon(packageName);
237                    ImageView imageView = (ImageView) contentView.findViewById(R.id.mdm_icon_view);
238                    imageView.setImageDrawable(packageIcon);
239
240                    String appLabel = pm.getApplicationLabel(ai).toString();
241                    TextView deviceManagerName = (TextView) contentView
242                            .findViewById(R.id.device_manager_name);
243                    deviceManagerName.setText(appLabel);
244                }
245            } catch (PackageManager.NameNotFoundException e) {
246                // Package does not exist, ignore. Should never happen.
247                ProvisionLogger.loge("Package does not exist. Should never happen.");
248            }
249        }
250    }
251
252    /**
253     * Checks if all required provisioning parameters are provided.
254     * Does not check for extras that are optional such as wifi ssid.
255     * Also checks whether type of admin extras bundle (if present) is PersistableBundle.
256     *
257     * @param intent The intent that started provisioning
258     */
259    private void initialize(Intent intent) throws ManagedProvisioningFailedException {
260        // Check if the admin extras bundle is of the right type.
261        try {
262            PersistableBundle bundle = (PersistableBundle) getIntent().getParcelableExtra(
263                    EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE);
264        } catch (ClassCastException e) {
265            throw new ManagedProvisioningFailedException("Extra "
266                    + EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
267                    + " must be of type PersistableBundle.", e);
268        }
269
270        // Validate package name and check if the package is installed
271        mMdmPackageName = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
272        if (TextUtils.isEmpty(mMdmPackageName)) {
273            throw new ManagedProvisioningFailedException("Missing intent extra: "
274                    + EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
275        } else {
276            try {
277                this.getPackageManager().getPackageInfo(mMdmPackageName, 0);
278            } catch (NameNotFoundException e) {
279                throw new ManagedProvisioningFailedException("Mdm "+ mMdmPackageName
280                        + " is not installed. ", e);
281            }
282        }
283    }
284
285    @Override
286    public void onBackPressed() {
287        // TODO: Handle this graciously by stopping the provisioning flow and cleaning up.
288    }
289
290    @Override
291    public void onDestroy() {
292        LocalBroadcastManager.getInstance(this).unregisterReceiver(mServiceMessageReceiver);
293        super.onDestroy();
294    }
295
296    /**
297     * If the device is encrypted start the service which does the provisioning, otherwise ask for
298     * user consent to encrypt the device.
299     */
300    private void checkEncryptedAndStartProvisioningService() {
301        if (EncryptDeviceActivity.isDeviceEncrypted()
302                || SystemProperties.getBoolean("persist.sys.no_req_encrypt", false)) {
303
304            // Notify the user once more that the admin will have full control over the profile,
305            // then start provisioning.
306            new UserConsentDialog(this, UserConsentDialog.PROFILE_OWNER, new Runnable() {
307                    @Override
308                    public void run() {
309                        setupEnvironmentAndProvision();
310                    }
311                } /* onUserConsented */ , null /* onCancel */).show(getFragmentManager(),
312                        "UserConsentDialogFragment");
313        } else {
314            Bundle resumeExtras = getIntent().getExtras();
315            resumeExtras.putString(EXTRA_RESUME_TARGET, TARGET_PROFILE_OWNER);
316            Intent encryptIntent = new Intent(this, EncryptDeviceActivity.class)
317                    .putExtra(EXTRA_RESUME, resumeExtras);
318            startActivityForResult(encryptIntent, ENCRYPT_DEVICE_REQUEST_CODE);
319            // Continue in onActivityResult or after reboot.
320        }
321    }
322
323    private void setupEnvironmentAndProvision() {
324        // Remove any pre-provisioning UI in favour of progress display
325        BootReminder.cancelProvisioningReminder(
326                ManagedProvisioningActivity.this);
327        mProgressView.setVisibility(View.VISIBLE);
328        mMainTextView.setVisibility(View.GONE);
329
330        // Check whether the current launcher supports managed profiles.
331        if (!currentLauncherSupportsManagedProfiles()) {
332            showCurrentLauncherInvalid();
333        } else {
334            startManagedProvisioningService();
335        }
336    }
337
338    private void pickLauncher() {
339        Intent changeLauncherIntent = new Intent("android.settings.HOME_SETTINGS");
340        changeLauncherIntent.putExtra(EXTRA_SUPPORT_MANAGED_PROFILES, true);
341        startActivityForResult(changeLauncherIntent, CHANGE_LAUNCHER_REQUEST_CODE);
342        // Continue in onActivityResult.
343    }
344
345    private void startManagedProvisioningService() {
346        Intent intent = new Intent(this, ManagedProvisioningService.class);
347        intent.putExtras(getIntent());
348        startService(intent);
349    }
350
351    @Override
352    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
353        if (requestCode == ENCRYPT_DEVICE_REQUEST_CODE) {
354            if (resultCode == RESULT_CANCELED) {
355                ProvisionLogger.loge("User canceled device encryption.");
356                setResult(Activity.RESULT_CANCELED);
357                finish();
358            }
359        } else if (requestCode == CHANGE_LAUNCHER_REQUEST_CODE) {
360            if (resultCode == RESULT_CANCELED) {
361                showCurrentLauncherInvalid();
362            } else if (resultCode == RESULT_OK) {
363                startManagedProvisioningService();
364            }
365        }
366    }
367
368    private void showCurrentLauncherInvalid() {
369        new AlertDialog.Builder(this)
370                .setCancelable(false)
371                .setMessage(R.string.managed_provisioning_not_supported_by_launcher)
372                .setNegativeButton(R.string.cancel_provisioning,
373                        new DialogInterface.OnClickListener() {
374                            @Override
375                            public void onClick(DialogInterface dialog,int id) {
376                                dialog.dismiss();
377                                setResult(Activity.RESULT_CANCELED);
378                                finish();
379                            }
380                        })
381                .setPositiveButton(R.string.pick_launcher,
382                        new DialogInterface.OnClickListener() {
383                            @Override
384                            public void onClick(DialogInterface dialog,int id) {
385                                pickLauncher();
386                            }
387                        }).show();
388    }
389
390    public void showErrorAndClose(int resourceId, String logText) {
391        ProvisionLogger.loge(logText);
392        new ManagedProvisioningErrorDialog(getString(resourceId))
393              .show(getFragmentManager(), "ErrorDialogFragment");
394    }
395
396    /**
397     * @return The User id of an already existing managed profile or -1 if none
398     * exists
399     */
400    int alreadyHasManagedProfile() {
401        UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
402        List<UserInfo> profiles = userManager.getProfiles(getUserId());
403        for (UserInfo userInfo : profiles) {
404            if (userInfo.isManagedProfile()) {
405                return userInfo.getUserHandle().getIdentifier();
406            }
407        }
408        return -1;
409    }
410
411    /**
412     * Builds a dialog that allows the user to remove an existing managed profile after they were
413     * shown an additional warning.
414     */
415    private void showManagedProfileExistsDialog(
416            final int existingManagedProfileUserId) {
417
418        // Before deleting, show a warning dialog
419        DialogInterface.OnClickListener warningListener =
420                new DialogInterface.OnClickListener() {
421            @Override
422            public void onClick(DialogInterface dialog, int which) {
423                // Really delete the profile if the user clicks delete on the warning dialog.
424                final DialogInterface.OnClickListener deleteListener =
425                        new DialogInterface.OnClickListener() {
426                    @Override
427                    public void onClick(DialogInterface dialog, int which) {
428                        UserManager userManager =
429                                (UserManager) getSystemService(Context.USER_SERVICE);
430                        userManager.removeUser(existingManagedProfileUserId);
431                        showStartProvisioningScreen();
432                    }
433                };
434                buildDeleteManagedProfileDialog(
435                        getString(R.string.sure_you_want_to_delete_profile),
436                        deleteListener).show();
437            }
438        };
439
440        buildDeleteManagedProfileDialog(
441                getString(R.string.managed_profile_already_present),
442                warningListener).show();
443    }
444
445    private AlertDialog buildDeleteManagedProfileDialog(String message,
446            DialogInterface.OnClickListener deleteListener) {
447        DialogInterface.OnClickListener cancelListener =
448                new DialogInterface.OnClickListener() {
449            @Override
450            public void onClick(DialogInterface dialog, int which) {
451                ManagedProvisioningActivity.this.finish();
452            }
453        };
454
455        AlertDialog.Builder builder = new AlertDialog.Builder(this);
456        builder.setMessage(message)
457                .setCancelable(false)
458                .setPositiveButton(getString(R.string.delete_profile), deleteListener)
459                .setNegativeButton(getString(R.string.cancel_delete_profile), cancelListener);
460
461        return builder.create();
462    }
463    /**
464     * Exception thrown when the managed provisioning has failed completely.
465     *
466     * We're using a custom exception to avoid catching subsequent exceptions that might be
467     * significant.
468     */
469    private class ManagedProvisioningFailedException extends Exception {
470        public ManagedProvisioningFailedException(String message) {
471            super(message);
472        }
473
474        public ManagedProvisioningFailedException(String message, Throwable t) {
475            super(message, t);
476        }
477    }
478}
479
480