/* * Copyright 2012 Evernote Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.evernote.client.android; import android.annotation.SuppressLint; import android.app.Activity; import android.app.Dialog; import android.app.ProgressDialog; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.Window; import android.webkit.WebChromeClient; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Toast; import com.evernote.androidsdk.R; import com.evernote.client.oauth.EvernoteAuthToken; import com.evernote.client.oauth.YinxiangApi; import com.evernote.edam.userstore.BootstrapInfo; import com.evernote.edam.userstore.BootstrapProfile; import org.scribe.builder.ServiceBuilder; import org.scribe.builder.api.EvernoteApi; import org.scribe.model.Token; import org.scribe.model.Verifier; import org.scribe.oauth.OAuthService; import java.util.ArrayList; /** * An Android Activity for authenticating to Evernote using OAuth. * Third parties should not need to use this class directly. * * * class created by @tylersmithnet */ public class EvernoteOAuthActivity extends Activity { private static final String LOGTAG = "EvernoteOAuthActivity"; static final String EXTRA_EVERNOTE_SERVICE = "EVERNOTE_HOST"; static final String EXTRA_CONSUMER_KEY = "CONSUMER_KEY"; static final String EXTRA_CONSUMER_SECRET = "CONSUMER_SECRET"; static final String EXTRA_REQUEST_TOKEN = "REQUEST_TOKEN"; static final String EXTRA_REQUEST_TOKEN_SECRET = "REQUEST_TOKEN_SECRET"; static final String EXTRA_BOOTSTRAP_SELECTED_PROFILE_POS = "BOOTSTRAP_SELECTED_PROFILE_POS"; static final String EXTRA_BOOTSTRAP_SELECTED_PROFILE = "BOOTSTRAP_SELECTED_PROFILE"; static final String EXTRA_BOOTSTRAP_SELECTED_PROFILES = "BOOTSTRAP_SELECTED_PROFILES"; private EvernoteSession.EvernoteService mEvernoteService = null; private BootstrapProfile mSelectedBootstrapProfile; private int mSelectedBootstrapProfilePos = 0; private ArrayList<BootstrapProfile> mBootstrapProfiles = new ArrayList<BootstrapProfile>(); private String mConsumerKey = null; private String mConsumerSecret = null; private String mRequestToken = null; private String mRequestTokenSecret = null; private final int DIALOG_PROGRESS = 101; private Activity mActivity; private WebView mWebView; private AsyncTask mBeginAuthSyncTask = null; private AsyncTask mCompleteAuthSyncTask = null; /** * Overrides the callback URL and authenticate */ private WebViewClient mWebViewClient = new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Uri uri = Uri.parse(url); if (uri.getScheme().equals(getCallbackScheme())) { if (mCompleteAuthSyncTask == null) { mCompleteAuthSyncTask = new CompleteAuthAsyncTask().execute(uri); } return true; } return super.shouldOverrideUrlLoading(view, url); } }; /** * Allows for showing progress */ private WebChromeClient mWebChromeClient = new WebChromeClient() { @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); mActivity.setProgress(newProgress * 1000); } }; @SuppressLint("SetJavaScriptEnabled") @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //Show web loading progress getWindow().requestFeature(Window.FEATURE_PROGRESS); setContentView(R.layout.esdk__webview); mActivity = this; mWebView = (WebView) findViewById(R.id.esdk__webview); mWebView.setWebViewClient(mWebViewClient); mWebView.setWebChromeClient(mWebChromeClient); mWebView.getSettings().setJavaScriptEnabled(true); if (savedInstanceState != null) { mEvernoteService = savedInstanceState.getParcelable(EXTRA_EVERNOTE_SERVICE); mConsumerKey = savedInstanceState.getString(EXTRA_CONSUMER_KEY); mConsumerSecret = savedInstanceState.getString(EXTRA_CONSUMER_SECRET); mRequestToken = savedInstanceState.getString(EXTRA_REQUEST_TOKEN); mRequestTokenSecret = savedInstanceState.getString(EXTRA_REQUEST_TOKEN_SECRET); mSelectedBootstrapProfile = (BootstrapProfile) savedInstanceState.getSerializable(EXTRA_BOOTSTRAP_SELECTED_PROFILE); mSelectedBootstrapProfilePos = savedInstanceState.getInt(EXTRA_BOOTSTRAP_SELECTED_PROFILE_POS); mBootstrapProfiles = (ArrayList<BootstrapProfile>) savedInstanceState.getSerializable(EXTRA_BOOTSTRAP_SELECTED_PROFILES); mWebView.restoreState(savedInstanceState); } else { Intent intent = getIntent(); mEvernoteService = intent.getParcelableExtra(EXTRA_EVERNOTE_SERVICE); mConsumerKey = intent.getStringExtra(EXTRA_CONSUMER_KEY); mConsumerSecret = intent.getStringExtra(EXTRA_CONSUMER_SECRET); } } @Override protected void onResume() { super.onResume(); if (TextUtils.isEmpty(mConsumerKey) || TextUtils.isEmpty(mConsumerSecret)) { exit(false); return; } if (mSelectedBootstrapProfile == null) { mBeginAuthSyncTask = new BootstrapAsyncTask().execute(); } } /** * Not needed because of conficChanges, but leaving in case developer does not add to manifest * @param outState */ @Override protected void onSaveInstanceState(Bundle outState) { outState.putParcelable(EXTRA_EVERNOTE_SERVICE, mEvernoteService); outState.putString(EXTRA_CONSUMER_KEY, mConsumerKey); outState.putString(EXTRA_CONSUMER_SECRET, mConsumerSecret); outState.putString(EXTRA_REQUEST_TOKEN, mRequestToken); outState.putString(EXTRA_REQUEST_TOKEN_SECRET, mRequestTokenSecret); outState.putSerializable(EXTRA_BOOTSTRAP_SELECTED_PROFILE, mSelectedBootstrapProfile); outState.putInt(EXTRA_BOOTSTRAP_SELECTED_PROFILE_POS, mSelectedBootstrapProfilePos); outState.putSerializable(EXTRA_BOOTSTRAP_SELECTED_PROFILES, mBootstrapProfiles); mWebView.saveState(outState); super.onSaveInstanceState(outState); } @Override protected Dialog onCreateDialog(int id) { switch (id) { case DIALOG_PROGRESS: return new ProgressDialog(EvernoteOAuthActivity.this); } // TODO onCreateDialog(int) is deprecated return super.onCreateDialog(id); } @Override protected void onPrepareDialog(int id, Dialog dialog) { switch(id) { case DIALOG_PROGRESS: ((ProgressDialog)dialog).setIndeterminate(true); dialog.setCancelable(false); ((ProgressDialog) dialog).setMessage(getString(R.string.esdk__loading)); } } /** * Specifies a URL scheme that uniquely identifies callbacks * to this application after a user authorizes access to their * Evernote account in our WebView. */ private String getCallbackScheme() { return "en-oauth"; } /** * Create a Scribe OAuthService object that can be used to * perform OAuth authentication with the appropriate Evernote * service. */ @SuppressWarnings("unchecked") private OAuthService createService() { OAuthService builder = null; @SuppressWarnings("rawtypes") Class apiClass = null; String host = EvernoteSession.HOST_PRODUCTION;//mSelectedBootstrapProfile.getSettings().getServiceHost(); if (host != null && !host.startsWith("http")) { host = "https://" + host; } if (host.equals(EvernoteSession.HOST_SANDBOX)) { apiClass = EvernoteApi.Sandbox.class; } else if (host.equals(EvernoteSession.HOST_PRODUCTION)) { apiClass = EvernoteApi.class; } else if (host.equals(EvernoteSession.HOST_CHINA)) { apiClass = YinxiangApi.class; } else { throw new IllegalArgumentException("Unsupported Evernote host: " + host); } builder = new ServiceBuilder() .provider(apiClass) .apiKey(mConsumerKey) .apiSecret(mConsumerSecret) .callback(getCallbackScheme() + "://callback") .build(); return builder; } /** * Exit the activity and display a toast message. * @param success Whether the OAuth process completed successfully. */ private void exit(final boolean success) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(mActivity, success ? R.string.esdk__evernote_login_successful : R.string.esdk__evernote_login_failed, Toast.LENGTH_LONG).show(); setResult(success ? RESULT_OK : RESULT_CANCELED); finish(); } }); } /** * On honeycomb and above this will create an actionbar with the item to switch services * Below honeycomb it will create the options menu bound to a hardware key * @param menu * @return */ @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.esdk__oauth, menu); return super.onCreateOptionsMenu(menu); } /** * On Honeycomb and above this is called when we invalidate, this happens when the {@link ArrayList} of * {@link BootstrapProfile} are updated. * * Below Honeycomb this is called when the user presses the menu button. * * This detects the number of bootstrap items and sets the UI element appropriately. * * @param menu * @return */ @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuItem itemSwitchService = menu.findItem(R.id.esdk__switch_service); if(mBootstrapProfiles != null && mBootstrapProfiles.size() > 1) { if(BootstrapManager.CHINA_PROFILE.equals(mSelectedBootstrapProfile.getName())) { itemSwitchService.setTitle(BootstrapManager.DISPLAY_EVERNOTE_INTL); } else { itemSwitchService.setTitle(BootstrapManager.DISPLAY_YXBIJI); } itemSwitchService.setVisible(true); } else { itemSwitchService.setVisible(false); } return super.onPrepareOptionsMenu(menu); } /** * This will select the next {@link BootstrapProfile} in {@link #mBootstrapProfiles} and start a new * webview load request. * @param item * @return */ @Override public boolean onOptionsItemSelected(MenuItem item) { if(item.getItemId() == R.id.esdk__switch_service) { if((mBeginAuthSyncTask == null || mBeginAuthSyncTask.getStatus() != AsyncTask.Status.RUNNING) && (mSelectedBootstrapProfile != null && mBootstrapProfiles != null)) { mSelectedBootstrapProfilePos = (mSelectedBootstrapProfilePos + 1) % mBootstrapProfiles.size(); mBootstrapProfiles = null; mSelectedBootstrapProfile = null; mBeginAuthSyncTask = new BootstrapAsyncTask().execute(); } } return true; } /** * Get a request token from the Evernote service and send the user * to our WebView to authorize access. */ private class BootstrapAsyncTask extends AsyncTask<Void, Void, String> { @Override protected void onPreExecute() { // TODO deprecated showDialog(DIALOG_PROGRESS); } @Override protected String doInBackground(Void... params) { String url = null; try { EvernoteSession session = EvernoteSession.getOpenSession(); if (session != null) { //Network request BootstrapManager.BootstrapInfoWrapper infoWrapper = session.getBootstrapSession().getBootstrapInfo(); if (infoWrapper != null){ BootstrapInfo info = infoWrapper.getBootstrapInfo(); if(info != null) { mBootstrapProfiles = (ArrayList<BootstrapProfile>) info.getProfiles(); if (mBootstrapProfiles != null && mBootstrapProfiles.size() > 0 && mSelectedBootstrapProfilePos < mBootstrapProfiles.size()){ mSelectedBootstrapProfile = mBootstrapProfiles.get(mSelectedBootstrapProfilePos); } } } } if(mSelectedBootstrapProfile == null || TextUtils.isEmpty(mSelectedBootstrapProfile.getSettings().getServiceHost())) { Log.d(LOGTAG, "Bootstrap did not return a valid host"); return null; } OAuthService service = createService(); Log.i(LOGTAG, "Retrieving OAuth request token..."); Token reqToken = service.getRequestToken(); mRequestToken = reqToken.getToken(); mRequestTokenSecret = reqToken.getSecret(); Log.i(LOGTAG, "Redirecting user for authorization..."); url = service.getAuthorizationUrl(reqToken); } catch(BootstrapManager.ClientUnsupportedException cue) { return null; } catch (Exception ex) { Log.e(LOGTAG, "Failed to obtain OAuth request token", ex); } return url; } /** * Open a WebView to allow the user to authorize access to their account. * @param url The URL of the OAuth authorization web page. */ @SuppressLint("NewApi") @Override protected void onPostExecute(String url) { // TODO deprecated removeDialog(DIALOG_PROGRESS); if (!TextUtils.isEmpty(url)) { mWebView.loadUrl(url); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { invalidateOptionsMenu(); } } else { exit(false); } } } /** * An AsyncTask to complete the OAuth process after successful user authorization. */ private class CompleteAuthAsyncTask extends AsyncTask<Uri, Void, EvernoteAuthToken> { @Override protected void onPreExecute() { // TODO deprecated showDialog(DIALOG_PROGRESS); } @Override protected EvernoteAuthToken doInBackground(Uri... uris) { EvernoteAuthToken authToken = null; if (uris == null || uris.length == 0) { return null; } Uri uri = uris[0]; if (!TextUtils.isEmpty(mRequestToken)) { OAuthService service = createService(); String verifierString = uri.getQueryParameter("oauth_verifier"); if (TextUtils.isEmpty(verifierString)) { Log.i(LOGTAG, "User did not authorize access"); } else { Verifier verifier = new Verifier(verifierString); Log.i(LOGTAG, "Retrieving OAuth access token..."); try { Token reqToken = new Token(mRequestToken, mRequestTokenSecret); authToken = new EvernoteAuthToken(service.getAccessToken(reqToken, verifier)); } catch (Exception ex) { Log.e(LOGTAG, "Failed to obtain OAuth access token", ex); } } } else { Log.d(LOGTAG, "Unable to retrieve OAuth access token, no request token"); } return authToken; } /** * Save the authentication information resulting from a successful * OAuth authorization and complete the activity. */ @Override protected void onPostExecute(EvernoteAuthToken authToken) { // TODO deprecated removeDialog(DIALOG_PROGRESS); if (EvernoteSession.getOpenSession() == null) { exit(false); return; } exit(EvernoteSession.getOpenSession().persistAuthenticationToken( getApplicationContext(), authToken, mSelectedBootstrapProfile.getSettings().getServiceHost())); } } }