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