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