com.android.managedprovisioning.ProfileOwnerProvisioningService.java Source code

Java tutorial

Introduction

Here is the source code for com.android.managedprovisioning.ProfileOwnerProvisioningService.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.DeviceAdminReceiver.ACTION_PROFILE_PROVISIONING_COMPLETE;
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;

import android.accounts.AccountManager;
import android.app.Activity;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.app.Service;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.AsyncTask;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.support.v4.content.LocalBroadcastManager;

import com.android.managedprovisioning.common.Utils;
import com.android.managedprovisioning.CrossProfileIntentFiltersHelper;
import com.android.managedprovisioning.model.ProvisioningParams;
import com.android.managedprovisioning.task.DeleteNonRequiredAppsTask;
import com.android.managedprovisioning.task.DisableBluetoothSharingTask;
import com.android.managedprovisioning.task.DisableInstallShortcutListenersTask;
import com.android.managedprovisioning.task.ManagedProfileSettingsTask;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * Service that runs the profile owner provisioning.
 *
 * <p>This service is started from and sends updates to the {@link ProfileOwnerProvisioningActivity},
 * which contains the provisioning UI.
 */
public class ProfileOwnerProvisioningService extends Service {
    // Intent actions for communication with DeviceOwnerProvisioningService.
    public static final String ACTION_PROVISIONING_SUCCESS = "com.android.managedprovisioning.provisioning_success";
    public static final String ACTION_PROVISIONING_ERROR = "com.android.managedprovisioning.error";
    public static final String ACTION_PROVISIONING_CANCELLED = "com.android.managedprovisioning.cancelled";
    public static final String EXTRA_LOG_MESSAGE_KEY = "ProvisioningErrorLogMessage";

    // Maximum time we will wait for ACTION_USER_UNLOCK until we give up and continue without
    // account migration.
    private static final int USER_UNLOCKED_TIMEOUT_SECONDS = 120; // 2 minutes

    // Status flags for the provisioning process.
    /** Provisioning not started. */
    private static final int STATUS_UNKNOWN = 0;
    /** Provisioning started, no errors or cancellation requested received. */
    private static final int STATUS_STARTED = 1;
    /** Provisioning in progress, but user has requested cancellation. */
    private static final int STATUS_CANCELLING = 2;
    // Final possible states for the provisioning process.
    /** Provisioning completed successfully. */
    private static final int STATUS_DONE = 3;
    /** Provisioning failed and cleanup complete. */
    private static final int STATUS_ERROR = 4;
    /** Provisioning cancelled and cleanup complete. */
    private static final int STATUS_CANCELLED = 5;

    private IPackageManager mIpm;
    private UserInfo mManagedProfileOrUserInfo;
    private AccountManager mAccountManager;
    private UserManager mUserManager;
    private UserUnlockedReceiver mUnlockedReceiver;

    private AsyncTask<Intent, Void, Void> runnerTask;

    // MessageId of the last error message.
    private String mLastErrorMessage = null;

    // Current status of the provisioning process.
    private int mProvisioningStatus = STATUS_UNKNOWN;

    private ProvisioningParams mParams;

    private final Utils mUtils = new Utils();

    private class RunnerTask extends AsyncTask<Intent, Void, Void> {
        @Override
        protected Void doInBackground(Intent... intents) {
            // Atomically move to STATUS_STARTED at most once.
            synchronized (ProfileOwnerProvisioningService.this) {
                if (mProvisioningStatus == STATUS_UNKNOWN) {
                    mProvisioningStatus = STATUS_STARTED;
                } else {
                    // Process already started, don't start again.
                    return null;
                }
            }

            try {
                initialize(intents[0]);
                startManagedProfileOrUserProvisioning();
            } catch (ProvisioningException e) {
                // Handle internal errors.
                error(e.getMessage(), e);
                finish();
            } catch (Exception e) {
                // General catch-all to ensure process cleans up in all cases.
                error("Failed to initialize managed profile, aborting.", e);
                finish();
            }

            return null;
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();

        mIpm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
        mAccountManager = (AccountManager) getSystemService(Context.ACCOUNT_SERVICE);
        mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);

        runnerTask = new RunnerTask();
    }

