illab.nabal.proxy.TwitterContext.java Source code

Java tutorial

Introduction

Here is the source code for illab.nabal.proxy.TwitterContext.java

Source

/*
 * Copyright (C) 2013-2014 Tan Jung
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package illab.nabal.proxy;

import illab.nabal.exception.AuthException;
import illab.nabal.proxy.AbstractProxy.SessionListener;
import illab.nabal.settings.SnsProperties;
import illab.nabal.settings.SystemProperties;
import illab.nabal.util.ParameterHelper;
import illab.nabal.util.StringHelper;

import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;

import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebView;

/**
 * Twitter network context.
 * 
 * @version 1.0, 02/10/14
 * @author <a href="mailto:tanito.jung@gmail.com">Tan Jung</a>
 */
class TwitterContext extends AbstractContext {
    final static String TAG = "TwitterContext";

    /**
     * Web view control for Twitter OAuth.
     * 
     * @version 1.0, 02/10/14
     * @author <a href="mailto:tanito.jung@gmail.com">Tan Jung</a>
     */
    private class TwitterWebViewClient extends AbstractNetworkWebViewClient {

        /**
          * Color for web view title.
          */
        private final static String TITLE_COLOR = "#4099FF";

        /**
         * Constructor for web view client.
         */
        private TwitterWebViewClient() {
            super(TITLE_COLOR);
        }

        /**
         * Flag indicating if authorization process has already been cancelled.
         */
        private boolean isAuthCancelled = false;

        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            //Log.d(TAG, "shouldOverrideUrlLoading URL: " + url);
            if (StringHelper.isEmpty(url) == false) {
                // when user has denied
                if (url.indexOf(DENIED) > -1) {

                    // make sure onAuthCancel is called only once
                    // (Android's WebViewClient often load the same page twice)
                    if (isAuthCancelled == false) {
                        isAuthCancelled = true;
                        view.stopLoading();
                        dismissSpinner();
                        onAuthCancel();
                    }
                }
                // when user has authorized 
                else if (url.startsWith(VALUE_OAUTH_CALLBACK) == true) {
                    view.stopLoading();
                    dismissSpinner();
                    Log.d(TAG, "successfully fetched access token from URL : " + url);
                    onAuthComplete(ParameterHelper.parseGetParams(url));
                }
            }
            // return false to have control on the web view
            return false;
        }

