1/*
2 * Copyright (C) 2009 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.customlocale2;
18
19
20import java.util.ArrayList;
21import java.util.Collections;
22import java.util.Comparator;
23import java.util.Locale;
24
25import android.app.ActivityManagerNative;
26import android.app.AlertDialog;
27import android.app.Dialog;
28import android.app.IActivityManager;
29import android.app.ListActivity;
30import android.content.DialogInterface;
31import android.content.Intent;
32import android.content.SharedPreferences;
33import android.content.res.Configuration;
34import android.os.Bundle;
35import android.os.RemoteException;
36import android.util.Log;
37import android.view.ContextMenu;
38import android.view.View;
39import android.view.ContextMenu.ContextMenuInfo;
40import android.widget.ArrayAdapter;
41import android.widget.Button;
42import android.widget.ListAdapter;
43import android.widget.ListView;
44import android.widget.TextView;
45import android.widget.Toast;
46import android.widget.AdapterView.AdapterContextMenuInfo;
47
48/**
49 * Displays the list of system locales as well as maintain a custom list of user
50 * locales. The user can select a locale and apply it or it can create or remove
51 * a custom locale.
52 */
53public class CustomLocaleActivity extends ListActivity {
54
55    private static final String TAG = "CustomLocale";
56    private static final boolean DEBUG = true;
57
58    private static final int DLG_REMOVE_ID = 0;
59
60    private static final String CUSTOM_LOCALES_SEP = " ";
61    private static final String CUSTOM_LOCALES = "custom_locales";
62
63    /** Request code returned when the NewLocaleDialog activity finishes. */
64    private static final int UPDATE_LIST = 42;
65    /** Menu item id for applying a locale */
66    private static final int MENU_APPLY = 43;
67    /** Menu item id for removing a custom locale */
68    private static final int MENU_REMOVE = 44;
69
70    /** List view displaying system and custom locales. */
71    private ListView mListView;
72    /** Textview used to display current locale */
73    private TextView mCurrentLocaleTextView;
74    /** Private shared preferences of this activity. */
75    private SharedPreferences mPrefs;
76
77    private Button mRemoveLocaleButton;
78    private Button mSelectLocaleButton;
79
80    @Override
81    protected void onCreate(Bundle savedInstanceState) {
82        super.onCreate(savedInstanceState);
83        setContentView(R.layout.main);
84
85        mPrefs = getPreferences(MODE_PRIVATE);
86
87        Button addLocaleButton = (Button) findViewById(R.id.add_new_locale_button);
88        mRemoveLocaleButton = (Button) findViewById(R.id.remove_locale_button);
89        mSelectLocaleButton = (Button) findViewById(R.id.select_locale_button);
90
91        addLocaleButton.setOnClickListener(new View.OnClickListener() {
92            public void onClick(View v) {
93                onAddNewLocale();
94            }
95        });
96
97        mRemoveLocaleButton.setOnClickListener(new View.OnClickListener() {
98            @Override
99            public void onClick(View v) {
100                showDialog(DLG_REMOVE_ID);
101            }
102        });
103
104        mSelectLocaleButton.setOnClickListener(new View.OnClickListener() {
105            @Override
106            public void onClick(View v) {
107                onSelectLocale();
108            }
109        });
110
111        mListView = (ListView) findViewById(android.R.id.list);
112        mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
113        mListView.setItemsCanFocus(false);
114        mListView.setFocusable(true);
115        mListView.setFocusableInTouchMode(true);
116        mListView.requestFocus();
117        setupLocaleList();
118
119        mCurrentLocaleTextView = (TextView) findViewById(R.id.current_locale);
120        displayCurrentLocale();
121    }
122
123    @Override
124    protected void onResume() {
125        super.onResume();
126        updateLocaleButtons();
127    }
128
129    @Override
130    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
131        super.onActivityResult(requestCode, resultCode, data);
132
133        if (requestCode == UPDATE_LIST && resultCode == RESULT_OK && data != null) {
134            String locale = data.getExtras().getString(NewLocaleDialog.INTENT_EXTRA_LOCALE);
135            if (locale != null && locale.length() > 0) {
136                // Get current custom locale list
137                String customLocales = mPrefs.getString(CUSTOM_LOCALES, null);
138
139                // Update
140                if (customLocales == null) {
141                    customLocales = locale;
142                } else {
143                    customLocales += CUSTOM_LOCALES_SEP + locale;
144                }
145
146                // Save prefs
147                if (DEBUG) {
148                    Log.d(TAG, "add/customLocales: " + customLocales);
149                }
150                mPrefs.edit().putString(CUSTOM_LOCALES, customLocales).commit();
151
152                Toast.makeText(this,
153                               getString(R.string.added_custom_locale_1s, locale),
154                               Toast.LENGTH_SHORT)
155                     .show();
156
157                // Update list view
158                setupLocaleList();
159
160                // Find the item to select it in the list view
161                checkLocaleInList(locale);
162
163                if (data.getExtras().getBoolean(NewLocaleDialog.INTENT_EXTRA_SELECT)) {
164                    changeSystemLocale(locale);
165                }
166            }
167        }
168    }
169
170    @Override
171    protected void onListItemClick(ListView l, View v, int position, long id) {
172        super.onListItemClick(l, v, position, id);
173        updateLocaleButtons();
174    }
175
176    @Override
177    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
178        super.onCreateContextMenu(menu, v, menuInfo);
179
180        if (menuInfo instanceof AdapterContextMenuInfo) {
181            int position = ((AdapterContextMenuInfo) menuInfo).position;
182            Object o = mListView.getItemAtPosition(position);
183            if (o instanceof LocaleInfo) {
184                if (((LocaleInfo) o).isCustom()) {
185                    menu.setHeaderTitle("System Locale");
186                    menu.add(0, MENU_APPLY, 0, "Apply");
187                } else {
188                    menu.setHeaderTitle("Custom Locale");
189                    menu.add(0, MENU_APPLY, 0, "Apply");
190                    menu.add(0, MENU_REMOVE, 0, "Remove");
191                }
192            }
193        }
194    }
195
196    @Override
197    protected Dialog onCreateDialog(int id) {
198        if (id == DLG_REMOVE_ID) {
199            return createRemoveLocaleDialog();
200        }
201        return super.onCreateDialog(id);
202    }
203
204    //--- private parts ---
205
206    private void setupLocaleList() {
207        if (DEBUG) {
208            Log.d(TAG, "Update locate list");
209        }
210
211        ArrayList<LocaleInfo> data = new ArrayList<LocaleInfo>();
212
213        // Insert all system locales
214        String[] locales = getAssets().getLocales();
215        for (String locale : locales) {
216            if (locale != null && locale.length() > 0) {
217                Locale loc = new Locale(locale);
218                data.add(new LocaleInfo(locale, loc.getDisplayName()));
219            }
220        }
221        locales = null;
222
223        // Insert all custom locales
224        String customLocales = mPrefs.getString(CUSTOM_LOCALES, "");
225        if (DEBUG) {
226            Log.d(TAG, "customLocales: " + customLocales);
227        }
228        for (String locale : customLocales.split(CUSTOM_LOCALES_SEP)) {
229            if (locale != null && locale.length() > 0) {
230                Locale loc = new Locale(locale);
231                data.add(new LocaleInfo(
232                                locale,
233                                loc.getDisplayName(),
234                                true /*custom*/));
235            }
236        }
237
238        // Sort all locales by code
239        Collections.sort(data, new Comparator<LocaleInfo>() {
240            public int compare(LocaleInfo lhs, LocaleInfo rhs) {
241                return lhs.getLocale().compareTo(rhs.getLocale());
242            }
243        });
244
245        // Update the list view adapter
246        mListView.setAdapter(new ArrayAdapter<LocaleInfo>(
247                        this,
248                        android.R.layout.simple_list_item_single_choice,
249                        data));
250        updateLocaleButtons();
251    }
252
253    private void changeSystemLocale(String locale) {
254        if (ChangeLocale.changeSystemLocale(locale)) {
255            Toast.makeText(this,
256                            getString(R.string.select_locale_1s, locale),
257                            Toast.LENGTH_SHORT)
258                 .show();
259        }
260    }
261
262    private void displayCurrentLocale() {
263        try {
264            IActivityManager am = ActivityManagerNative.getDefault();
265            Configuration config = am.getConfiguration();
266
267            if (config.locale != null) {
268                String text = String.format("%1$s - %2$s",
269                        config.locale.toString(),
270                        config.locale.getDisplayName());
271                mCurrentLocaleTextView.setText(text);
272
273                checkLocaleInList(config.locale.toString());
274            }
275        } catch (RemoteException e) {
276            if (DEBUG) {
277                Log.e(TAG, "get current locale failed", e);
278            }
279        }
280    }
281
282    /** Find the locale by code to select it in the list view */
283    private void checkLocaleInList(String locale) {
284        ListAdapter a = mListView.getAdapter();
285        for (int i = 0; i < a.getCount(); i++) {
286            Object o = a.getItem(i);
287            if (o instanceof LocaleInfo) {
288                String code = ((LocaleInfo) o).getLocale();
289                if (code != null && code.equals(locale)) {
290                    mListView.setSelection(i);
291                    mListView.clearChoices();
292                    mListView.setItemChecked(i, true);
293                    updateLocaleButtons();
294                    break;
295                }
296            }
297        }
298    }
299
300    private LocaleInfo getCheckedLocale() {
301        int pos = mListView.getCheckedItemPosition();
302        ListAdapter a = mListView.getAdapter();
303        int n = a.getCount();
304        if (pos >= 0 && pos < n) {
305            Object o = a.getItem(pos);
306            if (o instanceof LocaleInfo) {
307                return (LocaleInfo) o;
308            }
309        }
310
311        return null;
312    }
313
314    /** Update the Select/Remove buttons based on the currently checked locale. */
315    private void updateLocaleButtons() {
316        LocaleInfo info = getCheckedLocale();
317        if (info != null) {
318            // Enable it
319            mSelectLocaleButton.setEnabled(true);
320            mSelectLocaleButton.setText(
321                getString(R.string.select_locale_1s_button, info.getLocale()));
322
323            // Enable the remove button only for custom locales and set the tag to the locale
324            mRemoveLocaleButton.setEnabled(info.isCustom());
325        } else {
326            // If nothing is selected, disable the buttons
327            mSelectLocaleButton.setEnabled(false);
328            mSelectLocaleButton.setText(R.string.select_locale_button);
329
330            mRemoveLocaleButton.setEnabled(false);
331        }
332    }
333
334    /** Invoked by the button "Add new..." */
335    private void onAddNewLocale() {
336        Intent i = new Intent(CustomLocaleActivity.this, NewLocaleDialog.class);
337        startActivityForResult(i, UPDATE_LIST);
338    }
339
340    /** Invoked by the button Select / mSelectLocaleButton */
341    private void onSelectLocale() {
342        LocaleInfo info = getCheckedLocale();
343        if (info != null) {
344            changeSystemLocale(info.getLocale());
345        }
346    }
347
348    /**
349     * Invoked by the button Remove / mRemoveLocaleButton.
350     * Creates a dialog to ask for confirmation before actually remove the custom locale.
351     */
352    private Dialog createRemoveLocaleDialog() {
353
354        LocaleInfo info = getCheckedLocale();
355        final String localeToRemove = info == null ? "<error>" : info.getLocale();
356
357        AlertDialog.Builder b = new AlertDialog.Builder(this);
358        b.setMessage(getString(R.string.confirm_remove_locale_1s, localeToRemove));
359        b.setCancelable(false);
360        b.setPositiveButton(R.string.confirm_remove_locale_yes,
361            new DialogInterface.OnClickListener() {
362                @Override
363                public void onClick(DialogInterface dialog, int which) {
364                    removeCustomLocale(localeToRemove);
365                    dismissDialog(DLG_REMOVE_ID);
366                }
367        });
368        b.setNegativeButton(R.string.confirm_remove_locale_no,
369            new DialogInterface.OnClickListener() {
370                @Override
371                public void onClick(DialogInterface dialog, int which) {
372                    dismissDialog(DLG_REMOVE_ID);
373                }
374        });
375
376        return b.create();
377    }
378
379    private void removeCustomLocale(String localeToRemove) {
380        // Get current custom locale list
381        String oldLocales = mPrefs.getString(CUSTOM_LOCALES, "");
382
383        if (DEBUG) {
384            Log.d(TAG, "Remove " + localeToRemove + " from custom locales: " + oldLocales);
385        }
386
387        // Update
388        StringBuilder sb = new StringBuilder();
389        for (String locale : oldLocales.split(CUSTOM_LOCALES_SEP)) {
390            if (locale != null && locale.length() > 0 && !locale.equals(localeToRemove)) {
391                if (sb.length() > 0) {
392                    sb.append(CUSTOM_LOCALES_SEP);
393                }
394                sb.append(locale);
395            }
396        }
397
398        String newLocales = sb.toString();
399        if (!newLocales.equals(oldLocales)) {
400            // Save prefs
401            boolean ok = mPrefs.edit().putString(CUSTOM_LOCALES, newLocales).commit();
402            if (DEBUG) {
403                Log.d(TAG, "Prefs commit:" + Boolean.toString(ok) + ". Saved: " + newLocales);
404            }
405
406            Toast.makeText(this,
407                            getString(R.string.removed_custom_locale_1s, localeToRemove),
408                            Toast.LENGTH_SHORT)
409                 .show();
410
411            // Update list view
412            setupLocaleList();
413        }
414    }
415
416    /**
417     * Immutable structure that holds the information displayed by a list view item.
418     */
419    private static class LocaleInfo {
420        private final String mLocale;
421        private final String mDisplayName;
422        private final boolean mIsCustom;
423
424        public LocaleInfo(String locale, String displayName, boolean isCustom) {
425            mLocale = locale;
426            mDisplayName = displayName;
427            mIsCustom = isCustom;
428        }
429
430        public LocaleInfo(String locale, String displayName) {
431            this(locale, displayName, false /*custom*/);
432        }
433
434        public String getLocale() {
435            return mLocale;
436        }
437
438        public boolean isCustom() {
439            return mIsCustom;
440        }
441
442        @Override
443        public String toString() {
444            StringBuilder sb = new StringBuilder();
445            sb.append(mLocale)
446              .append(" - ")
447              .append(mDisplayName);
448            if (mIsCustom) {
449                sb.append(" [Custom]");
450            }
451            return sb.toString();
452        }
453    }
454}
455