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