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.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; } } }