1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.regionalpreferences;
18 
19 import android.app.settings.SettingsEnums;
20 import android.content.Context;
21 import android.icu.text.NumberingSystem;
22 import android.icu.util.ULocale;
23 import android.os.Bundle;
24 import android.os.LocaleList;
25 import android.text.TextUtils;
26 import android.util.Log;
27 
28 import androidx.preference.Preference;
29 import androidx.preference.PreferenceScreen;
30 
31 import com.android.internal.app.LocaleHelper;
32 import com.android.internal.app.LocalePicker;
33 import com.android.internal.app.LocaleStore;
34 import com.android.settings.core.BasePreferenceController;
35 import com.android.settings.core.SubSettingLauncher;
36 import com.android.settings.dashboard.DashboardFragment;
37 import com.android.settings.overlay.FeatureFactory;
38 import com.android.settings.widget.TickButtonPreference;
39 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
40 
41 import java.util.Locale;
42 
43 /** Uses to control the preference UI of numbering system page. */
44 public class NumberingSystemItemController extends BasePreferenceController {
45     private static final String TAG = NumberingSystemItemController.class.getSimpleName();
46     private static final String DISPLAY_KEYWORD_NUMBERING_SYSTEM = "numbers";
47 
48     static final String ARG_VALUE_NUMBERING_SYSTEM_SELECT = "arg_value_numbering_system_select";
49     static final String ARG_VALUE_LANGUAGE_SELECT = "arg_value_language_select";
50     static final String KEY_SELECTED_LANGUAGE = "key_selected_language";
51 
52     private final MetricsFeatureProvider mMetricsFeatureProvider;
53 
54     private String mOption = "";
55     private String mSelectedLanguage = "";
56     private DashboardFragment mParentFragment;
57     private PreferenceScreen mPreferenceScreen;
58 
NumberingSystemItemController(Context context, Bundle argument)59     public NumberingSystemItemController(Context context, Bundle argument) {
60         super(context, "no_key");
61         // Initialize the supported languages to LocaleInfos
62         LocaleStore.fillCache(context);
63         mOption = argument.getString(
64                 RegionalPreferencesEntriesFragment.ARG_KEY_REGIONAL_PREFERENCE, "");
65         mSelectedLanguage = argument.getString(
66                 NumberingSystemItemController.KEY_SELECTED_LANGUAGE, "");
67         mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
68     }
69 
70     /**
71      * Displays preference in this controller.
72      */
73     @Override
displayPreference(PreferenceScreen screen)74     public void displayPreference(PreferenceScreen screen) {
75         super.displayPreference(screen);
76         mPreferenceScreen = screen;
77         if (mOption.equals(ARG_VALUE_LANGUAGE_SELECT)) {
78             initLanguageOptionsUi(screen);
79         } else if (mOption.equals(ARG_VALUE_NUMBERING_SYSTEM_SELECT)) {
80             initNumberingSystemOptionsUi(screen, Locale.forLanguageTag(mSelectedLanguage));
81         }
82     }
83 
84     /**
85      * Sets the parent fragment and attaches this controller to the settings lifecycle.
86      *
87      * @param fragment the fragment to use as the parent
88      */
setParentFragment(DashboardFragment fragment)89     public void setParentFragment(DashboardFragment fragment) {
90         mParentFragment = fragment;
91     }
92 
93     /**
94      * @return {@link AvailabilityStatus} for the Setting. This status is used to determine if the
95      * Setting should be shown or disabled in Settings. Further, it can be used to produce
96      * appropriate error / warning Slice in the case of unavailability.
97      * </p>
98      * The status is used for the convenience methods: {@link #isAvailable()}, {@link
99      * #isSupported()}
100      * </p>
101      * The inherited class doesn't need to check work profile if android:forWork="true" is set in
102      * preference xml.
103      */
104     @Override
getAvailabilityStatus()105     public int getAvailabilityStatus() {
106         return AVAILABLE;
107     }
108 
109     @Override
handlePreferenceTreeClick(Preference preference)110     public boolean handlePreferenceTreeClick(Preference preference) {
111         if (mOption.equals(ARG_VALUE_LANGUAGE_SELECT)) {
112             handleLanguageSelect(preference);
113         } else if (mOption.equals(ARG_VALUE_NUMBERING_SYSTEM_SELECT)) {
114             handleNumberSystemSelect(preference);
115         }
116         return true;
117     }
118 
initLanguageOptionsUi(PreferenceScreen screen)119     private void initLanguageOptionsUi(PreferenceScreen screen) {
120         // Get current system language list to show on screen.
121         LocaleList localeList = LocaleList.getDefault();
122         for (int i = 0; i < localeList.size(); i++) {
123             Locale locale = localeList.get(i);
124             LocaleStore.LocaleInfo localeInfo = LocaleStore.getLocaleInfo(locale);
125             if (!localeInfo.hasNumberingSystems()) {
126                 continue;
127             }
128             Preference pref = new Preference(mContext);
129             pref.setTitle(LocaleHelper.getDisplayName(locale.stripExtensions(), locale, true));
130             pref.setKey(locale.toLanguageTag());
131             pref.setSummary(getNumberingSystem(locale));
132             screen.addPreference(pref);
133         }
134     }
135 
initNumberingSystemOptionsUi(PreferenceScreen screen, Locale targetLocale)136     private void initNumberingSystemOptionsUi(PreferenceScreen screen, Locale targetLocale) {
137         String[] locales = LocalePicker.getSupportedLocales(mContext);
138         for (String localeTag : locales) {
139             Locale supportedLocale = Locale.forLanguageTag(localeTag);
140             if (isSameBaseLocale(targetLocale, supportedLocale)) {
141                 TickButtonPreference pref = new TickButtonPreference(mContext);
142                 String numberingName = getNumberingSystem(supportedLocale);
143                 pref.setTitle(numberingName);
144                 String key = supportedLocale.getUnicodeLocaleType(
145                         ExtensionTypes.NUMBERING_SYSTEM);
146                 pref.setKey(key == null ? RegionalPreferencesDataUtils.DEFAULT_VALUE : key);
147                 pref.setSelected(isSameNumberingSystem(targetLocale, supportedLocale));
148                 screen.addPreference(pref);
149             }
150         }
151     }
152 
handleLanguageSelect(Preference preference)153     private void handleLanguageSelect(Preference preference) {
154         String selectedLanguage = preference.getKey();
155         mMetricsFeatureProvider.action(mContext,
156                 SettingsEnums.ACTION_CHOOSE_LANGUAGE_FOR_NUMBERS_PREFERENCES);
157         final Bundle extra = new Bundle();
158         extra.putString(RegionalPreferencesEntriesFragment.ARG_KEY_REGIONAL_PREFERENCE,
159                 ARG_VALUE_NUMBERING_SYSTEM_SELECT);
160         extra.putString(KEY_SELECTED_LANGUAGE, selectedLanguage);
161         new SubSettingLauncher(preference.getContext())
162                 .setDestination(NumberingPreferencesFragment.class.getName())
163                 .setSourceMetricsCategory(
164                         SettingsEnums.NUMBERING_SYSTEM_LANGUAGE_SELECTION_PREFERENCE)
165                 .setArguments(extra)
166                 .launch();
167     }
168 
handleNumberSystemSelect(Preference preference)169     private void handleNumberSystemSelect(Preference preference) {
170         for (int i = 0; i < mPreferenceScreen.getPreferenceCount(); i++) {
171             TickButtonPreference pref = (TickButtonPreference) mPreferenceScreen.getPreference(i);
172             Log.i(TAG, "[onPreferenceClick] key is " + pref.getKey());
173             if (pref.getKey().equals(preference.getKey())) {
174                 String numberingSystem = pref.getKey();
175                 pref.setSelected(true);
176                 Locale updatedLocale =
177                         saveNumberingSystemToLocale(Locale.forLanguageTag(mSelectedLanguage),
178                                 numberingSystem);
179                 mMetricsFeatureProvider.action(mContext,
180                         SettingsEnums.ACTION_SET_NUMBERS_PREFERENCES);
181                 // After updated locale to framework, this fragment will recreate,
182                 // so it needs to update the argument of selected language.
183                 Bundle bundle = new Bundle();
184                 bundle.putString(RegionalPreferencesEntriesFragment.ARG_KEY_REGIONAL_PREFERENCE,
185                         ARG_VALUE_NUMBERING_SYSTEM_SELECT);
186                 bundle.putString(KEY_SELECTED_LANGUAGE,
187                         updatedLocale != null ? updatedLocale.toLanguageTag() : "");
188                 mParentFragment.setArguments(bundle);
189                 continue;
190             }
191             pref.setSelected(false);
192         }
193     }
194 
saveNumberingSystemToLocale(Locale targetLocale, String value)195     private Locale saveNumberingSystemToLocale(Locale targetLocale, String value) {
196         LocaleList localeList = LocalePicker.getLocales();
197         Locale[] locales = new Locale[localeList.size()];
198         Locale updatedLocale = null;
199         for (int i = 0; i < localeList.size(); i++) {
200             Locale locale = localeList.get(i);
201             if (targetLocale.equals(locale)) {
202                 if (RegionalPreferencesDataUtils.DEFAULT_VALUE.equals(value)) {
203                     value = null;
204                 }
205                 updatedLocale = new Locale.Builder()
206                         .setLocale(locale)
207                         .setUnicodeLocaleKeyword(ExtensionTypes.NUMBERING_SYSTEM, value)
208                         .build();
209                 locales[i] = updatedLocale;
210                 continue;
211             }
212             locales[i] = localeList.get(i);
213         }
214         LocalePicker.updateLocales(new LocaleList(locales));
215         return updatedLocale;
216     }
217 
getNumberingSystem(Locale locale)218     private static String getNumberingSystem(Locale locale) {
219         ULocale uLocale = new ULocale.Builder()
220                 .setUnicodeLocaleKeyword(ExtensionTypes.NUMBERING_SYSTEM,
221                         NumberingSystem.getInstance(locale).getName())
222                 .build();
223         return uLocale.getDisplayKeywordValue(DISPLAY_KEYWORD_NUMBERING_SYSTEM,
224                 ULocale.forLocale(locale));
225     }
226 
isSameNumberingSystem(Locale locale1, Locale locale2)227     private static boolean isSameNumberingSystem(Locale locale1, Locale locale2) {
228         String name1 = NumberingSystem.getInstance(locale1).getName();
229         String name2 = NumberingSystem.getInstance(locale2).getName();
230         return TextUtils.equals(name1, name2);
231     }
232 
isSameBaseLocale(Locale locale1, Locale locale2)233     private static boolean isSameBaseLocale(Locale locale1, Locale locale2) {
234         return locale1.stripExtensions().equals(locale2.stripExtensions());
235     }
236 }
237