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.AlertDialog; 27 import android.app.AppGlobals; 28 import android.app.AppOpsManager; 29 import android.app.Dialog; 30 import android.app.Fragment; 31 import android.app.IActivityManager; 32 import android.app.KeyguardManager; 33 import android.app.admin.DevicePolicyManager; 34 import android.content.ActivityNotFoundException; 35 import android.content.ComponentName; 36 import android.content.ContentResolver; 37 import android.content.Context; 38 import android.content.DialogInterface; 39 import android.content.Intent; 40 import android.content.IntentFilter; 41 import android.content.pm.ApplicationInfo; 42 import android.content.pm.IPackageManager; 43 import android.content.pm.IntentFilterVerificationInfo; 44 import android.content.pm.PackageManager; 45 import android.content.pm.PackageManager.NameNotFoundException; 46 import android.content.pm.ResolveInfo; 47 import android.content.pm.UserInfo; 48 import android.content.res.Resources; 49 import android.content.res.TypedArray; 50 import android.database.Cursor; 51 import android.graphics.Bitmap; 52 import android.graphics.BitmapFactory; 53 import android.hardware.fingerprint.FingerprintManager; 54 import android.icu.text.MeasureFormat; 55 import android.icu.util.Measure; 56 import android.icu.util.MeasureUnit; 57 import android.net.ConnectivityManager; 58 import android.net.LinkProperties; 59 import android.net.Network; 60 import android.net.Uri; 61 import android.net.wifi.WifiManager; 62 import android.os.BatteryManager; 63 import android.os.Bundle; 64 import android.os.IBinder; 65 import android.os.INetworkManagementService; 66 import android.os.Looper; 67 import android.os.RemoteException; 68 import android.os.ServiceManager; 69 import android.os.UserHandle; 70 import android.os.UserManager; 71 import android.os.storage.StorageManager; 72 import android.os.storage.VolumeInfo; 73 import android.preference.PreferenceFrameLayout; 74 import android.provider.ContactsContract.CommonDataKinds; 75 import android.provider.ContactsContract.Contacts; 76 import android.provider.ContactsContract.Data; 77 import android.provider.ContactsContract.Profile; 78 import android.provider.ContactsContract.RawContacts; 79 import android.provider.Settings; 80 import android.support.annotation.StringRes; 81 import android.support.v7.preference.Preference; 82 import android.support.v7.preference.PreferenceGroup; 83 import android.support.v7.preference.PreferenceManager; 84 import android.support.v7.preference.PreferenceScreen; 85 import android.telephony.TelephonyManager; 86 import android.text.Spannable; 87 import android.text.SpannableString; 88 import android.text.SpannableStringBuilder; 89 import android.text.Spanned; 90 import android.text.TextUtils; 91 import android.text.format.DateUtils; 92 import android.text.style.TtsSpan; 93 import android.util.ArraySet; 94 import android.util.Log; 95 import android.util.SparseArray; 96 import android.util.TypedValue; 97 import android.view.LayoutInflater; 98 import android.view.View; 99 import android.view.ViewGroup; 100 import android.view.animation.Animation; 101 import android.view.animation.Animation.AnimationListener; 102 import android.view.animation.AnimationUtils; 103 import android.widget.ListView; 104 import android.widget.TabWidget; 105 106 import com.android.internal.app.UnlaunchableAppActivity; 107 import com.android.internal.util.ArrayUtils; 108 import com.android.internal.util.UserIcons; 109 import com.android.internal.widget.LockPatternUtils; 110 import com.android.settings.enterprise.DevicePolicyManagerWrapper; 111 import com.android.settings.password.FingerprintManagerWrapper; 112 import com.android.settings.password.IFingerprintManager; 113 114 import java.io.IOException; 115 import java.io.InputStream; 116 import java.net.InetAddress; 117 import java.util.ArrayList; 118 import java.util.Iterator; 119 import java.util.List; 120 import java.util.Locale; 121 122 public final class Utils extends com.android.settingslib.Utils { 123 124 private static final String TAG = "Settings"; 125 126 /** 127 * Set the preference's title to the matching activity's label. 128 */ 129 public static final int UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY = 1; 130 131 /** 132 * The opacity level of a disabled icon. 133 */ 134 public static final float DISABLED_ALPHA = 0.4f; 135 136 /** 137 * Color spectrum to use to indicate badness. 0 is completely transparent (no data), 138 * 1 is most bad (red), the last value is least bad (green). 139 */ 140 public static final int[] BADNESS_COLORS = new int[] { 141 0x00000000, 0xffc43828, 0xffe54918, 0xfff47b00, 142 0xfffabf2c, 0xff679e37, 0xff0a7f42 143 }; 144 145 private static final String SETTINGS_PACKAGE_NAME = "com.android.settings"; 146 147 private static final int SECONDS_PER_MINUTE = 60; 148 private static final int SECONDS_PER_HOUR = 60 * 60; 149 private static final int SECONDS_PER_DAY = 24 * 60 * 60; 150 151 public static final String OS_PKG = "os"; 152 153 private static SparseArray<Bitmap> sDarkDefaultUserBitmapCache = new SparseArray<Bitmap>(); 154 155 /** 156 * Finds a matching activity for a preference's intent. If a matching 157 * activity is not found, it will remove the preference. 158 * 159 * @param context The context. 160 * @param parentPreferenceGroup The preference group that contains the 161 * preference whose intent is being resolved. 162 * @param preferenceKey The key of the preference whose intent is being 163 * resolved. 164 * @param flags 0 or one or more of 165 * {@link #UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY} 166 * . 167 * @return Whether an activity was found. If false, the preference was 168 * removed. 169 */ 170 public static boolean updatePreferenceToSpecificActivityOrRemove(Context context, 171 PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags) { 172 173 Preference preference = parentPreferenceGroup.findPreference(preferenceKey); 174 if (preference == null) { 175 return false; 176 } 177 178 Intent intent = preference.getIntent(); 179 if (intent != null) { 180 // Find the activity that is in the system image 181 PackageManager pm = context.getPackageManager(); 182 List<ResolveInfo> list = pm.queryIntentActivities(intent, 0); 183 int listSize = list.size(); 184 for (int i = 0; i < listSize; i++) { 185 ResolveInfo resolveInfo = list.get(i); 186 if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) 187 != 0) { 188 189 // Replace the intent with this specific activity 190 preference.setIntent(new Intent().setClassName( 191 resolveInfo.activityInfo.packageName, 192 resolveInfo.activityInfo.name)); 193 194 if ((flags & UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY) != 0) { 195 // Set the preference title to the activity's label 196 preference.setTitle(resolveInfo.loadLabel(pm)); 197 } 198 199 return true; 200 } 201 } 202 } 203 204 // Did not find a matching activity, so remove the preference 205 parentPreferenceGroup.removePreference(preference); 206 207 return false; 208 } 209 210 /** 211 * Returns the UserManager for a given context 212 * 213 * @throws IllegalStateException if no UserManager could be retrieved. 214 */ 215 public static UserManager getUserManager(Context context) { 216 UserManager um = UserManager.get(context); 217 if (um == null) { 218 throw new IllegalStateException("Unable to load UserManager"); 219 } 220 return um; 221 } 222 223 /** 224 * Returns true if Monkey is running. 225 */ 226 public static boolean isMonkeyRunning() { 227 return ActivityManager.isUserAMonkey(); 228 } 229 230 /** 231 * Returns whether the device is voice-capable (meaning, it is also a phone). 232 */ 233 public static boolean isVoiceCapable(Context context) { 234 TelephonyManager telephony = 235 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 236 return telephony != null && telephony.isVoiceCapable(); 237 } 238 239 public static boolean isWifiOnly(Context context) { 240 ConnectivityManager cm = (ConnectivityManager)context.getSystemService( 241 Context.CONNECTIVITY_SERVICE); 242 return (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false); 243 } 244 245 /** 246 * Returns the WIFI IP Addresses, if any, taking into account IPv4 and IPv6 style addresses. 247 * @param context the application context 248 * @return the formatted and newline-separated IP addresses, or null if none. 249 */ 250 public static String getWifiIpAddresses(Context context) { 251 WifiManager wifiManager = context.getSystemService(WifiManager.class); 252 Network currentNetwork = wifiManager.getCurrentNetwork(); 253 if (currentNetwork != null) { 254 ConnectivityManager cm = (ConnectivityManager) 255 context.getSystemService(Context.CONNECTIVITY_SERVICE); 256 LinkProperties prop = cm.getLinkProperties(currentNetwork); 257 return formatIpAddresses(prop); 258 } 259 return null; 260 } 261 262 /** 263 * Returns the default link's IP addresses, if any, taking into account IPv4 and IPv6 style 264 * addresses. 265 * @return the formatted and newline-separated IP addresses, or null if none. 266 */ 267 public static String getDefaultIpAddresses(ConnectivityManager cm) { 268 LinkProperties prop = cm.getActiveLinkProperties(); 269 return formatIpAddresses(prop); 270 } 271 272 private static String formatIpAddresses(LinkProperties prop) { 273 if (prop == null) return null; 274 Iterator<InetAddress> iter = prop.getAllAddresses().iterator(); 275 // If there are no entries, return null 276 if (!iter.hasNext()) return null; 277 // Concatenate all available addresses, comma separated 278 String addresses = ""; 279 while (iter.hasNext()) { 280 addresses += iter.next().getHostAddress(); 281 if (iter.hasNext()) addresses += "\n"; 282 } 283 return addresses; 284 } 285 286 public static Locale createLocaleFromString(String localeStr) { 287 // TODO: is there a better way to actually construct a locale that will match? 288 // The main problem is, on top of Java specs, locale.toString() and 289 // new Locale(locale.toString()).toString() do not return equal() strings in 290 // many cases, because the constructor takes the only string as the language 291 // code. So : new Locale("en", "US").toString() => "en_US" 292 // And : new Locale("en_US").toString() => "en_us" 293 if (null == localeStr) 294 return Locale.getDefault(); 295 String[] brokenDownLocale = localeStr.split("_", 3); 296 // split may not return a 0-length array. 297 if (1 == brokenDownLocale.length) { 298 return new Locale(brokenDownLocale[0]); 299 } else if (2 == brokenDownLocale.length) { 300 return new Locale(brokenDownLocale[0], brokenDownLocale[1]); 301 } else { 302 return new Locale(brokenDownLocale[0], brokenDownLocale[1], brokenDownLocale[2]); 303 } 304 } 305 306 public static boolean isBatteryPresent(Intent batteryChangedIntent) { 307 return batteryChangedIntent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true); 308 } 309 310 public static String getBatteryPercentage(Intent batteryChangedIntent) { 311 return formatPercentage(getBatteryLevel(batteryChangedIntent)); 312 } 313 314 /** 315 * Prepare a custom preferences layout, moving padding to {@link ListView} 316 * when outside scrollbars are requested. Usually used to display 317 * {@link ListView} and {@link TabWidget} with correct padding. 318 */ 319 public static void prepareCustomPreferencesList( 320 ViewGroup parent, View child, View list, boolean ignoreSidePadding) { 321 final boolean movePadding = list.getScrollBarStyle() == View.SCROLLBARS_OUTSIDE_OVERLAY; 322 if (movePadding) { 323 final Resources res = list.getResources(); 324 final int paddingSide = res.getDimensionPixelSize(R.dimen.settings_side_margin); 325 final int paddingBottom = res.getDimensionPixelSize( 326 com.android.internal.R.dimen.preference_fragment_padding_bottom); 327 328 if (parent instanceof PreferenceFrameLayout) { 329 ((PreferenceFrameLayout.LayoutParams) child.getLayoutParams()).removeBorders = true; 330 331 final int effectivePaddingSide = ignoreSidePadding ? 0 : paddingSide; 332 list.setPaddingRelative(effectivePaddingSide, 0, effectivePaddingSide, paddingBottom); 333 } else { 334 list.setPaddingRelative(paddingSide, 0, paddingSide, paddingBottom); 335 } 336 } 337 } 338 339 public static void forceCustomPadding(View view, boolean additive) { 340 final Resources res = view.getResources(); 341 final int paddingSide = res.getDimensionPixelSize(R.dimen.settings_side_margin); 342 343 final int paddingStart = paddingSide + (additive ? view.getPaddingStart() : 0); 344 final int paddingEnd = paddingSide + (additive ? view.getPaddingEnd() : 0); 345 final int paddingBottom = res.getDimensionPixelSize( 346 com.android.internal.R.dimen.preference_fragment_padding_bottom); 347 348 view.setPaddingRelative(paddingStart, 0, paddingEnd, paddingBottom); 349 } 350 351 /* Used by UserSettings as well. Call this on a non-ui thread. */ 352 public static void copyMeProfilePhoto(Context context, UserInfo user) { 353 Uri contactUri = Profile.CONTENT_URI; 354 355 int userId = user != null ? user.id : UserHandle.myUserId(); 356 357 InputStream avatarDataStream = Contacts.openContactPhotoInputStream( 358 context.getContentResolver(), 359 contactUri, true); 360 // If there's no profile photo, assign a default avatar 361 if (avatarDataStream == null) { 362 assignDefaultPhoto(context, userId); 363 return; 364 } 365 366 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); 367 Bitmap icon = BitmapFactory.decodeStream(avatarDataStream); 368 um.setUserIcon(userId, icon); 369 try { 370 avatarDataStream.close(); 371 } catch (IOException ioe) { } 372 } 373 374 /** 375 * Assign the default photo to user with {@paramref userId} 376 * @param context used to get the {@link UserManager} 377 * @param userId used to get the icon bitmap 378 * @return true if assign photo successfully, false if failed 379 */ 380 public static boolean assignDefaultPhoto(Context context, int userId) { 381 if (context == null) { 382 return false; 383 } 384 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); 385 Bitmap bitmap = getDefaultUserIconAsBitmap(userId); 386 um.setUserIcon(userId, bitmap); 387 388 return true; 389 } 390 391 public static String getMeProfileName(Context context, boolean full) { 392 if (full) { 393 return getProfileDisplayName(context); 394 } else { 395 return getShorterNameIfPossible(context); 396 } 397 } 398 399 private static String getShorterNameIfPossible(Context context) { 400 final String given = getLocalProfileGivenName(context); 401 return !TextUtils.isEmpty(given) ? given : getProfileDisplayName(context); 402 } 403 404 private static String getLocalProfileGivenName(Context context) { 405 final ContentResolver cr = context.getContentResolver(); 406 407 // Find the raw contact ID for the local ME profile raw contact. 408 final long localRowProfileId; 409 final Cursor localRawProfile = cr.query( 410 Profile.CONTENT_RAW_CONTACTS_URI, 411 new String[] {RawContacts._ID}, 412 RawContacts.ACCOUNT_TYPE + " IS NULL AND " + 413 RawContacts.ACCOUNT_NAME + " IS NULL", 414 null, null); 415 if (localRawProfile == null) return null; 416 417 try { 418 if (!localRawProfile.moveToFirst()) { 419 return null; 420 } 421 localRowProfileId = localRawProfile.getLong(0); 422 } finally { 423 localRawProfile.close(); 424 } 425 426 // Find the structured name for the raw contact. 427 final Cursor structuredName = cr.query( 428 Profile.CONTENT_URI.buildUpon().appendPath(Contacts.Data.CONTENT_DIRECTORY).build(), 429 new String[] {CommonDataKinds.StructuredName.GIVEN_NAME, 430 CommonDataKinds.StructuredName.FAMILY_NAME}, 431 Data.RAW_CONTACT_ID + "=" + localRowProfileId, 432 null, null); 433 if (structuredName == null) return null; 434 435 try { 436 if (!structuredName.moveToFirst()) { 437 return null; 438 } 439 String partialName = structuredName.getString(0); 440 if (TextUtils.isEmpty(partialName)) { 441 partialName = structuredName.getString(1); 442 } 443 return partialName; 444 } finally { 445 structuredName.close(); 446 } 447 } 448 449 private static final String getProfileDisplayName(Context context) { 450 final ContentResolver cr = context.getContentResolver(); 451 final Cursor profile = cr.query(Profile.CONTENT_URI, 452 new String[] {Profile.DISPLAY_NAME}, null, null, null); 453 if (profile == null) return null; 454 455 try { 456 if (!profile.moveToFirst()) { 457 return null; 458 } 459 return profile.getString(0); 460 } finally { 461 profile.close(); 462 } 463 } 464 465 /** Not global warming, it's global change warning. */ 466 public static Dialog buildGlobalChangeWarningDialog(final Context context, int titleResId, 467 final Runnable positiveAction) { 468 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 469 builder.setTitle(titleResId); 470 builder.setMessage(R.string.global_change_warning); 471 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 472 @Override 473 public void onClick(DialogInterface dialog, int which) { 474 positiveAction.run(); 475 } 476 }); 477 builder.setNegativeButton(android.R.string.cancel, null); 478 479 return builder.create(); 480 } 481 482 public static boolean hasMultipleUsers(Context context) { 483 return ((UserManager) context.getSystemService(Context.USER_SERVICE)) 484 .getUsers().size() > 1; 485 } 486 487 /** 488 * Start a new instance of the activity, showing only the given fragment. 489 * When launched in this mode, the given preference fragment will be instantiated and fill the 490 * entire activity. 491 * 492 * @param context The context. 493 * @param fragmentName The name of the fragment to display. 494 * @param args Optional arguments to supply to the fragment. 495 * @param resultTo Option fragment that should receive the result of the activity launch. 496 * @param resultRequestCode If resultTo is non-null, this is the request code in which 497 * to report the result. 498 * @param titleResId resource id for the String to display for the title of this set 499 * of preferences. 500 * @param title String to display for the title of this set of preferences. 501 * @param metricsCategory The current metricsCategory for logging source when fragment starts 502 */ 503 public static void startWithFragment(Context context, String fragmentName, Bundle args, 504 Fragment resultTo, int resultRequestCode, int titleResId, 505 CharSequence title, int metricsCategory) { 506 startWithFragment(context, fragmentName, args, resultTo, resultRequestCode, 507 null /* titleResPackageName */, titleResId, title, false /* not a shortcut */, 508 metricsCategory); 509 } 510 511 /** 512 * Start a new instance of the activity, showing only the given fragment. 513 * When launched in this mode, the given preference fragment will be instantiated and fill the 514 * entire activity. 515 * 516 * @param context The context. 517 * @param fragmentName The name of the fragment to display. 518 * @param args Optional arguments to supply to the fragment. 519 * @param resultTo Option fragment that should receive the result of the activity launch. 520 * @param resultRequestCode If resultTo is non-null, this is the request code in which 521 * to report the result. 522 * @param titleResPackageName Optional package name for the resource id of the title. 523 * @param titleResId resource id for the String to display for the title of this set 524 * of preferences. 525 * @param title String to display for the title of this set of preferences. 526 * @param metricsCategory The current metricsCategory for logging source when fragment starts 527 */ 528 public static void startWithFragment(Context context, String fragmentName, Bundle args, 529 Fragment resultTo, int resultRequestCode, String titleResPackageName, int titleResId, 530 CharSequence title, int metricsCategory) { 531 startWithFragment(context, fragmentName, args, resultTo, resultRequestCode, 532 titleResPackageName, titleResId, title, false /* not a shortcut */, 533 metricsCategory); 534 } 535 536 public static void startWithFragment(Context context, String fragmentName, Bundle args, 537 Fragment resultTo, int resultRequestCode, int titleResId, 538 CharSequence title, boolean isShortcut, int metricsCategory) { 539 Intent intent = onBuildStartFragmentIntent(context, fragmentName, args, 540 null /* titleResPackageName */, titleResId, title, isShortcut, metricsCategory); 541 if (resultTo == null) { 542 context.startActivity(intent); 543 } else { 544 resultTo.getActivity().startActivityForResult(intent, resultRequestCode); 545 } 546 } 547 548 public static void startWithFragment(Context context, String fragmentName, Bundle args, 549 Fragment resultTo, int resultRequestCode, String titleResPackageName, int titleResId, 550 CharSequence title, boolean isShortcut, int metricsCategory) { 551 Intent intent = onBuildStartFragmentIntent(context, fragmentName, args, titleResPackageName, 552 titleResId, title, isShortcut, metricsCategory); 553 if (resultTo == null) { 554 context.startActivity(intent); 555 } else { 556 resultTo.startActivityForResult(intent, resultRequestCode); 557 } 558 } 559 560 public static void startWithFragmentAsUser(Context context, String fragmentName, Bundle args, 561 int titleResId, CharSequence title, boolean isShortcut, int metricsCategory, 562 UserHandle userHandle) { 563 // workaround to avoid crash in b/17523189 564 if (userHandle.getIdentifier() == UserHandle.myUserId()) { 565 startWithFragment(context, fragmentName, args, null, 0, titleResId, title, isShortcut, 566 metricsCategory); 567 } else { 568 Intent intent = onBuildStartFragmentIntent(context, fragmentName, args, 569 null /* titleResPackageName */, titleResId, title, isShortcut, metricsCategory); 570 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 571 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); 572 context.startActivityAsUser(intent, userHandle); 573 } 574 } 575 576 /** 577 * Build an Intent to launch a new activity showing the selected fragment. 578 * The implementation constructs an Intent that re-launches the current activity with the 579 * appropriate arguments to display the fragment. 580 * 581 * 582 * @param context The Context. 583 * @param fragmentName The name of the fragment to display. 584 * @param args Optional arguments to supply to the fragment. 585 * @param titleResPackageName Optional package name for the resource id of the title. 586 * @param titleResId Optional title resource id to show for this item. 587 * @param title Optional title to show for this item. 588 * @param isShortcut tell if this is a Launcher Shortcut or not 589 * @param sourceMetricsCategory The context (source) from which an action is performed 590 * @return Returns an Intent that can be launched to display the given 591 * fragment. 592 */ 593 public static Intent onBuildStartFragmentIntent(Context context, String fragmentName, 594 Bundle args, String titleResPackageName, int titleResId, CharSequence title, 595 boolean isShortcut, int sourceMetricsCategory) { 596 Intent intent = new Intent(Intent.ACTION_MAIN); 597 intent.setClass(context, SubSettings.class); 598 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, fragmentName); 599 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args); 600 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME, 601 titleResPackageName); 602 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, titleResId); 603 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, title); 604 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, isShortcut); 605 intent.putExtra(SettingsActivity.EXTRA_SOURCE_METRICS_CATEGORY, sourceMetricsCategory); 606 return intent; 607 } 608 609 /** 610 * Returns the managed profile of the current user or {@code null} if none is found or a profile 611 * exists but it is disabled. 612 */ 613 public static UserHandle getManagedProfile(UserManager userManager) { 614 List<UserHandle> userProfiles = userManager.getUserProfiles(); 615 final int count = userProfiles.size(); 616 for (int i = 0; i < count; i++) { 617 final UserHandle profile = userProfiles.get(i); 618 if (profile.getIdentifier() == userManager.getUserHandle()) { 619 continue; 620 } 621 final UserInfo userInfo = userManager.getUserInfo(profile.getIdentifier()); 622 if (userInfo.isManagedProfile()) { 623 return profile; 624 } 625 } 626 return null; 627 } 628 629 /** 630 * Returns the managed profile of the current user or {@code null} if none is found. Unlike 631 * {@link #getManagedProfile} this method returns enabled and disabled managed profiles. 632 */ 633 public static UserHandle getManagedProfileWithDisabled(UserManager userManager) { 634 // TODO: Call getManagedProfileId from here once Robolectric supports 635 // API level 24 and UserManager.getProfileIdsWithDisabled can be Mocked (to avoid having 636 // yet another implementation that loops over user profiles in this method). In the meantime 637 // we need to use UserManager.getProfiles that is available on API 23 (the one currently 638 // used for Settings Robolectric tests). 639 final int myUserId = UserHandle.myUserId(); 640 List<UserInfo> profiles = userManager.getProfiles(myUserId); 641 final int count = profiles.size(); 642 for (int i = 0; i < count; i++) { 643 final UserInfo profile = profiles.get(i); 644 if (profile.isManagedProfile() 645 && profile.getUserHandle().getIdentifier() != myUserId) { 646 return profile.getUserHandle(); 647 } 648 } 649 return null; 650 } 651 652 /** 653 * Retrieves the id for the given user's managed profile. 654 * 655 * @return the managed profile id or UserHandle.USER_NULL if there is none. 656 */ 657 public static int getManagedProfileId(UserManager um, int parentUserId) { 658 int[] profileIds = um.getProfileIdsWithDisabled(parentUserId); 659 for (int profileId : profileIds) { 660 if (profileId != parentUserId) { 661 return profileId; 662 } 663 } 664 return UserHandle.USER_NULL; 665 } 666 667 /** 668 * Returns the target user for a Settings activity. 669 * <p> 670 * User would be retrieved in this order: 671 * <ul> 672 * <li> If this activity is launched from other user, return that user id. 673 * <li> If this is launched from the Settings app in same user, return the user contained as an 674 * extra in the arguments or intent extras. 675 * <li> Otherwise, return UserHandle.myUserId(). 676 * </ul> 677 * <p> 678 * Note: This is secure in the sense that it only returns a target user different to the current 679 * one if the app launching this activity is the Settings app itself, running in the same user 680 * or in one that is in the same profile group, or if the user id is provided by the system. 681 */ 682 public static UserHandle getSecureTargetUser(IBinder activityToken, 683 UserManager um, @Nullable Bundle arguments, @Nullable Bundle intentExtras) { 684 UserHandle currentUser = new UserHandle(UserHandle.myUserId()); 685 IActivityManager am = ActivityManager.getService(); 686 try { 687 String launchedFromPackage = am.getLaunchedFromPackage(activityToken); 688 boolean launchedFromSettingsApp = SETTINGS_PACKAGE_NAME.equals(launchedFromPackage); 689 690 UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId( 691 am.getLaunchedFromUid(activityToken))); 692 if (launchedFromUser != null && !launchedFromUser.equals(currentUser)) { 693 // Check it's secure 694 if (isProfileOf(um, launchedFromUser)) { 695 return launchedFromUser; 696 } 697 } 698 UserHandle extrasUser = getUserHandleFromBundle(intentExtras); 699 if (extrasUser != null && !extrasUser.equals(currentUser)) { 700 // Check it's secure 701 if (launchedFromSettingsApp && isProfileOf(um, extrasUser)) { 702 return extrasUser; 703 } 704 } 705 UserHandle argumentsUser = getUserHandleFromBundle(arguments); 706 if (argumentsUser != null && !argumentsUser.equals(currentUser)) { 707 // Check it's secure 708 if (launchedFromSettingsApp && isProfileOf(um, argumentsUser)) { 709 return argumentsUser; 710 } 711 } 712 } catch (RemoteException e) { 713 // Should not happen 714 Log.v(TAG, "Could not talk to activity manager.", e); 715 } 716 return currentUser; 717 } 718 719 /** 720 * Lookup both {@link Intent#EXTRA_USER} and {@link Intent#EXTRA_USER_ID} in the bundle 721 * and return the {@link UserHandle} object. Return {@code null} if nothing is found. 722 */ 723 private static @Nullable UserHandle getUserHandleFromBundle(Bundle bundle) { 724 if (bundle == null) { 725 return null; 726 } 727 final UserHandle user = bundle.getParcelable(EXTRA_USER); 728 if (user != null) { 729 return user; 730 } 731 final int userId = bundle.getInt(EXTRA_USER_ID, -1); 732 if (userId != -1) { 733 return UserHandle.of(userId); 734 } 735 return null; 736 } 737 738 /** 739 * Returns the target user for a Settings activity. 740 * 741 * The target user can be either the current user, the user that launched this activity or 742 * the user contained as an extra in the arguments or intent extras. 743 * 744 * You should use {@link #getSecureTargetUser(IBinder, UserManager, Bundle, Bundle)} if 745 * possible. 746 * 747 * @see #getInsecureTargetUser(IBinder, Bundle, Bundle) 748 */ 749 public static UserHandle getInsecureTargetUser(IBinder activityToken, @Nullable Bundle arguments, 750 @Nullable Bundle intentExtras) { 751 UserHandle currentUser = new UserHandle(UserHandle.myUserId()); 752 IActivityManager am = ActivityManager.getService(); 753 try { 754 UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId( 755 am.getLaunchedFromUid(activityToken))); 756 if (launchedFromUser != null && !launchedFromUser.equals(currentUser)) { 757 return launchedFromUser; 758 } 759 UserHandle extrasUser = intentExtras != null 760 ? (UserHandle) intentExtras.getParcelable(EXTRA_USER) : null; 761 if (extrasUser != null && !extrasUser.equals(currentUser)) { 762 return extrasUser; 763 } 764 UserHandle argumentsUser = arguments != null 765 ? (UserHandle) arguments.getParcelable(EXTRA_USER) : null; 766 if (argumentsUser != null && !argumentsUser.equals(currentUser)) { 767 return argumentsUser; 768 } 769 } catch (RemoteException e) { 770 // Should not happen 771 Log.v(TAG, "Could not talk to activity manager.", e); 772 return null; 773 } 774 return currentUser; 775 } 776 777 /** 778 * Returns true if the user provided is in the same profiles group as the current user. 779 */ 780 private static boolean isProfileOf(UserManager um, UserHandle otherUser) { 781 if (um == null || otherUser == null) return false; 782 return (UserHandle.myUserId() == otherUser.getIdentifier()) 783 || um.getUserProfiles().contains(otherUser); 784 } 785 786 /** 787 * Return whether or not the user should have a SIM Cards option in Settings. 788 * TODO: Change back to returning true if count is greater than one after testing. 789 * TODO: See bug 16533525. 790 */ 791 public static boolean showSimCardTile(Context context) { 792 final TelephonyManager tm = 793 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 794 795 return tm.getSimCount() > 1; 796 } 797 798 /** 799 * Returns elapsed time for the given millis, in the following format: 800 * 2d 5h 40m 29s 801 * @param context the application context 802 * @param millis the elapsed time in milli seconds 803 * @param withSeconds include seconds? 804 * @return the formatted elapsed time 805 */ 806 public static CharSequence formatElapsedTime(Context context, double millis, 807 boolean withSeconds) { 808 SpannableStringBuilder sb = new SpannableStringBuilder(); 809 int seconds = (int) Math.floor(millis / 1000); 810 if (!withSeconds) { 811 // Round up. 812 seconds += 30; 813 } 814 815 int days = 0, hours = 0, minutes = 0; 816 if (seconds >= SECONDS_PER_DAY) { 817 days = seconds / SECONDS_PER_DAY; 818 seconds -= days * SECONDS_PER_DAY; 819 } 820 if (seconds >= SECONDS_PER_HOUR) { 821 hours = seconds / SECONDS_PER_HOUR; 822 seconds -= hours * SECONDS_PER_HOUR; 823 } 824 if (seconds >= SECONDS_PER_MINUTE) { 825 minutes = seconds / SECONDS_PER_MINUTE; 826 seconds -= minutes * SECONDS_PER_MINUTE; 827 } 828 829 final ArrayList<Measure> measureList = new ArrayList(4); 830 if (days > 0) { 831 measureList.add(new Measure(days, MeasureUnit.DAY)); 832 } 833 if (hours > 0) { 834 measureList.add(new Measure(hours, MeasureUnit.HOUR)); 835 } 836 if (minutes > 0) { 837 measureList.add(new Measure(minutes, MeasureUnit.MINUTE)); 838 } 839 if (withSeconds && seconds > 0) { 840 measureList.add(new Measure(seconds, MeasureUnit.SECOND)); 841 } 842 if (measureList.size() == 0) { 843 // Everything addable was zero, so nothing was added. We add a zero. 844 measureList.add(new Measure(0, withSeconds ? MeasureUnit.SECOND : MeasureUnit.MINUTE)); 845 } 846 final Measure[] measureArray = measureList.toArray(new Measure[measureList.size()]); 847 848 final Locale locale = context.getResources().getConfiguration().locale; 849 final MeasureFormat measureFormat = MeasureFormat.getInstance( 850 locale, MeasureFormat.FormatWidth.NARROW); 851 sb.append(measureFormat.formatMeasures(measureArray)); 852 853 if (measureArray.length == 1 && MeasureUnit.MINUTE.equals(measureArray[0].getUnit())) { 854 // Add ttsSpan if it only have minute value, because it will be read as "meters" 855 final TtsSpan ttsSpan = new TtsSpan.MeasureBuilder().setNumber(minutes) 856 .setUnit("minute").build(); 857 sb.setSpan(ttsSpan, 0, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 858 } 859 860 return sb; 861 } 862 863 /** 864 * Queries for the UserInfo of a user. Returns null if the user doesn't exist (was removed). 865 * @param userManager Instance of UserManager 866 * @param checkUser The user to check the existence of. 867 * @return UserInfo of the user or null for non-existent user. 868 */ 869 public static UserInfo getExistingUser(UserManager userManager, UserHandle checkUser) { 870 final List<UserInfo> users = userManager.getUsers(true /* excludeDying */); 871 final int checkUserId = checkUser.getIdentifier(); 872 for (UserInfo user : users) { 873 if (user.id == checkUserId) { 874 return user; 875 } 876 } 877 return null; 878 } 879 880 public static View inflateCategoryHeader(LayoutInflater inflater, ViewGroup parent) { 881 final TypedArray a = inflater.getContext().obtainStyledAttributes(null, 882 com.android.internal.R.styleable.Preference, 883 com.android.internal.R.attr.preferenceCategoryStyle, 0); 884 final int resId = a.getResourceId(com.android.internal.R.styleable.Preference_layout, 885 0); 886 a.recycle(); 887 return inflater.inflate(resId, parent, false); 888 } 889 890 /** 891 * Return if we are running low on storage space or not. 892 * 893 * @param context The context 894 * @return true if we are running low on storage space 895 */ 896 public static boolean isLowStorage(Context context) { 897 final StorageManager sm = StorageManager.from(context); 898 return (sm.getStorageBytesUntilLow(context.getFilesDir()) < 0); 899 } 900 901 /** 902 * Returns a default user icon (as a {@link Bitmap}) for the given user. 903 * 904 * Note that for guest users, you should pass in {@code UserHandle.USER_NULL}. 905 * @param userId the user id or {@code UserHandle.USER_NULL} for a non-user specific icon 906 */ 907 public static Bitmap getDefaultUserIconAsBitmap(int userId) { 908 Bitmap bitmap = null; 909 // Try finding the corresponding bitmap in the dark bitmap cache 910 bitmap = sDarkDefaultUserBitmapCache.get(userId); 911 if (bitmap == null) { 912 bitmap = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(userId, false)); 913 // Save it to cache 914 sDarkDefaultUserBitmapCache.put(userId, bitmap); 915 } 916 return bitmap; 917 } 918 919 public static boolean hasPreferredActivities(PackageManager pm, String packageName) { 920 // Get list of preferred activities 921 List<ComponentName> prefActList = new ArrayList<>(); 922 // Intent list cannot be null. so pass empty list 923 List<IntentFilter> intentList = new ArrayList<>(); 924 pm.getPreferredActivities(intentList, prefActList, packageName); 925 Log.d(TAG, "Have " + prefActList.size() + " number of activities in preferred list"); 926 return prefActList.size() > 0; 927 } 928 929 public static ArraySet<String> getHandledDomains(PackageManager pm, String packageName) { 930 List<IntentFilterVerificationInfo> iviList = pm.getIntentFilterVerifications(packageName); 931 List<IntentFilter> filters = pm.getAllIntentFilters(packageName); 932 933 ArraySet<String> result = new ArraySet<>(); 934 if (iviList.size() > 0) { 935 for (IntentFilterVerificationInfo ivi : iviList) { 936 for (String host : ivi.getDomains()) { 937 result.add(host); 938 } 939 } 940 } 941 if (filters != null && filters.size() > 0) { 942 for (IntentFilter filter : filters) { 943 if (filter.hasCategory(Intent.CATEGORY_BROWSABLE) 944 && (filter.hasDataScheme(IntentFilter.SCHEME_HTTP) || 945 filter.hasDataScheme(IntentFilter.SCHEME_HTTPS))) { 946 result.addAll(filter.getHostsList()); 947 } 948 } 949 } 950 return result; 951 } 952 953 /** 954 * Returns the application info of the currently installed MDM package. 955 */ 956 public static ApplicationInfo getAdminApplicationInfo(Context context, int profileId) { 957 DevicePolicyManager dpm = 958 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); 959 ComponentName mdmPackage = dpm.getProfileOwnerAsUser(profileId); 960 if (mdmPackage == null) { 961 return null; 962 } 963 String mdmPackageName = mdmPackage.getPackageName(); 964 try { 965 IPackageManager ipm = AppGlobals.getPackageManager(); 966 ApplicationInfo mdmApplicationInfo = 967 ipm.getApplicationInfo(mdmPackageName, 0, profileId); 968 return mdmApplicationInfo; 969 } catch (RemoteException e) { 970 Log.e(TAG, "Error while retrieving application info for package " + mdmPackageName 971 + ", userId " + profileId, e); 972 return null; 973 } 974 } 975 976 public static boolean isBandwidthControlEnabled() { 977 final INetworkManagementService netManager = INetworkManagementService.Stub 978 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); 979 try { 980 return netManager.isBandwidthControlEnabled(); 981 } catch (RemoteException e) { 982 return false; 983 } 984 } 985 986 /** 987 * Returns an accessible SpannableString. 988 * @param displayText the text to display 989 * @param accessibileText the text text-to-speech engines should read 990 */ 991 public static SpannableString createAccessibleSequence(CharSequence displayText, 992 String accessibileText) { 993 SpannableString str = new SpannableString(displayText); 994 str.setSpan(new TtsSpan.TextBuilder(accessibileText).build(), 0, 995 displayText.length(), 996 Spannable.SPAN_INCLUSIVE_INCLUSIVE); 997 return str; 998 } 999 1000 /** 1001 * Returns the user id present in the bundle with {@link Intent#EXTRA_USER_ID} if it 1002 * belongs to the current user. 1003 * 1004 * @throws SecurityException if the given userId does not belong to the current user group. 1005 */ 1006 public static int getUserIdFromBundle(Context context, Bundle bundle) { 1007 if (bundle == null) { 1008 return getCredentialOwnerUserId(context); 1009 } 1010 int userId = bundle.getInt(Intent.EXTRA_USER_ID, UserHandle.myUserId()); 1011 if (userId == LockPatternUtils.USER_FRP) { 1012 return enforceSystemUser(context, userId); 1013 } else { 1014 return enforceSameOwner(context, userId); 1015 } 1016 } 1017 1018 /** 1019 * Returns the given user id if the current user is the system user. 1020 * 1021 * @throws SecurityException if the current user is not the system user. 1022 */ 1023 public static int enforceSystemUser(Context context, int userId) { 1024 if (UserHandle.myUserId() == UserHandle.USER_SYSTEM) { 1025 return userId; 1026 } 1027 throw new SecurityException("Given user id " + userId + " must only be used from " 1028 + "USER_SYSTEM, but current user is " + UserHandle.myUserId()); 1029 } 1030 1031 /** 1032 * Returns the given user id if it belongs to the current user. 1033 * 1034 * @throws SecurityException if the given userId does not belong to the current user group. 1035 */ 1036 public static int enforceSameOwner(Context context, int userId) { 1037 final UserManager um = getUserManager(context); 1038 final int[] profileIds = um.getProfileIdsWithDisabled(UserHandle.myUserId()); 1039 if (ArrayUtils.contains(profileIds, userId)) { 1040 return userId; 1041 } 1042 throw new SecurityException("Given user id " + userId + " does not belong to user " 1043 + UserHandle.myUserId()); 1044 } 1045 1046 /** 1047 * Returns the effective credential owner of the calling user. 1048 */ 1049 public static int getCredentialOwnerUserId(Context context) { 1050 return getCredentialOwnerUserId(context, UserHandle.myUserId()); 1051 } 1052 1053 /** 1054 * Returns the user id of the credential owner of the given user id. 1055 */ 1056 public static int getCredentialOwnerUserId(Context context, int userId) { 1057 UserManager um = getUserManager(context); 1058 return um.getCredentialOwnerProfile(userId); 1059 } 1060 1061 public static int resolveResource(Context context, int attr) { 1062 TypedValue value = new TypedValue(); 1063 context.getTheme().resolveAttribute(attr, value, true); 1064 return value.resourceId; 1065 } 1066 1067 private static final StringBuilder sBuilder = new StringBuilder(50); 1068 private static final java.util.Formatter sFormatter = new java.util.Formatter( 1069 sBuilder, Locale.getDefault()); 1070 1071 public static String formatDateRange(Context context, long start, long end) { 1072 final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH; 1073 1074 synchronized (sBuilder) { 1075 sBuilder.setLength(0); 1076 return DateUtils.formatDateRange(context, sFormatter, start, end, flags, null) 1077 .toString(); 1078 } 1079 } 1080 1081 public static List<String> getNonIndexable(int xml, Context context) { 1082 if (Looper.myLooper() == null) { 1083 // Hack to make sure Preferences can initialize. Prefs expect a looper, but 1084 // don't actually use it for the basic stuff here. 1085 Looper.prepare(); 1086 } 1087 final List<String> ret = new ArrayList<>(); 1088 PreferenceManager manager = new PreferenceManager(context); 1089 PreferenceScreen screen = manager.inflateFromResource(context, xml, null); 1090 checkPrefs(screen, ret); 1091 1092 return ret; 1093 } 1094 1095 private static void checkPrefs(PreferenceGroup group, List<String> ret) { 1096 if (group == null) return; 1097 for (int i = 0; i < group.getPreferenceCount(); i++) { 1098 Preference pref = group.getPreference(i); 1099 if (pref instanceof SelfAvailablePreference 1100 && !((SelfAvailablePreference) pref).isAvailable(group.getContext())) { 1101 ret.add(pref.getKey()); 1102 if (pref instanceof PreferenceGroup) { 1103 addAll((PreferenceGroup) pref, ret); 1104 } 1105 } else if (pref instanceof PreferenceGroup) { 1106 checkPrefs((PreferenceGroup) pref, ret); 1107 } 1108 } 1109 } 1110 1111 private static void addAll(PreferenceGroup group, List<String> ret) { 1112 if (group == null) return; 1113 for (int i = 0; i < group.getPreferenceCount(); i++) { 1114 Preference pref = group.getPreference(i); 1115 ret.add(pref.getKey()); 1116 if (pref instanceof PreferenceGroup) { 1117 addAll((PreferenceGroup) pref, ret); 1118 } 1119 } 1120 } 1121 1122 public static boolean isDeviceProvisioned(Context context) { 1123 return Settings.Global.getInt(context.getContentResolver(), 1124 Settings.Global.DEVICE_PROVISIONED, 0) != 0; 1125 } 1126 1127 public static boolean startQuietModeDialogIfNecessary(Context context, UserManager um, 1128 int userId) { 1129 if (um.isQuietModeEnabled(UserHandle.of(userId))) { 1130 final Intent intent = UnlaunchableAppActivity.createInQuietModeDialogIntent(userId); 1131 context.startActivity(intent); 1132 return true; 1133 } 1134 return false; 1135 } 1136 1137 public static boolean unlockWorkProfileIfNecessary(Context context, int userId) { 1138 try { 1139 if (!ActivityManager.getService().isUserRunning(userId, 1140 ActivityManager.FLAG_AND_LOCKED)) { 1141 return false; 1142 } 1143 } catch (RemoteException e) { 1144 return false; 1145 } 1146 if (!(new LockPatternUtils(context)).isSecure(userId)) { 1147 return false; 1148 } 1149 return confirmWorkProfileCredentials(context, userId); 1150 } 1151 1152 public static boolean confirmWorkProfileCredentialsIfNecessary(Context context, int userId) { 1153 KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); 1154 if (!km.isDeviceLocked(userId)) { 1155 return false; 1156 } 1157 return confirmWorkProfileCredentials(context, userId); 1158 } 1159 1160 private static boolean confirmWorkProfileCredentials(Context context, int userId) { 1161 final KeyguardManager km = (KeyguardManager) context.getSystemService( 1162 Context.KEYGUARD_SERVICE); 1163 final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null, userId); 1164 if (unlockIntent != null) { 1165 context.startActivity(unlockIntent); 1166 return true; 1167 } else { 1168 return false; 1169 } 1170 } 1171 1172 public static CharSequence getApplicationLabel(Context context, String packageName) { 1173 try { 1174 final ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo( 1175 packageName, 1176 PackageManager.MATCH_DISABLED_COMPONENTS 1177 | PackageManager.MATCH_ANY_USER); 1178 return appInfo.loadLabel(context.getPackageManager()); 1179 } catch (PackageManager.NameNotFoundException e) { 1180 Log.w(TAG, "Unable to find info for package: " + packageName); 1181 } 1182 return null; 1183 } 1184 1185 public static boolean isPackageDirectBootAware(Context context, String packageName) { 1186 try { 1187 final ApplicationInfo ai = context.getPackageManager().getApplicationInfo( 1188 packageName, 0); 1189 return ai.isDirectBootAware() || ai.isPartiallyDirectBootAware(); 1190 } catch (NameNotFoundException ignored) { 1191 } 1192 return false; 1193 } 1194 1195 /** 1196 * Returns a context created from the given context for the given user, or null if it fails 1197 */ 1198 public static Context createPackageContextAsUser(Context context, int userId) { 1199 try { 1200 return context.createPackageContextAsUser( 1201 context.getPackageName(), 0 /* flags */, UserHandle.of(userId)); 1202 } catch (PackageManager.NameNotFoundException e) { 1203 Log.e(TAG, "Failed to create user context", e); 1204 } 1205 return null; 1206 } 1207 1208 public static FingerprintManager getFingerprintManagerOrNull(Context context) { 1209 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { 1210 return context.getSystemService(FingerprintManager.class); 1211 } else { 1212 return null; 1213 } 1214 } 1215 1216 public static IFingerprintManager getFingerprintManagerWrapperOrNull(Context context) { 1217 FingerprintManager fingerprintManager = getFingerprintManagerOrNull(context); 1218 if (fingerprintManager != null) { 1219 return new FingerprintManagerWrapper(fingerprintManager); 1220 } else { 1221 return null; 1222 } 1223 } 1224 1225 public static boolean hasFingerprintHardware(Context context) { 1226 FingerprintManager fingerprintManager = getFingerprintManagerOrNull(context); 1227 return fingerprintManager != null && fingerprintManager.isHardwareDetected(); 1228 } 1229 1230 /** 1231 * Launches an intent which may optionally have a user id defined. 1232 * @param fragment Fragment to use to launch the activity. 1233 * @param intent Intent to launch. 1234 */ 1235 public static void launchIntent(Fragment fragment, Intent intent) { 1236 try { 1237 final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1); 1238 1239 if (userId == -1) { 1240 fragment.startActivity(intent); 1241 } else { 1242 fragment.getActivity().startActivityAsUser(intent, new UserHandle(userId)); 1243 } 1244 } catch (ActivityNotFoundException e) { 1245 Log.w(TAG, "No activity found for " + intent); 1246 } 1247 } 1248 1249 public static boolean isDemoUser(Context context) { 1250 return UserManager.isDeviceInDemoMode(context) && getUserManager(context).isDemoUser(); 1251 } 1252 1253 public static ComponentName getDeviceOwnerComponent(Context context) { 1254 final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( 1255 Context.DEVICE_POLICY_SERVICE); 1256 return dpm.getDeviceOwnerComponentOnAnyUser(); 1257 } 1258 1259 /** 1260 * Returns if a given user is a profile of another user. 1261 * @param user The user whose profiles wibe checked. 1262 * @param profile The (potential) profile. 1263 * @return if the profile is actually a profile 1264 */ 1265 public static boolean isProfileOf(UserInfo user, UserInfo profile) { 1266 return user.id == profile.id || 1267 (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID 1268 && user.profileGroupId == profile.profileGroupId); 1269 } 1270 1271 /** 1272 * Tries to initalize a volume with the given bundle. If it is a valid, private, and readable 1273 * {@link VolumeInfo}, it is returned. If it is not valid, null is returned. 1274 */ 1275 @Nullable 1276 public static VolumeInfo maybeInitializeVolume(StorageManager sm, Bundle bundle) { 1277 final String volumeId = bundle.getString(VolumeInfo.EXTRA_VOLUME_ID, 1278 VolumeInfo.ID_PRIVATE_INTERNAL); 1279 VolumeInfo volume = sm.findVolumeById(volumeId); 1280 return isVolumeValid(volume) ? volume : null; 1281 } 1282 1283 /** 1284 * Return {@code true} if the supplied package is device owner or profile owner of at 1285 * least one user. 1286 * @param userManager used to get profile owner app for each user 1287 * @param devicePolicyManager used to check whether it is device owner app 1288 * @param packageName package to check about 1289 */ 1290 public static boolean isProfileOrDeviceOwner(UserManager userManager, 1291 DevicePolicyManagerWrapper devicePolicyManager, String packageName) { 1292 List<UserInfo> userInfos = userManager.getUsers(); 1293 if (devicePolicyManager.isDeviceOwnerAppOnAnyUser(packageName)) { 1294 return true; 1295 } 1296 for (int i = 0, size = userInfos.size(); i < size; i++) { 1297 ComponentName cn = devicePolicyManager.getProfileOwnerAsUser(userInfos.get(i).id); 1298 if (cn != null && cn.getPackageName().equals(packageName)) { 1299 return true; 1300 } 1301 } 1302 return false; 1303 } 1304 1305 /** 1306 * Return the resource id to represent the install status for an app 1307 */ 1308 @StringRes 1309 public static int getInstallationStatus(ApplicationInfo info) { 1310 if ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { 1311 return R.string.not_installed; 1312 } 1313 return info.enabled ? R.string.installed : R.string.disabled; 1314 } 1315 1316 private static boolean isVolumeValid(VolumeInfo volume) { 1317 return (volume != null) && (volume.getType() == VolumeInfo.TYPE_PRIVATE) 1318 && volume.isMountedReadable(); 1319 } 1320 1321 } 1322