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.AlertDialog;
     27 import android.content.BroadcastReceiver;
     28 import android.content.ComponentName;
     29 import android.content.Context;
     30 import android.content.DialogInterface;
     31 import android.content.Intent;
     32 import android.content.IntentFilter;
     33 import android.content.pm.ApplicationInfo;
     34 import android.content.pm.PackageManager;
     35 import android.content.pm.ResolveInfo;
     36 import android.content.pm.PackageManager.NameNotFoundException;
     37 import android.content.pm.UserInfo;
     38 import android.graphics.drawable.Drawable;
     39 import android.os.Build;
     40 import android.os.Bundle;
     41 import android.os.PersistableBundle;
     42 import android.os.Process;
     43 import android.os.SystemProperties;
     44 import android.os.UserHandle;
     45 import android.os.UserManager;
     46 import android.support.v4.content.LocalBroadcastManager;
     47 import android.text.TextUtils;
     48 import android.view.LayoutInflater;
     49 import android.view.View;
     50 import android.widget.ImageView;
     51 import android.widget.TextView;
     52 import android.widget.Button;
     53 
     54 import java.util.List;
     55 
     56 /**
     57  * Managed provisioning sets up a separate profile on a device whose primary user is already set up.
     58  * The typical example is setting up a corporate profile that is controlled by their employer on a
     59  * users personal device to keep personal and work data separate.
     60  *
     61  * The activity handles the input validation and UI for managed profile provisioning.
     62  * and starts the {@link ManagedProvisioningService}, which runs through the setup steps in an
     63  * async task.
     64  */
     65 // TODO: Proper error handling to report back to the user and potentially the mdm.
     66 public class ManagedProvisioningActivity extends Activity {
     67 
     68     private static final String MANAGE_USERS_PERMISSION = "android.permission.MANAGE_USERS";
     69 
     70     // Note: must match the constant defined in HomeSettings
     71     private static final String EXTRA_SUPPORT_MANAGED_PROFILES = "support_managed_profiles";
     72 
     73     protected static final String EXTRA_USER_HAS_CONSENTED_PROVISIONING =
     74             "com.android.managedprovisioning.EXTRA_USER_HAS_CONSENTED_PROVISIONING";
     75 
     76     // Aliases to start managed provisioning with and without MANAGE_USERS permission
     77     protected static final ComponentName ALIAS_CHECK_CALLER =
     78             new ComponentName("com.android.managedprovisioning",
     79                     "com.android.managedprovisioning.ManagedProvisioningActivity");
     80 
     81     protected static final ComponentName ALIAS_NO_CHECK_CALLER =
     82             new ComponentName("com.android.managedprovisioning",
     83                     "com.android.managedprovisioning.ManagedProvisioningActivityNoCallerCheck");
     84 
     85     protected static final int ENCRYPT_DEVICE_REQUEST_CODE = 2;
     86     protected static final int CHANGE_LAUNCHER_REQUEST_CODE = 1;
     87 
     88     private String mMdmPackageName;
     89     private BroadcastReceiver mServiceMessageReceiver;
     90 
     91     private View mContentView;
     92     private View mMainTextView;
     93     private View mProgressView;
     94 
     95     @Override
     96     protected void onCreate(Bundle savedInstanceState) {
     97         super.onCreate(savedInstanceState);
     98 
     99         ProvisionLogger.logd("Managed provisioning activity ONCREATE");
    100 
    101         final LayoutInflater inflater = getLayoutInflater();
    102         mContentView = inflater.inflate(R.layout.user_consent, null);
    103         mMainTextView = mContentView.findViewById(R.id.main_text_container);
    104         mProgressView = mContentView.findViewById(R.id.progress_container);
    105         setContentView(mContentView);
    106 
    107         // Check whether system has the required managed profile feature.
    108         if (!systemHasManagedProfileFeature()) {
    109             showErrorAndClose(R.string.managed_provisioning_not_supported,
    110                     "Exiting managed provisioning, managed profiles feature is not available");
    111             return;
    112         }
    113         if (Process.myUserHandle().getIdentifier() != UserHandle.USER_OWNER) {
    114             showErrorAndClose(R.string.user_is_not_owner,
    115                     "Exiting managed provisioning, calling user is not owner.");
    116             return;
    117         }
    118 
    119         // Setup broadcast receiver for feedback from service.
    120         mServiceMessageReceiver = new ServiceMessageReceiver();
    121         IntentFilter filter = new IntentFilter();
    122         filter.addAction(ManagedProvisioningService.ACTION_PROVISIONING_SUCCESS);
    123         filter.addAction(ManagedProvisioningService.ACTION_PROVISIONING_ERROR);
    124         LocalBroadcastManager.getInstance(this).registerReceiver(mServiceMessageReceiver, filter);
    125 
    126         // Initialize member variables from the intent, stop if the intent wasn't valid.
    127         try {
    128             initialize(getIntent());
    129         } catch (ManagedProvisioningFailedException e) {
    130             showErrorAndClose(R.string.managed_provisioning_error_text, e.getMessage());
    131             return;
    132         }
    133 
    134         setMdmIcon(mMdmPackageName, mContentView);
    135 
    136         // If the caller started us via ALIAS_NO_CHECK_CALLER then they must have permission to
    137         // MANAGE_USERS since it is a restricted intent. Otherwise, check the calling package.
    138         boolean hasManageUsersPermission = (getComponentName().equals(ALIAS_NO_CHECK_CALLER));
    139         if (!hasManageUsersPermission) {
    140             // Calling package has to equal the requested device admin package or has to be system.
    141             String callingPackage = getCallingPackage();
    142             if (callingPackage == null) {
    143                 showErrorAndClose(R.string.managed_provisioning_error_text,
    144                         "Calling package is null. " +
    145                         "Was startActivityForResult used to start this activity?");
    146                 return;
    147             }
    148             if (!callingPackage.equals(mMdmPackageName)
    149                     && !packageHasManageUsersPermission(callingPackage)) {
    150                 showErrorAndClose(R.string.managed_provisioning_error_text, "Permission denied, "
    151                         + "calling package tried to set a different package as profile owner. "
    152                         + "The system MANAGE_USERS permission is required.");
    153                 return;
    154             }
    155         }
    156 
    157         // If there is already a managed profile, allow the user to cancel or delete it.
    158         int existingManagedProfileUserId = alreadyHasManagedProfile();
    159         if (existingManagedProfileUserId != -1) {
    160             showManagedProfileExistsDialog(existingManagedProfileUserId);
    161         } else {
    162             showStartProvisioningScreen();
    163         }
    164     }
    165 
    166     private void showStartProvisioningScreen() {
    167         Button positiveButton = (Button) mContentView.findViewById(R.id.positive_button);
    168         positiveButton.setOnClickListener(new View.OnClickListener() {
    169             @Override
    170             public void onClick(View v) {
    171                 checkEncryptedAndStartProvisioningService();
    172             }
    173         });
    174     }
    175 
    176     private boolean packageHasManageUsersPermission(String pkg) {
    177         int packagePermission = this.getPackageManager()
    178                 .checkPermission(MANAGE_USERS_PERMISSION, pkg);
    179         if (packagePermission == PackageManager.PERMISSION_GRANTED) {
    180             return true;
    181         } else {
    182             return false;
    183         }
    184     }
    185 
    186     private boolean systemHasManagedProfileFeature() {
    187         PackageManager pm = getPackageManager();
    188         return pm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
    189     }
    190 
    191     private boolean currentLauncherSupportsManagedProfiles() {
    192         Intent intent = new Intent(Intent.ACTION_MAIN);
    193         intent.addCategory(Intent.CATEGORY_HOME);
    194 
    195         PackageManager pm = getPackageManager();
    196         ResolveInfo launcherResolveInfo
    197                 = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
    198         try {
    199             ApplicationInfo launcherAppInfo = getPackageManager().getApplicationInfo(
    200                     launcherResolveInfo.activityInfo.packageName, 0 /* default flags */);
    201             return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion);
    202         } catch (PackageManager.NameNotFoundException e) {
    203             return false;
    204         }
    205     }
    206 
    207     private boolean versionNumberAtLeastL(int versionNumber) {
    208         return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
    209     }
    210 
    211     class ServiceMessageReceiver extends BroadcastReceiver {
    212         @Override
    213         public void onReceive(Context context, Intent intent) {
    214             String action = intent.getAction();
    215             if (action.equals(ManagedProvisioningService.ACTION_PROVISIONING_SUCCESS)) {
    216                 ProvisionLogger.logd("Successfully provisioned."
    217                         + "Finishing ManagedProvisioningActivity");
    218                 ManagedProvisioningActivity.this.setResult(Activity.RESULT_OK);
    219                 ManagedProvisioningActivity.this.finish();
    220                 return;
    221             } else if (action.equals(ManagedProvisioningService.ACTION_PROVISIONING_ERROR)) {
    222                 String errorLogMessage = intent.getStringExtra(
    223                         ManagedProvisioningService.EXTRA_LOG_MESSAGE_KEY);
    224                 ProvisionLogger.logd("Error reported: " + errorLogMessage);
    225                 showErrorAndClose(R.string.managed_provisioning_error_text, errorLogMessage);
    226                 return;
    227             }
    228         }
    229     }
    230 
    231     private void setMdmIcon(String packageName, View contentView) {
    232         if (packageName != null) {
    233             PackageManager pm = getPackageManager();
    234             try {
    235                 ApplicationInfo ai = pm.getApplicationInfo(packageName, /* default flags */ 0);
    236                 if (ai != null) {
    237                     Drawable packageIcon = pm.getApplicationIcon(packageName);
    238                     ImageView imageView = (ImageView) contentView.findViewById(R.id.mdm_icon_view);
    239                     imageView.setImageDrawable(packageIcon);
    240 
    241                     String appLabel = pm.getApplicationLabel(ai).toString();
    242                     TextView deviceManagerName = (TextView) contentView
    243                             .findViewById(R.id.device_manager_name);
    244                     deviceManagerName.setText(appLabel);
    245                 }
    246             } catch (PackageManager.NameNotFoundException e) {
    247                 // Package does not exist, ignore. Should never happen.
    248                 ProvisionLogger.loge("Package does not exist. Should never happen.");
    249             }
    250         }
    251     }
    252 
    253     /**
    254      * Checks if all required provisioning parameters are provided.
    255      * Does not check for extras that are optional such as wifi ssid.
    256      * Also checks whether type of admin extras bundle (if present) is PersistableBundle.
    257      *
    258      * @param intent The intent that started provisioning
    259      */
    260     private void initialize(Intent intent) throws ManagedProvisioningFailedException {
    261         // Check if the admin extras bundle is of the right type.
    262         try {
    263             PersistableBundle bundle = (PersistableBundle) getIntent().getParcelableExtra(
    264                     EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE);
    265         } catch (ClassCastException e) {
    266             throw new ManagedProvisioningFailedException("Extra "
    267                     + EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
    268                     + " must be of type PersistableBundle.", e);
    269         }
    270 
    271         // Validate package name and check if the package is installed
    272         mMdmPackageName = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
    273         if (TextUtils.isEmpty(mMdmPackageName)) {
    274             throw new ManagedProvisioningFailedException("Missing intent extra: "
    275                     + EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
    276         } else {
    277             try {
    278                 this.getPackageManager().getPackageInfo(mMdmPackageName, 0);
    279             } catch (NameNotFoundException e) {
    280                 throw new ManagedProvisioningFailedException("Mdm "+ mMdmPackageName
    281                         + " is not installed. ", e);
    282             }
    283         }
    284     }
    285 
    286     @Override
    287     public void onBackPressed() {
    288         // TODO: Handle this graciously by stopping the provisioning flow and cleaning up.
    289     }
    290 
    291     @Override
    292     public void onDestroy() {
    293         LocalBroadcastManager.getInstance(this).unregisterReceiver(mServiceMessageReceiver);
    294         super.onDestroy();
    295     }
    296 
    297     /**
    298      * If the device is encrypted start the service which does the provisioning, otherwise ask for
    299      * user consent to encrypt the device.
    300      */
    301     private void checkEncryptedAndStartProvisioningService() {
    302         if (EncryptDeviceActivity.isDeviceEncrypted()
    303                 || SystemProperties.getBoolean("persist.sys.no_req_encrypt", false)) {
    304 
    305             // Notify the user once more that the admin will have full control over the profile,
    306             // then start provisioning.
    307             new UserConsentDialog(this, UserConsentDialog.PROFILE_OWNER, new Runnable() {
    308                     @Override
    309                     public void run() {
    310                         setupEnvironmentAndProvision();
    311                     }
    312                 } /* onUserConsented */ , null /* onCancel */).show(getFragmentManager(),
    313                         "UserConsentDialogFragment");
    314         } else {
    315             Bundle resumeExtras = getIntent().getExtras();
    316             resumeExtras.putString(EXTRA_RESUME_TARGET, TARGET_PROFILE_OWNER);
    317             Intent encryptIntent = new Intent(this, EncryptDeviceActivity.class)
    318                     .putExtra(EXTRA_RESUME, resumeExtras);
    319             startActivityForResult(encryptIntent, ENCRYPT_DEVICE_REQUEST_CODE);
    320             // Continue in onActivityResult or after reboot.
    321         }
    322     }
    323 
    324     private void setupEnvironmentAndProvision() {
    325         // Remove any pre-provisioning UI in favour of progress display
    326         BootReminder.cancelProvisioningReminder(
    327                 ManagedProvisioningActivity.this);
    328         mProgressView.setVisibility(View.VISIBLE);
    329         mMainTextView.setVisibility(View.GONE);
    330 
    331         // Check whether the current launcher supports managed profiles.
    332         if (!currentLauncherSupportsManagedProfiles()) {
    333             showCurrentLauncherInvalid();
    334         } else {
    335             startManagedProvisioningService();
    336         }
    337     }
    338 
    339     private void pickLauncher() {
    340         Intent changeLauncherIntent = new Intent("android.settings.HOME_SETTINGS");
    341         changeLauncherIntent.putExtra(EXTRA_SUPPORT_MANAGED_PROFILES, true);
    342         startActivityForResult(changeLauncherIntent, CHANGE_LAUNCHER_REQUEST_CODE);
    343         // Continue in onActivityResult.
    344     }
    345 
    346     private void startManagedProvisioningService() {
    347         Intent intent = new Intent(this, ManagedProvisioningService.class);
    348         intent.putExtras(getIntent());
    349         startService(intent);
    350     }
    351 
    352     @Override
    353     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    354         if (requestCode == ENCRYPT_DEVICE_REQUEST_CODE) {
    355             if (resultCode == RESULT_CANCELED) {
    356                 ProvisionLogger.loge("User canceled device encryption.");
    357                 setResult(Activity.RESULT_CANCELED);
    358                 finish();
    359             }
    360         } else if (requestCode == CHANGE_LAUNCHER_REQUEST_CODE) {
    361             if (resultCode == RESULT_CANCELED) {
    362                 showCurrentLauncherInvalid();
    363             } else if (resultCode == RESULT_OK) {
    364                 startManagedProvisioningService();
    365             }
    366         }
    367     }
    368 
    369     private void showCurrentLauncherInvalid() {
    370         new AlertDialog.Builder(this)
    371                 .setCancelable(false)
    372                 .setMessage(R.string.managed_provisioning_not_supported_by_launcher)
    373                 .setNegativeButton(R.string.cancel_provisioning,
    374                         new DialogInterface.OnClickListener() {
    375                             @Override
    376                             public void onClick(DialogInterface dialog,int id) {
    377                                 dialog.dismiss();
    378                                 setResult(Activity.RESULT_CANCELED);
    379                                 finish();
    380                             }
    381                         })
    382                 .setPositiveButton(R.string.pick_launcher,
    383                         new DialogInterface.OnClickListener() {
    384                             @Override
    385                             public void onClick(DialogInterface dialog,int id) {
    386                                 pickLauncher();
    387                             }
    388                         }).show();
    389     }
    390 
    391     public void showErrorAndClose(int resourceId, String logText) {
    392         ProvisionLogger.loge(logText);
    393         new ManagedProvisioningErrorDialog(getString(resourceId))
    394               .show(getFragmentManager(), "ErrorDialogFragment");
    395     }
    396 
    397     /**
    398      * @return The User id of an already existing managed profile or -1 if none
    399      * exists
    400      */
    401     int alreadyHasManagedProfile() {
    402         UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
    403         List<UserInfo> profiles = userManager.getProfiles(getUserId());
    404         for (UserInfo userInfo : profiles) {
    405             if (userInfo.isManagedProfile()) {
    406                 return userInfo.getUserHandle().getIdentifier();
    407             }
    408         }
    409         return -1;
    410     }
    411 
    412     /**
    413      * Builds a dialog that allows the user to remove an existing managed profile after they were
    414      * shown an additional warning.
    415      */
    416     private void showManagedProfileExistsDialog(
    417             final int existingManagedProfileUserId) {
    418 
    419         // Before deleting, show a warning dialog
    420         DialogInterface.OnClickListener warningListener =
    421                 new DialogInterface.OnClickListener() {
    422             @Override
    423             public void onClick(DialogInterface dialog, int which) {
    424                 // Really delete the profile if the user clicks delete on the warning dialog.
    425                 final DialogInterface.OnClickListener deleteListener =
    426                         new DialogInterface.OnClickListener() {
    427                     @Override
    428                     public void onClick(DialogInterface dialog, int which) {
    429                         UserManager userManager =
    430                                 (UserManager) getSystemService(Context.USER_SERVICE);
    431                         userManager.removeUser(existingManagedProfileUserId);
    432                         showStartProvisioningScreen();
    433                     }
    434                 };
    435                 buildDeleteManagedProfileDialog(
    436                         getString(R.string.sure_you_want_to_delete_profile),
    437                         deleteListener).show();
    438             }
    439         };
    440 
    441         buildDeleteManagedProfileDialog(
    442                 getString(R.string.managed_profile_already_present),
    443                 warningListener).show();
    444     }
    445 
    446     private AlertDialog buildDeleteManagedProfileDialog(String message,
    447             DialogInterface.OnClickListener deleteListener) {
    448         DialogInterface.OnClickListener cancelListener =
    449                 new DialogInterface.OnClickListener() {
    450             @Override
    451             public void onClick(DialogInterface dialog, int which) {
    452                 ManagedProvisioningActivity.this.finish();
    453             }
    454         };
    455 
    456         AlertDialog.Builder builder = new AlertDialog.Builder(this);
    457         builder.setMessage(message)
    458                 .setCancelable(false)
    459                 .setPositiveButton(getString(R.string.delete_profile), deleteListener)
    460                 .setNegativeButton(getString(R.string.cancel_delete_profile), cancelListener);
    461 
    462         return builder.create();
    463     }
    464     /**
    465      * Exception thrown when the managed provisioning has failed completely.
    466      *
    467      * We're using a custom exception to avoid catching subsequent exceptions that might be
    468      * significant.
    469      */
    470     private class ManagedProvisioningFailedException extends Exception {
    471         public ManagedProvisioningFailedException(String message) {
    472             super(message);
    473         }
    474 
    475         public ManagedProvisioningFailedException(String message, Throwable t) {
    476             super(message, t);
    477         }
    478     }
    479 }
    480 
    481