AccountManagerを利用する

# AccountManagerとは
`AccountManager`とは、Androidにおいて様々なWebサービスのアカウントを管理するための仕組みのことです。
アプリは`AccountManager`を利用することで以下のような様々なことができるようになります。

* アカウント情報の取得
* アカウントの編集
* アカウントのトークン取得

それぞれの操作は`<uses-permission>`による制限がかけられています。
呼び出す機能に応じてに応じて必要な`<uses-permission>`が変わってくるため、必要な機能を十分に検討し、適切な`<uses-permission>`を設定する必要があります。

また、どの機能を利用する場合でもそのサービスに対応した**アカウント管理アプリ**がインストールされていなければ`AccountManager`を経由してアカウント情報を管理することはできません。

# アカウント情報の取得
`AccountManager`からアカウント情報を取得する方法はいくつかあります。
ほとんどの方法で`GET_ACCOUNTS`パーミッションが必要です。

```AndroidManifest.xml
    <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`クラスの配列となっています。

```java
    AccountManager manager = AccountManager.get(context);
    Account[] accountArray = manager.getAccounts();
```

## サービス種別を指定してアカウント情報を取得
サービス種別を指定してアカウント情報を指定するには`AccountManager#getAccountsByType()`を使用します。
この時指定する`type`は`Account#type`と同じもので、サービスごとに決められた文字列です。
例として、Googleのアカウントであれば以下のように取得します。

```java
    AccountManager manager = AccountManager.get(context);
    Account[] accountArray = manager.getAccountsByType("com.google");
```

## 特定の機能を持ったアカウント情報を取得
あるサービス種別のアカウントのうち、さらに特定の機能を持ったアカウントのみを取得したい場合、`AccountManager#getAccountsByTypeAndFeatures()`を使用します。
このメソッドは、「Gmailを利用することができるGoogleユーザ」といったような指定に使います。
https://developers.google.com/gmail/android/

また、このメソッドのみ`Account`クラスの配列ではなく`AccountManagerFuture`を返しますが、この`AccountManagerFuture`はメインスレッドで扱ってはいけないため、コールバックを設定できるようになっています。

```java
// 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`で受ける形になります。

```java
 @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`パーミッションが必要です。
以下では、アカウント編集に関する代表的なメソッドを紹介します。

```AndroidManifest.xml
    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
```

## アカウントの追加
サービス種別を指定してアカウントの追加を行うには、`AccountManager#addAccount()`を利用します。
通常、このメソッドを実行するとサービス種別に対応したアカウントの登録画面が表示され、そこでアカウント情報を入力してログインし、アカウントを追加する形になります。
従って、このメソッドでもコールバックを利用して結果を受け取る方法を使います。
ログインに成功した場合、`KEY_ACCOUNT_NAME`と`KEY_ACCOUNT_TYPE`を受け取ることができます。
ここで第二引数に指定した"mail"は`authTokenType`という引数で、後にトークンを取得する際に重要になります。

```java
        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`パーミッションのみで動作します。

```java
    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`パーミッションのみで動作します。

```java
        AccountManager manager = AccountManager.get(this);
        manager.clearPassword(new Account("user@gmail.com","com.google"));
```

# トークンの取得
`AccountManager`では、指定したアカウントからサービスに接続するためのトークンを取得することができます。
この仕組により、`AccountManager`で管理されているアカウントでWebサービスを利用する場合、同一端末でのログインは初回のみで良いことになります。
トークンの取得には`USE_CREDENTIALS`パーミッションが必要です。
また、アカウントを直接指定した場合でも`GET_ACCOUNTS`が必要な場合があるようなのでこちらもあったほうが良いでしょう。

```AndroidManifest.xml
    <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
    <uses-permission android:name="android.permission.USE_CREDENTIALS"/>
```

トークンを取得するメソッドはいくつかありますが、下記では最も単純な`getAuthToken()`を例にあげます。
このメソッドを最初に実行したとき、アプリがアカウントにアクセスすることを承認するかどうかをユーザに確認する画面が表示されます。
この承認画面により、`AccountManager`ではユーザが許可しない限りはトークンを取得できないという仕組みになっているのです。
また、第二引数の`authTokenType`は取得するトークンの種別を定義するもので、必ず指定する必要があります。
通常、この`authTokenType`は`AccountManager#addAccount()`で指定する`authTokenType`と同じものになります。

```java
        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`は新しいトークンを取得して返します。

```java
AccountManager manager = AccountManager.get(this);
manager.invalidateAuthToken("com.google",TOKEN);
```