From 504a5dda439f7a0270c61dfa3d58414943bbbf1e Mon Sep 17 00:00:00 2001 From: tom hsu Date: Mon, 19 Dec 2022 20:05:44 +0800 Subject: [PATCH] [Regional Preference] Add each option entry - Add a entry in langauge and input page - Add new page for the options of Temperature unit, calendar type, first day of week, and numbering system. Bug: b/246929960 Test: atest pass Test: Manual test pass Change-Id: I69377fe9cd6dcd7a27c933a8dc98483df3da7665 --- res/values/strings.xml | 18 + res/xml/language_and_input.xml | 9 + res/xml/regional_preference_main_page.xml | 49 ++ .../CalendarTypeController.java | 66 ++ .../FirstDayOfWeekController.java | 64 ++ .../LocalePreferences.java | 604 ++++++++++++++++++ .../NumberingSystemController.java | 76 +++ .../RegionalPreferencesController.java | 48 ++ .../RegionalPreferencesEntriesFragment.java | 57 ++ .../TemperatureUnitController.java | 64 ++ .../CalendarTypeControllerTest.java | 96 +++ .../FirstDayOfWeekControllerTest.java | 94 +++ .../RegionalPreferenceUtils.java | 29 + .../RegionalPreferencesControllerTest.java | 84 +++ ...egionalPreferencesEntriesFragmentTest.java | 46 ++ .../TemperatureUnitControllerTest.java | 96 +++ 16 files changed, 1500 insertions(+) create mode 100644 res/xml/regional_preference_main_page.xml create mode 100644 src/com/android/settings/regionalpreferences/CalendarTypeController.java create mode 100644 src/com/android/settings/regionalpreferences/FirstDayOfWeekController.java create mode 100644 src/com/android/settings/regionalpreferences/LocalePreferences.java create mode 100644 src/com/android/settings/regionalpreferences/NumberingSystemController.java create mode 100644 src/com/android/settings/regionalpreferences/RegionalPreferencesController.java create mode 100644 src/com/android/settings/regionalpreferences/RegionalPreferencesEntriesFragment.java create mode 100644 src/com/android/settings/regionalpreferences/TemperatureUnitController.java create mode 100644 tests/unit/src/com/android/settings/regionalpreferences/CalendarTypeControllerTest.java create mode 100644 tests/unit/src/com/android/settings/regionalpreferences/FirstDayOfWeekControllerTest.java create mode 100644 tests/unit/src/com/android/settings/regionalpreferences/RegionalPreferenceUtils.java create mode 100644 tests/unit/src/com/android/settings/regionalpreferences/RegionalPreferencesControllerTest.java create mode 100644 tests/unit/src/com/android/settings/regionalpreferences/RegionalPreferencesEntriesFragmentTest.java create mode 100644 tests/unit/src/com/android/settings/regionalpreferences/TemperatureUnitControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index f9e1517249c..f308cd17fb7 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?} 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/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/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()); + } +}