1 /** 2 * Copyright (C) 2007 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 17 package com.android.car.developeroptions; 18 19 import static android.content.Intent.EXTRA_USER; 20 import static android.content.Intent.EXTRA_USER_ID; 21 import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH; 22 import static android.text.format.DateUtils.FORMAT_SHOW_DATE; 23 24 import android.annotation.Nullable; 25 import android.app.ActionBar; 26 import android.app.Activity; 27 import android.app.ActivityManager; 28 import android.app.AppGlobals; 29 import android.app.IActivityManager; 30 import android.app.KeyguardManager; 31 import android.app.admin.DevicePolicyManager; 32 import android.content.ActivityNotFoundException; 33 import android.content.ComponentName; 34 import android.content.ContentResolver; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.content.IntentFilter; 38 import android.content.pm.ApplicationInfo; 39 import android.content.pm.IPackageManager; 40 import android.content.pm.IntentFilterVerificationInfo; 41 import android.content.pm.PackageManager; 42 import android.content.pm.PackageManager.NameNotFoundException; 43 import android.content.pm.ResolveInfo; 44 import android.content.pm.UserInfo; 45 import android.content.res.Resources; 46 import android.content.res.TypedArray; 47 import android.database.Cursor; 48 import android.graphics.Bitmap; 49 import android.graphics.Canvas; 50 import android.graphics.drawable.BitmapDrawable; 51 import android.graphics.drawable.Drawable; 52 import android.graphics.drawable.VectorDrawable; 53 import android.hardware.face.FaceManager; 54 import android.hardware.fingerprint.FingerprintManager; 55 import android.net.ConnectivityManager; 56 import android.net.LinkProperties; 57 import android.net.Network; 58 import android.net.wifi.WifiManager; 59 import android.os.BatteryManager; 60 import android.os.Build; 61 import android.os.Bundle; 62 import android.os.IBinder; 63 import android.os.INetworkManagementService; 64 import android.os.RemoteException; 65 import android.os.ServiceManager; 66 import android.os.UserHandle; 67 import android.os.UserManager; 68 import android.os.storage.StorageManager; 69 import android.os.storage.VolumeInfo; 70 import android.preference.PreferenceFrameLayout; 71 import android.provider.ContactsContract.CommonDataKinds; 72 import android.provider.ContactsContract.Contacts; 73 import android.provider.ContactsContract.Data; 74 import android.provider.ContactsContract.Profile; 75 import android.provider.ContactsContract.RawContacts; 76 import android.provider.Settings; 77 import android.telephony.SubscriptionManager; 78 import android.telephony.TelephonyManager; 79 import android.text.Spannable; 80 import android.text.SpannableString; 81 import android.text.TextUtils; 82 import android.text.format.DateUtils; 83 import android.text.style.TtsSpan; 84 import android.util.ArraySet; 85 import android.util.IconDrawableFactory; 86 import android.util.Log; 87 import android.view.LayoutInflater; 88 import android.view.View; 89 import android.view.ViewGroup; 90 import android.widget.EditText; 91 import android.widget.ListView; 92 import android.widget.TabWidget; 93 94 import androidx.annotation.StringRes; 95 import androidx.core.graphics.drawable.IconCompat; 96 import androidx.fragment.app.Fragment; 97 import androidx.lifecycle.Lifecycle; 98 import androidx.preference.Preference; 99 import androidx.preference.PreferenceGroup; 100 101 import com.android.internal.app.UnlaunchableAppActivity; 102 import com.android.internal.util.ArrayUtils; 103 import com.android.internal.widget.LockPatternUtils; 104 import com.android.car.developeroptions.core.FeatureFlags; 105 import com.android.car.developeroptions.development.featureflags.FeatureFlagPersistent; 106 import com.android.car.developeroptions.password.ChooseLockSettingsHelper; 107 import com.android.settingslib.widget.ActionBarShadowController; 108 109 import java.net.InetAddress; 110 import java.util.Iterator; 111 import java.util.List; 112 import java.util.Locale; 113 114 public final class Utils extends com.android.settingslib.Utils { 115 116 private static final String TAG = "Settings"; 117 118 /** 119 * Set the preference's title to the matching activity's label. 120 */ 121 public static final int UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY = 1; 122 123 public static final String SETTINGS_PACKAGE_NAME = "com.android.car.developeroptions"; 124 125 public static final String OS_PKG = "os"; 126 127 /** 128 * Whether to disable the new device identifier access restrictions. 129 */ 130 public static final String PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED = 131 "device_identifier_access_restrictions_disabled"; 132 133 /** 134 * Whether to show the Permissions Hub. 135 */ 136 public static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled"; 137 138 /** 139 * Finds a matching activity for a preference's intent. If a matching 140 * activity is not found, it will remove the preference. 141 * 142 * @param context The context. 143 * @param parentPreferenceGroup The preference group that contains the 144 * preference whose intent is being resolved. 145 * @param preferenceKey The key of the preference whose intent is being 146 * resolved. 147 * @param flags 0 or one or more of 148 * {@link #UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY} 149 * . 150 * @return Whether an activity was found. If false, the preference was 151 * removed. 152 */ updatePreferenceToSpecificActivityOrRemove(Context context, PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags)153 public static boolean updatePreferenceToSpecificActivityOrRemove(Context context, 154 PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags) { 155 156 Preference preference = parentPreferenceGroup.findPreference(preferenceKey); 157 if (preference == null) { 158 return false; 159 } 160 161 Intent intent = preference.getIntent(); 162 if (intent != null) { 163 // Find the activity that is in the system image 164 PackageManager pm = context.getPackageManager(); 165 List<ResolveInfo> list = pm.queryIntentActivities(intent, 0); 166 int listSize = list.size(); 167 for (int i = 0; i < listSize; i++) { 168 ResolveInfo resolveInfo = list.get(i); 169 if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) 170 != 0) { 171 172 // Replace the intent with this specific activity 173 preference.setIntent(new Intent().setClassName( 174 resolveInfo.activityInfo.packageName, 175 resolveInfo.activityInfo.name)); 176 177 if ((flags & UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY) != 0) { 178 // Set the preference title to the activity's label 179 preference.setTitle(resolveInfo.loadLabel(pm)); 180 } 181 182 return true; 183 } 184 } 185 } 186 187 // Did not find a matching activity, so remove the preference 188 parentPreferenceGroup.removePreference(preference); 189 190 return false; 191 } 192 193 /** 194 * Returns the UserManager for a given context 195 * 196 * @throws IllegalStateException if no UserManager could be retrieved. 197 */ getUserManager(Context context)198 public static UserManager getUserManager(Context context) { 199 UserManager um = UserManager.get(context); 200 if (um == null) { 201 throw new IllegalStateException("Unable to load UserManager"); 202 } 203 return um; 204 } 205 206 /** 207 * Returns true if Monkey is running. 208 */ isMonkeyRunning()209 public static boolean isMonkeyRunning() { 210 return ActivityManager.isUserAMonkey(); 211 } 212 213 /** 214 * Returns whether the device is voice-capable (meaning, it is also a phone). 215 */ isVoiceCapable(Context context)216 public static boolean isVoiceCapable(Context context) { 217 TelephonyManager telephony = 218 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 219 return telephony != null && telephony.isVoiceCapable(); 220 } 221 222 /** 223 * Returns the WIFI IP Addresses, if any, taking into account IPv4 and IPv6 style addresses. 224 * @param context the application context 225 * @return the formatted and newline-separated IP addresses, or null if none. 226 */ getWifiIpAddresses(Context context)227 public static String getWifiIpAddresses(Context context) { 228 WifiManager wifiManager = context.getSystemService(WifiManager.class); 229 Network currentNetwork = wifiManager.getCurrentNetwork(); 230 if (currentNetwork != null) { 231 ConnectivityManager cm = (ConnectivityManager) 232 context.getSystemService(Context.CONNECTIVITY_SERVICE); 233 LinkProperties prop = cm.getLinkProperties(currentNetwork); 234 return formatIpAddresses(prop); 235 } 236 return null; 237 } 238 formatIpAddresses(LinkProperties prop)239 private static String formatIpAddresses(LinkProperties prop) { 240 if (prop == null) return null; 241 Iterator<InetAddress> iter = prop.getAllAddresses().iterator(); 242 // If there are no entries, return null 243 if (!iter.hasNext()) return null; 244 // Concatenate all available addresses, comma separated 245 String addresses = ""; 246 while (iter.hasNext()) { 247 addresses += iter.next().getHostAddress(); 248 if (iter.hasNext()) addresses += "\n"; 249 } 250 return addresses; 251 } 252 createLocaleFromString(String localeStr)253 public static Locale createLocaleFromString(String localeStr) { 254 // TODO: is there a better way to actually construct a locale that will match? 255 // The main problem is, on top of Java specs, locale.toString() and 256 // new Locale(locale.toString()).toString() do not return equal() strings in 257 // many cases, because the constructor takes the only string as the language 258 // code. So : new Locale("en", "US").toString() => "en_US" 259 // And : new Locale("en_US").toString() => "en_us" 260 if (null == localeStr) 261 return Locale.getDefault(); 262 String[] brokenDownLocale = localeStr.split("_", 3); 263 // split may not return a 0-length array. 264 if (1 == brokenDownLocale.length) { 265 return new Locale(brokenDownLocale[0]); 266 } else if (2 == brokenDownLocale.length) { 267 return new Locale(brokenDownLocale[0], brokenDownLocale[1]); 268 } else { 269 return new Locale(brokenDownLocale[0], brokenDownLocale[1], brokenDownLocale[2]); 270 } 271 } 272 isBatteryPresent(Intent batteryChangedIntent)273 public static boolean isBatteryPresent(Intent batteryChangedIntent) { 274 return batteryChangedIntent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true); 275 } 276 getBatteryPercentage(Intent batteryChangedIntent)277 public static String getBatteryPercentage(Intent batteryChangedIntent) { 278 return formatPercentage(getBatteryLevel(batteryChangedIntent)); 279 } 280 281 /** 282 * Prepare a custom preferences layout, moving padding to {@link ListView} 283 * when outside scrollbars are requested. Usually used to display 284 * {@link ListView} and {@link TabWidget} with correct padding. 285 */ prepareCustomPreferencesList( ViewGroup parent, View child, View list, boolean ignoreSidePadding)286 public static void prepareCustomPreferencesList( 287 ViewGroup parent, View child, View list, boolean ignoreSidePadding) { 288 final boolean movePadding = list.getScrollBarStyle() == View.SCROLLBARS_OUTSIDE_OVERLAY; 289 if (movePadding) { 290 final Resources res = list.getResources(); 291 final int paddingBottom = res.getDimensionPixelSize( 292 com.android.internal.R.dimen.preference_fragment_padding_bottom); 293 294 if (parent instanceof PreferenceFrameLayout) { 295 ((PreferenceFrameLayout.LayoutParams) child.getLayoutParams()).removeBorders = true; 296 } 297 list.setPaddingRelative(0 /* start */, 0 /* top */, 0 /* end */, paddingBottom); 298 } 299 } 300 forceCustomPadding(View view, boolean additive)301 public static void forceCustomPadding(View view, boolean additive) { 302 final Resources res = view.getResources(); 303 304 final int paddingStart = additive ? view.getPaddingStart() : 0; 305 final int paddingEnd = additive ? view.getPaddingEnd() : 0; 306 final int paddingBottom = res.getDimensionPixelSize( 307 com.android.internal.R.dimen.preference_fragment_padding_bottom); 308 309 view.setPaddingRelative(paddingStart, 0, paddingEnd, paddingBottom); 310 } 311 getMeProfileName(Context context, boolean full)312 public static String getMeProfileName(Context context, boolean full) { 313 if (full) { 314 return getProfileDisplayName(context); 315 } else { 316 return getShorterNameIfPossible(context); 317 } 318 } 319 getShorterNameIfPossible(Context context)320 private static String getShorterNameIfPossible(Context context) { 321 final String given = getLocalProfileGivenName(context); 322 return !TextUtils.isEmpty(given) ? given : getProfileDisplayName(context); 323 } 324 getLocalProfileGivenName(Context context)325 private static String getLocalProfileGivenName(Context context) { 326 final ContentResolver cr = context.getContentResolver(); 327 328 // Find the raw contact ID for the local ME profile raw contact. 329 final long localRowProfileId; 330 final Cursor localRawProfile = cr.query( 331 Profile.CONTENT_RAW_CONTACTS_URI, 332 new String[] {RawContacts._ID}, 333 RawContacts.ACCOUNT_TYPE + " IS NULL AND " + 334 RawContacts.ACCOUNT_NAME + " IS NULL", 335 null, null); 336 if (localRawProfile == null) return null; 337 338 try { 339 if (!localRawProfile.moveToFirst()) { 340 return null; 341 } 342 localRowProfileId = localRawProfile.getLong(0); 343 } finally { 344 localRawProfile.close(); 345 } 346 347 // Find the structured name for the raw contact. 348 final Cursor structuredName = cr.query( 349 Profile.CONTENT_URI.buildUpon().appendPath(Contacts.Data.CONTENT_DIRECTORY).build(), 350 new String[] {CommonDataKinds.StructuredName.GIVEN_NAME, 351 CommonDataKinds.StructuredName.FAMILY_NAME}, 352 Data.RAW_CONTACT_ID + "=" + localRowProfileId, 353 null, null); 354 if (structuredName == null) return null; 355 356 try { 357 if (!structuredName.moveToFirst()) { 358 return null; 359 } 360 String partialName = structuredName.getString(0); 361 if (TextUtils.isEmpty(partialName)) { 362 partialName = structuredName.getString(1); 363 } 364 return partialName; 365 } finally { 366 structuredName.close(); 367 } 368 } 369 getProfileDisplayName(Context context)370 private static final String getProfileDisplayName(Context context) { 371 final ContentResolver cr = context.getContentResolver(); 372 final Cursor profile = cr.query(Profile.CONTENT_URI, 373 new String[] {Profile.DISPLAY_NAME}, null, null, null); 374 if (profile == null) return null; 375 376 try { 377 if (!profile.moveToFirst()) { 378 return null; 379 } 380 return profile.getString(0); 381 } finally { 382 profile.close(); 383 } 384 } 385 hasMultipleUsers(Context context)386 public static boolean hasMultipleUsers(Context context) { 387 return ((UserManager) context.getSystemService(Context.USER_SERVICE)) 388 .getUsers().size() > 1; 389 } 390 391 /** 392 * Returns the managed profile of the current user or {@code null} if none is found or a profile 393 * exists but it is disabled. 394 */ getManagedProfile(UserManager userManager)395 public static UserHandle getManagedProfile(UserManager userManager) { 396 List<UserHandle> userProfiles = userManager.getUserProfiles(); 397 for (UserHandle profile : userProfiles) { 398 if (profile.getIdentifier() == userManager.getUserHandle()) { 399 continue; 400 } 401 final UserInfo userInfo = userManager.getUserInfo(profile.getIdentifier()); 402 if (userInfo.isManagedProfile()) { 403 return profile; 404 } 405 } 406 return null; 407 } 408 409 /** 410 * Returns the managed profile of the current user or {@code null} if none is found. Unlike 411 * {@link #getManagedProfile} this method returns enabled and disabled managed profiles. 412 */ getManagedProfileWithDisabled(UserManager userManager)413 public static UserHandle getManagedProfileWithDisabled(UserManager userManager) { 414 // TODO: Call getManagedProfileId from here once Robolectric supports 415 // API level 24 and UserManager.getProfileIdsWithDisabled can be Mocked (to avoid having 416 // yet another implementation that loops over user profiles in this method). In the meantime 417 // we need to use UserManager.getProfiles that is available on API 23 (the one currently 418 // used for Settings Robolectric tests). 419 final int myUserId = UserHandle.myUserId(); 420 List<UserInfo> profiles = userManager.getProfiles(myUserId); 421 final int count = profiles.size(); 422 for (int i = 0; i < count; i++) { 423 final UserInfo profile = profiles.get(i); 424 if (profile.isManagedProfile() 425 && profile.getUserHandle().getIdentifier() != myUserId) { 426 return profile.getUserHandle(); 427 } 428 } 429 return null; 430 } 431 432 /** 433 * Retrieves the id for the given user's managed profile. 434 * 435 * @return the managed profile id or UserHandle.USER_NULL if there is none. 436 */ getManagedProfileId(UserManager um, int parentUserId)437 public static int getManagedProfileId(UserManager um, int parentUserId) { 438 int[] profileIds = um.getProfileIdsWithDisabled(parentUserId); 439 for (int profileId : profileIds) { 440 if (profileId != parentUserId) { 441 return profileId; 442 } 443 } 444 return UserHandle.USER_NULL; 445 } 446 447 /** 448 * Returns the target user for a Settings activity. 449 * <p> 450 * User would be retrieved in this order: 451 * <ul> 452 * <li> If this activity is launched from other user, return that user id. 453 * <li> If this is launched from the Settings app in same user, return the user contained as an 454 * extra in the arguments or intent extras. 455 * <li> Otherwise, return UserHandle.myUserId(). 456 * </ul> 457 * <p> 458 * Note: This is secure in the sense that it only returns a target user different to the current 459 * one if the app launching this activity is the Settings app itself, running in the same user 460 * or in one that is in the same profile group, or if the user id is provided by the system. 461 */ getSecureTargetUser(IBinder activityToken, UserManager um, @Nullable Bundle arguments, @Nullable Bundle intentExtras)462 public static UserHandle getSecureTargetUser(IBinder activityToken, 463 UserManager um, @Nullable Bundle arguments, @Nullable Bundle intentExtras) { 464 UserHandle currentUser = new UserHandle(UserHandle.myUserId()); 465 IActivityManager am = ActivityManager.getService(); 466 try { 467 String launchedFromPackage = am.getLaunchedFromPackage(activityToken); 468 boolean launchedFromSettingsApp = SETTINGS_PACKAGE_NAME.equals(launchedFromPackage); 469 470 UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId( 471 am.getLaunchedFromUid(activityToken))); 472 if (launchedFromUser != null && !launchedFromUser.equals(currentUser)) { 473 // Check it's secure 474 if (isProfileOf(um, launchedFromUser)) { 475 return launchedFromUser; 476 } 477 } 478 UserHandle extrasUser = getUserHandleFromBundle(intentExtras); 479 if (extrasUser != null && !extrasUser.equals(currentUser)) { 480 // Check it's secure 481 if (launchedFromSettingsApp && isProfileOf(um, extrasUser)) { 482 return extrasUser; 483 } 484 } 485 UserHandle argumentsUser = getUserHandleFromBundle(arguments); 486 if (argumentsUser != null && !argumentsUser.equals(currentUser)) { 487 // Check it's secure 488 if (launchedFromSettingsApp && isProfileOf(um, argumentsUser)) { 489 return argumentsUser; 490 } 491 } 492 } catch (RemoteException e) { 493 // Should not happen 494 Log.v(TAG, "Could not talk to activity manager.", e); 495 } 496 return currentUser; 497 } 498 499 /** 500 * Lookup both {@link Intent#EXTRA_USER} and {@link Intent#EXTRA_USER_ID} in the bundle 501 * and return the {@link UserHandle} object. Return {@code null} if nothing is found. 502 */ getUserHandleFromBundle(Bundle bundle)503 private static @Nullable UserHandle getUserHandleFromBundle(Bundle bundle) { 504 if (bundle == null) { 505 return null; 506 } 507 final UserHandle user = bundle.getParcelable(EXTRA_USER); 508 if (user != null) { 509 return user; 510 } 511 final int userId = bundle.getInt(EXTRA_USER_ID, -1); 512 if (userId != -1) { 513 return UserHandle.of(userId); 514 } 515 return null; 516 } 517 518 /** 519 * Returns true if the user provided is in the same profiles group as the current user. 520 */ isProfileOf(UserManager um, UserHandle otherUser)521 private static boolean isProfileOf(UserManager um, UserHandle otherUser) { 522 if (um == null || otherUser == null) return false; 523 return (UserHandle.myUserId() == otherUser.getIdentifier()) 524 || um.getUserProfiles().contains(otherUser); 525 } 526 527 /** 528 * Return whether or not the user should have a SIM Cards option in Settings. 529 * TODO: Change back to returning true if count is greater than one after testing. 530 * TODO: See bug 16533525. 531 */ showSimCardTile(Context context)532 public static boolean showSimCardTile(Context context) { 533 if (FeatureFlagPersistent.isEnabled(context, FeatureFlags.NETWORK_INTERNET_V2)) { 534 return false; 535 } 536 final TelephonyManager tm = 537 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 538 539 return tm.getSimCount() > 1; 540 } 541 542 /** 543 * Queries for the UserInfo of a user. Returns null if the user doesn't exist (was removed). 544 * @param userManager Instance of UserManager 545 * @param checkUser The user to check the existence of. 546 * @return UserInfo of the user or null for non-existent user. 547 */ getExistingUser(UserManager userManager, UserHandle checkUser)548 public static UserInfo getExistingUser(UserManager userManager, UserHandle checkUser) { 549 final List<UserInfo> users = userManager.getUsers(true /* excludeDying */); 550 final int checkUserId = checkUser.getIdentifier(); 551 for (UserInfo user : users) { 552 if (user.id == checkUserId) { 553 return user; 554 } 555 } 556 return null; 557 } 558 inflateCategoryHeader(LayoutInflater inflater, ViewGroup parent)559 public static View inflateCategoryHeader(LayoutInflater inflater, ViewGroup parent) { 560 final TypedArray a = inflater.getContext().obtainStyledAttributes(null, 561 com.android.internal.R.styleable.Preference, 562 com.android.internal.R.attr.preferenceCategoryStyle, 0); 563 final int resId = a.getResourceId(com.android.internal.R.styleable.Preference_layout, 564 0); 565 a.recycle(); 566 return inflater.inflate(resId, parent, false); 567 } 568 getHandledDomains(PackageManager pm, String packageName)569 public static ArraySet<String> getHandledDomains(PackageManager pm, String packageName) { 570 List<IntentFilterVerificationInfo> iviList = pm.getIntentFilterVerifications(packageName); 571 List<IntentFilter> filters = pm.getAllIntentFilters(packageName); 572 573 ArraySet<String> result = new ArraySet<>(); 574 if (iviList != null && iviList.size() > 0) { 575 for (IntentFilterVerificationInfo ivi : iviList) { 576 for (String host : ivi.getDomains()) { 577 result.add(host); 578 } 579 } 580 } 581 if (filters != null && filters.size() > 0) { 582 for (IntentFilter filter : filters) { 583 if (filter.hasCategory(Intent.CATEGORY_BROWSABLE) 584 && (filter.hasDataScheme(IntentFilter.SCHEME_HTTP) || 585 filter.hasDataScheme(IntentFilter.SCHEME_HTTPS))) { 586 result.addAll(filter.getHostsList()); 587 } 588 } 589 } 590 return result; 591 } 592 593 /** 594 * Returns the application info of the currently installed MDM package. 595 */ getAdminApplicationInfo(Context context, int profileId)596 public static ApplicationInfo getAdminApplicationInfo(Context context, int profileId) { 597 DevicePolicyManager dpm = 598 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); 599 ComponentName mdmPackage = dpm.getProfileOwnerAsUser(profileId); 600 if (mdmPackage == null) { 601 return null; 602 } 603 String mdmPackageName = mdmPackage.getPackageName(); 604 try { 605 IPackageManager ipm = AppGlobals.getPackageManager(); 606 ApplicationInfo mdmApplicationInfo = 607 ipm.getApplicationInfo(mdmPackageName, 0, profileId); 608 return mdmApplicationInfo; 609 } catch (RemoteException e) { 610 Log.e(TAG, "Error while retrieving application info for package " + mdmPackageName 611 + ", userId " + profileId, e); 612 return null; 613 } 614 } 615 isBandwidthControlEnabled()616 public static boolean isBandwidthControlEnabled() { 617 final INetworkManagementService netManager = INetworkManagementService.Stub 618 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); 619 try { 620 return netManager.isBandwidthControlEnabled(); 621 } catch (RemoteException e) { 622 return false; 623 } 624 } 625 626 /** 627 * Returns an accessible SpannableString. 628 * @param displayText the text to display 629 * @param accessibileText the text text-to-speech engines should read 630 */ createAccessibleSequence(CharSequence displayText, String accessibileText)631 public static SpannableString createAccessibleSequence(CharSequence displayText, 632 String accessibileText) { 633 SpannableString str = new SpannableString(displayText); 634 str.setSpan(new TtsSpan.TextBuilder(accessibileText).build(), 0, 635 displayText.length(), 636 Spannable.SPAN_INCLUSIVE_INCLUSIVE); 637 return str; 638 } 639 640 /** 641 * Returns the user id present in the bundle with 642 * {@link Intent#EXTRA_USER_ID} if it belongs to the current user. 643 * 644 * @throws SecurityException if the given userId does not belong to the 645 * current user group. 646 */ getUserIdFromBundle(Context context, Bundle bundle)647 public static int getUserIdFromBundle(Context context, Bundle bundle) { 648 return getUserIdFromBundle(context, bundle, false); 649 } 650 651 /** 652 * Returns the user id present in the bundle with 653 * {@link Intent#EXTRA_USER_ID} if it belongs to the current user. 654 * 655 * @param isInternal indicating if the caller is "internal" to the system, 656 * meaning we're willing to trust extras like 657 * {@link ChooseLockSettingsHelper#EXTRA_ALLOW_ANY_USER}. 658 * @throws SecurityException if the given userId does not belong to the 659 * current user group. 660 */ getUserIdFromBundle(Context context, Bundle bundle, boolean isInternal)661 public static int getUserIdFromBundle(Context context, Bundle bundle, boolean isInternal) { 662 if (bundle == null) { 663 return getCredentialOwnerUserId(context); 664 } 665 final boolean allowAnyUser = isInternal 666 && bundle.getBoolean(ChooseLockSettingsHelper.EXTRA_ALLOW_ANY_USER, false); 667 int userId = bundle.getInt(Intent.EXTRA_USER_ID, UserHandle.myUserId()); 668 if (userId == LockPatternUtils.USER_FRP) { 669 return allowAnyUser ? userId : enforceSystemUser(context, userId); 670 } else { 671 return allowAnyUser ? userId : enforceSameOwner(context, userId); 672 } 673 } 674 675 /** 676 * Returns the given user id if the current user is the system user. 677 * 678 * @throws SecurityException if the current user is not the system user. 679 */ enforceSystemUser(Context context, int userId)680 public static int enforceSystemUser(Context context, int userId) { 681 if (UserHandle.myUserId() == UserHandle.USER_SYSTEM) { 682 return userId; 683 } 684 throw new SecurityException("Given user id " + userId + " must only be used from " 685 + "USER_SYSTEM, but current user is " + UserHandle.myUserId()); 686 } 687 688 /** 689 * Returns the given user id if it belongs to the current user. 690 * 691 * @throws SecurityException if the given userId does not belong to the current user group. 692 */ enforceSameOwner(Context context, int userId)693 public static int enforceSameOwner(Context context, int userId) { 694 final UserManager um = getUserManager(context); 695 final int[] profileIds = um.getProfileIdsWithDisabled(UserHandle.myUserId()); 696 if (ArrayUtils.contains(profileIds, userId)) { 697 return userId; 698 } 699 throw new SecurityException("Given user id " + userId + " does not belong to user " 700 + UserHandle.myUserId()); 701 } 702 703 /** 704 * Returns the effective credential owner of the calling user. 705 */ getCredentialOwnerUserId(Context context)706 public static int getCredentialOwnerUserId(Context context) { 707 return getCredentialOwnerUserId(context, UserHandle.myUserId()); 708 } 709 710 /** 711 * Returns the user id of the credential owner of the given user id. 712 */ getCredentialOwnerUserId(Context context, int userId)713 public static int getCredentialOwnerUserId(Context context, int userId) { 714 UserManager um = getUserManager(context); 715 return um.getCredentialOwnerProfile(userId); 716 } 717 718 private static final StringBuilder sBuilder = new StringBuilder(50); 719 private static final java.util.Formatter sFormatter = new java.util.Formatter( 720 sBuilder, Locale.getDefault()); 721 formatDateRange(Context context, long start, long end)722 public static String formatDateRange(Context context, long start, long end) { 723 final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH; 724 725 synchronized (sBuilder) { 726 sBuilder.setLength(0); 727 return DateUtils.formatDateRange(context, sFormatter, start, end, flags, null) 728 .toString(); 729 } 730 } 731 isDeviceProvisioned(Context context)732 public static boolean isDeviceProvisioned(Context context) { 733 return Settings.Global.getInt(context.getContentResolver(), 734 Settings.Global.DEVICE_PROVISIONED, 0) != 0; 735 } 736 startQuietModeDialogIfNecessary(Context context, UserManager um, int userId)737 public static boolean startQuietModeDialogIfNecessary(Context context, UserManager um, 738 int userId) { 739 if (um.isQuietModeEnabled(UserHandle.of(userId))) { 740 final Intent intent = UnlaunchableAppActivity.createInQuietModeDialogIntent(userId); 741 context.startActivity(intent); 742 return true; 743 } 744 return false; 745 } 746 unlockWorkProfileIfNecessary(Context context, int userId)747 public static boolean unlockWorkProfileIfNecessary(Context context, int userId) { 748 try { 749 if (!ActivityManager.getService().isUserRunning(userId, 750 ActivityManager.FLAG_AND_LOCKED)) { 751 return false; 752 } 753 } catch (RemoteException e) { 754 return false; 755 } 756 if (!(new LockPatternUtils(context)).isSecure(userId)) { 757 return false; 758 } 759 return confirmWorkProfileCredentials(context, userId); 760 } 761 confirmWorkProfileCredentials(Context context, int userId)762 private static boolean confirmWorkProfileCredentials(Context context, int userId) { 763 final KeyguardManager km = (KeyguardManager) context.getSystemService( 764 Context.KEYGUARD_SERVICE); 765 final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null, userId); 766 if (unlockIntent != null) { 767 context.startActivity(unlockIntent); 768 return true; 769 } else { 770 return false; 771 } 772 } 773 getApplicationLabel(Context context, String packageName)774 public static CharSequence getApplicationLabel(Context context, String packageName) { 775 try { 776 final ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo( 777 packageName, 778 PackageManager.MATCH_DISABLED_COMPONENTS 779 | PackageManager.MATCH_ANY_USER); 780 return appInfo.loadLabel(context.getPackageManager()); 781 } catch (PackageManager.NameNotFoundException e) { 782 Log.w(TAG, "Unable to find info for package: " + packageName); 783 } 784 return null; 785 } 786 isPackageDirectBootAware(Context context, String packageName)787 public static boolean isPackageDirectBootAware(Context context, String packageName) { 788 try { 789 final ApplicationInfo ai = context.getPackageManager().getApplicationInfo( 790 packageName, 0); 791 return ai.isDirectBootAware() || ai.isPartiallyDirectBootAware(); 792 } catch (NameNotFoundException ignored) { 793 } 794 return false; 795 } 796 797 /** 798 * Returns a context created from the given context for the given user, or null if it fails 799 */ createPackageContextAsUser(Context context, int userId)800 public static Context createPackageContextAsUser(Context context, int userId) { 801 try { 802 return context.createPackageContextAsUser( 803 context.getPackageName(), 0 /* flags */, UserHandle.of(userId)); 804 } catch (PackageManager.NameNotFoundException e) { 805 Log.e(TAG, "Failed to create user context", e); 806 } 807 return null; 808 } 809 getFingerprintManagerOrNull(Context context)810 public static FingerprintManager getFingerprintManagerOrNull(Context context) { 811 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { 812 return (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE); 813 } else { 814 return null; 815 } 816 } 817 hasFingerprintHardware(Context context)818 public static boolean hasFingerprintHardware(Context context) { 819 FingerprintManager fingerprintManager = getFingerprintManagerOrNull(context); 820 return fingerprintManager != null && fingerprintManager.isHardwareDetected(); 821 } 822 getFaceManagerOrNull(Context context)823 public static FaceManager getFaceManagerOrNull(Context context) { 824 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) { 825 return (FaceManager) context.getSystemService(Context.FACE_SERVICE); 826 } else { 827 return null; 828 } 829 } 830 hasFaceHardware(Context context)831 public static boolean hasFaceHardware(Context context) { 832 FaceManager faceManager = getFaceManagerOrNull(context); 833 return faceManager != null && faceManager.isHardwareDetected(); 834 } 835 836 /** 837 * Launches an intent which may optionally have a user id defined. 838 * @param fragment Fragment to use to launch the activity. 839 * @param intent Intent to launch. 840 */ launchIntent(Fragment fragment, Intent intent)841 public static void launchIntent(Fragment fragment, Intent intent) { 842 try { 843 final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1); 844 845 if (userId == -1) { 846 fragment.startActivity(intent); 847 } else { 848 fragment.getActivity().startActivityAsUser(intent, new UserHandle(userId)); 849 } 850 } catch (ActivityNotFoundException e) { 851 Log.w(TAG, "No activity found for " + intent); 852 } 853 } 854 isDemoUser(Context context)855 public static boolean isDemoUser(Context context) { 856 return UserManager.isDeviceInDemoMode(context) && getUserManager(context).isDemoUser(); 857 } 858 getDeviceOwnerComponent(Context context)859 public static ComponentName getDeviceOwnerComponent(Context context) { 860 final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( 861 Context.DEVICE_POLICY_SERVICE); 862 return dpm.getDeviceOwnerComponentOnAnyUser(); 863 } 864 865 /** 866 * Returns if a given user is a profile of another user. 867 * @param user The user whose profiles wibe checked. 868 * @param profile The (potential) profile. 869 * @return if the profile is actually a profile 870 */ isProfileOf(UserInfo user, UserInfo profile)871 public static boolean isProfileOf(UserInfo user, UserInfo profile) { 872 return user.id == profile.id || 873 (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID 874 && user.profileGroupId == profile.profileGroupId); 875 } 876 877 /** 878 * Tries to initalize a volume with the given bundle. If it is a valid, private, and readable 879 * {@link VolumeInfo}, it is returned. If it is not valid, null is returned. 880 */ 881 @Nullable maybeInitializeVolume(StorageManager sm, Bundle bundle)882 public static VolumeInfo maybeInitializeVolume(StorageManager sm, Bundle bundle) { 883 final String volumeId = bundle.getString(VolumeInfo.EXTRA_VOLUME_ID, 884 VolumeInfo.ID_PRIVATE_INTERNAL); 885 VolumeInfo volume = sm.findVolumeById(volumeId); 886 return isVolumeValid(volume) ? volume : null; 887 } 888 889 /** 890 * Return {@code true} if the supplied package is device owner or profile owner of at 891 * least one user. 892 * @param userManager used to get profile owner app for each user 893 * @param devicePolicyManager used to check whether it is device owner app 894 * @param packageName package to check about 895 */ isProfileOrDeviceOwner(UserManager userManager, DevicePolicyManager devicePolicyManager, String packageName)896 public static boolean isProfileOrDeviceOwner(UserManager userManager, 897 DevicePolicyManager devicePolicyManager, String packageName) { 898 List<UserInfo> userInfos = userManager.getUsers(); 899 if (devicePolicyManager.isDeviceOwnerAppOnAnyUser(packageName)) { 900 return true; 901 } 902 for (int i = 0, size = userInfos.size(); i < size; i++) { 903 ComponentName cn = devicePolicyManager.getProfileOwnerAsUser(userInfos.get(i).id); 904 if (cn != null && cn.getPackageName().equals(packageName)) { 905 return true; 906 } 907 } 908 return false; 909 } 910 911 /** 912 * Return the resource id to represent the install status for an app 913 */ 914 @StringRes getInstallationStatus(ApplicationInfo info)915 public static int getInstallationStatus(ApplicationInfo info) { 916 if ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { 917 return R.string.not_installed; 918 } 919 return info.enabled ? R.string.installed : R.string.disabled; 920 } 921 isVolumeValid(VolumeInfo volume)922 private static boolean isVolumeValid(VolumeInfo volume) { 923 return (volume != null) && (volume.getType() == VolumeInfo.TYPE_PRIVATE) 924 && volume.isMountedReadable(); 925 } 926 setEditTextCursorPosition(EditText editText)927 public static void setEditTextCursorPosition(EditText editText) { 928 editText.setSelection(editText.getText().length()); 929 } 930 931 /** 932 * Sets the preference icon with a drawable that is scaled down to to avoid crashing Settings if 933 * it's too big. 934 */ setSafeIcon(Preference pref, Drawable icon)935 public static void setSafeIcon(Preference pref, Drawable icon) { 936 Drawable safeIcon = icon; 937 if ((icon != null) && !(icon instanceof VectorDrawable)) { 938 safeIcon = getSafeDrawable(icon, 500, 500); 939 } 940 pref.setIcon(safeIcon); 941 } 942 943 /** 944 * Gets a drawable with a limited size to avoid crashing Settings if it's too big. 945 * 946 * @param original original drawable, typically an app icon. 947 * @param maxWidth maximum width, in pixels. 948 * @param maxHeight maximum height, in pixels. 949 */ getSafeDrawable(Drawable original, int maxWidth, int maxHeight)950 public static Drawable getSafeDrawable(Drawable original, int maxWidth, int maxHeight) { 951 final int actualWidth = original.getMinimumWidth(); 952 final int actualHeight = original.getMinimumHeight(); 953 954 if (actualWidth <= maxWidth && actualHeight <= maxHeight) { 955 return original; 956 } 957 958 float scaleWidth = ((float) maxWidth) / actualWidth; 959 float scaleHeight = ((float) maxHeight) / actualHeight; 960 float scale = Math.min(scaleWidth, scaleHeight); 961 final int width = (int) (actualWidth * scale); 962 final int height = (int) (actualHeight * scale); 963 964 final Bitmap bitmap; 965 if (original instanceof BitmapDrawable) { 966 bitmap = Bitmap.createScaledBitmap(((BitmapDrawable) original).getBitmap(), width, 967 height, false); 968 } else { 969 bitmap = createBitmap(original, width, height); 970 } 971 return new BitmapDrawable(null, bitmap); 972 } 973 974 /** 975 * Create an Icon pointing to a drawable. 976 */ createIconWithDrawable(Drawable drawable)977 public static IconCompat createIconWithDrawable(Drawable drawable) { 978 Bitmap bitmap; 979 if (drawable instanceof BitmapDrawable) { 980 bitmap = ((BitmapDrawable)drawable).getBitmap(); 981 } else { 982 final int width = drawable.getIntrinsicWidth(); 983 final int height = drawable.getIntrinsicHeight(); 984 bitmap = createBitmap(drawable, 985 width > 0 ? width : 1, 986 height > 0 ? height : 1); 987 } 988 return IconCompat.createWithBitmap(bitmap); 989 } 990 991 /** 992 * Creates a drawable with specified width and height. 993 */ createBitmap(Drawable drawable, int width, int height)994 public static Bitmap createBitmap(Drawable drawable, int width, int height) { 995 final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 996 final Canvas canvas = new Canvas(bitmap); 997 drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 998 drawable.draw(canvas); 999 return bitmap; 1000 } 1001 1002 /** 1003 * Get the {@link Drawable} that represents the app icon 1004 */ getBadgedIcon(IconDrawableFactory iconDrawableFactory, PackageManager packageManager, String packageName, int userId)1005 public static Drawable getBadgedIcon(IconDrawableFactory iconDrawableFactory, 1006 PackageManager packageManager, String packageName, int userId) { 1007 try { 1008 final ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser( 1009 packageName, PackageManager.GET_META_DATA, userId); 1010 return iconDrawableFactory.getBadgedIcon(appInfo, userId); 1011 } catch (PackageManager.NameNotFoundException e) { 1012 return packageManager.getDefaultActivityIcon(); 1013 } 1014 } 1015 1016 /** Returns true if the current package is installed & enabled. */ isPackageEnabled(Context context, String packageName)1017 public static boolean isPackageEnabled(Context context, String packageName) { 1018 try { 1019 return context.getPackageManager().getApplicationInfo(packageName, 0).enabled; 1020 } catch (Exception e) { 1021 Log.e(TAG, "Error while retrieving application info for package " + packageName, e); 1022 } 1023 return false; 1024 } 1025 1026 /** Get {@link Resources} by subscription id if subscription id is valid. */ getResourcesForSubId(Context context, int subId)1027 public static Resources getResourcesForSubId(Context context, int subId) { 1028 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 1029 return SubscriptionManager.getResourcesForSubId(context, subId); 1030 } else { 1031 return context.getResources(); 1032 } 1033 } 1034 1035 /** 1036 * Returns true if SYSTEM_ALERT_WINDOW permission is available. 1037 * Starting from Q, SYSTEM_ALERT_WINDOW is disabled on low ram phones. 1038 */ isSystemAlertWindowEnabled(Context context)1039 public static boolean isSystemAlertWindowEnabled(Context context) { 1040 // SYSTEM_ALERT_WINDOW is disabled on on low ram devices starting from Q 1041 ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 1042 return !(am.isLowRamDevice() && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)); 1043 } 1044 1045 /** 1046 * Adds a shadow appear/disappear animation to action bar scroll. 1047 * 1048 * <p/> 1049 * This method must be called after {@link Fragment#onCreate(Bundle)}. 1050 */ setActionBarShadowAnimation(Activity activity, Lifecycle lifecycle, View scrollView)1051 public static void setActionBarShadowAnimation(Activity activity, Lifecycle lifecycle, 1052 View scrollView) { 1053 if (activity == null) { 1054 Log.w(TAG, "No activity, cannot style actionbar."); 1055 return; 1056 } 1057 final ActionBar actionBar = activity.getActionBar(); 1058 if (actionBar == null) { 1059 Log.w(TAG, "No actionbar, cannot style actionbar."); 1060 return; 1061 } 1062 actionBar.setElevation(0); 1063 1064 if (lifecycle != null && scrollView != null) { 1065 ActionBarShadowController.attachToView(activity, lifecycle, scrollView); 1066 } 1067 } 1068 } 1069