illab.nabal.proxy.AbstractContext.java Source code

Java tutorial

Introduction

Here is the source code for illab.nabal.proxy.AbstractContext.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.exception.NetworkException;
import illab.nabal.exception.SystemException;
import illab.nabal.proxy.AbstractProxy.SessionListener;
import illab.nabal.settings.SnsProperties;
import illab.nabal.settings.SocialNetwork;
import illab.nabal.settings.SystemProperties;
import illab.nabal.util.ParameterHelper;
import illab.nabal.util.StorageHelper;
import illab.nabal.util.StringHelper;

import java.io.File;
import java.io.InputStream;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpTrace;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.json.JSONObject;

import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.http.AndroidHttpClient;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Window;
import android.webkit.WebViewClient;

/**
 * Helper for network contexts / OAuth consumers.
 * 
 * @version 1.0, 02/10/14
 * @author <a href="mailto:tanito.jung@gmail.com">Tan Jung</a>
 */
abstract class AbstractContext {
    final static String TAG = "AbstractContext";

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

        /**
          * Color for web view title.
          */
        private String mWebViewTitleColor;

        /**
         * Returns web view title color.
         * 
         * @return String
         */
        protected String getWebViewTitleColor() {
            return mWebViewTitleColor;
        }

        /**
         * Get SNS UID.
         * 
         * @return SNS UID
         */
        protected SocialNetwork getSnsUid() {
            return mSnsUid;
        }

        /**
         * Progress dialog for web view loading time.
         */
        private ProgressDialog mSpinner;

        /**
         * Constructor to set up progress dialog.
         */
        protected AbstractNetworkWebViewClient(String webViewTitleColor) {
            mWebViewTitleColor = webViewTitleColor;
        }

        /**
         * Show progress dialog.
         */
        protected void showSpinner() {
            // instantiate ProgressDialog here, NOT in constructor
            // because the constructor will be called on non-UI thread 
            if (mSpinner == null) {
                mSpinner = new ProgressDialog(mContext);
                mSpinner.requestWindowFeature(Window.FEATURE_NO_TITLE);
                mSpinner.setMessage(mSystemProperties.getLocalizedLodingMessage());
            }
            // show spinner
            if (mSpinner != null && mSpinner.isShowing() == false) {
                mSpinner.show();
            }
        }

        /**
         * Dismiss progress dialog.
         */
        protected void dismissSpinner() {
            // dismiss spinner
            if (mSpinner != null && mSpinner.isShowing() == true) {
                mSpinner.dismiss();
            }
        }

