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 17 package com.android.managedprovisioning.common; 18 19 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE; 20 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE; 21 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE; 22 import static android.app.admin.DevicePolicyManager.FLAG_SUPPORTED_MODES_DEVICE_OWNER; 23 import static android.app.admin.DevicePolicyManager.FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED; 24 import static android.content.pm.PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS; 25 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; 26 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; 27 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; 28 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; 29 30 import android.annotation.NonNull; 31 import android.annotation.Nullable; 32 import android.annotation.StringRes; 33 import android.app.admin.DevicePolicyManager; 34 import android.content.ComponentName; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.content.pm.ActivityInfo; 38 import android.content.pm.ApplicationInfo; 39 import android.content.pm.IPackageManager; 40 import android.content.pm.PackageInfo; 41 import android.content.pm.PackageManager; 42 import android.content.pm.PackageManager.NameNotFoundException; 43 import android.content.pm.ResolveInfo; 44 import android.content.res.TypedArray; 45 import android.net.ConnectivityManager; 46 import android.net.NetworkCapabilities; 47 import android.net.NetworkInfo; 48 import android.net.wifi.WifiManager; 49 import android.os.Build; 50 import android.os.RemoteException; 51 import android.os.ServiceManager; 52 import android.os.SystemProperties; 53 import android.os.UserHandle; 54 import android.os.UserManager; 55 import android.os.storage.StorageManager; 56 import android.text.SpannableString; 57 import android.text.Spanned; 58 import android.text.TextUtils; 59 import android.text.method.LinkMovementMethod; 60 import android.text.style.ClickableSpan; 61 import android.view.View; 62 import android.view.View.OnClickListener; 63 import android.view.ViewTreeObserver; 64 import android.widget.TextView; 65 66 import com.android.internal.annotations.VisibleForTesting; 67 import com.android.managedprovisioning.R; 68 import com.android.managedprovisioning.model.CustomizationParams; 69 import com.android.managedprovisioning.model.PackageDownloadInfo; 70 import com.android.managedprovisioning.model.ProvisioningParams; 71 import com.android.managedprovisioning.preprovisioning.WebActivity; 72 73 import com.google.android.setupcompat.template.FooterBarMixin; 74 import com.google.android.setupcompat.template.FooterButton; 75 import com.google.android.setupcompat.template.FooterButton.ButtonType; 76 import com.google.android.setupdesign.GlifLayout; 77 78 import java.io.FileInputStream; 79 import java.io.IOException; 80 import java.io.InputStream; 81 import java.security.MessageDigest; 82 import java.security.NoSuchAlgorithmException; 83 import java.util.Arrays; 84 import java.util.HashSet; 85 import java.util.List; 86 import java.util.Objects; 87 import java.util.Set; 88 import java.util.function.Consumer; 89 90 /** 91 * Class containing various auxiliary methods. 92 */ 93 public class Utils { 94 public static final String SHA256_TYPE = "SHA-256"; 95 96 // value chosen to match UX designs; when updating check status bar icon colors 97 private static final int THRESHOLD_BRIGHT_COLOR = 190; 98 Utils()99 public Utils() {} 100 101 /** 102 * Returns the system apps currently available to a given user. 103 * 104 * <p>Calls the {@link IPackageManager} to retrieve all system apps available to a user and 105 * returns their package names. 106 * 107 * @param ipm an {@link IPackageManager} object 108 * @param userId the id of the user to check the apps for 109 */ getCurrentSystemApps(IPackageManager ipm, int userId)110 public Set<String> getCurrentSystemApps(IPackageManager ipm, int userId) { 111 Set<String> apps = new HashSet<>(); 112 List<ApplicationInfo> aInfos = null; 113 try { 114 aInfos = ipm.getInstalledApplications( 115 MATCH_UNINSTALLED_PACKAGES | MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, userId) 116 .getList(); 117 } catch (RemoteException neverThrown) { 118 ProvisionLogger.loge("This should not happen.", neverThrown); 119 } 120 for (ApplicationInfo aInfo : aInfos) { 121 if ((aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 122 apps.add(aInfo.packageName); 123 } 124 } 125 return apps; 126 } 127 128 /** 129 * Disables a given component in a given user. 130 * 131 * @param toDisable the component that should be disabled 132 * @param userId the id of the user where the component should be disabled. 133 */ disableComponent(ComponentName toDisable, int userId)134 public void disableComponent(ComponentName toDisable, int userId) { 135 setComponentEnabledSetting( 136 IPackageManager.Stub.asInterface(ServiceManager.getService("package")), 137 toDisable, 138 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 139 userId); 140 } 141 142 /** 143 * Enables a given component in a given user. 144 * 145 * @param toEnable the component that should be enabled 146 * @param userId the id of the user where the component should be disabled. 147 */ enableComponent(ComponentName toEnable, int userId)148 public void enableComponent(ComponentName toEnable, int userId) { 149 setComponentEnabledSetting( 150 IPackageManager.Stub.asInterface(ServiceManager.getService("package")), 151 toEnable, 152 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 153 userId); 154 } 155 156 /** 157 * Disables a given component in a given user. 158 * 159 * @param ipm an {@link IPackageManager} object 160 * @param toDisable the component that should be disabled 161 * @param userId the id of the user where the component should be disabled. 162 */ 163 @VisibleForTesting setComponentEnabledSetting(IPackageManager ipm, ComponentName toDisable, int enabledSetting, int userId)164 void setComponentEnabledSetting(IPackageManager ipm, ComponentName toDisable, 165 int enabledSetting, int userId) { 166 try { 167 ipm.setComponentEnabledSetting(toDisable, 168 enabledSetting, PackageManager.DONT_KILL_APP, 169 userId); 170 } catch (RemoteException neverThrown) { 171 ProvisionLogger.loge("This should not happen.", neverThrown); 172 } catch (Exception e) { 173 ProvisionLogger.logw("Component not found, not changing enabled setting: " 174 + toDisable.toShortString()); 175 } 176 } 177 178 /** 179 * Check the validity of the admin component name supplied, or try to infer this componentName 180 * from the package. 181 * 182 * We are supporting lookup by package name for legacy reasons. 183 * 184 * If dpcComponentName is supplied (not null): dpcPackageName is ignored. 185 * Check that the package of dpcComponentName is installed, that dpcComponentName is a 186 * receiver in this package, and return it. The receiver can be in disabled state. 187 * 188 * Otherwise: dpcPackageName must be supplied (not null). 189 * Check that this package is installed, try to infer a potential device admin in this package, 190 * and return it. 191 */ 192 @NonNull findDeviceAdmin(String dpcPackageName, ComponentName dpcComponentName, Context context, int userId)193 public ComponentName findDeviceAdmin(String dpcPackageName, ComponentName dpcComponentName, 194 Context context, int userId) throws IllegalProvisioningArgumentException { 195 if (dpcComponentName != null) { 196 dpcPackageName = dpcComponentName.getPackageName(); 197 } 198 if (dpcPackageName == null) { 199 throw new IllegalProvisioningArgumentException("Neither the package name nor the" 200 + " component name of the admin are supplied"); 201 } 202 PackageInfo pi; 203 try { 204 pi = context.getPackageManager().getPackageInfoAsUser(dpcPackageName, 205 PackageManager.GET_RECEIVERS | PackageManager.MATCH_DISABLED_COMPONENTS, 206 userId); 207 } catch (NameNotFoundException e) { 208 throw new IllegalProvisioningArgumentException("Dpc " + dpcPackageName 209 + " is not installed. ", e); 210 } 211 212 final ComponentName componentName = findDeviceAdminInPackageInfo(dpcPackageName, 213 dpcComponentName, pi); 214 if (componentName == null) { 215 throw new IllegalProvisioningArgumentException("Cannot find any admin receiver in " 216 + "package " + dpcPackageName + " with component " + dpcComponentName); 217 } 218 return componentName; 219 } 220 221 /** 222 * If dpcComponentName is not null: dpcPackageName is ignored. 223 * Check that the package of dpcComponentName is installed, that dpcComponentName is a 224 * receiver in this package, and return it. The receiver can be in disabled state. 225 * 226 * Otherwise, try to infer a potential device admin component in this package info. 227 * 228 * @return infered device admin component in package info. Otherwise, null 229 */ 230 @Nullable findDeviceAdminInPackageInfo(@onNull String dpcPackageName, @Nullable ComponentName dpcComponentName, @NonNull PackageInfo pi)231 public ComponentName findDeviceAdminInPackageInfo(@NonNull String dpcPackageName, 232 @Nullable ComponentName dpcComponentName, @NonNull PackageInfo pi) { 233 if (dpcComponentName != null) { 234 if (!isComponentInPackageInfo(dpcComponentName, pi)) { 235 ProvisionLogger.logw("The component " + dpcComponentName + " isn't registered in " 236 + "the apk"); 237 return null; 238 } 239 return dpcComponentName; 240 } else { 241 return findDeviceAdminInPackage(dpcPackageName, pi); 242 } 243 } 244 245 /** 246 * Finds a device admin in a given {@link PackageInfo} object. 247 * 248 * <p>This function returns {@code null} if no or multiple admin receivers were found, and if 249 * the package name does not match dpcPackageName.</p> 250 * @param packageName packge name that should match the {@link PackageInfo} object. 251 * @param packageInfo package info to be examined. 252 * @return admin receiver or null in case of error. 253 */ 254 @Nullable findDeviceAdminInPackage(String packageName, PackageInfo packageInfo)255 private ComponentName findDeviceAdminInPackage(String packageName, PackageInfo packageInfo) { 256 if (packageInfo == null || !TextUtils.equals(packageInfo.packageName, packageName)) { 257 return null; 258 } 259 260 ComponentName mdmComponentName = null; 261 for (ActivityInfo ai : packageInfo.receivers) { 262 if (TextUtils.equals(ai.permission, android.Manifest.permission.BIND_DEVICE_ADMIN)) { 263 if (mdmComponentName != null) { 264 ProvisionLogger.logw("more than 1 device admin component are found"); 265 return null; 266 } else { 267 mdmComponentName = new ComponentName(packageName, ai.name); 268 } 269 } 270 } 271 return mdmComponentName; 272 } 273 isComponentInPackageInfo(ComponentName dpcComponentName, PackageInfo pi)274 private boolean isComponentInPackageInfo(ComponentName dpcComponentName, 275 PackageInfo pi) { 276 for (ActivityInfo ai : pi.receivers) { 277 if (dpcComponentName.getClassName().equals(ai.name)) { 278 return true; 279 } 280 } 281 return false; 282 } 283 284 /** 285 * Return if a given package has testOnly="true", in which case we'll relax certain rules 286 * for CTS. 287 * 288 * The system allows this flag to be changed when an app is updated. But 289 * {@link DevicePolicyManager} uses the persisted version to do actual checks for relevant 290 * dpm command. 291 * 292 * @see DevicePolicyManagerService#isPackageTestOnly for more info 293 */ isPackageTestOnly(PackageManager pm, String packageName, int userHandle)294 public static boolean isPackageTestOnly(PackageManager pm, String packageName, int userHandle) { 295 if (TextUtils.isEmpty(packageName)) { 296 return false; 297 } 298 299 try { 300 final ApplicationInfo ai = pm.getApplicationInfoAsUser(packageName, 301 PackageManager.MATCH_DIRECT_BOOT_AWARE 302 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle); 303 return ai != null && (ai.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0; 304 } catch (PackageManager.NameNotFoundException e) { 305 return false; 306 } 307 308 } 309 310 /** 311 * Returns whether the current user is the system user. 312 */ isCurrentUserSystem()313 public boolean isCurrentUserSystem() { 314 return UserHandle.myUserId() == UserHandle.USER_SYSTEM; 315 } 316 317 /** 318 * Returns whether the device is currently managed. 319 */ isDeviceManaged(Context context)320 public boolean isDeviceManaged(Context context) { 321 DevicePolicyManager dpm = 322 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); 323 return dpm.isDeviceManaged(); 324 } 325 326 /** 327 * Returns true if the given package requires an update. 328 * 329 * <p>There are two cases where an update is required: 330 * 1. The package is not currently present on the device. 331 * 2. The package is present, but the version is below the minimum supported version. 332 * 333 * @param packageName the package to be checked for updates 334 * @param minSupportedVersion the minimum supported version 335 * @param context a {@link Context} object 336 */ packageRequiresUpdate(String packageName, int minSupportedVersion, Context context)337 public boolean packageRequiresUpdate(String packageName, int minSupportedVersion, 338 Context context) { 339 try { 340 PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0); 341 // Always download packages if no minimum version given. 342 if (minSupportedVersion != PackageDownloadInfo.DEFAULT_MINIMUM_VERSION 343 && packageInfo.versionCode >= minSupportedVersion) { 344 return false; 345 } 346 } catch (NameNotFoundException e) { 347 // Package not on device. 348 } 349 350 return true; 351 } 352 353 /** 354 * Returns the first existing managed profile if any present, null otherwise. 355 * 356 * <p>Note that we currently only support one managed profile per device. 357 */ 358 // TODO: Add unit tests getManagedProfile(Context context)359 public UserHandle getManagedProfile(Context context) { 360 DevicePolicyManager devicePolicyManager = 361 context.getSystemService(DevicePolicyManager.class); 362 int currentUserId = UserHandle.myUserId(); 363 List<UserHandle> managedProfiles = 364 devicePolicyManager.getPolicyManagedProfiles(UserHandle.of(currentUserId)); 365 if (managedProfiles.isEmpty()) { 366 return null; 367 } 368 return managedProfiles.get(0); 369 } 370 371 /** 372 * Returns whether FRP is supported on the device. 373 */ isFrpSupported(Context context)374 public boolean isFrpSupported(Context context) { 375 Object pdbManager = context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE); 376 return pdbManager != null; 377 } 378 379 380 /** 381 * Returns {@code true} if the admin-integrated flow should be performed. 382 * 383 * <p>This method must not be called before the admin app has been installed. If it has not 384 * yet been installed, consider using {@link 385 * #checkAdminIntegratedFlowPreconditions(ProvisioningParams)}. 386 * 387 * <p>To perform the admin-integrated flow, all of the following criteria must be fulfilled: 388 * <ul> 389 * <li>All of the preconditions in {@link 390 * #checkAdminIntegratedFlowPreconditions(ProvisioningParams)}</li> 391 * <li>The DPC has an activity with intent filter with action {@link 392 * DevicePolicyManager#ACTION_GET_PROVISIONING_MODE}</li> 393 * <li>The DPC has an activity with intent filter with action {@link 394 * DevicePolicyManager#ACTION_ADMIN_POLICY_COMPLIANCE}</li> 395 * </ul> 396 */ canPerformAdminIntegratedFlow(Context context, ProvisioningParams params, PolicyComplianceUtils policyComplianceUtils, GetProvisioningModeUtils provisioningModeUtils)397 public boolean canPerformAdminIntegratedFlow(Context context, ProvisioningParams params, 398 PolicyComplianceUtils policyComplianceUtils, 399 GetProvisioningModeUtils provisioningModeUtils) { 400 if (!checkAdminIntegratedFlowPreconditions(params)) { 401 return false; 402 } 403 boolean isPolicyComplianceScreenAvailable = 404 policyComplianceUtils.isPolicyComplianceActivityResolvableForUser(context, params, 405 this, UserHandle.SYSTEM); 406 if (!isPolicyComplianceScreenAvailable) { 407 ProvisionLogger.logi("Policy compliance DPC screen not available."); 408 return false; 409 } 410 boolean isGetProvisioningModeScreenAvailable = 411 provisioningModeUtils.isGetProvisioningModeActivityResolvable(context, params); 412 if (!isGetProvisioningModeScreenAvailable) { 413 ProvisionLogger.logi("Get provisioning mode DPC screen not available."); 414 return false; 415 } 416 return true; 417 } 418 419 /** 420 * Returns {@code true} if the admin-integrated flow preconditions are met. 421 * 422 * <p>This method can be called before the admin app has been installed. Returning {@code true} 423 * does not mean the admin-integrated flow should be performed (for that, use {@link 424 * #canPerformAdminIntegratedFlow(Context, ProvisioningParams, PolicyComplianceUtils, 425 * GetProvisioningModeUtils)}), but returning {@code false} can be used as an early indication 426 * that it should <i>not</i> be performed. 427 * 428 * <p>The preconditions are: 429 * <ul> 430 * <li>Provisioning was started using {@link 431 * DevicePolicyManager#ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}</li> 432 * <li>The provisioning is not triggered by NFC</li> 433 * <li>This is not a financed device provisioning</li> 434 * </ul> 435 */ checkAdminIntegratedFlowPreconditions(ProvisioningParams params)436 public boolean checkAdminIntegratedFlowPreconditions(ProvisioningParams params) { 437 if (isFinancedDeviceAction(params.provisioningAction)) { 438 ProvisionLogger.logi("Financed device provisioning"); 439 return false; 440 } 441 if (!params.startedByTrustedSource) { 442 ProvisionLogger.logi("Provisioning not started by a trusted source"); 443 return false; 444 } 445 return true; 446 } 447 448 /** 449 * Factory resets the device. 450 */ factoryReset(Context context, String reason)451 public void factoryReset(Context context, String reason) { 452 context.getSystemService(DevicePolicyManager.class).wipeData(/* flags=*/ 0, reason); 453 } 454 455 /** 456 * Returns whether the given provisioning action is a profile owner action. 457 */ 458 // TODO: Move the list of device owner actions into a Globals class. isProfileOwnerAction(String action)459 public final boolean isProfileOwnerAction(String action) { 460 return ACTION_PROVISION_MANAGED_PROFILE.equals(action); 461 } 462 463 /** 464 * Returns whether the given provisioning action is a device owner action. 465 */ 466 // TODO: Move the list of device owner actions into a Globals class. isDeviceOwnerAction(String action)467 public final boolean isDeviceOwnerAction(String action) { 468 return ACTION_PROVISION_MANAGED_DEVICE.equals(action); 469 } 470 471 /** 472 * Returns whether the given provisioning action is a financed device action. 473 */ isFinancedDeviceAction(String action)474 public final boolean isFinancedDeviceAction(String action) { 475 return ACTION_PROVISION_FINANCED_DEVICE.equals(action); 476 } 477 478 /** 479 * Returns whether the device currently has connectivity. 480 */ isConnectedToNetwork(Context context)481 public boolean isConnectedToNetwork(Context context) { 482 NetworkInfo info = getActiveNetworkInfo(context); 483 return info != null && info.isConnected(); 484 } 485 isMobileNetworkConnectedToInternet(Context context)486 public boolean isMobileNetworkConnectedToInternet(Context context) { 487 final ConnectivityManager connectivityManager = 488 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 489 return Arrays.stream(connectivityManager.getAllNetworks()) 490 .filter(network -> { 491 return Objects.nonNull(connectivityManager.getNetworkCapabilities(network)); 492 }) 493 .map(connectivityManager::getNetworkCapabilities) 494 .filter(this::isCellularNetwork) 495 .anyMatch(this::isConnectedToInternet); 496 } 497 isConnectedToInternet(NetworkCapabilities capabilities)498 private boolean isConnectedToInternet(NetworkCapabilities capabilities) { 499 return capabilities.hasCapability(NET_CAPABILITY_INTERNET) 500 && capabilities.hasCapability(NET_CAPABILITY_VALIDATED); 501 } 502 isCellularNetwork(NetworkCapabilities capabilities)503 private boolean isCellularNetwork(NetworkCapabilities capabilities) { 504 return capabilities.hasTransport(TRANSPORT_CELLULAR); 505 } 506 507 /** 508 * Returns whether the device is currently connected to specific network type, such as {@link 509 * ConnectivityManager.TYPE_WIFI} or {@link ConnectivityManager.TYPE_ETHERNET} 510 * 511 * {@see ConnectivityManager} 512 */ isNetworkTypeConnected(Context context, int... types)513 public boolean isNetworkTypeConnected(Context context, int... types) { 514 final NetworkInfo networkInfo = getActiveNetworkInfo(context); 515 if (networkInfo != null && networkInfo.isConnected()) { 516 final int activeNetworkType = networkInfo.getType(); 517 for (int type : types) { 518 if (activeNetworkType == type) { 519 return true; 520 } 521 } 522 } 523 return false; 524 } 525 526 /** 527 * Returns the active network info of the device. 528 */ getActiveNetworkInfo(Context context)529 public NetworkInfo getActiveNetworkInfo(Context context) { 530 ConnectivityManager cm = 531 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 532 return cm.getActiveNetworkInfo(); 533 } 534 535 /** 536 * Returns whether encryption is required on this device. 537 * 538 * <p>Encryption is required if the device is not currently encrypted and the persistent 539 * system flag {@code persist.sys.no_req_encrypt} is not set. 540 */ isEncryptionRequired()541 public boolean isEncryptionRequired() { 542 return !isPhysicalDeviceEncrypted() 543 && !SystemProperties.getBoolean("persist.sys.no_req_encrypt", false); 544 } 545 546 /** 547 * Returns whether the device is currently encrypted. 548 */ isPhysicalDeviceEncrypted()549 public boolean isPhysicalDeviceEncrypted() { 550 return StorageManager.isEncrypted(); 551 } 552 553 /** 554 * Returns the wifi pick intent. 555 */ 556 // TODO: Move this intent into a Globals class. getWifiPickIntent()557 public Intent getWifiPickIntent() { 558 Intent wifiIntent = new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK); 559 wifiIntent.putExtra("extra_prefs_show_button_bar", true); 560 wifiIntent.putExtra("wifi_enable_next_on_connect", true); 561 return wifiIntent; 562 } 563 564 /** 565 * Returns whether the device is in headless system user mode. 566 */ isHeadlessSystemUserMode()567 public boolean isHeadlessSystemUserMode() { 568 return UserManager.isHeadlessSystemUserMode(); 569 } 570 571 /** 572 * Returns whether the currently chosen launcher supports managed profiles. 573 * 574 * <p>A launcher is deemed to support managed profiles when its target API version is at least 575 * {@link Build.VERSION_CODES#LOLLIPOP}. 576 */ currentLauncherSupportsManagedProfiles(Context context)577 public boolean currentLauncherSupportsManagedProfiles(Context context) { 578 Intent intent = new Intent(Intent.ACTION_MAIN); 579 intent.addCategory(Intent.CATEGORY_HOME); 580 581 PackageManager pm = context.getPackageManager(); 582 ResolveInfo launcherResolveInfo = pm.resolveActivity(intent, 583 PackageManager.MATCH_DEFAULT_ONLY); 584 if (launcherResolveInfo == null) { 585 return false; 586 } 587 try { 588 // If the user has not chosen a default launcher, then launcherResolveInfo will be 589 // referring to the resolver activity. It is fine to create a managed profile in 590 // this case since there will always be at least one launcher on the device that 591 // supports managed profile feature. 592 ApplicationInfo launcherAppInfo = pm.getApplicationInfo( 593 launcherResolveInfo.activityInfo.packageName, 0 /* default flags */); 594 return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion); 595 } catch (PackageManager.NameNotFoundException e) { 596 return false; 597 } 598 } 599 600 /** 601 * Returns whether the given version number is at least lollipop. 602 * 603 * @param versionNumber the version number to be verified. 604 */ versionNumberAtLeastL(int versionNumber)605 private boolean versionNumberAtLeastL(int versionNumber) { 606 return versionNumber >= Build.VERSION_CODES.LOLLIPOP; 607 } 608 609 /** 610 * Computes the sha 256 hash of a byte array. 611 */ 612 @Nullable computeHashOfByteArray(byte[] bytes)613 public byte[] computeHashOfByteArray(byte[] bytes) { 614 try { 615 MessageDigest md = MessageDigest.getInstance(SHA256_TYPE); 616 md.update(bytes); 617 return md.digest(); 618 } catch (NoSuchAlgorithmException e) { 619 ProvisionLogger.loge("Hashing algorithm " + SHA256_TYPE + " not supported.", e); 620 return null; 621 } 622 } 623 624 /** 625 * Computes a hash of a file with a spcific hash algorithm. 626 */ 627 // TODO: Add unit tests 628 @Nullable computeHashOfFile(String fileLocation, String hashType)629 public byte[] computeHashOfFile(String fileLocation, String hashType) { 630 InputStream fis = null; 631 MessageDigest md; 632 byte hash[] = null; 633 try { 634 md = MessageDigest.getInstance(hashType); 635 } catch (NoSuchAlgorithmException e) { 636 ProvisionLogger.loge("Hashing algorithm " + hashType + " not supported.", e); 637 return null; 638 } 639 try { 640 fis = new FileInputStream(fileLocation); 641 642 byte[] buffer = new byte[256]; 643 int n = 0; 644 while (n != -1) { 645 n = fis.read(buffer); 646 if (n > 0) { 647 md.update(buffer, 0, n); 648 } 649 } 650 hash = md.digest(); 651 } catch (IOException e) { 652 ProvisionLogger.loge("IO error.", e); 653 } finally { 654 // Close input stream quietly. 655 try { 656 if (fis != null) { 657 fis.close(); 658 } 659 } catch (IOException e) { 660 // Ignore. 661 } 662 } 663 return hash; 664 } 665 666 /** 667 * Returns whether given intent can be resolved for the user. 668 */ canResolveIntentAsUser(Context context, Intent intent, int userId)669 public boolean canResolveIntentAsUser(Context context, Intent intent, int userId) { 670 return intent != null 671 && context.getPackageManager().resolveActivityAsUser(intent, 0, userId) != null; 672 } 673 isPackageDeviceOwner(DevicePolicyManager dpm, String packageName)674 public boolean isPackageDeviceOwner(DevicePolicyManager dpm, String packageName) { 675 final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnCallingUser(); 676 return deviceOwner != null && deviceOwner.getPackageName().equals(packageName); 677 } 678 getAccentColor(Context context)679 public int getAccentColor(Context context) { 680 return getAttrColor(context, android.R.attr.colorAccent); 681 } 682 683 /** 684 * Returns the theme's background color. 685 */ getBackgroundColor(Context context)686 public int getBackgroundColor(Context context) { 687 return getAttrColor(context, android.R.attr.colorBackground); 688 } 689 690 /** 691 * Returns the theme's text primary color. 692 */ getTextPrimaryColor(Context context)693 public int getTextPrimaryColor(Context context) { 694 return getAttrColor(context, android.R.attr.textColorPrimary); 695 } 696 697 /** 698 * Returns the theme's text secondary color. 699 */ getTextSecondaryColor(Context context)700 public int getTextSecondaryColor(Context context) { 701 return getAttrColor(context, android.R.attr.textColorSecondary); 702 } 703 getAttrColor(Context context, int attr)704 private int getAttrColor(Context context, int attr) { 705 TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); 706 int attrColor = ta.getColor(0, 0); 707 ta.recycle(); 708 return attrColor; 709 } 710 handleSupportUrl(Context context, CustomizationParams customizationParams, AccessibilityContextMenuMaker contextMenuMaker, TextView textView, String deviceProvider, String contactDeviceProvider, Consumer<Intent> clickHandler)711 public void handleSupportUrl(Context context, CustomizationParams customizationParams, 712 AccessibilityContextMenuMaker contextMenuMaker, TextView textView, 713 String deviceProvider, String contactDeviceProvider, 714 Consumer<Intent> clickHandler) { 715 if (customizationParams.supportUrl == null) { 716 textView.setText(contactDeviceProvider); 717 return; 718 } 719 final Intent intent = WebActivity.createIntent( 720 context, customizationParams.supportUrl); 721 722 final ClickableSpanFactory spanFactory = 723 new ClickableSpanFactory(getAccentColor(context), clickHandler); 724 handlePartialClickableTextView( 725 textView, contactDeviceProvider, deviceProvider, intent, spanFactory); 726 727 contextMenuMaker.registerWithActivity(textView); 728 } 729 730 /** 731 * Utility function to make a TextView partial clickable. It also associates the TextView with 732 * an Intent. The intent will be triggered when the clickable part is clicked. 733 * 734 * @param textView The TextView which hosts the clickable string. 735 * @param content The content of the TextView. 736 * @param clickableString The substring which is clickable. 737 * @param intent The Intent that will be launched. 738 * @param clickableSpanFactory The factory which is used to create ClickableSpan to decorate 739 * clickable string. 740 */ handlePartialClickableTextView(TextView textView, String content, String clickableString, Intent intent, ClickableSpanFactory clickableSpanFactory)741 public void handlePartialClickableTextView(TextView textView, String content, 742 String clickableString, Intent intent, ClickableSpanFactory clickableSpanFactory) { 743 final SpannableString spannableString = new SpannableString(content); 744 if (intent != null) { 745 final ClickableSpan span = clickableSpanFactory.create(intent); 746 final int startIdx = content.indexOf(clickableString); 747 final int endIdx = startIdx + clickableString.length(); 748 749 spannableString.setSpan(span, startIdx, endIdx, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 750 textView.setMovementMethod(LinkMovementMethod.getInstance()); 751 } 752 753 textView.setText(spannableString); 754 } 755 756 /** 757 * Gets the device's current device owner admin component. 758 */ 759 @Nullable getCurrentDeviceOwnerComponentName(DevicePolicyManager dpm)760 public ComponentName getCurrentDeviceOwnerComponentName(DevicePolicyManager dpm) { 761 return isHeadlessSystemUserMode() 762 ? dpm.getDeviceOwnerComponentOnAnyUser() 763 : dpm.getDeviceOwnerComponentOnCallingUser(); 764 } 765 addNextButton(GlifLayout layout, @NonNull OnClickListener listener)766 public static FooterButton addNextButton(GlifLayout layout, @NonNull OnClickListener listener) { 767 return setPrimaryButton(layout, listener, ButtonType.NEXT, R.string.next); 768 } 769 770 /** 771 * Adds an encryption primary button mixin to a {@link GlifLayout} screen. 772 */ addEncryptButton( GlifLayout layout, @NonNull OnClickListener listener)773 public static FooterButton addEncryptButton( 774 GlifLayout layout, @NonNull OnClickListener listener) { 775 return setPrimaryButton(layout, listener, ButtonType.NEXT, R.string.encrypt); 776 } 777 addAcceptAndContinueButton(GlifLayout layout, @NonNull OnClickListener listener)778 public static FooterButton addAcceptAndContinueButton(GlifLayout layout, 779 @NonNull OnClickListener listener) { 780 return setPrimaryButton(layout, listener, ButtonType.NEXT, R.string.accept_and_continue); 781 } 782 783 /** Adds a primary "Cancel setup" button */ addResetButton(GlifLayout layout, @NonNull OnClickListener listener)784 public static FooterButton addResetButton(GlifLayout layout, 785 @NonNull OnClickListener listener) { 786 return setPrimaryButton(layout, listener, ButtonType.CANCEL, 787 R.string.fully_managed_device_reset_and_return_button); 788 } 789 setPrimaryButton(GlifLayout layout, OnClickListener listener, @ButtonType int buttonType, @StringRes int label)790 private static FooterButton setPrimaryButton(GlifLayout layout, OnClickListener listener, 791 @ButtonType int buttonType, @StringRes int label) { 792 final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class); 793 final FooterButton primaryButton = new FooterButton.Builder(layout.getContext()) 794 .setText(label) 795 .setListener(listener) 796 .setButtonType(buttonType) 797 .setTheme(R.style.SudGlifButton_Primary) 798 .build(); 799 mixin.setPrimaryButton(primaryButton); 800 return primaryButton; 801 } 802 803 /** Adds a secondary "abort & reset" button. */ addAbortAndResetButton(GlifLayout layout, @NonNull OnClickListener listener)804 public static FooterButton addAbortAndResetButton(GlifLayout layout, 805 @NonNull OnClickListener listener) { 806 final int buttonType = ButtonType.CANCEL; 807 final int buttonLabel = R.string.fully_managed_device_cancel_setup_button; 808 809 return addSecondaryButton(layout, listener, buttonType, buttonLabel); 810 } 811 addSecondaryButton(GlifLayout layout, @NonNull OnClickListener listener, @ButtonType int buttonType, @StringRes int buttonLabel)812 private static FooterButton addSecondaryButton(GlifLayout layout, 813 @NonNull OnClickListener listener, 814 @ButtonType int buttonType, @StringRes int buttonLabel) { 815 final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class); 816 final FooterButton secondaryButton = new FooterButton.Builder(layout.getContext()) 817 .setText(buttonLabel) 818 .setListener(listener) 819 .setButtonType(buttonType) 820 .setTheme(R.style.SudGlifButton_Secondary) 821 .build(); 822 mixin.setSecondaryButton(secondaryButton); 823 return secondaryButton; 824 } 825 createCancelProvisioningResetDialogBuilder()826 public SimpleDialog.Builder createCancelProvisioningResetDialogBuilder() { 827 final int positiveResId = R.string.reset; 828 final int negativeResId = R.string.device_owner_cancel_cancel; 829 final int dialogMsgResId = R.string.this_will_reset_take_back_first_screen; 830 return getBaseDialogBuilder(positiveResId, negativeResId, dialogMsgResId) 831 .setTitle(R.string.stop_setup_reset_device_question); 832 } 833 createCancelProvisioningDialogBuilder()834 public SimpleDialog.Builder createCancelProvisioningDialogBuilder() { 835 final int positiveResId = R.string.profile_owner_cancel_ok; 836 final int negativeResId = R.string.profile_owner_cancel_cancel; 837 final int dialogMsgResId = R.string.profile_owner_cancel_message; 838 return getBaseDialogBuilder(positiveResId, negativeResId, dialogMsgResId); 839 } 840 shouldShowOwnershipDisclaimerScreen(ProvisioningParams params)841 public boolean shouldShowOwnershipDisclaimerScreen(ProvisioningParams params) { 842 return !params.skipOwnershipDisclaimer; 843 } 844 isOrganizationOwnedAllowed(ProvisioningParams params)845 public boolean isOrganizationOwnedAllowed(ProvisioningParams params) { 846 int provisioningModes = params.initiatorRequestedProvisioningModes; 847 return containsBinaryFlags(provisioningModes, FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED) 848 || containsBinaryFlags(provisioningModes, FLAG_SUPPORTED_MODES_DEVICE_OWNER); 849 } 850 isManagedProfileProvisioningStartedByDpc( Context context, ProvisioningParams params, SettingsFacade settingsFacade)851 public boolean isManagedProfileProvisioningStartedByDpc( 852 Context context, 853 ProvisioningParams params, 854 SettingsFacade settingsFacade) { 855 if (!ACTION_PROVISION_MANAGED_PROFILE.equals(params.provisioningAction)) { 856 return false; 857 } 858 if (params.startedByTrustedSource) { 859 return false; 860 } 861 return settingsFacade.isUserSetupCompleted(context); 862 } 863 864 /** 865 * Returns {@code true} if {@code packageName} is installed on the primary user. 866 */ isPackageInstalled(String packageName, PackageManager packageManager)867 public boolean isPackageInstalled(String packageName, PackageManager packageManager) { 868 try { 869 final ApplicationInfo ai = packageManager.getApplicationInfo(packageName, 870 PackageManager.MATCH_DIRECT_BOOT_AWARE 871 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); 872 return ai != null; 873 } catch (PackageManager.NameNotFoundException e) { 874 return false; 875 } 876 } 877 getBaseDialogBuilder( int positiveResId, int negativeResId, int dialogMsgResId)878 private SimpleDialog.Builder getBaseDialogBuilder( 879 int positiveResId, int negativeResId, int dialogMsgResId) { 880 return new SimpleDialog.Builder() 881 .setCancelable(false) 882 .setMessage(dialogMsgResId) 883 .setNegativeButtonMessage(negativeResId) 884 .setPositiveButtonMessage(positiveResId); 885 } 886 887 /** 888 * Returns {@code true} if {@code value} contains the {@code flags} binary flags. 889 */ containsBinaryFlags(int value, int flags)890 public boolean containsBinaryFlags(int value, int flags) { 891 return (value & flags) == flags; 892 } 893 894 /** 895 * Calls {@code callback} when {@code view} has been measured. 896 */ onViewMeasured(View view, Consumer<View> callback)897 public void onViewMeasured(View view, Consumer<View> callback) { 898 view.getViewTreeObserver().addOnGlobalLayoutListener( 899 new ViewTreeObserver.OnGlobalLayoutListener() { 900 @Override 901 public void onGlobalLayout() { 902 view.getViewTreeObserver().removeOnGlobalLayoutListener(this); 903 callback.accept(view); 904 } 905 }); 906 } 907 } 908