    @Override
    public int onStartCommand(final Intent intent, int flags, int startId) {
        if (ProfileOwnerProvisioningActivity.ACTION_CANCEL_PROVISIONING.equals(intent.getAction())) {
            ProvisionLogger.logd("Cancelling profile owner provisioning service");
            cancelProvisioning();
            return START_NOT_STICKY;
        }

        ProvisionLogger.logd("Starting profile owner provisioning service");

        try {
            runnerTask.execute(intent);
        } catch (IllegalStateException e) {
            // runnerTask is either in progress, or finished.
            ProvisionLogger.logd("ProfileOwnerProvisioningService: Provisioning already started, "
                    + "second provisioning intent not being processed, only reporting status.");
            reportStatus();
        }
        return START_NOT_STICKY;
    }

    private void reportStatus() {
        synchronized (this) {
            switch (mProvisioningStatus) {
            case STATUS_DONE:
                notifyActivityOfSuccess();
                break;
            case STATUS_CANCELLED:
                notifyActivityCancelled();
                break;
            case STATUS_ERROR:
                notifyActivityError();
                break;
            case STATUS_UNKNOWN:
            case STATUS_STARTED:
            case STATUS_CANCELLING:
                // Don't notify UI of status when just-started/in-progress.
                break;
            }
        }
    }

    private void cancelProvisioning() {
        synchronized (this) {
            switch (mProvisioningStatus) {
            case STATUS_DONE:
                // Process completed, we should honor user request to cancel
                // though.
                mProvisioningStatus = STATUS_CANCELLING;
                cleanupUserProfile();
                mProvisioningStatus = STATUS_CANCELLED;
                reportStatus();
                break;
            case STATUS_UNKNOWN:
                // Process hasn't started, move straight to cancelled state.
                mProvisioningStatus = STATUS_CANCELLED;
                reportStatus();
                break;
            case STATUS_STARTED:
                // Process is mid-flow, flag up that the user has requested
                // cancellation.
                mProvisioningStatus = STATUS_CANCELLING;
                break;
            case STATUS_CANCELLING:
                // Cancellation already being processed.
                break;
            case STATUS_CANCELLED:
            case STATUS_ERROR:
                // Process already completed, nothing left to cancel.
                break;
            }
        }
    }

