読者です 読者をやめる 読者になる 読者になる

ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

Android のアカウントマネージャ (AccountManager) の概説

最近 Android のアカウントマネージャを仕事で触ったので、調べた内容としてアカウントマネージャの概要をまとめておきます。

Web 上を調べると AccountManager を使う処理の実装方法はいろいろ見つかるのですが、アカウントマネージャの概要を説明しているページはあんまりなくて全体像を掴みにくいと思っています。 そういう情報を探している人の役に立てば幸いです。

公式ドキュメント

アカウントマネージャに関する公式ドキュメントは以下のものぐらいしかなさそうです。 AccountManager クラスのドキュメントを全て (各メソッドの説明も含めて) 読むと大体理解できると思います。 (下記ドキュメントを見て理解したら本ページの内容を読む必要はありません。)

アカウントマネージャの概要

アカウントマネージャは、Android のシステムがユーザーのオンラインアカウントを中央管理するための機能です。 Android 端末の 「設定」 にずらずらとアカウントが並ぶと思いますが、これもアカウントマネージャの仕組みで実現されています。

f:id:nobuoka:20170308031030p:plain:w360

各種オンラインサービスはそれぞれ異なったアカウントの扱い方や認証方式を使うため、アカウントマネージャはアカウント種別ごとに authenticator モジュールを使い分けるようになっています。

使いどころ

複数アプリで認証情報を共有するときに便利です。 (認証情報の提供側、および利用側両方とも。)

また、単一アプリでのみ認証情報を使う場合でも便利に使えると思います。 なぜなら、アカウントマネージャを使うことで認証周りの処理を分離しやすくなり、また、統一された方法で認証トークンを扱えるようになるためです。

用語

  • Account Type : Authenticator を特定するための文字列。
  • Auth token (認証トークン) : オンラインサービスのユーザー認証に使われるトークン。
  • Auth token type : Authenticator 固有の、Auth token 種別。 1 つのアカウントが auth token を複数持つことができる。
  • Feature : Authenticator 固有の、アカウントプロパティを特定するトークン。
  • Client (クライアント) / Application : アカウントマネージャを使って認証情報を取得するアプリを指す。
  • Authenticator : トークンの払い出しなどを行うやつ。 実体は AbstractAccountAuthenticator を継承したクラス。
    • システム上で有効な authenticator (AbstractAccountAuthenticator) を持っているアプリを指して Authenticator と言ったりもしてる? (明示的にアプリを表す場合は 「Authenticator アプリ」 と呼ぶのが良さそう。)
    • クライアントであり、かつ Authenticator であるというようなアプリも存在しうる。

Authenticator とクライアントの関係

アカウントマネージャを扱う処理を大別すると、authenticator とクライアントに分かれます。

  • Authenticator (や、それに関連する処理) : オンラインサービスのアカウント取得や認証処理を行います。 典型的には、ユーザーに対してログイン画面を表示してユーザー名とパスワードを入力させ、それらのアカウント情報を使ってオンラインサービスから認証情報を取得して、アカウントマネージャに渡します。 普通はそのオンラインサービスの提供者がアプリの中に実装します。
    • Authenticator のメソッドはアカウントマネージャから呼ばれます。
  • クライアント : アカウントマネージャを通じて、オンラインサービスの認証情報 (アクセストークンなど) を取得し、それを使ってオンラインサービスにリクエストを投げる、というような処理です。
    • 自社サービスクライアント : 自社が提供するサービスの認証情報をアカウントマネージャから取得します。 このアプリのシグネチャは、対応する authenticator を含むアプリのシグネチャと同じになります。
    • 他社サービスクライアント : 他社が提供するサービスの認証情報をアカウントマネージャから取得します。 このアプリのシグネチャは、対応する authenticator を含むアプリのシグネチャと異なります。

クライアントが AccountManager のクライアント向けのメソッドを呼ぶと、(一部のメソッドは) 対応する Authenticator のメソッドを呼びます。 そして、典型的には Authenticator のメソッドは AccountManager の Authenticator 用のメソッドを呼びます。 クライアントも Authenticator も AccountManager のメソッドを呼ぶという点で混乱しやすいのですが、AccountManager のメソッドをクライアント用メソッド群と Authenticator 用メソッド群に分けると、理解しやすいと思います。

f:id:nobuoka:20170308034440p:plain

自社サービスクライアントと他社サービスクライアントについて公式ドキュメント上での区別はありませんが、こういう分け方で考えると理解しやすいと思うので、本ページではこのように表記します。 ちなみに厳密な区別としては、「扱うアカウント種別の authenticator アプリと同じシグネチャを持つアプリ」 が自社サービスクライアント、「異なるシグネチャを持つアプリ」 が他社サービスクライアントです。

AccountManager のメソッド一覧

アカウントマネージャについて理解するには AccountManager クラスに生えている全てのメソッドを把握すればいいのですが、ドキュメントそのままだと理解しづらいので、整理して一覧できるようにしておきます。 これらのメソッド一覧に目を通すと、アカウントマネージャについての理解が深まるはずです。

API level 25 のドキュメントを参照しています。

