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