2015年4月28日 星期二

Android External Storage

Android裡有兩種external storage, 一種是模擬出來的, 它是使用internal storage的空間, 另一種是真正的external storage, 像是sdcard與usb drive. 不管是哪一種, 它們都是使用一個service透過FUSE來管理的, 這個service叫做sdcard. 為了避免與external sdcard造成混淆, 我把它叫做sdcard service, external sdcard叫做sdcard storage. 如果你的系統支援sdcard與usb drive, 通常在rc檔裡會發現這三個service

    service sdcard /system/bin/sdcard -u 1023 -g 1023 -l /data/media /mnt/shell/emulated
        class late_start

    service fuse_sdcard1 /system/bin/sdcard -u 1023 -g 1023 -w 1023 -d /mnt/media_rw/sdcard1 /storage/sdcard1
        class late_start
        disabled
   
    service fuse_usbdrive /system/bin/sdcard -u 1023 -g 1023 -w 1023 -d /mnt/media_rw/usbdrive /storage/usbdrive
        class late_start
        disabled

這三個都是sdcard service, 使用同一個執行檔。每一個負責一個external stroage, 像是sdcard service負責模擬的external storage, fuse_sdcard1負責第一個實體的sdcard, fuse_usbdrive負責usb storage.

sdcard service
這個service的source code放在$TOP/system/core/sdcard下, 啟動這個service需要幾個參數
    -u:   service執行時所擁有的user id權限
    -g:   service執行時所擁有的groupd id權限
    -w: 擁有寫入storage空間的app id
    -d:  設定為UNIFIED derive permission.
    -l:   設定為LEGACY derive permission
    source: 原始的storage的路徑
    target: app存取storage的路徑

以emulated external storage為例, user id與groupd id都設為1023, 也就是media_rw. source是在/data/media下, 而target則是在/mnt/shell/emulated.

    service sdcard /system/bin/sdcard -u 1023 -g 1023 -l /data/media /mnt/shell/emulated

sdcard service是利用FUSE架構, 它會將/mnt/shell/emulated mount在/dev/fuse上, 所以任何在/mnt/shell/emulated上的IO都會交給/dev/fuse的driver, 這個driver會將這些IO request交給sdcard service, sdcard service再將這些request, 利用API去存取/data/media. 對/data/media的存取又會交給它底層的device driver去完成, 如下圖所示.



但並不是所有的系統一定要支援emulated external storage, 如果EMULATED_STORAGE_TARGET這個環境變數不存在, 代表emulated external storage不支援.

一般而言, 在rc file會有以下幾個變數設定
     export EXTERNAL_STORAGE /storage/emulated/legacy
     export EMULATED_STORAGE_SOURCE /mnt/shell/emulated
     export EMULATED_STORAGE_TARGET /storage/emulated
     export SECONDARY_STORAGE /storage/sdcard1

EMULATED_STORAGE_TARGET代表的是App看到的路徑, EMULATED_STORAGE_SOURCE是mount在/dev/fuse上的file system, 這也是Vold所看到的路徑. 現在問題來了, APP所存取的路徑如何對應到EMULATED_STORAGE_TARGET ? 答案就在Zygote裡. 在Zygote裡, 當一個child process產生出來之後, 它會將EMULATED_STORAGET_TARGET指的到位置mount到EMULATED_STORAGE_TARGET裡 (利用mount binding), 所以當這個App去存取/storage/emulated時, 相當於去存取/mnt/shell/emulated. 這中間還有一點值得特別注意, 在做mount的時候, 也會把user id的值放到路徑裡. 因為App只能存取啟動它的user目錄. 假設現在有兩個user, id分別是0與1. 兩個user都啟動同一個App, user 0所啟動的App只能存取/storage/emulated/0, user 1所啟動的App只能存取/storage/emulated/1. 所以, 當Zygote在做mount binding時, 只會根據user id來做mount, 因此, user 1啟動App時, Zygote只會mount /storage/emulated/1, 而不會mount /storage/emulated/0, 所以, 即使App故意更改路徑去存取/storage/emulated/0也不會成功.

