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