2012/01/30

Androidのセキュリティ:パーミッションの保護レベル



Androidのセキュリティを考える上でパーミッションは重要な要素です。
アプリのデータにアクセスするパーミッションや、アプリを実行するパーミッションなど、
開発者は様々なパーミッションを作成することができます。
同時にパーミッションはアプリがもつ権限の強さを示す指標でもあります。
リスクのあるパーミッションをアプリが使用するにはユーザの許可を得る必要があります。
(インストール確認画面で要求するパーミッションが表示されます)
# 様々なパーミッションを要求するアプリを不審に思うユーザもいるようです。

パーミッションには、Android標準で用意されているインターネットへのアクセスや電話帳
データのリード/ライトなど様々ですが、例えば「自アプリのデータにアクセスするパー
ミッション」「自アプリを起動するためのパーミッション」等を独自に作成することも可能です。

独自に作成したパーミッションには保護レベルを設定することができます。
保護レベルはパーミッションの定義側ではなく、使用側を制御する仕組みです。

●保護レベルの種類
・normal
 低リスクのパーミッションであることを表します。
 インストール確認画面でも[すべて表示]を選択しないと表示されません。

・dangerous
 リスクあるパーミッションであることを表します。
 インストール確認画面でユーザに許可を求める場合に指定します。

・signature
 同じ署名を持つアプリにのみ、パーミッションの使用を許可します。
 インストール確認画面には表示されません。

・signatureOrSystem
 サードパティアプリでは使用できない権限です。
 主にシステムアプリ間で使用される保護レベルです。

データベースにアクセスできるアプリを制限したい場合は保護レベルsignatureを指定します。
パーミッションの保護レベルはpermission要素のandroid:protectionLevel属性に指定します。

参考:http://developer.android.com/guide/topics/manifest/permission-element.html
参考:http://developer.android.com/guide/topics/manifest/permission-element.html#plevel

以上です。


2012/01/29

Androidのセキュリティ:SDカードへの保存とリスク


SDカードへデータを保存する場合には公開範囲に注意する必要があります。
adb shellより/mnt/sdcardでls -lコマンドを実行するとわかりますが、
外部記憶データファイルの所有者とグループはどのアプリが保存しても
systemとsdcard_rwになります。

外部記憶データへのアクセスはすべてのアプリケーションがアクセス可能です。
さらに、SDカードへの書き込みはWRITE_EXTERNAL_STORAGEパーミッションが
必要ですが、読み込みにパーミッションは必要ありません。

-SDカードのアクセス許可情報-
d---rwxr-x system   sdcard_rw          2011-12-12 13:26 Alarms
d---rwxr-x system   sdcard_rw          2012-01-10 12:12 DCIM

上記から、外部記憶データは抜き取られることを前提に考えて、個人情報や
秘匿が必要なデータはアプリデータ領域に保存するか、暗号化する等の対策
が必要です。

以上です。
2012/01/28

Android:Listのパフォーマンスを向上させる技


ListViewは情報を一覧するのに便利なViewですが、表示する情報量が多くなると
パフォーマンス面で心配が出てきます。

Android標準にはContactsアプリの通話履歴でListViewが使用されています。
通話履歴は最大500件保存でき、表示の際には電話番号と電話帳登録名を紐付け
て最新の情報を表示しています。
(参考ソースはcom.android.contacts.RecentCallsListActivity)(Android3.0以前)

最大件数が500件であることと、全てのデータを電話帳と紐付ける必要がある
ことを考えても、ここがボトルネックになる可能性があることは容易に想像で
きます。

通話履歴では、パフォーマンスを向上させるために様々なテクニックを使用し
ています。
今回はAndroid標準のコードを通じて、パフォーマンス向上のテクニックを
見ていきます。

●データベース
通話履歴のDBには、電話帳と紐付けた結果(電話帳登録名)をname_cacheとして
DBに保持しています。
通話履歴一覧画面では、このname_cacheをListViewに表示します。

