illab.nabal.proxy.WeiboContext.java Source code

Java tutorial

Introduction

Here is the source code for illab.nabal.proxy.WeiboContext.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.net.URI;
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.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONObject;

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

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

    /**
     * Web view control for Weibo OAuth.
     * 
     * @version 1.0 (2012-02-01)
     * @author <a href="mailto:tanito.jung@gmail.com">Tan Jung</a>
     */
    private class WeiboWebViewClient extends AbstractNetworkWebViewClient {

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

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

        /**
         * Handle response URL.
         * 
         * @param view
         * @param url
         */
        private void handleResponseUrl(WebView view, String url) {

            // if it's ?(authorization callback) URL 
            if (url.startsWith(mSnsProperties.getWeiboOAuthCallbackUrl()) == true) {
                view.stopLoading();
                Bundle bundle = ParameterHelper.parseGetParams(url);
                dismissSpinner();
                // if parameter contains an error
                if (bundle.getString(ERROR) != null) {
                    String errMsg = bundle.getString(ERROR_DESCRIPTION);
                    // if user canceled authorization
                    if (errMsg.indexOf(DENIED) > -1) {
                        onAuthCancel();
                    }
                    // if an error occurred
                    else {
                        onAuthError(errMsg);
                    }
                }
                // if user authorization granted successfully
                else {
                    Log.d(TAG, "successfully fetched access token from URL : " + url);
                    onAuthComplete(bundle);
                }
            }
            // if it's ??(user cancelation) URL
            else if (url.startsWith(mSnsProperties.getWeiboOAuthCancelUrl()) == true) {
                view.stopLoading();
                dismissSpinner();
                onAuthCancel();
            }
        }

        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            //Log.d(TAG, "shouldOverrideUrlLoading URL: " + url);
            if (StringHelper.isEmpty(url) == false) {
                handleResponseUrl(view, 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);
            // on some devices onReceivedError is called before shouldOverrideUrlLoading 
            if (StringHelper.isEmpty(failingUrl) == false
                    && (failingUrl.startsWith(mSnsProperties.getWeiboOAuthCallbackUrl()) == true
                            || failingUrl.startsWith(mSnsProperties.getWeiboOAuthCallbackUrl()) == true)) {
                handleResponseUrl(view, failingUrl);
            } else {
                view.stopLoading();
                dismissSpinner();
                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 Weibo.
     */
    private final static String WEIBO_AUTH_HOST = "api.weibo.com";

    /**
     * API host of Weibo.
     */
    private final static String API_HOST = "https://api.weibo.com/2";

    /**
     * URI path for OAuth.
     */
    private final static String AUTHORIZE_URI_PATH = "/oauth2/authorize";

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

    /**
     * Constant for grant type.
     */
    private final static String GRANT_TYPE = "grant_type";

    /**
     * Constant for authorization code.
     */
    private final static String AUTHORIZATION_CODE = "authorization_code";

    /**
     * Constant for authorization in lower case.
     */
    protected final static String AUTHORIZATION_LOWER_CASE = "authorization";

    /**
     * Constant for code in upper case.
     */
    protected final static String CODE_UPPER_CASE = "CODE";

    /**
     * Constant for remind in.
     */
    private final static String REMIND_IN = "remind_in";

    /**
     * Constant for UID.
     */
    private final static String UID = "uid";

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

    /**
     * Access Token reminder.
     */
    private String mRemindIn = "";

    /**
     * Expiry fetched from Service Provider.
     */
    private String mExpiresIn;

    /**
     * UID fetched from Service Provider.
     */
    private String mUid = "";

    /**
     * Get Access Token reminder.
     *
     * @return mRemindIn
     */
    protected String getRemindIn() {
        return mRemindIn;
    }

    /**
     * Get Access Token Expiry.
     *
     * @return mExpiresIn
     */
    protected String getExpiresIn() {
        return mExpiresIn;
    }

    /**
     * Get UID.
     *
     * @return mUid
     */
    protected String getUid() {
        return mUid;
    }

    /**
     * Inject a Weibo session.
     * 
     * @param accessToken
     */
    void injectSession(String accessToken) {
        mAccessToken = accessToken;
    }

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

    /**
    * Constructor for Weibo network context.
     * 
     * @param alias
     * @param context
     * @param sessionListener
     * @param systemProperties
     * @param snsProperties
     */
    WeiboContext(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;
    }

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

    /**
     * Fetch Request Token from Service Provider.
     * 
     * @deprecated
     * @throws Exception
     */
    protected void getRequestToken() throws Exception {
        Log.d(TAG, "Weibo uses OAuth 2.0 - no need to fetch a request token...");
    }

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

        // display OAuth web dialog
        displayWebDialog(new StringHelper().appendAllToString(SCHEME_HTTPS, WEIBO_AUTH_HOST, AUTHORIZE_URI_PATH,
                INTERROGATION_MARK, CLIENT_ID, EQUAL_MARK, mConsumerKey, AMPERSEND, RESPONSE_TYPE, EQUAL_MARK, CODE,
                AMPERSEND, REDIRECT_URI, EQUAL_MARK, mSnsProperties.getWeiboOAuthCallbackUrl()),
                new WeiboWebViewClient());
    }

    /**
     * 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);

                // notify the error
                onSessionInvalid(new AuthException(errMsg));
            }
        }
    }

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

        // notify the error
        onSessionInvalid(new AuthException("User declined your Weibo application."));
    }

    /**
     * Fetch Access Token from Service Provider.
     * 
     * @param bundle
     * @throws Exception
     */
    protected void getAccessToken(Bundle bundle) throws Exception {
        Log.d(TAG, "fetching access token...");

        // retrieve authorization code for fetching access token
        String authorizationCode = bundle.getString(CODE);

        // authorization code needed when fetching access token
        if (StringHelper.isEmpty(authorizationCode) == false) {
            Log.d(TAG, "authorizationCode : " + authorizationCode);

            // set parameters
            List<NameValuePair> params = ParameterHelper.addAllParams(
                    // since Twitter checks timestamp in seconds, nonce should be GUARANTEED to be unique
                    new BasicNameValuePair(CLIENT_ID, mConsumerKey),
                    new BasicNameValuePair(CLIENT_SECRET, mConsumerSecret),
                    new BasicNameValuePair(GRANT_TYPE, AUTHORIZATION_CODE),
                    new BasicNameValuePair(REDIRECT_URI, mSnsProperties.getWeiboOAuthCallbackUrl()),
                    new BasicNameValuePair(CODE, authorizationCode));

            // generate URI which lead us to retrieve Access Token and its Token Secret.
            HttpPost httpPost = new HttpPost(new URI(
                    new StringHelper().appendAllToString(SCHEME_HTTPS, WEIBO_AUTH_HOST, ACCESS_TOKEN_URI_PATH)));

            // set POST parameters
            httpPost.setEntity(new UrlEncodedFormEntity(params));

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

            // parse response string
            JSONObject responseJson = getResponseJson(httpPost);

            mAccessToken = responseJson.optString(ACCESS_TOKEN);
            mExpiresIn = responseJson.optString(EXPIRES_IN);
            mRemindIn = responseJson.optString(REMIND_IN);
            mUid = responseJson.optString(UID);

            // 02-01 07:06:10.591: D/WeiboContext(2730): responseString : {"access_token":"2.00yRhm9DRP6FjC5eb849c01c8okqfE","remind_in":"157679999","expires_in":157679999,"uid":"3173742054"}

            Log.i(TAG, "###############################");
            Log.i(TAG, "mAccessToken : " + mAccessToken);
            Log.i(TAG, "mExpiresIn : " + mExpiresIn);
            Log.i(TAG, "mRemindIn : " + mRemindIn);
            Log.i(TAG, "mUid : " + mUid);
            Log.i(TAG, "###############################");

            // if fetched token is empty, throw AuthException for further analysis
            if (StringHelper.isEmpty(mAccessToken) == true) {
                throw new AuthException("Access token you fetched is empty.", responseJson.getString(ERROR))
                        .setResponseString(responseJson.toString(4)).setResponseJson(responseJson);
            }

            // execute the stored job threads since OAuth has been finished
            onSessionEstablished();
        }
        // if authorization code is null or empty
        else {
            // notify the error
            onSessionInvalid(new AuthException("Authorization code cannot be null or empty."));
        }
    }

    /**
     * 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(ACCESS_TOKEN, mAccessToken) && commitPrefString(EXPIRES_IN, mExpiresIn)
                    && commitPrefString(REMIND_IN, mRemindIn) && commitPrefString(UID, mUid));
        }
    }

    /**
     * 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 (ACCESS_TOKEN.equals(key) == true) {
                        mAccessToken = value;
                    } else if (EXPIRES_IN.equals(key) == true) {
                        mExpiresIn = value;
                    } else if (REMIND_IN.equals(key) == true) {
                        mRemindIn = value;
                    } else if (UID.equals(key) == true) {
                        mUid = value;
                    }
                }
                // check integrity
                if (StringHelper.isAllFull(mAccessToken, mExpiresIn, mRemindIn, mUid) == true) {
                    Log.i(TAG, "successfully restored session data.");
                    Log.i(TAG, "###############################");
                    Log.i(TAG, "mAccessToken : " + mAccessToken);
                    Log.i(TAG, "mExpiresIn : " + mExpiresIn);
                    Log.i(TAG, "mRemindIn : " + mRemindIn);
                    Log.i(TAG, "mUid : " + mUid);
                    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;
        }
    }

}