[Android] Change locale, default locale, language list

在andoird 手機 ODM/OEM公司中.
常常要配合客戶的SPEC修改預設的語系.還有語言列表. 以及隨著特殊的SPEC修改目前的語系等等
會逐一分享.

Change Locale 更換語系

先從基本的更換語系開始

import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import java.util.Locale;
import android.content.res.Configuration;
import android.os.RemoteException;
.........
..........

public void updateLocale() {
    try {
        Locale l = new Locale("en", "US"); //Locale(string_language, string_location)
        IActivityManager am = ActivityManagerNative.getDefault();
        Configuration config = am.getConfiguration();
        config.setLocale(l);
        am.updateConfiguration(config);
    } catch (RemoteException e) {
        Log.d(TAG, "fail to update configuration");
    }
}

我記得要更換語系是需要system 權限的,所以別忘記在AndroidManifest.xml要加入 shareUserID=system

 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.android.settings"
        coreApp="true"
        android:sharedUserId="android.uid.system">

Language List 語言列表

語言列表通常會在哪裡看到呢?
1. OOBE
2. Settings->Input method & language

要討論語言列表有兩個角度可以探討:
- 手機中所有的語言列表有多少?
- Settings and OOBE 出現能選擇的語言有多少?

qualcomm /aosp
可以在 aosp/build/target/product/fullbase.mk 發現

$(call inherit-product, $(SRC_TARGET_DIR)/product/locales_full.mk)

那麼!aosp/build/target/product/locales_full.mk 會看到

PRODUCT_LOCALES := en_US en_IN fr_FR it_IT es_ES et_EE de_DE nl_NL cs_CZ pl_PL ja_JP zh_TW zh_CN zh_HK ru_RU ko_KR nb_NO es_US da_DK el_GR tr_TR pt_PT pt_BR rm_CH sv_SE bg_BG ca_ES en_GB fi_FI hi_IN hr_HR hu_HU in_ID iw_IL lt_LT lv_LV ro_RO sk_SK sl_SI sr_RS uk_UA vi_VN tl_PH ar_EG fa_IR th_TH sw_TZ ms_MY af_ZA zu_ZA am_ET hi_IN en_XA ar_XB fr_CA km_KH lo_LA ne_NP si_LK mn_MN hy_AM az_AZ ka_GE
..........................
..........................

便可以在這邊新增/刪除 客製化語言列表的功能.

MTK platform
其實大同小異,只是檔案名稱跟路徑不一樣

PRODUCT_LOCALES := en_US en_IN fr_FR it_IT es_ES et_EE de_DE nl_NL cs_CZ pl_PL ja_JP zh_TW zh_CN zh_HK ru_RU ko_KR nb_NO es_US da_DK el_GR tr_TR pt_PT pt_BR rm_CH sv_SE bg_BG ca_ES en_GB fi_FI hi_IN hr_HR hu_HU in_ID iw_IL lt_LT lv_LV ro_RO sk_SK sl_SI sr_RS uk_UA vi_VN tl_PH ar_EG fa_IR th_TH sw_TZ ms_MY af_ZA zu_ZA am_ET hi_IN en_XA ar_XB fr_CA km_KH lo_LA ne_NP si_LK mn_MN hy_AM az_AZ ka_GE

總是天不從人願,很多客戶很喜歡同個image有不一樣的設定.
ex: 隨著SIM卡不同?或是隨著operator變化?

首先,還是得先完成上面的工作。把所有可能會用到的語言添加進mk檔. (看是哪種platform)
再來。請看到 “frameworks/base/core/java/com/android/internal/app/LocalePicker.java

