買ってよかったAndroid関連電子書籍まとめ
Androidの技術書、どうせ買うなら電子書籍がいいよね!と電子書籍をあちこちで買ったり買い直したりしてるので知見を共有します。
どこで買うべきか
電子書籍で技術書を購入する際、なるべく制限の少ないものを探すことが重要です。 何も考えずに買ってしまうと、以下の様な制限に引っかかってしまいます。
技術書に限っては閲覧制限のかかっていないPDFを入手できることが最善だと思います。
達人出版会
閲覧制限のないPDFを入手することができます。 ここで購入できる書籍はなるべくここで購入しています。 領収書が発行されないので、必要な人は注意してください。 http://tatsu-zine.com/
技術評論社
閲覧制限のないPDFを入手することができます。 Android本もそこそこあって良いです。 AdobeReaderでコピーすると文字化けするPDFがあるので注意。 https://gihyo.jp/dp
O'Reilly Japan Ebook Store
閲覧制限のないPDFを入手することができます。 ただし、Android本があまりありません…。 1年以内に発売されたAndroid本が「実践 Android Developer Tools」しかない…。 http://www.oreilly.co.jp/ebook/
Impress Japan
あまり使ったことがないですが、書籍によって提供されるフォーマットがまちまちです。 PDFもコピペ制限があったりなかったりで、大抵の場合買うまでわかりません。 http://www.impressjapan.jp/
Google Play ブックス
PCでもブラウザで読めますが、文字列コピーと印刷ができません。 ソースコードを検索した際の精度もいまいち良くない気がします。 https://play.google.com/store/books
Amazon Kindle
Kindle(jp)はPCで読めないという致命的な欠点があるので、技術書の購入には向きません。 http://www.amazon.co.jp/
買ってよかった電子書籍
Smashing Android UI レスポンシブUIとデザインパターン
Androidのデザインについて深く考察した上で書かれた良著。 特に「第21章 UIデザインのアンチパターン」は「なぜ」Androidではそのデザインがだめなのか?ということを解決策付きで解説しており、Androidデザインの基本を理解するのに最適です。 サンプルアプリの一部挙動が若干分かり難いのが難点。 達人出版会で閲覧制限のないPDFが買えます。 http://tatsu-zine.com/books/smashing-android-ui
良いAndroidアプリを作る139の鉄則
タイトルの通り、良いAndroidアプリを作るためのノウハウがまとめられた本。 内容もデザインからテスト、リリースと多岐にわたっているので自分が苦手な部分への取っ掛かりをつかむには最適です。 また、コラムの内容も充実していて非常に参考になりました。 コンテンツ数が多い文、個々の内容が若干物足りない感じもしますが、そこは自分で調べればいいかな…。 技術評論社のPDFはAdobeReaderでコピーすると文字化けしますが、Chromeでコピーするとなぜか文字化けしません。 技術評論社で閲覧制限のないPDFが買えます。 https://gihyo.jp/dp/ebook/2014/978-4-7741-6607-0
Effective Android
デザインからNDKまで幅広く取り扱っているので、知識の幅を広げるのに非常に良い本。 Androidマスターしたわ!と思ったらこの本でボッコボコにされるといいと思います。 あまり細かい解説がなかったりするので、初級者向けではないです。 達人出版会で閲覧制限のないPDFが買えます。 http://tatsu-zine.com/books/effective-android
Master of Fragment (Android Professional Developerシリーズ)
Fragmentなんとなく使えるけどこれでいいんだっけ…くらいのときに読むと良い本。 よくある疑問点がまとめられていて非常に助かりました。 達人出版会で閲覧制限のないPDFが買えます。 http://tatsu-zine.com/books/master-of-fragments
Android Pattern Cookbook マーケットで埋もれないための差別化戦略
Kiatkat準拠のデザイン本。 解説が細かく、サンプルソースも読みやすいです。 Impress Japan版はコピペ制限がかかっていたので、達人出版会で買い直しました。 達人出版会で閲覧制限のないPDFが買えます。 http://tatsu-zine.com/books/android-pattern-cookbook
Android Security 安全なアプリケーションを作成するために
Androidアプリのセキュリティについて書かれた稀有な本。 2年以上前の本なので、後述の「Android アプリのセキュア設計・セキュアコーディングガイド」と読み合わせると良い感じ。 達人出版会で閲覧制限のないPDFが買えます。 http://tatsu-zine.com/books/androidsec
Androidアプリのセキュア設計・セキュアコーディングガイド
Androidアプリのセキュリティについて書かれた資料。しかも無料。 AccountManagerとSyncAdapterを学習する上で滅茶苦茶お世話なりました。しかも無料。 ソースコード内にわかりやすくポイントが書いてあり、読みやすいです。しかも無料。 無料でPDFが公開されています http://www.jssec.org/report/securecoding.html
まとめ
達人出版会の宣伝みたいになっていますが、汎用性を考えるとPDF最強になってしまうので仕方ないですね。 技術評論社もかなり充実してきたので今後が楽しみです! O'Reillyはもっと電子書籍化がんばってほしい…。
設定画面のインテントアクションまとめ
はじめに
Androidでは、アプリが必要な権限を持っていれば、アプリから端末の設定(一部)を変更することができます。 この仕組はアプリが必要とする機能を確実に使用するためには有用ですが、インストール時に設定変更権限が必要な旨がダイアログ表示されてしまい、ユーザから敬遠される恐れもあります。 また、端末設定の変更ロジックに問題があった場合、影響範囲がアプリだけに収まらなくなります。
Androidでは上記のようなアプリから直接設定を変更する方法の他に、設定アプリの各画面へ簡単にアクセスする方法も提供されています。
android.provider.Settings
クラスにはAndroidの各機能設定画面を呼び出すIntent
のAction
が定義されており、遷移先の設定画面でユーザ自身に必要な設定を行わせることができます。
この場合には自アプリには権限が必要ないため、インストール時の敬遠リスクは下がり、設定変更ロジックも書かなくて良いため実装コストは軽くなります。
この記事では、android.provider.Settings
クラスのアクション一覧を簡単な解説つきで列挙します。
キャプチャはAPI level 19未満で表示できるものはGalaxy Nexus@Android 4.3、API level 19以降が必要なものについてはNexus5@Android L Developer Previewを使用しています。
参考URL: http://developer.android.com/reference/android/provider/Settings.html
注意点
生成したIntent
で呼び出される画面は端末の種類(メーカー)、OSのバージョンなどによって変わります。
同じ画面構成であっても見た目が同じとは限らないので、ヘルプなどを書く際には注意が必要です。
また、基本的にAction
を指定した暗黙的インテントになるため、環境によっては複数のActivity
の<intent-filter>
に引っかかる場合があります。
逆に、<uses-feature>
の設定などによっては対象Activity
が見つからないことも考えられます。
どの設定画面への遷移を利用する場合でも、Intent
の対象となるアクティビティが存在するかどうかを確認後、遷移するようにしてください。
ACTION_ACCESSIBILITY_SETTINGS
API level 5から追加。 ユーザ補助設定画面のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);
ACTION_ADD_ACCOUNT
API level 8から追加。
アカウントの追加画面(アカウントの種別選択画面)のアクション。
Settings.EXTRA_ACCOUNT_TYPES
にaccount_type
を指定(String[]
形式)することで追加対象のアカウントをフィルタリング可能。
Settings.EXTRA_AUTHORITIES
にsyncable="true"
なContentProvider
のauthorities
を指定(String[]
形式)することで追加対象のアカウントをフィルタリング可能。
フィルタリングの結果、追加対象のアカウントが特定された場合は選択画面を経由せず、直接対象アカウントの追加画面を表示する。
Intent intent = new Intent(); intent.setAction(Settings.ACTION_ADD_ACCOUNT); // 対象をGoogleアカウントに限定 intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES,new String[]{"com.google"}); startActivity(intent);
ACTION_AIRPLANE_MODE_SETTINGS
API level 3から追加。 機内モードの設定が可能な画面のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_AIRPLANE_MODE_SETTINGS);
startActivity(intent);
ACTION_APN_SETTINGS
API level 1から追加。 APNの設定画面のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APN_SETTINGS);
startActivity(intent);
ACTION_APPLICATION_DETAILS_SETTINGS
API level9から追加。
指定したアプリケーションの詳細設定画面のアクション。
アプリケーションの指定はパッケージURI形式(package:com.my.app
)でsetData()
する。
Intent intent = new Intent(); intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.parse("package:com.android.settings")); startActivity(intent);
ACTION_APPLICATION_DEVELOPMENT_SETTINGS
API level 3から追加。 開発者向けオプション画面のアクション。 開発者向けオプションを有効にしていない場合にどうなるかは不明。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
startActivity(intent);
ACTION_APPLICATION_SETTINGS
API level 1から追加。 アプリケーションに関する設定画面のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_SETTINGS);
startActivity(intent);
ACTION_BLUETOOTH_SETTINGS
API level 1から追加。 Bluetooth設定画面のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_BLUETOOTH_SETTINGS);
startActivity(intent);
ACTION_CAPTIONING_SETTINGS
API level 19から追加。 Kitkatから追加されたユーザ補助設定のうち、ビデオキャプション設定画面のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_CAPTIONING_SETTINGS);
startActivity(intent);
ACTION_DATA_ROAMING_SETTINGS
API level 3から追加。 データローミング設定画面のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_DATA_ROAMING_SETTINGS);
startActivity(intent);
ACTION_DATE_SETTINGS
API level 1から追加。 日付と時刻設定画面のアクション
Intent intent = new Intent();
intent.setAction(Settings.ACTION_DATE_SETTINGS);
startActivity(intent);
ACTION_DEVICE_INFO_SETTINGS
API level 8から追加。 端末の状態画面のアクション。 Android L Developer Previewではなぜかクラッシュする。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_DEVICE_INFO_SETTINGS);
startActivity(intent);
ACTION_DISPLAY_SETTINGS
API level 1から追加。 ディスプレイ設定画面のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_DISPLAY_SETTINGS);
startActivity(intent);
ACTION_DREAM_SETTINGS
API level 18から追加。 JELLY_BEAN_MR1で追加されたDaydream(スクリーンセーバー)設定画面のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_DREAM_SETTINGS);
startActivity(intent);
ACTION_INPUT_METHOD_SETTINGS
API level 3から追加。 言語と入力設定画面のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_INPUT_METHOD_SETTINGS);
startActivity(intent);
ACTION_INPUT_METHOD_SUBTYPE_SETTINGS
API level 11から追加。
インプットメソッド(文字入力アプリ)のサブタイプ設定画面のアクション。
Settings.EXTRA_INPUT_METHOD_ID
にInputMethodInfo#getId()
で取得したidを指定することでサブタイプを指定できるらしいが未検証。
Nexus5では、Settings.EXTRA_INPUT_METHOD_ID
に何も設定しないとgoogleキーボードの入力言語設定が表示される。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
startActivity(intent);
ACTION_INTERNAL_STORAGE_SETTINGS
API level 3から追加。 内部ストレージ設定画面のアクション。 このアクションは端末ごとの差異が激しいかもしれない。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_INTERNAL_STORAGE_SETTINGS);
startActivity(intent);
ACTION_LOCALE_SETTINGS
API level 1から追加。 端末の言語設定画面のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_LOCALE_SETTINGS);
startActivity(intent);
ACTION_LOCATION_SOURCE_SETTINGS
API level 1から追加。 位置情報アクセス設定画面のアクション。 位置情報の取得モードを切り替えさせるのに使うっぽい。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(intent);
ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS
API level 9から追加。 すべてのアプリケーション情報画面のアクション。 アプリ設定画面の「すべて」タブのこと。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS);
startActivity(intent);
ACTION_MANAGE_APPLICATIONS_SETTINGS
API level 3から追加。
アプリ管理画面のアクション。
ACTION_APPLICATION_SETTINGS
と同じ画面が表示された。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS);
startActivity(intent);
ACTION_MEMORY_CARD_SETTINGS
API level 3から追加。 外部ストレージ設定画面のアクション。 このアクションは端末ごとの差異が激しいかもしれない。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MEMORY_CARD_SETTINGS);
startActivity(intent);
ACTION_NETWORK_OPERATOR_SETTINGS
API level 3から追加。 利用可能なネットワーク設定画面のアクション。 表示と同時に利用可能なネットワークの検索が始まる。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_NETWORK_OPERATOR_SETTINGS);
startActivity(intent);
ACTION_NFCSHARING_SETTINGS
API level 14から追加。
ICE_CREAM_SANDWICH
から追加されたAndroidビーム設定画面のアクション。
Androidビームの有効/無効はNfcAdapter#isNdefPushEnabled()
で確認できる。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_NFCSHARING_SETTINGS);
startActivity(intent);
ACTION_NFC_PAYMENT_SETTINGS
API level 19から追加。
KITKAT
から追加されたタップ&ペイ設定画面のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_NFC_PAYMENT_SETTINGS);
startActivity(intent);
ACTION_NFC_SETTINGS
API level 16から追加。 NFC設定画面のアクション。 Nexusなどでは無線とネットワーク画面内のチェックボックス。 NFCが追加されたのはAPI level 14からだが、この定数はAPI level 16からなので注意。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_NFC_SETTINGS);
startActivity(intent);
ACTION_PRINT_SETTINGS
API level 19から追加。
KITKAT
で追加された印刷サービス設定画面のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_PRINT_SETTINGS);
startActivity(intent);
ACTION_PRIVACY_SETTINGS
API level 5から追加。 プライバシー設定画面のアクション。 Nexusではバックアップとリセット画面が相当。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_PRIVACY_SETTINGS);
startActivity(intent);
ACTION_QUICK_LAUNCH_SETTINGS
API level 3から追加。 Searchキーと他キーの同時押しで起動するアプリ設定画面のアクション。 この機能自体を知らなかった。ハードウェアキーボードが存在する端末向きだろうか? Android L Developer Previewではクラッシュする。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_QUICK_LAUNCH_SETTINGS);
startActivity(intent);
ACTION_SEARCH_SETTINGS
API level 8から追加。 検索設定画面のアクション。 NexusではGoogle検索アプリの設定画面が表示される。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_SEARCH_SETTINGS);
startActivity(intent);
ACTION_SECURITY_SETTINGS
API level 1から追加。 端末のセキュリティ設定画面のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_SECURITY_SETTINGS);
startActivity(intent);
ACTION_SETTINGS
API level 1から追加。 設定アプリのアクション(設定アプリの起動)。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_SETTINGS);
startActivity(intent);
ACTION_SOUND_SETTINGS
API level 1から追加。 端末の音関連設定のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_SOUND_SETTINGS);
startActivity(intent);
ACTION_SYNC_SETTINGS
API level 3から追加。
アカウントの同期設定のアクション。
Settings.EXTRA_AUTHORITIES
でSyncAdapter
のauthorities
を指定することで直接その同期設定が開けるようだが未検証。
Settings.EXTRA_AUTHORITIES
未設定の場合は設定済み同期一覧が表示される。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_SYNC_SETTINGS);
startActivity(intent);
ACTION_USER_DICTIONARY_SETTINGS
API level 3から追加。 ユーザー辞書設定画面のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_USER_DICTIONARY_SETTINGS);
startActivity(intent);
ACTION_WIFI_IP_SETTINGS
API level 3から追加。 Wi-Fi接続時のIP設定画面のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_WIFI_IP_SETTINGS);
startActivity(intent);
ACTION_WIFI_SETTINGS
API level 1から追加。 Wi-Fi設定画面のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_WIFI_SETTINGS);
startActivity(intent);
ACTION_WIRELESS_SETTING
API level 1から追加。 無線とネットワーク設定のアクション。
Intent intent = new Intent();
intent.setAction(Settings.ACTION_WIRELESS_SETTING);
startActivity(intent);
画面回転時のActionBarの挙動まとめ
はじめに
ActionBar
の一部の機能は、端末の画面の向きによって挙動が異なる場合があります。
この記事では特に画面回転時に大きく挙動が変わる部分についてまとめました。
MenuItemの表示数が変わる
ActionBar
に表示されるMenuItem
の数は画面幅のdp
によって決定されています。
ActionBar
でタイトルなどが表示されるバーのことをメインアクションバーと呼びますが、
このメインアクションバーにMenuItem
を表示する場合、全幅の50%を超えない範囲で表示数が決められています。
メインアクションバーにおけるdp
とMenuItem
表示数の関係は以下のようになっています。
dp | Menu表示数 |
---|---|
-359 | 2 |
360-499 | 3 |
500-599 | 4 |
600- | 5 |
http://developer.android.com/design/patterns/actionbar.html
下記スクリーンショットはGalaxyNexus
での例です。
縦向きでは(オーバーフローを含めて)3つ、横向きでは4つ表示されています。
タイトル非表示の場合
ロゴおよびタイトルを非表示にした場合でもメインアクションバーのメニューの表示数は増えません。
final ActionBar actionbar = getSupportActionBar(); actionbar.setDisplayShowTitleEnabled(false); actionbar.setDisplayShowHomeEnabled(false);
分割アクションバーの統合
AndroidManifest.xml
に設定を記述することで、縦向き時のActionBar
の要素を分割することができます。
これにより画面下部では横幅いっぱいにMenuItem
を表示することができますが、横向き時にはすべてメインアクションバーに格納されてしまいます。
<manifest > <activity uiOptions="splitActionBarWhenNarrow" > <meta-data android:name="android.support.UI_OPTIONS" android:value="splitActionBarWhenNarrow" /> </activity> </manifest>
タブが格納される
ActionBar
のナビゲーションモードをNAVIGATION_MODE_TABS
にしている場合、画面が縦向きであればメインアクションバーの下にタブバーが表示されます。
画面が横向きの場合、このタブバーはメインアクションバーに格納されます。
また、横向き時にメインアクションバーにタブの内容が表示しきれない場合、NAVIGATION_MODE_LIST
に似た表示に変更されます。
final ActionBar actionbar = getSupportActionBar(); actionbar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); actionbar.addTab(actionbar.newTab().setText("ほげほげ").setTabListener(listener)); actionbar.addTab(actionbar.newTab().setText("ぴよぴよ").setTabListener(listener)); actionbar.addTab(actionbar.newTab().setText("かにかに").setTabListener(listener)); actionbar.addTab(actionbar.newTab().setText("えびえび").setTabListener(listener));
タブ要素を表示できる場合
メインアクションバーにタブの内容が入りきる場合、タブバーの内容はそのままメインアクションバーに表示されます。
タブ要素を表示できない場合
タイトルやMenuItem
によりメインアクションバーにタブの内容が入りきらない場合、タブバーの内容はドロップダウンリストとして表示されます。
このとき、MenuItem
をapp:showAsAction="ifRoom"
としていてもMenuItem
の表示のほうが優先されます。
タブ+分割
アクションバーの分割とタブ表示を両方行っていた場合、どちらの要素もメインアクションバーに格納されます。
タイトル非表示の場合
ロゴおよびタイトルを非表示にした場合、タブ要素は左詰めで表示されます。 画像では分割アクションバーですが、分割していない場合でも同様です。
final ActionBar actionbar = getSupportActionBar(); actionbar.setDisplayShowTitleEnabled(false); actionbar.setDisplayShowHomeEnabled(false);
ドロップダウンは特に変化なし
ActionBar
のナビゲーションモードをNAVIGATION_MODE_LIST
にしている場合、画面の向きにかかわらずメインアクションバーにドロップダウンメニューが表示されます。
final ActionBar actionbar = getSupportActionBar(); actionbar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); actionbar.setListNavigationCallbacks(new ArrayAdapter<String>( MyActivity.this,R.layout.list_item, new String[]{"ほげほげ","ぴよぴよ","かにかに","えびえび"}), new ActionBar.OnNavigationListener() { @Override public boolean onNavigationItemSelected(int i, long l) { return false; } });
参考資料
ActionBar
の表示に関する挙動は以下のURLに書いてあります。
http://developer.android.com/design/patterns/actionbar.html
http://developer.android.com/guide/topics/ui/actionbar.html
SyncAdapterの実装メモ
SyncAdapterとは
SyncAdapter
とは、AccountManager
上のアカウントとContentProvider
を紐付けることにより、クラウド上のデータと端末のデータを同期させる仕組みです。
一般的な動作としては「ContentResolver
にAccountManager
のアカウントとSyncAdapter
を登録し、指定した周期でAccountManager
から取得した認証トークンを利用して通信処理を行い、ContentProvider
を更新する」という挙動となります。
このとき、同期処理を管理・実行するのはContentResolver
であり、AccountManager
はアカウントとトークンの取得にのみ利用します。
よくある誤解
SyncAdapter
はAccountManager
上のaccountType
とContentProvider
のauthority
を利用して紐付けを行う仕組みですが、SyncAdapter
を実装したアプリがアカウントの管理機能(Authenticator
)を持つ必要はありません。
例えば、SyncAdapter
が対象とするaccountType
を"com.google"とすることでGoogleアカウントに自アプリのSyncAdapter
を登録することもできます。
この記事中のサンプルでも実際にGoogleアカウントにSyncAdapter
を紐付け、同期処理を実行させています。
SyncAdapterの実装
SyncAdapter
の実装はAuthenticator
の実装と少し似ています。
Authenticator
同様、システムから実行されるService
と、その実態(IBinder
)であるSyncAdapter
、そしてSyncAdapter
の宣言に用いるXMLファイルなどが必要なほか、同期するデータの置き場所としてContentProvider
の宣言も必要です。
なお、ContentProvider
の実装部分は通常のアプリと全く変わりがないため、今回のサンプルでは、ContentProvider
の実装については詳しく説明しません。
SyncAdapterの宣言
SyncAdapter
による同期機能を提供するためには、SyncAdapter
を実装したアプリであることをシステムに宣言する必要があります。
この宣言はAndroidManifest.xml
において、特定の種類の<intent-filter>を持ったサービスが存在するかどうかでチェックされます。
同期設定を編集するための権限として、WRITE_SYNC_SETTINGS
が必要な他、アカウント取得とトークン取得のためにGET_ACCOUNTS
、USE_CREDENTIALS
も必要になります。
また、注意点として<provider>
タグに同期対象であることを示すandroid:syncable
の設定が必要です。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.nein37.syncadaptersample" > <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.USE_CREDENTIALS" /> <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name"> <activity android:name=".MyActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <provider android:exported="false" android:syncable="true" android:authorities="com.example.nein37.syncadaptersample" android:name=".MyContentProvider" /> <service android:name=".MySyncService" android:exported="true" android:process=":sync"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/syncadapter" /> </service> </application> </manifest>
Authenticator
アプリの実装に似ています。
SyncAdapter
ではそれに加えてandroid:process=":sync"
という属性の宣言が必要なので、注意してください。
<meta-data>から参照されるXMLファイルにはSyncAdapter
に関する宣言を記述します。
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android" android:accountType="com.google" android:allowParallelSyncs="false" android:contentAuthority="com.example.nein37.syncadaptersample" android:isAlwaysSyncable="true" android:supportsUploading="false" android:userVisible="true" />
ここで設定している属性は、それぞれ以下の様な意味です。
属性名 | 意味 |
---|---|
android:accountType | 対象とするアカウントのaccountType |
android:allowParallelSyncs | 複数のSyncAdapterが同時に実行されることを許すかどうか。 複数のアカウントと同期したい場合に設定する。 |
android:contentAuthority | 同期対象のContentProvider のauthority と同じものにする |
android:isAlwaysSyncable | 常に同期が実行可能な常態かどうかを示すフラグ。手動同期のみの場合はfalse 。 |
android:supportsUploading | 同期作業でデータのアップロード行うかどうかのフラグ。true の場合、ContentResolver#notifyChange() されていると同期が必要と見なすようだ |
android:userVisible | 設定アプリのアカウントと同期にこのアプリの同期設定が表示されるかどうかのフラグ。true にするとユーザが同期設定を解除できるので注意。 |
SyncAdapterの実装
SyncAdapter
はContentResolver
によって実行される同期処理そのものを記述するクラスです。
必要なメソッドは抽象クラスであるAbstractThreadedSyncAdapter
で宣言されているため、このクラスを継承して実装します。
基本的にはAbstractThreadedSyncAdapter#onPerformSync()
メソッドのみ実装すれば良く、このメソッドの中でトークンの取得から通信処理、ContentProvider
の更新処理を記述します。
また、このメソッドからトークンを取得するためにはAccountManager#blockingGetAuthToken()
というメソッドを利用します。
このメソッドはトークンが取得できるまでの間処理をブロックするためメインスレッドでは使えませんが、AbstractThreadedSyncAdapter#onPerformSync()
はバックグラウンドで処理するメソッドなので大丈夫です。
public class MySyncAdapter extends AbstractThreadedSyncAdapter { ContentResolver mContentResolver; public MySyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); mContentResolver = context.getContentResolver(); } @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { try { AccountManager manager =AccountManager.get(getContext()); // トークンの取得 String token = manager.blockingGetAuthToken(account, "cl", true); // TODO ここで通信処理を行う // TODO DBへの反映処理を行う ContentValues values = new ContentValues(); values.put(COLUMN, VALUE); getContext().getContentResolver().insert(CONTENT_URI, values); } catch (OperationCanceledException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (AuthenticatorException e) { e.printStackTrace(); } } }
同期サービスの実装
認証サービスはContentResolver
とSyncAdapter
との橋渡しを行います。
実装的にはService#onBind()
でAbstractThreadedSyncAdapter#getSyncAdapterBinder()を結果を返すだけです。
public class MySyncService extends Service { private MySyncAdapter mSyncAdapter; @Override public void onCreate() { super.onCreate(); mSyncAdapter =new MySyncAdapter(this,true); } @Override public IBinder onBind(Intent intent) { return mSyncAdapter.getSyncAdapterBinder(); } }
SyncAdapterの登録
SyncAdapter
による同期を行うためには、ContentResolver
へのSyncAdapter
の登録が必要です。
通常、SyncAdapter
の登録はアプリ内でのログイン/アカウント選択時や設定画面などで行われますが、サンプルでは単純にアプリ起動時に一番最初に登録されたGoogleアカウントに対して登録を行います。
SyncAdapter
の登録メソッドは同期処理のタイミングによっていくつか種類があるので、以下それぞれ説明します。
定期的に同期を行いたい場合
何時間おき、何日おきというように間隔を指定して定期的に同期処理を実行したい場合、ContentResolver.addPeriodicSync()
を使用し、間隔を秒単位で指定します。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); // 今回は最初に登録されたGoogleアカウントが対象 Account account = AccountManager.get(this).getAccountsByType("com.google")[0]; Bundle args = new Bundle(); // 必要なパラメータがあればBundleに詰める // 10分ごとに同期 ContentResolver.addPeriodicSync(account, AUTHORITY, args, 600); }
ネットワークメッセージのタイミングで同期を行いたい場合
Androidシステムはネットワークに接続の確立後、TCP/IPコネクションを維持するために短い間隔でメッセージ送出しています。
このメッセージが送出されたタイミングで同期を行う場合、ContentResolver.setSyncAutomatically()
を使用します。
実際にはこの設定に関する挙動はもう少し複雑で、android:supportsUploading
設定やContentResolver#notifyChange()
などで更新が必要かどうかを判断しているようです。
そのため、ContentProvider
まわりの実装にミスがあるとうまく同期されなくなるかもしれません。
また、ContentResolver.setSyncAutomatically()
を使用した場合でもContentResolver.addPeriodicSync()
で登録した内容は削除されないことに注意してください。
ContentResolver.addPeriodicSync()
した後でネットワークメッセージタイミングのみの同期に切り替えたい場合、ContentResolver.removePeriodicSync()
を利用して既存設定を削除する必要があります。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); // 今回は最初に登録されたGoogleアカウントが対象 Account account = AccountManager.get(this).getAccountsByType("com.google")[0]; // ネットワークメッセージタイミングで同期 ContentResolver.setSyncAutomatically(account, AUTHORITY, true); }
任意のタイミングで同期を行いたい場合
ボタン押下時など、任意のタイミングで同期を行う場合、ContentResolver.requestSync()
を使用できます。
通常、SyncAdapter
の動作は同期間隔の指定やネットワークの効率的利用によってバッテリーの消費が抑えられていますが、
このメソッドを使用すると同期処理を強制的に行ってしまうため、バッテリーを過剰に消費する場合があります。
@Override public void onClick(View v) { Account account = AccountManager.get(this).getAccountsByType("com.google")[0]; ContentResolver.requestSync(account,SampleColumn.AUTHORITY,new Bundle()); }
注意点
SyncAdapter
は非常に優れた機能ですが、実装する際に注意すべきことが多い上に資料が少ないのが難点です。
ここでは、私が特に注意すべきと感じたことをまとめます。
AndroidManifest.xmlの設定
SyncAdapter
を使う場合、<service>
だけでなく<provider>
にも設定が必要なことに注意が必要です。
また、android:process=":sync"
をandroid:process="sync"
と書いてしまったり、<service>
をandroid:exported=false
にしてしまったりすると動作しません。
android:userVisible="false"
SyncAdapter
の設定をandroid:userVisible="false"
にすると、設定アプリの同期一覧で表示されなくなります。
この設定はユーザにSyncAdapter
を無効にさせないために有用ですが、SyncAdapter
の設定を判別することができなくなります。
特に定期同期とネットワークメッセージ同期は片方を設定してももう片方が解除されないため、意図せず頻繁に同期されてしまう可能性があります。
SyncAdapter
を設定する際はContentResolver.getPeriodicSyncs()
やContentResolver.getSyncAutomatically()
でSyncAdapter
の設定状況を取得し、不要な設定情報が存在しないか確認したほうが良いかもしれません。
APIレベル
SyncAdapter
の大半の機能はAPI level 5から実装されていますが、ContentResolver.addPeriodicSync()
などの定期同期のための機能はAPI level 8以降となります。
そのため、API level 5-7 の端末で定期同期を行いたい場合、AlarmManager
を使用する必要があります。
アプリの承認
Activity
でAccountManager#getAuthToken()
せず、SyncAdapter
ではじめてAccountManager#blockingGetAuthToken()
した場合、アプリの接続承認のための通知が表示されます。
この通知をタップすると承認画面へ遷移し、そこで承認してはじめて同期処理でトークンを利用できるようになりますが、若干わかりづらいのでActivity
側でアカウント選択時にAccountManager#getAuthToken()
したほうが良いかもしれません。
当然ですが、その際取得したトークンを保存してはいけません。
AccountManagerでアカウントを管理する
はじめに
AccountManager
において、アカウントの管理やトークンの取得を直接行うクラスのことをAuthenticator
と呼びます。
それにあわせて、この記事ではAuthenticator
を実装したアプリのことをAuthenticator
アプリと呼ぶことにします。
Authenticatorアプリの実装
Authenticator
アプリでは、以下の実装が必要です。
- アカウント種別の宣言 -
AccountManager
に追加するアカウント種別の宣言 - 認証画面 - ユーザに提供するログイン画面の
Activity
- Authenticator -
AccountManager
へ提供する機能の実装 - 認証サービス -
AccountManager
とAuthenticator
を繋ぐためのService
以下、それぞれの実装について詳しく説明します。
アカウント種別の宣言
アカウントの管理機能を提供するためには、Authenticator
アプリであることをシステムに宣言する必要があります。
Authenticator
アプリの宣言はAndroidManifest.xml
において、特定の種類の<intent-filter>
を持ったサービスが存在するかどうかでチェックされます。
また、アカウントを編集するための権限として、AUTHENTICATE_ACCOUNTS
が必要です。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.nein37.authenticatorsample"> <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name"> <service android:name=".AuthenticatoinService" android:exported="false"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator" /> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" /> </service> <activity android:name=".LoginActivity" android:exported="true" android:label="@string/app_name" /> </application> </manifest>
<Service>
タグの<intent-filter>
と<meta-data>
の記述はAuthenticator
アプリを作る場合の決まり文句です。
前者でAuthenticator
対応のService
があることを宣言し、後者で設定可能なアカウントの種類を宣言しています。
<meta-data>
から参照されるXMLファイルでは、アカウント種別の宣言となるaccount-authenticator
情報を記述します。
このうち、android:accountType
は必ず他と重複しないようなユニークな値にする必要があります。
android:accountType
はAccountManager#getAccountsByType()
などで指定するaccountType
と同じものです。
android:icon
、android:smallIcon
はどちらも設定アプリのアカウント管理画面で表示されるアイコンですが、このとき指定する画像サイズに関する情報は見つけられませんでした…。
android:label
は設定アプリでの表示に利用されます。直接記述すると表示されないので、必ず文字列リソースに定義してください。
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" android:accountType="com.example.test" android:icon="@drawable/ic_launcher" android:smallIcon="@drawable/ic_launcher" android:label="@string/account_label" />
認証画面の実装
認証画面はユーザIDやパスワードなど、必要な情報を入力し、サーバと通信して認証できる実装になっている必要があります。
認証処理に成功後、AccountManager#addAccountExplicitly()
によってAccountManager
へアカウントの登録を行います。
このとき、accountType
が必ずauthenticator.xml
で定義したものと同じになるようにしてください。
実装例では、サーバへの通信処理や例外処理などは省略しています。
public class LoginActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); final EditText nameEdit = (EditText) findViewById(R.id.name); final EditText passwordEdit = (EditText) findViewById(R.id.password); Button button = (Button) findViewById(R.id.login); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String name = nameEdit.getText().toString(); String password = passwordEdit.getText().toString(); login(name, password); } }); } // ログイン処理 public void login(final String name, final String password) { // TODO:このメソッドは非同期の通信処理でログインを試みます。 // ログインに成功した場合、loginSuccess()を呼び出します。 loginSuccess(name, password); } // ログイン処理のコールバック public void loginSuccess(final String name, final String password) { Account account = new Account(name, "com.example.test"); AccountManager am = AccountManager.get(this); // アカウント情報を保存 // TODO:本来はパスワードを暗号化する必要があります am.addAccountExplicitly(account, password, null); // 認証画面終了 setResult(RESULT_OK); finish(); } }
Authenticatorの実装
Authenticator
はAccountManager
からの要求に対して応答を行うAuthenticator
アプリで最も重要な機能です。
必要なメソッドなどはAbstractAccountAuthenticator
という抽象クラスで宣言されているので、このクラスを継承して実装します。
最低限実装が必要なメソッドはaddAccount()
とgetAuthToken()
の2つです。
addAccount()
はAccountManager#getAccount()
が呼び出されたときに実行されるメソッドで、認証画面を起動するためのIntent
を生成して返します。
getAuthToken()
も同じようにAccountManager#getAuthToken()
が呼び出されたときに実行され、メソッドの中でサーバとの通信処理を行ってトークンを取得後、返却します。
どちらのメソッドもBundle
に特定のキーで返却する必要が有るため、AccountManager
が持つ定数について調べておく必要があります。
public class MyAuthenticator extends AbstractAccountAuthenticator { public static final String ACCOUNT_TYPE = "com.example.test"; final Context mContext; public MyAuthenticator(Context context) { super(context); mContext = context; } @Override public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { // アカウントの追加を行う画面を呼び出すIntentを生成 final Intent intent = new Intent(mContext, LoginActivity.class); // アカウント追加後、戻り先の画面を設定 intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); // Intentを返却 final Bundle bundle = new Bundle(); bundle.putParcelable(AccountManager.KEY_INTENT, intent); return bundle; } @Override public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { AccountManager manager = AccountManager.get(mContext); String name = account.name; // TODO:本来はパスワードを復号化する必要があります String password = manager.getPassword(account); // TODO:本来はここで通信を行い、ユーザ名とパスワードからトークンの取得を行う String authToken = "AUTH_TOKEN"; // トークンをキャッシュ manager.setAuthToken(account,authTokenType,authToken); // トークンを返却する Bundle result = new Bundle(); result.putString(AccountManager.KEY_ACCOUNT_NAME, name); result.putString(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE); result.putString(AccountManager.KEY_AUTHTOKEN, authToken); return result; } @Override public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { return null; } @Override public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException { return null; } @Override public String getAuthTokenLabel(String authTokenType) { return null; } @Override public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { return null; } @Override public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException { return null; } }
認証サービスの実装
認証サービスはAccountManager
とAuthenticator
との橋渡しを行います。
…といっても難しいことはなく、Service#onBind()
でAbstractAccountAuthenticator#getIBinder()
を結果を返すだけです。
あとはAccountManager
がIBinder
を経由してAuthenticator
のメソッドを呼び出し、認証画面の呼び出しやトークンの返却を行ってくれます。
public class AuthenticatoinService extends Service { private MyAuthenticator mAuthenticator; @Override public void onCreate() { super.onCreate(); mAuthenticator =new MyAuthenticator(this); } @Override public IBinder onBind(Intent intent) { return mAuthenticator.getIBinder(); } }
おまけ
以上でAuthenticator
アプリに必要な最低限の実装は終わりです。
しかし、Authenticator
アプリが扱う内容は非常にデリケートなため、セキュリティに細心の注意を払う必要があります。
以下ではAuthenticator
アプリを実装する上での注意点や補足などをまとめます。
Service、Activityの公開範囲を設定する
認証サービスを非公開サービス、認証画面を公開アクティビティとして設定する必要があります。
設定方法については上記AndroidManifest.xml
のexported
設定を参照してください。
パスワードを暗号化する
AccountManager
はアカウント情報をDBに保存しています。
このDBは通常、アプリからアクセスすることができませんが、まったく暗号化されていません。
root化された端末などではAccountManager
のDBを簡単に見られてしまうため、パスワードを平文で保存してはいけません。
暗号化する場合はAccountManager#addAccountExplicitly()
する際に暗号化し、AccountManager#getPassword()
した際に復号化します。
パスワードを保存しない
サービスへのアクセスによりトークンの期限を延長できるようなサービスの場合、パスワードを保存しないという選択肢があります。
その場合、Authenticator#getAuthToken()
では新たにトークンを取得することが不可能なため、認証画面での認証成功事にAccountManager#setAuthToken()
を呼び出してトークンをキャッシュする必要があります。
また、Authenticator#getAuthToken()
が呼び出されたときはトークンの再取得が不可能なため、こちらも認証画面を再度表示させるなどの工夫が必要です。
トークンがキャッシュされる働きを理解する
認証トークンはAccountManager#setAuthToken()
されたときにキャッシュされ、AccountManager#invalidateAuthToken()
されたときに無効となります。
キャッシュが有効な間、AccountManager#getAuthToken()
はAuthenticator#getAuthToken()
を呼び出しません。
以下のようなAuthenticator#getAuthToken()
の実装は不要です。
@Override public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { AccountManager manager = AccountManager.get(mContext); // キャッシュからトークンを取得 String authToken = manager.peekAuthToken(account,authTokenType); if(TextUtils.isEmpty(authToken)){ // キャッシュが無効なのでトークンを取得 authToken = "AUTH_TOKEN"; manager.setAuthToken(account,authTokenType,authToken); } // ...
authTokenTypeによるトークン取得制限
アプリから認証トークンを取得する際、AccountManager#getAuthToken()
にauthTokenType
を指定する必要があります。
このauthTokenType
についてサンプルではチェックしていませんが、本来はAuthenticator#getAuthToken()
で有効な値かどうかを判定し、それに応じたトークンの取得を行うことになっています。
AccountManager
からは設定可能なauthTokenType
の一覧を知ることができないため、トークンを取得したいアプリは最初からauthTokenType
を知っている必要があります。
この制限のため、原理的にはauthTokenType
を非公開とすることでトークンの取得制限をかけることができますが、実際にはauthTokenType
も平文でDBに保存されるためroot化された端末では簡単に解析されてしまいます。
従って、非公開authTokenType
によってセキュリティが確保されたと考えるのは危険です。
Authenticatorアプリの競合
※Authenticatorの実装と直接関係ありません。
まったく同一のaccountType
を持つ複数のAuthenticator
アプリが端末にインストールされると、先にインストールされたAuthenticator
アプリだけが有効になります。
悪意のあるAuthenticator
アプリが先にインストールされた端末ではユーザIDやパスワードが漏洩する恐れがあるため、AccountManager
を利用するアプリは接続先のAuthenticator
が本物かどうか気をつける必要があります。
…多分AccountManager#getAuthenticatorTypes()
でAuthenticatorDescription
を取得してパッケージ探して証明書を検証するんじゃないかと思うんですが、具体的にどう実装すればチェックできるのか把握できていません。
証明書の異なるアプリからアクセスする際の注意
Authenticator
アプリと異なる証明書を持ったアプリでAccountManager#getAuthToken()
した場合、Android OS 4.0.x端末ではクラッシュします。
具体的には、システムがGrantCredentialsPermissionActivity
という画面を呼びだそうとして、その途中でNullPointerException
が発生するようです。
この問題はAOSPのissuesにも登録されていて、4.1.xでは修正されているようですが、回避方法がわかりません。
どなたか回避方法をご存知の方は教えてください…。
https://code.google.com/p/android/issues/detail?id=23421
参考資料
この記事を書くにあたって以下の資料を参考にしました。 JSSEC 『Android アプリのセキュア設計・セキュアコーディングガイド』2014年7月1日版 http://www.jssec.org/report/securecoding.html
タオソフトウェア株式会社 Android Security 安全なアプリケーションを作成するために http://www.amazon.co.jp/dp/4844331345
AccountManagerを利用する
AccountManagerとは
AccountManager
とは、Androidにおいて様々なWebサービスのアカウントを管理するための仕組みのことです。
アプリはAccountManager
を利用することで以下のような様々なことができるようになります。
- アカウント情報の取得
- アカウントの編集
- アカウントのトークン取得
それぞれの操作は<uses-permission>
による制限がかけられています。
呼び出す機能に応じてに応じて必要な<uses-permission>
が変わってくるため、必要な機能を十分に検討し、適切な<uses-permission>
を設定する必要があります。
また、どの機能を利用する場合でもそのサービスに対応したアカウント管理アプリがインストールされていなければAccountManager
を経由してアカウント情報を管理することはできません。
アカウント情報の取得
AccountManager
からアカウント情報を取得する方法はいくつかあります。
ほとんどの方法でGET_ACCOUNTS
パーミッションが必要です。
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
アカウント情報は基本的にAccount
クラスで管理されます。
このクラスはname
とtype
の2つの値を持っており、name
はアカウント名、type
はそのアカウントが属するサービスの種類となります。
例として、Googleのアカウントの場合、name
はuser@gmail.com、type
はcom.google
が格納されています。
すべてのアカウント情報を取得
すべてのアカウント情報を取得するにはAccountManager#getAccounts()
を使用します。
通常のアプリでは利用するサービス種別が決まっているため、この方法を使うことはあまりないでしょう。
取得できるアカウント情報はAccount
クラスの配列となっています。
AccountManager manager = AccountManager.get(context); Account[] accountArray = manager.getAccounts();
サービス種別を指定してアカウント情報を取得
サービス種別を指定してアカウント情報を指定するにはAccountManager#getAccountsByType()
を使用します。
この時指定するtype
はAccount#type
と同じもので、サービスごとに決められた文字列です。
例として、Googleのアカウントであれば以下のように取得します。
AccountManager manager = AccountManager.get(context);
Account[] accountArray = manager.getAccountsByType("com.google");
特定の機能を持ったアカウント情報を取得
あるサービス種別のアカウントのうち、さらに特定の機能を持ったアカウントのみを取得したい場合、AccountManager#getAccountsByTypeAndFeatures()
を使用します。
このメソッドは、「Gmailを利用することができるGoogleユーザ」といったような指定に使います。
https://developers.google.com/gmail/android/
また、このメソッドのみAccount
クラスの配列ではなくAccountManagerFuture
を返しますが、このAccountManagerFuture
はメインスレッドで扱ってはいけないため、コールバックを設定できるようになっています。
// Get the account list, and pick the first one final String ACCOUNT_TYPE_GOOGLE = "com.google"; final String[] FEATURES_MAIL = { "service_mail" }; AccountManager.get(this).getAccountsByTypeAndFeatures(ACCOUNT_TYPE_GOOGLE, FEATURES_MAIL, new AccountManagerCallback() { @Override public void run(AccountManagerFuture future) { Account[] accounts = null; try { accounts = future.getResult(); if (accounts != null && accounts.length > 0) { String selectedAccount = accounts[0].name; queryLabels(selectedAccount); } ru } catch (OperationCanceledException oce) { // TODO: handle exception } catch (IOException ioe) { // TODO: handle exception } catch (AuthenticatorException ae) { // TODO: handle exception } } }, null /* handler */);
アカウント選択ダイアログの表示
API level 14以降では登録済みアカウントから選択、または新しいアカウントを追加させるダイアログを表示させることができます。
このメソッドの実行には権限が必要ありませんが、アカウントの選択ダイアログはOS標準のUIとなり、選択結果もonActivityResult
で受ける形になります。
@Override public void onClick(View v) { Intent intent = AccountManager.get(this).newChooseAccountIntent(null, null, new String[] { "com.google" }, false, null, null, null, null); startActivityForResult(intent, REQUEST_CODE); } @Override protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); { if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) Toast.makeText(this, data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME), Toast.LENGTH_SHORT).show(); } }
アカウントの編集
AccountManager
ではアカウントの追加、削除などを行うことができます。
そのいずれの方法でもMANAGE_ACCOUNTS
パーミッションが必要です。
以下では、アカウント編集に関する代表的なメソッドを紹介します。
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
アカウントの追加
サービス種別を指定してアカウントの追加を行うには、AccountManager#addAccount()
を利用します。
通常、このメソッドを実行するとサービス種別に対応したアカウントの登録画面が表示され、そこでアカウント情報を入力してログインし、アカウントを追加する形になります。
従って、このメソッドでもコールバックを利用して結果を受け取る方法を使います。
ログインに成功した場合、KEY_ACCOUNT_NAME
とKEY_ACCOUNT_TYPE
を受け取ることができます。
ここで第二引数に指定した"mail"はauthTokenType
という引数で、後にトークンを取得する際に重要になります。
AccountManager manager = AccountManager.get(this); manager.addAccount("com.google", "mail", null, null, activity, new AccountManagerCallback<Bundle>() { @Override public void run(AccountManagerFuture<Bundle> future) { try { Bundle bundle = future.getResult(); String accountName = bundle.getString(AccountManager.KEY_ACCOUNT_NAME); } catch (OperationCanceledException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (AuthenticatorException e) { e.printStackTrace(); } } }, null);
アカウントの削除
アカウント情報を指定して削除を行うには、AccountManager#removeAccount()
を利用します。
このメソッドでは削除の成功可否booleanをコールバックで受け取ります。
一般的にアカウント情報を指定するためにはgetAccountsByType
を利用するため、GET_ACCOUNTS
パーミッションが必要ですが、name
とtype
が最初からわかっている場合、Account
を直接指定することができます。
実際に下記の例ではAccount
を直接生成していますが、このような場合はMANAGE_ACCOUNTS
パーミッションのみで動作します。
AccountManager manager = AccountManager.get(this); manager.removeAccount(new Account("user@gmail.com","com.google"), new AccountManagerCallback<Boolean>() { @Override public void run(AccountManagerFuture<Boolean> future) { try { boolean result = future.getResult(); } catch (OperationCanceledException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (AuthenticatorException e) { e.printStackTrace(); } } }, null);
アカウントの無効化
AccountManager
ではアカウントのユーザIDとパスワードを保存している場合があり、トークン期限が切れた場合でもこれらの情報でトークンの再取得を行っています。
何らかの理由でこの再取得を無効化したい場合、AccountManager#clearPassword()
を利用して保存されているパスワードをリセットすることができます。
このメソッドもアカウントを直接指定する場合にはMANAGE_ACCOUNTS
パーミッションのみで動作します。
AccountManager manager = AccountManager.get(this); manager.clearPassword(new Account("user@gmail.com","com.google"));
トークンの取得
AccountManager
では、指定したアカウントからサービスに接続するためのトークンを取得することができます。
この仕組により、AccountManager
で管理されているアカウントでWebサービスを利用する場合、同一端末でのログインは初回のみで良いことになります。
トークンの取得にはUSE_CREDENTIALS
パーミッションが必要です。
また、アカウントを直接指定した場合でもGET_ACCOUNTS
が必要な場合があるようなのでこちらもあったほうが良いでしょう。
<uses-permission android:name="android.permission.GET_ACCOUNTS"/> <uses-permission android:name="android.permission.USE_CREDENTIALS"/>
トークンを取得するメソッドはいくつかありますが、下記では最も単純なgetAuthToken()
を例にあげます。
このメソッドを最初に実行したとき、アプリがアカウントにアクセスすることを承認するかどうかをユーザに確認する画面が表示されます。
この承認画面により、AccountManager
ではユーザが許可しない限りはトークンを取得できないという仕組みになっているのです。
また、第二引数のauthTokenType
は取得するトークンの種別を定義するもので、必ず指定する必要があります。
通常、このauthTokenType
はAccountManager#addAccount()
で指定するauthTokenType
と同じものになります。
AccountManager manager = AccountManager.get(this); manager.getAuthToken(new Account("user@gmail.com", "com.google"), "mail", null, this, new AccountManagerCallback<Bundle>() { @Override public void run(AccountManagerFuture<Bundle> future) { Bundle bundle = null; try { bundle = future.getResult(); String accountName = bundle.getString(AccountManager.KEY_ACCOUNT_NAME); String accountType = bundle.getString(AccountManager.KEY_ACCOUNT_TYPE); String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN); Toast.makeText(MyActivity.this,authToken,Toast.LENGTH_SHORT).show(); } catch (OperationCanceledException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (AuthenticatorException e) { e.printStackTrace(); } } }, null);
注意点
AccountManager
を安全に使う上で、気をつけなければいけないことがあります。
以下はセキュリティの確保のために最低限注意しなければならない事柄です。
取得したトークン情報を永続化しない
AccountManager#getAuthToken()
で取得したトークンを保存してはいけません。
必要にった時にAccountManager#getAuthToken()
で取得しなおして下さい。
AccountManager
では有効期限内のトークンをキャッシュしているため、通常はトークン再取得に時間はかかりません。
取得したトークン情報を他のアプリに共有しない
AccountManager#getAuthToken()
で取得したトークンを他のアプリと共有してはいけません。
承認画面でユーザが確認できるのは AccountManager#getAuthToken()
を実行したアプリのみです。
他のアプリでトークンを使わせることはユーザを裏切ることになります。
取得したトークンが使えない場合、トークンを無効化する
取得したトークンでAPIアクセスに失敗する場合、トークンが無効になっている場合があります。
このような場合でも有効期限が切れるまではAccountManager
でキャッシュされてしまうため、アプリで検知した場合はAccountManager
に無効なトークンであることを通知する必要があります。
AccountManager#invalidateAuthToken()
を実行することで、指定したトークンのキャッシュを無効にできます。
AccountManager#invalidateAuthToken()
実行後にAccountManager#getAuthToken()
を実行した場合、AccountManager
は新しいトークンを取得して返します。
AccountManager manager = AccountManager.get(this); manager.invalidateAuthToken("com.google",TOKEN);