blob: 37ad912d81734fde6833bfc71c938dbf35658e95 [file] [log] [blame]
/*
* 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 android.app.Activity;
import android.app.AlertDialog;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.managedprovisioning.DeleteManagedProfileDialog.DeleteManagedProfileCallback;
import com.android.managedprovisioning.UserConsentDialog.ConsentCallback;
import com.android.managedprovisioning.Utils.IllegalProvisioningArgumentException;
import com.android.managedprovisioning.Utils.MdmPackageInfo;
import com.android.setupwizard.navigationbar.SetupWizardNavBar;
import com.android.setupwizard.navigationbar.SetupWizardNavBar.NavigationBarListener;
import java.util.List;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
import static com.android.managedprovisioning.EncryptDeviceActivity.EXTRA_RESUME;
import static com.android.managedprovisioning.EncryptDeviceActivity.EXTRA_RESUME_TARGET;
import static com.android.managedprovisioning.EncryptDeviceActivity.TARGET_PROFILE_OWNER;
/**
* The activity sets up the environment in which the {@link ProfileOwnerProvisioningActivity} can be run.
* It makes sure the device is encrypted, the current launcher supports managed profiles, the
* provisioning intent extras are valid, and that the already present managed profile is removed.
*/
public class ProfileOwnerPreProvisioningActivity extends Activity
implements ConsentCallback, DeleteManagedProfileCallback, NavigationBarListener {
private static final String MANAGE_USERS_PERMISSION = "android.permission.MANAGE_USERS";
// Note: must match the constant defined in HomeSettings
private static final String EXTRA_SUPPORT_MANAGED_PROFILES = "support_managed_profiles";
// Aliases to start profile owner provisioning with and without MANAGE_USERS permission
protected static final ComponentName ALIAS_CHECK_CALLER =
new ComponentName("com.android.managedprovisioning",
"com.android.managedprovisioning.ProfileOwnerProvisioningActivity");
protected static final ComponentName ALIAS_NO_CHECK_CALLER =
new ComponentName("com.android.managedprovisioning",
"com.android.managedprovisioning.ProfileOwnerProvisioningActivityNoCallerCheck");
protected static final int PROVISIONING_REQUEST_CODE = 3;
protected static final int ENCRYPT_DEVICE_REQUEST_CODE = 2;
protected static final int CHANGE_LAUNCHER_REQUEST_CODE = 1;
// Hide default system navigation bar.
protected static final int IMMERSIVE_FLAGS = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
private String mMdmPackageName;
private ComponentName mMdmComponentName;
private Button mSetupButton;
private DeleteManagedProfileDialog mDeleteDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final LayoutInflater inflater = getLayoutInflater();
View contentView = inflater.inflate(R.layout.user_consent, null);
setContentView(contentView);
TextView titleView = (TextView) findViewById(R.id.title);
if (titleView != null) titleView.setText(getString(R.string.setup_work_profile));
// Check whether system has the required managed profile feature.
if (!systemHasManagedProfileFeature()) {
showErrorAndClose(R.string.managed_provisioning_not_supported,
"Exiting managed profile provisioning, "
+ "managed profiles feature is not available");
return;
}
if (Process.myUserHandle().getIdentifier() != UserHandle.USER_OWNER) {
showErrorAndClose(R.string.user_is_not_owner,
"Exiting managed profile provisioning, calling user is not owner.");
return;
}
if (Utils.hasDeviceOwner(this)) {
showErrorAndClose(R.string.device_owner_exists,
"Exiting managed profile provisioning, a device owner exists");
}
// Initialize member variables from the intent, stop if the intent wasn't valid.
try {
initialize(getIntent());
} catch (IllegalProvisioningArgumentException e) {
showErrorAndClose(R.string.managed_provisioning_error_text, e.getMessage());
return;
}
setMdmIcon(mMdmPackageName);
// If the caller started us via ALIAS_NO_CHECK_CALLER then they must have permission to
// MANAGE_USERS since it is a restricted intent. Otherwise, check the calling package.
boolean hasManageUsersPermission = (getComponentName().equals(ALIAS_NO_CHECK_CALLER));
if (!hasManageUsersPermission) {
// Calling package has to equal the requested device admin package or has to be system.
String callingPackage = getCallingPackage();
if (callingPackage == null) {
showErrorAndClose(R.string.managed_provisioning_error_text,
"Calling package is null. " +
"Was startActivityForResult used to start this activity?");
return;
}
if (!callingPackage.equals(mMdmPackageName)
&& !packageHasManageUsersPermission(callingPackage)) {
showErrorAndClose(R.string.managed_provisioning_error_text, "Permission denied, "
+ "calling package tried to set a different package as profile owner. "
+ "The system MANAGE_USERS permission is required.");
return;
}
}
// If there is already a managed profile, setup the profile deletion dialog.
// Otherwise, check whether system has reached maximum user limit.
int existingManagedProfileUserId = alreadyHasManagedProfile();
if (existingManagedProfileUserId != -1) {
DevicePolicyManager dpm =
(DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
createDeleteManagedProfileDialog(dpm, existingManagedProfileUserId);
} else if (isMaximumUserLimitReached()) {
showErrorAndClose(R.string.maximum_user_limit_reached,
"Exiting managed profile provisioning, cannot add more users.");
} else {
showStartProvisioningButton();
}
}
@Override
protected void onResume() {
super.onResume();
if (alreadyHasManagedProfile() != -1) {
showDeleteManagedProfileDialog();
}
}
@Override
protected void onPause() {
super.onPause();
hideDeleteManagedProfileDialog();
}
private void showStartProvisioningButton() {
mSetupButton.setVisibility(View.VISIBLE);
}
private boolean packageHasManageUsersPermission(String pkg) {
return PackageManager.PERMISSION_GRANTED == getPackageManager()
.checkPermission(MANAGE_USERS_PERMISSION, pkg);
}
private boolean systemHasManagedProfileFeature() {
PackageManager pm = getPackageManager();
return pm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
}
private boolean isMaximumUserLimitReached() {
UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
return !userManager.canAddMoreUsers();
}
private boolean currentLauncherSupportsManagedProfiles() {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
PackageManager pm = getPackageManager();
ResolveInfo launcherResolveInfo
= pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
if (launcherResolveInfo == null) {
return false;
}
try {
// If the user has not chosen a default launcher, then launcherResolveInfo will be
// referring to the resolver activity. It is fine to create a managed profile in
// this case since there will always be at least one launcher on the device that
// supports managed profile feature.
ApplicationInfo launcherAppInfo = getPackageManager().getApplicationInfo(
launcherResolveInfo.activityInfo.packageName, 0 /* default flags */);
return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion);
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
private boolean versionNumberAtLeastL(int versionNumber) {
return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
}
private void setMdmIcon(String packageName) {
MdmPackageInfo packageInfo = Utils.getMdmPackageInfo(getPackageManager(), packageName);
if (packageInfo != null) {
ImageView imageView = (ImageView) findViewById(R.id.mdm_icon_view);
imageView.setImageDrawable(packageInfo.getPackageIcon());
TextView deviceManagerName = (TextView) findViewById(R.id.device_manager_name);
deviceManagerName.setText(packageInfo.getAppLabel());
}
}
/**
* Checks if all required provisioning parameters are provided.
* Does not check for extras that are optional such as wifi ssid.
* Also checks whether type of admin extras bundle (if present) is PersistableBundle.
*
* @param intent The intent that started provisioning
*/
private void initialize(Intent intent) throws IllegalProvisioningArgumentException {
// Check if the admin extras bundle is of the right type.
try {
PersistableBundle bundle = (PersistableBundle) getIntent().getParcelableExtra(
EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE);
} catch (ClassCastException e) {
throw new IllegalProvisioningArgumentException("Extra "
+ EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
+ " must be of type PersistableBundle.", e);
}
mMdmComponentName = Utils.findDeviceAdminFromIntent(intent, this);
mMdmPackageName = mMdmComponentName.getPackageName();
}
/**
* If the device is encrypted start the service which does the provisioning, otherwise ask for
* user consent to encrypt the device.
*/
private void checkEncryptedAndStartProvisioningService() {
if (EncryptDeviceActivity.isDeviceEncrypted()
|| SystemProperties.getBoolean("persist.sys.no_req_encrypt", false)) {
// Notify the user once more that the admin will have full control over the profile,
// then start provisioning.
UserConsentDialog.newInstance(UserConsentDialog.PROFILE_OWNER)
.show(getFragmentManager(), "UserConsentDialogFragment");
} else {
Bundle resumeExtras = getNewExtras();
resumeExtras.putString(EXTRA_RESUME_TARGET, TARGET_PROFILE_OWNER);
Intent encryptIntent = new Intent(this, EncryptDeviceActivity.class)
.putExtra(EXTRA_RESUME, resumeExtras);
startActivityForResult(encryptIntent, ENCRYPT_DEVICE_REQUEST_CODE);
// Continue in onActivityResult or after reboot.
}
}
@Override
public void onDialogConsent() {
setupEnvironmentAndProvision();
}
@Override
public void onDialogCancel() {
// Do nothing.
}
@Override
public void onRemoveProfileApproval(int existingManagedProfileUserId) {
mDeleteDialog = null;
UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
userManager.removeUser(existingManagedProfileUserId);
showStartProvisioningButton();
}
@Override
public void onRemoveProfileCancel() {
finish();
}
private void setupEnvironmentAndProvision() {
// Remove any pre-provisioning UI in favour of progress display
BootReminder.cancelProvisioningReminder(this);
// Check whether the current launcher supports managed profiles.
if (!currentLauncherSupportsManagedProfiles()) {
showCurrentLauncherInvalid();
} else {
startProfileOwnerProvisioning();
}
}
private void pickLauncher() {
Intent changeLauncherIntent = new Intent("android.settings.HOME_SETTINGS");
changeLauncherIntent.putExtra(EXTRA_SUPPORT_MANAGED_PROFILES, true);
startActivityForResult(changeLauncherIntent, CHANGE_LAUNCHER_REQUEST_CODE);
// Continue in onActivityResult.
}
private void startProfileOwnerProvisioning() {
Intent intent = new Intent(this, ProfileOwnerProvisioningActivity.class);
intent.putExtras(getNewExtras());
startActivityForResult(intent, PROVISIONING_REQUEST_CODE);
// Set cross-fade transition animation into the interstitial progress activity.
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
private Bundle getNewExtras() {
Bundle bundle = getIntent().getExtras();
// The original intent may have contained the package name but not the component name.
// But now, we know what the component name is. So let's pass it.
bundle.putParcelable(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
mMdmComponentName);
return bundle;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == ENCRYPT_DEVICE_REQUEST_CODE) {
if (resultCode == RESULT_CANCELED) {
ProvisionLogger.loge("User canceled device encryption.");
setResult(Activity.RESULT_CANCELED);
finish();
}
} else if (requestCode == CHANGE_LAUNCHER_REQUEST_CODE) {
if (resultCode == RESULT_CANCELED) {
showCurrentLauncherInvalid();
} else if (resultCode == RESULT_OK) {
startProfileOwnerProvisioning();
}
}
if (requestCode == PROVISIONING_REQUEST_CODE) {
setResult(resultCode);
finish();
}
}
private void showCurrentLauncherInvalid() {
new AlertDialog.Builder(this)
.setCancelable(false)
.setMessage(R.string.managed_provisioning_not_supported_by_launcher)
.setNegativeButton(R.string.cancel_provisioning,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,int id) {
dialog.dismiss();
setResult(Activity.RESULT_CANCELED);
finish();
}
})
.setPositiveButton(R.string.pick_launcher,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,int id) {
pickLauncher();
}
})
.show()
.getWindow().getDecorView().setSystemUiVisibility(IMMERSIVE_FLAGS);
}
public void showErrorAndClose(int resourceId, String logText) {
ProvisionLogger.loge(logText);
new AlertDialog.Builder(this)
.setTitle(R.string.provisioning_error_title)
.setMessage(getString(resourceId))
.setCancelable(false)
.setPositiveButton(R.string.device_owner_error_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,int id) {
// Close activity
ProfileOwnerPreProvisioningActivity.this
.setResult(Activity.RESULT_CANCELED);
ProfileOwnerPreProvisioningActivity.this.finish();
}
})
.show()
.getWindow().getDecorView().setSystemUiVisibility(IMMERSIVE_FLAGS);
}
/**
* @return The User id of an already existing managed profile or -1 if none
* exists
*/
int alreadyHasManagedProfile() {
UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
List<UserInfo> profiles = userManager.getProfiles(getUserId());
for (UserInfo userInfo : profiles) {
if (userInfo.isManagedProfile()) {
return userInfo.getUserHandle().getIdentifier();
}
}
return -1;
}
/**
* Builds a dialog that allows the user to remove an existing managed profile.
*/
private void createDeleteManagedProfileDialog(DevicePolicyManager dpm,
int existingManagedProfileUserId) {
if (mDeleteDialog != null) {
return;
}
ComponentName mdmPackageName = dpm.getProfileOwnerAsUser(existingManagedProfileUserId);
String domainName = dpm.getProfileOwnerNameAsUser(existingManagedProfileUserId);
mDeleteDialog = DeleteManagedProfileDialog.newInstance(existingManagedProfileUserId,
mdmPackageName, domainName);
}
private void showDeleteManagedProfileDialog() {
if (mDeleteDialog == null) {
return;
}
if (!mDeleteDialog.isVisible()) {
mDeleteDialog.show(getFragmentManager(), "DeleteManagedProfileDialogFragment");
}
}
private void hideDeleteManagedProfileDialog() {
if (mDeleteDialog == null) {
return;
}
mDeleteDialog.dismiss();
mDeleteDialog = null;
}
@Override
public void onNavigationBarCreated(SetupWizardNavBar bar) {
mSetupButton = bar.getNextButton();
mSetupButton.setText(R.string.set_up);
mSetupButton.setVisibility(View.INVISIBLE);
}
@Override
public void onNavigateBack() {
onBackPressed();
}
@Override
public void onNavigateNext() {
checkEncryptedAndStartProvisioningService();
}
}