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