Java tutorial
/* * 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; } } }