このキャッシュは常に最新に保たれているとは限りません。
そのため、通話履歴を立ち上げた直後に表示されるname_cacheの値(電話帳登録名)
は古い情報である可能性があります。
情報が古い場合は更新する必要があります。

●通話履歴DB更新処理
name_cacheを最新にするためには、電話帳DBに最新情報を問い合わせて、
name_cacheと突き合わせ、変更のある場合はname_cacheを更新します。
※ListViewのAdapterは通話履歴DBを参照するCursorAdapterのため、
 更新されたDBの情報が反映されます。

電話帳DBとの紐付け→更新はWorkerThreadパターンが採用されています。
メインスレッドはClientThreadとして動作し、ListAdapterのgetViewで通話履歴DB
の値がChannelに蓄積されリクエストとして処理されます。
電話帳DBへの問い合わせと更新確認はバックグラウンドで動作するWorkerThread
が担当します。
WorkerThreadはClientThreadからのリクエストを確認して、電話帳DBとの
差分がある場合は通話履歴DBを更新します。

以上です。

2012/01/27

AndroidでUUIDを作成する方法


世界中でただ1つの重複しない値、Universally Unique Identifier(UUID)
を作成する方法です。

Webサーバと連携するようなアプリケーションを作成している場合、サーバ側で
利用するユーザを特定したいケースがよくあります。

ユーザを特定するために採用される方法には、ユーザIDとパスワードによるアカウ
ント管理等がメジャーですが、ユーザにとってはアカウントを登録しなければならず
手間がかかります。
ユーザを特定したいだけで、アカウントを作成するまでもない場合にUUIDは効果
的です。

下記作成方法です。

String id = UUID.randomUUID().toString();

参考:http://android-developers.blogspot.com/2011/03/identifying-app-installations.html

ここで得たUUIDをファイルに保存する等して毎回使い回せば、サービスを利用
するユーザを特定することができるでしょう。
※ただし、アプリケーションが一度アンインストールされるとアプリデータ(UUID)
 も同時に削除されてしまいます。
 再インストールしたとしても次回生成されるUUIDは前回とは異なるものとなります。
 UUIDを採用する場合、このケースを許容できるかできないかは重要です。

ユーザを特定するという意味においては下記の値も利用できそうですが、それ
ぞれ一長一短があり、どれを採用するかは短所を把握しておく必要があります。

●IMEI(International Mobile Equipment Identifier)
携帯電話1つ1つに割り当てられた端末識別番号になります。
端末を特定、識別することに向いています。
注意すべきは「個人を特定するのには向いていない」ということです。
・1人で複数台の端末を持っている場合がある
・端末が他人に譲渡される可能性(中古売買等)
・携帯電話の買い替えでIMEIは変化する

●IMSI(International Mobile Subscriber Identify)
SIMカードに割り当てられる国際移動体加入者識別番号です。

●ICCID(IC Card ID)
SIMカードのシリアル番号です。
SIMカード関係の番号についても個人を特定するには向いていません。
・SIMカードを挿さないで端末を使用していた場合
・SIMカードは交換可能

●MACアドレス(Media Access Control Address)
ネットワーク機器に割り当てられたアドレスです。
端末ごとに一意な値となりますが、端末のネットワーク環境によってはMAC
アドレスを返さないケースがあります。

●ANDROID_ID
Android端末ごとに一意な値です。端末をファクトリリセットすると値が更新
されます。
Android2.2以前は一意性が確保されていません。
また、一部のメーカー製端末では全て同じANDROID_IDを返すというバグがあります。

他にも電話番号も候補として挙げられます。

上記を踏まえて、プログラムが対象の何を特定したいのか(端末なのか個人なのか?)
を明確にすることが大切です。
また、個人情報に関わるものもあるのでセキュリティとプライバシー問題には
注意しましょう。

以上です。

2012/01/22

Android:確実にファイル保存する方法

