com.mobiperf.speedometer.AccountSelector.java Source code

Java tutorial

Introduction

Here is the source code for com.mobiperf.speedometer.AccountSelector.java

Source

/* Copyright 2012 Google Inc.
 *
 * 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.mobiperf.speedometer;

import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.DefaultHttpClient;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;

/**
 * Helper class for google account checkins
 * 
 * @author hjx@umich.edu (Junxian Huang)
 * @author mdw@google.com (Matt Welsh)
 * @author wenjiezeng@google.com (Steve Zeng)
 * 
 */
public class AccountSelector {

    // The authentication period in milliseconds
    private static final long AUTHENTICATE_PERIOD_MSEC = 24 * 3600 * 1000;
    private static final String ACCOUNT_TYPE = "com.google";

    private Context context;
    private Checkin checkin;
    private String authToken = null;
    private ExecutorService checkinExecutor = null;
    private Future<Cookie> checkinFuture = null;
    private long lastAuthTime = 0;
    private boolean authImmediately = false;

    public AccountSelector(Context context, Checkin checkin) {
        this.context = context;
        this.checkin = checkin;
        this.checkinExecutor = Executors.newFixedThreadPool(1);
    }

    /** Returns the Future to monitor the checkin progress */
    public synchronized Future<Cookie> getCheckinFuture() {
        return this.checkinFuture;
    }

    /**
     * After checkin finishes, the client of AccountSelector SHOULD reset checkinFuture
     */
    public synchronized void resetCheckinFuture() {
        this.checkinFuture = null;
    }

    /** Shuts down the executor thread */
    public void shutDown() {
        // shutdown() removes all previously submitted task and no new tasks are accepted
        this.checkinExecutor.shutdown();
        // shutdownNow stops all currently executing tasks
        this.checkinExecutor.shutdownNow();
    }

    /**
     * Return the list of account names for users to select
     */
    public static String[] getAccountList(Context context) {
        AccountManager accountManager = AccountManager.get(context.getApplicationContext());
        Account[] accounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
        String[] accountNames = null;
        if (accounts != null && accounts.length > 0) {
            accountNames = new String[accounts.length];
            for (int i = 0; i < accounts.length; i++) {
                accountNames[i] = accounts[i].name;
            }
        }
        return accountNames;
    }

    /**
     * Allows clients of AccountSelector to request an authentication upon the next call to
     * authenticate()
     */
    public synchronized void setAuthImmediately(boolean val) {
        this.authImmediately = val;
    }

    private synchronized boolean shouldAuthImmediately() {
        return this.authImmediately;
    }

    private synchronized void setLastAuthTime(long lastTime) {
        this.lastAuthTime = lastTime;
    }

    private synchronized long getLastAuthTime() {
        return this.lastAuthTime;
    }

    /** Starts an authentication request */
    public void authenticate() throws OperationCanceledException, AuthenticatorException, IOException {
        Logger.i("AccountSelector.authenticate() running");
        /*
         * We only need to authenticate every AUTHENTICATE_PERIOD_MILLI milliseconds, during which we
         * can reuse the cookie. If authentication fails due to expired authToken, the client of
         * AccountSelector can call authImmedately() to request authenticate() upon the next checkin
         */
        long authTimeLast = this.getLastAuthTime();
        long timeSinceLastAuth = System.currentTimeMillis() - authTimeLast;
        if (!this.shouldAuthImmediately() && authTimeLast != 0 && (timeSinceLastAuth < AUTHENTICATE_PERIOD_MSEC)) {
            return;
        }

        Logger.i("Authenticating. Last authentication is " + timeSinceLastAuth / 1000 / 60 + " minutes ago. ");

        AccountManager accountManager = AccountManager.get(context.getApplicationContext());
        if (this.authToken != null) {
            // There will be no effect on the token if it is still valid
            Logger.i("Invalidating token");
            accountManager.invalidateAuthToken(ACCOUNT_TYPE, this.authToken);
        }

        Account[] accounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
        Logger.i("Got " + accounts.length + " accounts");

        // get selected account
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.context);
        String selectedAccount = prefs.getString(Config.PREF_KEY_SELECTED_ACCOUNT, null);

