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