在上圖中,user 0與user 1各啟動同一個App, Zygote會在user 0的process裡,將/storage/emulated/0 binding到/mnt/shell/emulated/0,Zygote也會在user 1的process裡,將/storage/emulated/1 binding到/mnt/shell/emulated/1裡。在上面有提到,/mnt/shell/emulated是mount在/dev/fuse上,sdcard service會polling /dev/fuse得知是否有任何對/mnt/shell/emulated的取存,如果有,會利用API從/data/media裡讀寫裡要求的檔案或目錄。

下面這段是Zygote裡截取出來的,大家可以做個參考

>>>
The default platform implementation of this feature leverages Linux kernel namespaces to create isolated mount tables for each Zygote-forked process, and then uses bind mounts to offer the correct user-specific primary external storage into that private namespace.
At boot, the system mounts a single emulated external storage FUSE daemon at EMULATED_STORAGE_SOURCE, which is hidden from apps. After the Zygote forks, it bind mounts the appropriate user-specific subdirectory from under the FUSE daemon to EMULATED_STORAGE_TARGET so that external storage paths resolve correctly for the app. Because an app lacks accessible mount points for other users' storage, they can only access storage for the user it was started as.
This implementation also uses the shared subtree kernel feature to propagate mount events from the default root namespace into app namespaces, which ensures that features like ASEC containers and OBB mounting continue working correctly. It does this by mounting the rootfs as shared, and then remounting it as slave after each Zygote namespace is created.
<<<


Emulated external storage的access要經過3層的轉換(/storage/emulated -> /mnt/shell/emulated -> /data/media),而真正的external storage (sdcard storage或usbdrive)則是只需要二層的轉換 (/storage/sdcard1 -> /mnt/media_rw/sdcard1與/storage/usbdrive -> /mnt/media_rw/usbdrive),external storage的轉換完全由sdservice一手包辦,不需要在Zygote做手腳。



2015年4月11日 星期六

Android Brightness

Brightness有兩種模式, 一種是App根據使用者或Window Manager Service(WMS)的要求去設定, 另一種則是透過Ambient Light Sensor(ALS)測得環境的亮度算出適當的Brightness值, 前者稱為手動模式, 後者又稱為自動模式. 在自動模式下, WMS/APP提供的Brightness不會被採用, PMS會根據ALS讀出的值, 算出要設定的Brightness, 最後再經由Light HAL設定到硬體裡.



上圖顯示的是WMS與APP如何通知PMS新的Brightness值. PMS提供兩個API給WMS與APP使用. 之所有用兩個API是因為優先權的問題. 如果WMS與APP同時要求改變Brightness, WMS的要求被會採用. 對APP而言, 除了setTemporaryScreenBrightnessSettingOverride之外, 其實還有一個方法是透過SettingProvider. 為何要提供兩個途徑供APP使用呢? 原因是setTemporaryScreenBrightnessSettingOverride是希望提供APP一個暫時改變Brightness的方式(像是提供Slider暫時改變Brightness讓user知道效果), 最後的設定是將值寫入SettingProvider裡的screen_brightness裡. PMS有設定Setting Observer, 所以當screen_brightness改變時, 它會收到通知, 並將screen_brightness的值反應到硬體裡. 我個人看不大出為何要提供APP兩個方式去設定Brightness, 感覺似乎只要是使用SettingProvider就好, 不過或許透過這兩種方式Brightness反應的時間有所不同吧.

不管WMS或APP所要求的Brightness是什麼, 在自動模式底下, PMS會根據ALS的值算出適當的Brightness, 並忽略WMS/APP的要求. 這種做法是否比較好, 見人見智. 或許應該要考慮將自動模式改成以user的選擇為基準, 再根據ALS的值做調整.

