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 onCreate(Bundle savedInstanceState)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 showStartProvisioningScreen()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 packageHasManageUsersPermission(String pkg)166 private boolean packageHasManageUsersPermission(String pkg) { 167 return PackageManager.PERMISSION_GRANTED == getPackageManager() 168 .checkPermission(MANAGE_USERS_PERMISSION, pkg); 169 } 170 systemHasManagedProfileFeature()171 private boolean systemHasManagedProfileFeature() { 172 PackageManager pm = getPackageManager(); 173 return pm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS); 174 } 175 currentLauncherSupportsManagedProfiles()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 versionNumberAtLeastL(int versionNumber)195 private boolean versionNumberAtLeastL(int versionNumber) { 196 return versionNumber >= Build.VERSION_CODES.LOLLIPOP; 197 } 198 setMdmIcon(String packageName)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 */ initialize(Intent intent)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 */ checkEncryptedAndStartProvisioningService()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 onDialogConsent()276 public void onDialogConsent() { 277 setupEnvironmentAndProvision(); 278 } 279 280 @Override onDialogCancel()281 public void onDialogCancel() { 282 // Do nothing. 283 } 284 setupEnvironmentAndProvision()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 pickLauncher()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 startProfileOwnerProvisioning()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 onActivityResult(int requestCode, int resultCode, Intent data)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 showCurrentLauncherInvalid()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 showErrorAndClose(int resourceId, String logText)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 */ alreadyHasManagedProfile()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 */ showManagedProfileExistsDialog( final int existingManagedProfileUserId)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 buildDeleteManagedProfileDialog(String message, DialogInterface.OnClickListener deleteListener)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 { ProvisioningFailedException(String message)444 public ProvisioningFailedException(String message) { 445 super(message); 446 } 447 ProvisioningFailedException(String message, Throwable t)448 public ProvisioningFailedException(String message, Throwable t) { 449 super(message, t); 450 } 451 } 452 } 453 454