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 onCreate(Bundle savedInstanceState)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 showStartProvisioningScreen()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 packageHasManageUsersPermission(String pkg)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 systemHasManagedProfileFeature()186 private boolean systemHasManagedProfileFeature() { 187 PackageManager pm = getPackageManager(); 188 return pm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS); 189 } 190 currentLauncherSupportsManagedProfiles()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 versionNumberAtLeastL(int versionNumber)207 private boolean versionNumberAtLeastL(int versionNumber) { 208 return versionNumber >= Build.VERSION_CODES.LOLLIPOP; 209 } 210 211 class ServiceMessageReceiver extends BroadcastReceiver { 212 @Override onReceive(Context context, Intent intent)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 setMdmIcon(String packageName, View contentView)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 */ initialize(Intent intent)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 onBackPressed()287 public void onBackPressed() { 288 // TODO: Handle this graciously by stopping the provisioning flow and cleaning up. 289 } 290 291 @Override onDestroy()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 */ checkEncryptedAndStartProvisioningService()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 setupEnvironmentAndProvision()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 pickLauncher()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 startManagedProvisioningService()346 private void startManagedProvisioningService() { 347 Intent intent = new Intent(this, ManagedProvisioningService.class); 348 intent.putExtras(getIntent()); 349 startService(intent); 350 } 351 352 @Override onActivityResult(int requestCode, int resultCode, Intent data)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 showCurrentLauncherInvalid()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 showErrorAndClose(int resourceId, String logText)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 */ alreadyHasManagedProfile()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 */ showManagedProfileExistsDialog( final int existingManagedProfileUserId)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 buildDeleteManagedProfileDialog(String message, DialogInterface.OnClickListener deleteListener)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 { ManagedProvisioningFailedException(String message)471 public ManagedProvisioningFailedException(String message) { 472 super(message); 473 } 474 ManagedProvisioningFailedException(String message, Throwable t)475 public ManagedProvisioningFailedException(String message, Throwable t) { 476 super(message, t); 477 } 478 } 479 } 480 481