Home | History | Annotate | Download | only in managedprovisioning
      1 /*
      2  * Copyright 2014, The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.managedprovisioning;
     18 
     19 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
     20 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
     21 import static com.android.managedprovisioning.EncryptDeviceActivity.EXTRA_RESUME;
     22 import static com.android.managedprovisioning.EncryptDeviceActivity.EXTRA_RESUME_TARGET;
     23 import static com.android.managedprovisioning.EncryptDeviceActivity.TARGET_PROFILE_OWNER;
     24 
     25 import android.app.Activity;
     26 import android.app.admin.DevicePolicyManager;
     27 import android.app.AlertDialog;
     28 import android.app.ProgressDialog;
     29 import android.content.BroadcastReceiver;
     30 import android.content.ComponentName;
     31 import android.content.Context;
     32 import android.content.DialogInterface;
     33 import android.content.Intent;
     34 import android.content.IntentFilter;
     35 import android.content.pm.ApplicationInfo;
     36 import android.content.pm.PackageManager;
     37 import android.content.pm.ResolveInfo;
     38 import android.content.pm.PackageManager.NameNotFoundException;
     39 import android.content.pm.UserInfo;
     40 import android.graphics.drawable.Drawable;
     41 import android.os.Build;
     42 import android.os.Bundle;
     43 import android.os.PersistableBundle;
     44 import android.os.Process;
     45 import android.os.SystemProperties;
     46 import android.os.UserHandle;
     47 import android.os.UserManager;
     48 import android.support.v4.content.LocalBroadcastManager;
     49 import android.text.TextUtils;
     50 import android.view.LayoutInflater;
     51 import android.view.View;
     52 import android.widget.ImageView;
     53 import android.widget.TextView;
     54 import android.widget.Button;
     55 
     56 import java.util.List;
     57 
     58 /**
     59  * The activity sets up the environment in which the {@link ProfileOwnerProvisioningActivity} can be run.
     60  * It makes sure the device is encrypted, the current launcher supports managed profiles, the
     61  * provisioning intent extras are valid, and that the already present managed profile is removed.
     62  */
     63 public class ProfileOwnerPreProvisioningActivity extends Activity
     64         implements UserConsentDialog.ConsentCallback {
     65 
     66     private static final String MANAGE_USERS_PERMISSION = "android.permission.MANAGE_USERS";
     67 
     68     // Note: must match the constant defined in HomeSettings
     69     private static final String EXTRA_SUPPORT_MANAGED_PROFILES = "support_managed_profiles";
     70 
     71     // Aliases to start profile owner provisioning with and without MANAGE_USERS permission
     72     protected static final ComponentName ALIAS_CHECK_CALLER =
     73             new ComponentName("com.android.managedprovisioning",
     74                     "com.android.managedprovisioning.ProfileOwnerProvisioningActivity");
     75 
     76     protected static final ComponentName ALIAS_NO_CHECK_CALLER =
     77             new ComponentName("com.android.managedprovisioning",
     78                     "com.android.managedprovisioning.ProfileOwnerProvisioningActivityNoCallerCheck");
     79 
     80     protected static final int PROVISIONING_REQUEST_CODE = 3;
     81     protected static final int ENCRYPT_DEVICE_REQUEST_CODE = 2;
     82     protected static final int CHANGE_LAUNCHER_REQUEST_CODE = 1;
     83 
     84     private String mMdmPackageName;
     85 
     86     @Override
     87     protected void onCreate(Bundle savedInstanceState) {
     88         super.onCreate(savedInstanceState);
     89 
     90         final LayoutInflater inflater = getLayoutInflater();
     91         View contentView = inflater.inflate(R.layout.user_consent, null);
     92         setContentView(contentView);
     93 
     94         // Check whether system has the required managed profile feature.
     95         if (!systemHasManagedProfileFeature()) {
     96             showErrorAndClose(R.string.managed_provisioning_not_supported,
     97                     "Exiting managed profile provisioning, "
     98                     + "managed profiles feature is not available");
     99             return;
    100         }
    101         if (Process.myUserHandle().getIdentifier() != UserHandle.USER_OWNER) {
    102             showErrorAndClose(R.string.user_is_not_owner,
    103                     "Exiting managed profile provisioning, calling user is not owner.");
    104             return;
    105         }
    106 
    107         // Initialize member variables from the intent, stop if the intent wasn't valid.
    108         try {
    109             initialize(getIntent());
    110         } catch (ProvisioningFailedException e) {
    111             showErrorAndClose(R.string.managed_provisioning_error_text, e.getMessage());
    112             return;
    113         }
    114 
    115         setMdmIcon(mMdmPackageName);
    116 
    117         // If the caller started us via ALIAS_NO_CHECK_CALLER then they must have permission to
    118         // MANAGE_USERS since it is a restricted intent. Otherwise, check the calling package.
    119         boolean hasManageUsersPermission = (getComponentName().equals(ALIAS_NO_CHECK_CALLER));
    120         if (!hasManageUsersPermission) {
    121             // Calling package has to equal the requested device admin package or has to be system.
    122             String callingPackage = getCallingPackage();
    123             if (callingPackage == null) {
    124                 showErrorAndClose(R.string.managed_provisioning_error_text,
    125                         "Calling package is null. " +
    126                         "Was startActivityForResult used to start this activity?");
    127                 return;
    128             }
    129             if (!callingPackage.equals(mMdmPackageName)
    130                     && !packageHasManageUsersPermission(callingPackage)) {
    131                 showErrorAndClose(R.string.managed_provisioning_error_text, "Permission denied, "
    132                         + "calling package tried to set a different package as profile owner. "
    133                         + "The system MANAGE_USERS permission is required.");
    134                 return;
    135             }
    136         }
    137 
    138         DevicePolicyManager dpm =
    139                 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
    140         String deviceOwner = dpm.getDeviceOwner();
    141         if (deviceOwner != null && !deviceOwner.equals(mMdmPackageName)) {
    142             showErrorAndClose(R.string.managed_provisioning_error_text, "Permission denied, "
    143                     + "profile owner must be in the same package as device owner.");
    144             return;
    145         }
    146 
    147         // If there is already a managed profile, allow the user to cancel or delete it.
    148         int existingManagedProfileUserId = alreadyHasManagedProfile();
    149         if (existingManagedProfileUserId != -1) {
    150             showManagedProfileExistsDialog(existingManagedProfileUserId);
    151         } else {
    152             showStartProvisioningScreen();
    153         }
    154     }
    155 
    156     private void showStartProvisioningScreen() {
    157         Button positiveButton = (Button) findViewById(R.id.positive_button);
    158         positiveButton.setOnClickListener(new View.OnClickListener() {
    159             @Override
    160             public void onClick(View v) {
    161                 checkEncryptedAndStartProvisioningService();
    162             }
    163         });
    164     }
    165 
    166     private boolean packageHasManageUsersPermission(String pkg) {
    167         return PackageManager.PERMISSION_GRANTED == getPackageManager()
    168                 .checkPermission(MANAGE_USERS_PERMISSION, pkg);
    169     }
    170 
    171     private boolean systemHasManagedProfileFeature() {
    172         PackageManager pm = getPackageManager();
    173         return pm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
    174     }
    175 
    176     private boolean currentLauncherSupportsManagedProfiles() {
    177         Intent intent = new Intent(Intent.ACTION_MAIN);
    178         intent.addCategory(Intent.CATEGORY_HOME);
    179 
    180         PackageManager pm = getPackageManager();
    181         ResolveInfo launcherResolveInfo
    182                 = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
    183         if (launcherResolveInfo == null) {
    184             return false;
    185         }
    186         try {
    187             ApplicationInfo launcherAppInfo = getPackageManager().getApplicationInfo(
    188                     launcherResolveInfo.activityInfo.packageName, 0 /* default flags */);
    189             return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion);
    190         } catch (PackageManager.NameNotFoundException e) {
    191             return false;
    192         }
    193     }
    194 
    195     private boolean versionNumberAtLeastL(int versionNumber) {
    196         return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
    197     }
    198 
    199     private void setMdmIcon(String packageName) {
    200         if (packageName != null) {
    201             PackageManager pm = getPackageManager();
    202             try {
    203                 ApplicationInfo ai = pm.getApplicationInfo(packageName, /* default flags */ 0);
    204                 if (ai != null) {
    205                     Drawable packageIcon = pm.getApplicationIcon(packageName);
    206                     ImageView imageView = (ImageView) findViewById(R.id.mdm_icon_view);
    207                     imageView.setImageDrawable(packageIcon);
    208 
    209                     String appLabel = pm.getApplicationLabel(ai).toString();
    210                     TextView deviceManagerName = (TextView) findViewById(R.id.device_manager_name);
    211                     deviceManagerName.setText(appLabel);
    212                 }
    213             } catch (PackageManager.NameNotFoundException e) {
    214                 // Package does not exist, ignore. Should never happen.
    215                 ProvisionLogger.loge("Package does not exist. Should never happen.");
    216             }
    217         }
    218     }
    219 
    220     /**
    221      * Checks if all required provisioning parameters are provided.
    222      * Does not check for extras that are optional such as wifi ssid.
    223      * Also checks whether type of admin extras bundle (if present) is PersistableBundle.
    224      *
    225      * @param intent The intent that started provisioning
    226      */
    227     private void initialize(Intent intent) throws ProvisioningFailedException {
    228         // Check if the admin extras bundle is of the right type.
    229         try {
    230             PersistableBundle bundle = (PersistableBundle) getIntent().getParcelableExtra(
    231                     EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE);
    232         } catch (ClassCastException e) {
    233             throw new ProvisioningFailedException("Extra "
    234                     + EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
    235                     + " must be of type PersistableBundle.", e);
    236         }
    237 
    238         // Validate package name and check if the package is installed
    239         mMdmPackageName = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
    240         if (TextUtils.isEmpty(mMdmPackageName)) {
    241             throw new ProvisioningFailedException("Missing intent extra: "
    242                     + EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
    243         } else {
    244             try {
    245                 this.getPackageManager().getPackageInfo(mMdmPackageName, 0);
    246             } catch (NameNotFoundException e) {
    247                 throw new ProvisioningFailedException("Mdm "+ mMdmPackageName
    248                         + " is not installed. ", e);
    249             }
    250         }
    251     }
    252 
    253     /**
    254      * If the device is encrypted start the service which does the provisioning, otherwise ask for
    255      * user consent to encrypt the device.
    256      */
    257     private void checkEncryptedAndStartProvisioningService() {
    258         if (EncryptDeviceActivity.isDeviceEncrypted()
    259                 || SystemProperties.getBoolean("persist.sys.no_req_encrypt", false)) {
    260 
    261             // Notify the user once more that the admin will have full control over the profile,
    262             // then start provisioning.
    263             UserConsentDialog.newInstance(UserConsentDialog.PROFILE_OWNER)
    264                     .show(getFragmentManager(), "UserConsentDialogFragment");
    265         } else {
    266             Bundle resumeExtras = getIntent().getExtras();
    267             resumeExtras.putString(EXTRA_RESUME_TARGET, TARGET_PROFILE_OWNER);
    268             Intent encryptIntent = new Intent(this, EncryptDeviceActivity.class)
    269                     .putExtra(EXTRA_RESUME, resumeExtras);
    270             startActivityForResult(encryptIntent, ENCRYPT_DEVICE_REQUEST_CODE);
    271             // Continue in onActivityResult or after reboot.
    272         }
    273     }
    274 
    275     @Override
    276     public void onDialogConsent() {
    277         setupEnvironmentAndProvision();
    278     }
    279 
    280     @Override
    281     public void onDialogCancel() {
    282         // Do nothing.
    283     }
    284 
    285     private void setupEnvironmentAndProvision() {
    286         // Remove any pre-provisioning UI in favour of progress display
    287         BootReminder.cancelProvisioningReminder(this);
    288 
    289         // Check whether the current launcher supports managed profiles.
    290         if (!currentLauncherSupportsManagedProfiles()) {
    291             showCurrentLauncherInvalid();
    292         } else {
    293             startProfileOwnerProvisioning();
    294         }
    295     }
    296 
    297     private void pickLauncher() {
    298         Intent changeLauncherIntent = new Intent("android.settings.HOME_SETTINGS");
    299         changeLauncherIntent.putExtra(EXTRA_SUPPORT_MANAGED_PROFILES, true);
    300         startActivityForResult(changeLauncherIntent, CHANGE_LAUNCHER_REQUEST_CODE);
    301         // Continue in onActivityResult.
    302     }
    303 
    304     private void startProfileOwnerProvisioning() {
    305         Intent intent = new Intent(this, ProfileOwnerProvisioningActivity.class);
    306         intent.putExtras(getIntent());
    307         startActivityForResult(intent, PROVISIONING_REQUEST_CODE);
    308     }
    309 
    310     @Override
    311     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    312         if (requestCode == ENCRYPT_DEVICE_REQUEST_CODE) {
    313             if (resultCode == RESULT_CANCELED) {
    314                 ProvisionLogger.loge("User canceled device encryption.");
    315                 setResult(Activity.RESULT_CANCELED);
    316                 finish();
    317             }
    318         } else if (requestCode == CHANGE_LAUNCHER_REQUEST_CODE) {
    319             if (resultCode == RESULT_CANCELED) {
    320                 showCurrentLauncherInvalid();
    321             } else if (resultCode == RESULT_OK) {
    322                 startProfileOwnerProvisioning();
    323             }
    324         }
    325         if (requestCode == PROVISIONING_REQUEST_CODE) {
    326             setResult(resultCode);
    327             finish();
    328         }
    329     }
    330 
    331     private void showCurrentLauncherInvalid() {
    332         new AlertDialog.Builder(this)
    333                 .setCancelable(false)
    334                 .setMessage(R.string.managed_provisioning_not_supported_by_launcher)
    335                 .setNegativeButton(R.string.cancel_provisioning,
    336                         new DialogInterface.OnClickListener() {
    337                             @Override
    338                             public void onClick(DialogInterface dialog,int id) {
    339                                 dialog.dismiss();
    340                                 setResult(Activity.RESULT_CANCELED);
    341                                 finish();
    342                             }
    343                         })
    344                 .setPositiveButton(R.string.pick_launcher,
    345                         new DialogInterface.OnClickListener() {
    346                             @Override
    347                             public void onClick(DialogInterface dialog,int id) {
    348                                 pickLauncher();
    349                             }
    350                         }).show();
    351     }
    352 
    353     public void showErrorAndClose(int resourceId, String logText) {
    354         ProvisionLogger.loge(logText);
    355         new AlertDialog.Builder(this)
    356                 .setTitle(R.string.provisioning_error_title)
    357                 .setMessage(getString(resourceId))
    358                 .setCancelable(false)
    359                 .setPositiveButton(R.string.device_owner_error_ok, new DialogInterface.OnClickListener() {
    360                         @Override
    361                         public void onClick(DialogInterface dialog,int id) {
    362                             // Close activity
    363                             ProfileOwnerPreProvisioningActivity.this
    364                                     .setResult(Activity.RESULT_CANCELED);
    365                             ProfileOwnerPreProvisioningActivity.this.finish();
    366                         }
    367                     }).show();
    368     }
    369 
    370     /**
    371      * @return The User id of an already existing managed profile or -1 if none
    372      * exists
    373      */
    374     int alreadyHasManagedProfile() {
    375         UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
    376         List<UserInfo> profiles = userManager.getProfiles(getUserId());
    377         for (UserInfo userInfo : profiles) {
    378             if (userInfo.isManagedProfile()) {
    379                 return userInfo.getUserHandle().getIdentifier();
    380             }
    381         }
    382         return -1;
    383     }
    384 
    385     /**
    386      * Builds a dialog that allows the user to remove an existing managed profile after they were
    387      * shown an additional warning.
    388      */
    389     private void showManagedProfileExistsDialog(
    390             final int existingManagedProfileUserId) {
    391 
    392         // Before deleting, show a warning dialog
    393         DialogInterface.OnClickListener warningListener =
    394                 new DialogInterface.OnClickListener() {
    395             @Override
    396             public void onClick(DialogInterface dialog, int which) {
    397                 // Really delete the profile if the user clicks delete on the warning dialog.
    398                 final DialogInterface.OnClickListener deleteListener =
    399                         new DialogInterface.OnClickListener() {
    400                     @Override
    401                     public void onClick(DialogInterface dialog, int which) {
    402                         UserManager userManager =
    403                                 (UserManager) getSystemService(Context.USER_SERVICE);
    404                         userManager.removeUser(existingManagedProfileUserId);
    405                         showStartProvisioningScreen();
    406                     }
    407                 };
    408                 buildDeleteManagedProfileDialog(
    409                         getString(R.string.sure_you_want_to_delete_profile),
    410                         deleteListener).show();
    411             }
    412         };
    413 
    414         buildDeleteManagedProfileDialog(
    415                 getString(R.string.managed_profile_already_present),
    416                 warningListener).show();
    417     }
    418 
    419     private AlertDialog buildDeleteManagedProfileDialog(String message,
    420             DialogInterface.OnClickListener deleteListener) {
    421         DialogInterface.OnClickListener cancelListener =
    422                 new DialogInterface.OnClickListener() {
    423             @Override
    424             public void onClick(DialogInterface dialog, int which) {
    425                 ProfileOwnerPreProvisioningActivity.this.finish();
    426             }
    427         };
    428 
    429         AlertDialog.Builder builder = new AlertDialog.Builder(this);
    430         builder.setMessage(message)
    431                 .setCancelable(false)
    432                 .setPositiveButton(getString(R.string.delete_profile), deleteListener)
    433                 .setNegativeButton(getString(R.string.cancel_delete_profile), cancelListener);
    434 
    435         return builder.create();
    436     }
    437     /**
    438      * Exception thrown when the provisioning has failed completely.
    439      *
    440      * We're using a custom exception to avoid catching subsequent exceptions that might be
    441      * significant.
    442      */
    443     private class ProvisioningFailedException extends Exception {
    444         public ProvisioningFailedException(String message) {
    445             super(message);
    446         }
    447 
    448         public ProvisioningFailedException(String message, Throwable t) {
    449             super(message, t);
    450         }
    451     }
    452 }
    453 
    454