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 27 import static java.util.Objects.requireNonNull; 28 29 import android.annotation.NonNull; 30 import android.annotation.Nullable; 31 import android.annotation.StringRes; 32 import android.app.admin.DevicePolicyManager; 33 import android.content.ComponentName; 34 import android.content.Context; 35 import android.content.Intent; 36 import android.content.pm.ActivityInfo; 37 import android.content.pm.ApplicationInfo; 38 import android.content.pm.IPackageManager; 39 import android.content.pm.PackageInfo; 40 import android.content.pm.PackageManager; 41 import android.content.pm.PackageManager.NameNotFoundException; 42 import android.content.pm.ResolveInfo; 43 import android.content.res.TypedArray; 44 import android.net.ConnectivityManager; 45 import android.net.NetworkCapabilities; 46 import android.net.NetworkInfo; 47 import android.net.wifi.WifiManager; 48 import android.os.Build; 49 import android.os.RemoteException; 50 import android.os.ServiceManager; 51 import android.os.SystemProperties; 52 import android.os.UserHandle; 53 import android.os.UserManager; 54 import android.os.storage.StorageManager; 55 import android.text.SpannableString; 56 import android.text.Spanned; 57 import android.text.TextUtils; 58 import android.text.method.LinkMovementMethod; 59 import android.text.style.ClickableSpan; 60 import android.view.View; 61 import android.view.View.OnClickListener; 62 import android.view.ViewTreeObserver; 63 import android.widget.TextView; 64 65 import com.android.internal.annotations.VisibleForTesting; 66 import com.android.managedprovisioning.R; 67 import com.android.managedprovisioning.model.CustomizationParams; 68 import com.android.managedprovisioning.model.PackageDownloadInfo; 69 import com.android.managedprovisioning.model.ProvisioningParams; 70 import com.android.managedprovisioning.preprovisioning.WebActivity; 71 import com.android.managedprovisioning.util.LazyStringResource; 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 import com.google.android.setupdesign.util.DeviceHelper; 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, "managedprovisioning"); 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 194 @VisibleForTesting findDeviceAdmin(String dpcPackageName, ComponentName dpcComponentName, Context context, int userId)195 public ComponentName findDeviceAdmin(String dpcPackageName, ComponentName dpcComponentName, 196 Context context, int userId) throws IllegalProvisioningArgumentException { 197 if (dpcComponentName != null) { 198 dpcPackageName = dpcComponentName.getPackageName(); 199 } 200 if (dpcPackageName == null) { 201 throw new IllegalProvisioningArgumentException("Neither the package name nor the" 202 + " component name of the admin are supplied"); 203 } 204 PackageInfo pi; 205 try { 206 pi = context.getPackageManager().getPackageInfoAsUser(dpcPackageName, 207 PackageManager.GET_RECEIVERS | PackageManager.MATCH_DISABLED_COMPONENTS, 208 userId); 209 } catch (NameNotFoundException e) { 210 throw new IllegalProvisioningArgumentException("Dpc " + dpcPackageName 211 + " is not installed. ", e); 212 } 213 214 final ComponentName componentName = findDeviceAdminInPackageInfo(dpcPackageName, 215 dpcComponentName, pi); 216 if (componentName == null) { 217 throw new IllegalProvisioningArgumentException("Cannot find any admin receiver in " 218 + "package " + dpcPackageName + " with component " + dpcComponentName); 219 } 220 return componentName; 221 } 222 223 /** 224 * If dpcComponentName is not null: dpcPackageName is ignored. 225 * Check that the package of dpcComponentName is installed, that dpcComponentName is a 226 * receiver in this package, and return it. The receiver can be in disabled state. 227 * 228 * Otherwise, try to infer a potential device admin component in this package info. 229 * 230 * @return infered device admin component in package info. Otherwise, null 231 */ 232 @Nullable findDeviceAdminInPackageInfo(@onNull String dpcPackageName, @Nullable ComponentName dpcComponentName, @NonNull PackageInfo pi)233 public ComponentName findDeviceAdminInPackageInfo(@NonNull String dpcPackageName, 234 @Nullable ComponentName dpcComponentName, @NonNull PackageInfo pi) { 235 if (dpcComponentName != null) { 236 if (!isComponentInPackageInfo(dpcComponentName, pi)) { 237 ProvisionLogger.logw("The component " + dpcComponentName + " isn't registered in " 238 + "the apk"); 239 return null; 240 } 241 return dpcComponentName; 242 } else { 243 return findDeviceAdminInPackage(dpcPackageName, pi); 244 } 245 } 246 247 /** 248 * Finds a device admin in a given {@link PackageInfo} object. 249 * 250 * <p>This function returns {@code null} if no or multiple admin receivers were found, and if 251 * the package name does not match dpcPackageName.</p> 252 * @param packageName packge name that should match the {@link PackageInfo} object. 253 * @param packageInfo package info to be examined. 254 * @return admin receiver or null in case of error. 255 */ 256 @Nullable findDeviceAdminInPackage(String packageName, PackageInfo packageInfo)257 private ComponentName findDeviceAdminInPackage(String packageName, PackageInfo packageInfo) { 258 if (packageInfo == null || !TextUtils.equals(packageInfo.packageName, packageName)) { 259 return null; 260 } 261 262 ComponentName mdmComponentName = null; 263 for (ActivityInfo ai : packageInfo.receivers) { 264 if (TextUtils.equals(ai.permission, android.Manifest.permission.BIND_DEVICE_ADMIN)) { 265 if (mdmComponentName != null) { 266 ProvisionLogger.logw("more than 1 device admin component are found"); 267 return null; 268 } else { 269 mdmComponentName = new ComponentName(packageName, ai.name); 270 } 271 } 272 } 273 return mdmComponentName; 274 } 275 isComponentInPackageInfo(ComponentName dpcComponentName, PackageInfo pi)276 private boolean isComponentInPackageInfo(ComponentName dpcComponentName, 277 PackageInfo pi) { 278 for (ActivityInfo ai : pi.receivers) { 279 if (dpcComponentName.getClassName().equals(ai.name)) { 280 return true; 281 } 282 } 283 return false; 284 } 285 286 /** 287 * Return if a given package has testOnly="true", in which case we'll relax certain rules 288 * for CTS. 289 * 290 * The system allows this flag to be changed when an app is updated. But 291 * {@link DevicePolicyManager} uses the persisted version to do actual checks for relevant 292 * dpm command. 293 * 294 * @see DevicePolicyManagerService#isPackageTestOnly for more info 295 */ isPackageTestOnly(PackageManager pm, String packageName, int userHandle)296 public static boolean isPackageTestOnly(PackageManager pm, String packageName, int userHandle) { 297 if (TextUtils.isEmpty(packageName)) { 298 return false; 299 } 300 301 try { 302 final ApplicationInfo ai = pm.getApplicationInfoAsUser(packageName, 303 PackageManager.MATCH_DIRECT_BOOT_AWARE 304 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle); 305 return ai != null && (ai.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0; 306 } catch (PackageManager.NameNotFoundException e) { 307 return false; 308 } 309 310 } 311 312 /** 313 * Returns whether the current user is the system user. 314 */ isCurrentUserSystem()315 public boolean isCurrentUserSystem() { 316 return UserHandle.myUserId() == UserHandle.USER_SYSTEM; 317 } 318 319 /** 320 * Returns whether the device is currently managed. 321 */ isDeviceManaged(Context context)322 public boolean isDeviceManaged(Context context) { 323 DevicePolicyManager dpm = 324 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); 325 return dpm.isDeviceManaged(); 326 } 327 328 /** 329 * Returns true if the given package requires an update. 330 * 331 * <p>There are two cases where an update is required: 332 * 1. The package is not currently present on the device. 333 * 2. The package is present, but the version is below the minimum supported version. 334 * 335 * @param packageName the package to be checked for updates 336 * @param minSupportedVersion the minimum supported version 337 * @param context a {@link Context} object 338 */ packageRequiresUpdate(String packageName, int minSupportedVersion, Context context)339 public boolean packageRequiresUpdate(String packageName, int minSupportedVersion, 340 Context context) { 341 try { 342 PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0); 343 // Always download packages if no minimum version given. 344 if (minSupportedVersion != PackageDownloadInfo.DEFAULT_MINIMUM_VERSION 345 && packageInfo.versionCode >= minSupportedVersion) { 346 return false; 347 } 348 } catch (NameNotFoundException e) { 349 // Package not on device. 350 } 351 352 return true; 353 } 354 355 /** 356 * Returns the first existing managed profile if any present, null otherwise. 357 * 358 * <p>Note that we currently only support one managed profile per device. 359 */ 360 // TODO: Add unit tests 361 @Nullable getManagedProfile(Context context)362 public UserHandle getManagedProfile(Context context) { 363 DevicePolicyManager devicePolicyManager = 364 requireNonNull( 365 /* obj= */ context.getSystemService(DevicePolicyManager.class), 366 /* message= */ "Unable to obtain DevicePolicyManager"); 367 int currentUserId = UserHandle.myUserId(); 368 List<UserHandle> managedProfiles = 369 devicePolicyManager.getPolicyManagedProfiles(UserHandle.of(currentUserId)); 370 if (managedProfiles.isEmpty()) { 371 return null; 372 } 373 return managedProfiles.get(0); 374 } 375 376 /** 377 * Returns whether FRP is supported on the device. 378 */ isFrpSupported(Context context)379 public boolean isFrpSupported(Context context) { 380 Object pdbManager = context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE); 381 return pdbManager != null; 382 } 383 384 385 /** 386 * Returns {@code true} if the admin-integrated flow should be performed. 387 * 388 * <p>This method must not be called before the admin app has been installed. If it has not 389 * yet been installed, consider using {@link 390 * #checkAdminIntegratedFlowPreconditions(ProvisioningParams)}. 391 * 392 * <p>To perform the admin-integrated flow, all of the following criteria must be fulfilled: 393 * <ul> 394 * <li>All of the preconditions in {@link 395 * #checkAdminIntegratedFlowPreconditions(ProvisioningParams)}</li> 396 * <li>The DPC has an activity with intent filter with action {@link 397 * DevicePolicyManager#ACTION_GET_PROVISIONING_MODE}</li> 398 * <li>The DPC has an activity with intent filter with action {@link 399 * DevicePolicyManager#ACTION_ADMIN_POLICY_COMPLIANCE}</li> 400 * </ul> 401 */ canPerformAdminIntegratedFlow(Context context, ProvisioningParams params, PolicyComplianceUtils policyComplianceUtils, GetProvisioningModeUtils provisioningModeUtils)402 public boolean canPerformAdminIntegratedFlow(Context context, ProvisioningParams params, 403 PolicyComplianceUtils policyComplianceUtils, 404 GetProvisioningModeUtils provisioningModeUtils) { 405 if (!checkAdminIntegratedFlowPreconditions(params)) { 406 return false; 407 } 408 boolean isPolicyComplianceScreenAvailable = 409 policyComplianceUtils.isPolicyComplianceActivityResolvableForUser(context, params, 410 this, UserHandle.SYSTEM); 411 if (!isPolicyComplianceScreenAvailable) { 412 ProvisionLogger.logi("Policy compliance DPC screen not available."); 413 return false; 414 } 415 boolean isGetProvisioningModeScreenAvailable = 416 provisioningModeUtils.isGetProvisioningModeActivityResolvable(context, params); 417 if (!isGetProvisioningModeScreenAvailable) { 418 ProvisionLogger.logi("Get provisioning mode DPC screen not available."); 419 return false; 420 } 421 return true; 422 } 423 424 /** 425 * Returns {@code true} if the admin-integrated flow preconditions are met. 426 * 427 * <p>This method can be called before the admin app has been installed. Returning {@code true} 428 * does not mean the admin-integrated flow should be performed (for that, use {@link 429 * #canPerformAdminIntegratedFlow(Context, ProvisioningParams, PolicyComplianceUtils, 430 * GetProvisioningModeUtils)}), but returning {@code false} can be used as an early indication 431 * that it should <i>not</i> be performed. 432 * 433 * <p>The preconditions are: 434 * <ul> 435 * <li>Provisioning was started using {@link 436 * DevicePolicyManager#ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}</li> 437 * <li>The provisioning is not triggered by NFC</li> 438 * <li>This is not a financed device provisioning</li> 439 * </ul> 440 */ checkAdminIntegratedFlowPreconditions(ProvisioningParams params)441 public boolean checkAdminIntegratedFlowPreconditions(ProvisioningParams params) { 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).wipeDevice(/* flags=*/ 0); 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 .map(connectivityManager::getNetworkCapabilities) 496 .filter(Objects::nonNull) 497 .anyMatch(this::isNetworkConnectedToInternetViaCellular); 498 } 499 500 /** 501 * Returns whether the device is currently connected to specific network type, such as 502 * {@link ConnectivityManager#TYPE_WIFI} or {@link ConnectivityManager#TYPE_ETHERNET} 503 * 504 * {@see ConnectivityManager} 505 * 506 * @deprecated use one of 507 * {@link #isNetworkConnectedToInternetViaEthernet(NetworkCapabilities)}, 508 * {@link #isNetworkConnectedToInternetViaWiFi(NetworkCapabilities)} 509 * {@link #isNetworkConnectedToInternetViaCellular(NetworkCapabilities)} 510 */ 511 @Deprecated isNetworkTypeConnected(Context context, int... types)512 public boolean isNetworkTypeConnected(Context context, int... types) { 513 final NetworkInfo networkInfo = getActiveNetworkInfo(context); 514 if (networkInfo != null && networkInfo.isConnected()) { 515 final int activeNetworkType = networkInfo.getType(); 516 for (int type : types) { 517 if (activeNetworkType == type) { 518 return true; 519 } 520 } 521 } 522 return false; 523 } 524 525 /** 526 * Checks if the network is active (can receive and send data) 527 */ isNetworkActive(NetworkCapabilities network)528 public boolean isNetworkActive(NetworkCapabilities network) { 529 return network.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) 530 && network.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); 531 } 532 533 /** 534 * Checks if the network has transport {@link NetworkCapabilities#TRANSPORT_ETHERNET} and 535 * {@link #isNetworkActive} 536 */ isNetworkConnectedToInternetViaEthernet(NetworkCapabilities network)537 public boolean isNetworkConnectedToInternetViaEthernet(NetworkCapabilities network) { 538 return network.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) 539 && isNetworkActive(network); 540 } 541 542 /** 543 * Checks if the network has transport {@link NetworkCapabilities#TRANSPORT_WIFI} and 544 * {@link #isNetworkActive} 545 */ isNetworkConnectedToInternetViaWiFi(NetworkCapabilities network)546 public boolean isNetworkConnectedToInternetViaWiFi(NetworkCapabilities network) { 547 return network.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) 548 && isNetworkActive(network); 549 } 550 551 /** 552 * Checks if the network has transport {@link NetworkCapabilities#TRANSPORT_CELLULAR} and 553 * {@link #isNetworkActive} 554 */ isNetworkConnectedToInternetViaCellular(NetworkCapabilities network)555 public boolean isNetworkConnectedToInternetViaCellular(NetworkCapabilities network) { 556 return network.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) 557 && isNetworkActive(network); 558 } 559 560 /** 561 * Returns the active network info of the device. 562 * 563 * @deprecated use {@link #getActiveNetworkCapabilities(Context)} 564 */ 565 @Deprecated getActiveNetworkInfo(Context context)566 public NetworkInfo getActiveNetworkInfo(Context context) { 567 ConnectivityManager cm = 568 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 569 return cm.getActiveNetworkInfo(); 570 } 571 572 /** 573 * Retrieves {@link NetworkCapabilities} of the currently active network 574 * or `null` if there is no active network 575 */ 576 @Nullable getActiveNetworkCapabilities(Context context)577 public NetworkCapabilities getActiveNetworkCapabilities(Context context) { 578 var cn = requireNonNull(context.getSystemService(ConnectivityManager.class), 579 "Unable to obtain ConnectivityManager"); 580 return cn.getNetworkCapabilities(cn.getActiveNetwork()); 581 } 582 583 /** 584 * Returns whether encryption is required on this device. 585 * 586 * <p>Encryption is required if the device is not currently encrypted and the persistent 587 * system flag {@code persist.sys.no_req_encrypt} is not set. 588 */ isEncryptionRequired()589 public boolean isEncryptionRequired() { 590 return !isPhysicalDeviceEncrypted() 591 && !SystemProperties.getBoolean("persist.sys.no_req_encrypt", false); 592 } 593 594 /** 595 * Returns whether the device is currently encrypted. 596 */ isPhysicalDeviceEncrypted()597 public boolean isPhysicalDeviceEncrypted() { 598 return StorageManager.isEncrypted(); 599 } 600 601 /** 602 * Returns the wifi pick intent. 603 */ 604 // TODO: Move this intent into a Globals class. getWifiPickIntent()605 public Intent getWifiPickIntent() { 606 Intent wifiIntent = new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK); 607 wifiIntent.putExtra("extra_prefs_show_button_bar", true); 608 wifiIntent.putExtra("wifi_enable_next_on_connect", true); 609 return wifiIntent; 610 } 611 612 /** 613 * Returns whether the device is in headless system user mode. 614 */ isHeadlessSystemUserMode()615 public boolean isHeadlessSystemUserMode() { 616 return UserManager.isHeadlessSystemUserMode(); 617 } 618 619 /** 620 * Returns whether the currently chosen launcher supports managed profiles. 621 * 622 * <p>A launcher is deemed to support managed profiles when its target API version is at least 623 * {@link Build.VERSION_CODES#LOLLIPOP}. 624 */ currentLauncherSupportsManagedProfiles(Context context)625 public boolean currentLauncherSupportsManagedProfiles(Context context) { 626 Intent intent = new Intent(Intent.ACTION_MAIN); 627 intent.addCategory(Intent.CATEGORY_HOME); 628 629 PackageManager pm = context.getPackageManager(); 630 ResolveInfo launcherResolveInfo = pm.resolveActivity(intent, 631 PackageManager.MATCH_DEFAULT_ONLY); 632 if (launcherResolveInfo == null) { 633 return false; 634 } 635 try { 636 // If the user has not chosen a default launcher, then launcherResolveInfo will be 637 // referring to the resolver activity. It is fine to create a managed profile in 638 // this case since there will always be at least one launcher on the device that 639 // supports managed profile feature. 640 ApplicationInfo launcherAppInfo = pm.getApplicationInfo( 641 launcherResolveInfo.activityInfo.packageName, 0 /* default flags */); 642 return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion); 643 } catch (PackageManager.NameNotFoundException e) { 644 return false; 645 } 646 } 647 648 /** 649 * Returns whether the given version number is at least lollipop. 650 * 651 * @param versionNumber the version number to be verified. 652 */ versionNumberAtLeastL(int versionNumber)653 private boolean versionNumberAtLeastL(int versionNumber) { 654 return versionNumber >= Build.VERSION_CODES.LOLLIPOP; 655 } 656 657 /** 658 * Computes the sha 256 hash of a byte array. 659 */ 660 @Nullable computeHashOfByteArray(byte[] bytes)661 public byte[] computeHashOfByteArray(byte[] bytes) { 662 try { 663 MessageDigest md = MessageDigest.getInstance(SHA256_TYPE); 664 md.update(bytes); 665 return md.digest(); 666 } catch (NoSuchAlgorithmException e) { 667 ProvisionLogger.loge("Hashing algorithm " + SHA256_TYPE + " not supported.", e); 668 return null; 669 } 670 } 671 672 /** 673 * Computes a hash of a file with a spcific hash algorithm. 674 */ 675 // TODO: Add unit tests 676 @Nullable computeHashOfFile(String fileLocation, String hashType)677 public byte[] computeHashOfFile(String fileLocation, String hashType) { 678 InputStream fis = null; 679 MessageDigest md; 680 byte[] hash = null; 681 try { 682 md = MessageDigest.getInstance(hashType); 683 } catch (NoSuchAlgorithmException e) { 684 ProvisionLogger.loge("Hashing algorithm " + hashType + " not supported.", e); 685 return null; 686 } 687 try { 688 fis = new FileInputStream(fileLocation); 689 690 byte[] buffer = new byte[256]; 691 int n = 0; 692 while (n != -1) { 693 n = fis.read(buffer); 694 if (n > 0) { 695 md.update(buffer, 0, n); 696 } 697 } 698 hash = md.digest(); 699 } catch (IOException e) { 700 ProvisionLogger.loge("IO error.", e); 701 } finally { 702 // Close input stream quietly. 703 try { 704 if (fis != null) { 705 fis.close(); 706 } 707 } catch (IOException e) { 708 // Ignore. 709 } 710 } 711 return hash; 712 } 713 714 /** 715 * Returns whether given intent can be resolved for the user. 716 */ canResolveIntentAsUser(Context context, Intent intent, int userId)717 public boolean canResolveIntentAsUser(Context context, Intent intent, int userId) { 718 return intent != null 719 && context.getPackageManager().resolveActivityAsUser(intent, 0, userId) != null; 720 } 721 isPackageDeviceOwner(DevicePolicyManager dpm, String packageName)722 public boolean isPackageDeviceOwner(DevicePolicyManager dpm, String packageName) { 723 final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnCallingUser(); 724 return deviceOwner != null && deviceOwner.getPackageName().equals(packageName); 725 } 726 getAccentColor(Context context)727 public int getAccentColor(Context context) { 728 return getAttrColor(context, android.R.attr.colorAccent); 729 } 730 731 /** 732 * Returns the theme's background color. 733 */ getBackgroundColor(Context context)734 public int getBackgroundColor(Context context) { 735 return getAttrColor(context, android.R.attr.colorBackground); 736 } 737 738 /** 739 * Returns the theme's text primary color. 740 */ getTextPrimaryColor(Context context)741 public int getTextPrimaryColor(Context context) { 742 return getAttrColor(context, android.R.attr.textColorPrimary); 743 } 744 745 /** 746 * Returns the theme's text secondary color. 747 */ getTextSecondaryColor(Context context)748 public int getTextSecondaryColor(Context context) { 749 return getAttrColor(context, android.R.attr.textColorSecondary); 750 } 751 getAttrColor(Context context, int attr)752 private int getAttrColor(Context context, int attr) { 753 TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); 754 int attrColor = ta.getColor(0, 0); 755 ta.recycle(); 756 return attrColor; 757 } 758 handleSupportUrl(Context context, CustomizationParams customizationParams, AccessibilityContextMenuMaker contextMenuMaker, TextView textView, String deviceProvider, String contactDeviceProvider, Consumer<Intent> clickHandler)759 public void handleSupportUrl(Context context, CustomizationParams customizationParams, 760 AccessibilityContextMenuMaker contextMenuMaker, TextView textView, 761 String deviceProvider, String contactDeviceProvider, 762 Consumer<Intent> clickHandler) { 763 if (customizationParams.supportUrl == null) { 764 textView.setText(contactDeviceProvider); 765 return; 766 } 767 final Intent intent = WebActivity.createIntent( 768 context, customizationParams.supportUrl); 769 770 final ClickableSpanFactory spanFactory = 771 new ClickableSpanFactory(getAccentColor(context), clickHandler); 772 handlePartialClickableTextView( 773 textView, contactDeviceProvider, deviceProvider, intent, spanFactory); 774 775 contextMenuMaker.registerWithActivity(textView); 776 } 777 778 /** 779 * Utility function to make a TextView partial clickable. It also associates the TextView with 780 * an Intent. The intent will be triggered when the clickable part is clicked. 781 * 782 * @param textView The TextView which hosts the clickable string. 783 * @param content The content of the TextView. 784 * @param clickableString The substring which is clickable. 785 * @param intent The Intent that will be launched. 786 * @param clickableSpanFactory The factory which is used to create ClickableSpan to decorate 787 * clickable string. 788 */ handlePartialClickableTextView(TextView textView, String content, String clickableString, Intent intent, ClickableSpanFactory clickableSpanFactory)789 public void handlePartialClickableTextView(TextView textView, String content, 790 String clickableString, Intent intent, ClickableSpanFactory clickableSpanFactory) { 791 final SpannableString spannableString = new SpannableString(content); 792 if (intent != null) { 793 final ClickableSpan span = clickableSpanFactory.create(intent); 794 final int startIdx = content.indexOf(clickableString); 795 final int endIdx = startIdx + clickableString.length(); 796 797 spannableString.setSpan(span, startIdx, endIdx, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 798 textView.setMovementMethod(LinkMovementMethod.getInstance()); 799 } 800 801 textView.setText(spannableString); 802 } 803 804 /** 805 * Gets the device's current device owner admin component. 806 */ 807 @Nullable getCurrentDeviceOwnerComponentName(DevicePolicyManager dpm)808 public ComponentName getCurrentDeviceOwnerComponentName(DevicePolicyManager dpm) { 809 return isHeadlessSystemUserMode() 810 ? dpm.getDeviceOwnerComponentOnAnyUser() 811 : dpm.getDeviceOwnerComponentOnCallingUser(); 812 } 813 addNextButton(GlifLayout layout, @NonNull OnClickListener listener)814 public static FooterButton addNextButton(GlifLayout layout, @NonNull OnClickListener listener) { 815 return setPrimaryButton(layout, listener, ButtonType.NEXT, R.string.next); 816 } 817 818 /** 819 * Adds an encryption primary button mixin to a {@link GlifLayout} screen. 820 */ addEncryptButton( GlifLayout layout, @NonNull OnClickListener listener)821 public static FooterButton addEncryptButton( 822 GlifLayout layout, @NonNull OnClickListener listener) { 823 return setPrimaryButton(layout, listener, ButtonType.NEXT, R.string.encrypt); 824 } 825 addAcceptAndContinueButton(GlifLayout layout, @NonNull OnClickListener listener)826 public static FooterButton addAcceptAndContinueButton(GlifLayout layout, 827 @NonNull OnClickListener listener) { 828 return setPrimaryButton(layout, listener, ButtonType.NEXT, R.string.accept_and_continue); 829 } 830 831 /** Adds a primary "Cancel setup" button */ addResetButton(GlifLayout layout, @NonNull OnClickListener listener, @StringRes int resetButtonString)832 public static FooterButton addResetButton(GlifLayout layout, 833 @NonNull OnClickListener listener, @StringRes int resetButtonString) { 834 return setPrimaryButton(layout, listener, ButtonType.CANCEL, 835 resetButtonString); 836 } 837 setPrimaryButton(GlifLayout layout, OnClickListener listener, @ButtonType int buttonType, @StringRes int label)838 private static FooterButton setPrimaryButton(GlifLayout layout, OnClickListener listener, 839 @ButtonType int buttonType, @StringRes int label) { 840 final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class); 841 final FooterButton primaryButton = new FooterButton.Builder(layout.getContext()) 842 .setText(label) 843 .setListener(listener) 844 .setButtonType(buttonType) 845 .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary) 846 .build(); 847 mixin.setPrimaryButton(primaryButton); 848 return primaryButton; 849 } 850 851 /** Adds a secondary "abort & reset" button. */ addAbortAndResetButton(GlifLayout layout, @NonNull OnClickListener listener)852 public static FooterButton addAbortAndResetButton(GlifLayout layout, 853 @NonNull OnClickListener listener) { 854 final int buttonType = ButtonType.CANCEL; 855 final int buttonLabel = R.string.fully_managed_device_cancel_setup_button; 856 857 return addSecondaryButton(layout, listener, buttonType, buttonLabel); 858 } 859 addSecondaryButton(GlifLayout layout, @NonNull OnClickListener listener, @ButtonType int buttonType, @StringRes int buttonLabel)860 private static FooterButton addSecondaryButton(GlifLayout layout, 861 @NonNull OnClickListener listener, 862 @ButtonType int buttonType, @StringRes int buttonLabel) { 863 final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class); 864 final FooterButton secondaryButton = new FooterButton.Builder(layout.getContext()) 865 .setText(buttonLabel) 866 .setListener(listener) 867 .setButtonType(buttonType) 868 .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary) 869 .build(); 870 mixin.setSecondaryButton(secondaryButton); 871 return secondaryButton; 872 } 873 createCancelProvisioningResetDialogBuilder(Context context)874 public SimpleDialog.Builder createCancelProvisioningResetDialogBuilder(Context context) { 875 CharSequence deviceName = DeviceHelper.getDeviceName(context); 876 final int positiveResId = R.string.reset; 877 final int negativeResId = R.string.device_owner_cancel_cancel; 878 return getBaseDialogBuilder(positiveResId, negativeResId) 879 .setMessage( 880 LazyStringResource.of(R.string.this_will_reset_take_back_first_screen, 881 deviceName)) 882 .setTitle( 883 LazyStringResource.of(R.string.stop_setup_reset_device_question, 884 deviceName)); 885 } 886 887 /** 888 * Create a builder for cancel provisioning dialog 889 * 890 * @return builder 891 */ createCancelProvisioningDialogBuilder()892 public SimpleDialog.Builder createCancelProvisioningDialogBuilder() { 893 final int positiveResId = R.string.profile_owner_cancel_ok; 894 final int negativeResId = R.string.profile_owner_cancel_cancel; 895 final int dialogMsgResId = R.string.profile_owner_cancel_message; 896 return getBaseDialogBuilder(positiveResId, negativeResId).setMessage(dialogMsgResId); 897 } 898 shouldShowOwnershipDisclaimerScreen(ProvisioningParams params)899 public boolean shouldShowOwnershipDisclaimerScreen(ProvisioningParams params) { 900 return !params.skipOwnershipDisclaimer; 901 } 902 isOrganizationOwnedAllowed(ProvisioningParams params)903 public boolean isOrganizationOwnedAllowed(ProvisioningParams params) { 904 int provisioningModes = params.initiatorRequestedProvisioningModes; 905 return containsBinaryFlags(provisioningModes, FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED) 906 || containsBinaryFlags(provisioningModes, FLAG_SUPPORTED_MODES_DEVICE_OWNER); 907 } 908 isManagedProfileProvisioningStartedByDpc( Context context, ProvisioningParams params, SettingsFacade settingsFacade)909 public boolean isManagedProfileProvisioningStartedByDpc( 910 Context context, 911 ProvisioningParams params, 912 SettingsFacade settingsFacade) { 913 if (!ACTION_PROVISION_MANAGED_PROFILE.equals(params.provisioningAction)) { 914 return false; 915 } 916 if (params.startedByTrustedSource) { 917 return false; 918 } 919 return settingsFacade.isUserSetupCompleted(context); 920 } 921 922 /** 923 * Returns {@code true} if {@code packageName} is installed on the primary user. 924 */ isPackageInstalled(String packageName, PackageManager packageManager)925 public boolean isPackageInstalled(String packageName, PackageManager packageManager) { 926 try { 927 final ApplicationInfo ai = packageManager.getApplicationInfo(packageName, 928 PackageManager.MATCH_DIRECT_BOOT_AWARE 929 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); 930 return ai != null; 931 } catch (PackageManager.NameNotFoundException e) { 932 return false; 933 } 934 } 935 getBaseDialogBuilder(int positiveResId, int negativeResId)936 private SimpleDialog.Builder getBaseDialogBuilder(int positiveResId, int negativeResId) { 937 return new SimpleDialog.Builder() 938 .setCancelable(false) 939 .setNegativeButtonMessage(negativeResId) 940 .setPositiveButtonMessage(positiveResId); 941 } 942 943 /** 944 * Returns {@code true} if {@code value} contains the {@code flags} binary flags. 945 */ containsBinaryFlags(int value, int flags)946 public boolean containsBinaryFlags(int value, int flags) { 947 return (value & flags) == flags; 948 } 949 950 /** 951 * Calls {@code callback} when {@code view} has been measured. 952 */ onViewMeasured(View view, Consumer<View> callback)953 public void onViewMeasured(View view, Consumer<View> callback) { 954 view.getViewTreeObserver().addOnGlobalLayoutListener( 955 new ViewTreeObserver.OnGlobalLayoutListener() { 956 @Override 957 public void onGlobalLayout() { 958 view.getViewTreeObserver().removeOnGlobalLayoutListener(this); 959 callback.accept(view); 960 } 961 }); 962 } 963 } 964