1 /*
2  * Copyright (C) 2010 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.internal.app;
18 
19 import com.android.internal.R;
20 
21 import android.app.ActivityManager;
22 import android.app.IActivityManager;
23 import android.app.ListFragment;
24 import android.app.backup.BackupManager;
25 import android.content.Context;
26 import android.content.res.Configuration;
27 import android.content.res.Resources;
28 import android.os.Bundle;
29 import android.os.LocaleList;
30 import android.os.RemoteException;
31 import android.provider.Settings;
32 import android.util.Log;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.widget.ArrayAdapter;
37 import android.widget.ListView;
38 import android.widget.TextView;
39 
40 import java.text.Collator;
41 import java.util.Collections;
42 import java.util.List;
43 import java.util.Locale;
44 import java.util.ArrayList;
45 
46 public class LocalePicker extends ListFragment {
47     private static final String TAG = "LocalePicker";
48     private static final boolean DEBUG = false;
49     private static final String[] pseudoLocales = { "en-XA", "ar-XB" };
50 
51     public static interface LocaleSelectionListener {
52         // You can add any argument if you really need it...
onLocaleSelected(Locale locale)53         public void onLocaleSelected(Locale locale);
54     }
55 
56     LocaleSelectionListener mListener;  // default to null
57 
58     public static class LocaleInfo implements Comparable<LocaleInfo> {
59         static final Collator sCollator = Collator.getInstance();
60 
61         String label;
62         final Locale locale;
63 
LocaleInfo(String label, Locale locale)64         public LocaleInfo(String label, Locale locale) {
65             this.label = label;
66             this.locale = locale;
67         }
68 
getLabel()69         public String getLabel() {
70             return label;
71         }
72 
getLocale()73         public Locale getLocale() {
74             return locale;
75         }
76 
77         @Override
toString()78         public String toString() {
79             return this.label;
80         }
81 
82         @Override
compareTo(LocaleInfo another)83         public int compareTo(LocaleInfo another) {
84             return sCollator.compare(this.label, another.label);
85         }
86     }
87 
getSystemAssetLocales()88     public static String[] getSystemAssetLocales() {
89         return Resources.getSystem().getAssets().getLocales();
90     }
91 
getSupportedLocales(Context context)92     public static String[] getSupportedLocales(Context context) {
93         return context.getResources().getStringArray(R.array.supported_locales);
94     }
95 
getAllAssetLocales(Context context, boolean isInDeveloperMode)96     public static List<LocaleInfo> getAllAssetLocales(Context context, boolean isInDeveloperMode) {
97         final Resources resources = context.getResources();
98 
99         final String[] locales = getSystemAssetLocales();
100         List<String> localeList = new ArrayList<String>(locales.length);
101         Collections.addAll(localeList, locales);
102 
103         Collections.sort(localeList);
104         final String[] specialLocaleCodes = resources.getStringArray(R.array.special_locale_codes);
105         final String[] specialLocaleNames = resources.getStringArray(R.array.special_locale_names);
106 
107         final ArrayList<LocaleInfo> localeInfos = new ArrayList<LocaleInfo>(localeList.size());
108         for (String locale : localeList) {
109             final Locale l = Locale.forLanguageTag(locale.replace('_', '-'));
110             if (l == null || "und".equals(l.getLanguage())
111                     || l.getLanguage().isEmpty() || l.getCountry().isEmpty()) {
112                 continue;
113             }
114             // Don't show the pseudolocales unless we're in developer mode. http://b/17190407.
115             if (!isInDeveloperMode && LocaleList.isPseudoLocale(l)) {
116                 continue;
117             }
118 
119             if (localeInfos.isEmpty()) {
120                 if (DEBUG) {
121                     Log.v(TAG, "adding initial "+ toTitleCase(l.getDisplayLanguage(l)));
122                 }
123                 localeInfos.add(new LocaleInfo(toTitleCase(l.getDisplayLanguage(l)), l));
124             } else {
125                 // check previous entry:
126                 //  same lang and a country -> upgrade to full name and
127                 //    insert ours with full name
128                 //  diff lang -> insert ours with lang-only name
129                 final LocaleInfo previous = localeInfos.get(localeInfos.size() - 1);
130                 if (previous.locale.getLanguage().equals(l.getLanguage()) &&
131                         !previous.locale.getLanguage().equals("zz")) {
132                     if (DEBUG) {
133                         Log.v(TAG, "backing up and fixing " + previous.label + " to " +
134                                 getDisplayName(previous.locale, specialLocaleCodes, specialLocaleNames));
135                     }
136                     previous.label = toTitleCase(getDisplayName(
137                             previous.locale, specialLocaleCodes, specialLocaleNames));
138                     if (DEBUG) {
139                         Log.v(TAG, "  and adding "+ toTitleCase(
140                                 getDisplayName(l, specialLocaleCodes, specialLocaleNames)));
141                     }
142                     localeInfos.add(new LocaleInfo(toTitleCase(
143                             getDisplayName(l, specialLocaleCodes, specialLocaleNames)), l));
144                 } else {
145                     String displayName = toTitleCase(l.getDisplayLanguage(l));
146                     if (DEBUG) {
147                         Log.v(TAG, "adding "+displayName);
148                     }
149                     localeInfos.add(new LocaleInfo(displayName, l));
150                 }
151             }
152         }
153 
154         Collections.sort(localeInfos);
155         return localeInfos;
156     }
157 
158     /**
159      * Constructs an Adapter object containing Locale information. Content is sorted by
160      * {@link LocaleInfo#label}.
161      */
constructAdapter(Context context)162     public static ArrayAdapter<LocaleInfo> constructAdapter(Context context) {
163         return constructAdapter(context, R.layout.locale_picker_item, R.id.locale);
164     }
165 
constructAdapter(Context context, final int layoutId, final int fieldId)166     public static ArrayAdapter<LocaleInfo> constructAdapter(Context context,
167             final int layoutId, final int fieldId) {
168         boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(),
169                 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
170         final List<LocaleInfo> localeInfos = getAllAssetLocales(context, isInDeveloperMode);
171 
172         final LayoutInflater inflater =
173                 (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
174         return new ArrayAdapter<LocaleInfo>(context, layoutId, fieldId, localeInfos) {
175             @Override
176             public View getView(int position, View convertView, ViewGroup parent) {
177                 View view;
178                 TextView text;
179                 if (convertView == null) {
180                     view = inflater.inflate(layoutId, parent, false);
181                     text = (TextView) view.findViewById(fieldId);
182                     view.setTag(text);
183                 } else {
184                     view = convertView;
185                     text = (TextView) view.getTag();
186                 }
187                 LocaleInfo item = getItem(position);
188                 text.setText(item.toString());
189                 text.setTextLocale(item.getLocale());
190 
191                 return view;
192             }
193         };
194     }
195 
196     private static String toTitleCase(String s) {
197         if (s.length() == 0) {
198             return s;
199         }
200 
201         return Character.toUpperCase(s.charAt(0)) + s.substring(1);
202     }
203 
204     private static String getDisplayName(
205             Locale l, String[] specialLocaleCodes, String[] specialLocaleNames) {
206         String code = l.toString();
207 
208         for (int i = 0; i < specialLocaleCodes.length; i++) {
209             if (specialLocaleCodes[i].equals(code)) {
210                 return specialLocaleNames[i];
211             }
212         }
213 
214         return l.getDisplayName(l);
215     }
216 
217     @Override
218     public void onActivityCreated(final Bundle savedInstanceState) {
219         super.onActivityCreated(savedInstanceState);
220         final ArrayAdapter<LocaleInfo> adapter = constructAdapter(getActivity());
221         setListAdapter(adapter);
222     }
223 
224     public void setLocaleSelectionListener(LocaleSelectionListener listener) {
225         mListener = listener;
226     }
227 
228     @Override
229     public void onResume() {
230         super.onResume();
231         getListView().requestFocus();
232     }
233 
234     /**
235      * Each listener needs to call {@link #updateLocale(Locale)} to actually change the locale.
236      *
237      * We don't call {@link #updateLocale(Locale)} automatically, as it halt the system for
238      * a moment and some callers won't want it.
239      */
240     @Override
241     public void onListItemClick(ListView l, View v, int position, long id) {
242         if (mListener != null) {
243             final Locale locale = ((LocaleInfo)getListAdapter().getItem(position)).locale;
244             mListener.onLocaleSelected(locale);
245         }
246     }
247 
248     /**
249      * Requests the system to update the system locale. Note that the system looks halted
250      * for a while during the Locale migration, so the caller need to take care of it.
251      *
252      * @see #updateLocales(LocaleList)
253      */
254     public static void updateLocale(Locale locale) {
255         updateLocales(new LocaleList(locale));
256     }
257 
258     /**
259      * Requests the system to update the list of system locales.
260      * Note that the system looks halted for a while during the Locale migration,
261      * so the caller need to take care of it.
262      */
263     public static void updateLocales(LocaleList locales) {
264         try {
265             final IActivityManager am = ActivityManager.getService();
266             final Configuration config = am.getConfiguration();
267 
268             config.setLocales(locales);
269             config.userSetLocale = true;
270 
271             am.updatePersistentConfiguration(config);
272             // Trigger the dirty bit for the Settings Provider.
273             BackupManager.dataChanged("com.android.providers.settings");
274         } catch (RemoteException e) {
275             // Intentionally left blank
276         }
277     }
278 
279     /**
280      * Get the locale list.
281      *
282      * @return The locale list.
283      */
284     public static LocaleList getLocales() {
285         try {
286             return ActivityManager.getService()
287                     .getConfiguration().getLocales();
288         } catch (RemoteException e) {
289             // If something went wrong
290             return LocaleList.getDefault();
291         }
292     }
293 }
294