/* * Copyright 2016, 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.finalization; import static android.app.admin.DeviceAdminReceiver.ACTION_PROFILE_PROVISIONING_COMPLETE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISIONING_SUCCESSFUL; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE; import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.NonNull; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.UserHandle; import com.android.internal.annotations.VisibleForTesting; import com.android.managedprovisioning.common.IllegalProvisioningArgumentException; import com.android.managedprovisioning.common.ProvisionLogger; import com.android.managedprovisioning.common.SettingsFacade; import com.android.managedprovisioning.common.Utils; import com.android.managedprovisioning.model.ProvisioningParams; import java.io.File; /** * Controller for the finalization of managed provisioning. * *

This controller is invoked when the active provisioning is completed via * {@link #provisioningInitiallyDone(ProvisioningParams)}. In the case of provisioning during SUW, * it is invoked again when provisioning is finalized via {@link #provisioningFinalized()}.

*/ public class FinalizationController { private static final String PROVISIONING_PARAMS_FILE_NAME = "finalization_activity_provisioning_params.xml"; private final Context mContext; private final Utils mUtils; private final SettingsFacade mSettingsFacade; private final UserProvisioningStateHelper mHelper; public FinalizationController(Context context) { this( context, new Utils(), new SettingsFacade(), new UserProvisioningStateHelper(context)); } @VisibleForTesting FinalizationController(Context context, Utils utils, SettingsFacade settingsFacade, UserProvisioningStateHelper helper) { mContext = checkNotNull(context); mUtils = checkNotNull(utils); mSettingsFacade = checkNotNull(settingsFacade); mHelper = checkNotNull(helper); } /** * This method is invoked when the provisioning process is done. * *

If provisioning happens as part of SUW, we rely on {@link #provisioningFinalized()} to be * called at the end of SUW. Otherwise, this method will finalize provisioning. If called after * SUW, this method notifies the DPC about the completed provisioning; otherwise, it stores the * provisioning params for later digestion.

* * @param params the provisioning params */ public void provisioningInitiallyDone(ProvisioningParams params) { if (!mHelper.isStateUnmanagedOrFinalized()) { // In any other state than STATE_USER_UNMANAGED and STATE_USER_SETUP_FINALIZED, we've // already run this method, so don't do anything. // STATE_USER_SETUP_FINALIZED can occur here if a managed profile is provisioned on a // device owner device. ProvisionLogger.logw("provisioningInitiallyDone called, but state is not finalized or " + "unmanaged"); return; } mHelper.markUserProvisioningStateInitiallyDone(params); if (ACTION_PROVISION_MANAGED_PROFILE.equals(params.provisioningAction) && mSettingsFacade.isUserSetupCompleted(mContext)) { // If a managed profile was provisioned after SUW, notify the DPC straight away notifyDpcManagedProfile(params); } else { // Otherwise store the information and wait for provisioningFinalized to be called storeProvisioningParams(params); } } /** * This method is invoked when provisioning is finalized. * *

This method has to be invoked after {@link #provisioningInitiallyDone(ProvisioningParams)} * was called. It is commonly invoked at the end of SUW if provisioning occurs during SUW. It * loads the provisioning params from the storage, notifies the DPC about the completed * provisioning and sets the right user provisioning states.

*/ void provisioningFinalized() { if (mHelper.isStateUnmanagedOrFinalized()) { ProvisionLogger.logw("provisioningInitiallyDone called, but state is finalized or " + "unmanaged"); return; } final ProvisioningParams params = loadProvisioningParamsAndClearFile(); if (params == null) { ProvisionLogger.logw("FinalizationController invoked, but no stored params"); return; } if (params.provisioningAction.equals(ACTION_PROVISION_MANAGED_PROFILE)) { notifyDpcManagedProfile(params); } else { // For managed user and device owner, we send the provisioning complete intent and maybe // launch the DPC. Intent provisioningCompleteIntent = createProvisioningCompleteIntent(params); if (provisioningCompleteIntent == null) { return; } mContext.sendBroadcast(provisioningCompleteIntent); maybeLaunchDpc(params, UserHandle.myUserId()); } mHelper.markUserProvisioningStateFinalized(params); } /** * Notify the DPC on the managed profile that provisioning has completed. When the DPC has * received the intent, send notify the primary instance that the profile is ready. */ private void notifyDpcManagedProfile(ProvisioningParams params) { UserHandle managedUserHandle = mUtils.getManagedProfile(mContext); // Use an ordered broadcast, so that we only finish when the DPC has received it. // Avoids a lag in the transition between provisioning and the DPC. BroadcastReceiver dpcReceivedSuccessReceiver = new DpcReceivedSuccessReceiver(params.accountToMigrate, params.keepAccountMigrated, managedUserHandle, params.deviceAdminComponentName.getPackageName()); Intent completeIntent = createProvisioningCompleteIntent(params); mContext.sendOrderedBroadcastAsUser(completeIntent, managedUserHandle, null, dpcReceivedSuccessReceiver, null, Activity.RESULT_OK, null, null); ProvisionLogger.logd("Provisioning complete broadcast has been sent to user " + managedUserHandle.getIdentifier()); maybeLaunchDpc(params, managedUserHandle.getIdentifier()); } private void maybeLaunchDpc(ProvisioningParams params, int userId) { final Intent dpcLaunchIntent = createDpcLaunchIntent(params); if (mUtils.canResolveIntentAsUser(mContext, dpcLaunchIntent, userId)) { mContext.startActivityAsUser(dpcLaunchIntent, UserHandle.of(userId)); ProvisionLogger.logd("Dpc was launched for user: " + userId); } } private Intent createProvisioningCompleteIntent(@NonNull ProvisioningParams params) { Intent intent = new Intent(ACTION_PROFILE_PROVISIONING_COMPLETE); try { intent.setComponent(mUtils.findDeviceAdmin( params.deviceAdminPackageName, params.deviceAdminComponentName, mContext)); } catch (IllegalProvisioningArgumentException e) { ProvisionLogger.loge("Failed to infer the device admin component name", e); return null; } intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES | Intent.FLAG_RECEIVER_FOREGROUND); addExtrasToIntent(intent, params); return intent; } private Intent createDpcLaunchIntent(@NonNull ProvisioningParams params) { Intent intent = new Intent(ACTION_PROVISIONING_SUCCESSFUL); final String packageName = params.inferDeviceAdminPackageName(); if (packageName == null) { ProvisionLogger.loge("Device admin package name is null"); return null; } intent.setPackage(packageName); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); addExtrasToIntent(intent, params); return intent; } private void addExtrasToIntent(Intent intent, ProvisioningParams params) { intent.putExtra(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE, params.adminExtrasBundle); } private void storeProvisioningParams(ProvisioningParams params) { params.save(getProvisioningParamsFile()); } private File getProvisioningParamsFile() { return new File(mContext.getFilesDir(), PROVISIONING_PARAMS_FILE_NAME); } @VisibleForTesting ProvisioningParams loadProvisioningParamsAndClearFile() { File file = getProvisioningParamsFile(); ProvisioningParams result = ProvisioningParams.load(file); file.delete(); return result; } }