public static ArrayAdapter<LocaleInfo> constructAdapter(Context context,
            final int layoutId, final int fieldId, final boolean isInDeveloperMode) {
        final Resources resources = context.getResources();

        ArrayList<String> localeList = new ArrayList<String>(Arrays.asList(
                Resources.getSystem().getAssets().getLocales())); //取得系統目前的所有語言列表
        if (isInDeveloperMode) {
            if (!localeList.contains("zz_ZZ")) {
                localeList.add("zz_ZZ");
            }
        /** - TODO: Enable when zz_ZY Pseudolocale is complete
         *  if (!localeList.contains("zz_ZY")) {
         *      localeList.add("zz_ZY");
         *      }
         */
        }
        String[] locales = new String[localeList.size()];
        locales = localeList.toArray(locales);

        final String[] specialLocaleCodes = resources.getStringArray(R.array.special_locale_codes);
        final String[] specialLocaleNames = resources.getStringArray(R.array.special_locale_names);
        Arrays.sort(locales); //排序語言列表
        ...................................................................
        ...................................................................

那其實最關鍵的地方就在於

    String[] locales = new String[localeList.size()];
    locales = localeList.toArray(locales); 

只要讓locales裝上客製化的語言列表即可.
ex:

String _locale = "en_US,zh_TW";
String locales[] = _locale.split(",");
Arrays.sort(locales);

Default Locale 預設語系

因應會有各客戶或是operator希望出貨時第一次開機的語系能跟出貨地區配合
所以有研究這個機制 (其實也沒什麼好研究的 囧)

首先先看會決定語系的幾個property

persist.sys.language=zh
persist.sys.region=TW
ro.product.locale.language=en
ro.product.locale.country=US

Locale = language_country(or region)
Android 系統會優先以 persist 開頭的property作為目前系統的語系
如上面的例子:目前系統的顯示語言應為 繁體中文 (zh_TW)

  1. 所以可以在 project/build/core/Makefile 中修改, 加入persist或是在init.rc跟system.prop 加上都可以
    Ex: Qualcomm platform
persist.sys.language=zh
persist.sys.region=TW
  1. Android系統有個機制,在persist.sys.laguage跟persist.sys.region沒有value時:系統會把SIM的語系資料套用到整個系統.
    Ex: 再第一次開機或是Factory reset後. 手機已經插入Orange的SIM卡,開機的預設語言變化設成 fr_FR.
    所以按照第一點的方式去預設語系,則此功能將不會有作用 = issue. (其實是看客戶啦.)
    那麼,就改設ro.product.locale.language跟ro.product.locale.country.
    範例其實跟第一點一樣,只是改成ro開頭的prop
    Ex: Qualcomm platform
ro.product.locale.language=zh
ro.product.locale.country=TW

如果是有更複雜的需求,如開機後才判斷預設語系要設成那一組
上面的方法可能比較不彈性。
我個人的經驗,曾經在幾個地方設置過機制
首先. 把下面這段code拿掉 or 註解, 如此就不會有ro跟persist開頭的語系properties生成

..............................
..............................
#echo "ro.product.manufacturer=$PRODUCT_MANUFACTURER"
if [ -n "$PRODUCT_DEFAULT_LANGUAGE" ] ; then
  echo "ro.product.locale.language=$PRODUCT_DEFAULT_LANGUAGE"
fi
if [ -n "$PRODUCT_DEFAULT_REGION" ] ; then
  echo "ro.product.locale.region=$PRODUCT_DEFAULT_REGION"
fi
.....................................
.....................................
- 在 依照需求建立不同的.prop檔. 然後在system/core/init/property_service.c中load.
  • 或是在 system/core/init/init.c 中設定
  • 最後就是在 frameworks/base/core/jni/AndroidRuntime.cpp 中設定
static void readLocale(char* language, char* region) {
    char propLang[PROPERTY_VALUE_MAX], propRegn[PROPERTY_VALUE_MAX];
    property_get("persist.sys.language", propLang, "");
    property_get("persist.sys.country", propRegn, "");
    if (*propLang == 0 && *propRegn == 0) {//在persist.sys.language跟persist.sys.country為空時進入
        /* Set to ro properties, default is en_US */
        /* 可以將下列的 en 與 US 替換成所需要的locale */
        property_get("ro.product.locale.language", propLang, "en");
        property_get("ro.product.locale.region", propRegn, "US");
    }
    strncat(language, propLang, 2);
    strncat(region, propRegn, 2);
    //ALOGD("language=%s region=%s\n", language, region);
}

Update at 2016: Android M

遇到issue了, 回過頭看一下發現在Android M的機制似乎有更改:

property:
新增了兩組:ro.product.localepersist.sys.locale

..............................
..............................
#echo "ro.product.manufacturer=$PRODUCT_MANUFACTURER"
if [ -n "$PRODUCT_DEFAULT_LANGUAGE" ] ; then
  echo "ro.product.locale.language=$PRODUCT_DEFAULT_LANGUAGE"
fi
if [ -n "$PRODUCT_DEFAULT_REGION" ] ; then
  echo "ro.product.locale.region=$PRODUCT_DEFAULT_REGION"
fi
.....................................
.....................................

上面這組sh 被替換成:

..............................
..............................
#echo "ro.product.manufacturer=$PRODUCT_MANUFACTURER"
if [ -n "$PRODUCT_DEFAULT_LOCALE" ] ; then
  echo "ro.product.locale=$PRODUCT_DEFAULT_LOCALES"
fi
.....................................
.....................................

ro.product.locale.language與ro.product.locale.region 已經在此檔案中移除

在AndroidRuntime.cpp 在readlocale() 中的機制也有所更改

const std::string readLocale()
{
    const std::string locale = getProperty("persist.sys.locale", "");
    if (!locale.empty()) {
        return locale;
    }

    const std::string language = getProperty("persist.sys.language", "");
    if (!language.empty()) {
        const std::string country = getProperty("persist.sys.country", "");
        const std::string variant = getProperty("persist.sys.localevar", "");

        std::string out = language;
        if (!country.empty()) {
            out = out + "-" + country;
        }

        if (!variant.empty()) {
            out = out + "-" + variant;
        }

        return out;
    }

    const std::string productLocale = getProperty("ro.product.locale", "");
    if (!productLocale.empty()) {
        return productLocale;
    }

    // If persist.sys.locale and ro.product.locale are missing,
    // construct a locale value from the individual locale components.
    const std::string productLanguage = getProperty("ro.product.locale.language", "en");
    const std::string productRegion = getProperty("ro.product.locale.region", "US");

    return productLanguage + "-" + productRegion;
}
- 先確認 persist.sys.locale這個property是否存在? return persist.sys.locale : next step
- 確認 persist.sys.locale.language/region存在? return persist.sys.locale.language + region : next step
- 確認ro.product.locale是否存在? return ro.product.locale : next step
- 確認ro.product.locale.language/region 存在? return ro.product.locale.language + region : return en_US

以上是Android M的更新