2015/04/24

Android : Google Sign-In

Prerequisites

  • Android 2.3 以上, あるいは最新のGoogle Play Storeを搭載していること.
  • 最新のAndroid SDK Toolsをインストールしていること.
  • Google Play Service SDKをインストールしていること.
  • コンパイルバージョンがAndroid2.3以上であること.

Step 1: Google Sign-In APIの有効化

  1. Google Developers Consoleへ移動
  2. プロジェクトを作成する
  3. AndroidアプリケーションとしてクライアントIDを作成する

NOTE
デバッグ用.keystoreのSHA1ハッシュを確認するには次のコマンドを参照.

keytool -exportcert -alias androiddebugkey -keystore path-to-debug-or-production-keystore -list -v

デバッグ用.keystoreはMacだと~/.android/debug.keystore. Windowsでは%USERPROFILE%\.android\debug.keystoreに標準で格納されている.

デバッグ用keystoreのパスワードはandroid.

Step 2: Android Studio Project の設定

build.gradleのdependenciesセクションにGoogle Play Serviceを追加.


dependencies {
    compile 'com.google.android.gms:play-services:7.0.0'
}

Step 3: Permission宣言

アプリケーションが使用するGoogle Play ServiceのバージョンをAndroidManifestのapplicationタグ配下に宣言する.

<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />

Google APIにアクセスするためINTERNETパーミッションを宣言する.

<uses-permission android:name="android.permission.INTERNET" />

アカウント名を取得するためGET_ACCOUNTパーミッションを宣言する.

<uses-permission android:name="android.permission.GET_ACCOUNTS" />

OAuth 2.0 tokenを取得するためUSE_CREDENTIALSパーミッションを宣言する.

<uses-permission android:name="android.permission.USE_CREDENTIALS" />

GoogleApiClient の初期化

GoogleApiClientはGoogle Play Serviceに接続されるServiceConnectionのラッパーとして作用する.
GoogleApiClientはGoogle Sign-In APIと通信し, 非同期通信によりサービスを有効化した後機能する. そのためには,

  • Google Play Serviceがデバイス上で実行されており, あなたのActivityがService connectionに接続できていること.
  • ユーザがあなたのアプリでアカウントを選択し, ユーザのアカウントがあなたのアプリに権限を与えていること.

GoogleApiClientのライフサイクルをActivityのライフサイクルの中で管理するための一般的な方法は下記.

  1. Activity.onCreateでGoogleApiClientを初期化
  2. Activity.onStartでGoogleApiClient.connectを起動
  3. Activity.onStopでGoogleApiClient.disconnectを起動

これを実行するとActivityにはコネクションが有効化あるいは接続失敗したことがConnectionCallbacksとOnConnectionFailedListenerに通知される.

addScopeメソッドでアプリが許容を求めるスコープを定義できる.

  • 基本的なプロフィール情報が必要であればprofile
  • ユーザのGoogle account emailアドレスが必要であればemail
import android.app.Activity;
import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.view.View;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.Scope;
import com.google.android.gms.plus.Plus;

public class MainActivity extends Activity implements
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener,
        View.OnClickListener {

    /* Request code used to invoke sign in user interactions. */
    private static final int RC_SIGN_IN = 0;

    /* Client used to interact with Google APIs. */
    private GoogleApiClient mGoogleApiClient;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(Plus.API)
                .addScope(new Scope("profile"))
                .build();

       findViewById(R.id.sign_in_button).setOnClickListener(this);
    }

    @Override
    protected void onStart() {
        super.onStart();
        mGoogleApiClient.connect();
    }

    @Override
    protected void onStop() {
        super.onStop();
        mGoogleApiClient.disconnect();
    }

    @Override
    public void onClick(View v) {
      // ...
    }

    // ...

  }

GoogleApiClientがコネクションを有効化できなかった時, onConnectionFailedのコールバックが呼ばれる. これにはConnectionResultが渡されるためエラーの解決に使用できる. ConnectionResult.getResolution()を呼ぶとユーザにアカウントを選択する操作を促すためのPendingIntentを得ることができる.
(例えば年とワーク接続を有効かするように求めたり, アカウントの選択をユーザに求めたりする)

