1 package com.android.settingslib;
2 
3 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL;
4 import static android.webkit.Flags.updateServiceV2;
5 
6 import android.annotation.ColorInt;
7 import android.app.admin.DevicePolicyManager;
8 import android.content.Context;
9 import android.content.Intent;
10 import android.content.pm.ApplicationInfo;
11 import android.content.pm.PackageInfo;
12 import android.content.pm.PackageManager;
13 import android.content.pm.PackageManager.NameNotFoundException;
14 import android.content.pm.Signature;
15 import android.content.pm.UserInfo;
16 import android.content.res.ColorStateList;
17 import android.content.res.Resources;
18 import android.content.res.TypedArray;
19 import android.graphics.Bitmap;
20 import android.graphics.Canvas;
21 import android.graphics.Color;
22 import android.graphics.ColorFilter;
23 import android.graphics.ColorMatrix;
24 import android.graphics.ColorMatrixColorFilter;
25 import android.graphics.drawable.Drawable;
26 import android.hardware.usb.UsbManager;
27 import android.hardware.usb.UsbPort;
28 import android.hardware.usb.UsbPortStatus;
29 import android.hardware.usb.flags.Flags;
30 import android.icu.text.NumberFormat;
31 import android.location.LocationManager;
32 import android.media.AudioManager;
33 import android.net.NetworkCapabilities;
34 import android.net.TetheringManager;
35 import android.net.vcn.VcnTransportInfo;
36 import android.net.wifi.WifiInfo;
37 import android.os.BatteryManager;
38 import android.os.Build;
39 import android.os.RemoteException;
40 import android.os.SystemProperties;
41 import android.os.UserHandle;
42 import android.os.UserManager;
43 import android.print.PrintManager;
44 import android.provider.Settings;
45 import android.telephony.AccessNetworkConstants;
46 import android.telephony.NetworkRegistrationInfo;
47 import android.telephony.ServiceState;
48 import android.telephony.TelephonyManager;
49 import android.util.Log;
50 import android.webkit.IWebViewUpdateService;
51 import android.webkit.WebViewFactory;
52 import android.webkit.WebViewProviderInfo;
53 import android.webkit.WebViewUpdateManager;
54 
55 import androidx.annotation.NonNull;
56 import androidx.annotation.Nullable;
57 import androidx.annotation.RequiresApi;
58 import androidx.core.graphics.drawable.RoundedBitmapDrawable;
59 import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
60 
61 import com.android.internal.annotations.VisibleForTesting;
62 import com.android.internal.util.UserIcons;
63 import com.android.launcher3.icons.BaseIconFactory.IconOptions;
64 import com.android.launcher3.icons.IconFactory;
65 import com.android.launcher3.util.UserIconInfo;
66 import com.android.settingslib.drawable.UserIconDrawable;
67 import com.android.settingslib.fuelgauge.BatteryStatus;
68 import com.android.settingslib.fuelgauge.BatteryUtils;
69 import com.android.settingslib.utils.BuildCompatUtils;
70 
71 import java.util.List;
72 
73 public class Utils {
74 
75     private static final String TAG = "Utils";
76 
77     public static final String INCOMPATIBLE_CHARGER_WARNING_DISABLED =
78             "incompatible_charger_warning_disabled";
79 
80     @VisibleForTesting
81     static final String STORAGE_MANAGER_ENABLED_PROPERTY = "ro.storage_manager.enabled";
82 
83     private static Signature[] sSystemSignature;
84     private static String sPermissionControllerPackageName;
85     private static String sServicesSystemSharedLibPackageName;
86     private static String sSharedSystemSharedLibPackageName;
87     private static String sDefaultWebViewPackageName;
88 
89     static final int[] WIFI_PIE = {
90         com.android.internal.R.drawable.ic_wifi_signal_0,
91         com.android.internal.R.drawable.ic_wifi_signal_1,
92         com.android.internal.R.drawable.ic_wifi_signal_2,
93         com.android.internal.R.drawable.ic_wifi_signal_3,
94         com.android.internal.R.drawable.ic_wifi_signal_4
95     };
96 
97     static final int[] SHOW_X_WIFI_PIE = {
98         R.drawable.ic_show_x_wifi_signal_0,
99         R.drawable.ic_show_x_wifi_signal_1,
100         R.drawable.ic_show_x_wifi_signal_2,
101         R.drawable.ic_show_x_wifi_signal_3,
102         R.drawable.ic_show_x_wifi_signal_4
103     };
104 
105     /** Update the location enable state. */
updateLocationEnabled( @onNull Context context, boolean enabled, int userId, int source)106     public static void updateLocationEnabled(
107             @NonNull Context context, boolean enabled, int userId, int source) {
108         Settings.Secure.putIntForUser(
109                 context.getContentResolver(), Settings.Secure.LOCATION_CHANGER, source, userId);
110 
111         LocationManager locationManager = context.getSystemService(LocationManager.class);
112         locationManager.setLocationEnabledForUser(enabled, UserHandle.of(userId));
113     }
114 
115     /**
116      * Return string resource that best describes combination of tethering options available on this
117      * device.
118      */
getTetheringLabel(TetheringManager tm)119     public static int getTetheringLabel(TetheringManager tm) {
120         String[] usbRegexs = tm.getTetherableUsbRegexs();
121         String[] wifiRegexs = tm.getTetherableWifiRegexs();
122         String[] bluetoothRegexs = tm.getTetherableBluetoothRegexs();
123 
124         boolean usbAvailable = usbRegexs.length != 0;
125         boolean wifiAvailable = wifiRegexs.length != 0;
126         boolean bluetoothAvailable = bluetoothRegexs.length != 0;
127 
128         if (wifiAvailable && usbAvailable && bluetoothAvailable) {
129             return R.string.tether_settings_title_all;
130         } else if (wifiAvailable && usbAvailable) {
131             return R.string.tether_settings_title_all;
132         } else if (wifiAvailable && bluetoothAvailable) {
133             return R.string.tether_settings_title_all;
134         } else if (wifiAvailable) {
135             return R.string.tether_settings_title_wifi;
136         } else if (usbAvailable && bluetoothAvailable) {
137             return R.string.tether_settings_title_usb_bluetooth;
138         } else if (usbAvailable) {
139             return R.string.tether_settings_title_usb;
140         } else {
141             return R.string.tether_settings_title_bluetooth;
142         }
143     }
144 
145     /** Returns a label for the user, in the form of "User: user name" or "Work profile". */
getUserLabel(Context context, UserInfo info)146     public static String getUserLabel(Context context, UserInfo info) {
147         String name = info != null ? info.name : null;
148         if (info.isManagedProfile()) {
149             // We use predefined values for managed profiles
150             return BuildCompatUtils.isAtLeastT()
151                     ? getUpdatableManagedUserTitle(context)
152                     : context.getString(R.string.managed_user_title);
153         } else if (info.isGuest()) {
154             name = context.getString(com.android.internal.R.string.guest_name);
155         }
156         if (name == null && info != null) {
157             name = Integer.toString(info.id);
158         } else if (info == null) {
159             name = context.getString(R.string.unknown);
160         }
161         return context.getResources().getString(R.string.running_process_item_user_label, name);
162     }
163 
164     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
getUpdatableManagedUserTitle(Context context)165     private static String getUpdatableManagedUserTitle(Context context) {
166         return context.getSystemService(DevicePolicyManager.class)
167                 .getResources()
168                 .getString(
169                         WORK_PROFILE_USER_LABEL,
170                         () -> context.getString(R.string.managed_user_title));
171     }
172 
173     /** Returns a circular icon for a user. */
getUserIcon(Context context, UserManager um, UserInfo user)174     public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) {
175         final int iconSize = UserIconDrawable.getDefaultSize(context);
176         if (user.isManagedProfile()) {
177             Drawable drawable = UserIconDrawable.getManagedUserDrawable(context);
178             drawable.setBounds(0, 0, iconSize, iconSize);
179             return drawable;
180         }
181         if (user.iconPath != null) {
182             Bitmap icon = um.getUserIcon(user.id);
183             if (icon != null) {
184                 return new UserIconDrawable(iconSize).setIcon(icon).bake();
185             }
186         }
187         return new UserIconDrawable(iconSize)
188                 .setIconDrawable(
189                         UserIcons.getDefaultUserIcon(
190                                 context.getResources(), user.id, /* light= */ false))
191                 .bake();
192     }
193 
194     /** Formats a double from 0.0..100.0 with an option to round */
formatPercentage(double percentage, boolean round)195     public static String formatPercentage(double percentage, boolean round) {
196         final int localPercentage = round ? Math.round((float) percentage) : (int) percentage;
197         return formatPercentage(localPercentage);
198     }
199 
200     /** Formats the ratio of amount/total as a percentage. */
formatPercentage(long amount, long total)201     public static String formatPercentage(long amount, long total) {
202         return formatPercentage(((double) amount) / total);
203     }
204 
205     /** Formats an integer from 0..100 as a percentage. */
formatPercentage(int percentage)206     public static String formatPercentage(int percentage) {
207         return formatPercentage(((double) percentage) / 100.0);
208     }
209 
210     /** Formats a double from 0.0..1.0 as a percentage. */
formatPercentage(double percentage)211     public static String formatPercentage(double percentage) {
212         return NumberFormat.getPercentInstance().format(percentage);
213     }
214 
getBatteryLevel(Intent batteryChangedIntent)215     public static int getBatteryLevel(Intent batteryChangedIntent) {
216         int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
217         int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 100);
218         return (level * 100) / scale;
219     }
220 
221     /**
222      * Get battery status string
223      *
224      * @param context the context
225      * @param batteryChangedIntent battery broadcast intent received from {@link
226      *     Intent.ACTION_BATTERY_CHANGED}.
227      * @param compactStatus to present compact battery charging string if {@code true}
228      * @return battery status string
229      */
230     @NonNull
getBatteryStatus( @onNull Context context, @NonNull Intent batteryChangedIntent, boolean compactStatus)231     public static String getBatteryStatus(
232             @NonNull Context context, @NonNull Intent batteryChangedIntent, boolean compactStatus) {
233         final int status =
234                 batteryChangedIntent.getIntExtra(
235                         BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN);
236         final Resources res = context.getResources();
237 
238         String statusString = res.getString(R.string.battery_info_status_unknown);
239         final BatteryStatus batteryStatus = new BatteryStatus(batteryChangedIntent);
240 
241         if (batteryStatus.isCharged()) {
242             statusString =
243                     res.getString(
244                             compactStatus
245                                     ? R.string.battery_info_status_full_charged
246                                     : R.string.battery_info_status_full);
247         } else {
248             if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
249                 if (compactStatus) {
250                     statusString = getRegularChargingStatusString(res);
251                 } else if (batteryStatus.isPluggedInWired()) {
252                     switch (batteryStatus.getChargingSpeed(context)) {
253                         case BatteryStatus.CHARGING_FAST:
254                             statusString = getFastChargingStatusString(res);
255                             break;
256                         case BatteryStatus.CHARGING_SLOWLY:
257                             statusString = getSlowChargingStatusString(res);
258                             break;
259                         default:
260                             statusString = getRegularChargingStatusString(res);
261                             break;
262                     }
263                 } else if (batteryStatus.isPluggedInDock()) {
264                     statusString = getDockChargingStatusString(res);
265                 } else {
266                     statusString = getWirelessChargingStatusString(res);
267                 }
268             } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
269                 statusString = res.getString(R.string.battery_info_status_discharging);
270             } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
271                 statusString = res.getString(R.string.battery_info_status_not_charging);
272             }
273         }
274 
275         return statusString;
276     }
277 
getFastChargingStatusString(Resources res)278     private static String getFastChargingStatusString(Resources res) {
279         return res.getString(
280                 BatteryUtils.isChargingStringV2Enabled()
281                         ? R.string.battery_info_status_charging_fast_v2
282                         : R.string.battery_info_status_charging_fast);
283     }
284 
getSlowChargingStatusString(Resources res)285     private static String getSlowChargingStatusString(Resources res) {
286         return res.getString(
287                 BatteryUtils.isChargingStringV2Enabled()
288                         ? R.string.battery_info_status_charging_v2
289                         : R.string.battery_info_status_charging_slow);
290     }
291 
getRegularChargingStatusString(Resources res)292     private static String getRegularChargingStatusString(Resources res) {
293         return res.getString(
294                 BatteryUtils.isChargingStringV2Enabled()
295                         ? R.string.battery_info_status_charging_v2
296                         : R.string.battery_info_status_charging);
297     }
298 
getWirelessChargingStatusString(Resources res)299     private static String getWirelessChargingStatusString(Resources res) {
300         return res.getString(
301                 BatteryUtils.isChargingStringV2Enabled()
302                         ? R.string.battery_info_status_charging_v2
303                         : R.string.battery_info_status_charging_wireless);
304     }
305 
getDockChargingStatusString(Resources res)306     private static String getDockChargingStatusString(Resources res) {
307         return res.getString(
308                 BatteryUtils.isChargingStringV2Enabled()
309                         ? R.string.battery_info_status_charging_v2
310                         : R.string.battery_info_status_charging_dock);
311     }
312 
getColorAccent(Context context)313     public static ColorStateList getColorAccent(Context context) {
314         return getColorAttr(context, android.R.attr.colorAccent);
315     }
316 
getColorError(Context context)317     public static ColorStateList getColorError(Context context) {
318         return getColorAttr(context, android.R.attr.colorError);
319     }
320 
321     @ColorInt
getColorAccentDefaultColor(Context context)322     public static int getColorAccentDefaultColor(Context context) {
323         return getColorAttrDefaultColor(context, android.R.attr.colorAccent);
324     }
325 
326     @ColorInt
getColorErrorDefaultColor(Context context)327     public static int getColorErrorDefaultColor(Context context) {
328         return getColorAttrDefaultColor(context, android.R.attr.colorError);
329     }
330 
331     @ColorInt
getColorStateListDefaultColor(Context context, int resId)332     public static int getColorStateListDefaultColor(Context context, int resId) {
333         final ColorStateList list =
334                 context.getResources().getColorStateList(resId, context.getTheme());
335         return list.getDefaultColor();
336     }
337 
338     /**
339      * This method computes disabled color from normal color
340      *
341      * @param context the context
342      * @param inputColor normal color.
343      * @return disabled color.
344      */
345     @ColorInt
getDisabled(Context context, int inputColor)346     public static int getDisabled(Context context, int inputColor) {
347         return applyAlphaAttr(context, android.R.attr.disabledAlpha, inputColor);
348     }
349 
350     @ColorInt
applyAlphaAttr(Context context, int attr, int inputColor)351     public static int applyAlphaAttr(Context context, int attr, int inputColor) {
352         TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
353         float alpha = ta.getFloat(0, 0);
354         ta.recycle();
355         return applyAlpha(alpha, inputColor);
356     }
357 
358     @ColorInt
applyAlpha(float alpha, int inputColor)359     public static int applyAlpha(float alpha, int inputColor) {
360         alpha *= Color.alpha(inputColor);
361         return Color.argb(
362                 (int) (alpha),
363                 Color.red(inputColor),
364                 Color.green(inputColor),
365                 Color.blue(inputColor));
366     }
367 
368     @ColorInt
getColorAttrDefaultColor(Context context, int attr)369     public static int getColorAttrDefaultColor(Context context, int attr) {
370         return getColorAttrDefaultColor(context, attr, 0);
371     }
372 
373     /** Get color styled attribute {@code attr}, default to {@code defValue} if not found. */
374     @ColorInt
getColorAttrDefaultColor(Context context, int attr, @ColorInt int defValue)375     public static int getColorAttrDefaultColor(Context context, int attr, @ColorInt int defValue) {
376         TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
377         @ColorInt int colorAccent = ta.getColor(0, defValue);
378         ta.recycle();
379         return colorAccent;
380     }
381 
getColorAttr(Context context, int attr)382     public static ColorStateList getColorAttr(Context context, int attr) {
383         TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
384         ColorStateList stateList = null;
385         try {
386             stateList = ta.getColorStateList(0);
387         } finally {
388             ta.recycle();
389         }
390         return stateList;
391     }
392 
getThemeAttr(Context context, int attr)393     public static int getThemeAttr(Context context, int attr) {
394         return getThemeAttr(context, attr, 0);
395     }
396 
getThemeAttr(Context context, int attr, int defaultValue)397     public static int getThemeAttr(Context context, int attr, int defaultValue) {
398         TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
399         int theme = ta.getResourceId(0, defaultValue);
400         ta.recycle();
401         return theme;
402     }
403 
getDrawable(Context context, int attr)404     public static Drawable getDrawable(Context context, int attr) {
405         TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
406         Drawable drawable = ta.getDrawable(0);
407         ta.recycle();
408         return drawable;
409     }
410 
411     /**
412      * Create a color matrix suitable for a ColorMatrixColorFilter that modifies only the color but
413      * preserves the alpha for a given drawable
414      *
415      * @return a color matrix that uses the source alpha and given color
416      */
getAlphaInvariantColorMatrixForColor(@olorInt int color)417     public static ColorMatrix getAlphaInvariantColorMatrixForColor(@ColorInt int color) {
418         int r = Color.red(color);
419         int g = Color.green(color);
420         int b = Color.blue(color);
421 
422         ColorMatrix cm =
423                 new ColorMatrix(
424                         new float[] {
425                             0, 0, 0, 0, r,
426                             0, 0, 0, 0, g,
427                             0, 0, 0, 0, b,
428                             0, 0, 0, 1, 0
429                         });
430 
431         return cm;
432     }
433 
434     /**
435      * Create a ColorMatrixColorFilter to tint a drawable but retain its alpha characteristics
436      *
437      * @return a ColorMatrixColorFilter which changes the color of the output but is invariant on
438      *     the source alpha
439      */
getAlphaInvariantColorFilterForColor(@olorInt int color)440     public static ColorFilter getAlphaInvariantColorFilterForColor(@ColorInt int color) {
441         return new ColorMatrixColorFilter(getAlphaInvariantColorMatrixForColor(color));
442     }
443 
444     /**
445      * Determine whether a package is a "system package", in which case certain things (like
446      * disabling notifications or disabling the package altogether) should be disallowed.
447      *
448      * <p>Note: This function is just for UI treatment, and should not be used for security
449      * purposes.
450      *
451      * @deprecated Use {@link ApplicationInfo#isSignedWithPlatformKey()} and {@link
452      *     #isEssentialPackage} instead.
453      */
454     @Deprecated
isSystemPackage(Resources resources, PackageManager pm, PackageInfo pkg)455     public static boolean isSystemPackage(Resources resources, PackageManager pm, PackageInfo pkg) {
456         if (sSystemSignature == null) {
457             sSystemSignature = new Signature[] {getSystemSignature(pm)};
458         }
459         return (sSystemSignature[0] != null && sSystemSignature[0].equals(getFirstSignature(pkg)))
460                 || isEssentialPackage(resources, pm, pkg.packageName);
461     }
462 
getFirstSignature(PackageInfo pkg)463     private static Signature getFirstSignature(PackageInfo pkg) {
464         if (pkg != null && pkg.signatures != null && pkg.signatures.length > 0) {
465             return pkg.signatures[0];
466         }
467         return null;
468     }
469 
getSystemSignature(PackageManager pm)470     private static Signature getSystemSignature(PackageManager pm) {
471         try {
472             final PackageInfo sys = pm.getPackageInfo("android", PackageManager.GET_SIGNATURES);
473             return getFirstSignature(sys);
474         } catch (NameNotFoundException e) {
475         }
476         return null;
477     }
478 
479     /**
480      * Determine whether a package is a "essential package".
481      *
482      * <p>In which case certain things (like disabling the package) should be disallowed.
483      */
isEssentialPackage( Resources resources, PackageManager pm, String packageName)484     public static boolean isEssentialPackage(
485             Resources resources, PackageManager pm, String packageName) {
486         if (sPermissionControllerPackageName == null) {
487             sPermissionControllerPackageName = pm.getPermissionControllerPackageName();
488         }
489         if (sServicesSystemSharedLibPackageName == null) {
490             sServicesSystemSharedLibPackageName = pm.getServicesSystemSharedLibraryPackageName();
491         }
492         if (sSharedSystemSharedLibPackageName == null) {
493             sSharedSystemSharedLibPackageName = pm.getSharedSystemSharedLibraryPackageName();
494         }
495         return packageName.equals(sPermissionControllerPackageName)
496                 || packageName.equals(sServicesSystemSharedLibPackageName)
497                 || packageName.equals(sSharedSystemSharedLibPackageName)
498                 || packageName.equals(PrintManager.PRINT_SPOOLER_PACKAGE_NAME)
499                 || (updateServiceV2() && packageName.equals(getDefaultWebViewPackageName()))
500                 || isDeviceProvisioningPackage(resources, packageName);
501     }
502 
503     /**
504      * Returns {@code true} if the supplied package is the device provisioning app. Otherwise,
505      * returns {@code false}.
506      */
isDeviceProvisioningPackage(Resources resources, String packageName)507     public static boolean isDeviceProvisioningPackage(Resources resources, String packageName) {
508         String deviceProvisioningPackage =
509                 resources.getString(com.android.internal.R.string.config_deviceProvisioningPackage);
510         return deviceProvisioningPackage != null && deviceProvisioningPackage.equals(packageName);
511     }
512 
513     /** Fetch the package name of the default WebView provider. */
514     @Nullable
getDefaultWebViewPackageName()515     private static String getDefaultWebViewPackageName() {
516         if (sDefaultWebViewPackageName != null) {
517             return sDefaultWebViewPackageName;
518         }
519 
520         WebViewProviderInfo provider = null;
521 
522         if (android.webkit.Flags.updateServiceIpcWrapper()) {
523             WebViewUpdateManager manager = WebViewUpdateManager.getInstance();
524             if (manager != null) {
525                 provider = manager.getDefaultWebViewPackage();
526             }
527         } else {
528             try {
529                 IWebViewUpdateService service = WebViewFactory.getUpdateService();
530                 if (service != null) {
531                     provider = service.getDefaultWebViewPackage();
532                 }
533             } catch (RemoteException e) {
534                 Log.e(TAG, "RemoteException when trying to fetch default WebView package Name", e);
535             }
536         }
537 
538         if (provider != null) {
539             sDefaultWebViewPackageName = provider.packageName;
540         }
541         return sDefaultWebViewPackageName;
542     }
543 
544     /**
545      * Returns the Wifi icon resource for a given RSSI level.
546      *
547      * @param level The number of bars to show (0-4)
548      * @throws IllegalArgumentException if an invalid RSSI level is given.
549      */
getWifiIconResource(int level)550     public static int getWifiIconResource(int level) {
551         return getWifiIconResource(false /* showX */, level);
552     }
553 
554     /**
555      * Returns the Wifi icon resource for a given RSSI level.
556      *
557      * @param showX True if a connected Wi-Fi network has the problem which should show Pie+x signal
558      *     icon to users.
559      * @param level The number of bars to show (0-4)
560      * @throws IllegalArgumentException if an invalid RSSI level is given.
561      */
getWifiIconResource(boolean showX, int level)562     public static int getWifiIconResource(boolean showX, int level) {
563         if (level < 0 || level >= WIFI_PIE.length) {
564             throw new IllegalArgumentException("No Wifi icon found for level: " + level);
565         }
566         return showX ? SHOW_X_WIFI_PIE[level] : WIFI_PIE[level];
567     }
568 
getDefaultStorageManagerDaysToRetain(Resources resources)569     public static int getDefaultStorageManagerDaysToRetain(Resources resources) {
570         int defaultDays = Settings.Secure.AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN_DEFAULT;
571         try {
572             defaultDays =
573                     resources.getInteger(
574                             com.android.internal.R.integer
575                                     .config_storageManagerDaystoRetainDefault);
576         } catch (Resources.NotFoundException e) {
577             // We are likely in a test environment.
578         }
579         return defaultDays;
580     }
581 
isWifiOnly(Context context)582     public static boolean isWifiOnly(Context context) {
583         return !context.getSystemService(TelephonyManager.class).isDataCapable();
584     }
585 
586     /** Returns if the automatic storage management feature is turned on or not. */
isStorageManagerEnabled(Context context)587     public static boolean isStorageManagerEnabled(Context context) {
588         boolean isDefaultOn;
589         try {
590             isDefaultOn = SystemProperties.getBoolean(STORAGE_MANAGER_ENABLED_PROPERTY, false);
591         } catch (Resources.NotFoundException e) {
592             isDefaultOn = false;
593         }
594         return Settings.Secure.getInt(
595                         context.getContentResolver(),
596                         Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
597                         isDefaultOn ? 1 : 0)
598                 != 0;
599     }
600 
601     /** get that {@link AudioManager#getMode()} is in ringing/call/communication(VoIP) status. */
isAudioModeOngoingCall(Context context)602     public static boolean isAudioModeOngoingCall(Context context) {
603         final AudioManager audioManager = context.getSystemService(AudioManager.class);
604         final int audioMode = audioManager.getMode();
605         return audioMode == AudioManager.MODE_RINGTONE
606                 || audioMode == AudioManager.MODE_IN_CALL
607                 || audioMode == AudioManager.MODE_IN_COMMUNICATION;
608     }
609 
610     /**
611      * Return the service state is in-service or not. To make behavior consistent with SystemUI and
612      * Settings/AboutPhone/SIM status UI
613      *
614      * @param serviceState Service state. {@link ServiceState}
615      */
isInService(ServiceState serviceState)616     public static boolean isInService(ServiceState serviceState) {
617         if (serviceState == null) {
618             return false;
619         }
620         int state = getCombinedServiceState(serviceState);
621         if (state == ServiceState.STATE_POWER_OFF
622                 || state == ServiceState.STATE_OUT_OF_SERVICE
623                 || state == ServiceState.STATE_EMERGENCY_ONLY) {
624             return false;
625         } else {
626             return true;
627         }
628     }
629 
630     /**
631      * Return the combined service state. To make behavior consistent with SystemUI and
632      * Settings/AboutPhone/SIM status UI.
633      *
634      * <p>This method returns a single service state int if either the voice reg state is {@link
635      * ServiceState#STATE_IN_SERVICE} or if data network is registered via a WWAN transport type. We
636      * consider the combined service state of an IWLAN network to be OOS.
637      *
638      * @param serviceState Service state. {@link ServiceState}
639      */
getCombinedServiceState(ServiceState serviceState)640     public static int getCombinedServiceState(ServiceState serviceState) {
641         if (serviceState == null) {
642             return ServiceState.STATE_OUT_OF_SERVICE;
643         }
644 
645         final int voiceRegState = serviceState.getVoiceRegState();
646 
647         // Consider a mobile connection to be "in service" if either voice is IN_SERVICE
648         // or the data registration reports IN_SERVICE on a transport type of WWAN. This
649         // effectively excludes the IWLAN condition. IWLAN connections imply service via
650         // Wi-Fi rather than cellular, and so we do not consider these transports when
651         // determining if cellular is "in service".
652 
653         if (voiceRegState == ServiceState.STATE_OUT_OF_SERVICE
654                 || voiceRegState == ServiceState.STATE_EMERGENCY_ONLY) {
655             if (isDataRegInWwanAndInService(serviceState)) {
656                 return ServiceState.STATE_IN_SERVICE;
657             }
658         }
659 
660         return voiceRegState;
661     }
662 
663     // ServiceState#mDataRegState can be set to IN_SERVICE if the network is registered
664     // on either a WLAN or WWAN network. Since we want to exclude the WLAN network, we can
665     // query the WWAN network directly and check for its registration state
isDataRegInWwanAndInService(ServiceState serviceState)666     private static boolean isDataRegInWwanAndInService(ServiceState serviceState) {
667         final NetworkRegistrationInfo networkRegWwan =
668                 serviceState.getNetworkRegistrationInfo(
669                         NetworkRegistrationInfo.DOMAIN_PS,
670                         AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
671 
672         if (networkRegWwan == null) {
673             return false;
674         }
675 
676         return networkRegWwan.isInService();
677     }
678 
679     /** Get the corresponding adaptive icon drawable. */
getBadgedIcon(Context context, Drawable icon, UserHandle user)680     public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) {
681         int userType = UserIconInfo.TYPE_MAIN;
682         try {
683             UserInfo ui =
684                     context.getSystemService(UserManager.class).getUserInfo(user.getIdentifier());
685             if (ui != null) {
686                 if (ui.isCloneProfile()) {
687                     userType = UserIconInfo.TYPE_CLONED;
688                 } else if (ui.isManagedProfile()) {
689                     userType = UserIconInfo.TYPE_WORK;
690                 } else if (ui.isPrivateProfile()) {
691                     userType = UserIconInfo.TYPE_PRIVATE;
692                 }
693             }
694         } catch (Exception e) {
695             // Ignore
696         }
697         try (IconFactory iconFactory = IconFactory.obtain(context)) {
698             return iconFactory
699                     .createBadgedIconBitmap(
700                             icon, new IconOptions().setUser(new UserIconInfo(user, userType)))
701                     .newIcon(context);
702         }
703     }
704 
705     /** Get the {@link Drawable} that represents the app icon */
getBadgedIcon(Context context, ApplicationInfo appInfo)706     public static Drawable getBadgedIcon(Context context, ApplicationInfo appInfo) {
707         return getBadgedIcon(
708                 context,
709                 appInfo.loadUnbadgedIcon(context.getPackageManager()),
710                 UserHandle.getUserHandleForUid(appInfo.uid));
711     }
712 
713     /**
714      * Returns a bitmap with rounded corner.
715      *
716      * @param context application context.
717      * @param source bitmap to apply round corner.
718      * @param cornerRadius corner radius value.
719      */
720     @NonNull
convertCornerRadiusBitmap( @onNull Context context, @NonNull Bitmap source, @NonNull float cornerRadius)721     public static Bitmap convertCornerRadiusBitmap(
722             @NonNull Context context, @NonNull Bitmap source, @NonNull float cornerRadius) {
723         final Bitmap roundedBitmap =
724                 Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
725         final RoundedBitmapDrawable drawable =
726                 RoundedBitmapDrawableFactory.create(context.getResources(), source);
727         drawable.setAntiAlias(true);
728         drawable.setCornerRadius(cornerRadius);
729         final Canvas canvas = new Canvas(roundedBitmap);
730         drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
731         drawable.draw(canvas);
732         return roundedBitmap;
733     }
734 
735     /**
736      * Returns the WifiInfo for the underlying WiFi network of the VCN network, returns null if the
737      * input NetworkCapabilities is not for a VCN network with underlying WiFi network.
738      *
739      * @param networkCapabilities NetworkCapabilities of the network.
740      */
741     @Nullable
tryGetWifiInfoForVcn(NetworkCapabilities networkCapabilities)742     public static WifiInfo tryGetWifiInfoForVcn(NetworkCapabilities networkCapabilities) {
743         if (networkCapabilities.getTransportInfo() == null
744                 || !(networkCapabilities.getTransportInfo() instanceof VcnTransportInfo)) {
745             return null;
746         }
747         VcnTransportInfo vcnTransportInfo =
748                 (VcnTransportInfo) networkCapabilities.getTransportInfo();
749         return vcnTransportInfo.getWifiInfo();
750     }
751 
752     /** Whether there is any incompatible chargers in the current UsbPort? */
containsIncompatibleChargers(Context context, String tag)753     public static boolean containsIncompatibleChargers(Context context, String tag) {
754         // Avoid the caller doesn't have permission to read the "Settings.Secure" data.
755         try {
756             // Whether the incompatible charger warning is disabled or not
757             if (Settings.Secure.getInt(
758                             context.getContentResolver(), INCOMPATIBLE_CHARGER_WARNING_DISABLED, 0)
759                     == 1) {
760                 Log.d(tag, "containsIncompatibleChargers: disabled");
761                 return false;
762             }
763         } catch (Exception e) {
764             Log.e(tag, "containsIncompatibleChargers()", e);
765             return false;
766         }
767 
768         final UsbManager usbManager = context.getSystemService(UsbManager.class);
769         if (usbManager == null) {
770             return false;
771         }
772         final List<UsbPort> usbPortList = usbManager.getPorts();
773         if (usbPortList == null || usbPortList.isEmpty()) {
774             return false;
775         }
776         for (UsbPort usbPort : usbPortList) {
777             Log.d(tag, "usbPort: " + usbPort);
778             if (!usbPort.supportsComplianceWarnings()) {
779                 continue;
780             }
781             final UsbPortStatus usbStatus = usbPort.getStatus();
782             if (usbStatus == null || !usbStatus.isConnected()) {
783                 continue;
784             }
785             final int[] complianceWarnings = usbStatus.getComplianceWarnings();
786             if (complianceWarnings == null || complianceWarnings.length == 0) {
787                 continue;
788             }
789             for (int complianceWarningType : complianceWarnings) {
790                 if (Flags.enableUsbDataComplianceWarning()
791                         && Flags.enableInputPowerLimitedWarning()) {
792                     switch (complianceWarningType) {
793                         case UsbPortStatus.COMPLIANCE_WARNING_INPUT_POWER_LIMITED:
794                         case UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY:
795                             return true;
796                         default:
797                             break;
798                     }
799                 } else {
800                     switch (complianceWarningType) {
801                         case UsbPortStatus.COMPLIANCE_WARNING_OTHER:
802                         case UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY:
803                             return true;
804                         default:
805                             break;
806                     }
807                 }
808             }
809         }
810         return false;
811     }
812 }
813