クライアント向け

他社サービスのクライアントが使えるメソッド

Authenticator 一覧を取得したり、アカウント一覧を取得したり (パーミッションがあれば他社サービスのアカウントも全て)、指定のアカウントの認証トークンを取得したり、指定の認証トークンが無効になっていることをアカウントマネージャに伝えたり、ユーザーが指定のアカウントのパスワードを知っていることを確認するよう依頼したり、といったことができます。

  • getAuthenticatorTypes()
  • addOnAccountsUpdatedListener
    • アカウントマネージャが管理するアカウントの変化 (追加や削除など) を検知するリスナ (OnAccountsUpdateListener) を追加する。
    • getAccounts メソッドで取得可能なアカウントの変化のみ検知できる。 (典型的には、任意のアカウントの変化を検知するには GET_ACCOUNTS パーミッションが必要。)
  • removeOnAccountsUpdatedListener(OnAccountsUpdateListener)
    • リスナの削除。
  • addAccount
    • 指定のアカウント種別のアカウントを追加するよう依頼する。 対応する Authenticator が適切な UI でこのリクエストを処理する。
    • API level 22 以下では MANAGE_ACCOUNTS パーミッションが必要。
  • アカウント取得系
    • getAccounts
      • バイスに登録されているすべてのアカウントのリストを取得する。
      • ただし、GET_ACCOUNTS パーミッションがない場合は、同じシグネチャの Authenticator が管理するアカウントしか取得できない。
      • API level 22 以下の場合は GET_ACCOUNTS パーミッションが必要。 (ドキュメントに明記されてないけど多分。)
    • getAccountsByType
      • バイスに登録されているアカウントのうち、指定のアカウント種別のものを取得する。
      • GET_ACCOUNTS パーミッションがない場合は、同じシグネチャの Authenticator が管理するアカウントのみ取得できる。
      • API level 22 以下の場合は GET_ACCOUNTS パーミッションが必要。
    • getAccountsByTypeAndFeatures
      • バイスに登録されているアカウントのうち、指定のアカウント種別で、指定の機能をもつものを取得する。
      • 機能を考慮しないアカウント取得のメソッド (上 2 つ) とは違い、Authentcator が処理を行う。 (機能の管理をするため。)
      • GET_ACCOUNTS パーミッションがない場合は、同じシグネチャの Authenticator が管理するアカウントのみ取得できる。
      • API level 22 以下の場合は GET_ACCOUNTS パーミッションが必要。 (ドキュメントに明記されてないけど多分。)
  • confirmCredentials
    • ユーザーが指定のアカウントのパスワードを知っていることを確認する。 対応する Authenticator がこのリクエストを処理する。
    • 買い物処理みたいな、重要な操作の前に呼び出す、というような使われ方だと思う。
    • 呼び出し側がパスワードを渡した場合はそれが使われて、そうでなければ Authenticator 側がパスワード入力画面を表示する必要があるっぽい。
    • API level 22 以下では MANAGE_ACCOUNTS パーミッションが必要。
  • updateCredentials(Account, String, Bundle, Activity, AccountManagerCallback<Bundle>, Handler)
    • ユーザーに対して、新しいパスワードを入力してもらうよう依頼する。 対応する Authenticator がこのリクエストを処理する。
    • 保存されているパスワードを更新するため? 使いどころをあんまりわかってない。
    • API level 22 以下では MANAGE_ACCOUNTS パーミッションが必要。
  • getPreviousName
    • 指定のアカウントの前の名前を返す。
    • LOGIN_ACCOUNTS_CHANGED_ACTION ブロードキャストを受け取ったときに、アカウント名が変更されているかどうか検知できるようにするためのメソッドらしい。
  • Auth token 取得系 : AccountManager がキャッシュしていたらそれを返し、なければ対応する Authenticator が認証トークンを取得する。
    • getAuthToken(Account, String, Bundle, boolean, AccountManagerCallback<Bundle>, Handler)
      • バックグラウンドの処理向け。
      • 指定のアカウントの指定のトークン種別の認証トークンを取得する。 アカウントマネージャがキャッシュしている場合はそれが返される。 キャッシュされていない場合は authenticator がリクエストを処理する。
      • Authenticator は、パスワードが使えるのであればそれでサーバーに問い合わせを行う。 それが無理ならユーザーにログイン画面を表示して、ユーザー入力によって認証トークンを取得する。
      • notifyAuthFailure に真を渡したならば、そのインテントを開始するステータスバーの通知も表示される。
      • その場合 (ログインする必要がある場合? それともステータスバーの通知が表示される場合?) には、ユーザーが対応するまで何時間も、何日も、あるいは永遠に待つことになる可能性がある。
      • notifyAuthFailure に偽を渡した場合は、アプリケーションが Intent を開始する責任がある。
      • API level 22 以下では USE_CREDENTIALS パーミッションが必要。
    • getAuthToken(Account, String, boolean, AccountManagerCallback<Bundle>, Handler)
      • API level 14 で Deprecated になってるので Bundle パラメータ有り版を呼ぶこと。
    • getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback<Bundle>, Handler)
      • フォアグラウンドで動いている処理向け。
      • 指定のアカウントの指定のトークン種別の認証トークンを取得する。 アカウントマネージャがキャッシュしている場合はそれが返される。 キャッシュされていない場合は authenticator がリクエストを処理する。
      • API level 22 以下では USE_CREDENTIALS パーミッションが必要。
    • blockingGetAuthToken
  • getAuthTokenByFeatures(String, String, String[], Activity, Bundle, Bundle, AccountManagerCallback<Bundle>, Handler)
    • 他のメソッド (getAccountsByTypeAndFeaturesgetAuthTokenaddAccount) の処理を組み合わせた便利メソッド。
    • API level 22 以下では MANAGE_ACCOUNTS パーミッションが必要。
  • invalidateAuthToken
  • hasFeatures
    • 指定のアカウントが指定の全ての機能を持っているかどうかを調べる。
    • 呼び出し側は GET_ACCOUNTS パーミッションを持っているか、もしくは Authenticator と同じシグネチャである必要がある。
