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