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