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 
17 package com.android.customlocale2;
18 
19 
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.Comparator;
23 import java.util.Locale;
24 
25 import android.app.ActivityManager;
26 import android.app.AlertDialog;
27 import android.app.Dialog;
28 import android.app.IActivityManager;
29 import android.app.ListActivity;
30 import android.content.DialogInterface;
31 import android.content.Intent;
32 import android.content.SharedPreferences;
33 import android.content.res.Configuration;
34 import android.os.Bundle;
35 import android.os.RemoteException;
36 import android.util.Log;
37 import android.view.ContextMenu;
38 import android.view.View;
39 import android.view.ContextMenu.ContextMenuInfo;
40 import android.widget.ArrayAdapter;
41 import android.widget.Button;
42 import android.widget.ListAdapter;
43 import android.widget.ListView;
44 import android.widget.TextView;
45 import android.widget.Toast;
46 import 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  */
53 public 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
onCreate(Bundle savedInstanceState)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
onResume()124     protected void onResume() {
125         super.onResume();
126         updateLocaleButtons();
127     }
128 
129     @Override
onActivityResult(int requestCode, int resultCode, Intent data)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
onListItemClick(ListView l, View v, int position, long id)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
onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)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
onCreateDialog(int id)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 
setupLocaleList()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 
changeSystemLocale(String locale)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 
displayCurrentLocale()262     private void displayCurrentLocale() {
263         try {
264             IActivityManager am = ActivityManager.getService();
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 */
checkLocaleInList(String locale)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 
getCheckedLocale()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. */
updateLocaleButtons()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..." */
onAddNewLocale()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 */
onSelectLocale()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      */
createRemoveLocaleDialog()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 
removeCustomLocale(String localeToRemove)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 
LocaleInfo(String locale, String displayName, boolean isCustom)424         public LocaleInfo(String locale, String displayName, boolean isCustom) {
425             mLocale = locale;
426             mDisplayName = displayName;
427             mIsCustom = isCustom;
428         }
429 
LocaleInfo(String locale, String displayName)430         public LocaleInfo(String locale, String displayName) {
431             this(locale, displayName, false /*custom*/);
432         }
433 
getLocale()434         public String getLocale() {
435             return mLocale;
436         }
437 
isCustom()438         public boolean isCustom() {
439             return mIsCustom;
440         }
441 
442         @Override
toString()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