至最新的Android P, Google已經提供了世界絕大多數語言的支持。但是對許多發往海外的項目,可能還是會有一些國家的語言google預設沒有支持。下麵將介紹下對於某一特定語言如何判斷Google是否支持這種語言、如何添加一種語言(如果google預設沒有支持)。 還有,自Android N以 ...
至最新的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可以從這個網址去查詢:https://zh.wikipedia.org/wiki/ISO_639-1
country code可以從這個網址去查詢:https://zh.wikipedia.org/wiki/ISO_3166-1
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}