ストレージの扱いが混沌の極みの件
パス列挙型の環境変数でデータが取れない機種がありました(Xperia acro HDなど)。
Ver.1.03で対応しました (2012-10-23)。
環境変数に設定のない機種(の一部?)に対応しました (2012-11-4)。
vold.fstabに対応しました (2012-11-4)。
 Androidアプリを書こうとすると開発者が直面する問題だったりします。
果たしてその外部ストレージは本当にSDカードなのか、という話。
 内蔵ストレージに加えてSDカードスロットも搭載する機種を出そうという企業が、最初の時点でOSの開発元であるGoogleと協議して、きっちり仕様を決めていればここまでメチャクチャな状態にはならなかったと思うのですが…。
 なっちまったのなら仕方がない。 野良開発者としてはできる方法で対処するしかない、と。
まずは基本情報の取得。キモは二つ。
1) マウントポイントの情報はどうやって取得するか
   これはちょっとググれば情報が出てきますが、環境変数に設定されています。でもってもうちょっと詳しくググると、この環境変数は一定のガイドラインに従っているどころか、メーカー間はおろか機種間ですら統一されていないことを知り、大抵の人はメダパニ級のショックを受ける訳です。
   詰まるところ、てんでバラバラの環境変数を総当たりでチェックするしかない。しかも、今後発売される端末ではどんな奇抜な変数名が出てくるかは全くの謎と、結構とんでもない状況だったり。
2) メディアの有無(マウント状態)は?
   環境変数について調べているうちに、Android OSが載っているLinux側のmountコマンドを呼び出すという方法が使えるらしいことを見つけました。
 コマンドの結果を文字列で受けて、各行を空白文字で分割、マウントポイントを環境変数の値と比較すればなんとかなりそうな感じ。
 とまぁ、かなり頭の痛い情報ばかりでてきますが、そこを踏まえてコードに落としてみます。こんな感じ
(使う場合はサンプルプログラムのソースから取り出してください)
 使い方は
ExternalStorageChecker esc = new ExternalStorageChecker();


String[] mountPoints = esc.getStoragePaths( ExternalStorageChecker.GET_ALL_STORAGE );

ExternalStorageChecker.FsInfo fsi = esc.getExStorageInfo( "/sdcard/external_sd/test.txt" );
のように、その時点のストレージ情報を格納したインスタンスを生成し、マウントポイント一覧を取得したり、ファイルのフルパスを与えて該当するストレージの情報を取り出したり…という仕組み。
 APIで用意されているEnviromentクラスを使う時とまぁだいたい同じ感覚です。とりあえずif文の羅列みたいな造りではないので、新しい実装の端末が出てきても更新は楽かな、と。
 組み込んだのは以下の環境変数を正規表現化したもの。
 
EXTERNAL_STORAGE
EXTERNAL_STORAGE2
EXTERNAL_SD_STORAGE
EXTERNAL_USB_STORAGE
EXTERNAL_ALT_STORAGE
INTERNAL_STORAGE
EXTERNAL_STORAGE_ALL
SECOND_VOLUME_STORAGE
THIRD_VOLUME_STORAGE
SECONDARY_STORAGE
EXTERNAL_REMOVABLE_SDCARD
 結構な範囲の機種が網羅できている…ならいいんですが。
