摸摸嚕嚕

從 Hello World 開始

因為客製化的需求,第一次嘗試在Settings 中添加新的功能
其中一項為 ListView 的物件可以拖拉排序

參考的為 Music 中的 TrackBrowserActivity.java 與 TouchInterceptor.java

首先先自訂一個Layout for activity

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <com.android.alucard.DragListView
       android:id="@+id/drag_list"
       android:layout_width="fill_parent"
       android:layout_height="fill_parent"
       android:cacheColorHint="#00000000"/>
</LinearLayout>

因為要實現能drag and drop 的效果,使用原生的ImageView or ListView 無法實現
所以寫了一個新的View (繼承ListView) 並在Layout中使用他.

以下是 DragListView 的實現

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;

public class DragListView extends ListView {

    private ImageView dragImageView;
    private int dragSrcPosition;
    private int dragPosition;

    private int dragPoint;
    private int dragOffset;
    private Context mContext;

    private WindowManager windowManager;
    private WindowManager.LayoutParams windowParams;

    private int scaledTouchSlop;
    private int upScrollBounce;
    private int downScrollBounce;

    private DropListener mDropListener;


    public DragListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    public interface DropListener {
        void drop(int dragPosition ,int dropPosition);
    }

    public void setDropListener(DropListener l) {
        mDropListener = l;
    }
    //
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if(ev.getAction() == MotionEvent.ACTION_DOWN){
            int x = (int) ev.getX();
            int y = (int) ev.getY();

            dragSrcPosition = dragPosition = pointToPosition(x, y);
            if(dragPosition == AdapterView.INVALID_POSITION){
                return super.onInterceptTouchEvent(ev);
            }
            ViewGroup itemView = (ViewGroup) getChildAt(dragPosition - getFirstVisiblePosition());
            dragPoint = y - itemView.getTop();
            dragOffset = (int) (ev.getRawY() - y);

            View dragger = itemView.findViewById(R.id.drag_list_item_image);
            if(dragger !=null && x > dragger.getLeft() - 20){

                upScrollBounce = Math.min(y-scaledTouchSlop, getHeight()/3);
                downScrollBounce = Math.max(y+scaledTouchSlop, getHeight()*2/3);

                itemView.setDrawingCacheEnabled(true);
                Bitmap bm = Bitmap.createBitmap(itemView.getDrawingCache());
                bm.setHasAlpha(true);
                startDrag(bm, y);
            }
            return false;
         }
         return super.onInterceptTouchEvent(ev);
    }

    /**
     *
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if(dragImageView != null && dragPosition != INVALID_POSITION){
            int action = ev.getAction();
            switch(action){
                case MotionEvent.ACTION_UP:
                    int upY = (int)ev.getY();
                    stopDrag();
                    onDrop(upY);
                    break;
                case MotionEvent.ACTION_MOVE:
                    int moveY = (int)ev.getY();
                    onDrag(moveY);
                    break;
                default:break;
            }
            return true;
        }
        //
        return super.onTouchEvent(ev);
    }

    /**
     *
     * @param bm
     * @param y
     * 在拖曳的時候,被拖曳的image會放大120%
     */
    public void startDrag(Bitmap bm ,int y){
        stopDrag();
        float disreHeight =  bm.getHeight() * (float) 1.2;
        float disreWidth = bm.getWidth()  * (float) 1.2;
        windowParams = new WindowManager.LayoutParams();
        windowParams.gravity = Gravity.TOP;
        windowParams.x = 0;
        windowParams.y = y - dragPoint + dragOffset;
        windowParams.width = (int) disreWidth;
        windowParams.height = (int )disreHeight;
        windowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                            | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                            | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                            | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
        windowParams.format = PixelFormat.TRANSLUCENT;
        windowParams.alpha=0.6f;
        windowParams.windowAnimations = 0;

        ImageView imageView = new ImageView(getContext());
        imageView.setBackgroundColor(Color.WHITE);
        imageView.setImageBitmap(bm);
        imageView.setScaleType(ImageView.ScaleType.FIT_XY);
        windowManager = (WindowManager)getContext().getSystemService("window");
        windowManager.addView(imageView, windowParams);
        dragImageView = imageView;
    }

    /**
     *
     */
    public void stopDrag(){
        if(dragImageView != null){
            windowManager.removeView(dragImageView);
            dragImageView = null;
        }
    }

    /**
      * 因為需求只要求可以上下移動,故只取縱向的數據
      *
      */
    public void onDrag(int y){
        if(dragImageView != null){
            windowParams.alpha = 0.8f;
            windowParams.y = y - dragPoint + dragOffset;
            windowManager.updateViewLayout(dragImageView, windowParams);
        }
        //
        int tempPosition = pointToPosition(0, y);
        if(tempPosition != INVALID_POSITION){
            dragPosition = tempPosition;
        }

        int scrollHeight = 0;
        if( y < upScrollBounce){
            scrollHeight = 8;
        }else if(y > downScrollBounce){
            scrollHeight = -8;
        }

        if(scrollHeight!=0){
            setSelectionFromTop(dragPosition, getChildAt(dragPosition - getFirstVisiblePosition()).getTop() + scrollHeight);
        }
    }

    /**
      * 因為需求只要求可以上下移動,故只取縱向的數據
      *
      */
    public void onDrop(int y){
        int tempPosition = pointToPosition(0, y);
        if(tempPosition != INVALID_POSITION){
            dragPosition = tempPosition;
        }
        if(y < getChildAt(1).getTop()){
            dragPosition = 0;
        }else if(y > getChildAt(getChildCount()-1).getBottom()){
            dragPosition = getAdapter().getCount()-1;
        }
        if(dragPosition >= 0 && dragPosition < getAdapter().getCount()){
            mDropListener.drop(dragSrcPosition, dragPosition);
        }

    }
}

