oauth-signpost(Android)でGoogle App Engine(+OAuth)

2013-09-06T00:00:00+00:00 Android Google App Engine Java

タイトル通り。要件は以下

※但し以下のソースがプロトタイプ段階な為、めちゃくちゃすぎるので注意

要件

Androidのブラウザ等から共有機能を使ってURLをGoogle App Engine向けに送信する。でその際にGoogle App Engine側はOAuthServiceによりOAuth認可が必要な為、oauth-signpostを利用して共有機能から得られたURLを送出する

でActivityな流れ的には

  • 共有機能からURLをシェアする
  • OAuthに関するトークン等が取得されているのであれば、それを利用してそのままクラウド側にOAuthパラメーターを付与する形式でリクエスト(POST)する
  • OAuthに関するトークン等が取得されていないのであれば、取得する処理を行う。その際にoauth_callback辺りに自身のアプリでコールバックを処理出来るようにURLスキームを特定な物にする
  • ↑でコールバックされたActivityでoauth_verifierを取得して、アクセストークンを取得。その後SharedPreferencesに格納しておく
  • 上記のコールバック先に認証前に取得しておいたURLなクエリーを付与しておいて、認証後にそれを処理する

っていう事。あくまで3以降なのは3に特有する。から1や2の状態で4や5な所を意識する必要は無い(かも)

Androidでoauth-signpostを使ってゴニョゴニョ利用するにはどうするかって所をやってみる

セットアップ

oauth-signpostなライブラリが必要なので https://code.google.com/p/oauth-signpost からダウンロードしてくる。ダウンロードしてくるファイルは

  • signpost-core-バージョン.jar
  • signpost-commonshttp4-バージョン.jar

の2つだけで良い。でAndroidアプリプロジェクトのlibsディレクトリに突っ込んでおく

AndroidManifest.xml

<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="shareroid.app"
    android:versionCode="1"
    android:versionName="1.0">

    <uses-sdk android:minSdkVersion="8" android:maxSdkVersion="18" android:targetSdkVersion="8" />

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

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name">

        <activity
            android:name=".ShareroidActivity"
            android:label="@string/app_name"
            android:theme="@android:style/Theme.NoDisplay">

            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
            </intent-filter>
        </activity>

        <activity
            android:name=".OAuthCallbackActivity"
            android:label="@string/app_name">

            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <!-- shareroid://oauth/callbackでActivityがフックされる? -->
                <data android:scheme="shareroid" android:host="oauth" android:path="/callback" />
            </intent-filter>
        </activity>
    </application>
</manifest>

ShareroidActivity.java

package shareroid.app;

import oauth.signpost.exception.OAuthCommunicationException;
import oauth.signpost.exception.OAuthExpectationFailedException;
import oauth.signpost.exception.OAuthMessageSignerException;
import oauth.signpost.exception.OAuthNotAuthorizedException;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;

public class ShareroidActivity extends OAuthActivity {

    private static final String TAG = ShareroidActivity.class.getName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.v(TAG, "onCreate");

        final String query = getQuery(getIntent());

        if (!TextUtils.isEmpty(query)) {
            Token token = new Token(this);

            // SharedPreferencesにトークン等が保管されていればtrue
            if (token.isAuthorized()) {
                // OAuthActivity#send(String query)
                send(query);
            } else {
                new Thread() {
                    @Override
                    public void run() {
                        try {
                            // リクエストトークンを取得してauthorizationを行うURLを取得してACTION_VIEWでstartActivityする

                            Intent intent = new Intent(
                                Intent.ACTION_VIEW,
                                Uri.parse(acquireRequestToken(query))
                            );
                            startActivity(intent);
                        } catch (OAuthMessageSignerException e) {
                            e.printStackTrace();
                        } catch (OAuthNotAuthorizedException e) {
                            e.printStackTrace();
                        } catch (OAuthExpectationFailedException e) {
                            e.printStackTrace();
                        } catch (OAuthCommunicationException e) {
                            e.printStackTrace();
                        }
                    }
                }.start();
            }
        }

        finish();
    }

    // 共有機能を使って得られたURLを取得するメソッド
    private String getQuery(Intent intent) {
        String url = null;

        if (intent != null) {
            if(Intent.ACTION_SEND.equals(intent.getAction())) {
                if (intent.hasExtra(Intent.EXTRA_TEXT)) {
                    url = intent.getStringExtra(Intent.EXTRA_TEXT);
                }
            }
        }

        return url;
    }
}

共有機能(android.intent.action.SEND)からぶっ飛んでくるActviity。でその共有から送信されたデータ(URL)を取得、んでサーバーに投げる前に認可状態をチェックして認可してればそのままぶん投げる、認可してなければoauth-signpostな機能を使ってぶん投げる。でそのAPI自体はOAuthActivityっていう基底Activityクラスでメソッド定義してある。それは後術

OAuthActivity.java

