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