在Activity中 調用 Adapter, 並且倒入到剛剛的View中.
Activity:

import android.content.Intent;
import android.view.View;
import android.view.ViewGroup;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import android.widget.WrapperListAdapter;
import android.content.Context;
import android.provider.Settings;


import android.app.Fragment;
import android.view.LayoutInflater;
import android.os.Bundle;

import android.util.Log;


public class LockFuncSettings extends Fragment {

    private final String TAG = "LockFuncSettings";
    private Context mContext;
    private View mlistview;
    private List<String> mFunctionList;

    private DragListAdapter adapter;
    private DragListView dragListView;
    private final String KEY_LOCK_FUNC_SETTING = "lockscrren_func_setting_pref";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mContext = getActivity();

        mFunctionList = new ArrayList<String>();
        loadFuncList();

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        mlistview = inflater.inflate(R.layout.drag_list_activity, null);
        dragListView = (DragListView) mlistview.findViewById(R.id.drag_list);
        return mlistview;
    }

    private void loadFuncList() {
        String list = Settings.System.getString(mContext.getContentResolver(), Settings.System.LOCKSCREEN_FUNC_LIST);
        for (String s : list.split(" ")) {
            mFunctionList.add(s);
        }

        mFunctionList.add(5, "12");
    }
    @Override
    public void onResume() {
        adapter = new DragListAdapter(mContext, mFunctionList);
        dragListView.setDropListener(mDropListener);
        dragListView.setAdapter(adapter);
        super.onResume();
    }

    @Override
    public void onPause() {
        super.onPause();
    }

    private DragListView.DropListener mDropListener =
            new DragListView.DropListener() {
                public void drop(int dragPosition ,int dropPosition) {
                    if (adapter != null) {
                        String dragItem = (String) adapter.getItem(dragPosition);
                        Log.d("LockFuncSettings", "dragItem = " + dragItem);
                        adapter.remove(dragItem);
                        adapter.insert(dragItem, dropPosition);
                        //---------------------------------------------------------
                        adapter.remove("12");
                        String result = "";
                        for(int i = 0; i < 12; i++) {
                            result = result + (String) adapter.getItem(i) + " ";
                        }
                        Log.d(TAG, "result = " + result);
                        Settings.System.putString(mContext.getContentResolver(),
                                Settings.System.LOCKSCREEN_FUNC_LIST, result.trim());
                        adapter.insert("12", 5);
                        adapter.notifyDataSetChanged();
                        mContext.sendBroadcast(new Intent("android.intent.action.FUNC_SETTINGS_CHANGED"));
                    }
                }
            };

}