上記で書いた基底Activityクラス

package shareroid.app;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

import oauth.signpost.OAuthConsumer;
import oauth.signpost.OAuthProvider;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
import oauth.signpost.commonshttp.CommonsHttpOAuthProvider;
import oauth.signpost.exception.OAuthCommunicationException;
import oauth.signpost.exception.OAuthExpectationFailedException;
import oauth.signpost.exception.OAuthMessageSignerException;
import oauth.signpost.exception.OAuthNotAuthorizedException;
import android.app.Activity;
import static shareroid.app.Constants.*;

public abstract class OAuthActivity extends Activity {

    // DefaultOAuthConsumerだとHttpRequest(apache-httpclient)をsign出来ないのでCommonsHttpOAuthConsumerを使う
    private static OAuthConsumer consumer = new CommonsHttpOAuthConsumer(
        OAUTH_CONSUMER_KEY,
        OAUTH_CONSUMER_SECRET
    );

    protected OAuthConsumer getConsumer() {
        return consumer;
    }

    protected String acquireRequestToken(String query) throws OAuthMessageSignerException, OAuthNotAuthorizedException, OAuthExpectationFailedException, OAuthCommunicationException {
        // 認可処理を行うURLを取得する。でその前に共有時に指定されたURLをクエリーに含んでおけばコールバック側でも取得できるので、コールバック時にそれを処理するようにする

        return getProvider().retrieveRequestToken(consumer, OAUTH_CALLBACK + "?query=" + query);
    }

    protected String[] acquireAccessToken(String verifier) throws OAuthMessageSignerException, OAuthNotAuthorizedException, OAuthExpectationFailedException, OAuthCommunicationException {
        getProvider().retrieveAccessToken(consumer, verifier);

        return new String[] {
            consumer.getToken(),
            consumer.getTokenSecret()
        };
    }

    protected void send(String query) {
        Token token = new Token(this);

        if (!token.isAuthorized())
            return;

        HttpClient httpClient = new DefaultHttpClient();

        try {
            List<nameValuePair> params = new ArrayList<nameValuePair>(1);
            params.add(new BasicNameValuePair("url", query));

            HttpPost request = new HttpPost("https://shareroid.appspot.com/push");
            request.setEntity(new UrlEncodedFormEntity(params));

            // OAuthConsumer.setTokenWithSecretメソッドをSharedPreferencesに保管されているトークンを取得して流しこむだけのメソッド
            token.sign(consumer);

            consumer.sign(request);

            int statusCode = httpClient.execute(
                request,
                new ResponseHandler<integer>() {
                    @Override
                    public Integer handleResponse(HttpResponse response)
                        throws ClientProtocolException, IOException {
                        return response.getStatusLine().getStatusCode();
                    }
                }
            );

            // 送信に成功したら通知する等
        } catch (OAuthMessageSignerException e) {
            e.printStackTrace();
        } catch (OAuthExpectationFailedException e) {
            e.printStackTrace();
        } catch (OAuthCommunicationException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            httpClient.getConnectionManager().shutdown();
        }
    }

    private OAuthProvider getProvider() {
        return new CommonsHttpOAuthProvider(
            BASE_URL + "/_ah/OAuthGetRequestToken",
            BASE_URL + "/_ah/OAuthGetAccessToken",
            BASE_URL + "/_ah/OAuthAuthorizeToken"
        );
    }
}

OAuthCallbackActivity.java

package shareroid.app;

import oauth.signpost.exception.OAuthCommunicationException;
import oauth.signpost.exception.OAuthExpectationFailedException;
import oauth.signpost.exception.OAuthMessageSignerException;
import oauth.signpost.exception.OAuthNotAuthorizedException;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;

public class OAuthCallbackActivity extends OAuthActivity {

    private static final String TAG = OAuthCallbackActivity.class.getName();

    @Override
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        Log.v(TAG, "onCraate");
        Intent intent = getIntent();

        if (Intent.ACTION_VIEW.equals(intent.getAction())) {
            Uri uri = intent.getData();

            if (uri != null) {
                String verifier = uri.getQueryParameter("oauth_verifier");
                String query = uri.getQueryParameter("query");

                try {
                    String[] tokens = acquireAccessToken(verifier);

                    // トークン等をSharedPreferencesに保管
                    Token.save(this, tokens[0], tokens[1]);

                    send(query);
                } catch (OAuthMessageSignerException e) {
                    e.printStackTrace();
                } catch (OAuthNotAuthorizedException e) {
                    e.printStackTrace();
                } catch (OAuthExpectationFailedException e) {
                    e.printStackTrace();
                } catch (OAuthCommunicationException e) {
                    e.printStackTrace();
                }
            }
        }

        finish();
    }
}

終わり。分かりづらいけど(ry

AccessibilityServiceを古いAndroidバージョンで動かす リポジトリのファイルを履歴もろとも削除する方法