package com.ijoomer.customviews; import twitter4j.Twitter; import twitter4j.TwitterException; import twitter4j.TwitterFactory; import twitter4j.auth.AccessToken; import twitter4j.auth.RequestToken; import android.content.Context; import android.graphics.Bitmap; import android.net.Uri; import android.net.http.SslError; import android.os.AsyncTask; import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.webkit.SslErrorHandler; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; /** * This Class Contains All Method Related To TwitterOAuthView. * * @author tasol * */ public class TwitterOAuthView extends WebView { /** * Tag for logging. */ private static final String TAG = "TwitterOAuthView"; /** * Internal flag for debug logging. Change the value to 'true' to turn on * debug logging. */ private static final boolean DEBUG = false; /** * Result code of Twitter OAuth process. * * @author Takahiko Kawasaki */ public enum Result { /** * The application has been authorized by the user and got an access * token successfully. */ SUCCESS, /** * Twitter OAuth process was cancelled. This result code is generated * when the internal {@link AsyncTask} subclass was cancelled for some * reasons. */ CANCELLATION, /** * Twitter OAuth process was not even started due to failure of getting * a request token. The pair of consumer key and consumer secret was * wrong or some kind of network error occurred. */ REQUEST_TOKEN_ERROR, /** * The application has not been authorized by the user, or a network * error occurred during the OAuth handshake. */ AUTHORIZATION_ERROR, /** * The application has been authorized by the user but failed to get an * access token. */ ACCESS_TOKEN_ERROR } /** * Listener to be notified of Twitter OAuth process result. * * <p> * The methods of this listener are called on the UI thread. * </p> * * @author Takahiko Kawasaki */ public interface Listener { /** * Called when the application has been authorized by the user and got * an access token successfully. * * @param view * @param accessToken */ void onSuccess(TwitterOAuthView view, AccessToken accessToken); /** * Called when the OAuth process was not completed successfully. * * @param view * @param result */ void onFailure(TwitterOAuthView view, Result result); } /** * A constructor that calls * {@link WebView#WebView(Context, AttributeSet, int) super}(context, attrs, * defStyle). * * @param context * @param attrs * @param defStyle */ public TwitterOAuthView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // Additional initialization. init(); } /** * A constructor that calls {@link WebView#WebView(Context, AttributeSet) * super}(context, attrs). * * @param context * @param attrs */ public TwitterOAuthView(Context context, AttributeSet attrs) { super(context, attrs); // Additional initialization. init(); } /** * A constructor that calls {@link WebView#WebView(Context) super}(context). * * @param context */ public TwitterOAuthView(Context context) { super(context); // Additional initialization. init(); } private void init() { WebSettings settings = getSettings(); settings.setDatabaseEnabled(false); // Not use cache. settings.setCacheMode(WebSettings.LOAD_NO_CACHE); // Enable JavaScript. settings.setJavaScriptEnabled(true); // Enable zoom control. settings.setBuiltInZoomControls(true); // Scroll bar setScrollBarStyle(WebView.SCROLLBARS_INSIDE_OVERLAY); } /** * Start Twitter OAuth process. * * <p> * This method does the following in the background. * </p> * * <ol> * <li>Get a request token using the given pair of consumer key and consumer * secret. * <li>Load the authorization URL that the obtained request token points to * into this TwitterOAuthView instance. * <li>Wait for the user to finish the authorization process at Twitter's * authorization site. This TwitterOAuthView instance is redirected to the * callback URL as a result. * <li>Detect the redirection to the callback URL and retrieve the value of * the oauth_verifier parameter from the URL. If and only if * dummyCallbackUrl is false, the callback URL is actually accessed. * <li>Get an access token using the oauth_verifier. * <li>Call {@link Listener#onSuccess(TwitterOAuthView, AccessToken) * onSuccess()} method of the {@link Listener listener} on the UI thread. * </ol> * * <p> * If an error occurred during the above steps, * {@link Listener#onFailure(TwitterOAuthView, TwitterOAuthView.Result) * onFailure()} of the {@link Listener listener} is called. * </p> * * @param consumerKey * @param consumerSecret * @param callbackUrl * @param dummyCallbackUrl * @param listener * * @throws IllegalArgumentException * At least one of 'consumerKey', 'consumerSecret' or * 'callbackUrl' is null. */ public void start(String consumerKey, String consumerSecret, String callbackUrl, boolean dummyCallbackUrl, Listener listener) { if (consumerKey == null || consumerSecret == null || callbackUrl == null || listener == null) { throw new IllegalArgumentException(); } Boolean dummy = Boolean.valueOf(dummyCallbackUrl); new TwitterOAuthTask().execute(consumerKey, consumerSecret, callbackUrl, dummy, listener); } /** * Inner class * @author tasol * */ private class TwitterOAuthTask extends AsyncTask<Object, Void, Result> { private String callbackUrl; private boolean dummyCallbackUrl; private Listener listener; private Twitter twitter; private RequestToken requestToken; private volatile boolean authorizationDone; private volatile String verifier; private AccessToken accessToken; @Override protected void onPreExecute() { // Set up a WebViewClient on the UI thread. TwitterOAuthView.this.setWebViewClient(new LocalWebViewClient()); } @Override protected Result doInBackground(Object... args) { String consumerKey = (String) args[0]; String consumerSecret = (String) args[1]; // Callback URL. callbackUrl = (String) args[2]; dummyCallbackUrl = (Boolean) args[3]; // Listener listener = (Listener) args[4]; if (DEBUG) { Log.d(TAG, "CONSUMER KEY = " + consumerKey); Log.d(TAG, "CONSUMER SECRET = " + consumerSecret); Log.d(TAG, "CALLBACK URL = " + callbackUrl); Log.d(TAG, "DUMMY CALLBACK URL = " + dummyCallbackUrl); } if (DEBUG) System.setProperty("twitter4j.debug", "true"); // Create a Twitter instance with the given pair of // consumer key and consumer secret. twitter = new TwitterFactory().getInstance(); twitter.setOAuthConsumer(consumerKey, consumerSecret); // Get a request token. This triggers network access. requestToken = getRequestToken(); if (requestToken == null) { // Failed to get a request token. return Result.REQUEST_TOKEN_ERROR; } // Access Twitter's authorization page. After the user's // operation, this web view is redirected to the callback // URL, which is caught by shouldOverrideUrlLoading() of // LocalWebViewClient. authorize(); // Wait until the authorization step is done. waitForAuthorization(); // If the authorization has succeeded, 'verifier' is not null. if (verifier == null) { // The authorization failed. return Result.AUTHORIZATION_ERROR; } // The authorization succeeded. The last step is to get // an access token using the verifier. accessToken = getAccessToken(); if (accessToken == null) { // Failed to get an access token. return Result.ACCESS_TOKEN_ERROR; } // All the steps were done successfully. return Result.SUCCESS; } @Override protected void onProgressUpdate(Void... values) { // In this implementation, onProgressUpdate() is called // only from authorize(). // The authorization URL. String url = requestToken.getAuthorizationURL(); if (DEBUG) Log.d(TAG, "Loading the authorization URL: " + url); // Load the authorization URL on the UI thread. TwitterOAuthView.this.loadUrl(url); } @Override protected void onPostExecute(Result result) { if (DEBUG) Log.d(TAG, "onPostExecute: result = " + result); if (result == null) { // Probably cancelled. result = Result.CANCELLATION; } if (result == Result.SUCCESS) { // Call onSuccess() method of the listener. listener.onSuccess(TwitterOAuthView.this, accessToken); } else { // Call onFailure() method of the listener. listener.onFailure(TwitterOAuthView.this, result); } } private RequestToken getRequestToken() { try { // Get a request token. This triggers network access. RequestToken token = twitter.getOAuthRequestToken(); if (DEBUG) Log.d(TAG, "Got a request token."); return token; } catch (TwitterException e) { // Failed to get a request token. e.printStackTrace(); Log.e(TAG, "Failed to get a request token.", e); // No request token. return null; } } private void authorize() { // WebView.loadUrl() needs to be called on the UI thread, // so trigger onProgressUpdate(). publishProgress(); } private void waitForAuthorization() { while (authorizationDone == false) { synchronized (this) { try { if (DEBUG) Log.d(TAG, "Waiting for the authorization step to be done."); this.wait(); } catch (InterruptedException e) { } } } if (DEBUG) Log.d(TAG, "Finished waiting for the authorization step to be done."); } private void notifyAuthorization() { // The authorization step was done. authorizationDone = true; synchronized (this) { if (DEBUG) Log.d(TAG, "Notifying that the authorization step was done."); this.notify(); } } /** * Inner class * @author tasol * */ private class LocalWebViewClient extends WebViewClient { @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { // Something wrong happened during the authorization step. Log.e(TAG, "onReceivedError: [" + errorCode + "] " + description); // Stop the authorization step. notifyAuthorization(); } @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { handler.proceed(); } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); // 11 = Build.VERSION_CODES.HONEYCOMB (Android 3.0) if (Build.VERSION.SDK_INT < 11) { // According to this page: // // http://www.catchingtales.com/android-webview-shouldoverrideurlloading-and-redirect/416/ // // shouldOverrideUrlLoading() is not called for redirects on // Android earlier than 3.0, so call the method manually. // // The implementation of shouldOverrideUrlLoading() returns // true only when the URL starts with the callback URL and // dummyCallbackUrl is true. boolean stop = shouldOverrideUrlLoading(view, url); if (stop) { // Stop loading the current page. stopLoading(); } } } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // Check if the given URL is the callback URL. // if (url.startsWith(callbackUrl) == false) { // // The URL is not the callback URL. // return false; // } if (!url.contains("oauth_verifier")) { return false; } // This web view is about to be redirected to the callback URL. if (DEBUG) Log.d(TAG, "Detected the callback URL: " + url); // Convert String to Uri. Uri uri = Uri.parse(url); // Get the value of the query parameter "oauth_verifier". // A successful response should contain the parameter. verifier = uri.getQueryParameter("oauth_verifier"); if (DEBUG) Log.d(TAG, "oauth_verifier = " + verifier); // Notify that the the authorization step was done. notifyAuthorization(); // Whether the callback URL is actually accessed or not // depends on the value of dummyCallbackUrl. If the // value of dummyCallbackUrl is true, the callback URL // is not accessed. return dummyCallbackUrl; } } private AccessToken getAccessToken() { try { // Get an access token. This triggers network access. AccessToken token = twitter.getOAuthAccessToken(requestToken, verifier); if (DEBUG) Log.d(TAG, "Got an access token for " + token.getScreenName()); return token; } catch (TwitterException e) { // Failed to get an access token. e.printStackTrace(); Log.e(TAG, "Failed to get an access token.", e); // No access token. return null; } } } }