で、ストレージの情報を取得するサンプルプログラムを作ってみました(ソースはこちら起動するとストレージの情報を読み取って画面に表示します。
インストール時にネットアクセスのパーミッションが表示されますが、これはこのページにレポートを送信するための機能によるものです。
  Dummy path test というのは得られたマウントポイントの一覧からダミーのパスを生成して、getExStorageInfo() メソッドに渡しているだけなので、実際にファイルが書き込まれたりすることはありません。念のため。
 これでSDカードのパスが取得できない機種は想定外の環境変数を使っていると言うことになります。
 うちの会社はAndroidアプリの開発もやっているので、社内に検証用端末がいくつか置いてあるんですが、とても全キャリア・全機種を網羅するのは不可能なわけでして。
 「しょーがねーな、教えてやるぜ」という深い慈悲の心をお持ちの方は、アプリの[Report]ボタンをポチっとお願いします(送信するのは画面に表示されているテキストのみです)。
---- ご協力戴きました機種一覧 ----
BV9900Pro (10)
()
KFKAWI (9)
Mi 9X (5.1)
Nexus 4 (4.1.2)
Nexus 5 (4.3)
vivo X9 (6.0.1)
F-02H (7.1.1)
Nexus 4 (6.0.1)
R7005 (4.4.2)
FZ-B2D (6.0.1)
DS83X (4.4.4)
Octopus A83t (4.4.4)
TONE m15 (5.1)
ALE-L02 (5.0)
Nexus S (2.3.7)
503SH (5.1.1)
305SH (4.4.4)
Android SDK built for x86 (6.0)
CW-Hi8-super (4.4.4)
7N-R (4.1.1)
GI-I9500_TMMARS (4.1.1)
Nexus 7 (6.0)
ASUS_T00P (4.4.2)
YOGA Tablet 2-1050F (4.4.2)
A500 (3.2.1)
SHL25 (4.4.2)
null (null)
LT25i (4.3)
Nexus 7 (4.4.4)
A777 (4.0.3)
MID9742 (4.0.3)
F-12C (4.4.4)
generic (2.3.4)
Nexus 5 (4.4.4)
SO-02E (4.1.2)
SO-04E (4.2.2)
SC-04E (4.4.2)
SO-02F (4.4.2)
HTL21 (4.1.1)
CP-F03a-KK (4.4.2)
SC-01D (4.0.4)
C5303 (4.3)
Nexus 7 (4.4.2)
SC-02C (4.0.3)
Nexus 5 (4.4.2)
Galaxy Nexus (4.1.1)
SHL22 (4.2.2)
Galaxy Nexus (4.2.2)
Nexus S (2.3.6)
SCL21 (4.0.4)
SOL22 (4.2.2)
F-02E (4.1.2)
Nexus 7 (4.3)
101F (4.0.4)
SOL22 (4.1.2)
KYY21 (4.2.2)
SOL21 (4.1.2)
ME371MG (4.1.2)
101N (2.3.5)
SO-01E (4.0.4)
SC-03E (4.1.1)
IS11S (2.3.4)
C5303 (4.1.2)
KYL21 (4.0.4)
AD501 (2.2.1)
S42HW (2.3.4)
sdk (4.2.2)
SO-03D (4.0.4)
Nexus 7 (4.2.1)
EB-A71GJ (4.0.3)
IS11T (2.3.4)
sdk (2.3.3)
Camangi-Mangrove7 (3.2)
GT-I9300 (4.1.1)
SC-02E (4.1.1)
Nexus 7 (4.2)
SH-02D (2.3.5)
F-08D (2.3.5)
F-05D (2.3.5)
ISW13HT (4.0.4)
SC-05D (4.0.4)
MZ604 (4.0.3)
IS12S (4.0.4)
SBM104SH (4.0.3)
IS12F (2.3.5)
ISW11K (2.3.5)
F-10D (4.0.3)
N-05D (2.3.6)
SH-10B (1.6)
Docomo HT-03A (1.6)
---- 追記(2012-11-4)----
 戌印-INUJIRUSHI-のとむ・やむくん様よりHTCの機種、ISW13HTの情報をいただきまして。
ご協力ありがとうございます。
 しかし…本当に設定がないんですね > ISW13HT   orz
 唯一の救いはSDカードがデフォルトストレージの直下にマウントされるということ。
 一般ユーザーが触るであろうこの領域に、端末の動作を左右するようなデバイスや領域がマウントされることはまずないだろう…って読みで、
1) マウントされているにも関わらず環境変数に記述されていない
2) マウントされているのはデフォルトストレージの直下である
3) 隠しディレクトリになっていない
に該当したら、そいつはストレージであると判断して一覧に追加する処理を加えてみました。
 ただ、誤判定の可能性は否定できないので、機能はデフォルトでオフ、コンストラクタを呼び出すときにパラメータを指定して動作させる形にしてあります。
 当然ですが、マウント状態をベースに一覧に組み入れますので、SDカードがスロットに入った状態でないと検出できません。
 それにあわせて(というわけでもありませんが)マウントポイントの数と一覧を取得するメソッドの呼び出し方をちょこっと変えてあったりします。
---- 追記(2013-2-5)----
 Androidにもfstabがあるんだそうです。もっとも、設定ファイルというよりはスクリプトみたいですが。そのあたりの情報をいただきまして
 ISW13HT(環境変数にSDのパスが設定されていない機種)での情報取得に成功したなんて話を聞いて調べてみたんですが…やや微妙。
 使えないはずのUSBメモリをマウントするためのエントリが盲腸のように残っている機種があったりで、環境変数によるパス取得の補強情報として利用するのが妥当かなというのが個人的な見解です。
 とはいうものの、ストレージ情報が環境変数に記載されていない機種で、前回の追記に書いたような強引な手法に頼らずともストレージの情報をゲットする巧い方法なわけでして、『載ってる物はROMでも使え』というこの業界の諺どおり、美味しいところは戴くことにしました。
(apkはこちら、でもってソースはこちら
 使い方は以前と全く変わらず、インスタンスの生成後、パスのリストを取得するか、ファイル/ディレクトリのフルパスを渡して、それが存在するストレージの情報を取得するか。
 マウントされているストレージの情報としてファイルシステムの種類と、vold.fstabにエントリがあった場合はそのボリュームに割り当てられたラベルを取得できるようにしてあります(ラベルが定義されていなければnull)
 いろいろいじってみた結論なんですが、ストレージが内蔵なのかSDなのか、はたまたUSBメモリなのか、ユーザー側アプリで完璧に判断するのはかなり難しいんじゃないかな、と。いやまぁ、当たり前っちゃー当たり前なんですが。
 特にNexus7の作りを眺めていると、getExternalStorageDirectory()が返すパス以外のストレージはシラネ、好きにすればぁ?がgoogleセンセイのスタンスのように見えたりもします。
 「保存先の初期値はデフォルトストレージ。で、ユーザーの意向にあわせて保存先のパスを動的に変更するインターフェイスを用意する」というのがandroidプログラミングの御作法なのかなぁ、と。
ほ~んと、あなたのお側に這い寄る混沌なのねん。