ファイルへの書き込みには一般的にflushメソッドを使用しますが、flushメソッドは実際
にファイルシステムへ書き込むわけではありません。
ストリームのフラッシュはOSのファイル出力バッファに書き込まれます。
そのため、実ファイル上に書き込まれている保証はこの時点ではありません。

Javaにはアプリからの書き込みをファイルシステムと同期させるための仕組みが用意され
ています。
ファイルシステムとの同期にはFileDescriptorクラスのsyncメソッドを使用します。
FileDescriptorはファイルストリームのgetFDメソッドから取得することが可能です。
参考:http://java.sun.com/javase/ja/6/docs/ja/api/java/io/FileOutputStream.html#getFD()

コード例
FileOutputStream out = new FileOutputStream(new File(foo));
out.write(bar);
out.flush();
out.getFD().sync();
out.close();
このテクニックはFileUtils.copyFileメソッドでも使用されています。
※ファイルシステムとの同期はパフォーマンスを大きく低下させる恐れがあります。
 そのため、頻繁にsyncメソッドを呼ぶのは避けるべきです。

以上です。
2012/01/20

アプリの無効/有効をコマンドで切り替える方法


Android4.0より搭載されたプリインアプリの無効化について。

プリインアプリ無効化時にどのような動作になるのか試験を行う場合、その都度
設定アプリに遷移して1つずつ無効化/有効化するのは非常に面倒です。
そこで、adb shellのpmコマンドを使用してコマンドラインからアプリの無効/
有効を切り替えたいと思います。

adb shellより、pmコマンドを実行するとオプション指定一覧が表示されます。
今回使用するのは下記コマンドです。

デバイス上のパッケージ一覧を表示します。
# pm list packages

オプション:
pm list packages: prints all packages, optionally only
   those whose package name contains the text in FILTER.  Options:
     -f: see their associated file.
     -d: filter to only show disbled packages.
     -e: filter to only show enabled packages.
   -s: filter to only show system packages.
     -3: filter to only show third party packages.
     -u: also include uninstalled packages.

よく使用するのは -e や -d あたりでしょうか。

有効中のパッケージ一覧を取得
# pm list packages -e

無効中のパッケージ一覧を取得
# pm list packages -d


次にpmコマンドから指定のパッケージの有効/無効を切り替える方法です。
これにはpm disable-userあるいはenableを使用します。

指定パッケージを無効化(ブラウザ無効化の例)
# pm disable-user com.android.browser

指定パッケージを有効化(ブラウザ有効化の例)
# pm enable com.android.browser

pm disableを実行すると設定アプリのアプリ一覧には表示されなくなってしま
います。
特に理由がない場合はdisable-userを指定するのが良いでしょう。

また、設定アプリからでは無効化できないアプリでもpmコマンドからはそれが
可能です。
設定アプリはDevicePolicyManager.packageHasActiveAdminsの値を参照して
無効化ボタンをdisableに設定しているにすぎません。
# このあたりはcom.android.settings.applications.InstalledAppDetails
のinitUninstallButtonsメソッドを確認した方がわかり易いと思います。

設定アプリのアプリ一覧で表示されているアプリのパッケージ名を知りたい場
合は、無効化時に出力されるログ「Force stopping package <Package名>」
から得ることができます。


ちなみに、エミュレータでは連携先アプリがいなくても「連携先アプリがない」
旨のToastが表示されるケースがあります。
これは開発用アプリ Fallbackアプリ が存在しているためです。
FallbackアプリがIntentを拾うのでActivityNotFoundExceptionが発生しません。

より実機に近い状態で試験したい場合はFallbackアプリを無効化すると良いで
しょう。
ラベル:Fallback  パッケージ:com.android.fallback

以上です。

2012/01/17

複数のデバイスやエミュレータが接続されている場合のadb接続


複数のデバイスを接続している場合や、デバイスとエミュレータを同時に接続
している場合、下記のコマンドで個別のデバイスへの接続が可能です。

