com.android.managedprovisioning.ProfileOwnerPreProvisioningActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.android.managedprovisioning.ProfileOwnerPreProvisioningActivity.java

Source

/*
 * Copyright 2014, The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.managedprovisioning;

import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
import static com.android.managedprovisioning.EncryptDeviceActivity.EXTRA_RESUME;
import static com.android.managedprovisioning.EncryptDeviceActivity.EXTRA_RESUME_TARGET;
import static com.android.managedprovisioning.EncryptDeviceActivity.TARGET_PROFILE_OWNER;

import android.app.Activity;
import android.app.admin.DevicePolicyManager;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Button;

import java.util.List;

/**
 * The activity sets up the environment in which the {@link ProfileOwnerProvisioningActivity} can be run.
 * It makes sure the device is encrypted, the current launcher supports managed profiles, the
 * provisioning intent extras are valid, and that the already present managed profile is removed.
 */
public class ProfileOwnerPreProvisioningActivity extends Activity implements UserConsentDialog.ConsentCallback {

    private static final String MANAGE_USERS_PERMISSION = "android.permission.MANAGE_USERS";

    // Note: must match the constant defined in HomeSettings
    private static final String EXTRA_SUPPORT_MANAGED_PROFILES = "support_managed_profiles";

    // Aliases to start profile owner provisioning with and without MANAGE_USERS permission
    protected static final ComponentName ALIAS_CHECK_CALLER = new ComponentName("com.android.managedprovisioning",
            "com.android.managedprovisioning.ProfileOwnerProvisioningActivity");

    protected static final ComponentName ALIAS_NO_CHECK_CALLER = new ComponentName(
            "com.android.managedprovisioning",
            "com.android.managedprovisioning.ProfileOwnerProvisioningActivityNoCallerCheck");

    protected static final int PROVISIONING_REQUEST_CODE = 3;
    protected static final int ENCRYPT_DEVICE_REQUEST_CODE = 2;
    protected static final int CHANGE_LAUNCHER_REQUEST_CODE = 1;

    private String mMdmPackageName;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final LayoutInflater inflater = getLayoutInflater();
        View contentView = inflater.inflate(R.layout.user_consent, null);
        setContentView(contentView);

        // Check whether system has the required managed profile feature.
        if (!systemHasManagedProfileFeature()) {
            showErrorAndClose(R.string.managed_provisioning_not_supported,
                    "Exiting managed profile provisioning, " + "managed profiles feature is not available");
            return;
        }
        if (Process.myUserHandle().getIdentifier() != UserHandle.USER_OWNER) {
            showErrorAndClose(R.string.user_is_not_owner,
                    "Exiting managed profile provisioning, calling user is not owner.");
            return;
        }

        // Initialize member variables from the intent, stop if the intent wasn't valid.
        try {
            initialize(getIntent());
        } catch (ProvisioningFailedException e) {
            showErrorAndClose(R.string.managed_provisioning_error_text, e.getMessage());
            return;
        }

        setMdmIcon(mMdmPackageName);

        // If the caller started us via ALIAS_NO_CHECK_CALLER then they must have permission to
        // MANAGE_USERS since it is a restricted intent. Otherwise, check the calling package.
        boolean hasManageUsersPermission = (getComponentName().equals(ALIAS_NO_CHECK_CALLER));
        if (!hasManageUsersPermission) {
            // Calling package has to equal the requested device admin package or has to be system.
            String callingPackage = getCallingPackage();
            if (callingPackage == null) {
                showErrorAndClose(R.string.managed_provisioning_error_text,
                        "Calling package is null. " + "Was startActivityForResult used to start this activity?");
                return;
            }
            if (!callingPackage.equals(mMdmPackageName) && !packageHasManageUsersPermission(callingPackage)) {
                showErrorAndClose(R.string.managed_provisioning_error_text,
                        "Permission denied, "
                                + "calling package tried to set a different package as profile owner. "
                                + "The system MANAGE_USERS permission is required.");
                return;
            }
        }

        DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
        String deviceOwner = dpm.getDeviceOwner();
        if (deviceOwner != null && !deviceOwner.equals(mMdmPackageName)) {
            showErrorAndClose(R.string.managed_provisioning_error_text,
                    "Permission denied, " + "profile owner must be in the same package as device owner.");
            return;
        }

