Utils.java revision 051eb6c74da5531e6b21d28c206707f18e08587d
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 true if the given package requires an update. 261 * 262 * <p>There are two cases where an update is required: 263 * 1. The package is not currently present on the device. 264 * 2. The package is present, but the version is below the minimum supported version. 265 * 266 * @param packageName the package to be checked for updates 267 * @param minSupportedVersion the minimum supported version 268 * @param context a {@link Context} object 269 */ 270 public boolean packageRequiresUpdate(String packageName, int minSupportedVersion, 271 Context context) { 272 try { 273 PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0); 274 // Always download packages if no minimum version given. 275 if (minSupportedVersion != PackageDownloadInfo.DEFAULT_MINIMUM_VERSION 276 && packageInfo.versionCode >= minSupportedVersion) { 277 return false; 278 } 279 } catch (NameNotFoundException e) { 280 // Package not on device. 281 } 282 283 return true; 284 } 285 286 /** 287 * Returns whether USER_SETUP_COMPLETE is set on the calling user. 288 */ 289 public boolean isUserSetupCompleted(Context context) { 290 return Secure.getInt(context.getContentResolver(), Secure.USER_SETUP_COMPLETE, 0) != 0; 291 } 292 293 /** 294 * Returns whether DEVICE_PROVISIONED is set. 295 */ 296 public boolean isDeviceProvisioned(Context context) { 297 return Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0) != 0; 298 } 299 300 /** 301 * Sets whether package verification is enabled or not. 302 */ 303 public void setPackageVerifierEnabled(Context context, boolean packageVerifierEnabled) { 304 Global.putInt(context.getContentResolver(), Global.PACKAGE_VERIFIER_ENABLE, 305 packageVerifierEnabled ? 1 : 0); 306 } 307 308 /** 309 * Returns whether package verification is enabled or not. 310 */ 311 public boolean isPackageVerifierEnabled(Context context) { 312 return Global.getInt(context.getContentResolver(), Global.PACKAGE_VERIFIER_ENABLE, 0) != 0; 313 } 314 315 /** 316 * Returns the first existing managed profile if any present, null otherwise. 317 * 318 * <p>Note that we currently only support one managed profile per device. 319 */ 320 // TODO: Add unit tests 321 public UserHandle getManagedProfile(Context context) { 322 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); 323 int currentUserId = userManager.getUserHandle(); 324 List<UserInfo> userProfiles = userManager.getProfiles(currentUserId); 325 for (UserInfo profile : userProfiles) { 326 if (profile.isManagedProfile()) { 327 return new UserHandle(profile.id); 328 } 329 } 330 return null; 331 } 332 333 /** 334 * Returns the user id of an already existing managed profile or -1 if none exists. 335 */ 336 // TODO: Add unit tests 337 public int alreadyHasManagedProfile(Context context) { 338 UserHandle managedUser = getManagedProfile(context); 339 if (managedUser != null) { 340 return managedUser.getIdentifier(); 341 } else { 342 return -1; 343 } 344 } 345 346 /** 347 * Removes an account. 348 * 349 * <p>This removes the given account from the calling user's list of accounts. 350 * 351 * @param context a {@link Context} object 352 * @param account the account to be removed 353 */ 354 // TODO: Add unit tests 355 public void removeAccount(Context context, Account account) { 356 try { 357 AccountManager accountManager = 358 (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); 359 AccountManagerFuture<Bundle> bundle = accountManager.removeAccount(account, 360 null, null /* callback */, null /* handler */); 361 // Block to get the result of the removeAccount operation 362 if (bundle.getResult().getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) { 363 ProvisionLogger.logw("Account removed from the primary user."); 364 } else { 365 Intent removeIntent = (Intent) bundle.getResult().getParcelable( 366 AccountManager.KEY_INTENT); 367 if (removeIntent != null) { 368 ProvisionLogger.logi("Starting activity to remove account"); 369 TrampolineActivity.startActivity(context, removeIntent); 370 } else { 371 ProvisionLogger.logw("Could not remove account from the primary user."); 372 } 373 } 374 } catch (OperationCanceledException | AuthenticatorException | IOException e) { 375 ProvisionLogger.logw("Exception removing account from the primary user.", e); 376 } 377 } 378 379 /** 380 * Returns whether FRP is supported on the device. 381 */ 382 public boolean isFrpSupported(Context context) { 383 Object pdbManager = context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE); 384 return pdbManager != null; 385 } 386 387 /** 388 * Translates a given managed provisioning intent to its corresponding provisioning flow, using 389 * the action from the intent. 390 * 391 * <p/>This is necessary because, unlike other provisioning actions which has 1:1 mapping, there 392 * are multiple actions that can trigger the device owner provisioning flow. This includes 393 * {@link ACTION_PROVISION_MANAGED_DEVICE}, {@link ACTION_NDEF_DISCOVERED} and 394 * {@link ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}. These 3 actions are equivalent 395 * excepts they are sent from a different source. 396 * 397 * @return the appropriate DevicePolicyManager declared action for the given incoming intent. 398 * @throws IllegalProvisioningArgumentException if intent is malformed 399 */ 400 // TODO: Add unit tests 401 public String mapIntentToDpmAction(Intent intent) 402 throws IllegalProvisioningArgumentException { 403 if (intent == null || intent.getAction() == null) { 404 throw new IllegalProvisioningArgumentException("Null intent action."); 405 } 406 407 // Map the incoming intent to a DevicePolicyManager.ACTION_*, as there is a N:1 mapping in 408 // some cases. 409 String dpmProvisioningAction; 410 switch (intent.getAction()) { 411 // Trivial cases. 412 case ACTION_PROVISION_MANAGED_DEVICE: 413 case ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE: 414 case ACTION_PROVISION_MANAGED_USER: 415 case ACTION_PROVISION_MANAGED_PROFILE: 416 dpmProvisioningAction = intent.getAction(); 417 break; 418 419 // NFC cases which need to take mime-type into account. 420 case ACTION_NDEF_DISCOVERED: 421 String mimeType = intent.getType(); 422 switch (mimeType) { 423 case MIME_TYPE_PROVISIONING_NFC: 424 dpmProvisioningAction = ACTION_PROVISION_MANAGED_DEVICE; 425 break; 426 427 default: 428 throw new IllegalProvisioningArgumentException( 429 "Unknown NFC bump mime-type: " + mimeType); 430 } 431 break; 432 433 // Device owner provisioning from a trusted app. 434 // TODO (b/27217042): review for new management modes in split system-user model 435 case ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE: 436 dpmProvisioningAction = ACTION_PROVISION_MANAGED_DEVICE; 437 break; 438 439 default: 440 throw new IllegalProvisioningArgumentException("Unknown intent action " 441 + intent.getAction()); 442 } 443 return dpmProvisioningAction; 444 } 445 446 /** 447 * Sends an intent to trigger a factory reset. 448 */ 449 // TODO: Move the FR intent into a Globals class. 450 public void sendFactoryResetBroadcast(Context context, String reason) { 451 Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR); 452 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 453 intent.putExtra(Intent.EXTRA_REASON, reason); 454 context.sendBroadcast(intent); 455 } 456 457 /** 458 * Returns whether the given provisioning action is a profile owner action. 459 */ 460 // TODO: Move the list of device owner actions into a Globals class. 461 public final boolean isProfileOwnerAction(String action) { 462 return action.equals(ACTION_PROVISION_MANAGED_PROFILE) 463 || action.equals(ACTION_PROVISION_MANAGED_USER); 464 } 465 466 /** 467 * Returns whether the given provisioning action is a device owner action. 468 */ 469 // TODO: Move the list of device owner actions into a Globals class. 470 public final boolean isDeviceOwnerAction(String action) { 471 return action.equals(ACTION_PROVISION_MANAGED_DEVICE) 472 || action.equals(ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE); 473 } 474 475 /** 476 * Returns whether the device currently has connectivity. 477 */ 478 public boolean isConnectedToNetwork(Context context) { 479 NetworkInfo info = getActiveNetworkInfo(context); 480 return info != null && info.isConnected(); 481 } 482 483 /** 484 * Returns whether the device is currently connected to a wifi. 485 */ 486 public boolean isConnectedToWifi(Context context) { 487 NetworkInfo info = getActiveNetworkInfo(context); 488 return info != null 489 && info.isConnected() 490 && info.getType() == ConnectivityManager.TYPE_WIFI; 491 } 492 493 private NetworkInfo getActiveNetworkInfo(Context context) { 494 ConnectivityManager cm = 495 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 496 if (cm != null) { 497 return cm.getActiveNetworkInfo(); 498 } 499 return null; 500 } 501 502 /** 503 * Returns whether encryption is required on this device. 504 * 505 * <p>Encryption is required if the device is not currently encrypted and the persistent 506 * system flag {@code persist.sys.no_req_encrypt} is not set. 507 */ 508 public boolean isEncryptionRequired() { 509 return !isPhysicalDeviceEncrypted() 510 && !SystemProperties.getBoolean("persist.sys.no_req_encrypt", false); 511 } 512 513 /** 514 * Returns whether the device is currently encrypted. 515 */ 516 public boolean isPhysicalDeviceEncrypted() { 517 return StorageManager.isEncrypted(); 518 } 519 520 /** 521 * Returns the wifi pick intent. 522 */ 523 // TODO: Move this intent into a Globals class. 524 public Intent getWifiPickIntent() { 525 Intent wifiIntent = new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK); 526 wifiIntent.putExtra("extra_prefs_show_button_bar", true); 527 wifiIntent.putExtra("wifi_enable_next_on_connect", true); 528 return wifiIntent; 529 } 530 531 /** 532 * Returns whether the device has a split system user. 533 * 534 * <p>Split system user means that user 0 is system only and all meat users are separate from 535 * the system user. 536 */ 537 public boolean isSplitSystemUser() { 538 return UserManager.isSplitSystemUser(); 539 } 540 541 /** 542 * Returns whether the currently chosen launcher supports managed profiles. 543 * 544 * <p>A launcher is deemed to support managed profiles when its target API version is at least 545 * {@link Build.VERSION_CODES#LOLLIPOP}. 546 */ 547 public boolean currentLauncherSupportsManagedProfiles(Context context) { 548 Intent intent = new Intent(Intent.ACTION_MAIN); 549 intent.addCategory(Intent.CATEGORY_HOME); 550 551 PackageManager pm = context.getPackageManager(); 552 ResolveInfo launcherResolveInfo = pm.resolveActivity(intent, 553 PackageManager.MATCH_DEFAULT_ONLY); 554 if (launcherResolveInfo == null) { 555 return false; 556 } 557 try { 558 // If the user has not chosen a default launcher, then launcherResolveInfo will be 559 // referring to the resolver activity. It is fine to create a managed profile in 560 // this case since there will always be at least one launcher on the device that 561 // supports managed profile feature. 562 ApplicationInfo launcherAppInfo = pm.getApplicationInfo( 563 launcherResolveInfo.activityInfo.packageName, 0 /* default flags */); 564 return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion); 565 } catch (PackageManager.NameNotFoundException e) { 566 return false; 567 } 568 } 569 570 /** 571 * Returns whether the given version number is at least lollipop. 572 * 573 * @param versionNumber the version number to be verified. 574 */ 575 private boolean versionNumberAtLeastL(int versionNumber) { 576 return versionNumber >= Build.VERSION_CODES.LOLLIPOP; 577 } 578 579 /** 580 * Computes the sha 256 hash of a byte array. 581 */ 582 public byte[] computeHashOfByteArray(byte[] bytes) throws NoSuchAlgorithmException { 583 MessageDigest md = MessageDigest.getInstance(SHA256_TYPE); 584 md.update(bytes); 585 return md.digest(); 586 } 587 588 /** 589 * Computes a hash of a file with a spcific hash algorithm. 590 */ 591 public byte[] computeHashOfFile(String fileLocation, String hashType) { 592 InputStream fis = null; 593 MessageDigest md; 594 byte hash[] = null; 595 try { 596 md = MessageDigest.getInstance(hashType); 597 } catch (NoSuchAlgorithmException e) { 598 ProvisionLogger.loge("Hashing algorithm " + hashType + " not supported.", e); 599 return null; 600 } 601 try { 602 fis = new FileInputStream(fileLocation); 603 604 byte[] buffer = new byte[256]; 605 int n = 0; 606 while (n != -1) { 607 n = fis.read(buffer); 608 if (n > 0) { 609 md.update(buffer, 0, n); 610 } 611 } 612 hash = md.digest(); 613 } catch (IOException e) { 614 ProvisionLogger.loge("IO error.", e); 615 } finally { 616 // Close input stream quietly. 617 try { 618 if (fis != null) { 619 fis.close(); 620 } 621 } catch (IOException e) { 622 // Ignore. 623 } 624 } 625 return hash; 626 } 627} 628