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