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