EMMA Coverage Report (generated Fri Aug 23 16:39:17 PDT 2013)
[all classes][org.chromium.sync.signin]

COVERAGE SUMMARY FOR SOURCE FILE [AccountManagerHelper.java]

nameclass, %method, %block, %line, %
AccountManagerHelper.java33%  (1/3)37%  (10/27)23%  (139/594)29%  (33.5/117)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class AccountManagerHelper$10%   (0/1)0%   (0/3)0%   (0/52)0%   (0/4)
AccountManagerHelper$1 (AccountManagerHelper, AccountManagerFuture, AtomicBoo... 0%   (0/1)0%   (0/27)0%   (0/1)
doInBackground (Void []): String 0%   (0/1)0%   (0/8)0%   (0/1)
onPostExecute (String): void 0%   (0/1)0%   (0/17)0%   (0/2)
     
class AccountManagerHelper$ConnectionRetry0%   (0/1)0%   (0/3)0%   (0/68)0%   (0/16)
<static initializer> 0%   (0/1)0%   (0/8)0%   (0/1)
AccountManagerHelper$ConnectionRetry (AccountManagerHelper, Account, String, ... 0%   (0/1)0%   (0/21)0%   (0/7)
onConnectionTypeChanged (int): void 0%   (0/1)0%   (0/39)0%   (0/8)
     
class AccountManagerHelper100% (1/1)48%  (10/21)29%  (139/474)34%  (33.5/98)
access$000 (AccountManagerHelper, Activity, Account, String, AccountManagerHe... 0%   (0/1)0%   (0/10)0%   (0/1)
access$100 (AccountManagerHelper, AccountManagerFuture, AtomicBoolean): String 0%   (0/1)0%   (0/5)0%   (0/1)
access$200 (AccountManagerHelper, Account, String, String, AccountManagerHelp... 0%   (0/1)0%   (0/10)0%   (0/1)
getAccountFromName (String): Account 0%   (0/1)0%   (0/30)0%   (0/5)
getAuthTokenFromBackground (Account, String): String 0%   (0/1)0%   (0/19)0%   (0/3)
getGoogleAccountNames (): List 0%   (0/1)0%   (0/32)0%   (0/5)
getNewAuthToken (Account, String, String): String 0%   (0/1)0%   (0/39)0%   (0/11)
getNewAuthTokenFromForeground (Account, String, String, AccountManagerHelper$... 0%   (0/1)0%   (0/30)0%   (0/6)
hasGoogleAccountAuthenticator (): boolean 0%   (0/1)0%   (0/29)0%   (0/4)
hasGoogleAccounts (): boolean 0%   (0/1)0%   (0/8)0%   (0/1)
invalidateAuthToken (String): void 0%   (0/1)0%   (0/6)0%   (0/2)
getAuthTokenInner (AccountManagerFuture, AtomicBoolean): String 100% (1/1)20%  (14/70)19%  (4/21)
onGotAuthTokenResult (Account, String, String, AccountManagerHelper$GetAuthTo... 100% (1/1)27%  (9/33)27%  (2.4/9)
getAuthTokenAsynchronously (Activity, Account, String, AccountManagerHelper$G... 100% (1/1)57%  (36/63)80%  (8/10)
overrideAccountManagerHelperForTests (Context, AccountManagerDelegate): void 100% (1/1)74%  (14/19)93%  (3.7/4)
get (Context): AccountManagerHelper 100% (1/1)80%  (20/25)87%  (4.4/5)
<static initializer> 100% (1/1)100% (5/5)100% (1/1)
AccountManagerHelper (Context, AccountManagerDelegate): void 100% (1/1)100% (10/10)100% (4/4)
createAccountFromName (String): Account 100% (1/1)100% (6/6)100% (1/1)
getAuthTokenFromForeground (Activity, Account, String, AccountManagerHelper$G... 100% (1/1)100% (20/20)100% (4/4)
getGoogleAccounts (): Account [] 100% (1/1)100% (5/5)100% (1/1)

1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4 
5package org.chromium.sync.signin;
6 
7 
8import com.google.common.annotations.VisibleForTesting;
9 
10import android.accounts.Account;
11import android.accounts.AccountManager;
12import android.accounts.AccountManagerFuture;
13import android.accounts.AuthenticatorDescription;
14import android.accounts.AuthenticatorException;
15import android.accounts.OperationCanceledException;
16import android.app.Activity;
17import android.content.Context;
18import android.content.Intent;
19import android.os.AsyncTask;
20import android.os.Bundle;
21import android.util.Log;
22 
23import org.chromium.base.ThreadUtils;
24import org.chromium.net.NetworkChangeNotifier;
25 
26import java.io.IOException;
27import java.util.ArrayList;
28import java.util.concurrent.atomic.AtomicBoolean;
29import java.util.concurrent.atomic.AtomicInteger;
30import java.util.List;
31import javax.annotation.Nullable;
32 
33/**
34 * AccountManagerHelper wraps our access of AccountManager in Android.
35 *
36 * Use the AccountManagerHelper.get(someContext) to instantiate it
37 */
38public class AccountManagerHelper {
39 
40    private static final String TAG = "AccountManagerHelper";
41 
42    public static final String GOOGLE_ACCOUNT_TYPE = "com.google";
43 
44    private static final Object lock = new Object();
45 
46    private static final int MAX_TRIES = 3;
47 
48    private static AccountManagerHelper sAccountManagerHelper;
49 
50    private final AccountManagerDelegate mAccountManager;
51 
52    private Context mApplicationContext;
53 
54    public interface GetAuthTokenCallback {
55        /**
56         * Invoked on the UI thread once a token has been provided by the AccountManager.
57         * @param token Auth token, or null if no token is available (bad credentials,
58         *      permission denied, etc).
59         */
60        void tokenAvailable(String token);
61    }
62 
63    /**
64     * @param context the Android context
65     * @param accountManager the account manager to use as a backend service
66     */
67    private AccountManagerHelper(Context context,
68                                 AccountManagerDelegate accountManager) {
69        mApplicationContext = context.getApplicationContext();
70        mAccountManager = accountManager;
71    }
72 
73    /**
74     * A factory method for the AccountManagerHelper.
75     *
76     * It is possible to override the AccountManager to use in tests for the instance of the
77     * AccountManagerHelper by calling overrideAccountManagerHelperForTests(...) with
78     * your MockAccountManager.
79     *
80     * @param context the applicationContext is retrieved from the context used as an argument.
81     * @return a singleton instance of the AccountManagerHelper
82     */
83    public static AccountManagerHelper get(Context context) {
84        synchronized (lock) {
85            if (sAccountManagerHelper == null) {
86                sAccountManagerHelper = new AccountManagerHelper(context,
87                        new SystemAccountManagerDelegate(context));
88            }
89        }
90        return sAccountManagerHelper;
91    }
92 
93    @VisibleForTesting
94    public static void overrideAccountManagerHelperForTests(Context context,
95            AccountManagerDelegate accountManager) {
96        synchronized (lock) {
97            sAccountManagerHelper = new AccountManagerHelper(context, accountManager);
98        }
99    }
100 
101    /**
102     * Creates an Account object for the given name.
103     */
104    public static Account createAccountFromName(String name) {
105        return new Account(name, GOOGLE_ACCOUNT_TYPE);
106    }
107 
108    public List<String> getGoogleAccountNames() {
109        List<String> accountNames = new ArrayList<String>();
110        Account[] accounts = mAccountManager.getAccountsByType(GOOGLE_ACCOUNT_TYPE);
111        for (Account account : accounts) {
112            accountNames.add(account.name);
113        }
114        return accountNames;
115    }
116 
117    public Account[] getGoogleAccounts() {
118        return mAccountManager.getAccountsByType(GOOGLE_ACCOUNT_TYPE);
119    }
120 
121    public boolean hasGoogleAccounts() {
122        return getGoogleAccounts().length > 0;
123    }
124 
125    /**
126     * Returns the account if it exists, null otherwise.
127     */
128    public Account getAccountFromName(String accountName) {
129        Account[] accounts = mAccountManager.getAccountsByType(GOOGLE_ACCOUNT_TYPE);
130        for (Account account : accounts) {
131            if (account.name.equals(accountName)) {
132                return account;
133            }
134        }
135        return null;
136    }
137 
138    /**
139     * @return Whether or not there is an account authenticator for Google accounts.
140     */
141    public boolean hasGoogleAccountAuthenticator() {
142        AuthenticatorDescription[] descs = mAccountManager.getAuthenticatorTypes();
143        for (AuthenticatorDescription desc : descs) {
144            if (GOOGLE_ACCOUNT_TYPE.equals(desc.type)) return true;
145        }
146        return false;
147    }
148 
149    /**
150     * Gets the auth token synchronously.
151     *
152     * - Assumes that the account is a valid account.
153     * - Should not be called on the main thread.
154     */
155    @Deprecated
156    public String getAuthTokenFromBackground(Account account, String authTokenType) {
157            AccountManagerFuture<Bundle> future = mAccountManager.getAuthToken(account,
158                    authTokenType, false, null, null);
159            AtomicBoolean errorEncountered = new AtomicBoolean(false);
160            return getAuthTokenInner(future, errorEncountered);
161    }
162 
163    /**
164     * Gets the auth token and returns the response asynchronously.
165     * This should be called when we have a foreground activity that needs an auth token.
166     * If encountered an IO error, it will attempt to retry when the network is back.
167     *
168     * - Assumes that the account is a valid account.
169     */
170    public void getAuthTokenFromForeground(Activity activity, Account account, String authTokenType,
171                GetAuthTokenCallback callback) {
172        AtomicInteger numTries = new AtomicInteger(0);
173        AtomicBoolean errorEncountered = new AtomicBoolean(false);
174        getAuthTokenAsynchronously(activity, account, authTokenType, callback, numTries,
175                errorEncountered, null);
176    }
177 
178    private class ConnectionRetry implements NetworkChangeNotifier.ConnectionTypeObserver {
179        private final Account mAccount;
180        private final String mAuthTokenType;
181        private final GetAuthTokenCallback mCallback;
182        private final AtomicInteger mNumTries;
183        private final AtomicBoolean mErrorEncountered;
184 
185        ConnectionRetry(Account account, String authTokenType, GetAuthTokenCallback callback,
186                AtomicInteger numTries, AtomicBoolean errorEncountered) {
187            mAccount = account;
188            mAuthTokenType = authTokenType;
189            mCallback = callback;
190            mNumTries = numTries;
191            mErrorEncountered = errorEncountered;
192        }
193 
194        @Override
195        public void onConnectionTypeChanged(int connectionType) {
196            assert mNumTries.get() <= MAX_TRIES;
197            if (mNumTries.get() == MAX_TRIES) {
198                NetworkChangeNotifier.removeConnectionTypeObserver(this);
199                return;
200            }
201            if (NetworkChangeNotifier.isOnline()) {
202                NetworkChangeNotifier.removeConnectionTypeObserver(this);
203                getAuthTokenAsynchronously(null, mAccount, mAuthTokenType, mCallback, mNumTries,
204                        mErrorEncountered, this);
205            }
206        }
207    }
208 
209    // Gets the auth token synchronously
210    private String getAuthTokenInner(AccountManagerFuture<Bundle> future,
211            AtomicBoolean errorEncountered) {
212        try {
213            Bundle result = future.getResult();
214            if (result != null) {
215                if (result.containsKey(AccountManager.KEY_INTENT)) {
216                    Log.d(TAG, "Starting intent to get auth credentials");
217                    // Need to start intent to get credentials
218                    Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
219                    int flags = intent.getFlags();
220                    flags |= Intent.FLAG_ACTIVITY_NEW_TASK;
221                    intent.setFlags(flags);
222                    mApplicationContext.startActivity(intent);
223                    return null;
224                }
225                return result.getString(AccountManager.KEY_AUTHTOKEN);
226            } else {
227                Log.w(TAG, "Auth token - getAuthToken returned null");
228            }
229        } catch (OperationCanceledException e) {
230            Log.w(TAG, "Auth token - operation cancelled", e);
231        } catch (AuthenticatorException e) {
232            Log.w(TAG, "Auth token - authenticator exception", e);
233        } catch (IOException e) {
234            Log.w(TAG, "Auth token - IO exception", e);
235            errorEncountered.set(true);
236        }
237        return null;
238    }
239 
240    private void getAuthTokenAsynchronously(@Nullable Activity activity, final Account account,
241            final String authTokenType, final GetAuthTokenCallback callback,
242            final AtomicInteger numTries, final AtomicBoolean errorEncountered,
243            final ConnectionRetry retry) {
244        AccountManagerFuture<Bundle> future;
245        if (numTries.get() == 0 && activity != null) {
246            future = mAccountManager.getAuthToken(
247                    account, authTokenType, null, activity, null, null);
248        } else {
249            future = mAccountManager.getAuthToken(
250                    account, authTokenType, false, null, null);
251        }
252        final AccountManagerFuture<Bundle> finalFuture = future;
253        errorEncountered.set(false);
254 
255        // On ICS onPostExecute is never called when running an AsyncTask from a different thread
256        // than the UI thread.
257        if (ThreadUtils.runningOnUiThread()) {
258            new AsyncTask<Void, Void, String>() {
259                @Override
260                public String doInBackground(Void... params) {
261                    return getAuthTokenInner(finalFuture, errorEncountered);
262                }
263                @Override
264                public void onPostExecute(String authToken) {
265                    onGotAuthTokenResult(account, authTokenType, authToken, callback, numTries,
266                            errorEncountered, retry);
267                }
268            }.execute();
269        } else {
270            String authToken = getAuthTokenInner(finalFuture, errorEncountered);
271            onGotAuthTokenResult(account, authTokenType, authToken, callback, numTries,
272                    errorEncountered, retry);
273        }
274    }
275 
276    private void onGotAuthTokenResult(Account account, String authTokenType, String authToken,
277            GetAuthTokenCallback callback, AtomicInteger numTries, AtomicBoolean errorEncountered,
278            ConnectionRetry retry) {
279        if (authToken != null || !errorEncountered.get() ||
280                numTries.incrementAndGet() == MAX_TRIES ||
281                !NetworkChangeNotifier.isInitialized()) {
282            callback.tokenAvailable(authToken);
283            return;
284        }
285        if (retry == null) {
286            ConnectionRetry newRetry = new ConnectionRetry(account, authTokenType, callback,
287                    numTries, errorEncountered);
288            NetworkChangeNotifier.addConnectionTypeObserver(newRetry);
289        } else {
290            NetworkChangeNotifier.addConnectionTypeObserver(retry);
291        }
292    }
293 
294    /**
295     * Invalidates the old token (if non-null/non-empty) and synchronously generates a new one.
296     * Also notifies the user (via status bar) if any user action is required. The method will
297     * return null if any user action is required to generate the new token.
298     *
299     * - Assumes that the account is a valid account.
300     * - Should not be called on the main thread.
301     */
302    @Deprecated
303    public String getNewAuthToken(Account account, String authToken, String authTokenType) {
304        // TODO(dsmyers): consider reimplementing using an AccountManager function with an
305        // explicit timeout.
306        // Bug: https://code.google.com/p/chromium/issues/detail?id=172394.
307        if (authToken != null && !authToken.isEmpty()) {
308            mAccountManager.invalidateAuthToken(GOOGLE_ACCOUNT_TYPE, authToken);
309        }
310 
311        try {
312            return mAccountManager.blockingGetAuthToken(account, authTokenType, true);
313        } catch (OperationCanceledException e) {
314            Log.w(TAG, "Auth token - operation cancelled", e);
315        } catch (AuthenticatorException e) {
316            Log.w(TAG, "Auth token - authenticator exception", e);
317        } catch (IOException e) {
318            Log.w(TAG, "Auth token - IO exception", e);
319        }
320        return null;
321    }
322 
323    /**
324     * Invalidates the old token (if non-null/non-empty) and asynchronously generates a new one.
325     *
326     * - Assumes that the account is a valid account.
327     */
328    public void getNewAuthTokenFromForeground(Account account, String authToken,
329                String authTokenType, GetAuthTokenCallback callback) {
330        if (authToken != null && !authToken.isEmpty()) {
331            mAccountManager.invalidateAuthToken(GOOGLE_ACCOUNT_TYPE, authToken);
332        }
333        AtomicInteger numTries = new AtomicInteger(0);
334        AtomicBoolean errorEncountered = new AtomicBoolean(false);
335        getAuthTokenAsynchronously(
336            null, account, authTokenType, callback, numTries, errorEncountered, null);
337    }
338 
339    /**
340     * Removes an auth token from the AccountManager's cache.
341     */
342    public void invalidateAuthToken(String authToken) {
343        mAccountManager.invalidateAuthToken(GOOGLE_ACCOUNT_TYPE, authToken);
344    }
345}

[all classes][org.chromium.sync.signin]
EMMA 2.0.5312 (C) Vladimir Roubtsov