diff --git a/res/values/strings.xml b/res/values/strings.xml index f9e1517249c..927f84d046f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -354,6 +354,24 @@ Only apps that support language selection are shown here. + + + Regional preferences + + Set units and number preferences + + Apps can use your regional preferences to personalize your experience + + Temperature + + Calendar + + First day of week + + Numbers + + [No preference] + {count, plural, =1 {Remove selected language?} @@ -492,10 +510,10 @@ Use location - - On - %1$d app has access to location - On - %1$d apps have access to location - + {count, plural, + =1 {On - # app has access to location} + other {On - # apps have access to location} + } Loading\u2026 @@ -677,10 +695,10 @@ Add fingerprint - - Fingerprint added - %1$d fingerprints added - + {count, plural, + =1 {Fingerprint added} + other {# fingerprints added} + } @@ -4453,27 +4471,26 @@ Keep on after device restarts - - Short (%1$s second) - Short (%1$s seconds) - + {count, plural, + =1 {Short ({time} second)} + other {Short ({time} seconds)} + } - - Medium (%1$s second) - Medium (%1$s seconds) - + {count, plural, + =1 {Medium ({time} second)} + other {Medium ({time} seconds)} + } - - Long (%1$s second) - Long (%1$s seconds) - + {count, plural, + =1 {Long ({time} second)} + other {Long ({time} seconds)} + } - - %1$s second - %1$s seconds - %1$s seconds - + {count, plural, + =1 {{time} second} + other {{time} seconds} + } Settings @@ -4672,16 +4689,16 @@ Printing - - 1 print service on - %1$d print services on - + {count, plural, + =1 {1 print service on} + other {# print services on} + } - - 1 print job - %1$d print jobs - + {count, plural, + =1 {1 print job} + other {# print jobs} + } Print services @@ -4878,31 +4895,31 @@ Includes high-power background activity - - Restrict %1$d app - Restrict %1$d apps - + {count, plural, + =1 {Restrict # app} + other {Restrict # apps} + } - - %1$s recently restricted - %2$d apps recently restricted - + {count, plural, + =1 {{label} recently restricted} + other {# apps recently restricted} + } - - %1$s has high background battery usage - %2$d apps have high background battery usage - + {count, plural, + =1 {{label} has high background battery usage} + other {# apps have high background battery usage} + } - - This app can\'t run in the background - These apps can\'t run in the background - + {count, plural, + =1 {This app can\'t run in the background} + other {These apps can\'t run in the background} + } - - Restrict app? - Restrict %1$d apps? - + {count, plural, + =1 {Restrict app?} + other {Restrict # apps?} + } To save battery, stop %1$s from using battery in the background. This app may not work properly and notifications may be delayed. @@ -4931,10 +4948,10 @@ Restricted apps - - Limiting battery usage for %1$d app - Limiting battery usage for %1$d apps - + {count, plural, + =1 {Limiting battery usage for # app} + other {Limiting battery usage for # apps} + } Restricted %1$s @@ -4953,10 +4970,10 @@ Detecting when apps drain battery - - %1$d app restricted - %1$d apps restricted - + {count, plural, + =1 {# app restricted} + other {# apps restricted} + } ^1 % @@ -5406,10 +5423,10 @@ This app won\u2019t manage certificates, but it will stay on your device. Any certificates installed by the app will be uninstalled. - - %d URL - %d URLs - + {count, plural, + =1 {# URL} + other {# URLs} + } Emergency dialing signal @@ -5510,10 +5527,10 @@ Recently dismissed - - %d notification - %d notifications - + {count, plural, + =1 {# notification} + other {# notifications} + } @@ -5954,10 +5971,10 @@ Done - - Trust or remove certificate - Trust or remove certificates - + {count, plural, + =1 {Trust or remove certificate} + other {Trust or remove certificates} + } {numberOfCertificates, plural, =1 {{orgName} has installed a certificate authority on your device, which may allow them to monitor your device network activity, including emails, apps, and secure websites.\n\nFor more information about this certificate, contact your admin.} @@ -5971,10 +5988,10 @@ A third party is capable of monitoring your network activity, including emails, apps, and secure websites.\n\nA trusted credential installed on your device is making this possible. - - Check certificate - Check certificates - + {count, plural, + =1 {Check certificate} + other {Check certificates} + } Multiple users @@ -7434,10 +7451,10 @@ No priority conversations - - %d priority conversation - %d priority conversations - + {count, plural, + =1 {# priority conversation} + other {# priority conversations} + } Priority conversations @@ -7530,14 +7547,14 @@ All %1$s notifications - - About %d notification per day - About %,d notifications per day - - - About %d notification per week - About %,d notifications per week - + {count, plural, + =1 {About # notification per day} + other {About # notifications per day} + } + {count, plural, + =1 {About # notification per week} + other {About # notifications per week} + } Never @@ -7734,10 +7751,10 @@ No apps connected - - %d app connected - %d apps connected - + {count, plural, + =1 {# app connected} + other {# apps connected} + } - - %d category deleted - %d categories deleted - + {count, plural, + =1 {# category deleted} + other {# categories deleted} + } Block all diff --git a/res/xml/language_and_input.xml b/res/xml/language_and_input.xml index 03614bce26a..601ba8e3537 100644 --- a/res/xml/language_and_input.xml +++ b/res/xml/language_and_input.xml @@ -38,6 +38,15 @@ android:name="classname" android:value="com.android.settings.applications.appinfo.AppLocaleDetails" /> + + + + + + + + + + + + + + + + + + + diff --git a/src/com/android/settings/MonitoringCertInfoActivity.java b/src/com/android/settings/MonitoringCertInfoActivity.java index 5c15eb3a73b..4225d724fd8 100644 --- a/src/com/android/settings/MonitoringCertInfoActivity.java +++ b/src/com/android/settings/MonitoringCertInfoActivity.java @@ -33,6 +33,7 @@ import android.provider.Settings; import androidx.appcompat.app.AlertDialog; import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.utils.StringUtil; import java.util.HashMap; import java.util.Locale; @@ -65,16 +66,17 @@ public class MonitoringCertInfoActivity extends Activity implements OnClickListe final int numberOfCertificates = getIntent().getIntExtra( Settings.EXTRA_NUMBER_OF_CERTIFICATES, 1); final int titleId = RestrictedLockUtils.getProfileOrDeviceOwner(this, user) != null - ? R.plurals.ssl_ca_cert_settings_button // Check certificate - : R.plurals.ssl_ca_cert_dialog_title; // Trust or remove certificate - final CharSequence title = getResources().getQuantityText(titleId, numberOfCertificates); + ? R.string.ssl_ca_cert_settings_button // Check certificate + : R.string.ssl_ca_cert_dialog_title; // Trust or remove certificate + final CharSequence title = StringUtil.getIcuPluralsString(this, numberOfCertificates, + titleId); setTitle(title); final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(title); builder.setCancelable(true); - builder.setPositiveButton(getResources().getQuantityText( - R.plurals.ssl_ca_cert_settings_button, numberOfCertificates) , this); + builder.setPositiveButton(StringUtil.getIcuPluralsString(this, numberOfCertificates, + R.string.ssl_ca_cert_settings_button) , this); builder.setNeutralButton(R.string.cancel, null); builder.setOnDismissListener(this); diff --git a/src/com/android/settings/accessibility/AutoclickPreferenceController.java b/src/com/android/settings/accessibility/AutoclickPreferenceController.java index b118718b34f..741035207a5 100644 --- a/src/com/android/settings/accessibility/AutoclickPreferenceController.java +++ b/src/com/android/settings/accessibility/AutoclickPreferenceController.java @@ -34,9 +34,9 @@ public class AutoclickPreferenceController extends BasePreferenceController { * placeholder for integer delay value. */ private static final int[] AUTOCLICK_PREFERENCE_SUMMARIES = { - R.plurals.accessibilty_autoclick_preference_subtitle_short_delay, - R.plurals.accessibilty_autoclick_preference_subtitle_medium_delay, - R.plurals.accessibilty_autoclick_preference_subtitle_long_delay + R.string.accessibilty_autoclick_preference_subtitle_short_delay, + R.string.accessibilty_autoclick_preference_subtitle_medium_delay, + R.string.accessibilty_autoclick_preference_subtitle_long_delay }; public AutoclickPreferenceController(Context context, String preferenceKey) { @@ -59,7 +59,7 @@ public class AutoclickPreferenceController extends BasePreferenceController { Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY, AccessibilityManager.AUTOCLICK_DELAY_DEFAULT); final int summaryIndex = getAutoclickPreferenceSummaryIndex(delayMillis); - return AutoclickUtils.getAutoclickDelaySummary(mContext.getResources(), + return AutoclickUtils.getAutoclickDelaySummary(mContext, AUTOCLICK_PREFERENCE_SUMMARIES[summaryIndex], delayMillis); } diff --git a/src/com/android/settings/accessibility/AutoclickUtils.java b/src/com/android/settings/accessibility/AutoclickUtils.java index cd7d662118f..89f2ceb57f9 100644 --- a/src/com/android/settings/accessibility/AutoclickUtils.java +++ b/src/com/android/settings/accessibility/AutoclickUtils.java @@ -20,9 +20,16 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.annotation.PluralsRes; +import android.annotation.StringRes; +import android.content.Context; import android.content.res.Resources; +import com.android.settings.R; +import com.android.settingslib.utils.StringUtil; + import java.lang.annotation.Retention; +import java.util.HashMap; +import java.util.Map; /** Provides utility methods related auto click. */ public final class AutoclickUtils { @@ -58,20 +65,23 @@ public final class AutoclickUtils { /** * Gets string that should be used for provided autoclick delay. * - * @param resources Resources from which string should be retrieved. + * @param context context from which string should be retrieved. * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource * entry. The value 0 is an invalid identifier. * @param delayMillis Delay for whose value summary should be retrieved. */ - public static CharSequence getAutoclickDelaySummary(Resources resources, - @PluralsRes int id, int delayMillis) { + public static CharSequence getAutoclickDelaySummary(Context context, + @StringRes int id, int delayMillis) { final int quantity = (delayMillis == 1000) ? Quantity.ONE : Quantity.FEW; final float delaySecond = (float) delayMillis / 1000; // Only show integer when delay time is 1. final String decimalFormat = (delaySecond == 1) ? "%.0f" : "%.1f"; - return resources.getQuantityString(id, quantity, String.format(decimalFormat, delaySecond)); + Map arguments = new HashMap<>(); + arguments.put("count", quantity); + arguments.put("time", String.format(decimalFormat, delaySecond)); + return StringUtil.getIcuPluralsString(context, arguments, id); } private AutoclickUtils(){} diff --git a/src/com/android/settings/accessibility/ToggleAutoclickCustomSeekbarController.java b/src/com/android/settings/accessibility/ToggleAutoclickCustomSeekbarController.java index 372c6f54303..6a04863fd4b 100644 --- a/src/com/android/settings/accessibility/ToggleAutoclickCustomSeekbarController.java +++ b/src/com/android/settings/accessibility/ToggleAutoclickCustomSeekbarController.java @@ -176,7 +176,7 @@ public class ToggleAutoclickCustomSeekbarController extends BasePreferenceContro } } private CharSequence delayTimeToString(int delayMillis) { - return AutoclickUtils.getAutoclickDelaySummary(mContext.getResources(), - R.plurals.accessibilty_autoclick_delay_unit_second, delayMillis); + return AutoclickUtils.getAutoclickDelaySummary(mContext, + R.string.accessibilty_autoclick_delay_unit_second, delayMillis); } } diff --git a/src/com/android/settings/applications/AppStateNotificationBridge.java b/src/com/android/settings/applications/AppStateNotificationBridge.java index 964eae42d4c..f0e447e17b4 100644 --- a/src/com/android/settings/applications/AppStateNotificationBridge.java +++ b/src/com/android/settings/applications/AppStateNotificationBridge.java @@ -110,11 +110,11 @@ public class AppStateNotificationBridge extends AppStateBaseBridge { context, System.currentTimeMillis() - state.lastSent, true); } else if (sortOrder == R.id.sort_order_frequent_notification) { if (state.avgSentDaily > 0) { - return context.getResources().getQuantityString( - R.plurals.notifications_sent_daily, state.avgSentDaily, state.avgSentDaily); + return StringUtil.getIcuPluralsString(context, state.avgSentDaily, + R.string.notifications_sent_daily); } - return context.getResources().getQuantityString(R.plurals.notifications_sent_weekly, - state.avgSentWeekly, state.avgSentWeekly); + return StringUtil.getIcuPluralsString(context, state.avgSentWeekly, + R.string.notifications_sent_weekly); } else { return ""; } diff --git a/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesController.java b/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesController.java index bc8542e551a..4d49a225ccc 100644 --- a/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesController.java +++ b/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesController.java @@ -25,6 +25,7 @@ import android.os.UserManager; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.utils.StringUtil; import java.util.List; @@ -66,9 +67,7 @@ public class InteractAcrossProfilesController extends BasePreferenceController { return connectedApps == 0 ? mContext.getResources().getString( R.string.interact_across_profiles_number_of_connected_apps_none) - : mContext.getResources().getQuantityString( - R.plurals.interact_across_profiles_number_of_connected_apps, - connectedApps, - connectedApps); + : StringUtil.getIcuPluralsString(mContext, connectedApps, + R.string.interact_across_profiles_number_of_connected_apps); } } diff --git a/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java b/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java index f5ad76b2d9d..1ca4278d0b0 100644 --- a/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java +++ b/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java @@ -28,6 +28,7 @@ import com.android.settings.Settings; import com.android.settings.Utils; import com.android.settings.biometrics.ParentalControlsUtils; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; +import com.android.settingslib.utils.StringUtil; /** * Utilities for combined biometric details shared between Security Settings and Safety Center. @@ -102,9 +103,8 @@ public class CombinedBiometricStatusUtils { } else if (faceEnrolled) { return mContext.getString(R.string.security_settings_face_preference_summary); } else if (numFingerprintsEnrolled > 0) { - return mContext.getResources().getQuantityString( - R.plurals.security_settings_fingerprint_preference_summary, - numFingerprintsEnrolled, numFingerprintsEnrolled); + return StringUtil.getIcuPluralsString(mContext, numFingerprintsEnrolled, + R.string.security_settings_fingerprint_preference_summary); } else { return mContext.getString( R.string.security_settings_biometric_preference_summary_none_enrolled); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java b/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java index 32b83004c74..b82e8b615f6 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java @@ -28,6 +28,7 @@ import com.android.settings.Utils; import com.android.settings.biometrics.ParentalControlsUtils; import com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; +import com.android.settingslib.utils.StringUtil; /** * Utilities for fingerprint details shared between Security Settings and Safety Center. @@ -69,9 +70,8 @@ public class FingerprintStatusUtils { public String getSummary() { if (hasEnrolled()) { final int numEnrolled = mFingerprintManager.getEnrolledFingerprints(mUserId).size(); - return mContext.getResources().getQuantityString( - R.plurals.security_settings_fingerprint_preference_summary, - numEnrolled, numEnrolled); + return StringUtil.getIcuPluralsString(mContext, numEnrolled, + R.string.security_settings_fingerprint_preference_summary); } else { return mContext.getString( R.string.security_settings_fingerprint_preference_summary_none); diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java index a8c43fb3e10..8eaff45b400 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java @@ -341,9 +341,7 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment @VisibleForTesting void setTitleForInputDevice() { - // TODO(b/254835745) once source filter for bt stylus merged - // && mInputDevice.supportsSource(InputDevice.SOURCE_BLUETOOTH_STYLUS)) - if (mInputDevice != null) { + if (StylusDevicesController.isDeviceStylus(mInputDevice, mCachedDevice)) { // This will override the default R.string.device_details_title "Device Details" // that will show on non-stylus bluetooth devices. // That title is set via the manifest and also from BluetoothDeviceUpdater. diff --git a/src/com/android/settings/development/featureflags/FeatureFlagPreference.java b/src/com/android/settings/development/featureflags/FeatureFlagPreference.java index 08ae760684b..d6bdb77d04b 100644 --- a/src/com/android/settings/development/featureflags/FeatureFlagPreference.java +++ b/src/com/android/settings/development/featureflags/FeatureFlagPreference.java @@ -17,8 +17,6 @@ package com.android.settings.development.featureflags; import android.content.Context; -import android.provider.Settings; -import android.text.TextUtils; import android.util.FeatureFlagUtils; import androidx.preference.SwitchPreference; @@ -51,13 +49,5 @@ public class FeatureFlagPreference extends SwitchPreference { } else { FeatureFlagUtils.setEnabled(getContext(), mKey, isChecked); } - - // A temporary logic for settings_hide_second_layer_page_navigate_up_button_in_two_pane - // Remove it before Android T release. - if (TextUtils.equals(mKey, - FeatureFlagUtils.SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE)) { - Settings.Global.putString(getContext().getContentResolver(), - mKey, String.valueOf(isChecked)); - } } } diff --git a/src/com/android/settings/fuelgauge/RestrictAppPreferenceController.java b/src/com/android/settings/fuelgauge/RestrictAppPreferenceController.java index b85db40d954..d43a488ad01 100644 --- a/src/com/android/settings/fuelgauge/RestrictAppPreferenceController.java +++ b/src/com/android/settings/fuelgauge/RestrictAppPreferenceController.java @@ -31,6 +31,7 @@ import com.android.settings.core.InstrumentedPreferenceFragment; import com.android.settings.fuelgauge.batterytip.AppInfo; import com.android.settings.fuelgauge.batterytip.BatteryTipUtils; import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.utils.StringUtil; import java.util.List; @@ -75,9 +76,8 @@ public class RestrictAppPreferenceController extends BasePreferenceController { final int num = mAppInfos.size(); // Fragment change RestrictedAppsList after onPause(), UI needs to be updated in onResume() preference.setVisible(num > 0); - preference.setSummary( - mContext.getResources().getQuantityString(R.plurals.restricted_app_summary, num, - num)); + preference.setSummary(StringUtil.getIcuPluralsString(mContext, num, + R.string.restricted_app_summary)); } @Override diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryManagerPreferenceController.java b/src/com/android/settings/fuelgauge/batterytip/BatteryManagerPreferenceController.java index a156395f44a..1c4c67c8781 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryManagerPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryManagerPreferenceController.java @@ -27,6 +27,7 @@ import com.android.settings.R; import com.android.settings.core.BasePreferenceController; import com.android.settings.fuelgauge.PowerUsageFeatureProvider; import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.utils.StringUtil; /** Preference controller to control the battery manager */ public class BatteryManagerPreferenceController extends BasePreferenceController { @@ -66,8 +67,8 @@ public class BatteryManagerPreferenceController extends BasePreferenceController @VisibleForTesting void updateSummary(Preference preference, int num) { if (num > 0) { - preference.setSummary(mContext.getResources().getQuantityString( - R.plurals.battery_manager_app_restricted, num, num)); + preference.setSummary(StringUtil.getIcuPluralsString(mContext, num, + R.string.battery_manager_app_restricted)); } else { preference.setSummary( mPowerUsageFeatureProvider.isAdaptiveChargingSupported() diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java index 5fd3905649f..3989e892818 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java @@ -42,6 +42,7 @@ import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; import com.android.settings.fuelgauge.batterytip.tips.HighUsageTip; import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip; import com.android.settings.fuelgauge.batterytip.tips.UnrestrictAppTip; +import com.android.settingslib.utils.StringUtil; import java.util.List; @@ -107,8 +108,8 @@ public class BatteryTipDialogFragment extends InstrumentedDialogFragment impleme restrictedAppList.get(0).packageName); final AlertDialog.Builder builder = new AlertDialog.Builder(context) - .setTitle(context.getResources().getQuantityString( - R.plurals.battery_tip_restrict_app_dialog_title, num, num)) + .setTitle(StringUtil.getIcuPluralsString(context, num, + R.string.battery_tip_restrict_app_dialog_title)) .setPositiveButton(R.string.battery_tip_restrict_app_dialog_ok, this) .setNegativeButton(android.R.string.cancel, null); if (num == 1) { diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java index c5f1171a8c7..6fb1c9a891f 100644 --- a/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java +++ b/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java @@ -28,9 +28,12 @@ import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.fuelgauge.batterytip.AppInfo; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import com.android.settingslib.utils.StringUtil; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Tip to suggest user to restrict some bad apps @@ -62,12 +65,15 @@ public class RestrictAppTip extends BatteryTip { final int num = mRestrictAppList.size(); final CharSequence appLabel = num > 0 ? Utils.getApplicationLabel(context, mRestrictAppList.get(0).packageName) : ""; - final Resources resources = context.getResources(); + Map arguments = new HashMap<>(); + arguments.put("count", num); + arguments.put("label", appLabel); return mState == StateType.HANDLED - ? resources.getQuantityString(R.plurals.battery_tip_restrict_handled_title, num, - appLabel, num) - : resources.getQuantityString(R.plurals.battery_tip_restrict_title, num, num); + ? StringUtil.getIcuPluralsString(context, arguments, + R.string.battery_tip_restrict_handled_title) + : StringUtil.getIcuPluralsString(context, arguments, + R.string.battery_tip_restrict_title); } @Override @@ -76,9 +82,12 @@ public class RestrictAppTip extends BatteryTip { final CharSequence appLabel = num > 0 ? Utils.getApplicationLabel(context, mRestrictAppList.get(0).packageName) : ""; final int resId = mState == StateType.HANDLED - ? R.plurals.battery_tip_restrict_handled_summary - : R.plurals.battery_tip_restrict_summary; - return context.getResources().getQuantityString(resId, num, appLabel, num); + ? R.string.battery_tip_restrict_handled_summary + : R.string.battery_tip_restrict_summary; + Map arguments = new HashMap<>(); + arguments.put("count", num); + arguments.put("label", appLabel); + return StringUtil.getIcuPluralsString(context, arguments, resId); } @Override diff --git a/src/com/android/settings/location/TopLevelLocationPreferenceController.java b/src/com/android/settings/location/TopLevelLocationPreferenceController.java index 91100b7628d..464344e9ee4 100644 --- a/src/com/android/settings/location/TopLevelLocationPreferenceController.java +++ b/src/com/android/settings/location/TopLevelLocationPreferenceController.java @@ -21,6 +21,7 @@ import com.android.settings.core.BasePreferenceController; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; +import com.android.settingslib.utils.StringUtil; import java.util.Arrays; import java.util.List; @@ -63,8 +64,8 @@ public class TopLevelLocationPreferenceController extends BasePreferenceControll @VisibleForTesting void setLocationAppCount(int numApps) { - sSummary = mContext.getResources().getQuantityString( - R.plurals.location_settings_summary_location_on, numApps, numApps); + sSummary = StringUtil.getIcuPluralsString(mContext, numApps, + R.string.location_settings_summary_location_on); refreshSummary(mPreference); } diff --git a/src/com/android/settings/network/tether/TetherSettings.java b/src/com/android/settings/network/tether/TetherSettings.java index a674722e837..118d407941d 100644 --- a/src/com/android/settings/network/tether/TetherSettings.java +++ b/src/com/android/settings/network/tether/TetherSettings.java @@ -77,7 +77,7 @@ import java.util.concurrent.atomic.AtomicReference; */ @SearchIndexable public class TetherSettings extends RestrictedSettingsFragment - implements DataSaverBackend.Listener { + implements DataSaverBackend.Listener, TetheringManager.TetheringEventCallback { @VisibleForTesting static final String KEY_TETHER_PREFS_SCREEN = "tether_prefs_screen"; @@ -111,7 +111,6 @@ public class TetherSettings extends RestrictedSettingsFragment private OnStartTetheringCallback mStartTetheringCallback; private ConnectivityManager mCm; private EthernetManager mEm; - private TetheringEventCallback mTetheringEventCallback; private EthernetListener mEthernetListener; private final HashSet mAvailableInterfaces = new HashSet<>(); @@ -133,6 +132,7 @@ public class TetherSettings extends RestrictedSettingsFragment Context mContext; @VisibleForTesting TetheringManager mTm; + private TetheringHelper mTetheringHelper; @Override public int getMetricsCategory() { @@ -148,6 +148,8 @@ public class TetherSettings extends RestrictedSettingsFragment super.onAttach(context); mWifiTetherPreferenceController = new WifiTetherPreferenceController(context, getSettingsLifecycle()); + mTetheringHelper = TetheringHelper.getInstance(context, this /* TetheringEventCallback */, + getSettingsLifecycle()); } @Override @@ -186,7 +188,7 @@ public class TetherSettings extends RestrictedSettingsFragment mDataSaverBackend.addListener(this); mCm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - mTm = (TetheringManager) getSystemService(Context.TETHERING_SERVICE); + mTm = mTetheringHelper.getTetheringManager(); // Some devices do not have available EthernetManager. In that case getSystemService will // return null. mEm = mContext.getSystemService(EthernetManager.class); @@ -364,15 +366,14 @@ public class TetherSettings extends RestrictedSettingsFragment mStartTetheringCallback = new OnStartTetheringCallback(this); - mTetheringEventCallback = new TetheringEventCallback(this); - mTm.registerTetheringEventCallback(r -> mHandler.post(r), mTetheringEventCallback); mMassStorageActive = Environment.MEDIA_SHARED.equals(Environment.getExternalStorageState()); registerReceiver(); mEthernetListener = new EthernetListener(this); if (mEm != null) { - mEm.addInterfaceStateListener(r -> mHandler.post(r), mEthernetListener); + mEm.addInterfaceStateListener(mContext.getApplicationContext().getMainExecutor(), + mEthernetListener); } updateUsbState(); @@ -387,13 +388,11 @@ public class TetherSettings extends RestrictedSettingsFragment return; } getActivity().unregisterReceiver(mTetherChangeReceiver); - mTm.unregisterTetheringEventCallback(mTetheringEventCallback); if (mEm != null) { mEm.removeInterfaceStateListener(mEthernetListener); } mTetherChangeReceiver = null; mStartTetheringCallback = null; - mTetheringEventCallback = null; } @VisibleForTesting @@ -688,25 +687,8 @@ public class TetherSettings extends RestrictedSettingsFragment } } - private static final class TetheringEventCallback implements - TetheringManager.TetheringEventCallback { - final WeakReference mTetherSettings; - - TetheringEventCallback(TetherSettings settings) { - mTetherSettings = new WeakReference<>(settings); - } - - @Override - public void onTetheredInterfacesChanged(List interfaces) { - final TetherSettings tetherSettings = mTetherSettings.get(); - if (tetherSettings == null) { - return; - } - tetherSettings.onTetheredInterfacesChanged(interfaces); - } - } - - void onTetheredInterfacesChanged(List interfaces) { + @Override + public void onTetheredInterfacesChanged(List interfaces) { Log.d(TAG, "onTetheredInterfacesChanged() interfaces : " + interfaces.toString()); final String[] tethered = interfaces.toArray(new String[interfaces.size()]); updateUsbState(tethered); diff --git a/src/com/android/settings/network/tether/TetheringHelper.java b/src/com/android/settings/network/tether/TetheringHelper.java new file mode 100644 index 00000000000..da559047f5d --- /dev/null +++ b/src/com/android/settings/network/tether/TetheringHelper.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network.tether; + +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.content.Context; +import android.net.TetheringManager; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Helper class for TetheringManager. + */ +public class TetheringHelper implements DefaultLifecycleObserver { + private static final String TAG = "TetheringHelper"; + + private static final Object sInstanceLock = new Object(); + @TestApi + @GuardedBy("sInstanceLock") + private static Map sTestInstances; + + protected static Context sAppContext; + protected static TetheringManager sTetheringManager; + protected static TetheringEventCallback sTetheringEventCallback = new TetheringEventCallback(); + protected static ArrayList sEventCallbacks = + new ArrayList<>(); + + protected TetheringManager.TetheringEventCallback mCallback; + + /** + * Static method to create a singleton class for TetheringHelper. + * + * @param context The Context this is associated with. + * @return an instance of {@link TetheringHelper} object. + */ + @NonNull + public static TetheringHelper getInstance(@NonNull Context context, + @NonNull TetheringManager.TetheringEventCallback callback, + @NonNull Lifecycle lifecycle) { + synchronized (sInstanceLock) { + if (sTestInstances != null && sTestInstances.containsKey(context)) { + TetheringHelper testInstance = sTestInstances.get(context); + Log.w(TAG, "The context owner use a test instance:" + testInstance); + return testInstance; + } + + if (sAppContext == null) { + sAppContext = context.getApplicationContext(); + sTetheringManager = sAppContext.getSystemService(TetheringManager.class); + } + TetheringHelper helper = new TetheringHelper(); + helper.mCallback = callback; + lifecycle.addObserver(helper); + return helper; + } + } + + /** + * A convenience method to set pre-prepared instance or mock class for testing. + * + * @param context The Context this is associated with. + * @param instance of {@link TetheringHelper} object. + * @hide + */ + @TestApi + @VisibleForTesting + public static void setTestInstance(@NonNull Context context, TetheringHelper instance) { + synchronized (sInstanceLock) { + if (sTestInstances == null) sTestInstances = new ConcurrentHashMap<>(); + Log.w(TAG, "Set a test instance by context:" + context); + sTestInstances.put(context, instance); + } + } + + /** + * The constructor can only be accessed from static method inside the class itself, this is + * to avoid creating a class by adding a private constructor. + */ + private TetheringHelper() { + // Do nothing. + } + + /** + * Returns the TetheringManager If the system service is successfully obtained. + */ + public TetheringManager getTetheringManager() { + return sTetheringManager; + } + + @Override + public void onStart(@NonNull LifecycleOwner owner) { + if (sEventCallbacks.contains(mCallback)) { + Log.w(TAG, "The callback already contains, don't register callback:" + mCallback); + return; + } + sEventCallbacks.add(mCallback); + if (sEventCallbacks.size() == 1) { + sTetheringManager.registerTetheringEventCallback(sAppContext.getMainExecutor(), + sTetheringEventCallback); + } + } + + @Override + public void onStop(@NonNull LifecycleOwner owner) { + if (!sEventCallbacks.remove(mCallback)) { + Log.w(TAG, "The callback does not contain, don't unregister callback:" + mCallback); + return; + } + if (sEventCallbacks.size() == 0) { + sTetheringManager.unregisterTetheringEventCallback(sTetheringEventCallback); + } + } + + @Override + public void onDestroy(@NonNull LifecycleOwner owner) { + mCallback = null; + } + + protected static final class TetheringEventCallback implements + TetheringManager.TetheringEventCallback { + @Override + public void onTetheredInterfacesChanged(List interfaces) { + for (TetheringManager.TetheringEventCallback callback : sEventCallbacks) { + if (callback == null) continue; + callback.onTetheredInterfacesChanged(interfaces); + } + } + } +} + + diff --git a/src/com/android/settings/notification/ConversationListSummaryPreferenceController.java b/src/com/android/settings/notification/ConversationListSummaryPreferenceController.java index 5d7ea205d0e..176d7489ffd 100644 --- a/src/com/android/settings/notification/ConversationListSummaryPreferenceController.java +++ b/src/com/android/settings/notification/ConversationListSummaryPreferenceController.java @@ -20,6 +20,7 @@ import android.content.Context; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.utils.StringUtil; public class ConversationListSummaryPreferenceController extends BasePreferenceController { @@ -41,9 +42,8 @@ public class ConversationListSummaryPreferenceController extends BasePreferenceC if (count == 0) { return mContext.getText(R.string.priority_conversation_count_zero); } - return mContext.getResources().getQuantityString( - R.plurals.priority_conversation_count, - count, count); + return StringUtil.getIcuPluralsString(mContext, count, + R.string.priority_conversation_count); } void setBackend(NotificationBackend backend) { diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java index aa7d547a92c..c63a294ba74 100644 --- a/src/com/android/settings/notification/NotificationBackend.java +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -509,11 +509,11 @@ public class NotificationBackend { context, System.currentTimeMillis() - state.lastSent, true); } else { if (state.avgSentDaily > 0) { - return context.getResources().getQuantityString(R.plurals.notifications_sent_daily, - state.avgSentDaily, state.avgSentDaily); + return StringUtil.getIcuPluralsString(context, state.avgSentDaily, + R.string.notifications_sent_daily); } - return context.getResources().getQuantityString(R.plurals.notifications_sent_weekly, - state.avgSentWeekly, state.avgSentWeekly); + return StringUtil.getIcuPluralsString(context, state.avgSentWeekly, + R.string.notifications_sent_weekly); } } diff --git a/src/com/android/settings/notification/app/DeletedChannelsPreferenceController.java b/src/com/android/settings/notification/app/DeletedChannelsPreferenceController.java index 77a692f1488..6a1d4cb1247 100644 --- a/src/com/android/settings/notification/app/DeletedChannelsPreferenceController.java +++ b/src/com/android/settings/notification/app/DeletedChannelsPreferenceController.java @@ -23,6 +23,7 @@ import androidx.preference.Preference; import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.notification.NotificationBackend; +import com.android.settingslib.utils.StringUtil; public class DeletedChannelsPreferenceController extends NotificationPreferenceController implements PreferenceControllerMixin { @@ -59,8 +60,8 @@ public class DeletedChannelsPreferenceController extends NotificationPreferenceC public void updateState(Preference preference) { if (mAppRow != null) { int deletedChannelCount = mBackend.getDeletedChannelCount(mAppRow.pkg, mAppRow.uid); - preference.setTitle(mContext.getResources().getQuantityString( - R.plurals.deleted_channels, deletedChannelCount, deletedChannelCount)); + preference.setTitle(StringUtil.getIcuPluralsString(mContext, deletedChannelCount, + R.string.deleted_channels)); } preference.setSelectable(false); } diff --git a/src/com/android/settings/notification/history/NotificationHistoryActivity.java b/src/com/android/settings/notification/history/NotificationHistoryActivity.java index 1f83cc72896..021ba6fead8 100644 --- a/src/com/android/settings/notification/history/NotificationHistoryActivity.java +++ b/src/com/android/settings/notification/history/NotificationHistoryActivity.java @@ -60,6 +60,7 @@ import com.android.internal.widget.NotificationExpandButton; import com.android.settings.R; import com.android.settings.notification.NotificationBackend; import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity; +import com.android.settingslib.utils.StringUtil; import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.widget.MainSwitchBar; import com.android.settingslib.widget.OnMainSwitchChangeListener; @@ -203,16 +204,15 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity { icon.setImageDrawable(nhp.icon); TextView count = viewForPackage.findViewById(R.id.count); - count.setText(getResources().getQuantityString(R.plurals.notification_history_count, - nhp.notifications.size(), nhp.notifications.size())); + count.setText(StringUtil.getIcuPluralsString(this, nhp.notifications.size(), + R.string.notification_history_count)); final NotificationHistoryRecyclerView rv = viewForPackage.findViewById(R.id.notification_list); rv.setAdapter(new NotificationHistoryAdapter(mNm, rv, newCount -> { - count.setText(getResources().getQuantityString( - R.plurals.notification_history_count, - newCount, newCount)); + count.setText(StringUtil.getIcuPluralsString(this, newCount, + R.string.notification_history_count)); if (newCount == 0) { viewForPackage.setVisibility(View.GONE); } diff --git a/src/com/android/settings/print/PrintSettingPreferenceController.java b/src/com/android/settings/print/PrintSettingPreferenceController.java index d4ae3116a0c..2a75e486447 100644 --- a/src/com/android/settings/print/PrintSettingPreferenceController.java +++ b/src/com/android/settings/print/PrintSettingPreferenceController.java @@ -34,6 +34,7 @@ import com.android.settingslib.RestrictedPreference; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; +import com.android.settingslib.utils.StringUtil; import java.util.List; @@ -110,17 +111,16 @@ public class PrintSettingPreferenceController extends BasePreferenceController i } if (numActivePrintJobs > 0) { - return mContext.getResources().getQuantityString( - R.plurals.print_jobs_summary, numActivePrintJobs, numActivePrintJobs); + return StringUtil.getIcuPluralsString(mContext, numActivePrintJobs, + R.string.print_jobs_summary); } else { final List services = mPrintManager.getPrintServices(PrintManager.ENABLED_SERVICES); if (services == null || services.isEmpty()) { return mContext.getText(R.string.off); } else { - final int count = services.size(); - return mContext.getResources().getQuantityString( - R.plurals.print_settings_summary, count, count); + return StringUtil.getIcuPluralsString(mContext, services.size(), + R.string.print_settings_summary); } } } diff --git a/src/com/android/settings/regionalpreferences/CalendarTypeController.java b/src/com/android/settings/regionalpreferences/CalendarTypeController.java new file mode 100644 index 00000000000..ea5919198df --- /dev/null +++ b/src/com/android/settings/regionalpreferences/CalendarTypeController.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.regionalpreferences; + +import android.content.Context; +import android.provider.Settings; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +import java.util.Locale; + +/** + * A controller for the entry of Calendar types' page + */ +public class CalendarTypeController extends BasePreferenceController { + public CalendarTypeController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + /** + * @return {@link AvailabilityStatus} for the Setting. This status is used to determine if the + * Setting should be shown or disabled in Settings. Further, it can be used to produce + * appropriate error / warning Slice in the case of unavailability. + *

+ * The status is used for the convenience methods: {@link #isAvailable()}, {@link + * #isSupported()} + *

+ * The inherited class doesn't need to check work profile if android:forWork="true" is set in + * preference xml. + */ + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public CharSequence getSummary() { + String record = Settings.System.getString( + mContext.getContentResolver(), Settings.System.LOCALE_PREFERENCES); + String result = ""; + if (record != null) { + result = LocalePreferences.getCalendarType(Locale.forLanguageTag(record), false); + } + + if (result.isEmpty()) { + result = LocalePreferences.getCalendarType(false); + } + return result.isEmpty() + ? mContext.getString(R.string.default_string_of_regional_preference) : result; + } +} diff --git a/src/com/android/settings/regionalpreferences/FirstDayOfWeekController.java b/src/com/android/settings/regionalpreferences/FirstDayOfWeekController.java new file mode 100644 index 00000000000..3fb8a1ec3d9 --- /dev/null +++ b/src/com/android/settings/regionalpreferences/FirstDayOfWeekController.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.regionalpreferences; + +import android.content.Context; +import android.provider.Settings; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +import java.util.Locale; + +/** A controller for the entry of First Day of Week's page */ +public class FirstDayOfWeekController extends BasePreferenceController { + public FirstDayOfWeekController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + /** + * @return {@link AvailabilityStatus} for the Setting. This status is used to determine if the + * Setting should be shown or disabled in Settings. Further, it can be used to produce + * appropriate error / warning Slice in the case of unavailability. + *

+ * The status is used for the convenience methods: {@link #isAvailable()}, {@link + * #isSupported()} + *

+ * The inherited class doesn't need to check work profile if android:forWork="true" is set in + * preference xml. + */ + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public CharSequence getSummary() { + String record = Settings.System.getString( + mContext.getContentResolver(), Settings.System.LOCALE_PREFERENCES); + String result = ""; + if (record != null) { + result = LocalePreferences.getFirstDayOfWeek(Locale.forLanguageTag(record), false); + } + + if (result.isEmpty()) { + result = LocalePreferences.getFirstDayOfWeek(false); + } + return result.isEmpty() + ? mContext.getString(R.string.default_string_of_regional_preference) : result; + } +} diff --git a/src/com/android/settings/regionalpreferences/LocalePreferences.java b/src/com/android/settings/regionalpreferences/LocalePreferences.java new file mode 100644 index 00000000000..d029ac2967e --- /dev/null +++ b/src/com/android/settings/regionalpreferences/LocalePreferences.java @@ -0,0 +1,604 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.regionalpreferences; +import android.icu.number.LocalizedNumberFormatter; +import android.icu.number.NumberFormatter; +import android.icu.text.DateFormat; +import android.icu.text.DateTimePatternGenerator; +import android.icu.util.MeasureUnit; +import android.os.Build.VERSION; + +import androidx.annotation.DoNotInline; +import androidx.annotation.NonNull; +import androidx.annotation.OptIn; +import androidx.annotation.RestrictTo; +import androidx.annotation.StringDef; +import androidx.core.os.BuildCompat; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Locale; +import java.util.Locale.Category; + +/** + * TODO(b/263861083) This is a temp file and will replace it to Androidx version. + * Provides friendly APIs to get the user's locale preferences. The data can refer to + * external/cldr/common/main/en.xml. + */ +public final class LocalePreferences { + private static final String TAG = LocalePreferences.class.getSimpleName(); + + /** APIs to get the user's preference of the hour cycle. */ + public static class HourCycle { + private static final String U_EXTENSION_OF_HOUR_CYCLE = "hc"; + + /** 12 Hour System (0-11) */ + public static final String H11 = "h11"; + /** 12 Hour System (1-12) */ + public static final String H12 = "h12"; + /** 24 Hour System (0-23) */ + public static final String H23 = "h23"; + /** 24 Hour System (1-24) */ + public static final String H24 = "h24"; + /** Default hour cycle for the locale */ + public static final String DEFAULT = ""; + + /** @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) + @StringDef({ + H11, + H12, + H23, + H24, + DEFAULT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HourCycleTypes { + } + + private HourCycle() { + } + } + + /** + * Return the user's preference of the hour cycle which is from + * {@link Locale#getDefault(Locale.Category)}. The returned result is resolved and + * bases on the {@code Locale#getDefault(Locale.Category)}. E.g. "h23" + */ + @NonNull + @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class) + @HourCycle.HourCycleTypes + public static String getHourCycle() { + return getHourCycle(true); + } + + /** + * Return the hour cycle setting of the inputted {@link Locale}. The returned result is resolved + * and bases on the inputted {@code Locale}. + * E.g. "h23" + */ + @NonNull + @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class) + @HourCycle.HourCycleTypes + public static String getHourCycle(@NonNull Locale locale) { + return getHourCycle(locale, true); + } + + /** + * Return the user's preference of the hour cycle which is from + * {@link Locale#getDefault(Locale.Category)}. E.g. "h23" + * + * @param resolved If the {@code Locale#getDefault(Locale.Category)} contains hour cycle subtag, + * this argument is ignored. If the + * {@code Locale#getDefault(Locale.Category)} doesn't contain hour cycle subtag + * and the resolved argument is true, this function tries to find the default + * hour cycle for the {@code Locale#getDefault(Locale.Category)}. If the + * {@code Locale#getDefault(Locale.Category)} doesn't contain hour cycle subtag + * and the resolved argument is false, this function returns empty string + * i.e. HourCycle.Default. + * @return {@link HourCycle.HourCycleTypes} If the malformed hour cycle format was specified + * in the hour cycle subtag, e.g. en-US-u-hc-h32, this function returns empty string + * i.e. HourCycle.Default. + */ + @NonNull + @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class) + @HourCycle.HourCycleTypes + public static String getHourCycle( + boolean resolved) { + return getHourCycle(Api33Impl.getDefaultLocale(), resolved); + } + + /** + * Return the hour cycle setting of the inputted {@link Locale}. E.g. "en-US-u-hc-h23". + * + * @param locale The {@code Locale} to get the hour cycle. + * @param resolved If the given {@code Locale} contains hour cycle subtag, this argument is + * ignored. If the given {@code Locale} doesn't contain hour cycle subtag and + * the resolved argument is true, this function tries to find the default + * hour cycle for the given {@code Locale}. If the given {@code Locale} doesn't + * contain hour cycle subtag and the resolved argument is false, this function + * return empty string i.e. HourCycle.Default. + * @return {@link HourCycle.HourCycleTypes} If the malformed hour cycle format was specified + * in the hour cycle subtag, e.g. en-US-u-hc-h32, this function returns empty string + * i.e. HourCycle.Default. + */ + @NonNull + @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class) + @HourCycle.HourCycleTypes + public static String getHourCycle(@NonNull Locale locale, boolean resolved) { + if (!BuildCompat.isAtLeastT()) { + throw new IllegalArgumentException("not a valid extension: " + VERSION.SDK_INT); + } + return Api33Impl.getHourCycle(locale, resolved); + } + + /** APIs to get the user's preference of Calendar. */ + public static class CalendarType { + private static final String U_EXTENSION_OF_CALENDAR = "ca"; + /** Chinese Calendar */ + public static final String CHINESE = "chinese"; + /** Dangi Calendar (Korea Calendar) */ + public static final String DANGI = "dangi"; + /** Gregorian Calendar */ + public static final String GREGORIAN = "gregorian"; + /** Hebrew Calendar */ + public static final String HEBREW = "hebrew"; + /** Indian National Calendar */ + public static final String INDIAN = "indian"; + /** Islamic Calendar */ + public static final String ISLAMIC = "islamic"; + /** Islamic Calendar (tabular, civil epoch) */ + public static final String ISLAMIC_CIVIL = "islamic-civil"; + /** Islamic Calendar (Saudi Arabia, sighting) */ + public static final String ISLAMIC_RGSA = "islamic-rgsa"; + /** Islamic Calendar (tabular, astronomical epoch) */ + public static final String ISLAMIC_TBLA = "islamic-tbla"; + /** Islamic Calendar (Umm al-Qura) */ + public static final String ISLAMIC_UMALQURA = "islamic-umalqura"; + /** Persian Calendar */ + public static final String PERSIAN = "persian"; + /** Default calendar for the locale */ + public static final String DEFAULT = ""; + + /** @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) + @StringDef({ + CHINESE, + DANGI, + GREGORIAN, + HEBREW, + INDIAN, + ISLAMIC, + ISLAMIC_CIVIL, + ISLAMIC_RGSA, + ISLAMIC_TBLA, + ISLAMIC_UMALQURA, + PERSIAN, + DEFAULT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CalendarTypes { + } + + private CalendarType() { + } + } + + /** + * Return the user's preference of the calendar type which is from {@link + * Locale#getDefault(Locale.Category)}. The returned result is resolved and bases on + * the {@code Locale#getDefault(Locale.Category)} settings. E.g. "chinese" + */ + @NonNull + @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class) + @CalendarType.CalendarTypes + public static String getCalendarType() { + return getCalendarType(true); + } + + /** + * Return the calendar type of the inputted {@link Locale}. The returned result is resolved and + * bases on the inputted {@link Locale} settings. + * E.g. "chinese" + */ + @NonNull + @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class) + @CalendarType.CalendarTypes + public static String getCalendarType(@NonNull Locale locale) { + return getCalendarType(locale, true); + } + + /** + * Return the user's preference of the calendar type which is from {@link + * Locale#getDefault(Locale.Category)}. E.g. "chinese" + * + * @param resolved If the {@code Locale#getDefault(Locale.Category)} contains calendar type + * subtag, this argument is ignored. If the + * {@code Locale#getDefault(Locale.Category)} doesn't contain calendar type + * subtag and the resolved argument is true, this function tries to find + * the default calendar type for the + * {@code Locale#getDefault(Locale.Category)}. If the + * {@code Locale#getDefault(Locale.Category)} doesn't contain calendar type + * subtag and the resolved argument is false, this function returns empty string + * i.e. CalendarTypes.Default. + * @return {@link CalendarType.CalendarTypes} If the malformed calendar type format was + * specified in the calendar type subtag, e.g. en-US-u-ca-calendar, this function returns + * empty string i.e. CalendarTypes.Default. + */ + @NonNull + @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class) + @CalendarType.CalendarTypes + public static String getCalendarType(boolean resolved) { + return getCalendarType(Api33Impl.getDefaultLocale(), resolved); + } + + /** + * Return the calendar type of the inputted {@link Locale}. E.g. "chinese" + * + * @param locale The {@link Locale} to get the calendar type. + * @param resolved If the given {@code Locale} contains calendar type subtag, this argument is + * ignored. If the given {@code Locale} doesn't contain calendar type subtag and + * the resolved argument is true, this function tries to find the default + * calendar type for the given {@code Locale}. If the given {@code Locale} + * doesn't contain calendar type subtag and the resolved argument is false, this + * function return empty string i.e. CalendarTypes.Default. + * @return {@link CalendarType.CalendarTypes} If the malformed calendar type format was + * specified in the calendar type subtag, e.g. en-US-u-ca-calendar, this function returns + * empty string i.e. CalendarTypes.Default. + */ + @NonNull + @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class) + @CalendarType.CalendarTypes + public static String getCalendarType(@NonNull Locale locale, boolean resolved) { + if (!BuildCompat.isAtLeastT()) { + throw new IllegalArgumentException("not a valid extension: " + VERSION.SDK_INT); + } + return Api33Impl.getCalendarType(locale, resolved); + } + + /** APIs to get the user's preference of temperature unit. */ + public static class TemperatureUnit { + private static final String U_EXTENSION_OF_TEMPERATURE_UNIT = "mu"; + /** Celsius */ + public static final String CELSIUS = "celsius"; + /** Fahrenheit */ + public static final String FAHRENHEIT = "fahrenheit"; + /** Kelvin */ + public static final String KELVIN = "kelvin"; + /** Default Temperature for the locale */ + public static final String DEFAULT = ""; + + /** @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) + @StringDef({ + CELSIUS, + FAHRENHEIT, + KELVIN, + DEFAULT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TemperatureUnits { + } + + private TemperatureUnit() { + } + } + + /** + * Return the user's preference of the temperature unit which is from {@link + * Locale#getDefault(Locale.Category)}. The returned result is resolved and bases on the + * {@code Locale#getDefault(Locale.Category)} settings. E.g. "fahrenheit" + */ + @NonNull + @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class) + @TemperatureUnit.TemperatureUnits + public static String getTemperatureUnit() { + return getTemperatureUnit(true); + } + + /** + * Return the temperature unit of the inputted {@link Locale}. The returned result is resolved + * and bases on the inputted {@code Locale} settings. E.g. "fahrenheit" + */ + @NonNull + @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class) + @TemperatureUnit.TemperatureUnits + public static String getTemperatureUnit( + @NonNull Locale locale) { + return getTemperatureUnit(locale, true); + } + + /** + * Return the user's preference of the temperature unit which is from {@link + * Locale#getDefault(Locale.Category)}. E.g. "fahrenheit" + * + * @param resolved If the {@code Locale#getDefault(Locale.Category)} contains temperature unit + * subtag, this argument is ignored. If the + * {@code Locale#getDefault(Locale.Category)} doesn't contain temperature unit + * subtag and the resolved argument is true, this function tries to find + * the default temperature unit for the + * {@code Locale#getDefault(Locale.Category)}. If the + * {@code Locale#getDefault(Locale.Category)} doesn't contain temperature unit + * subtag and the resolved argument is false, this function returns empty string + * i.e. TemperatureUnits.Default. + * @return {@link TemperatureUnit.TemperatureUnits} If the malformed temperature unit format was + * specified in the temperature unit subtag, e.g. en-US-u-mu-temperature, this function returns + * empty string i.e. TemperatureUnits.Default. + */ + @NonNull + @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class) + @TemperatureUnit.TemperatureUnits + public static String getTemperatureUnit(boolean resolved) { + return getTemperatureUnit(Api33Impl.getDefaultLocale(), resolved); + } + + /** + * Return the temperature unit of the inputted {@link Locale}. E.g. "fahrenheit" + * + * @param locale The {@link Locale} to get the temperature unit. + * @param resolved If the given {@code Locale} contains temperature unit subtag, this argument + * is ignored. If the given {@code Locale} doesn't contain temperature unit + * subtag and the resolved argument is true, this function tries to find + * the default temperature unit for the given {@code Locale}. If the given + * {@code Locale} doesn't contain temperature unit subtag and the resolved + * argument is false, this function return empty string + * i.e. TemperatureUnits.Default. + * @return {@link TemperatureUnit.TemperatureUnits} If the malformed temperature unit format was + * specified in the temperature unit subtag, e.g. en-US-u-mu-temperature, this function returns + * empty string i.e. TemperatureUnits.Default. + */ + @NonNull + @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class) + @TemperatureUnit.TemperatureUnits + public static String getTemperatureUnit(@NonNull Locale locale, boolean resolved) { + if (!BuildCompat.isAtLeastT()) { + throw new IllegalArgumentException("not a valid extension: " + VERSION.SDK_INT); + } + return Api33Impl.getTemperatureUnit(locale, resolved); + } + + /** APIs to get the user's preference of the first day of week. */ + public static class FirstDayOfWeek { + private static final String U_EXTENSION_OF_FIRST_DAY_OF_WEEK = "fw"; + /** Sunday */ + public static final String SUNDAY = "sun"; + /** Monday */ + public static final String MONDAY = "mon"; + /** Tuesday */ + public static final String TUESDAY = "tue"; + /** Wednesday */ + public static final String WEDNESDAY = "wed"; + /** Thursday */ + public static final String THURSDAY = "thu"; + /** Friday */ + public static final String FRIDAY = "fri"; + /** Saturday */ + public static final String SATURDAY = "sat"; + /** Default first day of week for the locale */ + public static final String DEFAULT = ""; + + /** @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) + @StringDef({ + SUNDAY, + MONDAY, + TUESDAY, + WEDNESDAY, + THURSDAY, + FRIDAY, + SATURDAY, + DEFAULT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Days { + } + + private FirstDayOfWeek() { + } + } + + /** + * Return the user's preference of the first day of week which is from + * {@link Locale#getDefault(Locale.Category)}. The returned result is resolved and bases on the + * {@code Locale#getDefault(Locale.Category)} settings. E.g. "sun" + */ + @NonNull + @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class) + @FirstDayOfWeek.Days + public static String getFirstDayOfWeek() { + return getFirstDayOfWeek(true); + } + + /** + * Return the first day of week of the inputted {@link Locale}. The returned result is resolved + * and bases on the inputted {@code Locale} settings. + * E.g. "sun" + */ + @NonNull + @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class) + public static @FirstDayOfWeek.Days String getFirstDayOfWeek(@NonNull Locale locale) { + return getFirstDayOfWeek(locale, true); + } + + /** + * Return the user's preference of the first day of week which is from {@link + * Locale#getDefault(Locale.Category)}. E.g. "sun" + * + * @param resolved If the {@code Locale#getDefault(Locale.Category)} contains first day of week + * subtag, this argument is ignored. If the + * {@code Locale#getDefault(Locale.Category)} doesn't contain first day of week + * subtag and the resolved argument is true, this function tries to find + * the default first day of week for the + * {@code Locale#getDefault(Locale.Category)}. If the + * {@code Locale#getDefault(Locale.Category)} doesn't contain first day of week + * subtag and the resolved argument is false, this function returns empty string + * i.e. Days.Default. + * @return {@link FirstDayOfWeek.Days} If the malformed first day of week format was specified + * in the first day of week subtag, e.g. en-US-u-fw-days, this function returns empty string + * i.e. Days.Default. + */ + @NonNull + @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class) + @FirstDayOfWeek.Days + public static String getFirstDayOfWeek(boolean resolved) { + return getFirstDayOfWeek(Api33Impl.getDefaultLocale(), resolved); + } + + /** + * Return the first day of week of the inputted {@link Locale}. E.g. "sun" + * + * @param locale The {@link Locale} to get the first day of week. + * @param resolved If the given {@code Locale} contains first day of week subtag, this argument + * is ignored. If the given {@code Locale} doesn't contain first day of week + * subtag and the resolved argument is true, this function tries to find + * the default first day of week for the given {@code Locale}. If the given + * {@code Locale} doesn't contain first day of week subtag and the resolved + * argument is false, this function return empty string i.e. Days.Default. + * @return {@link FirstDayOfWeek.Days} If the malformed first day of week format was + * specified in the first day of week subtag, e.g. en-US-u-fw-days, this function returns + * empty string i.e. Days.Default. + */ + @NonNull + @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class) + @FirstDayOfWeek.Days + public static String getFirstDayOfWeek( + @NonNull Locale locale, boolean resolved) { + if (!BuildCompat.isAtLeastT()) { + throw new IllegalArgumentException("not a valid extension: " + VERSION.SDK_INT); + } + + return Api33Impl.getFirstDayOfWeek(locale, resolved); + } + + private static class Api33Impl { + @DoNotInline + @HourCycle.HourCycleTypes + static String getHourCycle(@NonNull Locale locale, + boolean resolved) { + String hc = locale.getUnicodeLocaleType(HourCycle.U_EXTENSION_OF_HOUR_CYCLE); + if (hc != null) { + return hc; + } + if (!resolved) { + return HourCycle.DEFAULT; + } + + return getHourCycleType( + DateTimePatternGenerator.getInstance(locale).getDefaultHourCycle()); + + } + + @DoNotInline + @CalendarType.CalendarTypes + static String getCalendarType(@NonNull Locale locale, boolean resolved) { + String ca = locale.getUnicodeLocaleType(CalendarType.U_EXTENSION_OF_CALENDAR); + if (ca != null) { + return ca; + } + if (!resolved) { + return CalendarType.DEFAULT; + } + + return android.icu.util.Calendar.getInstance(locale).getType(); + } + + @DoNotInline + @TemperatureUnit.TemperatureUnits + static String getTemperatureUnit(@NonNull Locale locale, boolean resolved) { + String mu = + locale.getUnicodeLocaleType(TemperatureUnit.U_EXTENSION_OF_TEMPERATURE_UNIT); + if (mu != null) { + if (mu.contains("fahrenhe")) { + mu = TemperatureUnit.FAHRENHEIT; + } + return mu; + } + if (!resolved) { + return TemperatureUnit.DEFAULT; + } + + return getResolvedTemperatureUnit(locale); + } + + @DoNotInline + @FirstDayOfWeek.Days + static String getFirstDayOfWeek(@NonNull Locale locale, boolean resolved) { + String mu = + locale.getUnicodeLocaleType(FirstDayOfWeek.U_EXTENSION_OF_FIRST_DAY_OF_WEEK); + if (mu != null) { + return mu; + } + if (!resolved) { + return FirstDayOfWeek.DEFAULT; + } + // TODO(b/262294472) Use {@code android.icu.util.Calendar} instead of + // {@code java.util.Calendar}. + return getStringOfFirstDayOfWeek( + java.util.Calendar.getInstance(locale).getFirstDayOfWeek()); + } + + @DoNotInline + static Locale getDefaultLocale() { + return Locale.getDefault(Category.FORMAT); + } + + private static String getStringOfFirstDayOfWeek(int fw) { + String[] arrDays = { + FirstDayOfWeek.SUNDAY, + FirstDayOfWeek.MONDAY, + FirstDayOfWeek.TUESDAY, + FirstDayOfWeek.WEDNESDAY, + FirstDayOfWeek.THURSDAY, + FirstDayOfWeek.FRIDAY, + FirstDayOfWeek.SATURDAY}; + + return fw >= 1 && fw <= 7 ? arrDays[fw - 1] : FirstDayOfWeek.DEFAULT; + } + + @HourCycle.HourCycleTypes + private static String getHourCycleType( + DateFormat.HourCycle hourCycle) { + switch (hourCycle) { + case HOUR_CYCLE_11: + return HourCycle.H11; + case HOUR_CYCLE_12: + return HourCycle.H12; + case HOUR_CYCLE_23: + return HourCycle.H23; + case HOUR_CYCLE_24: + return HourCycle.H24; + default: + return HourCycle.DEFAULT; + } + } + + @TemperatureUnit.TemperatureUnits + private static String getResolvedTemperatureUnit(@NonNull Locale locale) { + LocalizedNumberFormatter nf = NumberFormatter.with() + .usage("temperature") + .unit(MeasureUnit.CELSIUS) + .locale(locale); + return nf.format(1).getOutputUnit().getIdentifier(); + } + + private Api33Impl() { + } + } + + private LocalePreferences() { + } +} diff --git a/src/com/android/settings/regionalpreferences/NumberingSystemController.java b/src/com/android/settings/regionalpreferences/NumberingSystemController.java new file mode 100644 index 00000000000..e951fc24751 --- /dev/null +++ b/src/com/android/settings/regionalpreferences/NumberingSystemController.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.regionalpreferences; + +import android.content.Context; +import android.icu.text.NumberingSystem; +import android.provider.Settings; +import android.text.TextUtils; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +import java.util.Locale; + +/** A controller for the entry of Numbering System's page */ +public class NumberingSystemController extends BasePreferenceController { + private static final String UNICODE_EXTENSION_NUMBERING_SYSTEM = "nu"; + + public NumberingSystemController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + /** + * @return {@link AvailabilityStatus} for the Setting. This status is used to determine if the + * Setting should be shown or disabled in Settings. Further, it can be used to produce + * appropriate error / warning Slice in the case of unavailability. + *

+ * The status is used for the convenience methods: {@link #isAvailable()}, {@link + * #isSupported()} + *

+ * The inherited class doesn't need to check work profile if android:forWork="true" is set in + * preference xml. + */ + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public CharSequence getSummary() { + String record = Settings.System.getString( + mContext.getContentResolver(), Settings.System.LOCALE_PREFERENCES); + String result = ""; + if (!TextUtils.isEmpty(record)) { + result = Locale.forLanguageTag(record) + .getUnicodeLocaleType(UNICODE_EXTENSION_NUMBERING_SYSTEM); + } + + if (TextUtils.isEmpty(result)) { + result = Locale.getDefault(Locale.Category.FORMAT) + .getUnicodeLocaleType(UNICODE_EXTENSION_NUMBERING_SYSTEM); + if (TextUtils.isEmpty(result)) { + return mContext.getString(R.string.default_string_of_regional_preference); + } + } + + Locale locale = new Locale.Builder() + .setUnicodeLocaleKeyword(UNICODE_EXTENSION_NUMBERING_SYSTEM, result) + .build(); + return NumberingSystem.getInstance(locale).getName(); + } +} diff --git a/src/com/android/settings/regionalpreferences/RegionalPreferencesController.java b/src/com/android/settings/regionalpreferences/RegionalPreferencesController.java new file mode 100644 index 00000000000..9f8d3c0b388 --- /dev/null +++ b/src/com/android/settings/regionalpreferences/RegionalPreferencesController.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.regionalpreferences; + +import android.content.Context; +import android.os.SystemProperties; + +import com.android.settings.core.BasePreferenceController; + +/** A controller for the entry of Regional preferences */ +public class RegionalPreferencesController extends BasePreferenceController { + // This is a feature flag and will be removed after feature completed. + static final String FEATURE_PROPERTY = "i18n-feature-locale-preference"; + public RegionalPreferencesController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + /** + * @return {@link AvailabilityStatus} for the Setting. This status is used to determine if the + * Setting should be shown or disabled in Settings. Further, it can be used to produce + * appropriate error / warning Slice in the case of unavailability. + *

+ * The status is used for the convenience methods: {@link #isAvailable()}, {@link + * #isSupported()} + *

+ * The inherited class doesn't need to check work profile if android:forWork="true" is set in + * preference xml. + */ + @Override + public int getAvailabilityStatus() { + return SystemProperties.getBoolean(FEATURE_PROPERTY, false) + ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } +} diff --git a/src/com/android/settings/regionalpreferences/RegionalPreferencesEntriesFragment.java b/src/com/android/settings/regionalpreferences/RegionalPreferencesEntriesFragment.java new file mode 100644 index 00000000000..08961dc110c --- /dev/null +++ b/src/com/android/settings/regionalpreferences/RegionalPreferencesEntriesFragment.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.regionalpreferences; + +import android.app.settings.SettingsEnums; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +/** Provides entries of each regional preferences */ +@SearchIndexable +public class RegionalPreferencesEntriesFragment extends DashboardFragment { + private static final String TAG = RegionalPreferencesEntriesFragment.class.getSimpleName(); + + @Override + public void onStart() { + super.onStart(); + getActivity().setTitle(R.string.regional_preferences_title); + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.REGIONAL_PREFERENCE; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.regional_preference_main_page; + } + + /** + * Get the tag string for logging. + */ + @Override + protected String getLogTag() { + return TAG; + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.regional_preference_main_page); +} diff --git a/src/com/android/settings/regionalpreferences/TemperatureUnitController.java b/src/com/android/settings/regionalpreferences/TemperatureUnitController.java new file mode 100644 index 00000000000..b13b6c4a7a1 --- /dev/null +++ b/src/com/android/settings/regionalpreferences/TemperatureUnitController.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.regionalpreferences; + +import android.content.Context; +import android.provider.Settings; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +import java.util.Locale; + +/** A controller for the entry of Temperature units' page */ +public class TemperatureUnitController extends BasePreferenceController { + public TemperatureUnitController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + /** + * @return {@link AvailabilityStatus} for the Setting. This status is used to determine if the + * Setting should be shown or disabled in Settings. Further, it can be used to produce + * appropriate error / warning Slice in the case of unavailability. + *

+ * The status is used for the convenience methods: {@link #isAvailable()}, {@link + * #isSupported()} + *

+ * The inherited class doesn't need to check work profile if android:forWork="true" is set in + * preference xml. + */ + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public CharSequence getSummary() { + String record = Settings.System.getString( + mContext.getContentResolver(), Settings.System.LOCALE_PREFERENCES); + String result = ""; + if (record != null) { + result = LocalePreferences.getTemperatureUnit(Locale.forLanguageTag(record), false); + } + + if (result.isEmpty()) { + result = LocalePreferences.getTemperatureUnit(false); + } + return result.isEmpty() + ? mContext.getString(R.string.default_string_of_regional_preference) : result; + } +} diff --git a/src/com/android/settings/security/CredentialManagementAppAdapter.java b/src/com/android/settings/security/CredentialManagementAppAdapter.java index 3f4eb2a4a2c..567cffda156 100644 --- a/src/com/android/settings/security/CredentialManagementAppAdapter.java +++ b/src/com/android/settings/security/CredentialManagementAppAdapter.java @@ -34,6 +34,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.android.settings.R; +import com.android.settingslib.utils.StringUtil; import java.util.ArrayList; import java.util.List; @@ -193,8 +194,8 @@ public class CredentialManagementAppAdapter extends RecyclerView.Adapter urisToAliases) { - return mContext.getResources().getQuantityString(R.plurals.number_of_urls, - urisToAliases.size(), urisToAliases.size()); + return StringUtil.getIcuPluralsString(mContext, urisToAliases.size(), + R.string.number_of_urls); } } diff --git a/src/com/android/settings/spa/notification/AppNotificationRepository.kt b/src/com/android/settings/spa/notification/AppNotificationRepository.kt index fe8babb56e7..8dc4f20e591 100644 --- a/src/com/android/settings/spa/notification/AppNotificationRepository.kt +++ b/src/com/android/settings/spa/notification/AppNotificationRepository.kt @@ -177,12 +177,14 @@ class AppNotificationRepository( fun calculateFrequencySummary(sentCount: Int): String { val dailyFrequency = (sentCount.toFloat() / DAYS_TO_CHECK).roundToInt() return if (dailyFrequency > 0) { - context.resources.getQuantityString( - R.plurals.notifications_sent_daily, dailyFrequency, dailyFrequency + context.formatString( + R.string.notifications_sent_daily, + "count" to dailyFrequency, ) } else { - context.resources.getQuantityString( - R.plurals.notifications_sent_weekly, sentCount, sentCount + context.formatString( + R.string.notifications_sent_weekly, + "count" to sentCount, ) } } diff --git a/src/com/android/settings/wifi/WifiEntryPreference.java b/src/com/android/settings/wifi/WifiEntryPreference.java index 5187ee3119c..5b448870016 100644 --- a/src/com/android/settings/wifi/WifiEntryPreference.java +++ b/src/com/android/settings/wifi/WifiEntryPreference.java @@ -82,7 +82,6 @@ public class WifiEntryPreference extends RestrictedPreference implements super(context); setLayoutResource(R.layout.preference_access_point); - setWidgetLayoutResource(R.layout.access_point_friction_widget); mFrictionSld = getFrictionStateListDrawable(); mWifiEntry = wifiEntry; mWifiEntry.setListener(this); @@ -280,6 +279,11 @@ public class WifiEntryPreference extends RestrictedPreference implements notifyChanged(); } + @Override + protected int getSecondTargetResId() { + return R.layout.access_point_friction_widget; + } + @Override public void onClick(View view) { if (view.getId() == R.id.icon_button) { diff --git a/tests/robotests/src/com/android/settings/accessibility/AutoclickPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/AutoclickPreferenceControllerTest.java index fd6bedc36d4..106b617a166 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AutoclickPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AutoclickPreferenceControllerTest.java @@ -72,8 +72,8 @@ public class AutoclickPreferenceControllerTest { assertThat(mController.getSummary().toString()) .isEqualTo(AutoclickUtils.getAutoclickDelaySummary( - mContext.getResources(), - R.plurals.accessibilty_autoclick_preference_subtitle_medium_delay, + mContext, + R.string.accessibilty_autoclick_preference_subtitle_medium_delay, AUTOCLICK_DELAY_DEFAULT).toString()); } } diff --git a/tests/robotests/src/com/android/settings/accessibility/AutoclickUtilsTest.java b/tests/robotests/src/com/android/settings/accessibility/AutoclickUtilsTest.java index 46b1e2b1b7b..a87c03f7e42 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AutoclickUtilsTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AutoclickUtilsTest.java @@ -40,7 +40,7 @@ public final class AutoclickUtilsTest { @Test public void getAutoclickDelaySummary_minDelay_shouldReturnOnSummary() { final CharSequence summary = AutoclickUtils.getAutoclickDelaySummary( - mContext.getResources(), R.plurals.accessibilty_autoclick_delay_unit_second, + mContext, R.string.accessibilty_autoclick_delay_unit_second, MIN_AUTOCLICK_DELAY_MS); assertThat(summary.toString()).isEqualTo("0.2 seconds"); } @@ -48,7 +48,7 @@ public final class AutoclickUtilsTest { @Test public void getAutoclickDelaySummary_maxDelay_shouldReturnOnSummary() { final CharSequence summary = AutoclickUtils.getAutoclickDelaySummary( - mContext.getResources(), R.plurals.accessibilty_autoclick_delay_unit_second, + mContext, R.string.accessibilty_autoclick_delay_unit_second, MAX_AUTOCLICK_DELAY_MS); assertThat(summary.toString()).isEqualTo("1 second"); } diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceControllerTest.java index c0e27b61264..a9cac87cebc 100644 --- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceControllerTest.java @@ -46,6 +46,7 @@ import com.android.settings.testutils.FakeFeatureFactory; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedPreference; import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.utils.StringUtil; import org.junit.Before; import org.junit.Ignore; @@ -140,8 +141,8 @@ public class FingerprintStatusPreferenceControllerTest { mController.updateState(mPreference); - assertThat(mPreference.getSummary()).isEqualTo(mContext.getResources().getQuantityString( - R.plurals.security_settings_fingerprint_preference_summary, 1, 1)); + assertThat(mPreference.getSummary()).isEqualTo(StringUtil.getIcuPluralsString(mContext, 1, + R.string.security_settings_fingerprint_preference_summary)); assertThat(mPreference.isVisible()).isTrue(); } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java index 57af4126540..501279c47f2 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java @@ -159,7 +159,9 @@ public class BluetoothDeviceDetailsFragmentTest { public void getTitle_inputDeviceTitle() { FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES, true); - doReturn(mock(InputDevice.class)).when(mFragment).getInputDevice(mContext); + InputDevice inputDevice = mock(InputDevice.class); + doReturn(true).when(inputDevice).supportsSource(InputDevice.SOURCE_STYLUS); + doReturn(inputDevice).when(mFragment).getInputDevice(mContext); mFragment.onAttach(mContext); mFragment.setTitleForInputDevice(); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTipTest.java index f02eec2cb9e..bb700776532 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTipTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTipTest.java @@ -35,6 +35,7 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; import com.android.settings.fuelgauge.batterytip.AppInfo; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import com.android.settingslib.utils.StringUtil; import org.junit.After; import org.junit.Before; @@ -148,8 +149,8 @@ public class RestrictAppTipTest { @Test public void getSummary_oneAppHandled_showHandledSummary() { assertThat(mHandledBatteryTip.getSummary(mContext).toString()) - .isEqualTo(mContext.getResources().getQuantityString( - R.plurals.battery_tip_restrict_handled_summary, 1)); + .isEqualTo(StringUtil.getIcuPluralsString(mContext, 1, + R.string.battery_tip_restrict_handled_summary)); } @Test @@ -157,8 +158,8 @@ public class RestrictAppTipTest { mUsageAppList.add(new AppInfo.Builder().build()); mHandledBatteryTip = new RestrictAppTip(BatteryTip.StateType.HANDLED, mUsageAppList); assertThat(mHandledBatteryTip.getSummary(mContext)) - .isEqualTo(mContext.getResources().getQuantityString( - R.plurals.battery_tip_restrict_handled_summary, 2)); + .isEqualTo(StringUtil.getIcuPluralsString(mContext, 2, + R.string.battery_tip_restrict_handled_summary)); } @Test diff --git a/tests/robotests/src/com/android/settings/location/TopLevelLocationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/TopLevelLocationPreferenceControllerTest.java index db809f380a0..0801586dc7a 100644 --- a/tests/robotests/src/com/android/settings/location/TopLevelLocationPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/location/TopLevelLocationPreferenceControllerTest.java @@ -22,6 +22,7 @@ import android.content.Context; import android.location.LocationManager; import com.android.settings.R; +import com.android.settingslib.utils.StringUtil; import org.junit.Before; import org.junit.Test; @@ -67,9 +68,8 @@ public class TopLevelLocationPreferenceControllerTest { // Turn on location again and check if the previous summary text is still cached. mLocationManager.setLocationEnabledForUser(true, android.os.Process.myUserHandle()); assertThat(mController.getSummary()).isEqualTo( - mContext.getResources().getQuantityString( - R.plurals.location_settings_summary_location_on, locationAppCount, - locationAppCount)); + StringUtil.getIcuPluralsString(mContext, locationAppCount, + R.string.location_settings_summary_location_on)); } @Test @@ -78,9 +78,8 @@ public class TopLevelLocationPreferenceControllerTest { mLocationManager.setLocationEnabledForUser(true, android.os.Process.myUserHandle()); mController.setLocationAppCount(locationAppCount); assertThat(mController.getSummary()).isEqualTo( - mContext.getResources().getQuantityString( - R.plurals.location_settings_summary_location_on, - locationAppCount, locationAppCount)); + StringUtil.getIcuPluralsString(mContext, locationAppCount, + R.string.location_settings_summary_location_on)); } @Test @@ -89,8 +88,7 @@ public class TopLevelLocationPreferenceControllerTest { mLocationManager.setLocationEnabledForUser(true, android.os.Process.myUserHandle()); mController.setLocationAppCount(locationAppCount); assertThat(mController.getSummary()).isEqualTo( - mContext.getResources().getQuantityString( - R.plurals.location_settings_summary_location_on, - locationAppCount, locationAppCount)); + StringUtil.getIcuPluralsString(mContext, locationAppCount, + R.string.location_settings_summary_location_on)); } } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/network/tether/TetheringHelperTest.java b/tests/robotests/src/com/android/settings/network/tether/TetheringHelperTest.java new file mode 100644 index 00000000000..5b9ace8ef7a --- /dev/null +++ b/tests/robotests/src/com/android/settings/network/tether/TetheringHelperTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network.tether; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.net.TetheringManager; + +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.List; +import java.util.concurrent.Executor; + +@RunWith(AndroidJUnit4.class) +public class TetheringHelperTest { + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Spy + Context mContext = ApplicationProvider.getApplicationContext(); + @Mock + Lifecycle mLifecycle; + @Mock + LifecycleOwner mLifecycleOwner; + @Mock + TetheringManager mTetheringManager; + @Mock + TetheringManager.TetheringEventCallback mTetheringEventCallback; + @Mock + List mInterfaces; + + TetheringHelper mHelper; + + @Before + public void setUp() { + mHelper = TetheringHelper.getInstance(mContext, mTetheringEventCallback, mLifecycle); + mHelper.sTetheringManager = mTetheringManager; + } + + @Test + public void constructor_propertiesShouldBeReady() { + assertThat(mHelper.sAppContext).isNotNull(); + assertThat(mHelper.sTetheringManager).isNotNull(); + assertThat(mHelper.mCallback).isNotNull(); + verify(mLifecycle).addObserver(any()); + } + + @Test + public void getTetheringManager_isNotNull() { + assertThat(mHelper.getTetheringManager()).isNotNull(); + } + + @Test + public void onStart_eventCallbacksClear_addAndRegisterCallback() { + mHelper.sEventCallbacks.clear(); + + mHelper.onStart(mLifecycleOwner); + + assertThat(mHelper.sEventCallbacks.contains(mTetheringEventCallback)).isTrue(); + assertThat(mHelper.sEventCallbacks.size()).isEqualTo(1); + verify(mTetheringManager).registerTetheringEventCallback(any(Executor.class), + eq(TetheringHelper.sTetheringEventCallback)); + } + + @Test + public void onStart_eventCallbacksContains_doNotRegisterCallback() { + mHelper.sEventCallbacks.clear(); + mHelper.sEventCallbacks.add(mTetheringEventCallback); + + mHelper.onStart(mLifecycleOwner); + + verify(mTetheringManager, never()).registerTetheringEventCallback(any(Executor.class), + eq(TetheringHelper.sTetheringEventCallback)); + } + + @Test + public void onStop_eventCallbacksContains_removeAndUnregisterCallback() { + mHelper.sEventCallbacks.clear(); + mHelper.sEventCallbacks.add(mTetheringEventCallback); + + mHelper.onStop(mLifecycleOwner); + + assertThat(mHelper.sEventCallbacks.contains(mTetheringEventCallback)).isFalse(); + assertThat(mHelper.sEventCallbacks.size()).isEqualTo(0); + verify(mTetheringManager).unregisterTetheringEventCallback( + eq(TetheringHelper.sTetheringEventCallback)); + } + + @Test + public void onStop_eventCallbacksClear_doNotUnregisterCallback() { + mHelper.sEventCallbacks.clear(); + + mHelper.onStop(mLifecycleOwner); + + assertThat(mHelper.sEventCallbacks.contains(mTetheringEventCallback)).isFalse(); + assertThat(mHelper.sEventCallbacks.size()).isEqualTo(0); + verify(mTetheringManager, never()).unregisterTetheringEventCallback( + eq(TetheringHelper.sTetheringEventCallback)); + } + + @Test + public void onTetheredInterfacesChanged_eventCallbacksContains_doCallback() { + mHelper.sEventCallbacks.clear(); + mHelper.sEventCallbacks.add(mTetheringEventCallback); + + mHelper.sTetheringEventCallback.onTetheredInterfacesChanged(mInterfaces); + + verify(mTetheringEventCallback).onTetheredInterfacesChanged(eq(mInterfaces)); + } + + @Test + public void onTetheredInterfacesChanged_eventCallbacksClear_doNotCallback() { + mHelper.sEventCallbacks.clear(); + + mHelper.sTetheringEventCallback.onTetheredInterfacesChanged(mInterfaces); + + verify(mTetheringEventCallback, never()).onTetheredInterfacesChanged(eq(mInterfaces)); + } +} diff --git a/tests/robotests/src/com/android/settings/print/PrintSettingsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/print/PrintSettingsPreferenceControllerTest.java index c782d9b23f9..9cecf4cbc2b 100644 --- a/tests/robotests/src/com/android/settings/print/PrintSettingsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/print/PrintSettingsPreferenceControllerTest.java @@ -43,6 +43,7 @@ import androidx.lifecycle.LifecycleOwner; import com.android.settings.R; import com.android.settingslib.RestrictedPreference; import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.utils.StringUtil; import org.junit.Before; import org.junit.Test; @@ -119,8 +120,8 @@ public class PrintSettingsPreferenceControllerTest { mController.updateState(mPreference); assertThat(mPreference.getSummary()) - .isEqualTo(mContext.getResources() - .getQuantityString(R.plurals.print_jobs_summary, 1, 1)); + .isEqualTo(StringUtil.getIcuPluralsString(mContext, 1, + R.string.print_jobs_summary)); } @Test @@ -135,8 +136,8 @@ public class PrintSettingsPreferenceControllerTest { mController.updateState(mPreference); assertThat(mPreference.getSummary()) - .isEqualTo(mContext.getResources() - .getQuantityString(R.plurals.print_settings_summary, 2, 2)); + .isEqualTo(StringUtil.getIcuPluralsString(mContext, 2, + R.string.print_settings_summary)); // No service when(mPrintManager.getPrintServices(PrintManager.ENABLED_SERVICES)).thenReturn(null); diff --git a/tests/robotests/src/com/android/settings/wifi/WifiEntryPreferenceTest.java b/tests/robotests/src/com/android/settings/wifi/WifiEntryPreferenceTest.java index a5d63dbd34d..a60b5313730 100644 --- a/tests/robotests/src/com/android/settings/wifi/WifiEntryPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/wifi/WifiEntryPreferenceTest.java @@ -80,6 +80,8 @@ public class WifiEntryPreferenceTest { private static final String MOCK_SUMMARY = "summary"; private static final String FAKE_URI_STRING = "fakeuri"; + WifiEntryPreference mPref; + @Before public void setUp() { mContext = RuntimeEnvironment.application; @@ -105,6 +107,8 @@ public class WifiEntryPreferenceTest { .thenReturn(mMockShowXDrawable3); when(mMockIconInjector.getIcon(true /* showX */, 4)) .thenReturn(mMockShowXDrawable4); + + mPref = new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector); } @Test @@ -128,52 +132,46 @@ public class WifiEntryPreferenceTest { @Test public void titleChanged_refresh_shouldUpdateTitle() { - final WifiEntryPreference pref = - new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector); final String updatedTitle = "updated title"; when(mMockWifiEntry.getTitle()).thenReturn(updatedTitle); - pref.refresh(); + mPref.refresh(); - assertThat(pref.getTitle()).isEqualTo(updatedTitle); + assertThat(mPref.getTitle().toString()).isEqualTo(updatedTitle); } @Test public void summaryChanged_refresh_shouldUpdateSummary() { - final WifiEntryPreference pref = - new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector); final String updatedSummary = "updated summary"; when(mMockWifiEntry.getSummary(false /* concise */)).thenReturn(updatedSummary); - pref.refresh(); + mPref.refresh(); - assertThat(pref.getSummary()).isEqualTo(updatedSummary); + assertThat(mPref.getSummary().toString()).isEqualTo(updatedSummary); } @Test public void levelChanged_refresh_shouldUpdateLevelIcon() { final List iconList = new ArrayList<>(); - final WifiEntryPreference pref = - new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector); when(mMockWifiEntry.getLevel()).thenReturn(0); - pref.refresh(); - iconList.add(pref.getIcon()); + mPref.refresh(); + iconList.add(mPref.getIcon()); when(mMockWifiEntry.getLevel()).thenReturn(1); - pref.refresh(); - iconList.add(pref.getIcon()); + mPref.refresh(); + iconList.add(mPref.getIcon()); when(mMockWifiEntry.getLevel()).thenReturn(2); - pref.refresh(); - iconList.add(pref.getIcon()); + mPref.refresh(); + iconList.add(mPref.getIcon()); when(mMockWifiEntry.getLevel()).thenReturn(3); - pref.refresh(); - iconList.add(pref.getIcon()); + mPref.refresh(); + iconList.add(mPref.getIcon()); when(mMockWifiEntry.getLevel()).thenReturn(4); - pref.refresh(); - iconList.add(pref.getIcon()); + mPref.refresh(); + iconList.add(mPref.getIcon()); when(mMockWifiEntry.getLevel()).thenReturn(-1); - pref.refresh(); - iconList.add(pref.getIcon()); + mPref.refresh(); + iconList.add(mPref.getIcon()); assertThat(iconList).containsExactly(mMockDrawable0, mMockDrawable1, mMockDrawable2, mMockDrawable3, mMockDrawable4, null); @@ -183,27 +181,25 @@ public class WifiEntryPreferenceTest { public void levelChanged_showXWifiRefresh_shouldUpdateLevelIcon() { final List iconList = new ArrayList<>(); when(mMockWifiEntry.shouldShowXLevelIcon()).thenReturn(true); - final WifiEntryPreference pref = - new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector); when(mMockWifiEntry.getLevel()).thenReturn(0); - pref.refresh(); - iconList.add(pref.getIcon()); + mPref.refresh(); + iconList.add(mPref.getIcon()); when(mMockWifiEntry.getLevel()).thenReturn(1); - pref.refresh(); - iconList.add(pref.getIcon()); + mPref.refresh(); + iconList.add(mPref.getIcon()); when(mMockWifiEntry.getLevel()).thenReturn(2); - pref.refresh(); - iconList.add(pref.getIcon()); + mPref.refresh(); + iconList.add(mPref.getIcon()); when(mMockWifiEntry.getLevel()).thenReturn(3); - pref.refresh(); - iconList.add(pref.getIcon()); + mPref.refresh(); + iconList.add(mPref.getIcon()); when(mMockWifiEntry.getLevel()).thenReturn(4); - pref.refresh(); - iconList.add(pref.getIcon()); + mPref.refresh(); + iconList.add(mPref.getIcon()); when(mMockWifiEntry.getLevel()).thenReturn(-1); - pref.refresh(); - iconList.add(pref.getIcon()); + mPref.refresh(); + iconList.add(mPref.getIcon()); assertThat(iconList).containsExactly(mMockShowXDrawable0, mMockShowXDrawable1, mMockShowXDrawable2, mMockShowXDrawable3, mMockShowXDrawable4, null); @@ -212,14 +208,12 @@ public class WifiEntryPreferenceTest { @Test public void notNull_whenGetHelpUriString_shouldSetImageButtonVisible() { when(mMockWifiEntry.getHelpUriString()).thenReturn(FAKE_URI_STRING); - final WifiEntryPreference pref = - new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector); final LayoutInflater inflater = LayoutInflater.from(mContext); - final View view = inflater.inflate(pref.getLayoutResource(), new LinearLayout(mContext), + final View view = inflater.inflate(mPref.getLayoutResource(), new LinearLayout(mContext), false); final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(view); - pref.onBindViewHolder(holder); + mPref.onBindViewHolder(holder); assertThat(view.findViewById(R.id.icon_button).getVisibility()).isEqualTo(View.VISIBLE); } @@ -227,14 +221,12 @@ public class WifiEntryPreferenceTest { @Test public void helpButton_whenGetHelpUriStringNotNull_shouldSetCorrectContentDescription() { when(mMockWifiEntry.getHelpUriString()).thenReturn(FAKE_URI_STRING); - final WifiEntryPreference pref = - new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector); final LayoutInflater inflater = LayoutInflater.from(mContext); - final View view = inflater.inflate(pref.getLayoutResource(), new LinearLayout(mContext), + final View view = inflater.inflate(mPref.getLayoutResource(), new LinearLayout(mContext), false); final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(view); - pref.onBindViewHolder(holder); + mPref.onBindViewHolder(holder); assertThat(view.findViewById(R.id.icon_button).getContentDescription()).isEqualTo( mContext.getString(R.string.help_label)); @@ -243,25 +235,25 @@ public class WifiEntryPreferenceTest { @Test public void subscriptionEntry_shouldSetImageButtonGone() { when(mMockWifiEntry.isSubscription()).thenReturn(true); - final WifiEntryPreference pref = - new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector); final LayoutInflater inflater = LayoutInflater.from(mContext); - final View view = inflater.inflate(pref.getLayoutResource(), new LinearLayout(mContext), + final View view = inflater.inflate(mPref.getLayoutResource(), new LinearLayout(mContext), false); final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(view); - pref.onBindViewHolder(holder); + mPref.onBindViewHolder(holder); assertThat(view.findViewById(R.id.icon_button).getVisibility()).isEqualTo(View.GONE); } @Test - public void updateIcon_ShouldSetTintListForDrawable() { - WifiEntryPreference pref = - new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector); - - pref.updateIcon(false /* showX */, 4 /* level */); + public void updateIcon_shouldSetTintListForDrawable() { + mPref.updateIcon(false /* showX */, 4 /* level */); verify(mMockDrawable4).setTintList(any()); } + + @Test + public void getSecondTargetResId_shouldNotReturnZero() { + assertThat(mPref.getSecondTargetResId()).isNotEqualTo(0); + } } diff --git a/tests/unit/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtilsTest.java b/tests/unit/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtilsTest.java index 10f11101ddf..706aedad074 100644 --- a/tests/unit/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtilsTest.java +++ b/tests/unit/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtilsTest.java @@ -41,6 +41,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.settings.Settings; import com.android.settings.testutils.ResourcesUtils; import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.utils.StringUtil; import org.junit.Before; import org.junit.Test; @@ -244,11 +245,10 @@ public class CombinedBiometricStatusUtilsTest { public void getSummary_whenNoFaceEnrolled_whenMultipleFingerprints_returnsFingerprints() { final int enrolledFingerprintsCount = 2; final int stringResId = ResourcesUtils.getResourcesId( - ApplicationProvider.getApplicationContext(), "plurals", + ApplicationProvider.getApplicationContext(), "string", "security_settings_fingerprint_preference_summary"); - final String summary = mApplicationContext.getResources().getQuantityString( - stringResId, enrolledFingerprintsCount /* quantity */, - enrolledFingerprintsCount /* formatArgs */); + final String summary = StringUtil.getIcuPluralsString(mApplicationContext, + enrolledFingerprintsCount, stringResId); when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false); when(mFingerprintManager.getEnrolledFingerprints(anyInt())) @@ -261,11 +261,10 @@ public class CombinedBiometricStatusUtilsTest { public void getSummary_whenNoFaceEnrolled_whenSingleFingerprints_returnsFingerprints() { final int enrolledFingerprintsCount = 1; final int stringResId = ResourcesUtils.getResourcesId( - ApplicationProvider.getApplicationContext(), "plurals", + ApplicationProvider.getApplicationContext(), "string", "security_settings_fingerprint_preference_summary"); - final String summary = mApplicationContext.getResources().getQuantityString( - stringResId, enrolledFingerprintsCount /* quantity */, - enrolledFingerprintsCount /* formatArgs */); + final String summary = StringUtil.getIcuPluralsString(mApplicationContext, + enrolledFingerprintsCount, stringResId); when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false); when(mFingerprintManager.getEnrolledFingerprints(anyInt())) diff --git a/tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtilsTest.java b/tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtilsTest.java index 69e5e2f485b..450adc60bb8 100644 --- a/tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtilsTest.java +++ b/tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtilsTest.java @@ -37,6 +37,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.settings.testutils.ResourcesUtils; import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.utils.StringUtil; import org.junit.Before; import org.junit.Test; @@ -165,11 +166,10 @@ public class FingerprintStatusUtilsTest { public void getSummary_whenEnrolled_returnsSummary() { final int enrolledFingerprintsCount = 2; final int stringResId = ResourcesUtils.getResourcesId( - ApplicationProvider.getApplicationContext(), "plurals", + ApplicationProvider.getApplicationContext(), "string", "security_settings_fingerprint_preference_summary"); - final String summary = mApplicationContext.getResources().getQuantityString( - stringResId, enrolledFingerprintsCount /* quantity */, - enrolledFingerprintsCount /* formatArgs */); + final String summary = StringUtil.getIcuPluralsString(mApplicationContext, + enrolledFingerprintsCount, stringResId); when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(true); when(mFingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn( diff --git a/tests/unit/src/com/android/settings/regionalpreferences/CalendarTypeControllerTest.java b/tests/unit/src/com/android/settings/regionalpreferences/CalendarTypeControllerTest.java new file mode 100644 index 00000000000..7e1ec94af60 --- /dev/null +++ b/tests/unit/src/com/android/settings/regionalpreferences/CalendarTypeControllerTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.regionalpreferences; + +import static org.junit.Assert.assertEquals; + +import android.content.Context; +import android.provider.Settings; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.testutils.ResourcesUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Locale; + +public class CalendarTypeControllerTest { + private Context mApplicationContext; + private CalendarTypeController mController; + private String mCacheProviderContent = ""; + private Locale mCacheLocale; + + @Before + public void setUp() throws Exception { + mApplicationContext = ApplicationProvider.getApplicationContext(); + mController = new CalendarTypeController(mApplicationContext, "key"); + mCacheProviderContent = Settings.System.getString( + mApplicationContext.getContentResolver(), Settings.System.LOCALE_PREFERENCES); + mCacheLocale = Locale.getDefault(Locale.Category.FORMAT); + } + + @After + public void tearDown() throws Exception { + RegionalPreferenceUtils.setSettingsProviderContent( + mApplicationContext, mCacheProviderContent); + Locale.setDefault(mCacheLocale); + } + + @Test + public void getSummary_hasProviderValue_resultIsChinese() { + RegionalPreferenceUtils.setSettingsProviderContent( + mApplicationContext, "und-u-ca-chinese"); + + CharSequence type = mController.getSummary(); + + assertEquals(LocalePreferences.CalendarType.CHINESE, type.toString()); + } + + @Test + public void getSummary_hasProviderValue_resultIsDangi() { + RegionalPreferenceUtils.setSettingsProviderContent( + mApplicationContext, "und-u-ca-dangi"); + + CharSequence type = mController.getSummary(); + + assertEquals(LocalePreferences.CalendarType.DANGI, type.toString()); + } + + @Test + public void getSummary_noProviderValueButHasDefaultLocaleWithSubtag_resultIsSat() { + RegionalPreferenceUtils.setSettingsProviderContent(mApplicationContext, ""); + Locale.setDefault(Locale.forLanguageTag("en-US-u-ca-chinese")); + + CharSequence type = mController.getSummary(); + + assertEquals(LocalePreferences.CalendarType.CHINESE, type.toString()); + } + + @Test + public void getSummary_noProviderValueAndDefaultLocaleWithoutSubtag_resultIsEmpty() { + RegionalPreferenceUtils.setSettingsProviderContent(mApplicationContext, ""); + Locale.setDefault(Locale.forLanguageTag("en-US")); + + CharSequence type = mController.getSummary(); + + assertEquals(ResourcesUtils.getResourcesString( + mApplicationContext, "default_string_of_regional_preference"), type.toString()); + } +} diff --git a/tests/unit/src/com/android/settings/regionalpreferences/FirstDayOfWeekControllerTest.java b/tests/unit/src/com/android/settings/regionalpreferences/FirstDayOfWeekControllerTest.java new file mode 100644 index 00000000000..b6582421362 --- /dev/null +++ b/tests/unit/src/com/android/settings/regionalpreferences/FirstDayOfWeekControllerTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.regionalpreferences; + +import static org.junit.Assert.assertEquals; + +import android.content.Context; +import android.provider.Settings; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.testutils.ResourcesUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Locale; + +public class FirstDayOfWeekControllerTest { + private Context mApplicationContext; + private FirstDayOfWeekController mController; + private String mCacheProviderContent = ""; + private Locale mCacheLocale; + + @Before + public void setUp() throws Exception { + mApplicationContext = ApplicationProvider.getApplicationContext(); + mController = new FirstDayOfWeekController(mApplicationContext, "key"); + mCacheProviderContent = Settings.System.getString( + mApplicationContext.getContentResolver(), Settings.System.LOCALE_PREFERENCES); + mCacheLocale = Locale.getDefault(Locale.Category.FORMAT); + } + + @After + public void tearDown() throws Exception { + RegionalPreferenceUtils.setSettingsProviderContent( + mApplicationContext, mCacheProviderContent); + Locale.setDefault(mCacheLocale); + } + + @Test + public void getSummary_hasProviderValue_resultIsWed() { + RegionalPreferenceUtils.setSettingsProviderContent(mApplicationContext, "und-u-fw-wed"); + + CharSequence day = mController.getSummary(); + + assertEquals(LocalePreferences.FirstDayOfWeek.WEDNESDAY, day.toString()); + } + + @Test + public void getSummary_hasProviderValue_resultIsSat() { + RegionalPreferenceUtils.setSettingsProviderContent(mApplicationContext, "und-u-fw-sat"); + + CharSequence day = mController.getSummary(); + + assertEquals(LocalePreferences.FirstDayOfWeek.SATURDAY, day.toString()); + } + + @Test + public void getSummary_noProviderValueButHasDefaultLocaleWithSubtag_resultIsSat() { + RegionalPreferenceUtils.setSettingsProviderContent(mApplicationContext, ""); + Locale.setDefault(Locale.forLanguageTag("en-US-u-fw-sat")); + + CharSequence day = mController.getSummary(); + + assertEquals(LocalePreferences.FirstDayOfWeek.SATURDAY, day.toString()); + } + + @Test + public void getSummary_noProviderValueAndDefaultLocaleWithoutSubtag_resultIsEmpty() { + RegionalPreferenceUtils.setSettingsProviderContent(mApplicationContext, ""); + Locale.setDefault(Locale.forLanguageTag("en-US")); + + CharSequence day = mController.getSummary(); + + assertEquals(ResourcesUtils.getResourcesString( + mApplicationContext, "default_string_of_regional_preference"), day.toString()); + } +} diff --git a/tests/unit/src/com/android/settings/regionalpreferences/RegionalPreferenceUtils.java b/tests/unit/src/com/android/settings/regionalpreferences/RegionalPreferenceUtils.java new file mode 100644 index 00000000000..709413c7523 --- /dev/null +++ b/tests/unit/src/com/android/settings/regionalpreferences/RegionalPreferenceUtils.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.regionalpreferences; + +import android.content.Context; +import android.provider.Settings; + +/** Utils for each regional preference unit test. */ +public final class RegionalPreferenceUtils { + /** Set language tag to Settings Provider */ + public static void setSettingsProviderContent(Context context, String languageTag) { + Settings.System.putString(context.getContentResolver(), + Settings.System.LOCALE_PREFERENCES, languageTag); + } +} diff --git a/tests/unit/src/com/android/settings/regionalpreferences/RegionalPreferencesControllerTest.java b/tests/unit/src/com/android/settings/regionalpreferences/RegionalPreferencesControllerTest.java new file mode 100644 index 00000000000..966c46ccb98 --- /dev/null +++ b/tests/unit/src/com/android/settings/regionalpreferences/RegionalPreferencesControllerTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.regionalpreferences; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; + +import static org.junit.Assert.assertEquals; + +import android.app.UiAutomation; +import android.content.Context; +import android.os.SystemProperties; + +import androidx.test.InstrumentationRegistry; +import androidx.test.core.app.ApplicationProvider; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class RegionalPreferencesControllerTest { + private boolean mCacheProperty = false; + private Context mApplicationContext; + private RegionalPreferencesController mController; + + @Before + public void setUp() throws Exception { + mApplicationContext = ApplicationProvider.getApplicationContext(); + mCacheProperty = + SystemProperties.getBoolean(RegionalPreferencesController.FEATURE_PROPERTY, false); + mController = new RegionalPreferencesController(mApplicationContext, "key"); + } + + @After + public void tearDown() throws Exception { + setProp(mCacheProperty); + } + + @Test + public void getAvailabilityStatus_systemPropertyIstrue_available() throws Exception { + setProp(true); + + int result = mController.getAvailabilityStatus(); + + assertEquals(AVAILABLE, result); + } + + @Test + public void getAvailabilityStatus_systemPropertyIstrue_unavailable() throws Exception { + setProp(false); + + int result = mController.getAvailabilityStatus(); + + assertEquals(CONDITIONALLY_UNAVAILABLE, result); + } + + private static void setProp(boolean isEnabled) throws Exception { + UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); + uiAutomation.executeShellCommand( + "setprop " + RegionalPreferencesController.FEATURE_PROPERTY + " " + isEnabled); + + for (int i = 0; i < 3; i++) { + Thread.sleep(500); + if (SystemProperties.getBoolean( + RegionalPreferencesController.FEATURE_PROPERTY, false) == isEnabled) { + break; + } + } + } +} diff --git a/tests/unit/src/com/android/settings/regionalpreferences/RegionalPreferencesEntriesFragmentTest.java b/tests/unit/src/com/android/settings/regionalpreferences/RegionalPreferencesEntriesFragmentTest.java new file mode 100644 index 00000000000..f572816966f --- /dev/null +++ b/tests/unit/src/com/android/settings/regionalpreferences/RegionalPreferencesEntriesFragmentTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.regionalpreferences; + +import static org.junit.Assert.assertEquals; + +import android.app.settings.SettingsEnums; +import android.os.Looper; + +import androidx.test.annotation.UiThreadTest; + +import org.junit.Before; +import org.junit.Test; + +public class RegionalPreferencesEntriesFragmentTest { + private RegionalPreferencesEntriesFragment mFragment; + + @Before + @UiThreadTest + public void setUp() throws Exception { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + mFragment = new RegionalPreferencesEntriesFragment(); + } + + @Test + @UiThreadTest + public void getMetricsCategory_returnRegionalPreference() { + assertEquals(SettingsEnums.REGIONAL_PREFERENCE, mFragment.getMetricsCategory()); + } +} diff --git a/tests/unit/src/com/android/settings/regionalpreferences/TemperatureUnitControllerTest.java b/tests/unit/src/com/android/settings/regionalpreferences/TemperatureUnitControllerTest.java new file mode 100644 index 00000000000..4f473ad604d --- /dev/null +++ b/tests/unit/src/com/android/settings/regionalpreferences/TemperatureUnitControllerTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.regionalpreferences; + +import static org.junit.Assert.assertEquals; + +import android.content.Context; +import android.provider.Settings; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.testutils.ResourcesUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Locale; + +public class TemperatureUnitControllerTest { + private Context mApplicationContext; + private TemperatureUnitController mController; + private String mCacheProviderContent = ""; + private Locale mCacheLocale; + + @Before + public void setUp() throws Exception { + mApplicationContext = ApplicationProvider.getApplicationContext(); + mController = new TemperatureUnitController(mApplicationContext, "key"); + mCacheProviderContent = Settings.System.getString( + mApplicationContext.getContentResolver(), Settings.System.LOCALE_PREFERENCES); + mCacheLocale = Locale.getDefault(Locale.Category.FORMAT); + } + + @After + public void tearDown() throws Exception { + RegionalPreferenceUtils.setSettingsProviderContent( + mApplicationContext, mCacheProviderContent); + Locale.setDefault(mCacheLocale); + } + + @Test + public void getSummary_hasProviderValue_resultIsCelsius() { + RegionalPreferenceUtils.setSettingsProviderContent( + mApplicationContext, "und-u-mu-celsius"); + + CharSequence unit = mController.getSummary(); + + assertEquals(LocalePreferences.TemperatureUnit.CELSIUS, unit.toString()); + } + + @Test + public void getSummary_hasProviderValue_resultIsFahrenheit() { + RegionalPreferenceUtils.setSettingsProviderContent( + mApplicationContext, "und-u-mu-fahrenhe"); + + CharSequence unit = mController.getSummary(); + + assertEquals(LocalePreferences.TemperatureUnit.FAHRENHEIT, unit.toString()); + } + + @Test + public void getSummary_noProviderValueButHasDefaultLocaleWithSubtag_resultIsFahrenheit() { + RegionalPreferenceUtils.setSettingsProviderContent(mApplicationContext, ""); + Locale.setDefault(Locale.forLanguageTag("en-US-u-mu-fahrenhe")); + + CharSequence unit = mController.getSummary(); + + assertEquals(LocalePreferences.TemperatureUnit.FAHRENHEIT, unit.toString()); + } + + @Test + public void getSummary_noProviderValueAndDefaultLocaleWithoutSubtag_resultIsEmpty() { + RegionalPreferenceUtils.setSettingsProviderContent(mApplicationContext, ""); + Locale.setDefault(Locale.forLanguageTag("en-US")); + + CharSequence unit = mController.getSummary(); + + assertEquals(ResourcesUtils.getResourcesString( + mApplicationContext, "default_string_of_regional_preference"), unit.toString()); + } +} diff --git a/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java index 07c1571f987..9e22e8d0b35 100644 --- a/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java +++ b/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java @@ -49,6 +49,7 @@ import com.android.settings.Settings; import com.android.settings.biometrics.face.FaceEnrollIntroductionInternal; import com.android.settings.biometrics.fingerprint.FingerprintSettings; import com.android.settings.testutils.ResourcesUtils; +import com.android.settingslib.utils.StringUtil; import org.junit.After; import org.junit.Before; @@ -568,13 +569,12 @@ public class BiometricsSafetySourceTest { private void assertSafetySourceDisabledDataSetWithPluralSummary(String expectedTitleResName, String expectedSummaryResName, int expectedSummaryQuantity) { final int stringResId = ResourcesUtils.getResourcesId( - ApplicationProvider.getApplicationContext(), "plurals", + ApplicationProvider.getApplicationContext(), "string", expectedSummaryResName); assertSafetySourceDisabledDataSet( ResourcesUtils.getResourcesString(mApplicationContext, expectedTitleResName), - mApplicationContext.getResources().getQuantityString(stringResId, - expectedSummaryQuantity /* quantity */, - expectedSummaryQuantity /* formatArgs */) + StringUtil.getIcuPluralsString(mApplicationContext, + expectedSummaryQuantity, stringResId) ); } @@ -582,13 +582,12 @@ public class BiometricsSafetySourceTest { String expectedSummaryResName, int expectedSummaryQuantity, String expectedSettingsClassName) { final int stringResId = ResourcesUtils.getResourcesId( - ApplicationProvider.getApplicationContext(), "plurals", + ApplicationProvider.getApplicationContext(), "string", expectedSummaryResName); assertSafetySourceEnabledDataSet( ResourcesUtils.getResourcesString(mApplicationContext, expectedTitleResName), - mApplicationContext.getResources().getQuantityString(stringResId, - expectedSummaryQuantity /* quantity */, - expectedSummaryQuantity /* formatArgs */), + StringUtil.getIcuPluralsString(mApplicationContext, + expectedSummaryQuantity, stringResId), expectedSettingsClassName ); }