Adapter:

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import android.widget.ImageView;
import android.provider.Settings;
import android.util.Log;

public class DragListAdapter extends ArrayAdapter<String> {

    public List<String> groupKey = new ArrayList<String>();
    public DragListAdapter(Context context, List<String> objects) {
        super(context, 0, objects);
        groupKey.add(getContext().getString(R.string.lock_settings_wont_show));
    }


    @Override
        public boolean isEnabled(int position) {
            if(groupKey.contains(getItem(position))){
                //
                return false;
            }
            return super.isEnabled(position);
        }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        View view = convertView;
        if(position == 5) {
            view = LayoutInflater.from(getContext()).inflate(R.layout.drag_list_item_tag, null);
        }else{
            view = LayoutInflater.from(getContext()).inflate(R.layout.drag_list_item, null);
        }

        TextView textView = (TextView)view.findViewById(R.id.drag_list_item_text);
        ImageView imageView = (ImageView) view.findViewById(R.id.drag_list_item_icon);
        int id = Integer.parseInt(getItem(position));
        boolean isBlue = (position < 5)? true:false;

        switch (id) {
            case Settings.System.FUNC_CALCULATOR:
                imageView.setImageResource(isBlue? R.drawable.list_calculator_blue:R.drawable.list_calculator_grey);
                textView.setText(R.string.func_calculator);
                break;
            case Settings.System.FUNC_PLAYLIST:
                imageView.setImageResource(isBlue? R.drawable.list_playlist_blue:R.drawable.list_playlist_grey);
                textView.setText(R.string.func_playlist);
                break;
            case Settings.System.FUNC_SELFIE:
                imageView.setImageResource(isBlue? R.drawable.list_selfie_blue:R.drawable.list_selfie_grey);
                textView.setText(R.string.func_selfie);
                break;
            case Settings.System.FUNC_CONTACT:
                imageView.setImageResource(isBlue? R.drawable.list_contact_blue:R.drawable.list_contact_grey);
                textView.setText(R.string.func_contact);
                break;
            case Settings.System.FUNC_QR_CODE:
                imageView.setImageResource(isBlue? R.drawable.list_qr_blue:R.drawable.list_qr_grey);
                textView.setText(R.string.func_qr_code);
                break;
            case Settings.System.FUNC_MESSAGE:
                imageView.setImageResource(isBlue? R.drawable.list_mes_blue:R.drawable.list_mes_grey);
                textView.setText(R.string.func_message);
                break;
            case Settings.System.FUNC_EMAIL:
                imageView.setImageResource(isBlue? R.drawable.list_email_blue:R.drawable.list_email_grey);
                textView.setText(R.string.func_email);
                break;
            case Settings.System.FUNC_AGENDA:
                imageView.setImageResource(isBlue? R.drawable.list_event_blue:R.drawable.list_event_grey);
                textView.setText(R.string.func_agenda);
                break;
            case Settings.System.FUNC_RECORD:
                imageView.setImageResource(isBlue? R.drawable.list_record_blue:R.drawable.list_record_grey);
                textView.setText(R.string.func_record);
                break;
            case Settings.System.FUNC_HOME:
                imageView.setImageResource(isBlue? R.drawable.list_naigation_blue:R.drawable.list_naigation_grey);
                textView.setText(R.string.func_home);
                break;
            case Settings.System.FUNC_ALARM:
                imageView.setImageResource(isBlue? R.drawable.list_alarm_blue:R.drawable.list_alarm_grey);
                textView.setText(R.string.func_alarm);
                break;
            case Settings.System.FUNC_NOTE:
                imageView.setImageResource(isBlue? R.drawable.list_note_blue:R.drawable.list_note_grey);
                textView.setText(R.string.func_note);
                break;
            default:
                imageView.setImageResource(R.drawable.list_arrow);
                textView.setText(R.string.lock_settings_wont_show);
        }
        if (!isBlue) {
            view.setBackgroundColor(Color.parseColor("#e5e5e5"));
            textView.setTextColor(Color.parseColor("#a0a0a0"));
        }
        return view;
    }
}

