Android アプリから Google アカウントを利用して GAE アプリケーションを利用する
Android と GAE を同時に Ecliipse からデバッグする環境は出来たので、Google アカウントを Android アプリケーションから利用してみる。
GAE アプリケーション側でログイン状態を判定
typea_android_apps.py
/cardroid というリクエストが来たら、cardroid パッケージ cardroid_request_handler モジュールの InitialCardroidPage クラスを呼び出すように設定
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from cardroid import cardroid_request_handler as cardroid_handler
class MainPage(webapp.RequestHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/plain'
self.response.out.write('Hello, webapp World!')
application = webapp.WSGIApplication([
('/', MainPage),
('/cardroid', cardroid_handler.InitialCardroidPage),
], debug=True)
def main():
run_wsgi_app(application)
if __name__ == "__main__":
main()
cardroid_request_handler.py
ユーザーがログイン状態であれば、ユーザーのニックネームとログアウトのリンク を返し、そうでなければ、ログインページへのリンクを返すようにする。
from google.appengine.ext import webapp
from google.appengine.api import users
class InitialCardroidPage(webapp.RequestHandler):
def get(self):
self.post()
def post(self):
user = users.get_current_user()
if user:
user_logout_url = users.create_logout_url("/")
return self.response.out.write("<html>%s <a href="%s">logout</a>" % (user.nickname(), user_logout_url));
else:
user_login_url = users.create_login_url('/cardroid')
return self.response.out.write("<html><a href="%s">login</a>" % (user_login_url));
PCのブラウザから確認
未ログイン状態の場合、ログインのリンクを表示
ログインする
ユーザーのニックネームとログアウトのリンクを表示
Android アプリケーションから Google アカウントを利用して GAE アプリケーションを呼び出す。
このあたりを参考に。
- http://github.com/Arachnid/AEAuth/blob/master/src/net/notdot/aeauth/AppInfo.java
- http://blog.notdot.net/2010/05/Authenticating-against-App-Engine-from-an-Android-app
- http://chrometophone.googlecode.com/svn-history/r26/trunk/android/src/com/google/android/apps/chrometophone/DeviceRegistrar.java
GaeUtil クラス
とりあえず、GAE 関係で利用するクラスをまとめるために作成。Static クラスの便利さに遅ればせながら気がついたので、使いまくり。
特に意味は無い。
PracticeUploader
名前は、自分が作っているアプリにちなんでいるだけでこちらも意味は無い。upload() メソッドの中からが本筋。
AccountManager
getAccounts() メソッドを利用すると、Android 端末で管理しているアカウント情報が取得できるようだ。
これらね。
で、それぞれのアカウントは type 情報を持っていて、getAccountsByType("com.google") で、Google アカウントの情報がとれてくる。
とりあえず、添え字=0 の値を利用しているが、マルチアカウントだと複数件とれてくるのかな?
その場合、選択させる仕組みも必要かも。
で、getAuthToken() メソッドを利用して、認証用のトークンを取得。
取得したアカウントオブジェクトと、"ah" と コールバック用のインスタンスを渡す。
Google アカウントは "ah" のようだ。認証用のトークンが取得できたら、渡したインスタンスがコールバックされる。
UploadPracticeCallback
このクラスも名前は意味がない(というか自分向け)、上記で参照したコード等は、非同期で処理を行ったりしているが、まずは挙動確認のため、べた書きする。
どうやら、認証が初回等必要な場合は、Intent intent = (Intent)bundle.get(AccountManager.KEY_INTENT); で Intent が取得できるようなので、startActivity してあげると、認証を確認する画面が表示される。
認証されていれば、String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN); で、認証トークンが取得できる。
Cookie の保持
認証トークンを含めて、GAE アプリケーションへ 以下の感じでGET リクエストを投げる。
https://{ GAEアプリケーションURL }/_ah/login?continue={ 認証後に遷移するURL }&auth={ 認証トークン }
http か、https かによって、Cookie に "ACSID" または、"SACSID" のキーが設定されてくるので、保持しておく。
実際のリクエスト
これで、実際にやりたいリクエストを投げる準備が整った。
上記で保持した Cookie を付加して、POST メソッドを投げる。
ログイン状態になっていれば、ニックネームが正しく LogCat にはかれるはず。
問題点
以下、GAE アプリケーションを、リリースして確認した。開発環境でデバッグしたい のだが、現時点ではどうしていいのかわからん。
お、来ました。とりあえず、成功!!
開発環境でどうやるかは、今後の課題。
デバッグモードとリリースモードでコードの切り替えが必要になったりするのかなぁ。
package info.typea.cardroid;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.DefaultHttpClient;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
public class GaeUtil {
/**
*
*/
public static class PracticeUploader {
private Context context;
public PracticeUploader(Context context) {
this.context = context;
}
public void upload() {
AccountManager accountManager = AccountManager.get(context);
Account[] accounts = accountManager.getAccountsByType("com.google");
if (accounts.length >= 0) {
Account account = accounts[0];
accountManager.getAuthToken(account,
"ah",
false,
new UploadPracticeCallback(this.context),
null);
}
}
}
/**
*
*/
public static class UploadPracticeCallback implements AccountManagerCallback {
private final String GAE_APP_URI = "http://typea-android-apps.appspot.com";
private Context context;
public UploadPracticeCallback(Context context) {
this.context = context;
}
@Override
public void run(AccountManagerFuture result) {
Bundle bundle;
try {
bundle = result.getResult();
Intent intent = (Intent)bundle.get(AccountManager.KEY_INTENT);
if (intent != null) {
this.context.startActivity(intent);
} else {
String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
DefaultHttpClient client = new DefaultHttpClient();
client.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false);
String uri = GAE_APP_URI + "/_ah/login?continue=/cardroid&auth=" + authToken;
HttpGet httpGet = new HttpGet(uri);
HttpResponse response = client.execute(httpGet);
String acsid = null;
for (Cookie cookie : client.getCookieStore().getCookies()) {
if ("SACSID".equals(cookie.getName()) ||
"ACSID".equals(cookie.getName())) {
acsid = cookie.getName() + "=" + cookie.getValue();
}
}
uri = GAE_APP_URI + "/cardroid";
HttpPost httpPost = new HttpPost(uri);
httpPost.setHeader("Cookie", acsid);
response = client.execute(httpPost);
InputStream in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String l = null;
while((l = reader.readLine()) != null) {
Log.i("MyApp",l);
}
}
} catch (OperationCanceledException e) {
showMessage(R.string.lbl_operation_canceled);
} catch (AuthenticatorException e) {
showMessage(R.string.lbl_authenticator_failed);
} catch (IOException e) {
showMessage(R.string.lbl_error);
}
}
/**
* @param resId
*/
private void showMessage(int resId) {
(Toast.makeText(this.context,resId, Toast.LENGTH_LONG)).show();
}
}
}
以上。

補足
1.アカウントを取得するには、
android.permission.GET_ACCOUNTS
パーミッションが必要。
2.型を指定せずにアカウントの配列を取得するには、
mgr.getAccounts()
ではなく、
mgr.getAccountsByType(null)
を利用する。
【重要】
Android からGoogleアカウント認証でGAEアクセスするとServer Error になってしまう
http://typea.info/blg/glob/2011/04/android-googlegaeserver-error.html
上記コードだけでは、認証トークンの期限切れに対応していない。期限切れの場合、サーバーエラーが発生するので、
public void invalidateAuthToken (String accountType, String authToken)
により、対処する必要あり。