Merge "AccountManager: add finishSession(...) API."
diff --git a/api/current.txt b/api/current.txt
index 1430185..c017754 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2728,6 +2728,7 @@
     method public android.os.Bundle addAccountFromCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, android.os.Bundle) throws android.accounts.NetworkErrorException;
     method public abstract android.os.Bundle confirmCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, android.os.Bundle) throws android.accounts.NetworkErrorException;
     method public abstract android.os.Bundle editProperties(android.accounts.AccountAuthenticatorResponse, java.lang.String);
+    method public android.os.Bundle finishSession(android.accounts.AccountAuthenticatorResponse, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
     method public android.os.Bundle getAccountCredentialsForCloning(android.accounts.AccountAuthenticatorResponse, android.accounts.Account) throws android.accounts.NetworkErrorException;
     method public android.os.Bundle getAccountRemovalAllowed(android.accounts.AccountAuthenticatorResponse, android.accounts.Account) throws android.accounts.NetworkErrorException;
     method public abstract android.os.Bundle getAuthToken(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
@@ -2773,6 +2774,7 @@
     method public void clearPassword(android.accounts.Account);
     method public android.accounts.AccountManagerFuture<android.os.Bundle> confirmCredentials(android.accounts.Account, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
     method public android.accounts.AccountManagerFuture<android.os.Bundle> editProperties(java.lang.String, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
+    method public android.accounts.AccountManagerFuture<android.os.Bundle> finishSession(android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
     method public static android.accounts.AccountManager get(android.content.Context);
     method public android.accounts.Account[] getAccounts();
     method public android.accounts.Account[] getAccountsByType(java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index 1c87414..3dfb316 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2829,6 +2829,7 @@
     method public android.os.Bundle addAccountFromCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, android.os.Bundle) throws android.accounts.NetworkErrorException;
     method public abstract android.os.Bundle confirmCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, android.os.Bundle) throws android.accounts.NetworkErrorException;
     method public abstract android.os.Bundle editProperties(android.accounts.AccountAuthenticatorResponse, java.lang.String);
+    method public android.os.Bundle finishSession(android.accounts.AccountAuthenticatorResponse, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
     method public android.os.Bundle getAccountCredentialsForCloning(android.accounts.AccountAuthenticatorResponse, android.accounts.Account) throws android.accounts.NetworkErrorException;
     method public android.os.Bundle getAccountRemovalAllowed(android.accounts.AccountAuthenticatorResponse, android.accounts.Account) throws android.accounts.NetworkErrorException;
     method public abstract android.os.Bundle getAuthToken(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
@@ -2874,6 +2875,7 @@
     method public void clearPassword(android.accounts.Account);
     method public android.accounts.AccountManagerFuture<android.os.Bundle> confirmCredentials(android.accounts.Account, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
     method public android.accounts.AccountManagerFuture<android.os.Bundle> editProperties(java.lang.String, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
+    method public android.accounts.AccountManagerFuture<android.os.Bundle> finishSession(android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
     method public static android.accounts.AccountManager get(android.content.Context);
     method public android.accounts.Account[] getAccounts();
     method public android.accounts.Account[] getAccountsByType(java.lang.String);
diff --git a/api/test-current.txt b/api/test-current.txt
index cd6ca86..d05dba2 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2728,6 +2728,7 @@
     method public android.os.Bundle addAccountFromCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, android.os.Bundle) throws android.accounts.NetworkErrorException;
     method public abstract android.os.Bundle confirmCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, android.os.Bundle) throws android.accounts.NetworkErrorException;
     method public abstract android.os.Bundle editProperties(android.accounts.AccountAuthenticatorResponse, java.lang.String);
+    method public android.os.Bundle finishSession(android.accounts.AccountAuthenticatorResponse, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
     method public android.os.Bundle getAccountCredentialsForCloning(android.accounts.AccountAuthenticatorResponse, android.accounts.Account) throws android.accounts.NetworkErrorException;
     method public android.os.Bundle getAccountRemovalAllowed(android.accounts.AccountAuthenticatorResponse, android.accounts.Account) throws android.accounts.NetworkErrorException;
     method public abstract android.os.Bundle getAuthToken(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
@@ -2773,6 +2774,7 @@
     method public void clearPassword(android.accounts.Account);
     method public android.accounts.AccountManagerFuture<android.os.Bundle> confirmCredentials(android.accounts.Account, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
     method public android.accounts.AccountManagerFuture<android.os.Bundle> editProperties(java.lang.String, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
+    method public android.accounts.AccountManagerFuture<android.os.Bundle> finishSession(android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
     method public static android.accounts.AccountManager get(android.content.Context);
     method public android.accounts.Account[] getAccounts();
     method public android.accounts.Account[] getAccountsByType(java.lang.String);
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index 041f591..a312e3f 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -18,6 +18,7 @@
 
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.text.TextUtils;
 import android.os.Binder;
 import android.os.IBinder;
 import android.content.pm.PackageManager;
@@ -121,24 +122,28 @@
      * This is used in the default implementation of
      * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}.
      */
-    private static final String KEY_AUTH_TOKEN_TYPE = "android.accounts.KEY_AUTH_TOKEN_TYPE";
+    private static final String KEY_AUTH_TOKEN_TYPE =
+            "android.accounts.AbstractAccountAuthenticato.KEY_AUTH_TOKEN_TYPE";
     /**
      * Bundle key used for the {@link String} array of required features in
      * session bundle. This is used in the default implementation of
      * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}.
      */
-    private static final String KEY_REQUIRED_FEATURES = "android.accounts.AbstractAccountAuthenticator.KEY_REQUIRED_FEATURES";
+    private static final String KEY_REQUIRED_FEATURES =
+            "android.accounts.AbstractAccountAuthenticator.KEY_REQUIRED_FEATURES";
     /**
      * Bundle key used for the {@link Bundle} options in session bundle. This is
      * used in default implementation of {@link #startAddAccountSession} and
      * {@link startUpdateCredentialsSession}.
      */
-    private static final String KEY_OPTIONS = "android.accounts.AbstractAccountAuthenticator.KEY_OPTIONS";
+    private static final String KEY_OPTIONS =
+            "android.accounts.AbstractAccountAuthenticator.KEY_OPTIONS";
     /**
      * Bundle key used for the {@link Account} account in session bundle. This is used
      * used in default implementation of {@link startUpdateCredentialsSession}.
      */
-    private static final String KEY_ACCOUNT = "android.accounts.AbstractAccountAuthenticator.KEY_ACCOUNT";
+    private static final String KEY_ACCOUNT =
+            "android.accounts.AbstractAccountAuthenticator.KEY_ACCOUNT";
 
     private final Context mContext;
 
@@ -418,6 +423,7 @@
                     }
                     Log.v(TAG, "startUpdateCredentialsSession: result "
                             + AccountManager.sanitizeResult(result));
+
                 }
                 if (result != null) {
                     response.onResult(result);
@@ -425,6 +431,33 @@
             } catch (Exception e) {
                 handleException(response, "startUpdateCredentialsSession",
                         account.toString() + "," + authTokenType, e);
+
+            }
+        }
+
+        public void finishSession(
+                IAccountAuthenticatorResponse response,
+                String accountType,
+                Bundle sessionBundle) throws RemoteException {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "finishSession: accountType " + accountType);
+            }
+            checkBinderPermission();
+            try {
+                final Bundle result = AbstractAccountAuthenticator.this.finishSession(
+                        new AccountAuthenticatorResponse(response), accountType, sessionBundle);
+                if (result != null) {
+                    result.keySet(); // force it to be unparcelled
+                }
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "finishSession: result " + AccountManager.sanitizeResult(result));
+                }
+                if (result != null) {
+                    response.onResult(result);
+                }
+            } catch (Exception e) {
+                handleException(response, "finishSession", accountType, e);
+
             }
         }
     }
@@ -697,7 +730,13 @@
 
     /**
      * Starts the add account session to authenticate user to an account of the
-     * specified accountType.
+     * specified accountType. No file I/O should be performed in this call.
+     * Account should be added to device only when {@link #finishSession} is
+     * called after this.
+     * <p>
+     * Note: when overriding this method, {@link #finishSession} should be
+     * overridden too.
+     * </p>
      *
      * @param response to send the result back to the AccountManager, will never
      *            be null
@@ -722,9 +761,13 @@
      *         </ul>
      * @throws NetworkErrorException if the authenticator could not honor the
      *             request due to a network error
+     * @see #finishSession(AccountAuthenticatorResponse, String, Bundle)
      */
-    public Bundle startAddAccountSession(final AccountAuthenticatorResponse response,
-            final String accountType, final String authTokenType, final String[] requiredFeatures,
+    public Bundle startAddAccountSession(
+            final AccountAuthenticatorResponse response,
+            final String accountType,
+            final String authTokenType,
+            final String[] requiredFeatures,
             final Bundle options)
             throws NetworkErrorException {
         new Thread(new Runnable() {
@@ -744,34 +787,43 @@
     }
 
     /**
-     * Asks user to re-authenticate for an account but defers updating the locally stored
-     * credentials.
+     * Asks user to re-authenticate for an account but defers updating the
+     * locally stored credentials. No file I/O should be performed in this call.
+     * Local credentials should be updated only when {@link #finishSession} is
+     * called after this.
+     * <p>
+     * Note: when overriding this method, {@link #finishSession} should be
+     * overridden too.
+     * </p>
      *
      * @param response to send the result back to the AccountManager, will never
      *            be null
      * @param account the account whose credentials are to be updated, will
      *            never be null
      * @param authTokenType the type of auth token to retrieve after updating
-     *            the credentials, may be null (TODO)
+     *            the credentials, may be null
      * @param options a Bundle of authenticator-specific options, may be null
      * @return a Bundle result or null if the result is to be returned via the
      *         response. The result will contain either:
      *         <ul>
      *         <li>{@link AccountManager#KEY_INTENT}, or
-     *         <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for updating the
-     *         locally stored credentials later, and if account is
-     *         re-authenticated, {@link AccountManager#KEY_PASSWORD} and
-     *         {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking the
-     *         status of the account later, or
+     *         <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for
+     *         updating the locally stored credentials later, and if account is
+     *         re-authenticated, optional {@link AccountManager#KEY_PASSWORD}
+     *         and {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking
+     *         the status of the account later, or
      *         <li>{@link AccountManager#KEY_ERROR_CODE} and
      *         {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
      *         </ul>
      * @throws NetworkErrorException if the authenticator could not honor the
      *             request due to a network error
+     * @see #finishSession(AccountAuthenticatorResponse, String, Bundle)
      */
-    public Bundle startUpdateCredentialsSession(final AccountAuthenticatorResponse response,
-            final Account account, final String authTokenType, final Bundle options)
-                    throws NetworkErrorException {
+    public Bundle startUpdateCredentialsSession(
+            final AccountAuthenticatorResponse response,
+            final Account account,
+            final String authTokenType,
+            final Bundle options) throws NetworkErrorException {
         new Thread(new Runnable() {
             @Override
             public void run() {
@@ -787,4 +839,102 @@
         }).start();
         return null;
     }
+
+    /**
+     * Finishes the session started by #startAddAccountSession or
+     * #startUpdateCredentials by installing the account to device with
+     * AccountManager, or updating the local credentials. File I/O may be
+     * performed in this call.
+     * <p>
+     * Note: when overriding this method, {@link #startAddAccountSession} and
+     * {@link #startUpdateCredentialsSession} should be overridden too.
+     * </p>
+     *
+     * @param response to send the result back to the AccountManager, will never
+     *            be null
+     * @param accountType the type of account to authenticate with, will never
+     *            be null
+     * @param sessionBundle a bundle of session data created by
+     *            {@link #startAddAccountSession} used for adding account to
+     *            device, or by {@link #startUpdateCredentialsSession} used for
+     *            updating local credentials.
+     * @return a Bundle result or null if the result is to be returned via the
+     *         response. The result will contain either:
+     *         <ul>
+     *         <li>{@link AccountManager#KEY_INTENT}, or
+     *         <li>{@link AccountManager#KEY_ACCOUNT_NAME} and
+     *         {@link AccountManager#KEY_ACCOUNT_TYPE} of the account that was
+     *         added or local credentials were updated, or
+     *         <li>{@link AccountManager#KEY_ERROR_CODE} and
+     *         {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
+     *         </ul>
+     * @throws NetworkErrorException
+     * @see #startAddAccountSession and #startUpdateCredentialsSession
+     */
+    public Bundle finishSession(
+            final AccountAuthenticatorResponse response,
+            final String accountType,
+            final Bundle sessionBundle) throws NetworkErrorException {
+        if (TextUtils.isEmpty(accountType)) {
+            Log.e(TAG, "Account type cannot be empty.");
+            Bundle result = new Bundle();
+            result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_ARGUMENTS);
+            result.putString(AccountManager.KEY_ERROR_MESSAGE,
+                    "accountType cannot be empty.");
+            return result;
+        }
+
+        if (sessionBundle == null) {
+            Log.e(TAG, "Session bundle cannot be null.");
+            Bundle result = new Bundle();
+            result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_ARGUMENTS);
+            result.putString(AccountManager.KEY_ERROR_MESSAGE,
+                    "sessionBundle cannot be null.");
+            return result;
+        }
+
+        if (!sessionBundle.containsKey(KEY_AUTH_TOKEN_TYPE)) {
+            // We cannot handle Session bundle not created by default startAddAccountSession(...)
+            // nor startUpdateCredentialsSession(...) implementation. Return error.
+            Bundle result = new Bundle();
+            result.putInt(AccountManager.KEY_ERROR_CODE,
+                    AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION);
+            result.putString(AccountManager.KEY_ERROR_MESSAGE,
+                    "Authenticator must override finishSession if startAddAccountSession"
+                            + " or startUpdateCredentialsSession is overridden.");
+            response.onResult(result);
+            return result;
+        }
+        String authTokenType = sessionBundle.getString(KEY_AUTH_TOKEN_TYPE);
+        Bundle options = sessionBundle.getBundle(KEY_OPTIONS);
+        String[] requiredFeatures = sessionBundle.getStringArray(KEY_REQUIRED_FEATURES);
+        Account account = sessionBundle.getParcelable(KEY_ACCOUNT);
+        boolean containsKeyAccount = sessionBundle.containsKey(KEY_ACCOUNT);
+
+        // Actual options passed to add account or update credentials flow.
+        Bundle sessionOptions = new Bundle(sessionBundle);
+        // Remove redundant extras in session bundle before passing it to addAccount(...) or
+        // updateCredentials(...).
+        sessionOptions.remove(KEY_AUTH_TOKEN_TYPE);
+        sessionOptions.remove(KEY_REQUIRED_FEATURES);
+        sessionOptions.remove(KEY_OPTIONS);
+        sessionOptions.remove(KEY_ACCOUNT);
+
+        if (options != null) {
+            // options may contains old system info such as
+            // AccountManager.KEY_ANDROID_PACKAGE_NAME required by the add account flow or update
+            // credentials flow, we should replace with the new values of the current call added
+            // to sessionBundle by AccountManager or AccountManagerService.
+            options.putAll(sessionOptions);
+            sessionOptions = options;
+        }
+
+        // Session bundle created by startUpdateCredentialsSession default implementation should
+        // contain KEY_ACCOUNT.
+        if (containsKeyAccount) {
+            return updateCredentials(response, account, authTokenType, options);
+        }
+        // Otherwise, session bundle was created by startAddAccountSession default implementation.
+        return addAccount(response, accountType, authTokenType, requiredFeatures, sessionOptions);
+    }
 }
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 5557905..ada1ac2 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -2617,7 +2617,8 @@
      * <p>
      * <p>
      * <b>NOTE:</b> The account will not be installed to the device by calling
-     * this api alone.
+     * this api alone. #finishSession should be called after this to install the
+     * account on device.
      *
      * @param accountType The type of account to add; must not be null
      * @param authTokenType The type of auth token (see {@link #getAuthToken})
@@ -2665,6 +2666,7 @@
      *         problem creating a new account, usually because of network
      *         trouble
      *         </ul>
+     * @see #finishSession
      */
     public AccountManagerFuture<Bundle> startAddAccountSession(
             final String accountType,
@@ -2697,14 +2699,14 @@
 
     /**
      * Asks the user to enter a new password for an account but not updating the
-     * saved credentials for the account until finishSession is
-     * called.
+     * saved credentials for the account until {@link #finishSession} is called.
      * <p>
      * This method may be called from any thread, but the returned
      * {@link AccountManagerFuture} must not be used on the main thread.
      * <p>
      * <b>NOTE:</b> The saved credentials for the account alone will not be
-     * updated by calling this API alone .
+     * updated by calling this API alone. #finishSession should be called after
+     * this to update local credentials
      *
      * @param account The account to update credentials for
      * @param authTokenType The credentials entered must allow an auth token of
@@ -2723,15 +2725,14 @@
      *            the main thread
      * @return An {@link AccountManagerFuture} which resolves to a Bundle with
      *         these fields if an activity was supplied and user was
-     *         successfully re-authenticated to the account (TODO: default impl
-     *         only returns escorw?):
+     *         successfully re-authenticated to the account:
      *         <ul>
      *         <li>{@link #KEY_ACCOUNT_SESSION_BUNDLE} - encrypted Bundle for
      *         updating the local credentials on device later.
-     *         <li>{@link #KEY_PASSWORD} - optional, the password or password hash of the
-     *         account
-     *         <li>{@link #KEY_ACCOUNT_STATUS_TOKEN} - optional, token to check status of
-     *         the account
+     *         <li>{@link #KEY_PASSWORD} - optional, the password or password
+     *         hash of the account
+     *         <li>{@link #KEY_ACCOUNT_STATUS_TOKEN} - optional, token to check
+     *         status of the account
      *         </ul>
      *         If no activity was specified, the returned Bundle contains
      *         {@link #KEY_INTENT} with the {@link Intent} needed to launch the
@@ -2747,6 +2748,7 @@
      *         problem verifying the password, usually because of network
      *         trouble
      *         </ul>
+     * @see #finishSession
      */
     public AccountManagerFuture<Bundle> startUpdateCredentialsSession(
             final Account account,
@@ -2770,4 +2772,71 @@
             }
         }.start();
     }
+
+    /**
+     * Finishes the session started by {@link #startAddAccountSession} or
+     * {@link #startUpdateCredentialsSession}. This will either add the account
+     * to AccountManager or update the local credentials stored.
+     * <p>
+     * This method may be called from any thread, but the returned
+     * {@link AccountManagerFuture} must not be used on the main thread.
+     *
+     * @param sessionBundle a {@link Bundle} created by {@link #startAddAccountSession} or
+     *            {@link #startUpdateCredentialsSession}
+     * @param activity The {@link Activity} context to use for launching a new
+     *            authenticator-defined sub-Activity to prompt the user to
+     *            create an account or reauthenticate existing account; used
+     *            only to call startActivity(); if null, the prompt will not
+     *            be launched directly, but the necessary {@link Intent} will
+     *            be returned to the caller instead
+     * @param callback Callback to invoke when the request completes, null for
+     *            no callback
+     * @param handler {@link Handler} identifying the callback thread, null for
+     *            the main thread
+     * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+     *         these fields if an activity was supplied and an account was added
+     *         to device or local credentials were updated::
+     *         <ul>
+     *         <li>{@link #KEY_ACCOUNT_NAME} - the name of the account created
+     *         <li>{@link #KEY_ACCOUNT_TYPE} - the type of the account
+     *         </ul>
+     *         If no activity was specified and additional information is needed
+     *         from user, the returned Bundle may contains only
+     *         {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+     *         actual account creation process. If an error occurred,
+     *         {@link AccountManagerFuture#getResult()} throws:
+     *         <ul>
+     *         <li>{@link AuthenticatorException} if no authenticator was
+     *         registered for this account type or the authenticator failed to
+     *         respond
+     *         <li>{@link OperationCanceledException} if the operation was
+     *         canceled for any reason, including the user canceling the
+     *         creation process or adding accounts (of this type) has been
+     *         disabled by policy
+     *         <li>{@link IOException} if the authenticator experienced an I/O
+     *         problem creating a new account, usually because of network
+     *         trouble
+     *         </ul>
+     * @see #startAddAccountSession and #startUpdateCredentialsSession
+     */
+    public AccountManagerFuture<Bundle> finishSession(
+            final Bundle sessionBundle,
+            final Activity activity,
+            AccountManagerCallback<Bundle> callback,
+            Handler handler) {
+        if (sessionBundle == null) {
+            throw new IllegalArgumentException("sessionBundle is null");
+        }
+
+        /* Add information required by add account flow */
+        final Bundle appInfo = new Bundle();
+        appInfo.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+        return new AmsTask(activity, handler, callback) {
+            @Override
+            public void doWork() throws RemoteException {
+                mService.finishSession(mResponse, sessionBundle, activity != null, appInfo);
+            }
+        }.start();
+    }
 }
diff --git a/core/java/android/accounts/IAccountAuthenticator.aidl b/core/java/android/accounts/IAccountAuthenticator.aidl
index 921fb19..6bda800 100644
--- a/core/java/android/accounts/IAccountAuthenticator.aidl
+++ b/core/java/android/accounts/IAccountAuthenticator.aidl
@@ -96,4 +96,12 @@
      */
     void startUpdateCredentialsSession(in IAccountAuthenticatorResponse response, in Account account,
         String authTokenType, in Bundle options);
+
+    /**
+     * Finishes the session started by startAddAccountSession(...) or
+     * startUpdateCredentialsSession(...) by adding account to or updating local credentials
+     * in the IAccountManager.
+     */
+    void finishSession(in IAccountAuthenticatorResponse response, String accountType,
+        in Bundle sessionBundle);
 }
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 8489e47..4af9f33 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -91,4 +91,8 @@
     /* Update credentials in two steps. */
     void startUpdateCredentialsSession(in IAccountManagerResponse response, in Account account,
         String authTokenType, boolean expectActivityLaunch, in Bundle options);
+
+    /* Finish session started by startAddAccountSession(...) or startUpdateCredentialsSession(...) */
+    void finishSession(in IAccountManagerResponse response, in Bundle sessionBundle,
+        boolean expectActivityLaunch, in Bundle appInfo);
 }
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 91702cf..502d61a 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2475,6 +2475,129 @@
         }
     }
 
+    @Override
+    public void finishSession(IAccountManagerResponse response,
+            @NonNull Bundle sessionBundle,
+            boolean expectActivityLaunch,
+            Bundle appInfo) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG,
+                    "finishSession: response "+ response
+                            + ", expectActivityLaunch " + expectActivityLaunch
+                            + ", caller's uid " + Binder.getCallingUid()
+                            + ", pid " + Binder.getCallingPid());
+        }
+        if (response == null) {
+            throw new IllegalArgumentException("response is null");
+        }
+
+        // Session bundle is the encrypted bundle of the original bundle created by authenticator.
+        // Account type is added to it before encryption.
+        if (sessionBundle == null || sessionBundle.size() == 0) {
+            throw new IllegalArgumentException("sessionBundle is empty");
+        }
+
+        int userId = Binder.getCallingUserHandle().getIdentifier();
+        if (!canUserModifyAccounts(userId)) {
+            sendErrorResponse(response,
+                    AccountManager.ERROR_CODE_USER_RESTRICTED,
+                    "User is not allowed to add an account!");
+            showCantAddAccount(AccountManager.ERROR_CODE_USER_RESTRICTED, userId);
+            return;
+        }
+
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        final Bundle decryptedBundle;
+        final String accountType;
+        // First decrypt session bundle to get account type for checking permission.
+        try {
+            CryptoHelper cryptoHelper = CryptoHelper.getInstance();
+            decryptedBundle = cryptoHelper.decryptBundle(sessionBundle);
+            if (decryptedBundle == null) {
+                sendErrorResponse(
+                        response,
+                        AccountManager.ERROR_CODE_BAD_REQUEST,
+                        "failed to decrypt session bundle");
+                return;
+            }
+            accountType = decryptedBundle.getString(AccountManager.KEY_ACCOUNT_TYPE);
+            // Account type cannot be null. This should not happen if session bundle was created
+            // properly by #StartAccountSession.
+            if (TextUtils.isEmpty(accountType)) {
+                sendErrorResponse(
+                        response,
+                        AccountManager.ERROR_CODE_BAD_ARGUMENTS,
+                        "accountType is empty");
+                return;
+            }
+
+            // If by any chances, decryptedBundle contains colliding keys with
+            // system info
+            // such as AccountManager.KEY_ANDROID_PACKAGE_NAME required by the add account flow or
+            // update credentials flow, we should replace with the new values of the current call.
+            if (appInfo != null) {
+                decryptedBundle.putAll(appInfo);
+            }
+
+            // Add info that may be used by add account or update credentials flow.
+            decryptedBundle.putInt(AccountManager.KEY_CALLER_UID, uid);
+            decryptedBundle.putInt(AccountManager.KEY_CALLER_PID, pid);
+        } catch (GeneralSecurityException e) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.v(TAG, "Failed to decrypt session bundle!", e);
+            }
+            sendErrorResponse(
+                    response,
+                    AccountManager.ERROR_CODE_BAD_REQUEST,
+                    "failed to decrypt session bundle");
+            return;
+        }
+
+        if (!canUserModifyAccountsForType(userId, accountType)) {
+            sendErrorResponse(
+                    response,
+                    AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE,
+                    "User cannot modify accounts of this type (policy).");
+            showCantAddAccount(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE,
+                    userId);
+            return;
+        }
+
+        long identityToken = clearCallingIdentity();
+        try {
+            UserAccounts accounts = getUserAccounts(userId);
+            logRecordWithUid(
+                    accounts,
+                    DebugDbHelper.ACTION_CALLED_ACCOUNT_SESSION_FINISH,
+                    TABLE_ACCOUNTS,
+                    uid);
+            new Session(
+                    accounts,
+                    response,
+                    accountType,
+                    expectActivityLaunch,
+                    true /* stripAuthTokenFromResult */,
+                    null /* accountName */,
+                    false /* authDetailsRequired */,
+                    true /* updateLastAuthenticationTime */) {
+                @Override
+                public void run() throws RemoteException {
+                    mAuthenticator.finishSession(this, mAccountType, decryptedBundle);
+                }
+
+                @Override
+                protected String toDebugString(long now) {
+                    return super.toDebugString(now)
+                            + ", finishSession"
+                            + ", accountType " + accountType;
+                }
+            }.bind();
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
     private void showCantAddAccount(int errorCode, int userId) {
         Intent cantAddAccount = new Intent(mContext, CantAddAccountActivity.class);
         cantAddAccount.putExtra(CantAddAccountActivity.EXTRA_ERROR_CODE, errorCode);
@@ -3589,10 +3712,11 @@
         private static String ACTION_CALLED_ACCOUNT_ADD = "action_called_account_add";
         private static String ACTION_CALLED_ACCOUNT_REMOVE = "action_called_account_remove";
 
-        // TODO: This action doesn't add account to accountdb. Account is only
-        // added in finishAddAccount or finishAddAccountAsUser which may be in
-        // a different user profile.
+        //This action doesn't add account to accountdb. Account is only
+        // added in finishSession which may be in a different user profile.
         private static String ACTION_CALLED_START_ACCOUNT_ADD = "action_called_start_account_add";
+        private static String ACTION_CALLED_ACCOUNT_SESSION_FINISH =
+                "action_called_account_session_finish";
 
         private static SimpleDateFormat dateFromat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");