    private void initialize(Intent intent) {
        // Load the ProvisioningParams (from message in Intent).
        mParams = (ProvisioningParams) intent.getParcelableExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS);
        if (mParams.accountToMigrate != null) {
            ProvisionLogger.logi("Migrating account to managed profile");
        }
    }

    /**
     * This is the core method to create a managed profile or user. It goes through every
     * provisioning step.
     */
    private void startManagedProfileOrUserProvisioning() throws ProvisioningException {

        ProvisionLogger.logd("Starting managed profile or user provisioning");

        if (isProvisioningManagedUser()) {
            mManagedProfileOrUserInfo = mUserManager.getUserInfo(mUserManager.getUserHandle());
            if (mManagedProfileOrUserInfo == null) {
                throw raiseError("Couldn't get current user information");
            }
        } else {
            // Work through the provisioning steps in their corresponding order
            createProfile(getString(R.string.default_managed_profile_name));
        }
        if (mManagedProfileOrUserInfo != null) {
            final DeleteNonRequiredAppsTask deleteNonRequiredAppsTask;
            final DisableInstallShortcutListenersTask disableInstallShortcutListenersTask;
            final DisableBluetoothSharingTask disableBluetoothSharingTask;
            final ManagedProfileSettingsTask managedProfileSettingsTask = new ManagedProfileSettingsTask(this,
                    mManagedProfileOrUserInfo.id);

            disableInstallShortcutListenersTask = new DisableInstallShortcutListenersTask(this,
                    mManagedProfileOrUserInfo.id);
            disableBluetoothSharingTask = new DisableBluetoothSharingTask(mManagedProfileOrUserInfo.id);
            // TODO Add separate set of apps for MANAGED_USER, currently same as of DEVICE_OWNER.
            deleteNonRequiredAppsTask = new DeleteNonRequiredAppsTask(this,
                    mParams.deviceAdminComponentName.getPackageName(),
                    (isProvisioningManagedUser() ? DeleteNonRequiredAppsTask.MANAGED_USER
                            : DeleteNonRequiredAppsTask.PROFILE_OWNER),
                    true /* creating new profile */, mManagedProfileOrUserInfo.id,
                    false /* delete non-required system apps */, new DeleteNonRequiredAppsTask.Callback() {

                        @Override
                        public void onSuccess() {
                            // Need to explicitly handle exceptions here, as
                            // onError() is not invoked for failures in
                            // onSuccess().
                            try {
                                disableBluetoothSharingTask.run();
                                if (!isProvisioningManagedUser()) {
                                    managedProfileSettingsTask.run();
                                    disableInstallShortcutListenersTask.run();
                                }
                                setUpUserOrProfile();
                            } catch (ProvisioningException e) {
                                error(e.getMessage(), e);
                            } catch (Exception e) {
                                error("Provisioning failed", e);
                            }
                            finish();
                        }

                        @Override
                        public void onError() {
                            // Raise an error with a tracing exception attached.
                            error("Delete non required apps task failed.", new Exception());
                            finish();
                        }
                    });

            deleteNonRequiredAppsTask.run();
        }
    }

    /**
     * Called when the new profile or managed user is ready for provisioning (the profile is created
     * and all the apps not needed have been deleted).
     */
    private void setUpUserOrProfile() throws ProvisioningException {
        installMdmOnManagedProfile();
        setMdmAsActiveAdmin();
        setMdmAsManagedProfileOwner();

        if (!isProvisioningManagedUser()) {
            setOrganizationColor();
            setDefaultUserRestrictions();
            CrossProfileIntentFiltersHelper.setFilters(getPackageManager(), getUserId(),
                    mManagedProfileOrUserInfo.id);
            if (!startManagedProfile(mManagedProfileOrUserInfo.id)) {
                throw raiseError("Could not start user in background");
            }
            // Wait for ACTION_USER_UNLOCKED to be sent before trying to migrate the account.
            // Even if no account is present, we should not send the provisioning complete broadcast
            // before the managed profile user is properly started.
            if ((mUnlockedReceiver != null) && !mUnlockedReceiver.waitForUserUnlocked()) {
                return;
            }

            // Note: account migration must happen after setting the profile owner.
            // Otherwise, there will be a time interval where some apps may think that the account
            // does not have a profile owner.
            mUtils.maybeCopyAccount(this, mParams.accountToMigrate, Process.myUserHandle(),
                    mManagedProfileOrUserInfo.getUserHandle());
        }
    }

    /**
     * Notify the calling activity of our final status, perform any cleanup if
     * the process didn't succeed.
     */
    private void finish() {
        ProvisionLogger.logi("Finishing provisioning process, status: " + mProvisioningStatus);
        // Reached the end of the provisioning process, take appropriate action
        // based on current mProvisioningStatus.
        synchronized (this) {
            switch (mProvisioningStatus) {
            case STATUS_STARTED:
                // Provisioning process completed normally.
                notifyMdmAndCleanup();
                mProvisioningStatus = STATUS_DONE;
                break;
            case STATUS_UNKNOWN:
                // No idea how we could end up in finish() in this state,
                // but for safety treat it as an error and fall-through to
                // STATUS_ERROR.
                mLastErrorMessage = "finish() invoked in STATUS_UNKNOWN";
                mProvisioningStatus = STATUS_ERROR;
                break;
            case STATUS_ERROR:
                // Process errored out, cleanup partially created managed
                // profile.
                cleanupUserProfile();
                break;
            case STATUS_CANCELLING:
                // User requested cancellation during processing, remove
                // the successfully created profile.
                cleanupUserProfile();
                mProvisioningStatus = STATUS_CANCELLED;
                break;
            case STATUS_CANCELLED:
            case STATUS_DONE:
                // Shouldn't be possible to already be in this state?!?
                ProvisionLogger.logw("finish() invoked multiple times?");
                break;
            }
        }

        ProvisionLogger.logi("Finished provisioning process, final status: " + mProvisioningStatus);

        // Notify UI activity of final status reached.
        reportStatus();
    }

    /**
     * Initialize the user that underlies the managed profile.
     * This is required so that the provisioning complete broadcast can be sent across to the
     * profile and apps can run on it.
     */
    private boolean startManagedProfile(int userId) {
        ProvisionLogger.logd("Starting user in background");
        IActivityManager iActivityManager = ActivityManagerNative.getDefault();
        // Register a receiver for the Intent.ACTION_USER_UNLOCKED to know when the managed profile
        // has been started and unlocked.
        mUnlockedReceiver = new UserUnlockedReceiver(this, userId);
        try {
            return iActivityManager.startUserInBackground(userId);
        } catch (RemoteException neverThrown) {
            // Never thrown, as we are making local calls.
            ProvisionLogger.loge("This should not happen.", neverThrown);
        }
        return false;
    }

    private void notifyActivityOfSuccess() {
        Intent successIntent = new Intent(ACTION_PROVISIONING_SUCCESS);
        LocalBroadcastManager.getInstance(ProfileOwnerProvisioningService.this).sendBroadcast(successIntent);
    }

    /**
     * Notify the mdm that provisioning has completed. When the mdm has received the intent, stop
     * the service and notify the {@link ProfileOwnerProvisioningActivity} so that it can finish
     * itself.
     */
    private void notifyMdmAndCleanup() {
        // Set DPM userProvisioningState appropriately and persist mParams for use during
        // FinalizationActivity if necessary.
        mUtils.markUserProvisioningStateInitiallyDone(this, mParams);

        if (mParams.provisioningAction.equals(ACTION_PROVISION_MANAGED_PROFILE)) {
            // Set the user_setup_complete flag on the managed-profile as setup-wizard is never run
            // for that user. This is not relevant for other cases since
            // Utils.markUserProvisioningStateInitiallyDone() communicates provisioning state to
            // setup-wizard via DPM.setUserProvisioningState() if necessary.
            mUtils.markUserSetupComplete(this, mManagedProfileOrUserInfo.id);
        }

        // If profile owner provisioning was started after current user setup is completed, then we
        // can directly send the ACTION_PROFILE_PROVISIONING_COMPLETE broadcast to the MDM.
        // But if the provisioning was started as part of setup wizard flow, we signal setup-wizard
        // should shutdown via DPM.setUserProvisioningState(), which will result in a finalization
        // intent being sent to us once setup-wizard finishes. As part of the finalization intent
        // handling we then broadcast ACTION_PROFILE_PROVISIONING_COMPLETE.
        if (mUtils.isUserSetupCompleted(this)) {
            UserHandle managedUserHandle = new UserHandle(mManagedProfileOrUserInfo.id);

            // Use an ordered broadcast, so that we only finish when the mdm has received it.
            // Avoids a lag in the transition between provisioning and the mdm.
            BroadcastReceiver mdmReceivedSuccessReceiver = new MdmReceivedSuccessReceiver(mParams.accountToMigrate,
                    mParams.deviceAdminComponentName.getPackageName());

            Intent completeIntent = new Intent(ACTION_PROFILE_PROVISIONING_COMPLETE);
            completeIntent.setComponent(mParams.deviceAdminComponentName);
            completeIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES | Intent.FLAG_RECEIVER_FOREGROUND);
            if (mParams.adminExtrasBundle != null) {
                completeIntent.putExtra(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE, mParams.adminExtrasBundle);
            }

            sendOrderedBroadcastAsUser(completeIntent, managedUserHandle, null, mdmReceivedSuccessReceiver, null,
                    Activity.RESULT_OK, null, null);
            ProvisionLogger.logd(
                    "Provisioning complete broadcast has been sent to user " + managedUserHandle.getIdentifier());
        }
    }

    private void createProfile(String profileName) throws ProvisioningException {

        ProvisionLogger.logd("Creating managed profile with name " + profileName);

        mManagedProfileOrUserInfo = mUserManager.createProfileForUser(profileName,
                UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_DISABLED, Process.myUserHandle().getIdentifier());

        if (mManagedProfileOrUserInfo == null) {
            throw raiseError("Couldn't create profile.");
        }
    }

    private void installMdmOnManagedProfile() throws ProvisioningException {
        ProvisionLogger.logd("Installing mobile device management app " + mParams.deviceAdminComponentName
                + " on managed profile");

        try {
            int status = mIpm.installExistingPackageAsUser(mParams.deviceAdminComponentName.getPackageName(),
                    mManagedProfileOrUserInfo.id);
            switch (status) {
            case PackageManager.INSTALL_SUCCEEDED:
                return;
            case PackageManager.INSTALL_FAILED_USER_RESTRICTED:
                // Should not happen because we're not installing a restricted user
                throw raiseError("Could not install mobile device management app on managed "
                        + "profile because the user is restricted");
            case PackageManager.INSTALL_FAILED_INVALID_URI:
                // Should not happen because we already checked
                throw raiseError("Could not install mobile device management app on managed "
                        + "profile because the package could not be found");
            default:
                throw raiseError("Could not install mobile device management app on managed "
                        + "profile. Unknown status: " + status);
            }
        } catch (RemoteException neverThrown) {
            // Never thrown, as we are making local calls.
            ProvisionLogger.loge("This should not happen.", neverThrown);
        }
    }

    private void setMdmAsManagedProfileOwner() throws ProvisioningException {
        ProvisionLogger.logd("Setting package " + mParams.deviceAdminComponentName + " as managed profile owner.");

        DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
        if (!dpm.setProfileOwner(mParams.deviceAdminComponentName,
                mParams.deviceAdminComponentName.getPackageName(), mManagedProfileOrUserInfo.id)) {
            ProvisionLogger.logw("Could not set profile owner.");
            throw raiseError("Could not set profile owner.");
        }
    }

    private void setMdmAsActiveAdmin() {
        ProvisionLogger.logd("Setting package " + mParams.deviceAdminComponentName + " as active admin.");

        DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
        dpm.setActiveAdmin(mParams.deviceAdminComponentName, true /* refreshing*/, mManagedProfileOrUserInfo.id);
    }

    private void setOrganizationColor() {
        if (mParams.mainColor != null) {
            DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
            dpm.setOrganizationColorForUser(mParams.mainColor, mManagedProfileOrUserInfo.id);
        }
    }

    private ProvisioningException raiseError(String message) throws ProvisioningException {
        throw new ProvisioningException(message);
    }

    /**
     * Record the fact that an error occurred, change mProvisioningStatus to
     * reflect the fact the provisioning process failed
     */
    private void error(String dialogMessage, Exception e) {
        synchronized (this) {
            // Only case where an error condition should be notified is if we
            // are in the normal flow for provisioning. If the process has been
            // cancelled or already completed, then the fact there is an error
            // is almost irrelevant.
            if (mProvisioningStatus == STATUS_STARTED) {
                mProvisioningStatus = STATUS_ERROR;
                mLastErrorMessage = dialogMessage;

                ProvisionLogger.logw("Error occured during provisioning process: " + dialogMessage, e);
            } else {
                ProvisionLogger.logw(
                        "Unexpected error occured in status [" + mProvisioningStatus + "]: " + dialogMessage, e);
            }
        }
    }

    private void setDefaultUserRestrictions() {
        mUserManager.setUserRestriction(UserManager.DISALLOW_WALLPAPER, true,
                mManagedProfileOrUserInfo.getUserHandle());
    }

    private void notifyActivityError() {
        Intent intent = new Intent(ACTION_PROVISIONING_ERROR);
        intent.putExtra(EXTRA_LOG_MESSAGE_KEY, mLastErrorMessage);
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }

    private void notifyActivityCancelled() {
        Intent cancelIntent = new Intent(ACTION_PROVISIONING_CANCELLED);
        LocalBroadcastManager.getInstance(this).sendBroadcast(cancelIntent);
    }

    /**
     * Performs cleanup of any created user-profile on failure/cancellation.
     */
    private void cleanupUserProfile() {
        if (mManagedProfileOrUserInfo != null && !isProvisioningManagedUser()) {
            ProvisionLogger.logd("Removing managed profile");
            mUserManager.removeUser(mManagedProfileOrUserInfo.id);
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * Internal exception to allow provisioning process to terminal quickly and
     * cleanly on first error, rather than continuing to process despite errors
     * occurring.
     */
    private static class ProvisioningException extends Exception {
        public ProvisioningException(String detailMessage) {
            super(detailMessage);
        }
    }

    public boolean isProvisioningManagedUser() {
        return mParams.provisioningAction.equals(DevicePolicyManager.ACTION_PROVISION_MANAGED_USER);
    }

    /**
     * BroadcastReceiver that listens to {@link Intent#ACTION_USER_UNLOCKED} in order to provide
     * a blocking wait until the managed profile has been started and unlocked.
     */
    private static class UserUnlockedReceiver extends BroadcastReceiver {
        private static final IntentFilter FILTER = new IntentFilter(Intent.ACTION_USER_UNLOCKED);

        private final Semaphore semaphore = new Semaphore(0);
        private final Context mContext;
        private final int mUserId;

        UserUnlockedReceiver(Context context, int userId) {
            mContext = context;
            mUserId = userId;
            mContext.registerReceiverAsUser(this, new UserHandle(userId), FILTER, null, null);
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            if (!Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
                ProvisionLogger.logw("Unexpected intent: " + intent);
                return;
            }
            if (intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL) == mUserId) {
                ProvisionLogger.logd("Received ACTION_USER_UNLOCKED for user " + mUserId);
                semaphore.release();
                mContext.unregisterReceiver(this);
            }
        }

        public boolean waitForUserUnlocked() {
            ProvisionLogger.logd("Waiting for ACTION_USER_UNLOCKED");
            try {
                return semaphore.tryAcquire(USER_UNLOCKED_TIMEOUT_SECONDS, TimeUnit.SECONDS);
            } catch (InterruptedException ie) {
                mContext.unregisterReceiver(this);
                return false;
            }
        }
    }
}