        // If there is already a managed profile, allow the user to cancel or delete it.
        int existingManagedProfileUserId = alreadyHasManagedProfile();
        if (existingManagedProfileUserId != -1) {
            showManagedProfileExistsDialog(existingManagedProfileUserId);
        } else {
            showStartProvisioningScreen();
        }
    }

    private void showStartProvisioningScreen() {
        Button positiveButton = (Button) findViewById(R.id.positive_button);
        positiveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                checkEncryptedAndStartProvisioningService();
            }
        });
    }

    private boolean packageHasManageUsersPermission(String pkg) {
        return PackageManager.PERMISSION_GRANTED == getPackageManager().checkPermission(MANAGE_USERS_PERMISSION,
                pkg);
    }

    private boolean systemHasManagedProfileFeature() {
        PackageManager pm = getPackageManager();
        return pm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
    }

    private boolean currentLauncherSupportsManagedProfiles() {
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);

        PackageManager pm = getPackageManager();
        ResolveInfo launcherResolveInfo = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
        if (launcherResolveInfo == null) {
            return false;
        }
        try {
            ApplicationInfo launcherAppInfo = getPackageManager()
                    .getApplicationInfo(launcherResolveInfo.activityInfo.packageName, 0 /* default flags */);
            return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion);
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }
    }

    private boolean versionNumberAtLeastL(int versionNumber) {
        return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
    }

    private void setMdmIcon(String packageName) {
        if (packageName != null) {
            PackageManager pm = getPackageManager();
            try {
                ApplicationInfo ai = pm.getApplicationInfo(packageName, /* default flags */ 0);
                if (ai != null) {
                    Drawable packageIcon = pm.getApplicationIcon(packageName);
                    ImageView imageView = (ImageView) findViewById(R.id.mdm_icon_view);
                    imageView.setImageDrawable(packageIcon);

                    String appLabel = pm.getApplicationLabel(ai).toString();
                    TextView deviceManagerName = (TextView) findViewById(R.id.device_manager_name);
                    deviceManagerName.setText(appLabel);
                }
            } catch (PackageManager.NameNotFoundException e) {
                // Package does not exist, ignore. Should never happen.
                ProvisionLogger.loge("Package does not exist. Should never happen.");
            }
        }
    }

    /**
     * Checks if all required provisioning parameters are provided.
     * Does not check for extras that are optional such as wifi ssid.
     * Also checks whether type of admin extras bundle (if present) is PersistableBundle.
     *
     * @param intent The intent that started provisioning
     */
    private void initialize(Intent intent) throws ProvisioningFailedException {
        // Check if the admin extras bundle is of the right type.
        try {
            PersistableBundle bundle = (PersistableBundle) getIntent()
                    .getParcelableExtra(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE);
        } catch (ClassCastException e) {
            throw new ProvisioningFailedException(
                    "Extra " + EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE + " must be of type PersistableBundle.", e);
        }

        // Validate package name and check if the package is installed
        mMdmPackageName = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
        if (TextUtils.isEmpty(mMdmPackageName)) {
            throw new ProvisioningFailedException(
                    "Missing intent extra: " + EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
        } else {
            try {
                this.getPackageManager().getPackageInfo(mMdmPackageName, 0);
            } catch (NameNotFoundException e) {
                throw new ProvisioningFailedException("Mdm " + mMdmPackageName + " is not installed. ", e);
            }
        }
    }

    /**
     * If the device is encrypted start the service which does the provisioning, otherwise ask for
     * user consent to encrypt the device.
     */
    private void checkEncryptedAndStartProvisioningService() {
        if (EncryptDeviceActivity.isDeviceEncrypted()
                || SystemProperties.getBoolean("persist.sys.no_req_encrypt", false)) {

            // Notify the user once more that the admin will have full control over the profile,
            // then start provisioning.
            UserConsentDialog.newInstance(UserConsentDialog.PROFILE_OWNER).show(getFragmentManager(),
                    "UserConsentDialogFragment");
        } else {
            Bundle resumeExtras = getIntent().getExtras();
            resumeExtras.putString(EXTRA_RESUME_TARGET, TARGET_PROFILE_OWNER);
            Intent encryptIntent = new Intent(this, EncryptDeviceActivity.class).putExtra(EXTRA_RESUME,
                    resumeExtras);
            startActivityForResult(encryptIntent, ENCRYPT_DEVICE_REQUEST_CODE);
            // Continue in onActivityResult or after reboot.
        }
    }

    @Override
    public void onDialogConsent() {
        setupEnvironmentAndProvision();
    }

    @Override
    public void onDialogCancel() {
        // Do nothing.
    }

    private void setupEnvironmentAndProvision() {
        // Remove any pre-provisioning UI in favour of progress display
        BootReminder.cancelProvisioningReminder(this);

        // Check whether the current launcher supports managed profiles.
        if (!currentLauncherSupportsManagedProfiles()) {
            showCurrentLauncherInvalid();
        } else {
            startProfileOwnerProvisioning();
        }
    }

    private void pickLauncher() {
        Intent changeLauncherIntent = new Intent("android.settings.HOME_SETTINGS");
        changeLauncherIntent.putExtra(EXTRA_SUPPORT_MANAGED_PROFILES, true);
        startActivityForResult(changeLauncherIntent, CHANGE_LAUNCHER_REQUEST_CODE);
        // Continue in onActivityResult.
    }

    private void startProfileOwnerProvisioning() {
        Intent intent = new Intent(this, ProfileOwnerProvisioningActivity.class);
        intent.putExtras(getIntent());
        startActivityForResult(intent, PROVISIONING_REQUEST_CODE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == ENCRYPT_DEVICE_REQUEST_CODE) {
            if (resultCode == RESULT_CANCELED) {
                ProvisionLogger.loge("User canceled device encryption.");
                setResult(Activity.RESULT_CANCELED);
                finish();
            }
        } else if (requestCode == CHANGE_LAUNCHER_REQUEST_CODE) {
            if (resultCode == RESULT_CANCELED) {
                showCurrentLauncherInvalid();
            } else if (resultCode == RESULT_OK) {
                startProfileOwnerProvisioning();
            }
        }
        if (requestCode == PROVISIONING_REQUEST_CODE) {
            setResult(resultCode);
            finish();
        }
    }

    private void showCurrentLauncherInvalid() {
        new AlertDialog.Builder(this).setCancelable(false)
                .setMessage(R.string.managed_provisioning_not_supported_by_launcher)
                .setNegativeButton(R.string.cancel_provisioning, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                        setResult(Activity.RESULT_CANCELED);
                        finish();
                    }
                }).setPositiveButton(R.string.pick_launcher, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int id) {
                        pickLauncher();
                    }
                }).show();
    }

    public void showErrorAndClose(int resourceId, String logText) {
        ProvisionLogger.loge(logText);
        new AlertDialog.Builder(this).setTitle(R.string.provisioning_error_title).setMessage(getString(resourceId))
                .setCancelable(false)
                .setPositiveButton(R.string.device_owner_error_ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int id) {
                        // Close activity
                        ProfileOwnerPreProvisioningActivity.this.setResult(Activity.RESULT_CANCELED);
                        ProfileOwnerPreProvisioningActivity.this.finish();
                    }
                }).show();
    }

    /**
     * @return The User id of an already existing managed profile or -1 if none
     * exists
     */
    int alreadyHasManagedProfile() {
        UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
        List<UserInfo> profiles = userManager.getProfiles(getUserId());
        for (UserInfo userInfo : profiles) {
            if (userInfo.isManagedProfile()) {
                return userInfo.getUserHandle().getIdentifier();
            }
        }
        return -1;
    }

    /**
     * Builds a dialog that allows the user to remove an existing managed profile after they were
     * shown an additional warning.
     */
    private void showManagedProfileExistsDialog(final int existingManagedProfileUserId) {

        // Before deleting, show a warning dialog
        DialogInterface.OnClickListener warningListener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                // Really delete the profile if the user clicks delete on the warning dialog.
                final DialogInterface.OnClickListener deleteListener = new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
                        userManager.removeUser(existingManagedProfileUserId);
                        showStartProvisioningScreen();
                    }
                };
                buildDeleteManagedProfileDialog(getString(R.string.sure_you_want_to_delete_profile), deleteListener)
                        .show();
            }
        };

        buildDeleteManagedProfileDialog(getString(R.string.managed_profile_already_present), warningListener)
                .show();
    }

    private AlertDialog buildDeleteManagedProfileDialog(String message,
            DialogInterface.OnClickListener deleteListener) {
        DialogInterface.OnClickListener cancelListener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                ProfileOwnerPreProvisioningActivity.this.finish();
            }
        };

        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setMessage(message).setCancelable(false)
                .setPositiveButton(getString(R.string.delete_profile), deleteListener)
                .setNegativeButton(getString(R.string.cancel_delete_profile), cancelListener);

        return builder.create();
    }

    /**
     * Exception thrown when the provisioning has failed completely.
     *
     * We're using a custom exception to avoid catching subsequent exceptions that might be
     * significant.
     */
    private class ProvisioningFailedException extends Exception {
        public ProvisioningFailedException(String message) {
            super(message);
        }

        public ProvisioningFailedException(String message, Throwable t) {
            super(message, t);
        }
    }
}