Airplane mode

在追bug時,去Trace的結果,後來發現如果Phone的RIL層有問題的話,會造成看起來沒有進Airplane mode但實際上卻已經進去的奇怪現象 。Android裡,選Airplane mode以後會發生的事情:

  1. AirplaneModeEnabler送broadcast message出來。有3個人會收到:1.wifi 2.bluetooth 3.Phone。wifi 跟 bluetooth 裡收到訊息以後的處理都很簡單,Phone 裡主要是呼叫 phone.setRadioPower() 這行 (Packages/apps/Phone/src/com/android/phone/PhoneApp.java)。
  2. 這時候會呼叫 GSMPhone/CDMAPhone 的 setRadioPower,GSMPhone/CDMAPhone 都繼承自 ServiceStateTracker,所以是呼叫 ServiceStateTracker.setRadioPower。
  3. ServiceStateTracker.setRadioPower 會呼叫 setPowerStateToDesired (CdmaServiceStateTracker/GsmServiceStateTracker),實際上又是呼叫 cm.setRadioPower
  4. cm 的型別是 CommandsInterface,只有 RIL 有實作此 interface (frameworks/base/telephony/java/com/android/internal/telephony/RIL.java)。自此,訊息往下丟到 RIL daemon,參照 reference-ril 裡的 setRadioState,之後處理完會回傳 RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED。
  5. RIL.java 的 processUnsolicited() 會處理,這邊會呼叫 setRadioStateFromRILInt(),setRadioStateFromRILInt()裏面又再呼叫 setRadioState (frameworks/base/telephony/java/com/android/internal/telephony/BaseCommands.java),這裡會去 notify,所以根據 mRadioStateChangedRestrants 找 registerForRadioStateChanged()
  6. 找 registerForRadioStateChanged 的結果,可以看到 GsmServiceStateTracker/CdmaServiceStateTracker 有去呼叫。再看 ServiceStateTracker 裡的 handler 去處理 EVENT_RADIO_STATE_CHANGED 的部份:setPowerStateToDesired() => pollState() => pollStateDone(),pollStateDone() 裏面就可以看到有用 phone.notifyServiceStateChanged(),notifyServiceStateChange 又是呼叫父類別的 notifyServiceStateChangedP()。
  7. notifyServiceStateChangedP() 呼叫 mNotifier.notifyServiceState。mNotifier 則是 DefaultPhoneNotifier,再看 DefaultPhoneNotifier,裏面就有 registry.notifyServiceState() (TelephonyRegistry.java),這會再呼叫 broadcastServiceStateChanged()。從這裡的 TelephonyIntents.ACTION_SERVICE_STATE_CHANGED 去找,可以發現只有 PhoneStateIntentReceiver 有收這個 broadcast message。
  8. PhoneStateIntentReceiver 會再去 sendMessage。AirplaneModeEnabler 裡就有使用 PhoneStateIntentReceiver,並註冊了自己的 handler,所以 AirplaneModeEnabler 最後就會收到,並且改變項目的狀態。

這段過程幾乎都是以非同步的方式在傳遞,並不是平鋪直敘的,所以我看了好幾次才確定是這麼一回事…沒邊寫邊記的話,恐怕會多繞好幾天…

Android Build Number

Settings > About Phone > Build number 跟 TARGET_BUILD_VARIANT 有關係。
如果 TARGET_BUILD_VARIANT 為空,那麼,顯示出來的會是一串長長的字串,由很多東西所組成。
如果 TARGET_BUILD_VARIANT 的值是 user,那麼,顯示出來的字串會依 DISPLAY_BUILD_NUMBER (true/false)來決定是只有 BUILD_ID 或 BUILD_ID + BUILD_NUMBER。(參考 build/core/Makefile)
那麼要怎麼自訂 BUILD_ID 或 BUILD_NUMBER 呢?照理說,應該是要在 buildspec.mk 裡定義,但是在 build/core/config.mk 裡,include buildspec.mk 之後,又 include 了 envsetup.mk 跟 BoardConfig.mk,envsetup.mk 裡有 include version_defaults.mk,這裡有 include build_id.mk 會把之前定義的 BUILD_ID 跟 BUILD_NUMBER 都覆蓋掉。所以比較適合的地方是 device 下的 BoardConfig.mk,只是這樣有個小缺點,就是 make 時,顯示出來的資訊是 build_id.mk 裡定義的。
以上,都是參考 build 下的檔案。

