2013/02/06

Android のマルチユーザ環境で ユーザID を取得する

Android 4.2 から本格的にマルチユーザ環境が使えるようになりました。
ユーザとしては喜ばしい機能ですが、開発者としては新たな問題の種になりそうで辛いところです。

出来る事ならば、ユーザの違いなど意識しない作りにしたいところですが、どうしても意識しないといけない場面はありそう。
例えば、「SDカードにデータを保存せざるを得ないが、ユーザは分けたい」場合とか。

そんな時に、どのユーザで実行されているのかということをアプリが知る方法がないか調べてみました。
2013/2/24 追記
「SDカードにデータを保存せざるを得ないが、ユーザは分けたい」場合とか。と書いたのですが、SDカードにデータを保存するときもマルチユーザを意識する必要はありませんでした。
穀風: マルチユーザ環境でSDカードのディレクトリをユーザ毎に分ける

ユーザ識別のための ID の種類

android.os.UserManagerandroid.os.UserHandle あたりを読んでみると、ユーザ(アプリ)を識別するのに、以下のような ID が用意されていることがわかりました。
UIDいわゆる Linux の UID
アプリ毎に振られる
UserIdUID / 100000
シングルユーザ環境の場合は0
AppIdUID % 100000
同じアプリの場合、ユーザが異なっても同じ
SerialNumberForUserユーザ固有のID
ユーザを削除後、追加しても同じ値が使われることはない
注: あくまで、コードの中で使用されているのを読み解いたもので、正式にこの名前が定義されているわけではありません

Android は アプリ毎に UID が振られます。
その仕組みを利用して、下位5桁を AppId, 上位を UserId として使用しているわけです。

ちなみに、各ユーザ毎に用意されるアプリケーションデータディレクトリは、UserId で管理されます。

どの ID を識別に使用すべきか?

現在、私が参照しているコードは AOSPAndroid 4.2.1_r1.2 ですが、 UserId を取得する UserHandle.myUserId() や AppId を取得する UserHandle.getAppId()@hide属性がついています。
つまり、正規の手段では呼ぶことが出来ません。
どうしても UserId が欲しい場合は、自分で uid を 100000 で割るか、リフレクションを使用するしかないようです。
しかし、そういう実装は新たな不具合の原因となるので、極力避けるべきでしょう。

それに対し、UserManager.getSerialNumberForUser()@hide属性は付いていませんし、その javadoc には以下のように書いてあります。

Return the serial number for a user. This is a device-unique number assigned to that user; if the user is deleted and then a new user created, the new users will not be given the same serial number.

(意訳)
ユーザのシリアルナンバーを返します。 ユーザに紐付けられたデバイスユニークな番号です; もし、ユーザが削除されて、その後、新しいユーザが作成されても、同じシリアルナンバーが与えられることはありません。

Android の思想としては、「ユーザの識別には SerialNumberForUser を使用する」というのが正しそうです。

SerialNumberForUser を取得する方法

以下のコードで SerialNumberForUser を取得することが出来ます。
// 4.2 未満では動作しません
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
    UserManager userManager = (UserManager) getSystemService(USER_SERVICE);
    UserHandle handle = Process.myUserHandle();
    long serialNumberForUser = userManager.getSerialNumberForUser(handle);
}

動作させてみる

Nexus7 上に 所有者+3人の新規ユーザを作成し、以下のコードを各ユーザで実行してみました。
UserManager userManager = (UserManager) getSystemService(USER_SERVICE);
UserHandle handle = Process.myUserHandle();

Log.d(TAG, "uid          : " + Process.myUid());
Log.d(TAG, "dir          : " + getFilesDir());
Log.d(TAG, "serialNumber : " + userManager.getSerialNumberForUser(handle));

以下がその結果です。
所有者
uid          : 10092
dir          : /data/data/com.kokufu.android.test.multipleusertest/files
serialNumber : 0

ユーザ1
uid          : 1010092
dir          : /data/user/10/com.kokufu.android.test.multipleusertest/files
serialNumber : 1

ユーザ2
uid          : 1110092
dir          : /data/user/11/com.kokufu.android.test.multipleusertest/files
serialNumber : 2

ユーザ3
uid          : 1210092
dir          : /data/user/12/com.kokufu.android.test.multipleusertest/files
serialNumber : 3

uid の下5桁が全ユーザで 10092 になっています。
そして、残りの上位2桁が UserId です。
アプリケーションディレクトリの識別子として使われれているのがわかります。

0 件のコメント: