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