| /* |
| * 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; |
| } |
| } |
| } |
| } |