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