@Override
public void onConnectionFailed(ConnectionResult result) {
  if (!mIntentInProgress && result.hasResolution()) {
    try {
      mIntentInProgress = true;
      result.startResolutionForResult(this, RC_SIGN_IN);
    } catch (SendIntentException e) {
      // The intent was canceled before it was sent.  Return to the default
      // state and attempt to connect to get an updated ConnectionResult.
      mIntentInProgress = false;
      mGoogleApiClient.connect();
    }
  }
}

@Override
public void onConnected(Bundle connectionHint) {
  // We've resolved any connection errors.  mGoogleApiClient can be used to
  // access Google APIs on behalf of the user.
}

@Override
protected void onActivityResult(int requestCode, int responseCode, Intent intent) {
  if (requestCode == RC_SIGN_IN) {
    mIntentInProgress = false;

    if (!mGoogleApiClient.isConnected()) {
      mGoogleApiClient.reconnect();
    }
  }
}

クライアントのサービス接続が確立されたらGoogleApiClient.disconnectをonStopメソッドの中で呼び出しこれを切断する必要がある.

Google Play ServiceはActivityがサービスコネクションをロストした際に呼ばれるonConnectionSuspendedコールバックを呼び出す. 一般的にこの場合はユーザが解決を試みることができるようにサービスへの再接続を試みる.

@Override
public void onConnectionSuspended(int cause) {
  mGoogleApiClient.connect();
}

Google Sign-In ボタンの追加

  1. アプリケーションのレイアウトにSignInButtonを追加
<com.google.android.gms.common.SignInButton
    android:id="@+id/sign_in_button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
  1. OnClickListenerを登録する
findViewById(R.id.sign_in_button).setOnClickListener(this);
  1. Sign-Inボタンを使用するにはActivityのSign-Inフローを変更する必要がある. ActivityがOnCnnectionFaildコールバックを受け取ったらすぐに問題解決のためのユーザインタラクションを開始する. Activityはその間Sing-inボタンのクリックを抑止するべき.
/**
 * True if the sign-in button was clicked.  When true, we know to resolve all
 * issues preventing sign-in without waiting.
 */
private boolean mSignInClicked;

/**
 * True if we are in the process of resolving a ConnectionResult
 */
private boolean mIntentInProgress;

@Override
public void onConnectionFailed(ConnectionResult result) {
  if (!mIntentInProgress) {
    if (mSignInClicked && result.hasResolution()) {
      // The user has already clicked 'sign-in' so we attempt to resolve all
      // errors until the user is signed in, or they cancel.
      try {
        result.startResolutionForResult(this, RC_SIGN_IN);
        mIntentInProgress = true;
      } catch (SendIntentException e) {
        // The intent was canceled before it was sent.  Return to the default
        // state and attempt to connect to get an updated ConnectionResult.
        mIntentInProgress = false;
        mGoogleApiClient.connect();
      }
    }
  }
}
  1. ユーザがSign-Inボタンをクリックした後, mSignInClickedフラグをセットし, onConnectionFailedで接続エラーを解決する必要がある. 解決可能なエラーはユーザにサービスへの接続とアプリへの権限承認を促す.
public void onClick(View view) {
  if (view.getId() == R.id.sign_in_button && !mGoogleApiClient.isConnecting()) {
    mSignInClicked = true;
    mGoogleApiClient.connect();
  }
}
  1. 接続が確立された場合はmSignInClickedフラグをリセットする.
protected void onActivityResult(int requestCode, int responseCode, Intent intent) {
  if (requestCode == RC_SIGN_IN) {
    if (responseCode != RESULT_OK) {
      mSignInClicked = false;
    }

    mIntentInProgress = false;

    if (!mGoogleApiClient.isConnected()) {
      mGoogleApiClient.reconnect();
    }
  }
}
  1. ユーザがSign-Inを無事終えるとonConnectedが呼ばれる. この時点でユーザの名前を取得するか認証リクエストを作成することができる.
@Override
public void onConnected(Bundle connectionHint) {
  mSignInClicked = false;
  Toast.makeText(this, "User is connected!", Toast.LENGTH_LONG).show();
}

以上.