1 package com.android.email.activity.setup; 2 3 import android.app.Activity; 4 import android.app.LoaderManager.LoaderCallbacks; 5 import android.content.Context; 6 import android.content.Intent; 7 import android.content.Loader; 8 import android.net.Uri; 9 import android.os.Bundle; 10 import android.text.TextUtils; 11 import android.webkit.CookieManager; 12 import android.webkit.CookieSyncManager; 13 import android.webkit.WebView; 14 import android.webkit.WebViewClient; 15 import android.widget.Toast; 16 17 import com.android.email.mail.internet.OAuthAuthenticator; 18 import com.android.email.mail.internet.OAuthAuthenticator.AuthenticationResult; 19 import com.android.email.R; 20 import com.android.emailcommon.Logging; 21 import com.android.emailcommon.VendorPolicyLoader.OAuthProvider; 22 import com.android.emailcommon.mail.AuthenticationFailedException; 23 import com.android.emailcommon.mail.MessagingException; 24 import com.android.mail.ui.MailAsyncTaskLoader; 25 import com.android.mail.utils.LogUtils; 26 27 import java.io.IOException; 28 29 30 /** 31 * Activity to display a webview to perform oauth authentication. This activity 32 * should obtain an authorization code, which can be used to obtain access and 33 * refresh tokens. 34 */ 35 public class OAuthAuthenticationActivity extends Activity implements 36 LoaderCallbacks<AuthenticationResult> { 37 public static final String EXTRA_EMAIL_ADDRESS = "email_address"; 38 public static final String EXTRA_PROVIDER = "provider"; 39 public static final String EXTRA_PROVIDER_ID = "provider_id"; 40 public static final String EXTRA_AUTHENTICATION_CODE = "authentication_code"; 41 42 public static final int LOADER_ID_OAUTH_TOKEN = 1; 43 44 public static final String EXTRA_OAUTH_ACCESS_TOKEN = "accessToken"; 45 public static final String EXTRA_OAUTH_REFRESH_TOKEN = "refreshToken"; 46 public static final String EXTRA_OAUTH_EXPIRES_IN = "expiresIn"; 47 48 public static final int REQUEST_OAUTH = 1; 49 50 public static final int RESULT_OAUTH_SUCCESS = Activity.RESULT_FIRST_USER + 0; 51 public static final int RESULT_OAUTH_USER_CANCELED = Activity.RESULT_FIRST_USER + 1; 52 public static final int RESULT_OAUTH_FAILURE = Activity.RESULT_FIRST_USER + 2; 53 54 private WebView mWv; 55 private OAuthProvider mProvider; 56 private String mAuthenticationCode; 57 58 private class MyWebViewClient extends WebViewClient { 59 60 @Override shouldOverrideUrlLoading(WebView wv, String url)61 public boolean shouldOverrideUrlLoading(WebView wv, String url) { 62 // TODO: This method works for Google's redirect url to https://localhost. 63 // Does it work for the general case? I don't know what redirect url other 64 // providers use, or how the authentication code is returned. 65 final String deparameterizedUrl; 66 int i = url.lastIndexOf('?'); 67 if (i == -1) { 68 deparameterizedUrl = url; 69 } else { 70 deparameterizedUrl = url.substring(0,i); 71 } 72 73 if (TextUtils.equals(deparameterizedUrl, mProvider.redirectUri)) { 74 final Uri uri = Uri.parse(url); 75 // Check the params of this uri, they contain success/failure info, 76 // along with the authentication token. 77 final String error = uri.getQueryParameter("error"); 78 79 if (error != null) { 80 final Intent intent = new Intent(); 81 setResult(RESULT_OAUTH_USER_CANCELED, intent); 82 finish(); 83 } else { 84 mAuthenticationCode = uri.getQueryParameter("code"); 85 Bundle params = new Bundle(); 86 params.putString(EXTRA_PROVIDER_ID, mProvider.id); 87 params.putString(EXTRA_AUTHENTICATION_CODE, mAuthenticationCode); 88 getLoaderManager().initLoader(LOADER_ID_OAUTH_TOKEN, params, 89 OAuthAuthenticationActivity.this); 90 } 91 return true; 92 } else { 93 return false; 94 } 95 } 96 } 97 98 @Override onCreate(Bundle bundle)99 public void onCreate(Bundle bundle) { 100 super.onCreate(bundle); 101 CookieSyncManager.createInstance(this); 102 CookieManager cm = CookieManager.getInstance(); 103 cm.removeAllCookie(); 104 105 mWv = new WebView(this); 106 mWv.setWebViewClient(new MyWebViewClient()); 107 mWv.getSettings().setJavaScriptEnabled(true); 108 setContentView(mWv); 109 110 final Intent i = getIntent(); 111 final String email = i.getStringExtra(EXTRA_EMAIL_ADDRESS); 112 final String providerName = i.getStringExtra(EXTRA_PROVIDER); 113 mProvider = AccountSettingsUtils.findOAuthProvider(this, providerName); 114 final Uri uri = AccountSettingsUtils.createOAuthRegistrationRequest(this, mProvider, email); 115 mWv.loadUrl(uri.toString()); 116 117 if (bundle != null) { 118 mAuthenticationCode = bundle.getString(EXTRA_AUTHENTICATION_CODE); 119 } else { 120 mAuthenticationCode = null; 121 } 122 if (mAuthenticationCode != null) { 123 Bundle params = new Bundle(); 124 params.putString(EXTRA_PROVIDER_ID, mProvider.id); 125 params.putString(EXTRA_AUTHENTICATION_CODE, mAuthenticationCode); 126 getLoaderManager().initLoader(LOADER_ID_OAUTH_TOKEN, params, 127 OAuthAuthenticationActivity.this); 128 } 129 // Set the result to cancelled until we have success. 130 setResult(RESULT_OAUTH_USER_CANCELED, null); 131 } 132 133 @Override onSaveInstanceState(Bundle outState)134 protected void onSaveInstanceState(Bundle outState) { 135 super.onSaveInstanceState(outState); 136 outState.putString(EXTRA_AUTHENTICATION_CODE, mAuthenticationCode); 137 } 138 139 private static class OAuthTokenLoader extends MailAsyncTaskLoader<AuthenticationResult> { 140 private final String mProviderId; 141 private final String mCode; 142 OAuthTokenLoader(Context context, String providerId, String code)143 public OAuthTokenLoader(Context context, String providerId, String code) { 144 super(context); 145 mProviderId = providerId; 146 mCode = code; 147 } 148 149 @Override onDiscardResult(AuthenticationResult result)150 protected void onDiscardResult(AuthenticationResult result) { 151 152 } 153 154 @Override loadInBackground()155 public AuthenticationResult loadInBackground() { 156 try { 157 final OAuthAuthenticator authenticator = new OAuthAuthenticator(); 158 final AuthenticationResult result = authenticator.requestAccess( 159 getContext(), mProviderId, mCode); 160 LogUtils.d(Logging.LOG_TAG, "authentication %s", result); 161 return result; 162 // TODO: do I need a better UI for displaying exceptions? 163 } catch (AuthenticationFailedException e) { 164 } catch (MessagingException e) { 165 } catch (IOException e) { 166 } 167 return null; 168 } 169 } 170 171 @Override onCreateLoader(int id, Bundle data)172 public Loader<AuthenticationResult> onCreateLoader(int id, Bundle data) { 173 if (id == LOADER_ID_OAUTH_TOKEN) { 174 final String providerId = data.getString(EXTRA_PROVIDER_ID); 175 final String code = data.getString(EXTRA_AUTHENTICATION_CODE); 176 return new OAuthTokenLoader(this, providerId, code); 177 } 178 return null; 179 } 180 181 @Override onLoadFinished(Loader<AuthenticationResult> loader, AuthenticationResult data)182 public void onLoadFinished(Loader<AuthenticationResult> loader, 183 AuthenticationResult data) { 184 if (data == null) { 185 // TODO: need a better way to display errors. We might get IO or 186 // MessagingExceptions. 187 setResult(RESULT_OAUTH_FAILURE, null); 188 Toast.makeText(this, R.string.oauth_error_description, Toast.LENGTH_SHORT).show(); 189 LogUtils.w(Logging.LOG_TAG, "null oauth result"); 190 } else { 191 final Intent intent = new Intent(); 192 intent.putExtra(EXTRA_OAUTH_ACCESS_TOKEN, data.mAccessToken); 193 intent.putExtra(EXTRA_OAUTH_REFRESH_TOKEN, data.mRefreshToken); 194 intent.putExtra(EXTRA_OAUTH_EXPIRES_IN, data.mExpiresInSeconds); 195 setResult(RESULT_OAUTH_SUCCESS, intent); 196 } 197 finish(); 198 } 199 200 @Override onLoaderReset(Loader<AuthenticationResult> loader)201 public void onLoaderReset(Loader<AuthenticationResult> loader) { 202 203 } 204 } 205