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