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