        if (accounts != null && accounts.length > 0 && selectedAccount != null) {
            Account accountToUse = null;
            for (Account account : accounts) {
                // if (account.name.toLowerCase().trim().endsWith(ACCOUNT_NAME)) {
                Logger.i("account list: " + account.name + " " + account.type + " " + account.toString());
                // If one of the available accounts is the one selected by user, use that
                if (account.name.equals(selectedAccount)) {
                    accountToUse = account;
                    Logger.i("selected account: " + account.name + " " + account.type + " " + account.toString());
                }
            }

            Logger.i("Trying to get auth token for " + accountToUse);

            AccountManagerFuture<Bundle> future = accountManager.getAuthToken(accountToUse, "ah", false,
                    new AccountManagerCallback<Bundle>() {
                        @Override
                        public void run(AccountManagerFuture<Bundle> result) {
                            Logger.i("AccountManagerCallback invoked");
                            try {
                                getAuthToken(result);
                            } catch (RuntimeException e) {
                                Logger.e("Failed to get authToken", e);
                                /*
                                 * TODO(Wenjie): May ask the user whether to quit the app nicely here if a number of
                                 * trials have been made and failed. Since Speedometer is basically useless without
                                 * checkin
                                 */
                            }
                        }
                    }, null);
            Logger.i("AccountManager.getAuthToken returned " + future);
        } else {
            throw new RuntimeException("No google account found or no google account selected");
        }
    }

    private void getAuthToken(AccountManagerFuture<Bundle> result) {
        Logger.i("getAuthToken() called, result " + result);
        String errMsg = "Failed to get login cookie. ";
        Bundle bundle;
        try {
            bundle = result.getResult();
            Intent intent = (Intent) bundle.get(AccountManager.KEY_INTENT);
            if (intent != null) {
                // User input required. (A UI will pop up for user's consent to
                // allow
                // this app access account information.)
                Logger.i("Starting account manager activity");
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                context.startActivity(intent);
            } else {
                Logger.i("Executing getCookie task");
                synchronized (this) {
                    this.authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
                    this.checkinFuture = checkinExecutor.submit(new GetCookieTask());
                }
            }
        } catch (OperationCanceledException e) {
            Logger.e(errMsg, e);
            throw new RuntimeException("Can't get login cookie", e);
        } catch (AuthenticatorException e) {
            Logger.e(errMsg, e);
            throw new RuntimeException("Can't get login cookie", e);
        } catch (IOException e) {
            Logger.e(errMsg, e);
            throw new RuntimeException("Can't get login cookie", e);
        }
    }

    private class GetCookieTask implements Callable<Cookie> {
        @Override
        public Cookie call() {
            Logger.i("GetCookieTask running: " + authToken);
            DefaultHttpClient httpClient = new DefaultHttpClient();
            boolean success = false;
            try {
                String loginUrlPrefix = checkin.getServerUrl() + "/_ah/login?continue=" + checkin.getServerUrl()
                        + "&action=Login&auth=";
                // Don't follow redirects
                httpClient.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false);
                HttpGet httpGet = new HttpGet(loginUrlPrefix + authToken);
                HttpResponse response;
                Logger.i("Accessing: " + loginUrlPrefix + authToken);
                response = httpClient.execute(httpGet);
                if (response.getStatusLine().getStatusCode() != 302) {
                    // Response should be a redirect to the "continue" URL.
                    Logger.e("Failed to get login cookie: " + loginUrlPrefix + " returned unexpected error code "
                            + response.getStatusLine().getStatusCode());
                    throw new RuntimeException("Failed to get login cookie: " + loginUrlPrefix
                            + " returned unexpected error code " + response.getStatusLine().getStatusCode());
                }

                Logger.i("Got " + httpClient.getCookieStore().getCookies().size() + " cookies back");

                for (Cookie cookie : httpClient.getCookieStore().getCookies()) {
                    Logger.i("Checking cookie " + cookie);
                    if (cookie.getName().equals("SACSID") || cookie.getName().equals("ACSID")) {
                        Logger.i("Got cookie " + cookie);
                        setLastAuthTime(System.currentTimeMillis());
                        success = true;
                        return cookie;
                    }
                }
                Logger.e("No (S)ASCID cookies returned");
                throw new RuntimeException(
                        "Failed to get login cookie: " + loginUrlPrefix + " did not return any (S)ACSID cookie");
            } catch (ClientProtocolException e) {
                Logger.e("Failed to get login cookie", e);
                throw new RuntimeException("Failed to get login cookie", e);
            } catch (IOException e) {
                Logger.e("Failed to get login cookie", e);
                throw new RuntimeException("Failed to get login cookie", e);
            } finally {
                httpClient.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, true);
                if (!success) {
                    resetCheckinFuture();
                }
            }
        }
    }

}