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