| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.browser; |
| |
| import android.accounts.Account; |
| import android.accounts.AccountManager; |
| import android.accounts.AccountManagerCallback; |
| import android.accounts.AccountManagerFuture; |
| import android.app.Activity; |
| import android.app.ProgressDialog; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.DialogInterface.OnCancelListener; |
| import android.content.SharedPreferences.Editor; |
| import android.net.Uri; |
| import android.net.http.AndroidHttpClient; |
| import android.os.Bundle; |
| import android.util.Log; |
| import android.webkit.CookieSyncManager; |
| import android.webkit.WebView; |
| import android.webkit.WebViewClient; |
| |
| import org.apache.http.HttpEntity; |
| import org.apache.http.HttpResponse; |
| import org.apache.http.HttpStatus; |
| import org.apache.http.client.methods.HttpPost; |
| import org.apache.http.util.EntityUtils; |
| |
| public class GoogleAccountLogin implements Runnable, |
| AccountManagerCallback<Bundle>, OnCancelListener { |
| |
| private static final String LOGTAG = "BrowserLogin"; |
| |
| // Url for issuing the uber token. |
| private Uri ISSUE_AUTH_TOKEN_URL = Uri.parse( |
| "https://www.google.com/accounts/IssueAuthToken?service=gaia&Session=false"); |
| // Url for signing into a particular service. |
| private static final Uri TOKEN_AUTH_URL = Uri.parse( |
| "https://www.google.com/accounts/TokenAuth"); |
| // Google account type |
| private static final String GOOGLE = "com.google"; |
| // Last auto login time |
| public static final String PREF_AUTOLOGIN_TIME = "last_autologin_time"; |
| |
| private final Activity mActivity; |
| private final Account mAccount; |
| private final WebView mWebView; |
| private Runnable mRunnable; |
| private ProgressDialog mProgressDialog; |
| |
| // SID and LSID retrieval process. |
| private String mSid; |
| private String mLsid; |
| private int mState; // {NONE(0), SID(1), LSID(2)} |
| private boolean mTokensInvalidated; |
| private String mUserAgent; |
| |
| private GoogleAccountLogin(Activity activity, Account account, |
| Runnable runnable) { |
| mActivity = activity; |
| mAccount = account; |
| mWebView = new WebView(mActivity); |
| mRunnable = runnable; |
| mUserAgent = mWebView.getSettings().getUserAgentString(); |
| |
| // XXX: Doing pre-login causes onResume to skip calling |
| // resumeWebViewTimers. So to avoid problems with timers not running, we |
| // duplicate the work here using the off-screen WebView. |
| CookieSyncManager.getInstance().startSync(); |
| WebViewTimersControl.getInstance().onBrowserActivityResume(mWebView); |
| |
| mWebView.setWebViewClient(new WebViewClient() { |
| @Override |
| public boolean shouldOverrideUrlLoading(WebView view, String url) { |
| return false; |
| } |
| @Override |
| public void onPageFinished(WebView view, String url) { |
| done(); |
| } |
| }); |
| } |
| |
| private void saveLoginTime() { |
| Editor ed = BrowserSettings.getInstance().getPreferences().edit(); |
| ed.putLong(PREF_AUTOLOGIN_TIME, System.currentTimeMillis()); |
| ed.apply(); |
| } |
| |
| // Runnable |
| @Override |
| public void run() { |
| String url = ISSUE_AUTH_TOKEN_URL.buildUpon() |
| .appendQueryParameter("SID", mSid) |
| .appendQueryParameter("LSID", mLsid) |
| .build().toString(); |
| // Intentionally not using Proxy. |
| AndroidHttpClient client = AndroidHttpClient.newInstance(mUserAgent); |
| HttpPost request = new HttpPost(url); |
| |
| String result = null; |
| try { |
| HttpResponse response = client.execute(request); |
| int status = response.getStatusLine().getStatusCode(); |
| if (status != HttpStatus.SC_OK) { |
| Log.d(LOGTAG, "LOGIN_FAIL: Bad status from auth url " |
| + status + ": " |
| + response.getStatusLine().getReasonPhrase()); |
| // Invalidate the tokens once just in case the 403 was for other |
| // reasons. |
| if (status == HttpStatus.SC_FORBIDDEN && !mTokensInvalidated) { |
| Log.d(LOGTAG, "LOGIN_FAIL: Invalidating tokens..."); |
| // Need to regenerate the auth tokens and try again. |
| invalidateTokens(); |
| // XXX: Do not touch any more member variables from this |
| // thread as a second thread will handle the next login |
| // attempt. |
| return; |
| } |
| done(); |
| return; |
| } |
| HttpEntity entity = response.getEntity(); |
| if (entity == null) { |
| Log.d(LOGTAG, "LOGIN_FAIL: Null entity in response"); |
| done(); |
| return; |
| } |
| result = EntityUtils.toString(entity, "UTF-8"); |
| } catch (Exception e) { |
| Log.d(LOGTAG, "LOGIN_FAIL: Exception acquiring uber token " + e); |
| request.abort(); |
| done(); |
| return; |
| } finally { |
| client.close(); |
| } |
| final String newUrl = TOKEN_AUTH_URL.buildUpon() |
| .appendQueryParameter("source", "android-browser") |
| .appendQueryParameter("auth", result) |
| .appendQueryParameter("continue", |
| BrowserSettings.getFactoryResetHomeUrl(mActivity)) |
| .build().toString(); |
| mActivity.runOnUiThread(new Runnable() { |
| @Override public void run() { |
| // Check mRunnable in case the request has been canceled. This |
| // is most likely not necessary as run() is the only non-UI |
| // thread that calls done() but I am paranoid. |
| synchronized (GoogleAccountLogin.this) { |
| if (mRunnable == null) { |
| return; |
| } |
| mWebView.loadUrl(newUrl); |
| } |
| } |
| }); |
| } |
| |
| private void invalidateTokens() { |
| AccountManager am = AccountManager.get(mActivity); |
| am.invalidateAuthToken(GOOGLE, mSid); |
| am.invalidateAuthToken(GOOGLE, mLsid); |
| mTokensInvalidated = true; |
| mState = 1; // SID |
| am.getAuthToken(mAccount, "SID", null, mActivity, this, null); |
| } |
| |
| // AccountManager callbacks. |
| @Override |
| public void run(AccountManagerFuture<Bundle> value) { |
| try { |
| String id = value.getResult().getString( |
| AccountManager.KEY_AUTHTOKEN); |
| switch (mState) { |
| default: |
| case 0: |
| throw new IllegalStateException( |
| "Impossible to get into this state"); |
| case 1: |
| mSid = id; |
| mState = 2; // LSID |
| AccountManager.get(mActivity).getAuthToken( |
| mAccount, "LSID", null, mActivity, this, null); |
| break; |
| case 2: |
| mLsid = id; |
| new Thread(this).start(); |
| break; |
| } |
| } catch (Exception e) { |
| Log.d(LOGTAG, "LOGIN_FAIL: Exception in state " + mState + " " + e); |
| // For all exceptions load the original signin page. |
| // TODO: toast login failed? |
| done(); |
| } |
| } |
| |
| // Start the login process if auto-login is enabled and the user is not |
| // already logged in. |
| public static void startLoginIfNeeded(Activity activity, |
| Runnable runnable) { |
| // Already logged in? |
| if (isLoggedIn()) { |
| runnable.run(); |
| return; |
| } |
| |
| // No account found? |
| Account[] accounts = getAccounts(activity); |
| if (accounts == null || accounts.length == 0) { |
| runnable.run(); |
| return; |
| } |
| |
| GoogleAccountLogin login = |
| new GoogleAccountLogin(activity, accounts[0], runnable); |
| login.startLogin(); |
| } |
| |
| private void startLogin() { |
| saveLoginTime(); |
| mProgressDialog = ProgressDialog.show(mActivity, |
| mActivity.getString(R.string.pref_autologin_title), |
| mActivity.getString(R.string.pref_autologin_progress, |
| mAccount.name), |
| true /* indeterminate */, |
| true /* cancelable */, |
| this); |
| mState = 1; // SID |
| AccountManager.get(mActivity).getAuthToken( |
| mAccount, "SID", null, mActivity, this, null); |
| } |
| |
| private static Account[] getAccounts(Context ctx) { |
| return AccountManager.get(ctx).getAccountsByType(GOOGLE); |
| } |
| |
| // Checks if we already did pre-login. |
| private static boolean isLoggedIn() { |
| // See if we last logged in less than a week ago. |
| long lastLogin = BrowserSettings.getInstance().getPreferences() |
| .getLong(PREF_AUTOLOGIN_TIME, -1); |
| if (lastLogin == -1) { |
| return false; |
| } |
| return true; |
| } |
| |
| // Used to indicate that the Browser should continue loading the main page. |
| // This can happen on success, error, or timeout. |
| private synchronized void done() { |
| if (mRunnable != null) { |
| Log.d(LOGTAG, "Finished login attempt for " + mAccount.name); |
| mActivity.runOnUiThread(mRunnable); |
| |
| try { |
| mProgressDialog.dismiss(); |
| } catch (Exception e) { |
| // TODO: Switch to a managed dialog solution (DialogFragment?) |
| // Also refactor this class, it doesn't |
| // play nice with the activity lifecycle, leading to issues |
| // with the dialog it manages |
| Log.w(LOGTAG, "Failed to dismiss mProgressDialog: " + e.getMessage()); |
| } |
| mRunnable = null; |
| mActivity.runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| mWebView.destroy(); |
| } |
| }); |
| } |
| } |
| |
| // Called by the progress dialog on startup. |
| public void onCancel(DialogInterface unused) { |
| done(); |
| } |
| |
| } |