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