com.mobiperf.AccountSelector.java Source code

Java tutorial

Introduction

Here is the source code for com.mobiperf.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;

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;

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 java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import com.mobiperf.util.PhoneUtils;
import com.mobiperf.R;

/**
 * Helper class for google account checkins
 */
public class AccountSelector {
    private static final String ACCOUNT_TYPE = "com.google";
    private static final String ACCOUNT_NAME = "@google.com";
    // The authentication period in milliseconds
    private static final long AUTHENTICATE_PERIOD_MSEC = 24 * 3600 * 1000;
    private Context context;
    private String authToken = null;
    private ExecutorService checkinExecutor = null;
    private Future<Cookie> checkinFuture = null;
    private long lastAuthTime = 0;
    private boolean authImmediately = false;
    private PhoneUtils phoneUtils;

    private boolean isAnonymous = true;

    public boolean isAnonymous() {
        return isAnonymous;
    }

    public AccountSelector(Context context) {
        this.context = context;
        this.checkinExecutor = Executors.newFixedThreadPool(1);
        this.phoneUtils = PhoneUtils.getPhoneUtils();
    }

    /** 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();
    }

    /** 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;
    }

    /**
     * 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);
        int numAccounts = accounts == null ? 1 : accounts.length + 1;
        String[] accountNames = new String[numAccounts];
        for (int i = 0; i < accounts.length; i++) {
            accountNames[i] = accounts[i].name;
        }
        accountNames[numAccounts - 1] = context.getString(R.string.defaultUser);
        return accountNames;
    }

    /** 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");

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

        final String defaultUserName = context.getString(R.string.defaultUser);
        isAnonymous = true;
        if (selectedAccount != null && selectedAccount.equals(defaultUserName)) {
            return;
        }

        if (accounts != null && accounts.length > 0) {
            // Default account should be the Anonymous account
            Account accountToUse = accounts[accounts.length - 1];
            if (!accounts[accounts.length - 1].name.equals(defaultUserName)) {
                for (Account account : accounts) {
                    if (account.name.equals(defaultUserName)) {
                        accountToUse = account;
                        break;
                    }
                }
            }
            if (selectedAccount != null) {
                for (Account account : accounts) {
                    if (account.name.equals(selectedAccount)) {
                        accountToUse = account;
                        break;
                    }
                }
            }

            isAnonymous = accountToUse.name.equals(defaultUserName);

            if (isAnonymous) {
                Logger.d("Skipping authentication as account is " + defaultUserName);
                return;
            }

            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");
        }
    }

    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 = phoneUtils.getServerUrl() + "/_ah/login?continue="
                        + phoneUtils.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();
                }
            }
        }
    }

}