在DragListView中的物件,有兩個xml

第一個是可以拖拉物件的layout 定義
請注意,一定要用 RelativeLayout

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content">
    <ImageView android:id="@+id/drag_list_item_icon"
        android:layout_centerVertical="true"
        android:layout_width="wrap_content"
        android:layout_marginStart="@dimen/search_result_item_image_margin_start"
        android:layout_marginEnd="@dimen/search_result_item_image_margin_end"
        android:layout_height="@dimen/drag_item_normal_height"/>
    <TextView
       android:id="@+id/drag_list_item_text"
       android:layout_width="wrap_content"
       android:layout_height="@dimen/drag_item_normal_height"
       android:layout_marginLeft="75dp"
       android:layout_alignParentStart="true"
       android:textSize="16sp"
       android:gravity="center_vertical"/>
    <ImageView android:id="@+id/drag_list_item_image"
       android:src="@drawable/list_menu"
       android:layout_marginRight="15dp"
       android:layout_alignParentRight="true"
       android:layout_centerVertical="true"
       android:layout_width="wrap_content"
       android:layout_height="@dimen/drag_item_normal_height"/>
</RelativeLayout>

第二個為不可拖拉的物件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="#a0a0a0"
    android:padding="5dip"
    android:paddingLeft="10dip">
    <ImageView android:id="@+id/drag_list_item_icon"
        android:layout_centerVertical="true"
        android:layout_width="wrap_content"
        android:layout_marginStart="@dimen/search_result_item_image_margin_start"
        android:layout_marginEnd="@dimen/search_result_item_image_margin_end"
        android:layout_height="@dimen/drag_item_normal_height"/>
    <TextView
       android:id="@+id/drag_list_item_text"
       android:layout_width="wrap_content"
       android:layout_height="20dip"
       android:textColor="#ffffff"
       android:textSize="14sp"
       android:layout_marginStart="@dimen/settings_side_margin"
       android:layout_marginEnd="@dimen/settings_side_margin"
       android:gravity="center_vertical"/>
</LinearLayout>

Android Studio:

這個應該就不需要解釋了.
Download android studio

AOSP:Android Open Source Project

Google 推出的 android codebase. 介紹
請安裝好環境並且下載codebase.

安裝環境教學:看這邊

下載教學: 再看這邊

步驟

因為codebase 實在龐大
單一修改某之檔案或是一個apk. 我會使用sublime text
修改好後再使用 make 去編譯

但是一邊trace,又要一邊開發新的feature 沒有IDE實在麻煩

所以找到了可以用 android studio 看整包codebase的方法

  1. 先build完整包codebase, 先確認codebase 是否有遺漏或是文法錯誤
  2. 終端機回到 AOSP的root path, 執行mmm development/tools/idegen/
  3. build 完成後,請再執行 sh ./development/tools/idegen/idegen.sh

最後會產生 android.iws, android.ipr, android.iml 等檔案
此時請在 android studio 中,選擇 Open an exists Android Studio project
將目錄導到AOSP的root 即可

成功開啟專案,此時andorid studio會進行一場很漫長的indexing過程
就是下午茶的時候啦!!!

補充

我在執行3的時候,會遇到以下的fail

Exception in thread "main" java.io.FileNotFoundException: ./out/target/product/hammerhead/obj/GYP/shared_intermediates/res.java (Is a directory)

此時將 res.java rename 為 res.j 這樣就可以build過了

Broadcast receiver: static and non-static.

Static boradcast recever:

請先註冊一個 “receiver” 在 AndroidManifests.xml

<receiver
    android:name=".TypeBReceiver">
    <intent-filter>
         <action android:name="test_TypeB" />
    </intent-filter>
</receiver>

android:name 此為receiver的名稱, 需要額外新增一個檔案名稱與他相同.
intent-filter 此為篩選要接受哪些action的篩選器.

註冊好後 請新增一支java 檔案,


/**
 * Created by alumincan_leu on 2015/1/22.
 */
public class TypeBReceiver extends BroadcastReceiver {
    private final String TAG = "TypeBReceiver";
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "get type B");
    }
}

此種方式,不需要launch activity, 也可以收到broadcast intent.