        /**
         * Cancel OAuth dialog.
         */
        protected abstract void onCancel();
    }

    /**
     * Constant for http.
     */
    protected final static String HTTP = "http";

    /**
     * Constant for https.
     */
    protected final static String HTTPS = "https";

    /**
     * Constant for scheme http.
     */
    protected final static String SCHEME_HTTP = "http://";

    /**
     * Constant for scheme https.
     */
    protected final static String SCHEME_HTTPS = "https://";

    /**
     * Constant for method GET.
     */
    protected final static String GET = "GET";

    /**
     * Constant for method POST.
     */
    protected final static String POST = "POST";

    /**
     * Constant for question mark.
     * 
     */
    protected final static String INTERROGATION_MARK = "?";

    /**
     * Constant for ampersend delimeter.
     */
    protected final static String AMPERSEND = "&";

    /**
     * Constant for equal mark.
     */
    protected final static String EQUAL_MARK = "=";

    /**
     * Constant for comma.
     */
    protected final static String COMMA = ", ";

    /**
     * Constant for comma.
     */
    protected final static String UNDERBAR = "_";

    /**
     * Constant for string OAuth in camel case.
     */
    protected final static String OAUTH_CAMEL_CASE = "OAuth";

    /**
     * Constant for string HMAC-SHA1 in camel case.
     */
    protected final static String HMAC_SHA1_CAMEL_CASE = "HmacSHA1";

    /**
     * Constant for signature method.
     */
    protected final static String HMAC_SHA1 = "HMAC-SHA1";

    /**
     * Constant for UTF-8;
     */
    protected final static String UTF_8 = "UTF-8";

    /**
     * Constant for OAuth callback - out of band.
     */
    protected final static String OUT_OF_BAND = "oob";

    /**
     * Constant for Authorization.
     */
    protected final static String AUTHORIZATION = "Authorization";

    /**
     * Constant for accepted.
     */
    protected final static String ACCEPTED = "accepted";

    /**
     * Constant for denied.
     */
    protected final static String DENIED = "denied";

    /**
     * Constant for code.
     */
    protected final static String CODE = "code";

    /**
     * Constant for token.
     */
    protected final static String TOKEN = "token";

    /**
     * Constant for OAuth Consumer Key.
     */
    protected final static String OAUTH_CONSUMER_KEY = "oauth_consumer_key";

    /**
     * Constant for OAuth Consumer Secret.
     */
    protected final static String OAUTH_CONSUMER_SECRET = "oauth_consumer_secret";

    /**
     * Constant for OAuth signature method.
     */
    protected final static String OAUTH_SIGNATURE_METHOD = "oauth_signature_method";

    /**
     * Constant for OAuth signature.
     */
    protected final static String OAUTH_SIGNATURE = "oauth_signature";

    /**
     * Constant for OAuth timestamp.
     */
    protected final static String OAUTH_TIMESTAMP = "oauth_timestamp";

    /**
     * Constant for OAuth nonce.
     */
    protected final static String OAUTH_NONCE = "oauth_nonce";

    /**
     * Constant for OAuth version.
     */
    protected final static String OAUTH_VERSION = "oauth_version";

    /**
     * Constant for OAuth callback.
     */
    protected final static String OAUTH_CALLBACK = "oauth_callback";

    /**
     * Constant for OAuth callback confirmed.
     */
    protected final static String OAUTH_CALLBACK_CONFIRMED = "oauth_callback_confirmed";

    /**
     * Constant for OAuth verifier.
     */
    protected final static String OAUTH_VERIFIER = "oauth_verifier";

    /**
     * Constant for OAuth token.
     */
    protected final static String OAUTH_TOKEN = "oauth_token";

    /**
     * Constant for OAuth token secret.
     */
    protected final static String OAUTH_TOKEN_SECRET = "oauth_token_secret";

    /**
     * Constant for Client ID.
     */
    protected final static String CLIENT_ID = "client_id";

    /**
     * Constant for Client Secret.
     */
    protected final static String CLIENT_SECRET = "client_secret";

    /**
     * Constant for Redirect URI.
     */
    protected final static String REDIRECT_URI = "redirect_uri";

    /**
     * Constant for Resonse Type.
     */
    protected final static String RESPONSE_TYPE = "response_type";

    /**
     * Constant for error.
     */
    protected final static String ERROR = "error";

    /**
     * Constant for error description.
     */
    protected final static String ERROR_DESCRIPTION = "error_description";

    /**
     * Constant for Access Token.
     */
    protected final static String ACCESS_TOKEN = "access_token";

    /**
     * Constant for Access Token Expiry.
     */
    protected final static String EXPIRES_IN = "expires_in";

    /**
     * Constant for User ID.
     */
    protected final static String USER_ID = "user_id";

    /**
     * Constant for Oauth version - 1.0.
     */
    protected final static String OAUTH_VESION_1_0 = "1.0";

    /**
     * Constant for Oauth version - 2.0.
     */
    protected final static String OAUTH_VESION_2_0 = "2.0";

    /**
     * System properties.
     */
    private SystemProperties mSystemProperties;

    /**
     * Social network properties.
     */
    protected SnsProperties mSnsProperties;

    /**
     * SNS UID.
     */
    private final SocialNetwork mSnsUid;

    /**
     * Op agent's alias to be used as prefix for session preservation 
     * entries in shared preferences file.
     */
    protected String mAlias = "";

    /**
     * Android context.
     */
    protected final Context mContext;

    /**
     * Android HTTP client.
     */
    private AndroidHttpClient mHttpClient;

    /**
     * Handler for UI thread.
     * <p><b>
     * Note that his handler must be instantiated on UI thread.
     * Otherwise an error will occur when accessing UI components.
     * </b></p>
     */
    protected final Handler mHandler = new Handler();

    /**
     * Persistent storage. 
     */
    private final StorageHelper mStorageHelper;

    /**
     * Dialog for OAuth Web.
     */
    protected Dialog mAuthWebDialog;

    /**
     * OAuth session listener.
     */
    protected final SessionListener mSessionListener;

    /**
     * Flag indicating if OAuth session is under process.
     */
    private boolean isFetchingSession = false;

    /**
     * Check if session is currently being fetched. 
     * 
     * @return boolean - if session is currently being fetched
     */
    protected synchronized boolean isFetchingSession() {
        synchronized (mAccessToken) {
            return isFetchingSession;
        }
    }

    /**
     * Check if session is already established and ready to use.
     * 
     * @return boolean - if session is already established
     */
    protected boolean isSessionEstablished() {
        synchronized (mAccessToken) {
            return !StringHelper.isEmpty(mAccessToken);
        }
    }

    /**
     * Callback when session is established.
     */
    protected void onSessionEstablished() {

        boolean isSessionStored = false;

        // store fetched session data
        try {
            isSessionStored = storeSession();
        }

        // if failed to store session data
        catch (Exception e) {
            Log.e(TAG, e.getMessage());
            Log.e(TAG, "failed to write session data to persistent storage");
        }

        // if session data is successfully stored in persistent storage
        if (isSessionStored == true) {

            // notify the success
            mSessionListener.onSessionEstablished();

            // set the flag off
            isFetchingSession = false;
        }

        // notify an error when failed to write to storage
        else {
            onSessionInvalid(new AuthException("Failed to write session data to persistent storage."));
        }
    }

    /**
     * Callback when error occurrs while establishing session.
     * 
     * @param e
     */
    protected void onSessionInvalid(NetworkException e) {

        // notify the error
        mSessionListener.onSessionInvalid(e);

        // set the flag off since fetching session info has failed
        isFetchingSession = false;
    }

    /**
     * Fetch session key/access token.
     */
    protected synchronized void fetchSession() {

        // set the flag on
        isFetchingSession = true;

        // try restoring session data from persistent storage
        boolean isSessionRestored = false;
        try {
            isSessionRestored = restoreSession();
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
            Log.e(TAG, "failed to restore session from storage");
        }

        // if session data is successfully restored from persistent storage
        if (isSessionRestored == true) {

            // no need to start OAuth session
            onSessionEstablished();
        }

        // if no session data restored, start a new OAuth session
        else {

            // create a new thread for HTTP connections (which is forbidden on UI thread) 
            new Thread() {
                public void run() {
                    try {
                        // Request Token Secret and Access Token Secret should be empty 
                        // when fetching Request Token 
                        mRequestTokenSecret = "";
                        mAccessTokenSecret = "";

                        // fetch Request Token first
                        getRequestToken();

                        // grant user authorization
                        getUserAuthorization();

                    } catch (Exception e) {
                        Log.e(TAG, e.getMessage());

                        // notify the error
                        onSessionInvalid(new NetworkException(e.getMessage()));
                    }
                }
            }.start();
        }
    }

    /**
     * Display OAuth web dialog.
     * 
     * @param networkWebViewClient
     * @param url
     */
    protected void displayWebDialog(final String url, final AbstractNetworkWebViewClient networkWebViewClient) {

        // post web view thread to UI thread
        mHandler.post(new Runnable() {
            public void run() {

                // cookie must be deleted so that multiple sessions can be established
                StorageHelper.clearCookies(mContext);

                // bring a dialog to display authorization web page.
                mAuthWebDialog = new AuthWebDialog(
                        // Android context 
                        mContext
                // URL to redirect to authoriaztion web page
                , url
                // client control for OAuth web view
                , networkWebViewClient
                // system properties
                , mSystemProperties);
                mAuthWebDialog.show();
            }
        });
    }

    /**
     * Remove session key/access token.
     * 
     * @return if session is purged from both memory and storage successfully
     */
    protected synchronized boolean purgeSession() {
        synchronized (mAccessToken) {
            boolean isSessionPurged = false;
            mAccessToken = "";
            mAccessTokenSecret = "";
            try {
                isSessionPurged = clearSharedPreferences();
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
                Log.e(TAG, "Error occurred while purging " + "session data stored in persistent storage");
            }
            return isSessionPurged;
        }
    }

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

    /**
     * Prompts user to provide authorization via OAuth web dialog.
     */
    protected abstract void getUserAuthorization();

    /**
     * Called when user authorizes app request on OAuth web dialog.
     * 
     * @param bundle
     */
    protected abstract void onAuthComplete(Bundle bundle);

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

    /**
     * Called if user canceled OAuth authorization.
     */
    protected abstract void onAuthCancel();

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

    /**
     * Store session data to persistent storage. 
     * <p><b>
     * Make sure you use {@link AbstractContext#commitPrefString(String, String)} 
     * to store session data synchronously.
     * </b></p>
     * 
     * @return true if successfully stored to persistent storage
     * @throws Exception
     */
    protected abstract boolean storeSession() throws Exception;

    /**
     * Restore session data from persistent storage. 
     * <p><b>
     * Make sure you use {@link AbstractContext#getPrefMap()} 
     * to get a map from shared prefences file.
     * </b></p>
     * 
     * @return true if successfully restored from persistent storage
     * @throws Exception
     */
    protected abstract boolean restoreSession() throws Exception;

    /**
     * Cosumer Key provided by Service Provider.
     */
    protected final String mConsumerKey;
    /**
     * Cosumer Secret provided by Service Provider to be used 
     * as a part of HMAC-SHA1 secret key.
     */
    protected final String mConsumerSecret;

    /**
     * Request Token fetched from Service Provider.
     */
    protected String mRequestToken = "";

    /**
     * Request Token Secret to be used as a part of HMAC-SHA1 secret key.
     */
    protected String mRequestTokenSecret = "";

    /**
     * OAuth Verifier fetched from Service Provider.
     */
    protected String mOauthVerifier = "";

    /**
     * Secret Key for HMAC-SHA1.
     */
    protected String mSecretKey = "";

    /**
     * Access Token fetched from Service Provider.
     */
    protected String mAccessToken = "";

    /**
     * Access Token Secret fetched from Service Provider.
     */
    protected String mAccessTokenSecret = "";

    /**
     * Get Access Token.
     *
     * @return access token
     */
    protected String getAccessToken() {
        return mAccessToken;
    }

    /**
     * Get Access Token Secret.
     *
     * @return access token secret
     */
    protected String getAccessTokenSecret() {
        return mAccessTokenSecret;
    }

    /**
    * Constructor for network context.
     * 
     * @param context
     * @param sessionListener
     * @param systemProperties
     * @param snsProperties
     */
    protected AbstractContext(Context context, SessionListener sessionListener, SystemProperties systemProperties,
            SnsProperties snsProperties) {
        mContext = context;
        mSessionListener = sessionListener;
        mSystemProperties = systemProperties;
        mSnsProperties = snsProperties;
        mSnsUid = SocialNetwork.getSnsUid(this.getClass().getSimpleName());
        mConsumerKey = mSnsProperties.getConsumerKey(mSnsUid);
        mConsumerSecret = mSnsProperties.getConsumerSecret(mSnsUid);
        mStorageHelper = StorageHelper.newInstance(mContext);
    }

    /**
    * Constructor for network context.
     * 
     * @param alias
     * @param context
     * @param sessionListener
     * @param systemProperties
     * @param snsProperties
     */
    protected AbstractContext(String alias, Context context, SessionListener sessionListener,
            SystemProperties systemProperties, SnsProperties snsProperties) {
        mAlias = alias;
        mContext = context;
        mSessionListener = sessionListener;
        mSystemProperties = systemProperties;
        mSnsProperties = snsProperties;
        mSnsUid = SocialNetwork.getSnsUid(this.getClass().getSimpleName());
        mConsumerKey = mSnsProperties.getConsumerKey(mSnsUid);
        mConsumerSecret = mSnsProperties.getConsumerSecret(mSnsUid);
        mStorageHelper = StorageHelper.newInstance(mContext);
    }

    /** 
     * Returns a long timestamp value.
     * 
     * @return current time in sec
     */
    protected String getTimestamp() {
        return (System.currentTimeMillis() / 1000) + "";
    }

    /**
     * Returns a long nonce value.
     * 
     * @return a nonce value composed of 8 random digits
     */
    protected String getLongNonce() {
        return ((long) Math.floor(Math.random() * 90000000L) + 10000000L) + "";
    }

    /**
     * Returns a super unique nonce.
     * 
     * @return UUID
     */
    protected String getSuperNonce() {
        // well this seems a bit too much
        /*
        String nonce 
        = UUID.nameUUIDFromBytes(
                    new UUID(
                            System.currentTimeMillis()
                            , new Random(
                                    System.currentTimeMillis()
                            ).nextLong()
                    ).toString().getBytes()
            ).toString().replaceAll("-", "");
        return nonce;
        */
        // this will get the job done
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    /**
     * Get HMAC-SHA1 secret key.
     *
     * @return secretKey
     */
    protected String getSecretKey() {
        if (StringHelper.isEmpty(mAccessTokenSecret) == true) {
            mSecretKey = mConsumerSecret + AMPERSEND + StringHelper.nvl(mRequestTokenSecret);
        } else {
            mSecretKey = mConsumerSecret + AMPERSEND + mAccessTokenSecret;
        }
        return mSecretKey;
    }

    /* START OF [RESERVED] (2013-09-02) */
    /*
    protected String getBaseString(String httpMethod, String baseUri, 
      List<NameValuePair> sortedQueryParams) throws Exception {
        
      String baseString = null;
          
      if (StringHelper.isEmpty(httpMethod) == false && sortedQueryParams != null && 
        sortedQueryParams.size() > 0) {
          
      // sort parameters using lexicographical byte value ordering
     sortedQueryParams = ParameterHelper.sortParams(sortedQueryParams);
          
      // compose a base string corresponding to method, host and URI path
      baseString = new StringHelper().appendAllToString(
            httpMethod
              , AMPERSEND
              , URLEncoder.encode(baseUri, UTF_8)
              , AMPERSEND
              , URLEncoder.encode(
                      URLEncodedUtils.format(sortedQueryParams, UTF_8), UTF_8)
     );
      }
      return baseString;
     }
    */
    /* END OF [RESERVED] (2013-09-02) */

    /**
     * Get base string for HMAC-SHA1 encoding.
     * 
     * @param httpMethod
     * @param baseUri
     * @param queryParams
     * @return baseString - composed baseString by OAuth 1.0a specification
     * @throws Exception
     */
    protected String getBaseString(String httpMethod, String baseUri, List<NameValuePair> queryParams)
            throws Exception {
        /*
         *#################################################
         *  This algorithm(for OAuth 1.0a) is simply expressed in the pseudo-code :
         *#################################################
         *
         *          httpMethod + "&" +
         *          url_encode(  base_uri ) + "&" +
         *          sorted_query_params.each { | k, v |
         *              url_encode ( k ) + "%3D" +
         *              url_encode ( v )
         *          }.join("%26")
         *      
         *#################################################
         */
        String baseString = null;
        List<NameValuePair> sortedQueryParams = queryParams;

        if (StringHelper.isEmpty(httpMethod) == false && sortedQueryParams != null
                && sortedQueryParams.size() > 0) {

            // sort parameters using lexicographical byte value ordering
            sortedQueryParams = ParameterHelper.sortParams(sortedQueryParams);

            String paramQueryString = URLEncodedUtils.format(sortedQueryParams, UTF_8);

            //Log.d(TAG, "## BEFORE :: paramQueryString :\n" + paramQueryString);

            // replace '+' with '%20' to process POST parameter values with spaces
            paramQueryString = paramQueryString.replaceAll("\\+", "%20");

            // TODO error still occurs even though asterisks are all URL-encoded (2014-01-06)
            /*
            * Unauthorized {"errors":[{"message":"Could not authenticate you","code":32}]}
             */
            // replace '*' with '%2A' to process POST parameter values with asterisks
            paramQueryString = paramQueryString.replaceAll("\\*", "%2A");

            //Log.d(TAG, "## AFTER :: paramQueryString :\n" + paramQueryString);

            // compose a base string corresponding to method, host and URI path
            baseString = new StringHelper().appendAllToString(httpMethod, AMPERSEND,
                    URLEncoder.encode(baseUri, UTF_8), AMPERSEND, URLEncoder.encode(paramQueryString, UTF_8));
        }
        return baseString;
    }

    /**
     * Get base string for HMAC-SHA1 encoding.
     * 
     * @deprecated
     * @param request
     * @return  signatureBaseString
     * @throws Exception
     */
    protected String getSignatureBaseString(HttpUriRequest request) throws Exception {

        String signatureBaseString = "";

        URI uri = request.getURI();

        StringHelper strings = new StringHelper();
        strings.appendAllAtOnce(request.getMethod(), AMPERSEND, URLEncoder.encode(uri.getPath(), UTF_8), AMPERSEND);

        List<NameValuePair> oauthParams = new ArrayList<NameValuePair>();
        if (request instanceof HttpEntityEnclosingRequest) {
            HttpEntity entry = ((HttpEntityEnclosingRequest) request).getEntity();
            if (entry instanceof UrlEncodedFormEntity) {
                List<NameValuePair> params = URLEncodedUtils.parse(entry);
                if (params != null) {
                    oauthParams.addAll(params);
                }
            }
        }

        List<NameValuePair> params = URLEncodedUtils.parse(uri, UTF_8);
        if (params != null) {
            oauthParams.addAll(params);
            if (oauthParams.size() > 0) {
                StringHelper paramStrings = new StringHelper();
                NameValuePair[] sortedParams = oauthParams.toArray(new NameValuePair[params.size()]);
                Arrays.sort(sortedParams, ParameterHelper.paramComparator);
                int paramCnt = 0;
                for (NameValuePair param : sortedParams) {
                    if (paramCnt > 0) {
                        paramStrings.append(AMPERSEND);
                    }
                    paramStrings.append(param.getName());
                    paramStrings.append(EQUAL_MARK);
                    if (StringHelper.isEmpty(param.getName()) == false && OAUTH_CALLBACK.equals(param.getName())) {
                        paramStrings.append(URLEncoder.encode(param.getValue(), UTF_8));
                    } else {
                        paramStrings.append(StringHelper.nvl(param.getValue()));
                    }
                    paramCnt++;
                }
                strings.append(URLEncoder.encode(paramStrings.toString(), UTF_8));

                paramStrings = null;
                sortedParams = null;
                params = null;
                oauthParams = null;
            }
        }

        signatureBaseString = strings.toString();

        strings = null;

        return signatureBaseString;
    }

    /**
     * Extracts authorization header string from parameter list.
     * 
     * @param params
     * @return headerString
     * @throws Exception
     */
    protected String getOAuthHeaderString(List<NameValuePair> params) throws Exception {

        String headerString = null;

        if (params != null && params.size() > 0) {
            StringHelper stringHelper = new StringHelper().append(OAUTH_CAMEL_CASE + " ");

            int paramCount = 1;
            for (NameValuePair param : params) {
                if (StringHelper.isEmpty(param.getName()) == false) {

                    if (paramCount > 1) {
                        stringHelper.append(COMMA);
                    }
                    stringHelper.appendAllAtOnce(param.getName(), EQUAL_MARK,
                            StringHelper.doubleQuote(URLEncoder.encode(StringHelper.nvl(param.getValue()), UTF_8)));
                    paramCount++;
                }
            }
            headerString = stringHelper.toString();
            stringHelper = null;
        }
        return headerString;
    }

    /**
     * Get a singleton object of AndroidHttpClient.
     * 
     * @return AndroidHttpClient
     */
    protected AndroidHttpClient getHttpClient() {
        if (mHttpClient == null) {
            mHttpClient = AndroidHttpClient.newInstance(SystemProperties.USER_AGENT_ANDROID, mContext);

            ClientConnectionManager conMgr = mHttpClient.getConnectionManager();
            SchemeRegistry schReg = conMgr.getSchemeRegistry();
            for (String scheme : schReg.getSchemeNames()) {
                Log.i(TAG, "Scheme: " + scheme + ", port: " + schReg.getScheme(scheme).getDefaultPort()
                        + ", factory: " + schReg.getScheme(scheme).getSocketFactory().getClass().getName());
            }
        }
        return mHttpClient;
    }

    /**
     * Close and nullify Android HTTP client object.
     */
    protected void closeHttpClient() {
        if (mHttpClient != null) {
            if (mHttpClient.getConnectionManager() != null) {
                mHttpClient.getConnectionManager().shutdown();
            }
            mHttpClient.close();
            mHttpClient = null;
        }
    }

    /**
     * Returns API host URL.
     * 
     * @return API host URL
     */
    protected abstract String getApiHost();

    /**
     * Get HTTP GET object.
     * 
     * @param uri
     * @return HttpGet
     */
    protected synchronized HttpGet getHttpGet(URI uri) {
        return new HttpGet(uri);
    }

    /**
     * Get HTTP GET object.
     * 
     * @param apiUri
     * @return HttpGet
    * @throws Exception
     */
    protected synchronized HttpGet getHttpGet(String apiUri) throws Exception {
        return new HttpGet(new URI(getApiHost() + apiUri));
    }

    /**
     * Get HTTP GET object.
     * 
    * @param apiUri 
    * @param nvPairs 
    * @return HttpGet
    * @throws Exception
    */
    protected HttpGet getHttpGet(String apiUri, NameValuePair... nvPairs) throws Exception {

        List<NameValuePair> params = null;
        if (nvPairs != null && nvPairs.length > 0) {
            params = Arrays.asList(nvPairs);
        }

        return getHttpGet(apiUri, params);
    }

    /**
      * Get HTTP GET object.
     * 
     * @param apiUri
     * @param params
     * @return HttpGet
     * @throws Exception
     */
    protected synchronized HttpGet getHttpGet(String apiUri, List<NameValuePair> params) throws Exception {

        // URL-encode GET parameters
        String paramString = "";
        if (params != null && params.size() > 0) {
            paramString = "?" + URLEncodedUtils.format(params, UTF_8);
        }

        return new HttpGet(new URI(getApiHost() + apiUri + paramString));
    }

    /**
      * Get HTTP POST object.
     * 
     * @param uri
     * @return HttpPost
     */
    protected synchronized HttpPost getHttpPost(URI uri) {
        return new HttpPost(uri);
    }

    /**
      * Get HTTP POST object.
     * 
     * @param apiUri
     * @return HttpPost
     * @throws Exception
     */
    protected synchronized HttpPost getHttpPost(String apiUri) throws Exception {
        return new HttpPost(new URI(getApiHost() + apiUri));
    }

    /**
      * Get HTTP POST object.
     * 
     * @param apiUri
     * @param nvPairs
     * @return HttpPost
     * @throws Exception
     */
    protected HttpPost getHttpPost(String apiUri, NameValuePair... nvPairs) throws Exception {

        List<NameValuePair> params = null;
        if (nvPairs != null && nvPairs.length > 0) {
            params = Arrays.asList(nvPairs);
        }

        return getHttpPost(apiUri, params);
    }

    /**
      * Get HTTP POST object.
     * 
     * @param apiUri
     * @param params
     * @return HttpPost
     * @throws Exception
     */
    protected synchronized HttpPost getHttpPost(String apiUri, List<NameValuePair> params) throws Exception {

        HttpPost httpPost = new HttpPost(new URI(getApiHost() + apiUri));

        if (params != null && params.size() > 0) {
            httpPost.setEntity(new UrlEncodedFormEntity(params, UTF_8));
        }

        return httpPost;
    }

    /**
     * Get multipart HTTP POST object.
     * 
     * @param apiUri
     * @param params
     * @param paramNameForFile
     * @param absoultePathOfFile
     * @return HttpPost
     * @throws Exception
     */
    protected HttpPost getMultipartHttpPost(String apiUri, List<NameValuePair> params, String paramNameForFile,
            String absoultePathOfFile) throws Exception {
        return getMultipartHttpPost(apiUri, params, paramNameForFile, new File(absoultePathOfFile));
    }

    /**
     * Get multipart HTTP POST object.
     * 
     * @param apiUri
     * @param params
     * @param paramNameForFile
     * @param file
     * @return HttpPost
     * @throws Exception
     */
    protected HttpPost getMultipartHttpPost(String apiUri, List<NameValuePair> params, String paramNameForFile,
            File file) throws Exception {

        HttpPost httpPost = new HttpPost(new URI(getApiHost() + apiUri));

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

    /**
     * Add multipart data to given HTTP POST object.
     * 
     * @param httpPost
     * @param params
     * @param paramNameForFile
     * @param file
     * @return HttpPost
     * @throws Exception
     */
    protected synchronized HttpPost getMultipartHttpPost(HttpPost httpPost, List<NameValuePair> params,
            String paramNameForFile, File file) throws Exception {

        MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);

        if (params != null && params.size() > 0) {
            for (NameValuePair nvPair : params) {
                entity.addPart(nvPair.getName(), new StringBody(StringHelper.nvl(nvPair.getValue())));
            }
        }

        // check if given File is an actual file
        if (file == null || file.isFile() == false) {
            throw new Exception("File is invalid. check file path.");
        } else {
            entity.addPart(paramNameForFile, new FileBody(file));
        }

        httpPost.setEntity(entity);

        return httpPost;
    }

    /**
      * Get HTTP PUT object.
     * 
     * @param uri
     * @return HttpPut
     */
    protected synchronized HttpPut getHttpPut(URI uri) {
        return new HttpPut(uri);
    }

    /**
      * Get HTTP PUT object.
     * 
     * @param apiUri
     * @return HttpPut
     * @throws Exception
     */
    protected synchronized HttpPut getHttpPut(String apiUri) throws Exception {
        return new HttpPut(new URI(getApiHost() + apiUri));
    }

    /**
      * Get HTTP DELETE object.
     * 
     * @param uri
     * @return HttpDelete
     */
    protected synchronized HttpDelete getHttpDelete(URI uri) {
        return new HttpDelete(uri);
    }

    /**
      * Get HTTP DELETE object.
     * 
     * @param apiUri
     * @return HttpDelete
     * @throws Exception
     */
    protected synchronized HttpDelete getHttpDelete(String apiUri) throws Exception {
        return new HttpDelete(new URI(getApiHost() + apiUri));
    }

    /**
      * Get HTTP HEAD object.
     * 
     * @param uri
     * @return HttpHead
     */
    protected synchronized HttpHead getHttpHead(URI uri) {
        return new HttpHead(uri);
    }

    /**
      * Get HTTP HEAD object.
     * 
     * @param apiUri
     * @return HttpHead
     * @throws Exception
     */
    protected synchronized HttpHead getHttpHead(String apiUri) throws Exception {
        return new HttpHead(new URI(getApiHost() + apiUri));
    }

    /**
      * Get HTTP OPTIONS object.
     * 
     * @param uri
     * @return HttpOptions
     */
    protected synchronized HttpOptions getHttpOptions(URI uri) {
        return new HttpOptions(uri);
    }

    /**
      * Get HTTP OPTIONS object.
     * 
     * @param apiUri
     * @return HttpOptions
     * @throws Exception
     */
    protected synchronized HttpOptions getHttpOptions(String apiUri) throws Exception {
        return new HttpOptions(new URI(getApiHost() + apiUri));
    }

    /**
      * Get HTTP TRACE object.
     * 
     * @param uri
     * @return HttpTrace
     */
    protected synchronized HttpTrace getHttpTrace(URI uri) {
        return new HttpTrace(uri);
    }

    /**
      * Get HTTP TRACE object.
     * 
     * @param apiUri
     * @return HttpTrace
     * @throws Exception
     */
    protected synchronized HttpTrace getHttpTrace(String apiUri) throws Exception {
        return new HttpTrace(new URI(getApiHost() + apiUri));
    }

    /**
     * Execute a request and retrieve a response as a JSON object.
     * 
     * @param request
     * @return JSONObject
     * @throws Exception
     */
    protected JSONObject getResponseJson(HttpUriRequest request) throws Exception {
        return new JSONObject(getResponseString(request, false));
    }

    /**
     * Execute a request and retrieve a response as a string value.
     * 
     * @param request
     * @return responseString
     * @throws Exception
     */
    protected String getResponseString(HttpUriRequest request) throws Exception {
        return getResponseString(request, true);
    }

    /**
     * Execute a request and retrieve a response as a string value.
     * 
     * @param request
     * @param isEmptyAllowed
     * @return responseString
     * @throws Exception
     */
    protected synchronized String getResponseString(HttpUriRequest request, boolean isEmptyAllowed)
            throws Exception {

        String responseString = null;

        if (request != null) {

            AndroidHttpClient client = AndroidHttpClient.newInstance(SystemProperties.USER_AGENT_ANDROID, mContext);
            try {
                HttpResponse response = client.execute(request);
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    StringHelper strings = new StringHelper();
                    InputStream instream = entity.getContent();
                    int len;
                    byte[] tmp = new byte[2048];
                    while ((len = instream.read(tmp)) != -1) {
                        strings.append(new String(tmp, 0, len, UTF_8));
                    }
                    responseString = strings.toString().trim();

                    Log.v(TAG, " ## HTTP response for " + request.getURI() + " : \n" + responseString);

                    strings = null;
                    entity.consumeContent();
                } else {
                    Log.w(TAG, "entity IS NULL");
                }

                StatusLine statusLine = response.getStatusLine();
                int statusCode = statusLine.getStatusCode();
                if (statusCode > HttpStatus.SC_MULTIPLE_CHOICES) {
                    Log.e(TAG, "HTTP status : " + statusCode + " - " + statusLine.getReasonPhrase());
                    JSONObject responseJson = null;
                    try {
                        responseJson = new JSONObject(responseString);
                    } catch (Exception e) {
                        // DO NOTHING
                    }

                    // if response came in JSON format
                    if (responseJson != null) {
                        Log.e(TAG, "responseJson.toString() : " + responseJson.toString());
                        NetworkException ne = new NetworkException(statusCode, statusLine.getReasonPhrase(),
                                responseJson.toString());
                        ne.setResponseJson(responseJson);
                        throw ne;
                    }

                    // if response didn't came in JSON format
                    else {
                        Log.e(TAG, "responseString : " + responseString);
                        NetworkException ne = new NetworkException(statusCode, statusLine.getReasonPhrase(),
                                responseString);
                        ne.setResponseString(responseString);
                        throw ne;
                    }
                }

            } catch (Exception e) {
                request.abort();
                Log.e(TAG, e.getMessage());
                throw e;

            } finally {
                if (client != null) {
                    client.close();
                }
            }
        }

        // throw an exception if empty reponse is not allowed
        if (isEmptyAllowed == false && StringHelper.isEmpty(responseString) == true) {
            Log.e(TAG, "HTTP response is empty");
            throw new NetworkException("HTTP response is empty.");
        }

        return responseString;
    }

    /* START OF [RESERVED] (2013-01-21) */
    /*
    protected synchronized String getResponseString(
      HttpUriRequest request) throws Exception {
    String responseString = "";
    if (request != null) {
        HttpResponse response = new DefaultHttpClient().execute(request);
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            StringHelper strings = new StringHelper();
            InputStream instream = entity.getContent();
            int len;
            byte[] tmp = new byte[2048];
            while ((len = instream.read(tmp)) != -1) {
                strings.append(new String(tmp, 0, len, UTF_8));
            }
            responseString = strings.toString().trim();
            strings = null;
        }
        StatusLine statusLine = response.getStatusLine();
        int statusCode = statusLine.getStatusCode();
        if (statusCode > HttpStatus.SC_MULTIPLE_CHOICES) {
            Log.e(TAG, "HTTP status code : " + statusCode 
                  + " - " + statusLine.getReasonPhrase());
         throw new NetworkException(statusCode, 
               statusLine.getReasonPhrase(), responseString);
        }
    }
    return responseString;
    }
     */
    /* END OF [RESERVED] (2013-01-21) */

    /**
     * Download a bitmap from a given URL.
     * 
     * @param photoUrl
     * @return Bitmap
     * @throws Exception
     */
    protected synchronized Bitmap getResponseBitmap(String photoUrl) throws Exception {

        Bitmap bitmap = null;

        if (StringHelper.isEmpty(photoUrl) == false) {

            AndroidHttpClient client = AndroidHttpClient.newInstance(SystemProperties.USER_AGENT_ANDROID, mContext);
            HttpGet request = new HttpGet(photoUrl);

            try {
                HttpResponse response = client.execute(request);
                StatusLine statusLine = response.getStatusLine();
                int statusCode = statusLine.getStatusCode();
                if (statusCode > HttpStatus.SC_MULTIPLE_CHOICES) {
                    Log.e(TAG, "HTTP status code : " + statusCode + " - " + statusLine.getReasonPhrase());
                    throw new NetworkException(statusCode, statusLine.getReasonPhrase(),
                            "Failed to fetch BitMap from URL - " + photoUrl);
                }

                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    InputStream inputStream = null;
                    try {
                        inputStream = entity.getContent();
                        bitmap = BitmapFactory.decodeStream(inputStream);
                        return bitmap;
                    } finally {
                        if (inputStream != null) {
                            inputStream.close();
                        }
                        entity.consumeContent();
                    }
                }

            } catch (Exception e) {
                request.abort();
                throw new NetworkException("Error occurred while retrieving bitmap from " + photoUrl, e.toString(),
                        e);

            } finally {
                if (client != null) {
                    client.close();
                }
            }

        }

        // if given url is empty
        else {
            throw new SystemException("Photo URL is invalid.");
        }

        return bitmap;
    }

    /**
     * Get system shared preferences and store deciphered keys and values in Map object.
     * 
     * @return Map<String, String>
     * @throws Exception
     */
    protected Map<String, String> getPrefMap() throws Exception {
        return mStorageHelper.getPrefMap(mAlias, mSnsUid);
    }

    /**
     * Read and decipher a string value from shared preferences file.
     * 
     * @param key
     * @return String
     * @throws Exception
     */
    protected String getPrefString(String key) throws Exception {
        return mStorageHelper.getPrefString(mAlias, mSnsUid, key);
    }

    /**
     * Write a ciphered string value to shared preferences file. 
     * Note that actual data will be written to storage ASYNCHRNOUSLY.
     * 
     * @param key
     * @param value
     * @throws Exception
     */
    protected void applyPrefString(String key, String value) throws Exception {
        mStorageHelper.applyPrefString(mAlias, mSnsUid, key, value);
    }

    /**
     * Write a ciphered string value to shared preferences file. 
     * Note that actual data will be written to storage IMMEDIATELY.
     * 
     * @param key
     * @param value
     * @return true if written successfully
     * @throws Exception
     */
    protected boolean commitPrefString(String key, String value) throws Exception {
        return mStorageHelper.commitPrefString(mAlias, mSnsUid, key, value);
    }

    /**
     * Clear all values from shared preferences.
     * 
     * @return if session is purged from storage successfully
    * @throws Exception
     */
    private boolean clearSharedPreferences() throws Exception {
        return mStorageHelper.clearSharedPreferences(mAlias, mSnsUid);
    }

}