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