【デバイスとエミュレータが同時に接続されている場合】
エミュレータに接続
# adb -e shell

デバイスに接続
# adb -d shell

【複数のデバイスが同時に接続されている場合】
# adb -s <serialNumber> shell

serialNumberはadb devicesで得られるデバイスのシリアル番号を指定します。
-sオプションはエミュレータに対しても有効です。

以上です。

2012/01/14

AndroidでリードオンリーなSDをマウントさせる方法

AndroidにはSDカードのマウント状態を調べるEnvironment.getExternalStorageState()
が用意されています。
マウント状態の中にはSDカードが読み取り専用であった場合に返される
MEDIA_MOUNTED_READ_ONLYがあります。
参考:Android Developer

SDがリードオンリーであることは通常ありません。
しかし、非常に稀ですがメカニカルライトプロテクトスイッチを備えたSDカード
(SDカード自体にスイッチがあり、これをONにすることで読み取り専用とするもの)
や、ソフトウェアのバイナリをSDで提供しているようなケースでも読み取り専用
として提供されます。

読み取り専用のSDカードは入手するのが困難です。
ここではadb shellのコマンドを使用してSDを読み取り専用としてマウントす
る方法を紹介します。

まずはmountコマンドを使用して/mnt/sdcardの状態を調べます。
# mount

複数の結果が返されると思いますが、SDカードにあたる項目を探してください。
手元の環境では下記が取得できました。
(以降はこの値をもとにコマンドを指定していきます)

/dev/block/vold/179:0 /mnt/sdcard vfat rw,

この値は後で指定する際に使用するので覚えておきます。

次にSDカードをアンマウントします。
# umount /mnt/sdcard

これでSDカードのマウントが解除されます。
(Galaxy Nexusのような内蔵SD型の場合はアンマウントできないかも...)

次にSDカードをリードオンリー(ro)として再マウントします。
先ほど取得した値を参考に下記コマンドを発行します。
# mount -t vfat -o ro /dev/block/vold/179:0 /mnt/sdcard

vfatはファイルシステムタイプ、-oはオプション指定でリードオンリー(ro)
を指定します。
続いてdeviceを指定し、最後にパスを指定します。

これで再度mountコマンドを発行すると下記の結果が得られると思います。

/dev/block/vold/179:0 /mnt/sdcard vfat ro,

SDカードがリードオンリーになっていることがわかります。
この状態で動作確認することでSDがリードオンリー状態で試験することが可能です。
なお、この方法ではメディアMOUNTに関するブロードキャストは通知されません。
MOUNTブロードキャストを試験したい場合はam broadcastを使用して自分で発行
するなどの工夫が必要です。

以上です。

アプリ内の全Activityのライフサイクルコールバックメソッドを検知する方法

ApplicationクラスにはActivityLifecycleCallbacksというインタフェースが
用意されています。
参考:Android Developerサイト

これを利用することでアプリケーション内にあるActivityのライフサイクル
メソッドのコールバックを検知することができます。

下記サンプルコートです。

public class TestApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifeCycleListener());
    }
   
    private static class ActivityLifeCycleListener implements ActivityLifecycleCallbacks {
        @Override
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            android.util.Log.e("yuki", "yuki call onCreated:" + activity);
        }
        @Override
        public void onActivityStarted(Activity activity) {}
        @Override
        public void onActivityResumed(Activity activity) {
            android.util.Log.e("yuki", "yuki call onResumed:" + activity);
        }
        @Override
        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
        @Override
        public void onActivityPaused(Activity activity) {
            android.util.Log.e("yuki", "yuki call onPaused:" + activity);}
        @Override
        public void onActivityStopped(Activity activity) {}
        @Override
        public void onActivityDestroyed(Activity activity) {}
    }
}
このアプリに内包されているTestApplication2Activityを起動→終了した場合
のログは下記になります。

