blob: 06135d2cb7573a1348910cf79c5636c516764ab0 [file] [log] [blame]
/*
* 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.uiflows;
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE;
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.managedprovisioning.common.Globals.ACTION_RESUME_PROVISIONING;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.KeyguardManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.AsyncTask;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings.Global;
import android.service.persistentdata.PersistentDataBlockManager;
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.managedprovisioning.common.IllegalProvisioningArgumentException;
import com.android.managedprovisioning.common.Utils;
import com.android.managedprovisioning.model.ProvisioningParams;
import com.android.managedprovisioning.parser.MessageParser;
import com.android.managedprovisioning.ProvisionLogger;
import com.android.managedprovisioning.R;
import java.util.List;
public class PreProvisioningController {
private final Context mContext;
private final Ui mUi;
private final MessageParser mMessageParser;
private final Utils mUtils;
private final EncryptionController mEncryptionController;
// used system services
private final DevicePolicyManager mDevicePolicyManager;
private final UserManager mUserManager;
private final PackageManager mPackageManager;
private final ActivityManager mActivityManager;
private final KeyguardManager mKeyguardManager;
private final PersistentDataBlockManager mPdbManager;
private ProvisioningParams mParams;
private boolean mIsProfileOwnerProvisioning;
public PreProvisioningController(
@NonNull Context context,
@NonNull Ui ui) {
this(context, ui, new MessageParser(), new Utils(),
EncryptionController.getInstance(context));
}
@VisibleForTesting
PreProvisioningController(
@NonNull Context context,
@NonNull Ui ui,
@NonNull MessageParser parser,
@NonNull Utils utils,
@NonNull EncryptionController encryptionController) {
mContext = checkNotNull(context, "Context must not be null");
mUi = checkNotNull(ui, "Ui must not be null");
mMessageParser = checkNotNull(parser, "MessageParser must not be null");
mUtils = checkNotNull(utils, "Utils must not be null");
mEncryptionController = checkNotNull(encryptionController,
"EncryptionController must not be null");
mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
Context.DEVICE_POLICY_SERVICE);
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mPackageManager = mContext.getPackageManager();
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
mPdbManager = (PersistentDataBlockManager) mContext.getSystemService(
Context.PERSISTENT_DATA_BLOCK_SERVICE);
}
interface Ui {
/**
* Show an error message and cancel provisioning.
*
* @param resId resource id used to form the user facing error message
* @param errorMessage an error message that gets logged for debugging
*/
void showErrorAndClose(int resId, 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();
/**
* Initialize the pre provisioning UI with the mdm info and the relevant strings.
*
* @param headerRes resource id for the header text
* @param titleRes resource id for the title text
* @param consentRes resource id of the consent text
* @param mdmInfoRes resource id for the mdm info text
* @param params the {@link ProvisioningParams} object related to the ongoing provisioning
*/
void initiateUi(int headerRes, int titleRes, int consentRes, int mdmInfoRes,
ProvisioningParams params);
/**
* Start device owner provisioning.
*
* @param userId the id of the user we want to start provisioning on
* @param params the {@link ProvisioningParams} object related to the ongoing provisioning
*/
void startDeviceOwnerProvisioning(int userId, ProvisioningParams params);
/**
* Start profile owner provisioning.
*
* @param params the {@link ProvisioningParams} object related to the ongoing provisioning
*/
void startProfileOwnerProvisioning(ProvisioningParams params);
/**
* Show a user consent dialog.
*
* @param params the {@link ProvisioningParams} object related to the ongoing provisioning
* @param isProfileOwnerProvisioning whether we're provisioning a profile owner
*/
void showUserConsentDialog(ProvisioningParams params, boolean isProfileOwnerProvisioning);
/**
* Show a dialog to delete an existing managed profile.
*
* @param mdmPackageName the {@link ComponentName} of the existing profile's profile owner
* @param domainName domain name of the organization which owns the managed profile
*
* @param userId the user id of the existing profile
*/
void showDeleteManagedProfileDialog(ComponentName mdmPackageName, String domainName,
int userId);
/**
* 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();
}
public void initiateProvisioning(Intent intent, String callingPackage) {
// Check factory reset protection as the first thing
if (factoryResetProtected()) {
mUi.showErrorAndClose(R.string.device_owner_error_frp,
"Factory reset protection blocks provisioning.");
return;
}
try {
// Read the provisioning params from the provisioning intent
mParams = mMessageParser.parse(intent, mContext);
// If this is a resume after encryption or trusted intent, we don't need to verify the
// caller. Otherwise, verify that the calling app is trying to set itself as
// Device/ProfileOwner
if (!ACTION_RESUME_PROVISIONING.equals(intent.getAction()) &&
!mParams.startedByTrustedSource) {
verifyCaller(callingPackage);
}
} catch (IllegalProvisioningArgumentException e) {
// TODO: make this a generic error message
mUi.showErrorAndClose(R.string.device_owner_error_general, e.getMessage());
return;
}
mIsProfileOwnerProvisioning = mUtils.isProfileOwnerAction(mParams.provisioningAction);
// Check whether provisioning is allowed for the current action
if (!mDevicePolicyManager.isProvisioningAllowed(mParams.provisioningAction)) {
showProvisioningError(mParams.provisioningAction);
return;
}
// Initiate the corresponding provisioning mode
if (mIsProfileOwnerProvisioning) {
initiateProfileOwnerProvisioning(intent);
} else {
initiateDeviceOwnerProvisioning(intent);
}
}
/**
* Verify that the caller is trying to set itself as owner.
*
* @throws IllegalProvisioningArgumentException if the caller is trying to set a different
* package as owner.
*/
private void verifyCaller(@NonNull String callingPackage)
throws IllegalProvisioningArgumentException {
checkNotNull(callingPackage,
"Calling package is null. Was startActivityForResult used to start this activity?");
if (!callingPackage.equals(mParams.inferDeviceAdminPackageName())) {
throw new IllegalProvisioningArgumentException("Permission denied, "
+ "calling package tried to set a different package as owner. ");
}
}
private void initiateDeviceOwnerProvisioning(Intent intent) {
if (!mParams.startedByTrustedSource) {
mUi.initiateUi(
R.string.setup_work_device,
R.string.setup_device_start_setup,
R.string.company_controls_device,
R.string.the_following_is_your_mdm_for_device,
mParams);
}
// Ask to encrypt the device before proceeding
if (isEncryptionRequired()) {
maybeTriggerEncryption();
return;
}
// 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()) {
ProvisionLogger.logi("Cannot pick wifi because the screen is locked.");
// Have the user pick a wifi network if necessary.
} else if (!mUtils.isConnectedToNetwork(mContext) && mParams.wifiInfo == null
&& mParams.deviceAdminDownloadInfo != null) {
if (canRequestWifiPick()) {
mUi.requestWifiPick();
return;
} else {
ProvisionLogger.logi(
"Cannot pick wifi because there is no handler to the intent");
}
}
askForConsentOrStartDeviceOwnerProvisioning();
}
private void initiateProfileOwnerProvisioning(Intent intent) {
mUi.initiateUi(
R.string.setup_work_profile,
R.string.setup_profile_start_setup,
R.string.company_controls_workspace,
R.string.the_following_is_your_mdm,
mParams);
// If there is already a managed profile, setup the profile deletion dialog.
int existingManagedProfileUserId = mUtils.alreadyHasManagedProfile(mContext);
if (existingManagedProfileUserId != -1) {
ComponentName mdmPackageName = mDevicePolicyManager
.getProfileOwnerAsUser(existingManagedProfileUserId);
String domainName = mDevicePolicyManager
.getProfileOwnerNameAsUser(existingManagedProfileUserId);
mUi.showDeleteManagedProfileDialog(mdmPackageName, domainName,
existingManagedProfileUserId);
}
}
/**
* 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() {
if (isProfileOwnerProvisioning()) {
checkLauncherAndStartProfileOwnerProvisioning();
} else {
maybeCreateUserAndStartDeviceOwnerProvisioning();
}
}
/**
* Invoked when the user continues provisioning by pressing the next button.
*
* <p>If device hasn't been encrypted yet, invoke the encryption flow. Otherwise, show a user
* consent before starting provisioning.
*/
public void afterNavigateNext() {
if (isEncryptionRequired()) {
maybeTriggerEncryption();
} else {
// Notify the user once more that the admin will have full control over the profile,
// then start provisioning.
mUi.showUserConsentDialog(mParams, mIsProfileOwnerProvisioning);
}
}
/**
* Returns whether the device needs encryption.
*
* @param skip indicating whether the parameter to skip encryption was given.
*/
private boolean isEncryptionRequired() {
return !mParams.skipEncryption && mUtils.isEncryptionRequired();
}
/**
* Check whether the device supports encryption. If it does not support encryption, but
* encryption is requested, show an error dialog.
*/
private void maybeTriggerEncryption() {
if (mDevicePolicyManager.getStorageEncryptionStatus() ==
DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED) {
mUi.showErrorAndClose(R.string.preprovisioning_error_encryption_not_supported,
"This device does not support encryption, but "
+ DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION
+ " was not passed.");
} else {
mUi.requestEncryption(mParams);
}
}
private void checkLauncherAndStartProfileOwnerProvisioning() {
// Check whether the current launcher supports managed profiles.
if (!mUtils.currentLauncherSupportsManagedProfiles(mContext)) {
mUi.showCurrentLauncherInvalid();
} else {
// Cancel the boot reminder as provisioning has now started.
mEncryptionController.cancelEncryptionReminder();
mUi.startProfileOwnerProvisioning(mParams);
}
}
public void askForConsentOrStartDeviceOwnerProvisioning() {
// If we are started by Nfc and the device supports FRP, we need to ask for user consent
// since FRP will not be activated at the end of the flow.
if (mParams.startedByTrustedSource) {
if (mUtils.isFrpSupported(mContext)) {
mUi.showUserConsentDialog(mParams, false);
} else {
maybeCreateUserAndStartDeviceOwnerProvisioning();
}
}
// In other provisioning modes we wait for the user to press next.
}
private void maybeCreateUserAndStartDeviceOwnerProvisioning() {
// Cancel the boot reminder as provisioning has now started.
mEncryptionController.cancelEncryptionReminder();
if (isMeatUserCreationRequired(mParams.provisioningAction)) {
// Create the primary user, and continue the provisioning in this user.
new CreatePrimaryUserTask().execute();
} else {
mUi.startDeviceOwnerProvisioning(mUserManager.getUserHandle(), mParams);
}
}
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 (mUtils.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;
}
public boolean isMeatUserCreationRequired(String action) {
if (mUtils.isSplitSystemUser()
&& ACTION_PROVISION_MANAGED_DEVICE.equals(action)) {
List<UserInfo> users = mUserManager.getUsers();
if (users.size() > 1) {
mUi.showErrorAndClose(R.string.device_owner_error_general,
"Cannot start Device Owner Provisioning because there are already "
+ users.size() + " users");
return false;
}
return true;
} else {
return false;
}
}
private boolean canRequestWifiPick() {
return mPackageManager.resolveActivity(mUtils.getWifiPickIntent(), 0) != null;
}
private boolean systemHasManagedProfileFeature() {
return mPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
}
/**
* Returns whether the provisioning process is a profile owner provisioning process.
*/
public boolean isProfileOwnerProvisioning() {
return mIsProfileOwnerProvisioning;
}
@NonNull
public ProvisioningParams getParams() {
if (mParams == null) {
throw new IllegalStateException("ProvisioningParams are null");
}
return mParams;
}
// TODO: review the use of async task for the case where the activity might have got killed
private class CreatePrimaryUserTask extends AsyncTask<Void, Void, UserInfo> {
@Override
protected UserInfo doInBackground(Void... args) {
// Create the user where we're going to install the device owner.
UserInfo userInfo = mUserManager.createUser(
mContext.getString(R.string.default_first_meat_user_name),
UserInfo.FLAG_PRIMARY | UserInfo.FLAG_ADMIN);
if (userInfo != null) {
ProvisionLogger.logi("Created user " + userInfo.id + " to hold the device owner");
}
return userInfo;
}
@Override
protected void onPostExecute(UserInfo userInfo) {
if (userInfo == null) {
mUi.showErrorAndClose(R.string.device_owner_error_general,
"Could not create user to hold the device owner");
} else {
mActivityManager.switchUser(userInfo.id);
mUi.startDeviceOwnerProvisioning(userInfo.id, mParams);
}
}
}
private void showProvisioningError(String action) {
UserInfo userInfo = mUserManager.getUserInfo(mUserManager.getUserHandle());
if (DevicePolicyManager.ACTION_PROVISION_MANAGED_USER.equals(action)) {
mUi.showErrorAndClose(R.string.user_setup_incomplete,
"Exiting managed user provisioning, setup incomplete");
} else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE.equals(action)) {
// Try to show an error message explaining why provisioning is not allowed.
if (!systemHasManagedProfileFeature()) {
mUi.showErrorAndClose(R.string.managed_provisioning_not_supported,
"Exiting managed profile provisioning, "
+ "managed profiles feature is not available");
} else if (!userInfo.canHaveProfile()) {
mUi.showErrorAndClose(R.string.user_cannot_have_work_profile,
"Exiting managed profile provisioning, calling user cannot have managed"
+ "profiles.");
} else if (mUtils.isDeviceManaged(mContext)) {
// The actual check in isProvisioningAllowed() is more than just "is there DO?",
// but for error message showing purpose, isDeviceManaged() will do.
mUi.showErrorAndClose(R.string.device_owner_exists,
"Exiting managed profile provisioning, a device owner exists");
} else if (!mUserManager.canAddMoreManagedProfiles(UserHandle.myUserId(),
true /* after removing one eventual existing managed profile */)) {
mUi.showErrorAndClose(R.string.maximum_user_limit_reached,
"Exiting managed profile provisioning, cannot add more managed profiles.");
} else {
mUi.showErrorAndClose(R.string.managed_provisioning_error_text, "Managed profile"
+ " provisioning not allowed for an unknown reason.");
}
} else if (mUtils.isDeviceProvisioned(mContext)) {
mUi.showErrorAndClose(R.string.device_owner_error_already_provisioned,
"Device already provisioned.");
} else if (!mUtils.isCurrentUserSystem()) {
mUi.showErrorAndClose(R.string.device_owner_error_general,
"Device owner can only be set up for USER_SYSTEM.");
} else if (action.equals(ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE) &&
!UserManager.isSplitSystemUser()) {
mUi.showErrorAndClose(R.string.device_owner_error_general,
"System User Device owner can only be set on a split-user system.");
} else {
// TODO: show generic error
mUi.showErrorAndClose(R.string.device_owner_error_general,
"Device Owner provisioning not allowed for an unknown reason.");
}
}
}