non-static boradcast recever:

此種方法要在Activity 或是 Service 中另外註冊
若是activity or service 沒有被launch, 則不會收到intent.

首先請先import package.

import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.content.Intent;

宣告一個Broadcast receiver:

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            Log.d(TAG, "get typeA");
            mTextVire_msg1.setText("");
            if (action.equals(INTENT_ACTION_TYPEA)){
                mTextVire_msg1.setText("getMsg");
            }
        }
    };

宣告filter, 要接受哪些 action; 然後註冊broadcast receiver

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...................;
        ...................;
        IntentFilter mfilter = new IntentFilter();
        mfilter.addAction(INTENT_ACTION_TYPEA);
        registerReceiver(mReceiver, mfilter);
{
       

有註冊 當然要有註銷

@Override
protected void onDestroy(){
    Log.d(TAG, "onDestroy");
    unregisterReceiver(mReceiver);
    super.onDestroy();
}

請注意:
如果是在 onCreate 註冊,請在onDestroy註銷; 同理
若是在 onResume 註冊,則請在 onPause 註銷.
請參閱 android life cycle: android developer

在工作上必須碰到跟語言相關的Function
其中一個就是ICU. (絕對不是加護病房)

ICU: International_Components for Unicode
International Components for Unicode (ICU) is an open source project of mature C/C++ and Java libraries for Unicode support, software internationalization, and software globalization. ICU is widely portable to many operating systems and environments. It gives applications the same results on all platforms and between C, C++, and Java software. The ICU project is sponsored, supported, and used by IBM and many other companies.[1]

http://en.wikipedia.org/wiki/International_Components_for_Unicode
詳細可以參考上面wiki網址,很遺憾沒有繁體中文的wiki. 連同繁體中文的介紹也很少 QQ

首先是環境需求首先是環境需求:
因為文件未提起,所以我不知道要準備哪些東西
基本上能build android 的codebase 應該就可以了!!!! (完全的不負責任)

Issue 描述:
choose language used “Burma” is worng, please change to “”Myanmar”

icu_1.png
大意就是 - 上面這張圖的紅色框框字是錯的. 我希望改成下圖框框內的文字
icu_2.png

很簡單吧!?

首先,這是緬甸語.
可以下達adb shell getprop | grep persist 確認語系.
我確認過為語系為 my_ZG

所以修改一下這個檔案external/icu4c/data/lang/my_ZG.txt

找到my 發現旁邊的文字跟第一張圖相同. 然後 修改阿 xD
改好之後

產生一個icuBuild的資料夾在external/icu4c 並到路徑底下

mkdir external/icu4c/icuBuild
cd external/icu4c/icuBuild

產生make 文件,然後make

.././runConfigureICU Linux
make –j2

確認

Make後在external/icu4c/icuBuild/data/out/tmp/ 產生一個 icudt48l.dat. 可以將此檔案push到手機中 測試是否修改正確。

cd external/icu4c/icuBuild/data/out/tmp/
adb remount
adb push icudt48l.dat system/usr/icu
adb reboot

icudt48l 會因為icu版本不同而有不同的數字,所以不一定皆為48l.dat

此時重開機已經將文字修改過

上code

修改好了,必須將檔案push到git server上. 請將 external/icu4c/icuBuild/data/out/tmp/icudt48l.dat rename 為 icudt44l-all.dat. 並將此檔案覆蓋至 external\icu4c\stubdata. 請注意,須覆蓋原始檔案.

設定環境變數

export ANDROID_BUILD_TOP=project_path

執行script, 重新產生.dat

cd external/icu4c/studdata
./icu_dat_generator.py

git status可以發現icudt48l.dat 已經被修改過.
就直接 git commit -a 然後push到 server上吧

icu_3.png

在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的更新

Android 內建了很多Activity, 有些是不希望被end user看到
所以有幾種方式可以將Activity不出現在 Home/app list

  • 1 AndroidManifest.xml
<action android:name="android.intent.action.MAIN"  />
<category android:name="android.intent.category.LAUNCHER" />

將上述兩個其中一個移除即可
優點:簡單、易懂
缺點:需要rebuild app. Activity才會出現在app list.

  • 2 使用PackageManager 的API
    使用PackageManager的API, 可以在程式中設定一個Activity的state為enable or disable.
import android.content.ComponentName;
import android.content.pm.PackageManager;
/*......................................................*/
......
......
PackageManager pm=getPackageManager();
ComponentName cpn = new ComponentName(packageName, ClassName);
pm.setComponentEnabledSetting(cpn, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 
            PackageManager.DONT_KILL_APP); // enable app
pm.setComponentEnabledSetting(cpn, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 
            PackageManager.DONT_KILL_APP); // disable app.
.......
.......
```	
上面的例子是使用setComponentEnabledSetting這個API.	
下面的例子則是使用 setApplicationEnabledSetting.		

```java
import android.content.pm.PackageManager;
/*......................................................*/
......
......
PackageManager pm=getPackageManager();

pm.setApplicationEnabledSetting(ActivityName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 
            PackageManager.DONT_KILL_APP); // enable app
pm.setApplicationEnabledSetting(ActivityName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 
            PackageManager.DONT_KILL_APP); // disable app.
.......
.......

[參考連結 - setComponentEnabledSetting](http://developer.android.com/reference/android/content/pm/PackageManager.html#setComponentEnabledSetting(android.content.ComponentName, int, int))
[參考連結 - setApplicationEnabledSetting](http://developer.android.com/reference/android/content/pm/PackageManager.html#setApplicationEnabledSetting(java.lang.String, int, int))

因為工作需求,有一台Linux電腦專門在build android codebase 是很正常的事情:

你需要一台電腦

請先下載ubuntu iso ubuntu官網

安裝ubuntu. (燒錄成光碟或是使用usb安裝)

先連接上網路

安裝JDK. (Java develope kit).

####”NOTE: 不要安裝open-jdk, 請安裝 jdk6” for Android 5.0 lower

  1. 請到oracle 下載 Java SE 6.
  2. 安裝jdk-6
     $ sudo chmod 755 ./jdk-6u32-linux-x64.bin
     $ sudo -s ./jdk-6u32-linux-x64.bin /opt  // 一直next.
     
  3. 修改class-path
     $ sudo gedit .bashrc
     在檔案最後貼上 
         export JAVA_HOME="/opt/java/jdk"
         export JRE_HOME="$JAVA_HOME/jre"
         export CLASSPATH=.:$CLASSPATH:$JAVA_HOME/lib:$JRE_HOME/lib
         export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH
     存檔  離開
     
  4. 先確認 /usr/lib/jvm 路徑是否存在,若否: 搬移jdk資料夾
     $ sudo mkdir /usr/lib/jvm
     $ sudo mv jdk1.6.0_32 /usr/lib/jvm
     
  5. 搬好資料夾後
     $ sudo update-alternatives --install "/usr/bin/java" "java" "/usr/lib/jvm/jdk1.6.0_32/bin/java" 1
     $ sudo update-alternatives --install "/usr/lib/mozilla/plugins/libjavaplugin.so" "mozilla-javaplugin.so" "/usr/lib/jvm/jdk1.6.0_32/jre/lib/amd64/libnpjp2.so" 1
     
  6. 切換Java的設定
     $ sudo update-alternatives --config java
     $ sudo update-alternatives --config mozilla-javaplugin.so
     
  7. 查看目前Java版本
     $java -version
     

Install packages

$ sudo apt-get install git gnupg flex bison gperf build-essential zip curl libc6-dev libncurses5-dev:i386 x11proto-core-dev libx11-dev:i386 libreadline6-dev:i386 libgl1-mesa-glx:i386 libgl1-mesa-dev g++-multilib mingw32 tofrodos python-markdown libxml2-utils xsltproc zlib1g-dev:i386
  $ sudo ln -s /usr/lib/i386-linux-gnu/mesa/libGL.so.1 /usr/lib/i386-linux-gnu/libGL.so
  $ sudo apt-get install software-properties-common
  $ sudo apt-get install python-software-properties

安裝Openjdk-7-jdk

sudo add-apt-repository ppa:openjdk-r/ppa
sudo apt-get update
sudo apt-get install openjdk-7-jdk