E/yuki(687): yuki call onCreated:test.yuki.TestApplication2Activity@4136e050
E/yuki(687): yuki call onResumed:test.yuki.TestApplication2Activity@4136e050
E/yuki(687): yuki call onPaused:test.yuki.TestApplication2Activity@4136e050

コールバックで渡されてくる引数ActivityからどのActivityかを判断するのは
instanceofやマーカーインターフェースを利用て判定できるでしょう。
またActivityのライフサイクルで共通の処理を実装する場合や、Activityを
一元管理したい場合にも便利です。

以上です。

HierarchyViewerでパフォーマンスを向上する手がかりを得る


アプリケーションのUI構築速度は非常に重要です。
Viewのレンダリングに時間がかかり、UI構築に手間取ることはレスポンスタイ
ムの遅れに繋がります。

AndroidのSDKにはHieralchyViewerというツールが同梱されています。
このツールを使用することで、レンダリングに時間をかけているViewを特定し
アプリのパフォーマンス向上に役立てることができます。
HieralchyViewerの使い方は他サイト様でも扱われている内容なので割愛します。

HieralchyViewerでは下図のような結果が得られます。


Viewアイテム内に表示される三色の丸型アイコンには下記の意味があります。

 左:
  Measure値です。Measure値はViewのサイズ決定にかかる時間です。

 中央:
  Layout値です。Layout値はViewの配置にかかる時間です。

 右:
  Draw値です。Draw値はViewの描画にかかる時間です。


それぞれの丸型アイコンの色については下記の意味合いを持ちます。
ここでの「同階層」とは上図の赤枠で囲まれた範囲を指します。

 緑:
  解析されたViewツリー内の同階層にある全てのViewオブジェクトの中で、
  対象のViewに対するレンダリング所要時間が50%未満である。
  このViewは、同階層にある全てのViewオブジェクトよりレンダリング速度が
  50%以上高速であることになります。

 黄:
  解析されたViewツリー内の同階層にある全てのViewオブジェクトの中で、
  対象のViewに対するレンダリング所要時間が50%以上である。
  このViewは、同階層にある全てのViewオブジェクトよりレンダリング速度が
  50%以上低速であることになります。

 赤:
  解析されたViewツリー内の同階層にある全てのViewオブジェクトの中で、
  対象のViewに対するレンダリング所要時間が最遅である。
  このViewは、同階層にある全てのViewオブジェクトの中でレンダリング速度
  が最も遅いことになります。

HieralchyViewerで問題点が見つかったら改善するかの判断をします。
改善にあたっては、アプリのレイアウトとレイアウト階層の最適化をサポート
してくれるツール layoutopt が役に立ちます。

参考:HieralchyViewer
http://developer.android.com/guide/developing/debugging/debugging-ui.html

参考:layoutopt
http://www.techdoctranslator.com/android/developing/tools/layoutopt

【備考】
Viewの表示設定としてvisible, invisibleとgoneがあります。
invisibleとgoneの違いは、不可視なViewが自身の描画領域を持っているかどうかです。
どちらも描画されないため、先述のdrawは実行されません。
invisibleは描画領域を持つのでmeasureとlayoutが実行されます。
goneは描画領域を持たないのでいずれも実行されません。
つまりは、invisibleよりgoneの方がレンダリングにかかる時間がより短くなります。

以上です。

2012/01/11

プリインアプリの有効・無効を判定する方法


Android4.0より、プリインアプリの有効/無効をユーザがある程度指定できる
ようになりました。

この機能により、「プリインアプリは必ず存在する」という前提が崩れました。
つまりはプリインアプリと連携しようとしても相手先が存在せず、ActivityNotFound
例外等が発生するケースがあり得ます。
# ブラウザやSMSアプリとの連携など

プリインアプリの無効化はAndroid標準で[設定]→[アプリケーション]から
プリインアプリを選択し[無効にする]を選択することで実行できます。

プリインアプリが無効化されているか否かは下記のコードより判断できます。

int state =
    getPackageManager().getApplicationEnabledSetting("com.android.browser");
