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.ActivityManager; 26 import android.app.AppGlobals; 27 import android.app.Fragment; 28 import android.app.IActivityManager; 29 import android.app.KeyguardManager; 30 import android.app.admin.DevicePolicyManager; 31 import android.content.ActivityNotFoundException; 32 import android.content.ComponentName; 33 import android.content.ContentResolver; 34 import android.content.Context; 35 import android.content.Intent; 36 import android.content.IntentFilter; 37 import android.content.pm.ApplicationInfo; 38 import android.content.pm.IPackageManager; 39 import android.content.pm.IntentFilterVerificationInfo; 40 import android.content.pm.PackageManager; 41 import android.content.pm.PackageManager.NameNotFoundException; 42 import android.content.pm.ResolveInfo; 43 import android.content.pm.UserInfo; 44 import android.content.res.Resources; 45 import android.content.res.TypedArray; 46 import android.database.Cursor; 47 import android.graphics.Bitmap; 48 import android.graphics.Canvas; 49 import android.graphics.drawable.BitmapDrawable; 50 import android.graphics.drawable.Drawable; 51 import android.graphics.drawable.VectorDrawable; 52 import android.hardware.fingerprint.FingerprintManager; 53 import android.net.ConnectivityManager; 54 import android.net.LinkProperties; 55 import android.net.Network; 56 import android.net.wifi.WifiManager; 57 import android.os.BatteryManager; 58 import android.os.Bundle; 59 import android.os.IBinder; 60 import android.os.INetworkManagementService; 61 import android.os.RemoteException; 62 import android.os.ServiceManager; 63 import android.os.UserHandle; 64 import android.os.UserManager; 65 import android.os.storage.StorageManager; 66 import android.os.storage.VolumeInfo; 67 import android.preference.PreferenceFrameLayout; 68 import android.provider.ContactsContract.CommonDataKinds; 69 import android.provider.ContactsContract.Contacts; 70 import android.provider.ContactsContract.Data; 71 import android.provider.ContactsContract.Profile; 72 import android.provider.ContactsContract.RawContacts; 73 import android.provider.Settings; 74 import android.support.annotation.StringRes; 75 import android.support.v7.preference.Preference; 76 import android.support.v7.preference.PreferenceGroup; 77 import android.telephony.TelephonyManager; 78 import android.text.Spannable; 79 import android.text.SpannableString; 80 import android.text.TextUtils; 81 import android.text.format.DateUtils; 82 import android.text.style.TtsSpan; 83 import android.util.ArraySet; 84 import android.util.IconDrawableFactory; 85 import android.util.Log; 86 import android.view.LayoutInflater; 87 import android.view.View; 88 import android.view.ViewGroup; 89 import android.widget.EditText; 90 import android.widget.ListView; 91 import android.widget.TabWidget; 92 93 import com.android.internal.app.UnlaunchableAppActivity; 94 import com.android.internal.util.ArrayUtils; 95 import com.android.internal.widget.LockPatternUtils; 96 import com.android.settings.password.ChooseLockSettingsHelper; 97 98 import java.net.InetAddress; 99 import java.util.Iterator; 100 import java.util.List; 101 import java.util.Locale; 102 103 public final class Utils extends com.android.settingslib.Utils { 104 105 private static final String TAG = "Settings"; 106 107 /** 108 * Set the preference's title to the matching activity's label. 109 */ 110 public static final int UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY = 1; 111 112 /** 113 * Color spectrum to use to indicate badness. 0 is completely transparent (no data), 114 * 1 is most bad (red), the last value is least bad (green). 115 */ 116 public static final int[] BADNESS_COLORS = new int[] { 117 0x00000000, 0xffc43828, 0xffe54918, 0xfff47b00, 118 0xfffabf2c, 0xff679e37, 0xff0a7f42 119 }; 120 121 private static final String SETTINGS_PACKAGE_NAME = "com.android.settings"; 122 123 public static final String OS_PKG = "os"; 124 125 /** 126 * Finds a matching activity for a preference's intent. If a matching 127 * activity is not found, it will remove the preference. 128 * 129 * @param context The context. 130 * @param parentPreferenceGroup The preference group that contains the 131 * preference whose intent is being resolved. 132 * @param preferenceKey The key of the preference whose intent is being 133 * resolved. 134 * @param flags 0 or one or more of 135 * {@link #UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY} 136 * . 137 * @return Whether an activity was found. If false, the preference was 138 * removed. 139 */ 140 public static boolean updatePreferenceToSpecificActivityOrRemove(Context context, 141 PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags) { 142 143 Preference preference = parentPreferenceGroup.findPreference(preferenceKey); 144 if (preference == null) { 145 return false; 146 } 147 148 Intent intent = preference.getIntent(); 149 if (intent != null) { 150 // Find the activity that is in the system image 151 PackageManager pm = context.getPackageManager(); 152 List<ResolveInfo> list = pm.queryIntentActivities(intent, 0); 153 int listSize = list.size(); 154 for (int i = 0; i < listSize; i++) { 155 ResolveInfo resolveInfo = list.get(i); 156 if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) 157 != 0) { 158 159 // Replace the intent with this specific activity 160 preference.setIntent(new Intent().setClassName( 161 resolveInfo.activityInfo.packageName, 162 resolveInfo.activityInfo.name)); 163 164 if ((flags & UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY) != 0) { 165 // Set the preference title to the activity's label 166 preference.setTitle(resolveInfo.loadLabel(pm)); 167 } 168 169 return true; 170 } 171 } 172 } 173 174 // Did not find a matching activity, so remove the preference 175 parentPreferenceGroup.removePreference(preference); 176 177 return false; 178 } 179 180 /** 181 * Returns the UserManager for a given context 182 * 183 * @throws IllegalStateException if no UserManager could be retrieved. 184 */ 185 public static UserManager getUserManager(Context context) { 186 UserManager um = UserManager.get(context); 187 if (um == null) { 188 throw new IllegalStateException("Unable to load UserManager"); 189 } 190 return um; 191 } 192 193 /** 194 * Returns true if Monkey is running. 195 */ 196 public static boolean isMonkeyRunning() { 197 return ActivityManager.isUserAMonkey(); 198 } 199 200 /** 201 * Returns whether the device is voice-capable (meaning, it is also a phone). 202 */ 203 public static boolean isVoiceCapable(Context context) { 204 TelephonyManager telephony = 205 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 206 return telephony != null && telephony.isVoiceCapable(); 207 } 208 209 /** 210 * Returns the WIFI IP Addresses, if any, taking into account IPv4 and IPv6 style addresses. 211 * @param context the application context 212 * @return the formatted and newline-separated IP addresses, or null if none. 213 */ 214 public static String getWifiIpAddresses(Context context) { 215 WifiManager wifiManager = context.getSystemService(WifiManager.class); 216 Network currentNetwork = wifiManager.getCurrentNetwork(); 217 if (currentNetwork != null) { 218 ConnectivityManager cm = (ConnectivityManager) 219 context.getSystemService(Context.CONNECTIVITY_SERVICE); 220 LinkProperties prop = cm.getLinkProperties(currentNetwork); 221 return formatIpAddresses(prop); 222 } 223 return null; 224 } 225 226 private static String formatIpAddresses(LinkProperties prop) { 227 if (prop == null) return null; 228 Iterator<InetAddress> iter = prop.getAllAddresses().iterator(); 229 // If there are no entries, return null 230 if (!iter.hasNext()) return null; 231 // Concatenate all available addresses, comma separated 232 String addresses = ""; 233 while (iter.hasNext()) { 234 addresses += iter.next().getHostAddress(); 235 if (iter.hasNext()) addresses += "\n"; 236 } 237 return addresses; 238 } 239 240 public static Locale createLocaleFromString(String localeStr) { 241 // TODO: is there a better way to actually construct a locale that will match? 242 // The main problem is, on top of Java specs, locale.toString() and 243 // new Locale(locale.toString()).toString() do not return equal() strings in 244 // many cases, because the constructor takes the only string as the language 245 // code. So : new Locale("en", "US").toString() => "en_US" 246 // And : new Locale("en_US").toString() => "en_us" 247 if (null == localeStr) 248 return Locale.getDefault(); 249 String[] brokenDownLocale = localeStr.split("_", 3); 250 // split may not return a 0-length array. 251 if (1 == brokenDownLocale.length) { 252 return new Locale(brokenDownLocale[0]); 253 } else if (2 == brokenDownLocale.length) { 254 return new Locale(brokenDownLocale[0], brokenDownLocale[1]); 255 } else { 256 return new Locale(brokenDownLocale[0], brokenDownLocale[1], brokenDownLocale[2]); 257 } 258 } 259 260 public static boolean isBatteryPresent(Intent batteryChangedIntent) { 261 return batteryChangedIntent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true); 262 } 263 264 public static String getBatteryPercentage(Intent batteryChangedIntent) { 265 return formatPercentage(getBatteryLevel(batteryChangedIntent)); 266 } 267 268 /** 269 * Prepare a custom preferences layout, moving padding to {@link ListView} 270 * when outside scrollbars are requested. Usually used to display 271 * {@link ListView} and {@link TabWidget} with correct padding. 272 */ 273 public static void prepareCustomPreferencesList( 274 ViewGroup parent, View child, View list, boolean ignoreSidePadding) { 275 final boolean movePadding = list.getScrollBarStyle() == View.SCROLLBARS_OUTSIDE_OVERLAY; 276 if (movePadding) { 277 final Resources res = list.getResources(); 278 final int paddingSide = res.getDimensionPixelSize(R.dimen.settings_side_margin); 279 final int paddingBottom = res.getDimensionPixelSize( 280 com.android.internal.R.dimen.preference_fragment_padding_bottom); 281 282 if (parent instanceof PreferenceFrameLayout) { 283 ((PreferenceFrameLayout.LayoutParams) child.getLayoutParams()).removeBorders = true; 284 285 final int effectivePaddingSide = ignoreSidePadding ? 0 : paddingSide; 286 list.setPaddingRelative(effectivePaddingSide, 0, effectivePaddingSide, paddingBottom); 287 } else { 288 list.setPaddingRelative(paddingSide, 0, paddingSide, paddingBottom); 289 } 290 } 291 } 292 293 public static void forceCustomPadding(View view, boolean additive) { 294 final Resources res = view.getResources(); 295 final int paddingSide = res.getDimensionPixelSize(R.dimen.settings_side_margin); 296 297 final int paddingStart = paddingSide + (additive ? view.getPaddingStart() : 0); 298 final int paddingEnd = paddingSide + (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 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 313 private static String getShorterNameIfPossible(Context context) { 314 final String given = getLocalProfileGivenName(context); 315 return !TextUtils.isEmpty(given) ? given : getProfileDisplayName(context); 316 } 317 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 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 379 public static boolean hasMultipleUsers(Context context) { 380 return ((UserManager) context.getSystemService(Context.USER_SERVICE)) 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 */ 388 public static UserHandle getManagedProfile(UserManager userManager) { 389 List<UserHandle> userProfiles = userManager.getUserProfiles(); 390 final int count = userProfiles.size(); 391 for (int i = 0; i < count; i++) { 392 final UserHandle profile = userProfiles.get(i); 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 527 public static boolean showSimCardTile(Context context) { 528 final TelephonyManager tm = 529 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 530 531 return tm.getSimCount() > 1; 532 } 533 534 /** 535 * Queries for the UserInfo of a user. Returns null if the user doesn't exist (was removed). 536 * @param userManager Instance of UserManager 537 * @param checkUser The user to check the existence of. 538 * @return UserInfo of the user or null for non-existent user. 539 */ 540 public static UserInfo getExistingUser(UserManager userManager, UserHandle checkUser) { 541 final List<UserInfo> users = userManager.getUsers(true /* excludeDying */); 542 final int checkUserId = checkUser.getIdentifier(); 543 for (UserInfo user : users) { 544 if (user.id == checkUserId) { 545 return user; 546 } 547 } 548 return null; 549 } 550 551 public static View inflateCategoryHeader(LayoutInflater inflater, ViewGroup parent) { 552 final TypedArray a = inflater.getContext().obtainStyledAttributes(null, 553 com.android.internal.R.styleable.Preference, 554 com.android.internal.R.attr.preferenceCategoryStyle, 0); 555 final int resId = a.getResourceId(com.android.internal.R.styleable.Preference_layout, 556 0); 557 a.recycle(); 558 return inflater.inflate(resId, parent, false); 559 } 560 561 public static ArraySet<String> getHandledDomains(PackageManager pm, String packageName) { 562 List<IntentFilterVerificationInfo> iviList = pm.getIntentFilterVerifications(packageName); 563 List<IntentFilter> filters = pm.getAllIntentFilters(packageName); 564 565 ArraySet<String> result = new ArraySet<>(); 566 if (iviList != null && iviList.size() > 0) { 567 for (IntentFilterVerificationInfo ivi : iviList) { 568 for (String host : ivi.getDomains()) { 569 result.add(host); 570 } 571 } 572 } 573 if (filters != null && filters.size() > 0) { 574 for (IntentFilter filter : filters) { 575 if (filter.hasCategory(Intent.CATEGORY_BROWSABLE) 576 && (filter.hasDataScheme(IntentFilter.SCHEME_HTTP) || 577 filter.hasDataScheme(IntentFilter.SCHEME_HTTPS))) { 578 result.addAll(filter.getHostsList()); 579 } 580 } 581 } 582 return result; 583 } 584 585 /** 586 * Returns the application info of the currently installed MDM package. 587 */ 588 public static ApplicationInfo getAdminApplicationInfo(Context context, int profileId) { 589 DevicePolicyManager dpm = 590 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); 591 ComponentName mdmPackage = dpm.getProfileOwnerAsUser(profileId); 592 if (mdmPackage == null) { 593 return null; 594 } 595 String mdmPackageName = mdmPackage.getPackageName(); 596 try { 597 IPackageManager ipm = AppGlobals.getPackageManager(); 598 ApplicationInfo mdmApplicationInfo = 599 ipm.getApplicationInfo(mdmPackageName, 0, profileId); 600 return mdmApplicationInfo; 601 } catch (RemoteException e) { 602 Log.e(TAG, "Error while retrieving application info for package " + mdmPackageName 603 + ", userId " + profileId, e); 604 return null; 605 } 606 } 607 608 public static boolean isBandwidthControlEnabled() { 609 final INetworkManagementService netManager = INetworkManagementService.Stub 610 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); 611 try { 612 return netManager.isBandwidthControlEnabled(); 613 } catch (RemoteException e) { 614 return false; 615 } 616 } 617 618 /** 619 * Returns an accessible SpannableString. 620 * @param displayText the text to display 621 * @param accessibileText the text text-to-speech engines should read 622 */ 623 public static SpannableString createAccessibleSequence(CharSequence displayText, 624 String accessibileText) { 625 SpannableString str = new SpannableString(displayText); 626 str.setSpan(new TtsSpan.TextBuilder(accessibileText).build(), 0, 627 displayText.length(), 628 Spannable.SPAN_INCLUSIVE_INCLUSIVE); 629 return str; 630 } 631 632 /** 633 * Returns the user id present in the bundle with 634 * {@link Intent#EXTRA_USER_ID} if it belongs to the current user. 635 * 636 * @throws SecurityException if the given userId does not belong to the 637 * current user group. 638 */ 639 public static int getUserIdFromBundle(Context context, Bundle bundle) { 640 return getUserIdFromBundle(context, bundle, false); 641 } 642 643 /** 644 * Returns the user id present in the bundle with 645 * {@link Intent#EXTRA_USER_ID} if it belongs to the current user. 646 * 647 * @param isInternal indicating if the caller is "internal" to the system, 648 * meaning we're willing to trust extras like 649 * {@link ChooseLockSettingsHelper#EXTRA_ALLOW_ANY_USER}. 650 * @throws SecurityException if the given userId does not belong to the 651 * current user group. 652 */ 653 public static int getUserIdFromBundle(Context context, Bundle bundle, boolean isInternal) { 654 if (bundle == null) { 655 return getCredentialOwnerUserId(context); 656 } 657 final boolean allowAnyUser = isInternal 658 && bundle.getBoolean(ChooseLockSettingsHelper.EXTRA_ALLOW_ANY_USER, false); 659 int userId = bundle.getInt(Intent.EXTRA_USER_ID, UserHandle.myUserId()); 660 if (userId == LockPatternUtils.USER_FRP) { 661 return allowAnyUser ? userId : enforceSystemUser(context, userId); 662 } else { 663 return allowAnyUser ? userId : enforceSameOwner(context, userId); 664 } 665 } 666 667 /** 668 * Returns the given user id if the current user is the system user. 669 * 670 * @throws SecurityException if the current user is not the system user. 671 */ 672 public static int enforceSystemUser(Context context, int userId) { 673 if (UserHandle.myUserId() == UserHandle.USER_SYSTEM) { 674 return userId; 675 } 676 throw new SecurityException("Given user id " + userId + " must only be used from " 677 + "USER_SYSTEM, but current user is " + UserHandle.myUserId()); 678 } 679 680 /** 681 * Returns the given user id if it belongs to the current user. 682 * 683 * @throws SecurityException if the given userId does not belong to the current user group. 684 */ 685 public static int enforceSameOwner(Context context, int userId) { 686 final UserManager um = getUserManager(context); 687 final int[] profileIds = um.getProfileIdsWithDisabled(UserHandle.myUserId()); 688 if (ArrayUtils.contains(profileIds, userId)) { 689 return userId; 690 } 691 throw new SecurityException("Given user id " + userId + " does not belong to user " 692 + UserHandle.myUserId()); 693 } 694 695 /** 696 * Returns the effective credential owner of the calling user. 697 */ 698 public static int getCredentialOwnerUserId(Context context) { 699 return getCredentialOwnerUserId(context, UserHandle.myUserId()); 700 } 701 702 /** 703 * Returns the user id of the credential owner of the given user id. 704 */ 705 public static int getCredentialOwnerUserId(Context context, int userId) { 706 UserManager um = getUserManager(context); 707 return um.getCredentialOwnerProfile(userId); 708 } 709 710 private static final StringBuilder sBuilder = new StringBuilder(50); 711 private static final java.util.Formatter sFormatter = new java.util.Formatter( 712 sBuilder, Locale.getDefault()); 713 714 public static String formatDateRange(Context context, long start, long end) { 715 final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH; 716 717 synchronized (sBuilder) { 718 sBuilder.setLength(0); 719 return DateUtils.formatDateRange(context, sFormatter, start, end, flags, null) 720 .toString(); 721 } 722 } 723 724 public static boolean isDeviceProvisioned(Context context) { 725 return Settings.Global.getInt(context.getContentResolver(), 726 Settings.Global.DEVICE_PROVISIONED, 0) != 0; 727 } 728 729 public static boolean startQuietModeDialogIfNecessary(Context context, UserManager um, 730 int userId) { 731 if (um.isQuietModeEnabled(UserHandle.of(userId))) { 732 final Intent intent = UnlaunchableAppActivity.createInQuietModeDialogIntent(userId); 733 context.startActivity(intent); 734 return true; 735 } 736 return false; 737 } 738 739 public static boolean unlockWorkProfileIfNecessary(Context context, int userId) { 740 try { 741 if (!ActivityManager.getService().isUserRunning(userId, 742 ActivityManager.FLAG_AND_LOCKED)) { 743 return false; 744 } 745 } catch (RemoteException e) { 746 return false; 747 } 748 if (!(new LockPatternUtils(context)).isSecure(userId)) { 749 return false; 750 } 751 return confirmWorkProfileCredentials(context, userId); 752 } 753 754 private static boolean confirmWorkProfileCredentials(Context context, int userId) { 755 final KeyguardManager km = (KeyguardManager) context.getSystemService( 756 Context.KEYGUARD_SERVICE); 757 final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null, userId); 758 if (unlockIntent != null) { 759 context.startActivity(unlockIntent); 760 return true; 761 } else { 762 return false; 763 } 764 } 765 766 public static CharSequence getApplicationLabel(Context context, String packageName) { 767 try { 768 final ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo( 769 packageName, 770 PackageManager.MATCH_DISABLED_COMPONENTS 771 | PackageManager.MATCH_ANY_USER); 772 return appInfo.loadLabel(context.getPackageManager()); 773 } catch (PackageManager.NameNotFoundException e) { 774 Log.w(TAG, "Unable to find info for package: " + packageName); 775 } 776 return null; 777 } 778 779 public static boolean isPackageDirectBootAware(Context context, String packageName) { 780 try { 781 final ApplicationInfo ai = context.getPackageManager().getApplicationInfo( 782 packageName, 0); 783 return ai.isDirectBootAware() || ai.isPartiallyDirectBootAware(); 784 } catch (NameNotFoundException ignored) { 785 } 786 return false; 787 } 788 789 /** 790 * Returns a context created from the given context for the given user, or null if it fails 791 */ 792 public static Context createPackageContextAsUser(Context context, int userId) { 793 try { 794 return context.createPackageContextAsUser( 795 context.getPackageName(), 0 /* flags */, UserHandle.of(userId)); 796 } catch (PackageManager.NameNotFoundException e) { 797 Log.e(TAG, "Failed to create user context", e); 798 } 799 return null; 800 } 801 802 public static FingerprintManager getFingerprintManagerOrNull(Context context) { 803 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { 804 return (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE); 805 } else { 806 return null; 807 } 808 } 809 810 public static boolean hasFingerprintHardware(Context context) { 811 FingerprintManager fingerprintManager = getFingerprintManagerOrNull(context); 812 return fingerprintManager != null && fingerprintManager.isHardwareDetected(); 813 } 814 815 /** 816 * Launches an intent which may optionally have a user id defined. 817 * @param fragment Fragment to use to launch the activity. 818 * @param intent Intent to launch. 819 */ 820 public static void launchIntent(Fragment fragment, Intent intent) { 821 try { 822 final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1); 823 824 if (userId == -1) { 825 fragment.startActivity(intent); 826 } else { 827 fragment.getActivity().startActivityAsUser(intent, new UserHandle(userId)); 828 } 829 } catch (ActivityNotFoundException e) { 830 Log.w(TAG, "No activity found for " + intent); 831 } 832 } 833 834 public static boolean isDemoUser(Context context) { 835 return UserManager.isDeviceInDemoMode(context) && getUserManager(context).isDemoUser(); 836 } 837 838 public static ComponentName getDeviceOwnerComponent(Context context) { 839 final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( 840 Context.DEVICE_POLICY_SERVICE); 841 return dpm.getDeviceOwnerComponentOnAnyUser(); 842 } 843 844 /** 845 * Returns if a given user is a profile of another user. 846 * @param user The user whose profiles wibe checked. 847 * @param profile The (potential) profile. 848 * @return if the profile is actually a profile 849 */ 850 public static boolean isProfileOf(UserInfo user, UserInfo profile) { 851 return user.id == profile.id || 852 (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID 853 && user.profileGroupId == profile.profileGroupId); 854 } 855 856 /** 857 * Tries to initalize a volume with the given bundle. If it is a valid, private, and readable 858 * {@link VolumeInfo}, it is returned. If it is not valid, null is returned. 859 */ 860 @Nullable 861 public static VolumeInfo maybeInitializeVolume(StorageManager sm, Bundle bundle) { 862 final String volumeId = bundle.getString(VolumeInfo.EXTRA_VOLUME_ID, 863 VolumeInfo.ID_PRIVATE_INTERNAL); 864 VolumeInfo volume = sm.findVolumeById(volumeId); 865 return isVolumeValid(volume) ? volume : null; 866 } 867 868 /** 869 * Return {@code true} if the supplied package is device owner or profile owner of at 870 * least one user. 871 * @param userManager used to get profile owner app for each user 872 * @param devicePolicyManager used to check whether it is device owner app 873 * @param packageName package to check about 874 */ 875 public static boolean isProfileOrDeviceOwner(UserManager userManager, 876 DevicePolicyManager devicePolicyManager, String packageName) { 877 List<UserInfo> userInfos = userManager.getUsers(); 878 if (devicePolicyManager.isDeviceOwnerAppOnAnyUser(packageName)) { 879 return true; 880 } 881 for (int i = 0, size = userInfos.size(); i < size; i++) { 882 ComponentName cn = devicePolicyManager.getProfileOwnerAsUser(userInfos.get(i).id); 883 if (cn != null && cn.getPackageName().equals(packageName)) { 884 return true; 885 } 886 } 887 return false; 888 } 889 890 /** 891 * Return the resource id to represent the install status for an app 892 */ 893 @StringRes 894 public static int getInstallationStatus(ApplicationInfo info) { 895 if ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { 896 return R.string.not_installed; 897 } 898 return info.enabled ? R.string.installed : R.string.disabled; 899 } 900 901 private static boolean isVolumeValid(VolumeInfo volume) { 902 return (volume != null) && (volume.getType() == VolumeInfo.TYPE_PRIVATE) 903 && volume.isMountedReadable(); 904 } 905 906 public static void setEditTextCursorPosition(EditText editText) { 907 editText.setSelection(editText.getText().length()); 908 } 909 910 /** 911 * Sets the preference icon with a drawable that is scaled down to to avoid crashing Settings if 912 * it's too big. 913 */ 914 public static void setSafeIcon(Preference pref, Drawable icon) { 915 Drawable safeIcon = icon; 916 if ((icon != null) && !(icon instanceof VectorDrawable)) { 917 safeIcon = getSafeDrawable(icon, 500, 500); 918 } 919 pref.setIcon(safeIcon); 920 } 921 922 /** 923 * Gets a drawable with a limited size to avoid crashing Settings if it's too big. 924 * 925 * @param original original drawable, typically an app icon. 926 * @param maxWidth maximum width, in pixels. 927 * @param maxHeight maximum height, in pixels. 928 */ 929 public static Drawable getSafeDrawable(Drawable original, int maxWidth, int maxHeight) { 930 final int actualWidth = original.getMinimumWidth(); 931 final int actualHeight = original.getMinimumHeight(); 932 933 if (actualWidth <= maxWidth && actualHeight <= maxHeight) { 934 return original; 935 } 936 937 float scaleWidth = ((float) maxWidth) / actualWidth; 938 float scaleHeight = ((float) maxHeight) / actualHeight; 939 float scale = Math.min(scaleWidth, scaleHeight); 940 final int width = (int) (actualWidth * scale); 941 final int height = (int) (actualHeight * scale); 942 943 final Bitmap bitmap; 944 if (original instanceof BitmapDrawable) { 945 bitmap = Bitmap.createScaledBitmap(((BitmapDrawable) original).getBitmap(), width, 946 height, false); 947 } else { 948 bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 949 final Canvas canvas = new Canvas(bitmap); 950 original.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 951 original.draw(canvas); 952 } 953 return new BitmapDrawable(null, bitmap); 954 } 955 956 /** 957 * Get the {@link Drawable} that represents the app icon 958 */ 959 public static Drawable getBadgedIcon(IconDrawableFactory iconDrawableFactory, 960 PackageManager packageManager, String packageName, int userId) { 961 try { 962 final ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser( 963 packageName, PackageManager.GET_META_DATA, userId); 964 return iconDrawableFactory.getBadgedIcon(appInfo, userId); 965 } catch (PackageManager.NameNotFoundException e) { 966 return packageManager.getDefaultActivityIcon(); 967 } 968 } 969 } 970