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