crosscompile lua

bc 好久都沒出新版了,裏面原本的 configure 對 crosscompile 支援不良,而且因為用到 flex 跟 bison 的關係,這兩個也要跟著 crosscompile,太麻煩,所以就想找一些輕量級的 script 語言來用。
仔細想想,lua 似乎是最好的選擇,就拿來用了。lua 原始碼解開只有 Makefile,原本以為 crosscompile 會很麻煩的,仔細看過以後,發現不難,只要這樣編譯,就可以了。

make CC=arm-none-linux-gnueabi-gcc RANLIB=arm-none-linux-gnueabi-ranlib ansi

用 ansi 的原因,是可以避掉要連結 ncurse、dl 等的 library dependencies,如果用 generic 或 linux 就得要再 crosscompile ncurse。

Android 的多國語言

一般抓下來 build,應該就會有多國語言了,如果沒有的話,就檢查 AndroidProduct.mk 裡的 PRODUCT_LOCALES。
從 T 公司拿到的這一包,裏面正好就只放了 en_US,所以 build 出來就只看到英文,其他國家都沒看到。在 PRODUCT_LOCALES 加上所需的區域代碼以後,重新 build,就可以看到了。

mtab有多重要?

android 裡面沒有提供 mkfs.ext3,雖然 android source code 裡面有e2fsprogs,但沒有 build 出來。
如果使用 android source code 裡提供的 1.40.x 版的e2fsprogs,cross compile 時,要用 LDFLAGS=-static ./configure –host=arm-none-linux-gnueabi –with-cc=arm-none-linux-gnueabi-gcc 才行,只用 –host 的話,是不行的。我試了好一陣子才試出來。
如果你自己下載 1.41.x版的e2fsprogs,編譯指令簡單多了,只要用 LDFLAGS=-static ./configure –host=arm-none-linux-gnueabi 就行了。
為什麼會提到 1.40.x 跟 1.41.x 呢?這是因為我拿 mkfs.ext3 到 android 環境下格式化分割區,在格式化以後卻發現無法 mount,mount 會出現 Invalid argument的錯誤。原本以為是版本的問題,可是試過1.40.8、1.40.9跟1.41.x以後,發現都不行。接著再試 busybox 提供的 mke2fs,也不行。
實在是沒辦法,只好用 dd 來做。不過 dd 速度真的是不快,而且為了要能把 image 放到 sd 卡上,我還用了 gzip 來壓縮。既要在 android 機器上解壓縮,又要做 dd,自然是慢到爆。 (指令是 gunzip -c xxx.img.gz | dd of=/dev/block/mmcblknpn)
最後的最後,終於想到要注意 mke2fs 關於 /etc/mtab 的警告訊息,它一直很盡責的告訴我,沒有 mtab,它沒辦法檢查分割區是否有 mount 起來。所以我在 /etc 加了一個空的 mtab,再去 mke2fs,完成以後再 mount,想不到就可以了耶~
追查 e2fsprogs 的原始碼,發現的確是有檢查 mtab,而且沒有 mtab 的時候,它不會去製作 journaling,換言之,也就是分割區的確格式化了,但是沒有 journaling 訊息,這也導致了無法 mount 的情況。
呼應主題,mtab有多重要呢? 拿 HTML 的 h1~h7 來比喻的話,mtab 大概有 h1 那麼重要!!!

omap36xx的vibrator

我是沒看原來 android 釋出的原始碼,不過按照 TI 釋出的這份code,vibrator 主要是對 /sys/class/timed_output/vibrator/enable 寫入時間,寫入以後,就會開始震動。時間的單位是 ms。
但是 kernel 的 code 就沒有這部份的 driver,唯一會在 /sys 建出這檔案的 driver 是 vib-omap-pwm.c ,可是,這檔案裏面只有操作硬體 timer ,跟震動一點關係都沒有。後來是從 2.6.35 那邊拿原本是 input driver 的 twl4030-vibra.c 來改。
改的時候遇到一點小 trouble,最初的想法是用 timer 來做,但是 kernel 裡的 timer 是用 soft interrupt 完成的,所以在 timer 被觸發的時候,如果用 i2c 去寫的話,會導致 kernel panic (因為i2c 也會利用 soft interrupt 去等),這時候就得利用 workqueue,在 timer 觸發的時候,不要第1時間用 i2c 去寫,而是丟到 workqueue 裡,kernel 會儘快安排時間去執行這部份。
於是,這樣就搞出 vibrator driver 了。