Code Flow
PMS會將WMS/APP的要求, 用一個DisplayPowerRequest的結構描述, 並交由DisplayManagerService (DMS)的DisplayPowerController來處置. DisplayPowerController的updatePowerState就是主要處理的function. 我們可以將updatePowerState分成兩個部份來看, 一個是screen的state update, 另一個則是brightness update. 這兩個state的update是同時進行的, 並以Animation的形態進行, 這樣user才不會覺得畫面的改變太突兀. 在updatePowerState裡, 會決定display state與brightness值, 並啟動這兩個animation, user就會慢慢看到screen state與brightness改變.

Screen State Animation
Screen state有以下四種, 分別代表不同的意義
  • STATE_ON
  • STATE_OFF
  • STATE_DOZE
  • STATE_SOZE_SUSPEND
monitor的狀態不是ON就是OFF, 突然由ON->OFF或OFF->ON會讓user覺得不夠smooth, 因此, CodeFade是一個Software的方式, 藉由改變畫面讓user覺得monitor漸漸關掉或漸漸打開. 所謂的screen state animation其實是利用另一個View (ColorFade)蓋住其它的Window, 然後一直改變Fade Level (0.0 -> 1.0或1.0 -> 0.0), 最後將monitor的打開或關掉. 這種漸漸改變的方式很適合利用Android Animation架構. animateScreenStateChange()就是用來啟動這個Animation.

Brightness Animation
一般來講, Brightness的值是介於0~255之間, 如果一個一個Level改變, user會覺得畫面亮度的改變不會太突兀, 但是user可能從80直接調到200, 因此, 當一個新的brightness值要改變時, AOSP也是利用animation的方式慢慢改變. animateScreenBrightness會觸發brightness animation.

DisplayPowerController算出screen state與brightness後, 就會啟動這兩個animation. 這兩個animation的執行沒有dependency, 但由於都利用Animation的架構, 所以當一個Animation進行一個frame時, 另一個Animation也會在很接近的時間處理下一個frame. 但這兩個animation也是有需要才會使用, 比方說, 如果只是brightness改變, 只要使用brightness animation即可, 不需要啟動screen state animation. 但 是當monitor變成OFF時, brightness會被設成0, 因此在screen off時, 這兩個animation同時會啟動.


Settings
Brightness有一些default值的設定, OEM可以利用這些設定預設的brightness值

config_screenBrightnessSettingMinimum
config_screenBrightnessDoze
config_screenBrightnessDim
config_screenBrightnessDark
config_animateScreenLights


APP可以將brightness的值或相關的設定寫入SettingProvider的database裡, 這個database位於
/data/data/com.android.providers.settings/databases/settings.db裡, 下面這3個跟brightness有關
  • screen_brightness - APP所設定的brightness值
  • screen_off_timeout - Monitor自動關掉的時間, 單位是milliseconds
  • screen_brightness_mode - 自動模式或手動模式
在Debug時, 我們可以利用adb讀取這些值, 下面可以讀取APP所設定的brightness值
adb shell sqlite3 /data/data/com.android.providers.settings/databases/settings.db "select value from system where name='screen_brightness';"

下面則可以寫入monitor timeout時間
adb shell sqlite3 /data/data/com.android.providers.settings/databases/settings.db "update system set value='600000' where name='screen_off_timeout';"

有時候Debug時, 我們也可以用下面的command來調整brightness的值, 而不用真的去操作系統.
    adb shell input keyevent KEYCODE_BRIGHTNESS_DOWN
    adb shell input keyevent KEYCODE_BRIGHTNESS_UP

要注意一點, 當我們使用上面兩個command時, SystemUI會將brightness模式改成手動模式.

FYI. 可以用下面的command打開monitor, 並將系統lock或unlock
    adb shell input keyevent 26 --> power key
    adb shell input keyevent 82 --> unlock
    adb shell input keyevent 82 --> lock