/* * 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.preprovisioning; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DISCLAIMERS; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_IMEI; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCALE; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCAL_TIME; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SERIAL_NUMBER; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TIME_ZONE; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TRIGGER; import static android.app.admin.DevicePolicyManager.FLAG_SUPPORTED_MODES_DEVICE_OWNER; import static android.app.admin.DevicePolicyManager.FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED; import static android.app.admin.DevicePolicyManager.PROVISIONING_MODE_MANAGED_PROFILE_ON_PERSONAL_DEVICE; import static android.app.admin.DevicePolicyManager.PROVISIONING_TRIGGER_NFC; import static android.app.admin.DevicePolicyManager.PROVISIONING_TRIGGER_QR_CODE; import static android.app.admin.DevicePolicyManager.PROVISIONING_TRIGGER_UNSPECIFIED; import static android.app.admin.DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE; import static android.app.admin.DevicePolicyManager.STATUS_HAS_DEVICE_OWNER; import static android.app.admin.DevicePolicyManager.STATUS_MANAGED_USERS_NOT_SUPPORTED; import static android.app.admin.DevicePolicyManager.STATUS_NOT_SYSTEM_USER; import static android.app.admin.DevicePolicyManager.STATUS_OK; import static android.app.admin.DevicePolicyManager.STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS; import static android.app.admin.DevicePolicyManager.STATUS_USER_SETUP_COMPLETED; import static com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker.CANCELLED_BEFORE_PROVISIONING; import static com.android.managedprovisioning.common.Globals.ACTION_RESUME_PROVISIONING; import static com.android.managedprovisioning.model.ProvisioningParams.DEFAULT_EXTRA_PROVISIONING_KEEP_ACCOUNT_MIGRATED; import static com.android.managedprovisioning.model.ProvisioningParams.DEFAULT_EXTRA_PROVISIONING_PERMISSION_GRANT_OPT_OUT; import static com.android.managedprovisioning.model.ProvisioningParams.DEFAULT_EXTRA_PROVISIONING_SKIP_ENCRYPTION; import static com.android.managedprovisioning.model.ProvisioningParams.DEFAULT_LEAVE_ALL_SYSTEM_APPS_ENABLED; import static com.android.managedprovisioning.model.ProvisioningParams.FLOW_TYPE_ADMIN_INTEGRATED; import static java.util.Objects.requireNonNull; import android.accounts.Account; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.KeyguardManager; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.os.PersistableBundle; import android.os.SystemClock; import android.os.UserManager; import android.service.persistentdata.PersistentDataBlockManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import androidx.activity.ComponentActivity; import androidx.lifecycle.LiveData; import androidx.lifecycle.ViewModelProvider; import com.android.internal.annotations.VisibleForTesting; import com.android.managedprovisioning.ManagedProvisioningBaseApplication; import com.android.managedprovisioning.ManagedProvisioningScreens; import com.android.managedprovisioning.R; import com.android.managedprovisioning.analytics.MetricsWriterFactory; import com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker; import com.android.managedprovisioning.common.DefaultFeatureFlagChecker; import com.android.managedprovisioning.common.DefaultIntentResolverChecker; import com.android.managedprovisioning.common.DefaultPackageInstallChecker; import com.android.managedprovisioning.common.DeviceManagementRoleHolderHelper; import com.android.managedprovisioning.common.DeviceManagementRoleHolderHelper.DefaultResolveIntentChecker; import com.android.managedprovisioning.common.DeviceManagementRoleHolderHelper.DefaultRoleHolderStubChecker; import com.android.managedprovisioning.common.DeviceManagementRoleHolderUpdaterHelper; import com.android.managedprovisioning.common.GetProvisioningModeUtils; import com.android.managedprovisioning.common.IllegalProvisioningArgumentException; import com.android.managedprovisioning.common.ManagedProvisioningSharedPreferences; import com.android.managedprovisioning.common.PolicyComplianceUtils; import com.android.managedprovisioning.common.ProvisionLogger; import com.android.managedprovisioning.common.RoleHolderProvider; import com.android.managedprovisioning.common.RoleHolderUpdaterProvider; import com.android.managedprovisioning.common.SettingsFacade; import com.android.managedprovisioning.common.StoreUtils; import com.android.managedprovisioning.common.Utils; import com.android.managedprovisioning.model.DisclaimersParam; import com.android.managedprovisioning.model.ProvisioningParams; import com.android.managedprovisioning.model.ProvisioningParams.FlowType; import com.android.managedprovisioning.parser.DisclaimerParser; import com.android.managedprovisioning.parser.DisclaimersParserImpl; import com.android.managedprovisioning.preprovisioning.PreProvisioningViewModel.DefaultConfig; import com.android.managedprovisioning.preprovisioning.PreProvisioningViewModel.PreProvisioningViewModelFactory; import com.android.managedprovisioning.provisioning.Constants; import com.android.managedprovisioning.util.LazyStringResource; import com.google.android.setupdesign.util.DeviceHelper; import java.util.IllformedLocaleException; import java.util.List; import java.util.function.BiFunction; /** * Controller which contains business logic related to provisioning preparation. * * @see PreProvisioningActivity */ public class PreProvisioningActivityController { private final Context mContext; private final Ui mUi; private final Utils mUtils; private final PolicyComplianceUtils mPolicyComplianceUtils; private final GetProvisioningModeUtils mGetProvisioningModeUtils; private final SettingsFacade mSettingsFacade; // used system services private final DevicePolicyManager mDevicePolicyManager; private final UserManager mUserManager; private final PackageManager mPackageManager; private final KeyguardManager mKeyguardManager; private final PersistentDataBlockManager mPdbManager; private final ProvisioningAnalyticsTracker mProvisioningAnalyticsTracker; private final ManagedProvisioningSharedPreferences mSharedPreferences; private final PreProvisioningViewModel mViewModel; private final BiFunction mDisclaimerParserProvider; private final DeviceManagementRoleHolderHelper mRoleHolderHelper; private final DeviceManagementRoleHolderUpdaterHelper mRoleHolderUpdaterHelper; public PreProvisioningActivityController( @NonNull ComponentActivity activity, @NonNull Ui ui) { this(activity, ui, new Utils(), new SettingsFacade(), new ManagedProvisioningSharedPreferences(activity), new PolicyComplianceUtils(), new GetProvisioningModeUtils(), new ViewModelProvider( activity, new PreProvisioningViewModelFactory( (ManagedProvisioningBaseApplication) activity.getApplication(), new DefaultConfig(), new Utils())) .get(PreProvisioningViewModel.class), DisclaimersParserImpl::new, new DeviceManagementRoleHolderHelper( RoleHolderProvider.DEFAULT.getPackageName(activity), new DefaultPackageInstallChecker(activity.getPackageManager(), new Utils()), new DefaultResolveIntentChecker(), new DefaultRoleHolderStubChecker(), new DefaultFeatureFlagChecker(activity.getContentResolver()) ), new DeviceManagementRoleHolderUpdaterHelper( RoleHolderUpdaterProvider.DEFAULT.getPackageName(activity), RoleHolderProvider.DEFAULT.getPackageName(activity), new DefaultPackageInstallChecker(activity.getPackageManager(), new Utils()), new DefaultIntentResolverChecker(activity.getPackageManager()), new DefaultFeatureFlagChecker(activity.getContentResolver()))); } @VisibleForTesting PreProvisioningActivityController( @NonNull Context context, @NonNull Ui ui, @NonNull Utils utils, @NonNull SettingsFacade settingsFacade, @NonNull ManagedProvisioningSharedPreferences sharedPreferences, @NonNull PolicyComplianceUtils policyComplianceUtils, @NonNull GetProvisioningModeUtils getProvisioningModeUtils, @NonNull PreProvisioningViewModel viewModel, @NonNull BiFunction disclaimerParserProvider, @NonNull DeviceManagementRoleHolderHelper roleHolderHelper, @NonNull DeviceManagementRoleHolderUpdaterHelper roleHolderUpdaterHelper) { mContext = requireNonNull(context, "Context must not be null"); mUi = requireNonNull(ui, "Ui must not be null"); mSettingsFacade = requireNonNull(settingsFacade); mUtils = requireNonNull(utils, "Utils must not be null"); mPolicyComplianceUtils = requireNonNull(policyComplianceUtils, "PolicyComplianceUtils cannot be null"); mGetProvisioningModeUtils = requireNonNull(getProvisioningModeUtils, "GetProvisioningModeUtils cannot be null"); mSharedPreferences = requireNonNull(sharedPreferences); mViewModel = requireNonNull(viewModel); mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class); mUserManager = mContext.getSystemService(UserManager.class); mPackageManager = mContext.getPackageManager(); mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); mPdbManager = (PersistentDataBlockManager) mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE); mProvisioningAnalyticsTracker = new ProvisioningAnalyticsTracker( MetricsWriterFactory.getMetricsWriter(mContext, mSettingsFacade), mSharedPreferences); mDisclaimerParserProvider = requireNonNull(disclaimerParserProvider); mRoleHolderHelper = requireNonNull(roleHolderHelper); mRoleHolderUpdaterHelper = requireNonNull(roleHolderUpdaterHelper); } /** * Starts provisioning via the role holder if possible, or if offline provisioning is allowed, * falls back to AOSP ManagedProvisioning provisioning. * * @return {@code true} if any form of provisioning was started (either role holder or * platform). */ boolean startAppropriateProvisioning( Intent managedProvisioningIntent, Bundle roleHolderAdditionalExtras, String callingPackage) { boolean isRoleHolderReadyForProvisioning = mRoleHolderHelper .isRoleHolderReadyForProvisioning(mContext, managedProvisioningIntent); boolean isRoleHolderProvisioningAllowed = Constants.isRoleHolderProvisioningAllowedForAction( managedProvisioningIntent.getAction()); // In T allowOffline is used here to force platform provisioning. if (getParams().allowOffline) { ProvisionLogger.logw("allowOffline set, provisioning via platform."); performPlatformProvidedProvisioning(); return true; } if (isRoleHolderReadyForProvisioning && isRoleHolderProvisioningAllowed) { ProvisionLogger.logw("Provisioning via role holder."); mRoleHolderHelper.ensureRoleGranted(mContext, success -> { if (success) { Intent roleHolderProvisioningIntent = mRoleHolderHelper.createRoleHolderProvisioningIntent( managedProvisioningIntent, roleHolderAdditionalExtras, callingPackage, mViewModel.getRoleHolderState() ); mSharedPreferences.setIsProvisioningFlowDelegatedToRoleHolder(true); mViewModel.onRoleHolderProvisioningInitiated(); mUi.startRoleHolderProvisioning(roleHolderProvisioningIntent); } else { ProvisionLogger.logw("Falling back to provisioning via platform."); performPlatformProvidedProvisioning(); } }); return true; } else if (!mRoleHolderHelper.isRoleHolderProvisioningEnabled() || !mRoleHolderUpdaterHelper.isRoleHolderUpdaterDefined() || !isRoleHolderProvisioningAllowed) { ProvisionLogger.logw("Provisioning via platform."); performPlatformProvidedProvisioning(); return true; } ProvisionLogger.logw("Role holder is configured, can't provision via role holder and " + "PROVISIONING_ALLOW_OFFLINE is false."); return false; } /** * Starts the role holder updater, saving {@code roleHolderState} to be used to restart * the role holder. * * @see DevicePolicyManager#EXTRA_ROLE_HOLDER_STATE */ public void startRoleHolderUpdater( boolean isRoleHolderRequestedUpdate, @Nullable PersistableBundle roleHolderState) { mViewModel.onRoleHolderUpdateInitiated(); mViewModel.setRoleHolderState(roleHolderState); mUi.startRoleHolderUpdater(isRoleHolderRequestedUpdate); } /** * Starts the role holder updater with the last provided role holder state. * *

This can be useful in update retry cases. */ public void startRoleHolderUpdaterWithLastState(boolean isRoleHolderRequestedUpdate) { startRoleHolderUpdater(isRoleHolderRequestedUpdate, mViewModel.getRoleHolderState()); } interface Ui { /** * Show an error message and cancel provisioning. * * @param title resource id used to form the user facing error title * @param message resource id used to form the user facing error message * @param errorMessage an error message that gets logged for debugging */ void showErrorAndClose( LazyStringResource title, LazyStringResource message, String errorMessage); /** * Show an error message and cancel provisioning. * * @see #showErrorAndClose(LazyStringResource, LazyStringResource, String) */ void showErrorAndClose(Integer titleId, int messageId, String errorMessage); /** * Request the user to encrypt the device. * * @param params the {@link ProvisioningParams} object related to the ongoing provisioning */ void requestEncryption(ProvisioningParams params); /** * Request the user to choose a wifi network. */ void requestWifiPick(); /** * Start provisioning. * * @param params the {@link ProvisioningParams} object related to the ongoing provisioning */ void startProvisioning(ProvisioningParams params); /** * Show an error dialog indicating that the current launcher does not support managed * profiles and ask the user to choose a different one. */ void showCurrentLauncherInvalid(); void showOwnershipDisclaimerScreen(ProvisioningParams params); void prepareFinancedDeviceFlow(ProvisioningParams params); void showFactoryResetDialog(Integer titleId, int messageId); void showFactoryResetDialog(LazyStringResource titleId, LazyStringResource messageId); void initiateUi(UiParams uiParams); /** * Abort provisioning and close app */ void abortProvisioning(); void prepareAdminIntegratedFlow(ProvisioningParams params); void startRoleHolderUpdater(boolean isRoleHolderRequestedUpdate); void startRoleHolderProvisioning(Intent intent); void onParamsValidated(ProvisioningParams params); void startPlatformDrivenRoleHolderDownload(); } /** * Wrapper which holds information related to the consent screen. *

Does not implement {@link Object#equals(Object)}, {@link Object#hashCode()} * or {@link Object#toString()}. */ public static class UiParams { /** * Admin application package name. */ public String packageName; /** * List of headings for the organization-provided terms and conditions. */ public List disclaimerHeadings; public boolean isDeviceManaged; /** * The original provisioning action, kept for backwards compatibility. */ public String provisioningAction; public boolean isOrganizationOwnedProvisioning; } /** * Initiates Profile owner and device owner provisioning. * * @param intent Intent that started provisioning. * @param callingPackage Package that started provisioning. */ public void initiateProvisioning(Intent intent, String callingPackage) { mSharedPreferences.writeProvisioningStartedTimestamp(SystemClock.elapsedRealtime()); mSharedPreferences.setIsProvisioningFlowDelegatedToRoleHolder(false); mProvisioningAnalyticsTracker.logProvisioningSessionStarted(mContext); logProvisioningExtras(intent); if (!tryParseParameters(intent)) { return; } ProvisioningParams params = mViewModel.getParams(); if (!checkFactoryResetProtection(params, callingPackage)) { return; } if (!verifyActionAndCaller(intent, callingPackage)) { return; } mProvisioningAnalyticsTracker.logProvisioningExtras(mContext, intent); mProvisioningAnalyticsTracker.logEntryPoint(mContext, intent, mSettingsFacade); // Check whether provisioning is allowed for the current action. This check needs to happen // before any actions that might affect the state of the device. // Note that checkDevicePolicyPreconditions takes care of calling // showProvisioningErrorAndClose. So we only need to show the factory reset dialog (if // applicable) and return. if (!checkDevicePolicyPreconditions()) { return; } if (!isIntentActionValid(intent.getAction())) { ProvisionLogger.loge( ACTION_PROVISION_MANAGED_DEVICE + " is no longer a supported intent action."); mUi.abortProvisioning(); return; } if (isDeviceOwnerProvisioning()) { // TODO: make a general test based on deviceAdminDownloadInfo field // PO doesn't ever initialize that field, so OK as a general case if (shouldShowWifiPicker(intent)) { // Have the user pick a wifi network if necessary. // It is not possible to ask the user to pick a wifi network if // the screen is locked. // TODO: remove this check once we know the screen will not be locked. if (mKeyguardManager.inKeyguardRestrictedInputMode()) { // TODO: decide on what to do in that case; fail? retry on screen unlock? ProvisionLogger.logi("Cannot pick wifi because the screen is locked."); } else if (canRequestWifiPick()) { // we resume this method after a successful WiFi pick // TODO: refactor as evil - logic should be less spread out mUi.requestWifiPick(); return; } else { mUi.showErrorAndClose(R.string.cant_set_up_device, R.string.contact_your_admin_for_help, "Cannot pick WiFi because there is no handler to the intent"); } } } mUi.onParamsValidated(params); // TODO(b/207376815): Have a PreProvisioningForwarderActivity to forward to either // platform-provided provisioning or DMRH if (mRoleHolderUpdaterHelper.shouldPlatformDownloadRoleHolder(intent, params) && !params.allowOffline) { mUi.startPlatformDrivenRoleHolderDownload(); } else if (mRoleHolderUpdaterHelper .shouldStartRoleHolderUpdater(mContext, intent, params) && !params.allowOffline) { resetRoleHolderUpdateRetryCount(); startRoleHolderUpdater( /* isRoleHolderRequestedUpdate= */ false, /* roleHolderState= */ null); } else { boolean isProvisioningStarted = startAppropriateProvisioning(intent, new Bundle(), callingPackage); if (!isProvisioningStarted) { mUi.showErrorAndClose( R.string.cant_set_up_device, R.string.contact_your_admin_for_help, "Could not start provisioning."); } } } private void logProvisioningExtras(Intent intent) { Bundle extras = intent.getExtras(); if (extras == null) { ProvisionLogger.logi("No extras have been passed."); return; } ProvisionLogger.logi("Start logging provisioning extras"); for (String key : extras.keySet()) { ProvisionLogger.logi("Extra key: " + key + ", extra value: " + extras.get(key)); } ProvisionLogger.logi("Finish logging provisioning extras"); } void performPlatformProvidedProvisioning() { ProvisionLogger.logw("Provisioning via platform-provided provisioning"); ProvisioningParams params = mViewModel.getParams(); if (mSharedPreferences.isProvisioningFlowDelegatedToRoleHolder()) { mSharedPreferences.setIsProvisioningFlowDelegatedToRoleHolder(false); } mViewModel.getTimeLogger().start(); mViewModel.onPlatformProvisioningInitiated(); if (mUtils.checkAdminIntegratedFlowPreconditions(params)) { if (mUtils.shouldShowOwnershipDisclaimerScreen(params)) { mUi.showOwnershipDisclaimerScreen(params); } else { mUi.prepareAdminIntegratedFlow(params); } mViewModel.onAdminIntegratedFlowInitiated(); } else if (mUtils.isFinancedDeviceAction(params.provisioningAction)) { mUi.prepareFinancedDeviceFlow(params); } else if (isProfileOwnerProvisioning()) { startManagedProfileFlow(); } } private boolean isIntentActionValid(String action) { return !ACTION_PROVISION_MANAGED_DEVICE.equals(action); } private void startManagedProfileFlow() { ProvisionLogger.logi("Starting the managed profile flow."); showUserConsentScreen(); } private boolean isNfcProvisioning(Intent intent) { return intent.getIntExtra(EXTRA_PROVISIONING_TRIGGER, PROVISIONING_TRIGGER_UNSPECIFIED) == PROVISIONING_TRIGGER_NFC; } private boolean isQrCodeProvisioning(Intent intent) { if (!ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE.equals(intent.getAction())) { return false; } final int provisioningTrigger = intent.getIntExtra(EXTRA_PROVISIONING_TRIGGER, PROVISIONING_TRIGGER_UNSPECIFIED); return provisioningTrigger == PROVISIONING_TRIGGER_QR_CODE; } private boolean shouldShowWifiPicker(Intent intent) { if (mSharedPreferences.isEstablishNetworkConnectionRun()) { return false; } ProvisioningParams params = mViewModel.getParams(); if (params.wifiInfo != null) { return false; } if (params.deviceAdminDownloadInfo == null) { return false; } var networkCapabilities = mUtils.getActiveNetworkCapabilities(mContext); if (networkCapabilities != null && (mUtils.isNetworkConnectedToInternetViaWiFi(networkCapabilities) || mUtils.isNetworkConnectedToInternetViaEthernet(networkCapabilities))) { return false; } // we intentionally disregard whether mobile is connected for QR and NFC // provisioning. b/153442588 for context if (params.useMobileData && (isQrCodeProvisioning(intent) || isNfcProvisioning(intent))) { return false; } if (params.useMobileData) { return !mUtils.isMobileNetworkConnectedToInternet(mContext); } return true; } void showUserConsentScreen() { // Check whether provisioning is allowed for the current action if (!checkDevicePolicyPreconditions()) { return; } if (mViewModel.getParams().provisioningAction.equals(ACTION_PROVISION_MANAGED_PROFILE) && mViewModel.getParams().isOrganizationOwnedProvisioning) { mProvisioningAnalyticsTracker.logOrganizationOwnedManagedProfileProvisioning(); } // show UI so we can get user's consent to continue final String packageName = mViewModel.getParams().inferDeviceAdminPackageName(); final UiParams uiParams = new UiParams(); uiParams.provisioningAction = mViewModel.getParams().provisioningAction; uiParams.packageName = packageName; uiParams.isDeviceManaged = mDevicePolicyManager.isDeviceManaged(); uiParams.isOrganizationOwnedProvisioning = mViewModel.getParams().isOrganizationOwnedProvisioning; mUi.initiateUi(uiParams); mViewModel.onShowUserConsent(); } boolean updateProvisioningParamsFromIntent(Intent resultIntent) { final int provisioningMode = resultIntent.getIntExtra( DevicePolicyManager.EXTRA_PROVISIONING_MODE, 0); if (!mViewModel.getParams().allowedProvisioningModes.contains(provisioningMode)) { ProvisionLogger.loge("Invalid provisioning mode chosen by the DPC: " + provisioningMode + ", but expected one of " + mViewModel.getParams().allowedProvisioningModes.toString()); return false; } switch (provisioningMode) { case DevicePolicyManager.PROVISIONING_MODE_FULLY_MANAGED_DEVICE: updateParamsPostProvisioningModeDecision( resultIntent, ACTION_PROVISION_MANAGED_DEVICE, /* isOrganizationOwnedProvisioning */ true, /* updateAccountToMigrate */ false); return true; case DevicePolicyManager.PROVISIONING_MODE_MANAGED_PROFILE: updateParamsPostProvisioningModeDecision( resultIntent, ACTION_PROVISION_MANAGED_PROFILE, mUtils.isOrganizationOwnedAllowed(mViewModel.getParams()), /* updateAccountToMigrate */ true); return true; case PROVISIONING_MODE_MANAGED_PROFILE_ON_PERSONAL_DEVICE: updateParamsPostProvisioningModeDecision( resultIntent, ACTION_PROVISION_MANAGED_PROFILE, /* isOrganizationOwnedProvisioning */ false, /* updateAccountToMigrate */ true); return true; default: ProvisionLogger.logw("Unknown returned provisioning mode:" + provisioningMode); return false; } } private void updateParamsPostProvisioningModeDecision(Intent resultIntent, String provisioningAction, boolean isOrganizationOwnedProvisioning, boolean updateAccountToMigrate) { ProvisioningParams.Builder builder = mViewModel.getParams().toBuilder(); builder.setFlowType(FLOW_TYPE_ADMIN_INTEGRATED); builder.setProvisioningAction(provisioningAction); builder.setIsOrganizationOwnedProvisioning(isOrganizationOwnedProvisioning); maybeUpdateAdminExtrasBundle(builder, resultIntent); maybeUpdateSkipEducationScreens(builder, resultIntent); maybeUpdateDisclaimers(builder, resultIntent); maybeUpdateSkipEncryption(builder, resultIntent); if (updateAccountToMigrate) { maybeUpdateAccountToMigrate(builder, resultIntent); } if (provisioningAction.equals(ACTION_PROVISION_MANAGED_PROFILE)) { maybeUpdateKeepAccountMigrated(builder, resultIntent); maybeUpdateLeaveAllSystemAppsEnabled(builder, resultIntent); } else if (provisioningAction.equals(ACTION_PROVISION_MANAGED_DEVICE)) { maybeUpdateDeviceOwnerPermissionGrantOptOut(builder, resultIntent); maybeUpdateLocale(builder, resultIntent); maybeUpdateLocalTime(builder, resultIntent); maybeUpdateTimeZone(builder, resultIntent); } mViewModel.updateParams(builder.build()); } private void maybeUpdateDeviceOwnerPermissionGrantOptOut( ProvisioningParams.Builder builder, Intent resultIntent) { if (resultIntent.hasExtra(EXTRA_PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT)) { builder.setDeviceOwnerPermissionGrantOptOut(resultIntent.getBooleanExtra( EXTRA_PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT, DEFAULT_EXTRA_PROVISIONING_PERMISSION_GRANT_OPT_OUT)); } } private void maybeUpdateSkipEncryption( ProvisioningParams.Builder builder, Intent resultIntent) { if (resultIntent.hasExtra(EXTRA_PROVISIONING_SKIP_ENCRYPTION)) { builder.setSkipEncryption(resultIntent.getBooleanExtra( EXTRA_PROVISIONING_SKIP_ENCRYPTION, DEFAULT_EXTRA_PROVISIONING_SKIP_ENCRYPTION)); } } private void maybeUpdateTimeZone(ProvisioningParams.Builder builder, Intent resultIntent) { if (resultIntent.hasExtra(EXTRA_PROVISIONING_TIME_ZONE)) { builder.setTimeZone(resultIntent.getStringExtra(EXTRA_PROVISIONING_TIME_ZONE)); } } private void maybeUpdateLocalTime(ProvisioningParams.Builder builder, Intent resultIntent) { if (resultIntent.hasExtra(EXTRA_PROVISIONING_LOCAL_TIME)) { builder.setLocalTime(resultIntent.getLongExtra( EXTRA_PROVISIONING_LOCAL_TIME, ProvisioningParams.DEFAULT_LOCAL_TIME)); } } private void maybeUpdateLocale(ProvisioningParams.Builder builder, Intent resultIntent) { if (resultIntent.hasExtra(EXTRA_PROVISIONING_LOCALE)) { try { builder.setLocale(StoreUtils.stringToLocale( resultIntent.getStringExtra(EXTRA_PROVISIONING_LOCALE))); } catch (IllformedLocaleException e) { ProvisionLogger.loge("Could not parse locale.", e); } } } private void maybeUpdateDisclaimers(ProvisioningParams.Builder builder, Intent resultIntent) { if (resultIntent.hasExtra(EXTRA_PROVISIONING_DISCLAIMERS)) { try { DisclaimersParam disclaimersParam = mDisclaimerParserProvider.apply( mContext, mSharedPreferences.getProvisioningId()) .parse(resultIntent.getParcelableArrayExtra( EXTRA_PROVISIONING_DISCLAIMERS)); builder.setDisclaimersParam(disclaimersParam); } catch (ClassCastException e) { ProvisionLogger.loge("Could not parse disclaimer params.", e); } } } private void maybeUpdateSkipEducationScreens(ProvisioningParams.Builder builder, Intent resultIntent) { if (resultIntent.hasExtra(EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS)) { builder.setSkipEducationScreens(resultIntent.getBooleanExtra( EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS, /* defaultValue */ false)); } } private void maybeUpdateAccountToMigrate(ProvisioningParams.Builder builder, Intent resultIntent) { if (resultIntent.hasExtra(EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE)) { final Account account = resultIntent.getParcelableExtra( EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE); builder.setAccountToMigrate(account); } } /** * Appends the admin bundle in {@code resultIntent}, if provided, to the existing admin bundle, * if it exists, and stores the result in {@code builder}. */ private void maybeUpdateAdminExtrasBundle(ProvisioningParams.Builder builder, Intent resultIntent) { if (resultIntent.hasExtra(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE)) { PersistableBundle resultBundle = resultIntent.getParcelableExtra(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE); if (mViewModel.getParams().adminExtrasBundle != null) { PersistableBundle existingBundle = new PersistableBundle(mViewModel.getParams().adminExtrasBundle); existingBundle.putAll(resultBundle); resultBundle = existingBundle; } builder.setAdminExtrasBundle(resultBundle); } } private void maybeUpdateKeepAccountMigrated( ProvisioningParams.Builder builder, Intent resultIntent) { if (resultIntent.hasExtra(EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION)) { final boolean keepAccountMigrated = resultIntent.getBooleanExtra( EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION, DEFAULT_EXTRA_PROVISIONING_KEEP_ACCOUNT_MIGRATED); builder.setKeepAccountMigrated(keepAccountMigrated); } } private void maybeUpdateLeaveAllSystemAppsEnabled( ProvisioningParams.Builder builder, Intent resultIntent) { if (resultIntent.hasExtra(EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED)) { final boolean leaveAllSystemAppsEnabled = resultIntent.getBooleanExtra( EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED, DEFAULT_LEAVE_ALL_SYSTEM_APPS_ENABLED); builder.setLeaveAllSystemAppsEnabled(leaveAllSystemAppsEnabled); } } void updateProvisioningFlowState(@FlowType int flowType) { mViewModel.updateParams(mViewModel.getParams().toBuilder().setFlowType(flowType).build()); } Bundle getAdditionalExtrasForGetProvisioningModeIntent() { Bundle bundle = new Bundle(); if (shouldPassPersonalDataToAdminApp()) { final TelephonyManager telephonyManager = mContext.getSystemService( TelephonyManager.class); bundle.putString(EXTRA_PROVISIONING_IMEI, telephonyManager.getImei()); bundle.putString(EXTRA_PROVISIONING_SERIAL_NUMBER, Build.getSerial()); } ProvisioningParams params = mViewModel.getParams(); bundle.putParcelable(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE, params.adminExtrasBundle); bundle.putIntegerArrayList(EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES, params.allowedProvisioningModes); if (params.allowedProvisioningModes.contains( DevicePolicyManager.PROVISIONING_MODE_FULLY_MANAGED_DEVICE)) { bundle.putBoolean(EXTRA_PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT, params.deviceOwnerPermissionGrantOptOut); } return bundle; } private boolean shouldPassPersonalDataToAdminApp() { ProvisioningParams params = mViewModel.getParams(); return params.initiatorRequestedProvisioningModes == FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED || params.initiatorRequestedProvisioningModes == FLAG_SUPPORTED_MODES_DEVICE_OWNER; } protected Intent createViewTermsIntent() { return new Intent(mContext, getTermsActivityClass()) .putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, mViewModel.getParams()); } private Class getTermsActivityClass() { return getBaseApplication().getActivityClassForScreen(ManagedProvisioningScreens.TERMS); } private ManagedProvisioningBaseApplication getBaseApplication() { return (ManagedProvisioningBaseApplication) mContext.getApplicationContext(); } /** * Start provisioning for real. In profile owner case, double check that the launcher * supports managed profiles if necessary. In device owner case, possibly create a new user * before starting provisioning. */ public void continueProvisioningAfterUserConsent() { mProvisioningAnalyticsTracker.logProvisioningAction( mContext, mViewModel.getParams().provisioningAction); // check if encryption is required if (isEncryptionRequired()) { if (mDevicePolicyManager.getStorageEncryptionStatus() == DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED) { CharSequence deviceName = DeviceHelper.getDeviceName(mContext); mUi.showErrorAndClose( LazyStringResource.of(R.string.cant_set_up_device), LazyStringResource.of( R.string.device_doesnt_allow_encryption_contact_admin, deviceName), "This device does not support encryption, and " + DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION + " was not passed."); } else { mUi.requestEncryption(mViewModel.getParams()); // we come back to this method after returning from encryption dialog // TODO: refactor as evil - logic should be less spread out } return; } if (isProfileOwnerProvisioning()) { // PO case // Check whether the current launcher supports managed profiles. if (!mUtils.currentLauncherSupportsManagedProfiles(mContext)) { mUi.showCurrentLauncherInvalid(); // we come back to this method after returning from launcher dialog // TODO: refactor as evil - logic should be less spread out return; } } // Cancel the boot reminder as provisioning has now started. mViewModel.getEncryptionController().cancelEncryptionReminder(); stopTimeLogger(); mUi.startProvisioning(mViewModel.getParams()); mViewModel.onProvisioningStartedAfterUserConsent(); } /** * @return False if condition preventing further provisioning */ @VisibleForTesting boolean checkFactoryResetProtection(ProvisioningParams params, String callingPackage) { if (skipFactoryResetProtectionCheck(params, callingPackage)) { return true; } if (factoryResetProtected()) { CharSequence deviceName = DeviceHelper.getDeviceName(mContext); mUi.showErrorAndClose( LazyStringResource.of(R.string.cant_set_up_device), LazyStringResource.of( R.string.device_has_reset_protection_contact_admin, deviceName), "Factory reset protection blocks provisioning."); return false; } return true; } private boolean skipFactoryResetProtectionCheck( ProvisioningParams params, String callingPackage) { if (TextUtils.isEmpty(callingPackage)) { return false; } String persistentDataPackageName = mContext.getResources() .getString(com.android.internal.R.string.config_persistentDataPackageName); try { // Only skip the FRP check if the caller is the package responsible for maintaining FRP // - i.e. if this is a flow for restoring device owner after factory reset. PackageInfo callingPackageInfo = mPackageManager.getPackageInfo(callingPackage, 0); return callingPackageInfo != null && callingPackageInfo.applicationInfo != null && callingPackageInfo.applicationInfo.isSystemApp() && !TextUtils.isEmpty(persistentDataPackageName) && callingPackage.equals(persistentDataPackageName) && params != null && params.startedByTrustedSource; } catch (PackageManager.NameNotFoundException e) { ProvisionLogger.loge("Calling package not found.", e); return false; } } /** @return False if condition preventing further provisioning */ @VisibleForTesting protected boolean checkDevicePolicyPreconditions() { ProvisioningParams params = mViewModel.getParams(); int provisioningPreCondition = mDevicePolicyManager.checkProvisioningPrecondition( params.provisioningAction, params.inferDeviceAdminPackageName()); // Check whether provisioning is allowed for the current action. if (provisioningPreCondition != STATUS_OK) { mProvisioningAnalyticsTracker.logProvisioningNotAllowed(mContext, provisioningPreCondition); showProvisioningErrorAndClose( params.provisioningAction, provisioningPreCondition); return false; } return true; } /** @return False if condition preventing further provisioning */ private boolean tryParseParameters(Intent intent) { try { // Read the provisioning params from the provisioning intent mViewModel.loadParamsIfNecessary(intent); } catch (IllegalProvisioningArgumentException e) { mUi.showErrorAndClose(R.string.cant_set_up_device, R.string.contact_your_admin_for_help, e.getMessage()); return false; } return true; } /** @return False if condition preventing further provisioning */ @VisibleForTesting protected boolean verifyActionAndCaller(Intent intent, String callingPackage) { if (verifyActionAndCallerInner(intent, callingPackage)) { return true; } else { mUi.showErrorAndClose(R.string.cant_set_up_device, R.string.contact_your_admin_for_help, "invalid intent or calling package"); return false; } } private boolean verifyActionAndCallerInner(Intent intent, String callingPackage) { // If this is a resume after encryption or trusted intent, we verify the activity alias. // Otherwise, verify that the calling app is trying to set itself as Device/ProfileOwner if (ACTION_RESUME_PROVISIONING.equals(intent.getAction())) { return verifyActivityAlias(intent, "PreProvisioningActivityAfterEncryption"); } else if (ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE.equals(intent.getAction()) || ACTION_PROVISION_FINANCED_DEVICE.equals(intent.getAction())) { return verifyActivityAlias(intent, "PreProvisioningActivityViaTrustedApp"); } else { return verifyCaller(callingPackage); } } private boolean verifyActivityAlias(Intent intent, String activityAlias) { ComponentName componentName = intent.getComponent(); if (componentName == null || componentName.getClassName() == null) { ProvisionLogger.loge("null class in component when verifying activity alias " + activityAlias); return false; } if (!componentName.getClassName().endsWith(activityAlias)) { ProvisionLogger.loge("Looking for activity alias " + activityAlias + ", but got " + componentName.getClassName()); return false; } return true; } /** * Verify that the caller is trying to set itself as owner. * * @return false if the caller is trying to set a different package as owner. */ private boolean verifyCaller(@NonNull String callingPackage) { if (callingPackage == null) { ProvisionLogger.loge("Calling package is null. Was startActivityForResult used to " + "start this activity?"); return false; } if (!callingPackage.equals(mViewModel.getParams().inferDeviceAdminPackageName())) { ProvisionLogger.loge("Permission denied, " + "calling package tried to set a different package as owner. "); return false; } return true; } /** * Returns whether the device needs encryption. */ private boolean isEncryptionRequired() { return !mViewModel.getParams().skipEncryption && mUtils.isEncryptionRequired(); } /** * Returns whether the device is frp protected during setup wizard. */ private boolean factoryResetProtected() { // If we are started during setup wizard, check for factory reset protection. // If the device is already setup successfully, do not check factory reset protection. if (mSettingsFacade.isDeviceProvisioned(mContext)) { ProvisionLogger.logd("Device is provisioned, FRP not required."); return false; } if (mPdbManager == null) { ProvisionLogger.logd("Reset protection not supported."); return false; } int size = mPdbManager.getDataBlockSize(); ProvisionLogger.logd("Data block size: " + size); return size > 0; } /** * Returns whether activity to pick wifi can be requested or not. */ private boolean canRequestWifiPick() { return mPackageManager.resolveActivity(mUtils.getWifiPickIntent(), 0) != null; } /** * Returns whether the provisioning process is a profile owner provisioning process. */ public boolean isProfileOwnerProvisioning() { return mUtils.isProfileOwnerAction(mViewModel.getParams().provisioningAction); } /** * Returns whether the provisioning process is a device owner provisioning process. */ public boolean isDeviceOwnerProvisioning() { return mUtils.isDeviceOwnerAction(mViewModel.getParams().provisioningAction); } @Nullable public ProvisioningParams getParams() { return mViewModel.getParams(); } /** * Notifies the time logger to stop. */ public void stopTimeLogger() { mViewModel.getTimeLogger().stop(); } /** * Log if PreProvisioning was cancelled. */ public void logPreProvisioningCancelled() { mProvisioningAnalyticsTracker.logProvisioningCancelled(mContext, CANCELLED_BEFORE_PROVISIONING); } /** * Logs the provisioning flow type. */ public void logProvisioningFlowType() { mProvisioningAnalyticsTracker.logProvisioningFlowType(mViewModel.getParams()); } /** * Removes a user profile. If we are in COMP case, and were blocked by having to delete a user, * resumes COMP provisioning. */ public void removeUser(int userProfileId) { // There is a possibility that the DO has set the disallow remove managed profile user // restriction, but is initiating the provisioning. In this case, we still want to remove // the managed profile. // We know that we can remove the managed profile because we checked // DevicePolicyManager.checkProvisioningPreCondition mUserManager.removeUserEvenWhenDisallowed(userProfileId); } SettingsFacade getSettingsFacade() { return mSettingsFacade; } public PolicyComplianceUtils getPolicyComplianceUtils() { return mPolicyComplianceUtils; } public GetProvisioningModeUtils getGetProvisioningModeUtils() { return mGetProvisioningModeUtils; } void onReturnFromProvisioning() { mViewModel.onReturnFromProvisioning(); } LiveData getState() { return mViewModel.getState(); } void incrementRoleHolderUpdateRetryCount() { mViewModel.incrementRoleHolderUpdateRetryCount(); } void resetRoleHolderUpdateRetryCount() { mViewModel.resetRoleHolderUpdateRetryCount(); } boolean canRetryRoleHolderUpdate() { return mViewModel.canRetryRoleHolderUpdate(); } private void showProvisioningErrorAndClose(String action, int provisioningPreCondition) { // Try to show an error message explaining why provisioning is not allowed. switch (action) { case ACTION_PROVISION_MANAGED_PROFILE: showManagedProfileErrorAndClose(provisioningPreCondition); return; case ACTION_PROVISION_MANAGED_DEVICE: showDeviceOwnerErrorAndClose(provisioningPreCondition); } // This should never be the case, as showProvisioningError is always called after // verifying the supported provisioning actions. } private void showManagedProfileErrorAndClose(int provisioningPreCondition) { var userInfo = mUserManager.getUserInfo(mUserManager.getProcessUserId()); ProvisionLogger.logw( "DevicePolicyManager.checkProvisioningPrecondition returns code: " + provisioningPreCondition); // If this is organization-owned provisioning, do not show any other error dialog, just // show the factory reset dialog and return. // This cannot be abused by regular apps to force a factory reset because // isOrganizationOwnedProvisioning is only set to true if the provisioning action was // from a trusted source. See Utils.isOrganizationOwnedProvisioning where we check for // ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE which is guarded by the // DISPATCH_PROVISIONING_MESSAGE system|privileged permission. if (mUtils.isOrganizationOwnedAllowed(mViewModel.getParams())) { ProvisionLogger.loge( "Provisioning preconditions failed for organization-owned provisioning."); mUi.showFactoryResetDialog(R.string.cant_set_up_device, R.string.contact_your_admin_for_help); return; } CharSequence deviceName = DeviceHelper.getDeviceName(mContext); switch (provisioningPreCondition) { case STATUS_MANAGED_USERS_NOT_SUPPORTED: mUi.showErrorAndClose( LazyStringResource.of(R.string.cant_add_work_profile), LazyStringResource.of( R.string.work_profile_cant_be_added_contact_admin, deviceName), "Exiting managed profile provisioning, managed profiles " + "feature is not available"); break; case STATUS_CANNOT_ADD_MANAGED_PROFILE: String errorMessage; if (!userInfo.canHaveProfile()) { errorMessage = "Exiting managed profile provisioning, calling user cannot " + "have managed profiles"; } else if (!canAddManagedProfile()) { errorMessage = "Exiting managed profile provisioning, a managed profile " + "already exists"; } else { errorMessage = "Exiting managed profile provisioning, cannot add more managed " + "profiles"; } mUi.showErrorAndClose( LazyStringResource.of(R.string.cant_add_work_profile), LazyStringResource.of( R.string.work_profile_cant_be_added_contact_admin, deviceName), errorMessage); break; case STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS: mUi.showErrorAndClose( LazyStringResource.of(R.string.cant_add_work_profile), LazyStringResource.of( R.string.work_profile_cant_be_added_contact_admin, deviceName), "Exiting managed profile provisioning, " + "provisioning not allowed by OEM"); break; default: mUi.showErrorAndClose( R.string.cant_add_work_profile, R.string.contact_your_admin_for_help, "Managed profile provisioning not allowed for an unknown " + "reason, code: " + provisioningPreCondition); } } private boolean canAddManagedProfile() { return mUserManager.canAddMoreManagedProfiles( mContext.getUserId(), /* allowedToRemoveOne= */ false); } private void showDeviceOwnerErrorAndClose(int provisioningPreCondition) { CharSequence deviceName = DeviceHelper.getDeviceName(mContext); switch (provisioningPreCondition) { case STATUS_HAS_DEVICE_OWNER: case STATUS_USER_SETUP_COMPLETED: mUi.showErrorAndClose( LazyStringResource.of(R.string.device_already_set_up, deviceName), LazyStringResource.of(R.string.if_questions_contact_admin), "Device already provisioned."); return; case STATUS_NOT_SYSTEM_USER: mUi.showErrorAndClose( R.string.cant_set_up_device, R.string.contact_your_admin_for_help, "Device owner can only be set up for USER_SYSTEM."); return; case STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS: mUi.showErrorAndClose( R.string.cant_set_up_device, R.string.contact_your_admin_for_help, "Provisioning not allowed by OEM"); return; } mUi.showErrorAndClose( R.string.cant_set_up_device, R.string.contact_your_admin_for_help, "Device Owner provisioning not allowed for an unknown reason."); } }