Android アプリから Google アカウントを利用して GAE アプリケーションを利用する

Android と GAE を同時に Ecliipse からデバッグする環境は出来たので、Google アカウントを Android アプリケーションから利用してみる。

GAE アプリケーション側でログイン状態を判定

android_gae_07

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_gae_08

ログインする

android_gae_09

ユーザーのニックネームとログアウトのリンクを表示

 android_gae_10

Android アプリケーションから Google アカウントを利用して GAE アプリケーションを呼び出す。

このあたりを参考に。

GaeUtil クラス

とりあえず、GAE 関係で利用するクラスをまとめるために作成。Static クラスの便利さに遅ればせながら気がついたので、使いまくり。

特に意味は無い。

PracticeUploader

名前は、自分が作っているアプリにちなんでいるだけでこちらも意味は無い。upload() メソッドの中からが本筋。

AccountManager

getAccounts() メソッドを利用すると、Android 端末で管理しているアカウント情報が取得できるようだ。

android_gae06

これらね。

で、それぞれのアカウントは type 情報を持っていて、getAccountsByType("com.google")  で、Google アカウントの情報がとれてくる。

とりあえず、添え字=0 の値を利用しているが、マルチアカウントだと複数件とれてくるのかな?

その場合、選択させる仕組みも必要かも。

で、getAuthToken() メソッドを利用して、認証用のトークンを取得。

取得したアカウントオブジェクトと、"ah" と コールバック用のインスタンスを渡す。

Google アカウントは "ah"  のようだ。認証用のトークンが取得できたら、渡したインスタンスがコールバックされる。

UploadPracticeCallback

このクラスも名前は意味がない(というか自分向け)、上記で参照したコード等は、非同期で処理を行ったりしているが、まずは挙動確認のため、べた書きする。

どうやら、認証が初回等必要な場合は、Intent intent = (Intent)bundle.get(AccountManager.KEY_INTENT);  で Intent が取得できるようなので、startActivity してあげると、認証を確認する画面が表示される。

android_gae03

認証されていれば、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 アプリケーションを、リリースして確認した。開発環境でデバッグしたい のだが、現時点ではどうしていいのかわからん。

 ==> 2010/09/08 解決(?) しました!

android_gae_11

お、来ました。とりあえず、成功!!

開発環境でどうやるかは、今後の課題。

デバッグモードとリリースモードでコードの切り替えが必要になったりするのかなぁ。

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();
        }
    }
}

  

以上。

Follow me!

Android アプリから Google アカウントを利用して GAE アプリケーションを利用する” に対して1件のコメントがあります。

  1. pppiroto (Hiroto YAGI) より:

    補足
    1.アカウントを取得するには、
    android.permission.GET_ACCOUNTS
    パーミッションが必要。
    2.型を指定せずにアカウントの配列を取得するには、
    mgr.getAccounts()
    ではなく、
    mgr.getAccountsByType(null)
    を利用する。

  2. pppiroto (Hiroto YAGI) より:

    【重要】
    Android からGoogleアカウント認証でGAEアクセスするとServer Error になってしまう
    http://typea.info/blg/glob/2011/04/android-googlegaeserver-error.html
    上記コードだけでは、認証トークンの期限切れに対応していない。期限切れの場合、サーバーエラーが発生するので、
    public void invalidateAuthToken (String accountType, String authToken)
    により、対処する必要あり。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です