Utils.java revision 3fee5b87a1d3bbace3932937520b238d1b8923ef
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 17package com.android.managedprovisioning.common; 18 19import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE; 20import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE; 21import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE; 22import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE; 23import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; 24import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC; 25import static android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED; 26 27import android.accounts.Account; 28import android.accounts.AccountManager; 29import android.accounts.AccountManagerFuture; 30import android.accounts.AuthenticatorException; 31import android.accounts.OperationCanceledException; 32import android.app.admin.DevicePolicyManager; 33import android.content.ComponentName; 34import android.content.Context; 35import android.content.Intent; 36import android.content.pm.ActivityInfo; 37import android.content.pm.ApplicationInfo; 38import android.content.pm.IPackageManager; 39import android.content.pm.PackageInfo; 40import android.content.pm.PackageManager; 41import android.content.pm.PackageManager.NameNotFoundException; 42import android.content.pm.ResolveInfo; 43import android.content.pm.UserInfo; 44import android.net.ConnectivityManager; 45import android.net.NetworkInfo; 46import android.net.wifi.WifiManager; 47import android.os.Build; 48import android.os.Bundle; 49import android.os.RemoteException; 50import android.os.ServiceManager; 51import android.os.SystemProperties; 52import android.os.UserHandle; 53import android.os.UserManager; 54import android.os.storage.StorageManager; 55import android.provider.Settings.Global; 56import android.provider.Settings.Secure; 57import android.text.TextUtils; 58 59import com.android.internal.annotations.VisibleForTesting; 60import com.android.managedprovisioning.ProvisionLogger; 61import com.android.managedprovisioning.TrampolineActivity; 62import com.android.managedprovisioning.model.PackageDownloadInfo; 63 64import java.io.FileInputStream; 65import java.io.IOException; 66import java.io.InputStream; 67import java.security.MessageDigest; 68import java.security.NoSuchAlgorithmException; 69import java.util.HashSet; 70import java.util.List; 71import java.util.Set; 72 73/** 74 * Class containing various auxiliary methods. 75 */ 76public class Utils { 77 public static final String SHA256_TYPE = "SHA-256"; 78 public static final String SHA1_TYPE = "SHA-1"; 79 80 public Utils() {} 81 82 /** 83 * Returns the currently installed system apps on a given user. 84 * 85 * <p>Calls into the {@link IPackageManager} to retrieve all installed packages on the given 86 * user and returns the package names of all system apps. 87 * 88 * @param ipm an {@link IPackageManager} object 89 * @param userId the id of the user we are interested in 90 */ 91 public Set<String> getCurrentSystemApps(IPackageManager ipm, int userId) { 92 Set<String> apps = new HashSet<String>(); 93 List<ApplicationInfo> aInfos = null; 94 try { 95 aInfos = ipm.getInstalledApplications( 96 PackageManager.GET_UNINSTALLED_PACKAGES, userId).getList(); 97 } catch (RemoteException neverThrown) { 98 ProvisionLogger.loge("This should not happen.", neverThrown); 99 } 100 for (ApplicationInfo aInfo : aInfos) { 101 if ((aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 102 apps.add(aInfo.packageName); 103 } 104 } 105 return apps; 106 } 107 108 /** 109 * Disables a given component in a given user. 110 * 111 * @param toDisable the component that should be disabled 112 * @param userId the id of the user where the component should be disabled. 113 */ 114 public void disableComponent(ComponentName toDisable, int userId) { 115 setComponentEnabledSetting( 116 IPackageManager.Stub.asInterface(ServiceManager.getService("package")), 117 toDisable, 118 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 119 userId); 120 } 121 122 /** 123 * Enables a given component in a given user. 124 * 125 * @param toEnable the component that should be enabled 126 * @param userId the id of the user where the component should be disabled. 127 */ 128 public void enableComponent(ComponentName toEnable, int userId) { 129 setComponentEnabledSetting( 130 IPackageManager.Stub.asInterface(ServiceManager.getService("package")), 131 toEnable, 132 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 133 userId); 134 } 135 136 /** 137 * Disables a given component in a given user. 138 * 139 * @param ipm an {@link IPackageManager} object 140 * @param toDisable the component that should be disabled 141 * @param userId the id of the user where the component should be disabled. 142 */ 143 @VisibleForTesting 144 void setComponentEnabledSetting(IPackageManager ipm, ComponentName toDisable, 145 int enabledSetting, int userId) { 146 try { 147 ipm.setComponentEnabledSetting(toDisable, 148 enabledSetting, PackageManager.DONT_KILL_APP, 149 userId); 150 } catch (RemoteException neverThrown) { 151 ProvisionLogger.loge("This should not happen.", neverThrown); 152 } catch (Exception e) { 153 ProvisionLogger.logw("Component not found, not changing enabled setting: " 154 + toDisable.toShortString()); 155 } 156 } 157 158 /** 159 * Check the validity of the admin component name supplied, or try to infer this componentName 160 * from the package. 161 * 162 * We are supporting lookup by package name for legacy reasons. 163 * 164 * If mdmComponentName is supplied (not null): 165 * mdmPackageName is ignored. 166 * Check that the package of mdmComponentName is installed, that mdmComponentName is a 167 * receiver in this package, and return it. The receiver can be in disabled state. 168 * 169 * Otherwise: 170 * mdmPackageName must be supplied (not null). 171 * Check that this package is installed, try to infer a potential device admin in this package, 172 * and return it. 173 */ 174 // TODO: Add unit tests 175 public ComponentName findDeviceAdmin(String mdmPackageName, 176 ComponentName mdmComponentName, Context c) throws IllegalProvisioningArgumentException { 177 if (mdmComponentName != null) { 178 mdmPackageName = mdmComponentName.getPackageName(); 179 } 180 if (mdmPackageName == null) { 181 throw new IllegalProvisioningArgumentException("Neither the package name nor the" 182 + " component name of the admin are supplied"); 183 } 184 PackageInfo pi; 185 try { 186 pi = c.getPackageManager().getPackageInfo(mdmPackageName, 187 PackageManager.GET_RECEIVERS | PackageManager.MATCH_DISABLED_COMPONENTS); 188 } catch (NameNotFoundException e) { 189 throw new IllegalProvisioningArgumentException("Mdm "+ mdmPackageName 190 + " is not installed. ", e); 191 } 192 if (mdmComponentName != null) { 193 // If the component was specified in the intent: check that it is in the manifest. 194 checkAdminComponent(mdmComponentName, pi); 195 return mdmComponentName; 196 } else { 197 // Otherwise: try to find a potential device admin in the manifest. 198 return findDeviceAdminInPackage(mdmPackageName, pi); 199 } 200 } 201 202 /** 203 * Verifies that an admin component is part of a given package. 204 * 205 * @param mdmComponentName the admin component to be checked 206 * @param pi the {@link PackageInfo} of the package to be checked. 207 * 208 * @throws IllegalProvisioningArgumentException if the given component is not part of the 209 * package 210 */ 211 private void checkAdminComponent(ComponentName mdmComponentName, PackageInfo pi) 212 throws IllegalProvisioningArgumentException{ 213 for (ActivityInfo ai : pi.receivers) { 214 if (mdmComponentName.getClassName().equals(ai.name)) { 215 return; 216 } 217 } 218 throw new IllegalProvisioningArgumentException("The component " + mdmComponentName 219 + " cannot be found"); 220 } 221 222 private ComponentName findDeviceAdminInPackage(String mdmPackageName, PackageInfo pi) 223 throws IllegalProvisioningArgumentException { 224 ComponentName mdmComponentName = null; 225 for (ActivityInfo ai : pi.receivers) { 226 if (!TextUtils.isEmpty(ai.permission) && 227 ai.permission.equals(android.Manifest.permission.BIND_DEVICE_ADMIN)) { 228 if (mdmComponentName != null) { 229 throw new IllegalProvisioningArgumentException("There are several " 230 + "device admins in " + mdmPackageName + " but no one in specified"); 231 } else { 232 mdmComponentName = new ComponentName(mdmPackageName, ai.name); 233 } 234 } 235 } 236 if (mdmComponentName == null) { 237 throw new IllegalProvisioningArgumentException("There are no device admins in" 238 + mdmPackageName); 239 } 240 return mdmComponentName; 241 } 242 243 /** 244 * Returns whether the current user is the system user. 245 */ 246 public boolean isCurrentUserSystem() { 247 return UserHandle.myUserId() == UserHandle.USER_SYSTEM; 248 } 249 250 /** 251 * Returns whether the device is currently managed. 252 */ 253 public boolean isDeviceManaged(Context context) { 254 DevicePolicyManager dpm = 255 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); 256 return dpm.isDeviceManaged(); 257 } 258 259 /** 260 * Returns whether the calling user is a managed profile. 261 */ 262 public boolean isManagedProfile(Context context) { 263 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); 264 UserInfo user = um.getUserInfo(UserHandle.myUserId()); 265 return user != null ? user.isManagedProfile() : false; 266 } 267 268 /** 269 * Returns true if the given package requires an update. 270 * 271 * <p>There are two cases where an update is required: 272 * 1. The package is not currently present on the device. 273 * 2. The package is present, but the version is below the minimum supported version. 274 * 275 * @param packageName the package to be checked for updates 276 * @param minSupportedVersion the minimum supported version 277 * @param context a {@link Context} object 278 */ 279 public boolean packageRequiresUpdate(String packageName, int minSupportedVersion, 280 Context context) { 281 try { 282 PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0); 283 // Always download packages if no minimum version given. 284 if (minSupportedVersion != PackageDownloadInfo.DEFAULT_MINIMUM_VERSION 285 && packageInfo.versionCode >= minSupportedVersion) { 286 return false; 287 } 288 } catch (NameNotFoundException e) { 289 // Package not on device. 290 } 291 292 return true; 293 } 294 295 /** 296 * Returns whether USER_SETUP_COMPLETE is set on the calling user. 297 */ 298 public boolean isUserSetupCompleted(Context context) { 299 return Secure.getInt(context.getContentResolver(), Secure.USER_SETUP_COMPLETE, 0) != 0; 300 } 301 302 /** 303 * Returns whether DEVICE_PROVISIONED is set. 304 */ 305 public boolean isDeviceProvisioned(Context context) { 306 return Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0) != 0; 307 } 308 309 /** 310 * Sets whether package verification is enabled or not. 311 */ 312 public void setPackageVerifierEnabled(Context context, boolean packageVerifierEnabled) { 313 Global.putInt(context.getContentResolver(), Global.PACKAGE_VERIFIER_ENABLE, 314 packageVerifierEnabled ? 1 : 0); 315 } 316 317 /** 318 * Returns whether package verification is enabled or not. 319 */ 320 public boolean isPackageVerifierEnabled(Context context) { 321 return Global.getInt(context.getContentResolver(), Global.PACKAGE_VERIFIER_ENABLE, 0) != 0; 322 } 323 324 /** 325 * Returns the first existing managed profile if any present, null otherwise. 326 * 327 * <p>Note that we currently only support one managed profile per device. 328 */ 329 // TODO: Add unit tests 330 public UserHandle getManagedProfile(Context context) { 331 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); 332 int currentUserId = userManager.getUserHandle(); 333 List<UserInfo> userProfiles = userManager.getProfiles(currentUserId); 334 for (UserInfo profile : userProfiles) { 335 if (profile.isManagedProfile()) { 336 return new UserHandle(profile.id); 337 } 338 } 339 return null; 340 } 341 342 /** 343 * Returns the user id of an already existing managed profile or -1 if none exists. 344 */ 345 // TODO: Add unit tests 346 public int alreadyHasManagedProfile(Context context) { 347 UserHandle managedUser = getManagedProfile(context); 348 if (managedUser != null) { 349 return managedUser.getIdentifier(); 350 } else { 351 return -1; 352 } 353 } 354 355 /** 356 * Removes an account. 357 * 358 * <p>This removes the given account from the calling user's list of accounts. 359 * 360 * @param context a {@link Context} object 361 * @param account the account to be removed 362 */ 363 // TODO: Add unit tests 364 public void removeAccount(Context context, Account account) { 365 try { 366 AccountManager accountManager = 367 (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); 368 AccountManagerFuture<Bundle> bundle = accountManager.removeAccount(account, 369 null, null /* callback */, null /* handler */); 370 // Block to get the result of the removeAccount operation 371 if (bundle.getResult().getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) { 372 ProvisionLogger.logw("Account removed from the primary user."); 373 } else { 374 Intent removeIntent = (Intent) bundle.getResult().getParcelable( 375 AccountManager.KEY_INTENT); 376 if (removeIntent != null) { 377 ProvisionLogger.logi("Starting activity to remove account"); 378 TrampolineActivity.startActivity(context, removeIntent); 379 } else { 380 ProvisionLogger.logw("Could not remove account from the primary user."); 381 } 382 } 383 } catch (OperationCanceledException | AuthenticatorException | IOException e) { 384 ProvisionLogger.logw("Exception removing account from the primary user.", e); 385 } 386 } 387 388 /** 389 * Returns whether FRP is supported on the device. 390 */ 391 public boolean isFrpSupported(Context context) { 392 Object pdbManager = context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE); 393 return pdbManager != null; 394 } 395 396 /** 397 * Translates a given managed provisioning intent to its corresponding provisioning flow, using 398 * the action from the intent. 399 * 400 * <p/>This is necessary because, unlike other provisioning actions which has 1:1 mapping, there 401 * are multiple actions that can trigger the device owner provisioning flow. This includes 402 * {@link ACTION_PROVISION_MANAGED_DEVICE}, {@link ACTION_NDEF_DISCOVERED} and 403 * {@link ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}. These 3 actions are equivalent 404 * excepts they are sent from a different source. 405 * 406 * @return the appropriate DevicePolicyManager declared action for the given incoming intent. 407 * @throws IllegalProvisioningArgumentException if intent is malformed 408 */ 409 // TODO: Add unit tests 410 public String mapIntentToDpmAction(Intent intent) 411 throws IllegalProvisioningArgumentException { 412 if (intent == null || intent.getAction() == null) { 413 throw new IllegalProvisioningArgumentException("Null intent action."); 414 } 415 416 // Map the incoming intent to a DevicePolicyManager.ACTION_*, as there is a N:1 mapping in 417 // some cases. 418 String dpmProvisioningAction; 419 switch (intent.getAction()) { 420 // Trivial cases. 421 case ACTION_PROVISION_MANAGED_DEVICE: 422 case ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE: 423 case ACTION_PROVISION_MANAGED_USER: 424 case ACTION_PROVISION_MANAGED_PROFILE: 425 dpmProvisioningAction = intent.getAction(); 426 break; 427 428 // NFC cases which need to take mime-type into account. 429 case ACTION_NDEF_DISCOVERED: 430 String mimeType = intent.getType(); 431 switch (mimeType) { 432 case MIME_TYPE_PROVISIONING_NFC: 433 dpmProvisioningAction = ACTION_PROVISION_MANAGED_DEVICE; 434 break; 435 436 default: 437 throw new IllegalProvisioningArgumentException( 438 "Unknown NFC bump mime-type: " + mimeType); 439 } 440 break; 441 442 // Device owner provisioning from a trusted app. 443 // TODO (b/27217042): review for new management modes in split system-user model 444 case ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE: 445 dpmProvisioningAction = ACTION_PROVISION_MANAGED_DEVICE; 446 break; 447 448 default: 449 throw new IllegalProvisioningArgumentException("Unknown intent action " 450 + intent.getAction()); 451 } 452 return dpmProvisioningAction; 453 } 454 455 /** 456 * Sends an intent to trigger a factory reset. 457 */ 458 // TODO: Move the FR intent into a Globals class. 459 public void sendFactoryResetBroadcast(Context context, String reason) { 460 Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR); 461 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 462 intent.putExtra(Intent.EXTRA_REASON, reason); 463 context.sendBroadcast(intent); 464 } 465 466 /** 467 * Returns whether the given provisioning action is a profile owner action. 468 */ 469 // TODO: Move the list of device owner actions into a Globals class. 470 public final boolean isProfileOwnerAction(String action) { 471 return action.equals(ACTION_PROVISION_MANAGED_PROFILE) 472 || action.equals(ACTION_PROVISION_MANAGED_USER); 473 } 474 475 /** 476 * Returns whether the given provisioning action is a device owner action. 477 */ 478 // TODO: Move the list of device owner actions into a Globals class. 479 public final boolean isDeviceOwnerAction(String action) { 480 return action.equals(ACTION_PROVISION_MANAGED_DEVICE) 481 || action.equals(ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE); 482 } 483 484 /** 485 * Returns whether the device currently has connectivity. 486 */ 487 public boolean isConnectedToNetwork(Context context) { 488 NetworkInfo info = getActiveNetworkInfo(context); 489 return info != null && info.isConnected(); 490 } 491 492 /** 493 * Returns whether the device is currently connected to a wifi. 494 */ 495 public boolean isConnectedToWifi(Context context) { 496 NetworkInfo info = getActiveNetworkInfo(context); 497 return info != null 498 && info.isConnected() 499 && info.getType() == ConnectivityManager.TYPE_WIFI; 500 } 501 502 private NetworkInfo getActiveNetworkInfo(Context context) { 503 ConnectivityManager cm = 504 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 505 if (cm != null) { 506 return cm.getActiveNetworkInfo(); 507 } 508 return null; 509 } 510 511 /** 512 * Returns whether encryption is required on this device. 513 * 514 * <p>Encryption is required if the device is not currently encrypted and the persistent 515 * system flag {@code persist.sys.no_req_encrypt} is not set. 516 */ 517 public boolean isEncryptionRequired() { 518 return !isPhysicalDeviceEncrypted() 519 && !SystemProperties.getBoolean("persist.sys.no_req_encrypt", false); 520 } 521 522 /** 523 * Returns whether the device is currently encrypted. 524 */ 525 public boolean isPhysicalDeviceEncrypted() { 526 return StorageManager.isEncrypted(); 527 } 528 529 /** 530 * Returns the wifi pick intent. 531 */ 532 // TODO: Move this intent into a Globals class. 533 public Intent getWifiPickIntent() { 534 Intent wifiIntent = new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK); 535 wifiIntent.putExtra("extra_prefs_show_button_bar", true); 536 wifiIntent.putExtra("wifi_enable_next_on_connect", true); 537 return wifiIntent; 538 } 539 540 /** 541 * Returns whether the device has a split system user. 542 * 543 * <p>Split system user means that user 0 is system only and all meat users are separate from 544 * the system user. 545 */ 546 public boolean isSplitSystemUser() { 547 return UserManager.isSplitSystemUser(); 548 } 549 550 /** 551 * Returns whether the currently chosen launcher supports managed profiles. 552 * 553 * <p>A launcher is deemed to support managed profiles when its target API version is at least 554 * {@link Build.VERSION_CODES#LOLLIPOP}. 555 */ 556 public boolean currentLauncherSupportsManagedProfiles(Context context) { 557 Intent intent = new Intent(Intent.ACTION_MAIN); 558 intent.addCategory(Intent.CATEGORY_HOME); 559 560 PackageManager pm = context.getPackageManager(); 561 ResolveInfo launcherResolveInfo = pm.resolveActivity(intent, 562 PackageManager.MATCH_DEFAULT_ONLY); 563 if (launcherResolveInfo == null) { 564 return false; 565 } 566 try { 567 // If the user has not chosen a default launcher, then launcherResolveInfo will be 568 // referring to the resolver activity. It is fine to create a managed profile in 569 // this case since there will always be at least one launcher on the device that 570 // supports managed profile feature. 571 ApplicationInfo launcherAppInfo = pm.getApplicationInfo( 572 launcherResolveInfo.activityInfo.packageName, 0 /* default flags */); 573 return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion); 574 } catch (PackageManager.NameNotFoundException e) { 575 return false; 576 } 577 } 578 579 /** 580 * Returns whether the given version number is at least lollipop. 581 * 582 * @param versionNumber the version number to be verified. 583 */ 584 private boolean versionNumberAtLeastL(int versionNumber) { 585 return versionNumber >= Build.VERSION_CODES.LOLLIPOP; 586 } 587 588 /** 589 * Computes the sha 256 hash of a byte array. 590 */ 591 public byte[] computeHashOfByteArray(byte[] bytes) throws NoSuchAlgorithmException { 592 MessageDigest md = MessageDigest.getInstance(SHA256_TYPE); 593 md.update(bytes); 594 return md.digest(); 595 } 596 597 /** 598 * Computes a hash of a file with a spcific hash algorithm. 599 */ 600 public byte[] computeHashOfFile(String fileLocation, String hashType) { 601 InputStream fis = null; 602 MessageDigest md; 603 byte hash[] = null; 604 try { 605 md = MessageDigest.getInstance(hashType); 606 } catch (NoSuchAlgorithmException e) { 607 ProvisionLogger.loge("Hashing algorithm " + hashType + " not supported.", e); 608 return null; 609 } 610 try { 611 fis = new FileInputStream(fileLocation); 612 613 byte[] buffer = new byte[256]; 614 int n = 0; 615 while (n != -1) { 616 n = fis.read(buffer); 617 if (n > 0) { 618 md.update(buffer, 0, n); 619 } 620 } 621 hash = md.digest(); 622 } catch (IOException e) { 623 ProvisionLogger.loge("IO error.", e); 624 } finally { 625 // Close input stream quietly. 626 try { 627 if (fis != null) { 628 fis.close(); 629 } 630 } catch (IOException e) { 631 // Ignore. 632 } 633 } 634 return hash; 635 } 636} 637