        public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
            //Log.d(TAG, "onReceivedError URL: " + failingUrl);
            view.stopLoading();
            dismissSpinner();
            if (errorCode != -2) {
                onAuthError(errorCode + " error : " + description);
            }
        }

        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            //Log.d(TAG, "onPageStarted URL: " + url);
            showSpinner();
        }

        public void onPageFinished(WebView view, String url) {
            //Log.d(TAG, "onPageFinished URL: " + url);
            dismissSpinner();
        }

        /**
        * Cancel OAuth dialog.
         */
        public void onCancel() {
            onAuthCancel();
        }
    }

    /**
     * Host of Twitter.
     */
    private final static String TWITTER_AUTH_HOST = "api.twitter.com";

    /**
     * API host of Twitter.
     */
    private final static String API_HOST = "https://api.twitter.com/1.1";

    /**
     * URI path for Request Token.
     */
    private final static String REQUEST_TOKEN_URI_PATH = "/oauth/request_token";

    /**
     * URI path for User Authorization
     */
    private final static String AUTHORIZE_URI_PATH = "/oauth/authorize";

    /**
     * URI path for Access Token.
     */
    private final static String ACCESS_TOKEN_URI_PATH = "/oauth/access_token";

    /**
     * Callback URI for fetching OAuth verifier.
     */
    private final static String VALUE_OAUTH_CALLBACK = "http://twconnect.success.com";

    /**
     * Constant for Twitter screen name.
     */
    private final static String SCREEN_NAME = "screen_name";

    /**
     * If this Twitter session is covert op.
     */
    private boolean mIsCovertOp;

    /**
     * User ID fetched from Service Provider.
     */
    protected String mUserId;

    /**
     * Screen name fetched from Service Provider.
     */
    private String mScreenName = "";

    /**
     * Get User ID.
     *
     * @return userId
     */
    protected String getUserId() {
        return mUserId;
    }

    /**
     * Get Twitter screen name.
     *
     * @return screenName
     */
    protected String getScreenName() {
        return mScreenName;
    }

    /**
     * Inject a Twitter session.
     * 
     * @param accessToken
     * @param accessTokenSecret
     */
    void injectSession(String accessToken, String accessTokenSecret) {
        mAccessToken = accessToken;
        mAccessTokenSecret = accessTokenSecret;
    }

    /**
    * Constructor for Twitter network context.
     * 
     * @param context
     * @param sessionListener
     * @param systemProperties
     * @param snsProperties
     */
    TwitterContext(Context context, SessionListener sessionListener, SystemProperties systemProperties,
            SnsProperties snsProperties) {
        super(context, sessionListener, systemProperties, snsProperties);
        mIsCovertOp = true;
    }

    /**
    * Constructor for Twitter network context.
     * 
     * @param alias
     * @param context
     * @param sessionListener
     * @param systemProperties
     * @param snsProperties
     */
    TwitterContext(String alias, Context context, SessionListener sessionListener,
            SystemProperties systemProperties, SnsProperties snsProperties) {
        super(alias, context, sessionListener, systemProperties, snsProperties);
        mIsCovertOp = false;
    }

    @Override
    protected String getApiHost() {
        return API_HOST;
    }

    // override for OAuth 1.0a authorization
    @Override
    protected synchronized HttpGet getHttpGet(String apiUri, List<NameValuePair> params) throws Exception {
        return getOAuthHttpGet(getApiHost() + apiUri, params);
    }

    // override for OAuth 1.0a authorization
    @Override
    protected synchronized HttpPost getHttpPost(String apiUri, List<NameValuePair> params) throws Exception {
        return getOAuthHttpPost(getApiHost() + apiUri, params);
    }

    // override for OAuth 1.0a authorization
    @Override
    protected HttpPost getMultipartHttpPost(String apiUri, List<NameValuePair> params, String paramNameForFile,
            File file) throws Exception {

        HttpPost httpPost = getOAuthHttpPost(getApiHost() + apiUri);

        return getMultipartHttpPost(httpPost, params, paramNameForFile, file);
    }

    /**
     * Purge Twitter session.
     * 
     * @return if Twitter session is purged from both memory and storage successfully
     */
    protected synchronized boolean purgeSession() {
        boolean isPurged = super.purgeSession();
        mUserId = null;
        mScreenName = null;
        return isPurged;
    }

    /**
     * Fetch Request Token from Service Provider.
     * 
     * @throws Exception
     */
    protected void getRequestToken() throws Exception {

        String apiFullUrl = new StringHelper().appendAllToString(SCHEME_HTTPS, TWITTER_AUTH_HOST,
                REQUEST_TOKEN_URI_PATH);

        // set OAuth Callback
        List<NameValuePair> additionalOAuthParams = ParameterHelper
                .addAllParams(new BasicNameValuePair(OAUTH_CALLBACK, VALUE_OAUTH_CALLBACK));

        HttpPost httpPost = (HttpPost) getOAuthRequestBase(POST, apiFullUrl, null, additionalOAuthParams, true);

        Log.d(TAG, "Get Token and Token Secrect from : " + httpPost.getURI().toString());

        // get response string using POST method
        String responseString = getResponseString(httpPost, false);

        // parse response string
        Bundle values = ParameterHelper.decodeGetParams(responseString);
        mRequestToken = values.getString(OAUTH_TOKEN);
        mRequestTokenSecret = values.getString(OAUTH_TOKEN_SECRET);

        Log.d(TAG, "requestToken : " + mRequestToken);
        Log.d(TAG, "requestTokenSecret :" + mRequestTokenSecret);
    }

    /**
     * Prompts user to provide authorization.
     */
    protected void getUserAuthorization() {
        Log.d(TAG, "redirecting to user authorization web page...");

        // Request Token must be provided to redirect to authorization web page 
        if (StringHelper.isEmpty(mRequestToken) == false) {

            // display OAuth web dialog
            displayWebDialog(
                    new StringHelper().appendAllToString(SCHEME_HTTPS, TWITTER_AUTH_HOST, AUTHORIZE_URI_PATH,
                            INTERROGATION_MARK, OAUTH_TOKEN, EQUAL_MARK, mRequestToken),
                    new TwitterWebViewClient());
        } else {
            Log.e(TAG, "Request Token cannot be null");

            // notify the error
            onSessionInvalid(new AuthException("Request Token cannot be null"));
        }
    }

    /**
     * Called when user authorizes app request on OAuth web dialog.
     * 
     * @param bundle
     */
    protected void onAuthComplete(final Bundle bundle) {
        Log.d(TAG, "called onAuthComplete()");
        mAuthWebDialog.dismiss();

        if (bundle != null) {
            Log.d(TAG, "bundle.size() : " + bundle.size());

            // create a new thread for HTTP connections 
            // since HTTP connections are forbidden on UI thread
            new Thread() {
                public void run() {
                    try {
                        // if user authorizes app request, 
                        // fetch access token on non-UI thread
                        getAccessToken(bundle);
                    } catch (Exception e) {
                        Log.e(TAG, e.getMessage());
                        onAuthError(e.getMessage());
                    }
                }
            }.start();

        } else {
            Log.e(TAG, "bundle IS NULL");
            onAuthError("bundle IS NULL");
        }
    }

    /**
     * Called when an error occured on OAuth web dialog.
     * 
     * @param errMsg
     */
    protected void onAuthError(String errMsg) {
        mAuthWebDialog.dismiss();

        // if session is already established, ignore errors from android web view 
        // because WebView#stopLoading() method doesn't work immediately 
        // so it can trigger WebViewClient#onReceivedError() occasionally
        if (isSessionEstablished() == false) {

            // notify the error only if session is currently being fetched
            if (isFetchingSession() == true) {
                Log.d(TAG, "called onAuthError()");
                Log.i(TAG, "isFetchingSession : " + isFetchingSession());
                Log.i(TAG, "isSessionEstablished() : " + isSessionEstablished());
                Log.e(TAG, "an error occurred while authorization : " + errMsg);

                onSessionInvalid(new AuthException(errMsg));
            }
        }
    }

    /**
     * Called if user canceled OAuth authorization.
     */
    protected void onAuthCancel() {
        Log.d(TAG, "called onAuthCancel()");
        Log.d(TAG, "user declined your Twitter application.");
        mAuthWebDialog.dismiss();

        onSessionInvalid(new AuthException("User declined your Twitter application."));
    }

    /**
     * Fetch Access Token from Service Provider.
     * 
     * @param bundle
     * @throws Exception
     */
    protected void getAccessToken(Bundle bundle) throws Exception {

        // retrieve OAuth Verifier
        mOauthVerifier = bundle.getString(OAUTH_VERIFIER);
        Log.d(TAG, "oauthVerifier :" + mOauthVerifier);

        String apiFullUrl = new StringHelper().appendAllToString(SCHEME_HTTPS, TWITTER_AUTH_HOST,
                ACCESS_TOKEN_URI_PATH);

        // set Request Token and Verifier
        List<NameValuePair> additionalOAuthParams = ParameterHelper.addAllParams(
                new BasicNameValuePair(OAUTH_VERIFIER, mOauthVerifier),
                new BasicNameValuePair(OAUTH_TOKEN, mRequestToken));

        HttpPost httpPost = (HttpPost) getOAuthRequestBase(POST, apiFullUrl, null, additionalOAuthParams, true);

        Log.d(TAG, "Get Token and Token Secrect from : " + httpPost.getURI().toString());

        // get response string using POST method
        String responseString = getResponseString(httpPost, false);

        // parse response string
        Bundle responseValues = ParameterHelper.decodeGetParams(responseString);
        mAccessToken = responseValues.getString(OAUTH_TOKEN);
        mAccessTokenSecret = responseValues.getString(OAUTH_TOKEN_SECRET);
        mUserId = responseValues.getString(USER_ID);
        mScreenName = responseValues.getString(SCREEN_NAME);

        Log.i(TAG, "###############################");
        Log.i(TAG, "mAccessToken : " + mAccessToken);
        Log.i(TAG, "mAccessTokenSecret : " + mAccessTokenSecret);
        Log.i(TAG, "mUserId : " + mUserId);
        Log.i(TAG, "mScreenName : " + mScreenName);
        Log.i(TAG, "###############################");

        // execute the stored job threads since OAuth has been finished
        onSessionEstablished();
    }

    /**
     * Get OAuth-1.0a-ready HTTP GET request.
     * 
     * @param apiFullUrl
     * @return HttpGet
     * @throws Exception
     */
    protected HttpGet getOAuthHttpGet(String apiFullUrl) throws Exception {

        // set Access Token
        List<NameValuePair> additionalOAuthParams = ParameterHelper
                .addAllParams(new BasicNameValuePair(OAUTH_TOKEN, getAccessToken()));
        /*
        return (HttpGet) getOAuthRequestBase(
        GET, apiFullUrl, (List<NameValuePair>) null, additionalOAuthParams);
        */
        return (HttpGet) getOAuthRequestBase(GET, apiFullUrl, (List<NameValuePair>) null, additionalOAuthParams);
    }

    /**
     * Get OAuth-1.0a-ready HTTP GET request.
     * 
     * @param apiFullUrl
     * @param params
     * @return HttpGet
     * @throws Exception
     */
    protected HttpGet getOAuthHttpGet(String apiFullUrl, List<NameValuePair> params) throws Exception {

        // set Access Token
        List<NameValuePair> additionalOAuthParams = ParameterHelper
                .addAllParams(new BasicNameValuePair(OAUTH_TOKEN, getAccessToken()));

        return (HttpGet) getOAuthRequestBase(GET, apiFullUrl, params, additionalOAuthParams);
    }

    /**
     * Get OAuth-1.0a-ready HTTP POST request.
     * 
     * @param apiFullUrl
     * @return HttpPost
     * @throws Exception
     */
    protected HttpPost getOAuthHttpPost(String apiFullUrl) throws Exception {

        // set Access Token
        List<NameValuePair> additionalOAuthParams = ParameterHelper
                .addAllParams(new BasicNameValuePair(OAUTH_TOKEN, getAccessToken()));

        return (HttpPost) getOAuthRequestBase(POST, apiFullUrl, (List<NameValuePair>) null, additionalOAuthParams);
    }

    /**
     * Get OAuth-1.0a-ready HTTP POST request.
     * 
     * @param apiFullUrl
     * @param params
     * @return HttpPost
     * @throws Exception
     */
    protected HttpPost getOAuthHttpPost(String apiFullUrl, List<NameValuePair> params) throws Exception {

        // set Access Token
        List<NameValuePair> additionalOAuthParams = ParameterHelper
                .addAllParams(new BasicNameValuePair(OAUTH_TOKEN, getAccessToken()));

        return (HttpPost) getOAuthRequestBase(POST, apiFullUrl, params, additionalOAuthParams);
    }

    /**
     * Get OAuth-1.0a-ready HTTP request for test use only.
     * 
     * @param httpMethod
     * @param apiFullUrl
     * @param params
     * @param additionalOAuthParams
     * @return HttpUriRequest HttpPost or HttpGet
     * @throws Exception
     */
    private HttpUriRequest getOAuthRequestBase(String httpMethod, String apiFullUrl, List<NameValuePair> params,
            List<NameValuePair> additionalOAuthParams) throws Exception {
        return getOAuthRequestBase(httpMethod, apiFullUrl, params, additionalOAuthParams, false);
    }

    /**
     * Get OAuth-1.0a-ready HTTP request for test use only.
     * 
     * @param httpMethod POST or GET
     * @param apiFullUrl
     * @param params
     * @param additionalOAuthParams
     * @param isAuthorizationCall true if the current request is a part of 
     *       OAuth authorization process
     * @return HttpUriRequest HttpPost or HttpGet
     * @throws Exception
     */
    private HttpUriRequest getOAuthRequestBase(String httpMethod, String apiFullUrl, List<NameValuePair> params,
            List<NameValuePair> additionalOAuthParams, boolean isAuthorizationCall) throws Exception {

        HttpUriRequest httpRequest = null;

        // set OAuth parameters
        List<NameValuePair> oauthParams = ParameterHelper.addAllParams(
                // since Twitter checks timestamp in seconds, nonce should be GUARANTEED to be unique
                new BasicNameValuePair(OAUTH_NONCE, getSuperNonce()),
                new BasicNameValuePair(OAUTH_TIMESTAMP, getTimestamp()),
                new BasicNameValuePair(OAUTH_CONSUMER_KEY, mConsumerKey),
                new BasicNameValuePair(OAUTH_SIGNATURE_METHOD, HMAC_SHA1),
                new BasicNameValuePair(OAUTH_VERSION, OAUTH_VESION_1_0));

        // add additional OAuth parameters if exist
        if (additionalOAuthParams != null && additionalOAuthParams.size() > 0) {
            oauthParams.addAll(additionalOAuthParams);
        }

        // generate OAuth signature base string
        List<NameValuePair> baseStringParams = new ArrayList<NameValuePair>();
        baseStringParams.addAll(oauthParams);
        if (params != null && params.size() > 0) {
            baseStringParams.addAll(params);
        }
        String baseString = getBaseString(httpMethod, apiFullUrl, baseStringParams);

        // generate Authorization header string 
        String headerString = getOAuthHeaderString(
                ParameterHelper.addSignature(getSecretKey(), baseString, oauthParams));

        Log.d(TAG, "headerString : " + headerString);

        // add POST parameters to request body
        if (POST.equals(httpMethod)) {
            httpRequest = new HttpPost(new URI(apiFullUrl));
            if (params != null && params.size() > 0) {
                ((HttpPost) httpRequest)
                        .setEntity(new UrlEncodedFormEntity(params, org.apache.http.protocol.HTTP.UTF_8));
            }
        }

        // add GET query strings
        else if (GET.equals(httpMethod)) {
            String paramString = "";
            if (params != null && params.size() > 0) {
                paramString = "?" + URLEncodedUtils.format(params, org.apache.http.protocol.HTTP.UTF_8);
            }
            httpRequest = new HttpGet(new URI(apiFullUrl + paramString));
        }

        // add Authorization header to request header
        httpRequest.addHeader(AUTHORIZATION, headerString);

        // return HTTP request
        return httpRequest;
    }

    /**
     * Store session data to persistent storage.
     * 
     * @return true if successfully stored
     * @throws Exception
     */
    protected boolean storeSession() throws Exception {

        // no need to store session if this session is covert op
        if (mIsCovertOp == true) {
            return true;
        }

        // store session
        synchronized (mAccessToken) {
            if (StringHelper.isEmpty(mAlias) == false) {
                Log.i(TAG, "storing session data to storage" + " for agent " + mAlias + " ...");
            } else {
                Log.i(TAG, "storing session data to storage...");
            }
            // store session data to shared preferences file
            return (commitPrefString(OAUTH_TOKEN, mAccessToken)
                    && commitPrefString(OAUTH_TOKEN_SECRET, mAccessTokenSecret)
                    && commitPrefString(USER_ID, mUserId) && commitPrefString(SCREEN_NAME, mScreenName));
        }
    }

    /**
     * Restore session data from persistent storage. 
     * 
     * @return true if successfully restored from persistent storage
     * @throws Exception
     */
    protected boolean restoreSession() throws Exception {

        // no need to restore session if this session is covert op
        if (mIsCovertOp == true) {
            return false;
        }

        // restore session
        synchronized (mAccessToken) {
            if (StringHelper.isEmpty(mAlias) == false) {
                Log.i(TAG, "trying to restore session data from storage" + " for agent " + mAlias + " ...");
            } else {
                Log.i(TAG, "trying to restore session data from storage...");
            }
            try {
                // get a map from shared preferences file stored in persistent storage
                Map<String, String> map = getPrefMap();
                for (String key : map.keySet()) {
                    // assign values to variables read from storage
                    String value = (String) map.get(key);
                    if (OAUTH_TOKEN.equals(key) == true) {
                        mAccessToken = value;
                    } else if (OAUTH_TOKEN_SECRET.equals(key) == true) {
                        mAccessTokenSecret = value;
                    } else if (USER_ID.equals(key) == true) {
                        mUserId = value;
                    } else if (SCREEN_NAME.equals(key) == true) {
                        mScreenName = value;
                    }
                }
                // check integrity
                if (StringHelper.isAllFull(mAccessToken, mAccessTokenSecret, mUserId, mScreenName) == true) {
                    Log.i(TAG, "successfully restored session data.");
                    Log.i(TAG, "###############################");
                    Log.i(TAG, "mAccessToken : " + mAccessToken);
                    Log.i(TAG, "mAccessTokenSecret : " + mAccessTokenSecret);
                    Log.i(TAG, "mUserId : " + mUserId);
                    Log.i(TAG, "mScreenName : " + mScreenName);
                    Log.i(TAG, "###############################");
                    return true;
                }
            } catch (ClassCastException e) {
                Log.e(TAG, e.getMessage());
            }
            // make sure empty session data if failed to restore
            Log.i(TAG, "failed to restore session data.");
            purgeSession();
            return false;
        }
    }

}