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