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