Android Language
至最新的Android P, Google已经提供了世界绝大多数语言的支持。但是对许多发往海外的项目,可能还是会有一些国家的语言google默认没有支持。下面将介绍下对于某一特定语言如何判断Google是否支持这种语言、如何添加一种语言(如果google默认没有支持)。 还有,自Android N以来至Android P,首次开机时系统语言不再随SIM卡自适应,即SIM卡是法国的,首次开机时系统语言却不是法国。对此,可以参考下面提供的Feature去设计。
一、如何判断Google是否支持某种语言
整个过程可以分为下面几步:
1. 先要确定这种语言对应的country code和language code(如中文简体, country code是cn, language code是zh; 美国英语,country code是us, language code是en), 确定好country code和language code之后再进行2以下步骤;
language code可以从这个网址去查询:
country code可以从这个网址去查询:
2. 是否有这种语言对应语言的icu资源(前面确定好了language code和country code, 如中文简体是zh_CN)。可以看到external\icu\icu4c\source\data下coll、curr、lang、locales、region,zone这些子文件夹中都有zh.txt和zh_CN.txt. 这些目录下记载的对应的都是这种语言对应显示的region, zone, locale, language, dateformat...这些信息。
3. 是否有这种语言对应的字库。Google已经提供了约大多数语言的字库,很多语言是共用同一字库。所以基本不需要再添加字库。如需添加,得找对应字库公司购买。
4. 在frameworks/base/core/res/res/下是否有对应的values-xx-rYY的文件夹(xx是language code, YY是country code,下同). 即要有这种语言对应的resource, 不然语言显示的信息从何而来呢。
5. 每个app对应的res目录下面是否有values-xx文件夹或values-xx-rYY文件夹. app下没有这个语言对应的resource, 所带来的影响会是这个app不能显示这种语言。如果需要显示,得手动去添加对应的resource.
6. 如果是复杂语言,还需看看是否有对应的字体引擎。 复杂语言一般有这些特性: 阅读顺序从右到左、某几个字符相邻时会发生变形或替换等。对于复杂语言,要去external\harfbuzz_ng\src\hb-old\harfbuzz-shaper.cpp中添加对应的引擎。
如果以上几点都是YES, 那就说明这种语言已经支持。
二、Google不支持的语言,如何添加这种语言呢
上面介绍了如何判断是否支持某一种语言,对于某一种不支持的语言,是不是做到满足上面几点就能支持了呢? YES! 当然,答案是肯定的。
可以参考下面步骤:
1. 修改编译配置文件。 一般是在alps\device\公司名字\项目名字\full_项目名字.mk 中的 PRODUCT_LOCALES宏 下添加需要添加的语言代码。在此添加的目的是为系统打开这种语言。原因是虽然Android系统支持这么多的语言,但对于绝大多数的用户来说,并不需要这么多的语言,所以一般情况下系统只需打开对应的几种语言即可。如添加中文简体,那就将zh_CN添加到PRODUCT_LOCALES下面。 如果想要系统的默认语言(即首次开机时的语言)就是zh_CN, 那可以将zh_CN添加到PRODUCT_LOCALES下的第一个位置。
2. 添加ICU资源。 ICU资源是开源组织提供的, 可以网络搜索对应的ICU资源添加(上面判断是否支持一种语言中有介绍需要哪些ICU资源).
3. 如缺少这种语言对应的字库,则还需要添加字库。
4. 在frameworks/base/core/res/res/下添加这种语言对应的resource. 如添加values-zh-rCN.
5. 复杂语言需要添加对应的引擎。
三、 首次开机随Sim卡自适应语言的feature Design
如下所示:
/frameworks/opt/telephony/src/java/com/android/internal/telephony/MccTable.java
174 public static void updateMccMncConfiguration(Context context, String mccmnc,
175 boolean fromServiceState) {
176 Slog.d(LOG_TAG, "updateMccMncConfiguration mccmnc='" + mccmnc + "' fromServiceState=" + fromServiceState);
177
178 if (Build.IS_DEBUGGABLE) {
179 String overrideMcc = SystemProperties.get("persist.sys.override_mcc");
180 if (!TextUtils.isEmpty(overrideMcc)) {
181 mccmnc = overrideMcc;
182 Slog.d(LOG_TAG, "updateMccMncConfiguration overriding mccmnc='" + mccmnc + "'");
183 }
184 }
185
186 if (!TextUtils.isEmpty(mccmnc)) {
187 int mcc, mnc;
188
189 String defaultMccMnc = TelephonyManager.getDefault().getSimOperatorNumeric();
190 Slog.d(LOG_TAG, "updateMccMncConfiguration defaultMccMnc=" + defaultMccMnc);
191 //Update mccmnc only for default subscription in case of MultiSim.
192// if (!defaultMccMnc.equals(mccmnc)) {
193// Slog.d(LOG_TAG, "Not a Default subscription, ignoring mccmnc config update.");
194// return;
195// }
196
197 try {
198 mcc = Integer.parseInt(mccmnc.substring(0,3));
199 mnc = Integer.parseInt(mccmnc.substring(3));
200 } catch (NumberFormatException e) {
201 Slog.e(LOG_TAG, "Error parsing IMSI: " + mccmnc);
202 return;
203 }
204
205 Slog.d(LOG_TAG, "updateMccMncConfiguration: mcc=" + mcc + ", mnc=" + mnc);
Locale mccLocale = null; //添加这行
206 if (mcc != 0) {
207 setTimezoneFromMccIfNeeded(context, mcc);
mccLocale = getLocaleFromMcc(context, mcc); //添加这行
208 }
209 if (fromServiceState) {
210 setWifiCountryCodeFromMcc(context, mcc);
211 } else {
212 // from SIM
213 try {
214 Configuration config = new Configuration();
215 boolean updateConfig = false;
216 if (mcc != 0) {
217 config.mcc = mcc;
218 config.mnc = mnc == 0 ? Configuration.MNC_ZERO : mnc;
219 updateConfig = true;
220 }
221
if (mccLocale != null) { //添加这行
Configuration conLocale = new Configuration(); //添加这行
conLocale = ActivityManagerNative.getDefault().getConfiguration(); //添加这行
LocaleList userLocale = conLocale.getLocales(); //添加这行
LocaleList newUserLocale = new LocaleList(mccLocale,userLocale); //添加这行
config.setLocales(newUserLocale); //添加这行
updateConfig = true; //添加这行
} //添加这行
222 if (updateConfig) {
223 Slog.d(LOG_TAG, "updateMccMncConfiguration updateConfig config=" + config);
224 ActivityManagerNative.getDefault().updateConfiguration(config);
225 } else {
226 Slog.d(LOG_TAG, "updateMccMncConfiguration nothing to update");
227 }
228 } catch (RemoteException e) {
229 Slog.e(LOG_TAG, "Can't update configuration", e);
230 }
231 }
232 } else {
233 if (fromServiceState) {
234 // an empty mccmnc means no signal - tell wifi we don't know
235 setWifiCountryCodeFromMcc(context, 0);
236 }
237 }
238 }
//添加下面函数
245 private static boolean canUpdateLocale(Context context) {
246 return !(userHasPersistedLocale() || isDeviceProvisioned(context));
247 }
248
249 private static boolean userHasPersistedLocale() {
250 String persistSysLanguage = SystemProperties.get("persist.sys.locale", "");
251 String persistSysCountry = SystemProperties.get("persist.sys.country", "");
252 return !(persistSysLanguage.isEmpty() && persistSysCountry.isEmpty());
253 }
254
255 private static boolean isDeviceProvisioned(Context context) {
256 try {
257 return Settings.Global.getInt(
258 context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED) != 0;
259 } catch (Settings.SettingNotFoundException e) {
260 return false;
261 }
262 }
284 private static Locale getLocaleForLanguageCountry(Context context, String language,
285 String country) {
286 if (language == null) {
287 Slog.d(LOG_TAG, "getLocaleForLanguageCountry: skipping no language");
288 return null; // no match possible
289 }
290 if (country == null) {
291 country = ""; // The Locale constructor throws if passed null.
292 }
if(!canUpdateLocale()){ //添加这行
return null; //添加这行
} //添加这行
293
294 final Locale target = new Locale(language, country);
295 try {
296 String[] localeArray = context.getAssets().getLocales();
297 List<String> locales = new ArrayList<>(Arrays.asList(localeArray));
298
299 // Even in developer mode, you don't want the pseudolocales.
300 locales.remove("ar-XB");
301 locales.remove("en-XA");
302
303 List<Locale> languageMatches = new ArrayList<>();
304 for (String locale : locales) {
305 final Locale l = Locale.forLanguageTag(locale.replace('_', '-'));
306
307 // Only consider locales with both language and country.
308 if (l == null || "und".equals(l.getLanguage()) ||
309 l.getLanguage().isEmpty() || l.getCountry().isEmpty()) {
310 continue;
311 }
312 if (l.getLanguage().equals(target.getLanguage())) {
313 // If we got a perfect match, we're done.
314 if (l.getCountry().equals(target.getCountry())) {
315 Slog.d(LOG_TAG, "getLocaleForLanguageCountry: got perfect match: " +
316 l.toLanguageTag());
317 return l;
318 }
319
320 // We've only matched the language, not the country.
321 languageMatches.add(l);
322 }
323 }
325 Locale bestMatch = chooseBestFallback(target, languageMatches);
326 if (bestMatch != null) {
327 Slog.d(LOG_TAG, "getLocaleForLanguageCountry: got a language-only match: " +
328 bestMatch.toLanguageTag());
329 return bestMatch;
330 } else {
331 Slog.d(LOG_TAG, "getLocaleForLanguageCountry: no locales for language " +
332 language);
333 }
334 } catch (Exception e) {
335 Slog.d(LOG_TAG, "getLocaleForLanguageCountry: exception", e);
336 }
337
338 return null;
339 }
340
/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
19627 private boolean updateConfigurationLocked(Configuration values,
19628 ActivityRecord starting, boolean initLocale, boolean persistent, int userId) {
...
19643 if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) { //删除这行
if (!initLocale && !values.getLocales().isEmpty()) { //添加这行
19644 final LocaleList locales = values.getLocales();
19645 int bestLocaleIndex = 0;
19646 if (locales.size() > 1) {
19647 if (mSupportedSystemLocales == null) {
19648 mSupportedSystemLocales =
19649 Resources.getSystem().getAssets().getLocales();
19650 }
19651 bestLocaleIndex = Math.max(0,
19652 locales.getFirstMatchIndex(mSupportedSystemLocales));
19653 }
if(values.userSetLocale){
SystemProperties.set("persist.sys.locale", //添加这行
locales.get(bestLocaleIndex).toLanguageTag()); //添加这行
}else{ //添加这行
SystemProperties.set("persist.sys.simLocale", //添加这行
locales.get(bestLocaleIndex).toLanguageTag()); //添加这行
} //添加这行
19654 SystemProperties.set("persist.sys.locale", //删除这行
19655 locales.get(bestLocaleIndex).toLanguageTag()); //删除这行
19656 LocaleList.setDefault(locales, bestLocaleIndex);
19657 mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG,
19658 locales.get(bestLocaleIndex)));
19659 }
...
}
/frameworks/base/core/jni/AndroidRuntime.cpp
414const std::string readLocale()
415{
416 const std::string locale = getProperty("persist.sys.locale", "");
417 if (!locale.empty()) {
418 return locale;
419 }
420
421 const std::string language = getProperty("persist.sys.language", "");
422 if (!language.empty()) {
423 const std::string country = getProperty("persist.sys.country", "");
424 const std::string variant = getProperty("persist.sys.localevar", "");
425
426 std::string out = language;
427 if (!country.empty()) {
428 out = out + "-" + country;
429 }
430
431 if (!variant.empty()) {
432 out = out + "-" + variant;
433 }
434
435 return out;
436 }
437
const std::string simLocale = getProperty("persist.sys.simLocale", ""); //添加这行
if (!simLocale.empty()) { //添加这行
return simLocale; //添加这行
} //添加这行
438 const std::string productLocale = getProperty("ro.product.locale", "");
439 if (!productLocale.empty()) {
440 return productLocale;
441 }
442
443 // If persist.sys.locale and ro.product.locale are missing,
444 // construct a locale value from the individual locale components.
445 const std::string productLanguage = getProperty("ro.product.locale.language", "en");
446 const std::string productRegion = getProperty("ro.product.locale.region", "US");
447
448 return productLanguage + "-" + productRegion;
449}