android.util.Log.e("yuki" , "yuki state=" + state);

有効化中のstateは0(COMPONENT_ENABLED_STATE_DEFAULT)が、
無効化中のstateは2(COMPONENT_ENABLED_STATE_DISABLED)が返されます。
それぞれの値はDeveloperサイトを参照下さい。
参考:Developerサイト

以上です。

2012/01/10

StrictMode:画面遷移の度に表示される赤枠を抑止する方法

GingerBreadから追加された機能StrictMode(厳格モード)について。

Android4.0(3.xから?)のエミュレータ等でStrictMode機能をONにしていなくても
StrictMode機能がONの状態となります。
この状態でUIスレッド上でDBにアクセスする等推奨ルールに違反すると画面
全体に赤枠が点滅表示されます。


設定アプリの[開発者向けオプション]→[厳格モードを有効にする]ON/OFFに
関わらず、engビルドされた環境では強制的にStrictModeがONになります。


userビルドの場合は、アプリが明示的にStrictModeをONに設定しない限り
StrictModeはOFFとなり赤枠点滅による通知は行われません。

しかし、engビルド環境と言えど赤枠が表示されるのは少々邪魔なものです。
赤枠表示をOFFにしたい場合は下記の方法が有効です。

【Dev toolsを使用した方法】
1.Dev Tool→StrictMode visual indicator:off に設定する
2.プロセスを再起動する

【システムプロパティを設定する方法】
1.adb shell setprop persist.sys.strictmode.visual 0
2.プロセスを再起動する

StrictModeの設定値を変更した後は、赤枠を表示したくないプロセスの再起動
が必要です。

ちなみに、StrictModeのログを抑止したい場合はadb logcatコマンドで
adb logcat StrictMode:S *:V
とします。

以上です。

2012/01/06

MENUキー押下でonPrepareOptionsMenuが呼ばれない問題


Honeycomb以降、ActivityのonCreate時にonCreateOptionsMenuとonPrepareOptionsMenu
が呼ばれるようになりました。
# アクションアイテム実装による変更かな?

注意するべきは、Activity起動後のMENUキー押下1回目にonPrepareOptionsMenuが
呼ばれなくなったということです。
# 2回目以降のMENUキー押下時はonPrepareOptionsMenuが呼ばれます。
もしonPrepareOptionsMenuでメニュー項目を動的に変更する、あるいは有効/無効を切
り変えるようなアプリは要注意です。

例えば、チェックボックスのON/OFFでメニュー項目の状態を変化させるために、
onPrepareOptionsMenuでチェックボックスの状態をチェックしてメニュー項目の状態を
動的に変更するような場合です。
1回目のMENUキー押下時はonPrepareOptionsMenuが実行されないため、正しくチェ
ックボックスの状態に合わせてメニュー項目を更新することができません。

解決方法の一つに、1回目のMENUキー押下時にも強制的にonPrepareOptionsMenu
を実行させて、従来通りのシーケンスを再現する方法があります。

1回目のMENUキー押下時にonPrepareOptionsMenuが実行されない原因は、Activity
のonCreate時にメニューが準備(onPrepareOptionsMenu)されることで、1回目のMENU
キー押下時の準備処理は不要と判断されるためです。

そこで、ActivityのonKeyDown処理で1回目のMENUキー押下時にオプションメニュー
を一度破棄します。
オプションメニューを破棄するにはinvalidateOptionsMenuを使用します。
オプションメニューを破棄することで、再度オプションメニューを表示しようとする時、
onCreateOptionsMenu⇒onPrepareOptionsMenuと呼ばれるようになります。

private boolean oneTimeFlag = true;
public boolean onKeyDown(int keyCode, KeyEvent event) {
  if (oneTimeFlag && keyCode == KeyEvent.KEYCODE_MENU) {
    invalidateOptionsMenu();
    oneTimeFlag = false;
  }
}

参考:Android Developer

以上です。