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