• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.accounts;
18 
19 import android.Manifest;
20 import android.annotation.SystemApi;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.PackageManager;
24 import android.os.Binder;
25 import android.os.Bundle;
26 import android.os.IBinder;
27 import android.os.RemoteException;
28 import android.text.TextUtils;
29 import android.util.Log;
30 
31 import java.util.Arrays;
32 
33 /**
34  * Abstract base class for creating AccountAuthenticators.
35  * In order to be an authenticator one must extend this class, provider implementations for the
36  * abstract methods and write a service that returns the result of {@link #getIBinder()}
37  * in the service's {@link android.app.Service#onBind(android.content.Intent)} when invoked
38  * with an intent with action {@link AccountManager#ACTION_AUTHENTICATOR_INTENT}. This service
39  * must specify the following intent filter and metadata tags in its AndroidManifest.xml file
40  * <pre>
41  *   &lt;intent-filter&gt;
42  *     &lt;action android:name="android.accounts.AccountAuthenticator" /&gt;
43  *   &lt;/intent-filter&gt;
44  *   &lt;meta-data android:name="android.accounts.AccountAuthenticator"
45  *             android:resource="@xml/authenticator" /&gt;
46  * </pre>
47  * The <code>android:resource</code> attribute must point to a resource that looks like:
48  * <pre>
49  * &lt;account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
50  *    android:accountType="typeOfAuthenticator"
51  *    android:icon="@drawable/icon"
52  *    android:smallIcon="@drawable/miniIcon"
53  *    android:label="@string/label"
54  *    android:accountPreferences="@xml/account_preferences"
55  * /&gt;
56  * </pre>
57  * Replace the icons and labels with your own resources. The <code>android:accountType</code>
58  * attribute must be a string that uniquely identifies your authenticator and will be the same
59  * string that user will use when making calls on the {@link AccountManager} and it also
60  * corresponds to {@link Account#type} for your accounts. One user of the android:icon is the
61  * "Account & Sync" settings page and one user of the android:smallIcon is the Contact Application's
62  * tab panels.
63  * <p>
64  * The preferences attribute points to a PreferenceScreen xml hierarchy that contains
65  * a list of PreferenceScreens that can be invoked to manage the authenticator. An example is:
66  * <pre>
67  * &lt;PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"&gt;
68  *    &lt;PreferenceCategory android:title="@string/title_fmt" /&gt;
69  *    &lt;PreferenceScreen
70  *         android:key="key1"
71  *         android:title="@string/key1_action"
72  *         android:summary="@string/key1_summary"&gt;
73  *         &lt;intent
74  *             android:action="key1.ACTION"
75  *             android:targetPackage="key1.package"
76  *             android:targetClass="key1.class" /&gt;
77  *     &lt;/PreferenceScreen&gt;
78  * &lt;/PreferenceScreen&gt;
79  * </pre>
80  *
81  * <p>
82  * The standard pattern for implementing any of the abstract methods is the following:
83  * <ul>
84  * <li> If the supplied arguments are enough for the authenticator to fully satisfy the request
85  * then it will do so and return a {@link Bundle} that contains the results.
86  * <li> If the authenticator needs information from the user to satisfy the request then it
87  * will create an {@link Intent} to an activity that will prompt the user for the information
88  * and then carry out the request. This intent must be returned in a Bundle as key
89  * {@link AccountManager#KEY_INTENT}.
90  * <p>
91  * The activity needs to return the final result when it is complete so the Intent should contain
92  * the {@link AccountAuthenticatorResponse} as {@link AccountManager#KEY_ACCOUNT_MANAGER_RESPONSE}.
93  * The activity must then call {@link AccountAuthenticatorResponse#onResult} or
94  * {@link AccountAuthenticatorResponse#onError} when it is complete.
95  * <li> If the authenticator cannot synchronously process the request and return a result then it
96  * may choose to return null and then use the AccountManagerResponse to send the result
97  * when it has completed the request.
98  * </ul>
99  * <p>
100  * The following descriptions of each of the abstract authenticator methods will not describe the
101  * possible asynchronous nature of the request handling and will instead just describe the input
102  * parameters and the expected result.
103  * <p>
104  * When writing an activity to satisfy these requests one must pass in the AccountManagerResponse
105  * and return the result via that response when the activity finishes (or whenever else  the
106  * activity author deems it is the correct time to respond).
107  * The {@link AccountAuthenticatorActivity} handles this, so one may wish to extend that when
108  * writing activities to handle these requests.
109  */
110 public abstract class AbstractAccountAuthenticator {
111     private static final String TAG = "AccountAuthenticator";
112 
113     /**
114      * Bundle key used for the {@code long} expiration time (in millis from the unix epoch) of the
115      * associated auth token.
116      *
117      * @see #getAuthToken
118      */
119     public static final String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry";
120 
121     /**
122      * Bundle key used for the {@link String} account type in session bundle.
123      * This is used in the default implementation of
124      * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}.
125      */
126     private static final String KEY_AUTH_TOKEN_TYPE =
127             "android.accounts.AbstractAccountAuthenticato.KEY_AUTH_TOKEN_TYPE";
128     /**
129      * Bundle key used for the {@link String} array of required features in
130      * session bundle. This is used in the default implementation of
131      * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}.
132      */
133     private static final String KEY_REQUIRED_FEATURES =
134             "android.accounts.AbstractAccountAuthenticator.KEY_REQUIRED_FEATURES";
135     /**
136      * Bundle key used for the {@link Bundle} options in session bundle. This is
137      * used in default implementation of {@link #startAddAccountSession} and
138      * {@link startUpdateCredentialsSession}.
139      */
140     private static final String KEY_OPTIONS =
141             "android.accounts.AbstractAccountAuthenticator.KEY_OPTIONS";
142     /**
143      * Bundle key used for the {@link Account} account in session bundle. This is used
144      * used in default implementation of {@link startUpdateCredentialsSession}.
145      */
146     private static final String KEY_ACCOUNT =
147             "android.accounts.AbstractAccountAuthenticator.KEY_ACCOUNT";
148 
149     private final Context mContext;
150 
AbstractAccountAuthenticator(Context context)151     public AbstractAccountAuthenticator(Context context) {
152         mContext = context;
153     }
154 
155     private class Transport extends IAccountAuthenticator.Stub {
156         @Override
addAccount(IAccountAuthenticatorResponse response, String accountType, String authTokenType, String[] features, Bundle options)157         public void addAccount(IAccountAuthenticatorResponse response, String accountType,
158                 String authTokenType, String[] features, Bundle options)
159                 throws RemoteException {
160             if (Log.isLoggable(TAG, Log.VERBOSE)) {
161                 Log.v(TAG, "addAccount: accountType " + accountType
162                         + ", authTokenType " + authTokenType
163                         + ", features " + (features == null ? "[]" : Arrays.toString(features)));
164             }
165             checkBinderPermission();
166             try {
167                 final Bundle result = AbstractAccountAuthenticator.this.addAccount(
168                     new AccountAuthenticatorResponse(response),
169                         accountType, authTokenType, features, options);
170                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
171                     if (result != null) {
172                         result.keySet(); // force it to be unparcelled
173                     }
174                     Log.v(TAG, "addAccount: result " + AccountManager.sanitizeResult(result));
175                 }
176                 if (result != null) {
177                     response.onResult(result);
178                 }
179             } catch (Exception e) {
180                 handleException(response, "addAccount", accountType, e);
181             }
182         }
183 
184         @Override
confirmCredentials(IAccountAuthenticatorResponse response, Account account, Bundle options)185         public void confirmCredentials(IAccountAuthenticatorResponse response,
186                 Account account, Bundle options) throws RemoteException {
187             if (Log.isLoggable(TAG, Log.VERBOSE)) {
188                 Log.v(TAG, "confirmCredentials: " + account);
189             }
190             checkBinderPermission();
191             try {
192                 final Bundle result = AbstractAccountAuthenticator.this.confirmCredentials(
193                     new AccountAuthenticatorResponse(response), account, options);
194                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
195                     if (result != null) {
196                         result.keySet(); // force it to be unparcelled
197                     }
198                     Log.v(TAG, "confirmCredentials: result "
199                             + AccountManager.sanitizeResult(result));
200                 }
201                 if (result != null) {
202                     response.onResult(result);
203                 }
204             } catch (Exception e) {
205                 handleException(response, "confirmCredentials", account.toString(), e);
206             }
207         }
208 
209         @Override
getAuthTokenLabel(IAccountAuthenticatorResponse response, String authTokenType)210         public void getAuthTokenLabel(IAccountAuthenticatorResponse response,
211                 String authTokenType)
212                 throws RemoteException {
213             if (Log.isLoggable(TAG, Log.VERBOSE)) {
214                 Log.v(TAG, "getAuthTokenLabel: authTokenType " + authTokenType);
215             }
216             checkBinderPermission();
217             try {
218                 Bundle result = new Bundle();
219                 result.putString(AccountManager.KEY_AUTH_TOKEN_LABEL,
220                         AbstractAccountAuthenticator.this.getAuthTokenLabel(authTokenType));
221                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
222                     if (result != null) {
223                         result.keySet(); // force it to be unparcelled
224                     }
225                     Log.v(TAG, "getAuthTokenLabel: result "
226                             + AccountManager.sanitizeResult(result));
227                 }
228                 response.onResult(result);
229             } catch (Exception e) {
230                 handleException(response, "getAuthTokenLabel", authTokenType, e);
231             }
232         }
233 
234         @Override
getAuthToken(IAccountAuthenticatorResponse response, Account account, String authTokenType, Bundle loginOptions)235         public void getAuthToken(IAccountAuthenticatorResponse response,
236                 Account account, String authTokenType, Bundle loginOptions)
237                 throws RemoteException {
238             if (Log.isLoggable(TAG, Log.VERBOSE)) {
239                 Log.v(TAG, "getAuthToken: " + account
240                         + ", authTokenType " + authTokenType);
241             }
242             checkBinderPermission();
243             try {
244                 final Bundle result = AbstractAccountAuthenticator.this.getAuthToken(
245                         new AccountAuthenticatorResponse(response), account,
246                         authTokenType, loginOptions);
247                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
248                     if (result != null) {
249                         result.keySet(); // force it to be unparcelled
250                     }
251                     Log.v(TAG, "getAuthToken: result " + AccountManager.sanitizeResult(result));
252                 }
253                 if (result != null) {
254                     response.onResult(result);
255                 }
256             } catch (Exception e) {
257                 handleException(response, "getAuthToken",
258                         account.toString() + "," + authTokenType, e);
259             }
260         }
261 
262         @Override
updateCredentials(IAccountAuthenticatorResponse response, Account account, String authTokenType, Bundle loginOptions)263         public void updateCredentials(IAccountAuthenticatorResponse response, Account account,
264                 String authTokenType, Bundle loginOptions) throws RemoteException {
265             if (Log.isLoggable(TAG, Log.VERBOSE)) {
266                 Log.v(TAG, "updateCredentials: " + account
267                         + ", authTokenType " + authTokenType);
268             }
269             checkBinderPermission();
270             try {
271                 final Bundle result = AbstractAccountAuthenticator.this.updateCredentials(
272                     new AccountAuthenticatorResponse(response), account,
273                         authTokenType, loginOptions);
274                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
275                     // Result may be null.
276                     if (result != null) {
277                         result.keySet(); // force it to be unparcelled
278                     }
279                     Log.v(TAG, "updateCredentials: result "
280                             + AccountManager.sanitizeResult(result));
281                 }
282                 if (result != null) {
283                     response.onResult(result);
284                 }
285             } catch (Exception e) {
286                 handleException(response, "updateCredentials",
287                         account.toString() + "," + authTokenType, e);
288             }
289         }
290 
291         @Override
editProperties(IAccountAuthenticatorResponse response, String accountType)292         public void editProperties(IAccountAuthenticatorResponse response,
293                 String accountType) throws RemoteException {
294             checkBinderPermission();
295             try {
296                 final Bundle result = AbstractAccountAuthenticator.this.editProperties(
297                     new AccountAuthenticatorResponse(response), accountType);
298                 if (result != null) {
299                     response.onResult(result);
300                 }
301             } catch (Exception e) {
302                 handleException(response, "editProperties", accountType, e);
303             }
304         }
305 
306         @Override
hasFeatures(IAccountAuthenticatorResponse response, Account account, String[] features)307         public void hasFeatures(IAccountAuthenticatorResponse response,
308                 Account account, String[] features) throws RemoteException {
309             checkBinderPermission();
310             try {
311                 final Bundle result = AbstractAccountAuthenticator.this.hasFeatures(
312                     new AccountAuthenticatorResponse(response), account, features);
313                 if (result != null) {
314                     response.onResult(result);
315                 }
316             } catch (Exception e) {
317                 handleException(response, "hasFeatures", account.toString(), e);
318             }
319         }
320 
321         @Override
getAccountRemovalAllowed(IAccountAuthenticatorResponse response, Account account)322         public void getAccountRemovalAllowed(IAccountAuthenticatorResponse response,
323                 Account account) throws RemoteException {
324             checkBinderPermission();
325             try {
326                 final Bundle result = AbstractAccountAuthenticator.this.getAccountRemovalAllowed(
327                     new AccountAuthenticatorResponse(response), account);
328                 if (result != null) {
329                     response.onResult(result);
330                 }
331             } catch (Exception e) {
332                 handleException(response, "getAccountRemovalAllowed", account.toString(), e);
333             }
334         }
335 
336         @Override
getAccountCredentialsForCloning(IAccountAuthenticatorResponse response, Account account)337         public void getAccountCredentialsForCloning(IAccountAuthenticatorResponse response,
338                 Account account) throws RemoteException {
339             checkBinderPermission();
340             try {
341                 final Bundle result =
342                         AbstractAccountAuthenticator.this.getAccountCredentialsForCloning(
343                                 new AccountAuthenticatorResponse(response), account);
344                 if (result != null) {
345                     response.onResult(result);
346                 }
347             } catch (Exception e) {
348                 handleException(response, "getAccountCredentialsForCloning", account.toString(), e);
349             }
350         }
351 
352         @Override
addAccountFromCredentials(IAccountAuthenticatorResponse response, Account account, Bundle accountCredentials)353         public void addAccountFromCredentials(IAccountAuthenticatorResponse response,
354                 Account account,
355                 Bundle accountCredentials) throws RemoteException {
356             checkBinderPermission();
357             try {
358                 final Bundle result =
359                         AbstractAccountAuthenticator.this.addAccountFromCredentials(
360                                 new AccountAuthenticatorResponse(response), account,
361                                 accountCredentials);
362                 if (result != null) {
363                     response.onResult(result);
364                 }
365             } catch (Exception e) {
366                 handleException(response, "addAccountFromCredentials", account.toString(), e);
367             }
368         }
369 
370         @Override
startAddAccountSession(IAccountAuthenticatorResponse response, String accountType, String authTokenType, String[] features, Bundle options)371         public void startAddAccountSession(IAccountAuthenticatorResponse response,
372                 String accountType, String authTokenType, String[] features, Bundle options)
373                 throws RemoteException {
374             if (Log.isLoggable(TAG, Log.VERBOSE)) {
375                 Log.v(TAG,
376                         "startAddAccountSession: accountType " + accountType
377                         + ", authTokenType " + authTokenType
378                         + ", features " + (features == null ? "[]" : Arrays.toString(features)));
379             }
380             checkBinderPermission();
381             try {
382                 final Bundle result = AbstractAccountAuthenticator.this.startAddAccountSession(
383                         new AccountAuthenticatorResponse(response), accountType, authTokenType,
384                         features, options);
385                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
386                     if (result != null) {
387                         result.keySet(); // force it to be unparcelled
388                     }
389                     Log.v(TAG, "startAddAccountSession: result "
390                             + AccountManager.sanitizeResult(result));
391                 }
392                 if (result != null) {
393                     response.onResult(result);
394                 }
395             } catch (Exception e) {
396                 handleException(response, "startAddAccountSession", accountType, e);
397             }
398         }
399 
400         @Override
startUpdateCredentialsSession( IAccountAuthenticatorResponse response, Account account, String authTokenType, Bundle loginOptions)401         public void startUpdateCredentialsSession(
402                 IAccountAuthenticatorResponse response,
403                 Account account,
404                 String authTokenType,
405                 Bundle loginOptions) throws RemoteException {
406             if (Log.isLoggable(TAG, Log.VERBOSE)) {
407                 Log.v(TAG, "startUpdateCredentialsSession: "
408                         + account
409                         + ", authTokenType "
410                         + authTokenType);
411             }
412             checkBinderPermission();
413             try {
414                 final Bundle result = AbstractAccountAuthenticator.this
415                         .startUpdateCredentialsSession(
416                                 new AccountAuthenticatorResponse(response),
417                                 account,
418                                 authTokenType,
419                                 loginOptions);
420                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
421                     // Result may be null.
422                     if (result != null) {
423                         result.keySet(); // force it to be unparcelled
424                     }
425                     Log.v(TAG, "startUpdateCredentialsSession: result "
426                             + AccountManager.sanitizeResult(result));
427 
428                 }
429                 if (result != null) {
430                     response.onResult(result);
431                 }
432             } catch (Exception e) {
433                 handleException(response, "startUpdateCredentialsSession",
434                         account.toString() + "," + authTokenType, e);
435 
436             }
437         }
438 
439         @Override
finishSession( IAccountAuthenticatorResponse response, String accountType, Bundle sessionBundle)440         public void finishSession(
441                 IAccountAuthenticatorResponse response,
442                 String accountType,
443                 Bundle sessionBundle) throws RemoteException {
444             if (Log.isLoggable(TAG, Log.VERBOSE)) {
445                 Log.v(TAG, "finishSession: accountType " + accountType);
446             }
447             checkBinderPermission();
448             try {
449                 final Bundle result = AbstractAccountAuthenticator.this.finishSession(
450                         new AccountAuthenticatorResponse(response), accountType, sessionBundle);
451                 if (result != null) {
452                     result.keySet(); // force it to be unparcelled
453                 }
454                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
455                     Log.v(TAG, "finishSession: result " + AccountManager.sanitizeResult(result));
456                 }
457                 if (result != null) {
458                     response.onResult(result);
459                 }
460             } catch (Exception e) {
461                 handleException(response, "finishSession", accountType, e);
462 
463             }
464         }
465 
466         @Override
isCredentialsUpdateSuggested( IAccountAuthenticatorResponse response, Account account, String statusToken)467         public void isCredentialsUpdateSuggested(
468                 IAccountAuthenticatorResponse response,
469                 Account account,
470                 String statusToken) throws RemoteException {
471             checkBinderPermission();
472             try {
473                 final Bundle result = AbstractAccountAuthenticator.this
474                         .isCredentialsUpdateSuggested(
475                                 new AccountAuthenticatorResponse(response), account, statusToken);
476                 if (result != null) {
477                     response.onResult(result);
478                 }
479             } catch (Exception e) {
480                 handleException(response, "isCredentialsUpdateSuggested", account.toString(), e);
481             }
482         }
483     }
484 
handleException(IAccountAuthenticatorResponse response, String method, String data, Exception e)485     private void handleException(IAccountAuthenticatorResponse response, String method,
486             String data, Exception e) throws RemoteException {
487         if (e instanceof NetworkErrorException) {
488             if (Log.isLoggable(TAG, Log.VERBOSE)) {
489                 Log.v(TAG, method + "(" + data + ")", e);
490             }
491             response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage());
492         } else if (e instanceof UnsupportedOperationException) {
493             if (Log.isLoggable(TAG, Log.VERBOSE)) {
494                 Log.v(TAG, method + "(" + data + ")", e);
495             }
496             response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
497                     method + " not supported");
498         } else if (e instanceof IllegalArgumentException) {
499             if (Log.isLoggable(TAG, Log.VERBOSE)) {
500                 Log.v(TAG, method + "(" + data + ")", e);
501             }
502             response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS,
503                     method + " not supported");
504         } else {
505             Log.w(TAG, method + "(" + data + ")", e);
506             response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
507                     method + " failed");
508         }
509     }
510 
checkBinderPermission()511     private void checkBinderPermission() {
512         final int uid = Binder.getCallingUid();
513         final String perm = Manifest.permission.ACCOUNT_MANAGER;
514         if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
515             throw new SecurityException("caller uid " + uid + " lacks " + perm);
516         }
517     }
518 
519     private Transport mTransport = new Transport();
520 
521     /**
522      * @return the IBinder for the AccountAuthenticator
523      */
getIBinder()524     public final IBinder getIBinder() {
525         return mTransport.asBinder();
526     }
527 
528     /**
529      * Returns a Bundle that contains the Intent of the activity that can be used to edit the
530      * properties. In order to indicate success the activity should call response.setResult()
531      * with a non-null Bundle.
532      * @param response used to set the result for the request. If the Constants.INTENT_KEY
533      *   is set in the bundle then this response field is to be used for sending future
534      *   results if and when the Intent is started.
535      * @param accountType the AccountType whose properties are to be edited.
536      * @return a Bundle containing the result or the Intent to start to continue the request.
537      *   If this is null then the request is considered to still be active and the result should
538      *   sent later using response.
539      */
editProperties(AccountAuthenticatorResponse response, String accountType)540     public abstract Bundle editProperties(AccountAuthenticatorResponse response,
541             String accountType);
542 
543     /**
544      * Adds an account of the specified accountType.
545      * @param response to send the result back to the AccountManager, will never be null
546      * @param accountType the type of account to add, will never be null
547      * @param authTokenType the type of auth token to retrieve after adding the account, may be null
548      * @param requiredFeatures a String array of authenticator-specific features that the added
549      * account must support, may be null
550      * @param options a Bundle of authenticator-specific options, may be null
551      * @return a Bundle result or null if the result is to be returned via the response. The result
552      * will contain either:
553      * <ul>
554      * <li> {@link AccountManager#KEY_INTENT}, or
555      * <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of
556      * the account that was added, or
557      * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
558      * indicate an error
559      * </ul>
560      * @throws NetworkErrorException if the authenticator could not honor the request due to a
561      * network error
562      */
addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options)563     public abstract Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
564             String authTokenType, String[] requiredFeatures, Bundle options)
565             throws NetworkErrorException;
566 
567     /**
568      * Checks that the user knows the credentials of an account.
569      * @param response to send the result back to the AccountManager, will never be null
570      * @param account the account whose credentials are to be checked, will never be null
571      * @param options a Bundle of authenticator-specific options, may be null
572      * @return a Bundle result or null if the result is to be returned via the response. The result
573      * will contain either:
574      * <ul>
575      * <li> {@link AccountManager#KEY_INTENT}, or
576      * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the check succeeded, false otherwise
577      * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
578      * indicate an error
579      * </ul>
580      * @throws NetworkErrorException if the authenticator could not honor the request due to a
581      * network error
582      */
confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)583     public abstract Bundle confirmCredentials(AccountAuthenticatorResponse response,
584             Account account, Bundle options)
585             throws NetworkErrorException;
586 
587     /**
588      * Gets an authtoken for an account.
589      *
590      * If not {@code null}, the resultant {@link Bundle} will contain different sets of keys
591      * depending on whether a token was successfully issued and, if not, whether one
592      * could be issued via some {@link android.app.Activity}.
593      * <p>
594      * If a token cannot be provided without some additional activity, the Bundle should contain
595      * {@link AccountManager#KEY_INTENT} with an associated {@link Intent}. On the other hand, if
596      * there is no such activity, then a Bundle containing
597      * {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} should be
598      * returned.
599      * <p>
600      * If a token can be successfully issued, the implementation should return the
601      * {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of the
602      * account associated with the token as well as the {@link AccountManager#KEY_AUTHTOKEN}. In
603      * addition {@link AbstractAccountAuthenticator} implementations that declare themselves
604      * {@code android:customTokens=true} may also provide a non-negative {@link
605      * #KEY_CUSTOM_TOKEN_EXPIRY} long value containing the expiration timestamp of the expiration
606      * time (in millis since the unix epoch).
607      * <p>
608      * Implementers should assume that tokens will be cached on the basis of account and
609      * authTokenType. The system may ignore the contents of the supplied options Bundle when
610      * determining to re-use a cached token. Furthermore, implementers should assume a supplied
611      * expiration time will be treated as non-binding advice.
612      * <p>
613      * Finally, note that for android:customTokens=false authenticators, tokens are cached
614      * indefinitely until some client calls {@link
615      * AccountManager#invalidateAuthToken(String,String)}.
616      *
617      * @param response to send the result back to the AccountManager, will never be null
618      * @param account the account whose credentials are to be retrieved, will never be null
619      * @param authTokenType the type of auth token to retrieve, will never be null
620      * @param options a Bundle of authenticator-specific options, may be null
621      * @return a Bundle result or null if the result is to be returned via the response.
622      * @throws NetworkErrorException if the authenticator could not honor the request due to a
623      * network error
624      */
getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)625     public abstract Bundle getAuthToken(AccountAuthenticatorResponse response,
626             Account account, String authTokenType, Bundle options)
627             throws NetworkErrorException;
628 
629     /**
630      * Ask the authenticator for a localized label for the given authTokenType.
631      * @param authTokenType the authTokenType whose label is to be returned, will never be null
632      * @return the localized label of the auth token type, may be null if the type isn't known
633      */
getAuthTokenLabel(String authTokenType)634     public abstract String getAuthTokenLabel(String authTokenType);
635 
636     /**
637      * Update the locally stored credentials for an account.
638      * @param response to send the result back to the AccountManager, will never be null
639      * @param account the account whose credentials are to be updated, will never be null
640      * @param authTokenType the type of auth token to retrieve after updating the credentials,
641      * may be null
642      * @param options a Bundle of authenticator-specific options, may be null
643      * @return a Bundle result or null if the result is to be returned via the response. The result
644      * will contain either:
645      * <ul>
646      * <li> {@link AccountManager#KEY_INTENT}, or
647      * <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of
648      * the account whose credentials were updated, or
649      * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
650      * indicate an error
651      * </ul>
652      * @throws NetworkErrorException if the authenticator could not honor the request due to a
653      * network error
654      */
updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)655     public abstract Bundle updateCredentials(AccountAuthenticatorResponse response,
656             Account account, String authTokenType, Bundle options) throws NetworkErrorException;
657 
658     /**
659      * Checks if the account supports all the specified authenticator specific features.
660      * @param response to send the result back to the AccountManager, will never be null
661      * @param account the account to check, will never be null
662      * @param features an array of features to check, will never be null
663      * @return a Bundle result or null if the result is to be returned via the response. The result
664      * will contain either:
665      * <ul>
666      * <li> {@link AccountManager#KEY_INTENT}, or
667      * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the account has all the features,
668      * false otherwise
669      * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
670      * indicate an error
671      * </ul>
672      * @throws NetworkErrorException if the authenticator could not honor the request due to a
673      * network error
674      */
hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features)675     public abstract Bundle hasFeatures(AccountAuthenticatorResponse response,
676             Account account, String[] features) throws NetworkErrorException;
677 
678     /**
679      * Checks if the removal of this account is allowed.
680      * @param response to send the result back to the AccountManager, will never be null
681      * @param account the account to check, will never be null
682      * @return a Bundle result or null if the result is to be returned via the response. The result
683      * will contain either:
684      * <ul>
685      * <li> {@link AccountManager#KEY_INTENT}, or
686      * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the removal of the account is
687      * allowed, false otherwise
688      * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
689      * indicate an error
690      * </ul>
691      * @throws NetworkErrorException if the authenticator could not honor the request due to a
692      * network error
693      */
getAccountRemovalAllowed(AccountAuthenticatorResponse response, Account account)694     public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response,
695             Account account) throws NetworkErrorException {
696         final Bundle result = new Bundle();
697         result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
698         return result;
699     }
700 
701     /**
702      * Returns a Bundle that contains whatever is required to clone the account on a different
703      * user. The Bundle is passed to the authenticator instance in the target user via
704      * {@link #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)}.
705      * The default implementation returns null, indicating that cloning is not supported.
706      * @param response to send the result back to the AccountManager, will never be null
707      * @param account the account to clone, will never be null
708      * @return a Bundle result or null if the result is to be returned via the response.
709      * @throws NetworkErrorException
710      * @see #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)
711      */
getAccountCredentialsForCloning(final AccountAuthenticatorResponse response, final Account account)712     public Bundle getAccountCredentialsForCloning(final AccountAuthenticatorResponse response,
713             final Account account) throws NetworkErrorException {
714         new Thread(new Runnable() {
715             @Override
716             public void run() {
717                 Bundle result = new Bundle();
718                 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
719                 response.onResult(result);
720             }
721         }).start();
722         return null;
723     }
724 
725     /**
726      * Creates an account based on credentials provided by the authenticator instance of another
727      * user on the device, who has chosen to share the account with this user.
728      * @param response to send the result back to the AccountManager, will never be null
729      * @param account the account to clone, will never be null
730      * @param accountCredentials the Bundle containing the required credentials to create the
731      * account. Contents of the Bundle are only meaningful to the authenticator. This Bundle is
732      * provided by {@link #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)}.
733      * @return a Bundle result or null if the result is to be returned via the response.
734      * @throws NetworkErrorException
735      * @see #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)
736      */
addAccountFromCredentials(final AccountAuthenticatorResponse response, Account account, Bundle accountCredentials)737     public Bundle addAccountFromCredentials(final AccountAuthenticatorResponse response,
738             Account account,
739             Bundle accountCredentials) throws NetworkErrorException {
740         new Thread(new Runnable() {
741             @Override
742             public void run() {
743                 Bundle result = new Bundle();
744                 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
745                 response.onResult(result);
746             }
747         }).start();
748         return null;
749     }
750 
751     /**
752      * Starts the add account session to authenticate user to an account of the
753      * specified accountType. No file I/O should be performed in this call.
754      * Account should be added to device only when {@link #finishSession} is
755      * called after this.
756      * <p>
757      * Note: when overriding this method, {@link #finishSession} should be
758      * overridden too.
759      * </p>
760      *
761      * @param response to send the result back to the AccountManager, will never
762      *            be null
763      * @param accountType the type of account to authenticate with, will never
764      *            be null
765      * @param authTokenType the type of auth token to retrieve after
766      *            authenticating with the account, may be null
767      * @param requiredFeatures a String array of authenticator-specific features
768      *            that the account authenticated with must support, may be null
769      * @param options a Bundle of authenticator-specific options, may be null
770      * @return a Bundle result or null if the result is to be returned via the
771      *         response. The result will contain either:
772      *         <ul>
773      *         <li>{@link AccountManager#KEY_INTENT}, or
774      *         <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for adding
775      *         the account to device later, and if account is authenticated,
776      *         optional {@link AccountManager#KEY_PASSWORD} and
777      *         {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking the
778      *         status of the account, or
779      *         <li>{@link AccountManager#KEY_ERROR_CODE} and
780      *         {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
781      *         </ul>
782      * @throws NetworkErrorException if the authenticator could not honor the
783      *             request due to a network error
784      * @see #finishSession(AccountAuthenticatorResponse, String, Bundle)
785      */
startAddAccountSession( final AccountAuthenticatorResponse response, final String accountType, final String authTokenType, final String[] requiredFeatures, final Bundle options)786     public Bundle startAddAccountSession(
787             final AccountAuthenticatorResponse response,
788             final String accountType,
789             final String authTokenType,
790             final String[] requiredFeatures,
791             final Bundle options)
792             throws NetworkErrorException {
793         new Thread(new Runnable() {
794             @Override
795             public void run() {
796                 Bundle sessionBundle = new Bundle();
797                 sessionBundle.putString(KEY_AUTH_TOKEN_TYPE, authTokenType);
798                 sessionBundle.putStringArray(KEY_REQUIRED_FEATURES, requiredFeatures);
799                 sessionBundle.putBundle(KEY_OPTIONS, options);
800                 Bundle result = new Bundle();
801                 result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
802                 response.onResult(result);
803             }
804 
805         }).start();
806         return null;
807     }
808 
809     /**
810      * Asks user to re-authenticate for an account but defers updating the
811      * locally stored credentials. No file I/O should be performed in this call.
812      * Local credentials should be updated only when {@link #finishSession} is
813      * called after this.
814      * <p>
815      * Note: when overriding this method, {@link #finishSession} should be
816      * overridden too.
817      * </p>
818      *
819      * @param response to send the result back to the AccountManager, will never
820      *            be null
821      * @param account the account whose credentials are to be updated, will
822      *            never be null
823      * @param authTokenType the type of auth token to retrieve after updating
824      *            the credentials, may be null
825      * @param options a Bundle of authenticator-specific options, may be null
826      * @return a Bundle result or null if the result is to be returned via the
827      *         response. The result will contain either:
828      *         <ul>
829      *         <li>{@link AccountManager#KEY_INTENT}, or
830      *         <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for
831      *         updating the locally stored credentials later, and if account is
832      *         re-authenticated, optional {@link AccountManager#KEY_PASSWORD}
833      *         and {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking
834      *         the status of the account later, or
835      *         <li>{@link AccountManager#KEY_ERROR_CODE} and
836      *         {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
837      *         </ul>
838      * @throws NetworkErrorException if the authenticator could not honor the
839      *             request due to a network error
840      * @see #finishSession(AccountAuthenticatorResponse, String, Bundle)
841      */
startUpdateCredentialsSession( final AccountAuthenticatorResponse response, final Account account, final String authTokenType, final Bundle options)842     public Bundle startUpdateCredentialsSession(
843             final AccountAuthenticatorResponse response,
844             final Account account,
845             final String authTokenType,
846             final Bundle options) throws NetworkErrorException {
847         new Thread(new Runnable() {
848             @Override
849             public void run() {
850                 Bundle sessionBundle = new Bundle();
851                 sessionBundle.putString(KEY_AUTH_TOKEN_TYPE, authTokenType);
852                 sessionBundle.putParcelable(KEY_ACCOUNT, account);
853                 sessionBundle.putBundle(KEY_OPTIONS, options);
854                 Bundle result = new Bundle();
855                 result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
856                 response.onResult(result);
857             }
858 
859         }).start();
860         return null;
861     }
862 
863     /**
864      * Finishes the session started by #startAddAccountSession or
865      * #startUpdateCredentials by installing the account to device with
866      * AccountManager, or updating the local credentials. File I/O may be
867      * performed in this call.
868      * <p>
869      * Note: when overriding this method, {@link #startAddAccountSession} and
870      * {@link #startUpdateCredentialsSession} should be overridden too.
871      * </p>
872      *
873      * @param response to send the result back to the AccountManager, will never
874      *            be null
875      * @param accountType the type of account to authenticate with, will never
876      *            be null
877      * @param sessionBundle a bundle of session data created by
878      *            {@link #startAddAccountSession} used for adding account to
879      *            device, or by {@link #startUpdateCredentialsSession} used for
880      *            updating local credentials.
881      * @return a Bundle result or null if the result is to be returned via the
882      *         response. The result will contain either:
883      *         <ul>
884      *         <li>{@link AccountManager#KEY_INTENT}, or
885      *         <li>{@link AccountManager#KEY_ACCOUNT_NAME} and
886      *         {@link AccountManager#KEY_ACCOUNT_TYPE} of the account that was
887      *         added or local credentials were updated, and optional
888      *         {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking
889      *         the status of the account later, or
890      *         <li>{@link AccountManager#KEY_ERROR_CODE} and
891      *         {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
892      *         </ul>
893      * @throws NetworkErrorException if the authenticator could not honor the request due to a
894      *             network error
895      * @see #startAddAccountSession and #startUpdateCredentialsSession
896      */
finishSession( final AccountAuthenticatorResponse response, final String accountType, final Bundle sessionBundle)897     public Bundle finishSession(
898             final AccountAuthenticatorResponse response,
899             final String accountType,
900             final Bundle sessionBundle) throws NetworkErrorException {
901         if (TextUtils.isEmpty(accountType)) {
902             Log.e(TAG, "Account type cannot be empty.");
903             Bundle result = new Bundle();
904             result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_ARGUMENTS);
905             result.putString(AccountManager.KEY_ERROR_MESSAGE,
906                     "accountType cannot be empty.");
907             return result;
908         }
909 
910         if (sessionBundle == null) {
911             Log.e(TAG, "Session bundle cannot be null.");
912             Bundle result = new Bundle();
913             result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_ARGUMENTS);
914             result.putString(AccountManager.KEY_ERROR_MESSAGE,
915                     "sessionBundle cannot be null.");
916             return result;
917         }
918 
919         if (!sessionBundle.containsKey(KEY_AUTH_TOKEN_TYPE)) {
920             // We cannot handle Session bundle not created by default startAddAccountSession(...)
921             // nor startUpdateCredentialsSession(...) implementation. Return error.
922             Bundle result = new Bundle();
923             result.putInt(AccountManager.KEY_ERROR_CODE,
924                     AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION);
925             result.putString(AccountManager.KEY_ERROR_MESSAGE,
926                     "Authenticator must override finishSession if startAddAccountSession"
927                             + " or startUpdateCredentialsSession is overridden.");
928             response.onResult(result);
929             return result;
930         }
931         String authTokenType = sessionBundle.getString(KEY_AUTH_TOKEN_TYPE);
932         Bundle options = sessionBundle.getBundle(KEY_OPTIONS);
933         String[] requiredFeatures = sessionBundle.getStringArray(KEY_REQUIRED_FEATURES);
934         Account account = sessionBundle.getParcelable(KEY_ACCOUNT);
935         boolean containsKeyAccount = sessionBundle.containsKey(KEY_ACCOUNT);
936 
937         // Actual options passed to add account or update credentials flow.
938         Bundle sessionOptions = new Bundle(sessionBundle);
939         // Remove redundant extras in session bundle before passing it to addAccount(...) or
940         // updateCredentials(...).
941         sessionOptions.remove(KEY_AUTH_TOKEN_TYPE);
942         sessionOptions.remove(KEY_REQUIRED_FEATURES);
943         sessionOptions.remove(KEY_OPTIONS);
944         sessionOptions.remove(KEY_ACCOUNT);
945 
946         if (options != null) {
947             // options may contains old system info such as
948             // AccountManager.KEY_ANDROID_PACKAGE_NAME required by the add account flow or update
949             // credentials flow, we should replace with the new values of the current call added
950             // to sessionBundle by AccountManager or AccountManagerService.
951             options.putAll(sessionOptions);
952             sessionOptions = options;
953         }
954 
955         // Session bundle created by startUpdateCredentialsSession default implementation should
956         // contain KEY_ACCOUNT.
957         if (containsKeyAccount) {
958             return updateCredentials(response, account, authTokenType, options);
959         }
960         // Otherwise, session bundle was created by startAddAccountSession default implementation.
961         return addAccount(response, accountType, authTokenType, requiredFeatures, sessionOptions);
962     }
963 
964     /**
965      * Checks if update of the account credentials is suggested.
966      *
967      * @param response to send the result back to the AccountManager, will never be null.
968      * @param account the account to check, will never be null
969      * @param statusToken a String of token to check if update of credentials is suggested.
970      * @return a Bundle result or null if the result is to be returned via the response. The result
971      *         will contain either:
972      *         <ul>
973      *         <li>{@link AccountManager#KEY_BOOLEAN_RESULT}, true if update of account's
974      *         credentials is suggested, false otherwise
975      *         <li>{@link AccountManager#KEY_ERROR_CODE} and
976      *         {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
977      *         </ul>
978      * @throws NetworkErrorException if the authenticator could not honor the request due to a
979      *             network error
980      */
isCredentialsUpdateSuggested( final AccountAuthenticatorResponse response, Account account, String statusToken)981     public Bundle isCredentialsUpdateSuggested(
982             final AccountAuthenticatorResponse response,
983             Account account,
984             String statusToken) throws NetworkErrorException {
985         Bundle result = new Bundle();
986         result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
987         return result;
988     }
989 }
990