2016年10月16日日曜日

Android 6.0 Marshmallow の SD カード書込みを改善する

●やりたいこと
Android 6.0 Marshmallow で
1. どんなアプリでも外部 SD カード (以下,単に SD カード) に書き込めるようにする
2. 内蔵ストレージのデータを自分で選択して SD カードに逃がす

●やったことを一言で言うと
・内蔵ストレージのパスに SD カードを mount --rbind した (要 root)
アプリから見えるパスでやってもダメなので,実体のパスでやるのがミソ.

●動機
Android 4.4 KitKat では,platform.xml を書き換えることで,どんなアプリでも SD カードに書き込めるようにできたが,Marshmallow ではその方法が使えなくなった.ちゃんとしたアプリならきちんと SD カードの書込み権限を Get するので問題ないんだけど,例えば Google フォトなんかは SD カードライト権限が無いらしく,Web 上で削除した写真が SD カード上にあると,連動して端末の写真を削除してくれない.

代わりに Marshmallow では「SD カードの内部ストレージ化」なる機能が追加されたが,これがまた中途半端な機能で,内部ストレージがスパンボリュームのように拡張されるのかと思ったらそうではなく,
・SD カードに置かれるファイルは Android が決めるので,自分で選べない
・SD カードが暗号化されるので,SD カードを取り出しても PC 等で読めない
と,自分にとっては使い物にならない.

●やったこと詳細
以下の作業は全部 root 状態の adb shell で行う.(行頭が # が入力コマンド)
また Redmi note 3 pro, Cyanogenmod 13 でやったので,いろいと機種依存してるかも.

1. SD カードの実体のマウントポイントを調べる
SD カードのパス名に 86FC-619D とかって 16進数が入っていると思うけど,その名前でマウントされているパスのうち,fuse を除いたものが実体.
# mount | grep 86FC-619D | grep -v fuse
/dev/block/vold/public:179_65 on /mnt/media_rw/86FC-619D type vfat (rw,dirsync,nosuid,nodev,noexec,relatime,uid=1023,gid=1023,fmask=0007,dmask=0007,allow_utime=0020,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro)

2. 内部ストレージのパスが SD カードを指すようにし,アプリからは内部ストレージに書き込むことで,SD カードに書き込むようにする.
内部ストレージの実体は /data/media/0 でファイルシステムは ext4 なので,シンボリックリンクが使えるかと思ったが,アプリから見える内部ストレージのパスは,シンボリックリンクを解釈しないらしく,要はシンボリックリンクが使えない.

なので,mount --rbind を使う.
以下は内部ストレージの DCIM (写真 dir) を SD カードにするコマンド例.
# mount --rbind /mnt/media_rw/86FC-619D/DCIM /data/media/0/DCIM
こうすることによって,内部ストレージの任意のフォルダを SD カードにすり替えることができる.

3. スマフォ起動時に上記のマウントを自動的に行う
あとは,上記のことをスクリプト化して端末起動時に実行すれば良い.
自分は↓のようにして,SD カードと内蔵ストレージの両方に同じ dir がある場合 (ただし Android/ は除く),SD カードのパスを内蔵ストレージにマウントするようにした.
#!/system/bin/sh

sdpath=/mnt/media_rw/86FC-619D
storage=/data/media/0
export PATH=/system/xbin:$PATH

cd $sdpath
for dir in *; do
    if [ $dir != Android ] && [ -d $dir ] && [ -d $storage/$dir ]; then
        mount --rbind $sdpath/$dir $storage/$dir
    fi
done

起動時にスクリプトを実行する方法はこれとかが正統な方法だとは思うけど,自分はむやみに /system 以下を書き換えるのが嫌だったので,自分は Automate でフローを組んだ.
これに限らず,起動時にスクリプトを実行するアプリは色々とあると思うんでお好みで.

以上の方法を応用すれば,通常ライト権限がない /system 以下とかも本体ストレージを変更することなく書き換え放題できることになる.
ぱっと思いつくのは,カメラシャッター音の *.ogg を消してしまうとかが簡単にできる.