Android 檔案系統用哪個好? ext3 或 ext4?

因為看到有人說 ext4 在 Android 裡的表現比 ext3 好,就想說來試試看。

本來是打算用 Quadrant 來評測,可是跑完以後,他會要上傳數據到網站上才能得到結果,我的 device 沒辦法連上網,所以只能放棄。後來改用 Linux 傳統的 iozone 來做,下載、解開 iozone tarball 以後,用

make CC=/opt/arm-2009q1/bin/arm-none-linux-gnueabi-gcc GCC=/opt/arm-2009q1/bin/arm-none-linux-gnueabi-gcc LDFLAGS=-static linux-arm

就可以編譯出來了。(我的ARM toolchain 放在 /opt/arm-2009q1 下)

丟到 device 上執行的時候跑了好幾次,都沒成功,只能降低最大 record size 跟最大 file size,然後就行了。我用的指令如下:

/bin/iozone -R -a -q 2048 -g 4096

從最後得到的報告看起來 ext4 是略優於 ext3 的,但是使用上的感覺並不是很明顯,可能是因為沒有用大檔案的關係吧,以後再仔細試試看。

添加自訂的類別

就我目前所知道的有兩種方法:

  1. 放在mydroid/frameworks/base下:在mydroid/frameworks/base下建立目錄,然後裏面放java的source(請記得建必要的目錄),如果有需要的話,也可以在這裡放jni,對,不需要放Android.mk,只有jni目錄下需要。然後修改mydroid/frameworks/base/Android.mk,在packages_to_document後,加上你的目錄名稱,就這樣。最後你的類別會被加到 android.jar 裡。
  2. 放在mydroid/vendors/your_vendor目錄下:建立frameworks目錄,然後裏面再放目錄、Android.mk,接著就跟上面很類似了。最後再修改mydroid/vendors/your_vendor/Android.mk去include frameworks目錄下的Android.mk即可。這部份可以參考mydroid/vendor/sample/。最後還要記得修改 init.rc 裡的 BOOTCLASSPATH,加上你編譯出來的 .jar。

不過以上的方法,在 make sdk 的時候,都不會被包到 SDK 裏面去,這邊我還沒找到方法。

Sensors in Android

拜請Google大神以後,找到這篇:阅读android有关sensor的源码总结,這篇寫的超級詳細,從上層到底層巨細靡遺的都描述出來了,但他並沒有描述當你有新的 sensor device 時,該怎麼做?
基本上,除了 Kernel driver 以外,還需要寫 library(sensor module),去實作 hardware/libhardware/include/hardware/sensors.h 所提出的介面,也就是上面文章最後面提到的 JNI 函數。這部份可以再參考這篇:Android Sensors Development對 HTC G1 Sensor的剖析。
對照著看,大致上就能了解了。Kernel sensor driver 被實作為一個 input system driver,當有事件時,會把 input event 發出去;sensor module 是被上層的 Sensor service 呼叫,主要是透過 _data_poll 函數詢問 input device 是否有事件並且取得數據。

NDK r4

跟 r3 比起來,這一版更方便了。
我的環境是 Ubuntu 10.04,所以我下載 for linux 的 zip 檔以後,解開放到 /opt 下,這樣就完成安裝了。
印象中,r3 Build 的方法,需要把程式放在 apps 下面,然後打 make APP=your_project 才能編譯,這一版可以允許你不放在指定目錄,你可以隨意放,像我放在 ~/NdkProjects/my_project 下,就可以直接打 /opt/android-ndk-r4/ndk-build 來進行編譯了。甚至我還意外發現,我不一定要在 ~/NdkProjects/my_project 下,只要在 ~/NdkProjects/my_project 的任意目錄下,都可以用 /opt/android-ndk-r4/ndk-build 來編譯。
此外,也可以用 /opt/android-ndk-r4/ndk-build -C ~/NdkProjects/my_project 來直接指定目錄,表示要編譯該目錄下的檔案,這樣就更容易整合到 Makefile 或 script 裡了。
gdb 現在也內建了,我還沒試過,要找時間試試看。然後也有限度的可以讀圖,不必再利用 Skia 硬幹了。
詳細的變動可以參考 /opt/android-ndk-r4/docs/CHANGES.TXT