1package com.android.email.activity.setup;
2
3import android.app.Activity;
4import android.app.LoaderManager.LoaderCallbacks;
5import android.content.Context;
6import android.content.Intent;
7import android.content.Loader;
8import android.net.Uri;
9import android.os.Bundle;
10import android.text.TextUtils;
11import android.webkit.CookieManager;
12import android.webkit.CookieSyncManager;
13import android.webkit.WebView;
14import android.webkit.WebViewClient;
15import android.widget.Toast;
16
17import com.android.email.mail.internet.OAuthAuthenticator;
18import com.android.email.mail.internet.OAuthAuthenticator.AuthenticationResult;
19import com.android.email.R;
20import com.android.emailcommon.Logging;
21import com.android.emailcommon.VendorPolicyLoader.OAuthProvider;
22import com.android.emailcommon.mail.AuthenticationFailedException;
23import com.android.emailcommon.mail.MessagingException;
24import com.android.mail.ui.MailAsyncTaskLoader;
25import com.android.mail.utils.LogUtils;
26
27import 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 */
35public 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
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
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
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
143        public OAuthTokenLoader(Context context, String providerId, String code) {
144            super(context);
145            mProviderId = providerId;
146            mCode = code;
147        }
148
149        @Override
150        protected void onDiscardResult(AuthenticationResult result) {
151
152        }
153
154        @Override
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
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
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
201    public void onLoaderReset(Loader<AuthenticationResult> loader) {
202
203    }
204}
205