自社アプリ (同じシグネチャを持つアプリ) のみ
  • removeAccount(Account, Activity, AccountManagerCallback<Bundle>, Handler)
    • アカウントマネージャからアカウントを削除する。 Authenticator は固有のポリシーによってアカウント削除をしないという選択をしてよい。
    • 呼び出し側は Authenticator アプリと同じシグネチャを持つ必要がある。
    • API level 22 以下では MANAGE_ACCOUNTS パーミッションが必要。
  • removeAccount(Account, AccountManagerCallback<Boolean>, Handler)
    • 上に同じ。 Deprecated。
  • renameAccount(Account, String, AccountManagerCallback<Account>, Handler)
    • 古いアカウントを削除し、古いアカウントのユーザーデータを持つ新しいアカウントを追加するのと同じ効果。 (Auth token はどうなるんだろ?)
    • 呼び出し側は Authenticator と同じシグネチャを持つ必要がある。
    • API level 22 以下では AUTHENTICATE_ACCOUNTS パーミッションと、authenticator と同じ UID である必要がある。
  • clearPassword
    • アカウントマネージャが保持しているパスワードを削除する。 「ログアウト」 ボタンの処理などで使われることを想定しているらしい。
    • setPassword メソッドでも同じことができるけどこっちの方が必要な権限は少ないっぽい。
    • アカウントの Authenticator と同じシグネチャが必要。
  • editProperties
    • Authenticator 固有の、Authenticator のプロパティ (アカウントごとではない) を編集する。 対応する Authenticator がこのリクエストを処理する? (未調査。)
    • 全ての Authenticator がこの機能を実装しなければならないわけではない。
    • Authenticator と同じシグネチャが必要。
    • API level 22 以下の場合は MANAGE_ACCOUNTS パーミッションが必要。

Authenticator (または、それに関連する処理) 向け

  • addAccountExplicitly(Account, String, Bundle)
    • アカウントをアカウントマネージャに追加する。
    • Authenticator に関連するサインアップ処理向け。
    • addAccount メソッドなどのシステムがアカウント追加を検知できるメソッド経由で呼ばれたわけじゃない場合は、自前で notifyAccountAuthenticated メソッドを呼ぶべき。
  • removeAccountExplicitly(Account)
    • アカウントをアカウントマネージャから削除する。
    • クライアント用のアカウント削除メソッドとは違い、Authenticator でのポリシー設定は見られないのだと思う。 (未調査。)
    • API level 22 以下では AUTHENTICATE_ACCOUNTS パーミッションと、さらに Authenticator と同じ UID である必要がある。
  • notifyAccountAuthenticated(Account)
    • アカウントが認証されたことをシステムに伝える。
    • このイベント情報が他のアプリで使われたりするっぽい? (未調査。)
    • 呼び出し側はアカウントの Authenticator と同じシグネチャを持つ必要がある。
  • getPassword
    • アカウントマネージャが保持しているパスワードを取得する。
    • 呼び出し側は Authenticator と同じシグネチャを持っている必要がある。
    • API level 22 以下では AUTHENTICATE_ACCOUNTS パーミッションが必要。
  • setPassword(Account, String)
    • アカウントマネージャにパスワードを保存する。 (あるいは削除する。)
    • Authentiator と同じシグネチャを持つ必要がある。
    • API level 22 以下では AUTHENTICATE_ACCOUNTS パーミッションと、さらに Authenticator と同じ UID である必要がある。
  • getUserData
  • setUserData(Account, String, String)
  • peekAuthToken(Account, String)
    • AccountManager のキャッシュから auth token を取得する。 なければ null が返される。
    • API level 22 以下では AUTHENTICATE_ACCOUNTS パーミッションが必要で、さらに Authenticator アプリの UID と同じ UID を持っている必要がある。
  • setAuthToken(Account, String, String)
    • Auth token をアカウントマネージャのキャッシュに追加する。
    • Authentiator と同じシグネチャを持つ必要がある。
    • API level 22 以下では AUTHENTICATE_ACCOUNTS パーミッションと、さらに Authenticator と同じ UID である必要がある。

その他

関連ページ