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.util.Log;
31import android.view.View;
32import android.widget.ArrayAdapter;
33import android.widget.ListView;
34
35import java.text.Collator;
36import java.util.Arrays;
37import java.util.Locale;
38
39public class LocalePicker extends ListFragment {
40    private static final String TAG = "LocalePicker";
41    private static final boolean DEBUG = false;
42
43    public static interface LocaleSelectionListener {
44        // You can add any argument if you really need it...
45        public void onLocaleSelected(Locale locale);
46    }
47
48    LocaleSelectionListener mListener;  // default to null
49
50    public static class LocaleInfo implements Comparable<LocaleInfo> {
51        static final Collator sCollator = Collator.getInstance();
52
53        String label;
54        Locale locale;
55
56        public LocaleInfo(String label, Locale locale) {
57            this.label = label;
58            this.locale = locale;
59        }
60
61        public String getLabel() {
62            return label;
63        }
64
65        public Locale getLocale() {
66            return locale;
67        }
68
69        @Override
70        public String toString() {
71            return this.label;
72        }
73
74        @Override
75        public int compareTo(LocaleInfo another) {
76            return sCollator.compare(this.label, another.label);
77        }
78    }
79
80    /**
81     * Constructs an Adapter object containing Locale information. Content is sorted by
82     * {@link LocaleInfo#label}.
83     */
84    public static ArrayAdapter<LocaleInfo> constructAdapter(Context context) {
85        return constructAdapter(context, R.layout.locale_picker_item, R.id.locale);
86    }
87
88    public static ArrayAdapter<LocaleInfo> constructAdapter(Context context,
89            int layoutId, int fieldId) {
90        final Resources resources = context.getResources();
91        final String[] locales = Resources.getSystem().getAssets().getLocales();
92        final String[] specialLocaleCodes = resources.getStringArray(R.array.special_locale_codes);
93        final String[] specialLocaleNames = resources.getStringArray(R.array.special_locale_names);
94        Arrays.sort(locales);
95        final int origSize = locales.length;
96        final LocaleInfo[] preprocess = new LocaleInfo[origSize];
97        int finalSize = 0;
98        for (int i = 0 ; i < origSize; i++ ) {
99            final String s = locales[i];
100            final int len = s.length();
101            if (len == 5) {
102                String language = s.substring(0, 2);
103                String country = s.substring(3, 5);
104                final Locale l = new Locale(language, country);
105
106                if (finalSize == 0) {
107                    if (DEBUG) {
108                        Log.v(TAG, "adding initial "+ toTitleCase(l.getDisplayLanguage(l)));
109                    }
110                    preprocess[finalSize++] =
111                            new LocaleInfo(toTitleCase(l.getDisplayLanguage(l)), l);
112                } else {
113                    // check previous entry:
114                    //  same lang and a country -> upgrade to full name and
115                    //    insert ours with full name
116                    //  diff lang -> insert ours with lang-only name
117                    if (preprocess[finalSize-1].locale.getLanguage().equals(
118                            language)) {
119                        if (DEBUG) {
120                            Log.v(TAG, "backing up and fixing "+
121                                    preprocess[finalSize-1].label+" to "+
122                                    getDisplayName(preprocess[finalSize-1].locale,
123                                            specialLocaleCodes, specialLocaleNames));
124                        }
125                        preprocess[finalSize-1].label = toTitleCase(
126                                getDisplayName(preprocess[finalSize-1].locale,
127                                        specialLocaleCodes, specialLocaleNames));
128                        if (DEBUG) {
129                            Log.v(TAG, "  and adding "+ toTitleCase(
130                                    getDisplayName(l, specialLocaleCodes, specialLocaleNames)));
131                        }
132                        preprocess[finalSize++] =
133                                new LocaleInfo(toTitleCase(
134                                        getDisplayName(
135                                                l, specialLocaleCodes, specialLocaleNames)), l);
136                    } else {
137                        String displayName;
138                        if (s.equals("zz_ZZ")) {
139                            displayName = "Pseudo...";
140                        } else {
141                            displayName = toTitleCase(l.getDisplayLanguage(l));
142                        }
143                        if (DEBUG) {
144                            Log.v(TAG, "adding "+displayName);
145                        }
146                        preprocess[finalSize++] = new LocaleInfo(displayName, l);
147                    }
148                }
149            }
150        }
151
152        final LocaleInfo[] localeInfos = new LocaleInfo[finalSize];
153        for (int i = 0; i < finalSize; i++) {
154            localeInfos[i] = preprocess[i];
155        }
156        Arrays.sort(localeInfos);
157        return new ArrayAdapter<LocaleInfo>(context, layoutId, fieldId, localeInfos);
158    }
159
160    private static String toTitleCase(String s) {
161        if (s.length() == 0) {
162            return s;
163        }
164
165        return Character.toUpperCase(s.charAt(0)) + s.substring(1);
166    }
167
168    private static String getDisplayName(
169            Locale l, String[] specialLocaleCodes, String[] specialLocaleNames) {
170        String code = l.toString();
171
172        for (int i = 0; i < specialLocaleCodes.length; i++) {
173            if (specialLocaleCodes[i].equals(code)) {
174                return specialLocaleNames[i];
175            }
176        }
177
178        return l.getDisplayName(l);
179    }
180
181    @Override
182    public void onActivityCreated(final Bundle savedInstanceState) {
183        super.onActivityCreated(savedInstanceState);
184        final ArrayAdapter<LocaleInfo> adapter = constructAdapter(getActivity());
185        setListAdapter(adapter);
186    }
187
188    public void setLocaleSelectionListener(LocaleSelectionListener listener) {
189        mListener = listener;
190    }
191
192    @Override
193    public void onResume() {
194        super.onResume();
195        getListView().requestFocus();
196    }
197
198    /**
199     * Each listener needs to call {@link #updateLocale(Locale)} to actually change the locale.
200     *
201     * We don't call {@link #updateLocale(Locale)} automatically, as it halt the system for
202     * a moment and some callers won't want it.
203     */
204    @Override
205    public void onListItemClick(ListView l, View v, int position, long id) {
206        if (mListener != null) {
207            final Locale locale = ((LocaleInfo)getListAdapter().getItem(position)).locale;
208            mListener.onLocaleSelected(locale);
209        }
210    }
211
212    /**
213     * Requests the system to update the system locale. Note that the system looks halted
214     * for a while during the Locale migration, so the caller need to take care of it.
215     */
216    public static void updateLocale(Locale locale) {
217        try {
218            IActivityManager am = ActivityManagerNative.getDefault();
219            Configuration config = am.getConfiguration();
220
221            config.locale = locale;
222
223            // indicate this isn't some passing default - the user wants this remembered
224            config.userSetLocale = true;
225
226            am.updateConfiguration(config);
227            // Trigger the dirty bit for the Settings Provider.
228            BackupManager.dataChanged("com.android.providers.settings");
229        } catch (RemoteException e) {
230            // Intentionally left blank
231        }
232    }
233}