New AccountManager method to copy accounts between users.

Adding the copyAccountToUser method which copies an account
along with its credentials to a different user.

Also an extra in the public api to identify the account to migrate
during provisioning.

Bug: 17716971
Change-Id: I2f29f1765ba0d360a3894b13ef86253b7c7d3284
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 64c2fd0..6957435c 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -1443,6 +1443,40 @@
     }
 
     /**
+     * Copies an account from the primary user to another user.
+     * @param account the account to copy
+     * @param user the target user
+     * @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 Boolean indicated wether it
+     * succeeded.
+     * @hide
+     */
+    public AccountManagerFuture<Boolean> copyAccountToUser(
+            final Account account, final UserHandle user,
+            AccountManagerCallback<Boolean> callback, Handler handler) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        if (user == null) throw new IllegalArgumentException("user is null");
+
+        return new Future2Task<Boolean>(handler, callback) {
+            @Override
+            public void doWork() throws RemoteException {
+                mService.copyAccountToUser(
+                        mResponse, account, UserHandle.USER_OWNER, user.getIdentifier());
+            }
+            @Override
+            public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
+                if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
+                    throw new AuthenticatorException("no result in response");
+                }
+                return bundle.getBoolean(KEY_BOOLEAN_RESULT);
+            }
+        }.start();
+    }
+
+    /**
      * @hide
      * Removes the shared account.
      * @param account the account to remove
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index bc75b9b..aa41161 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -42,6 +42,8 @@
     void removeAccountAsUser(in IAccountManagerResponse response, in Account account,
         boolean expectActivityLaunch, int userId);
     boolean removeAccountExplicitly(in Account account);
+    void copyAccountToUser(in IAccountManagerResponse response, in Account account,
+        int userFrom, int userTo);
     void invalidateAuthToken(String accountType, String authToken);
     String peekAuthToken(in Account account, String authTokenType);
     void setAuthToken(in Account account, String authTokenType, String authToken);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 85eed859..a2f4d56 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -486,7 +486,7 @@
         for (Account sa : sharedAccounts) {
             if (ArrayUtils.contains(accounts, sa)) continue;
             // Account doesn't exist. Copy it now.
-            copyAccountToUser(sa, UserHandle.USER_OWNER, userId);
+            copyAccountToUser(null /*no response*/, sa, UserHandle.USER_OWNER, userId);
         }
     }
 
@@ -672,16 +672,31 @@
         }
     }
 
-    private boolean copyAccountToUser(final Account account, int userFrom, int userTo) {
+    @Override
+    public void copyAccountToUser(final IAccountManagerResponse response, final Account account,
+            int userFrom, int userTo) {
+        enforceCrossUserPermission(UserHandle.USER_ALL, "Calling copyAccountToUser requires "
+                    + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
         final UserAccounts fromAccounts = getUserAccounts(userFrom);
         final UserAccounts toAccounts = getUserAccounts(userTo);
         if (fromAccounts == null || toAccounts == null) {
-            return false;
+            if (response != null) {
+                Bundle result = new Bundle();
+                result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+                try {
+                    response.onResult(result);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to report error back to the client." + e);
+                }
+            }
+            return;
         }
 
+        Slog.d(TAG, "Copying account " + account.name
+                + " from user " + userFrom + " to user " + userTo);
         long identityToken = clearCallingIdentity();
         try {
-            new Session(fromAccounts, null, account.type, false,
+            new Session(fromAccounts, response, account.type, false,
                     false /* stripAuthTokenFromResult */) {
                 @Override
                 protected String toDebugString(long now) {
@@ -696,12 +711,10 @@
 
                 @Override
                 public void onResult(Bundle result) {
-                    if (result != null) {
-                        if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
-                            // Create a Session for the target user and pass in the bundle
-                            completeCloningAccount(result, account, toAccounts);
-                        }
-                        return;
+                    if (result != null
+                            && result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
+                        // Create a Session for the target user and pass in the bundle
+                        completeCloningAccount(response, result, account, toAccounts);
                     } else {
                         super.onResult(result);
                     }
@@ -710,14 +723,13 @@
         } finally {
             restoreCallingIdentity(identityToken);
         }
-        return true;
     }
 
-    void completeCloningAccount(final Bundle result, final Account account,
-            final UserAccounts targetUser) {
+    private void completeCloningAccount(IAccountManagerResponse response,
+            final Bundle accountCredentials, final Account account, final UserAccounts targetUser) {
         long id = clearCallingIdentity();
         try {
-            new Session(targetUser, null, account.type, false,
+            new Session(targetUser, response, account.type, false,
                     false /* stripAuthTokenFromResult */) {
                 @Override
                 protected String toDebugString(long now) {
@@ -730,10 +742,10 @@
                     // Confirm that the owner's account still exists before this step.
                     UserAccounts owner = getUserAccounts(UserHandle.USER_OWNER);
                     synchronized (owner.cacheLock) {
-                        Account[] ownerAccounts = getAccounts(UserHandle.USER_OWNER);
-                        for (Account acc : ownerAccounts) {
+                        for (Account acc : getAccounts(UserHandle.USER_OWNER)) {
                             if (acc.equals(account)) {
-                                mAuthenticator.addAccountFromCredentials(this, account, result);
+                                mAuthenticator.addAccountFromCredentials(
+                                        this, account, accountCredentials);
                                 break;
                             }
                         }
@@ -742,17 +754,10 @@
 
                 @Override
                 public void onResult(Bundle result) {
-                    if (result != null) {
-                        if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
-                            // TODO: Anything?
-                        } else {
-                            // TODO: Show error notification
-                            // TODO: Should we remove the shadow account to avoid retries?
-                        }
-                        return;
-                    } else {
-                        super.onResult(result);
-                    }
+                    // TODO: Anything to do if if succedded?
+                    // TODO: If it failed: Show error notification? Should we remove the shadow
+                    // account to avoid retries?
+                    super.onResult(result);
                 }
 
                 @Override
@@ -2740,7 +2745,7 @@
                     break;
 
                 case MESSAGE_COPY_SHARED_ACCOUNT:
-                    copyAccountToUser((Account) msg.obj, msg.arg1, msg.arg2);
+                    copyAccountToUser(/*no response*/ null, (Account) msg.obj, msg.arg1, msg.arg2);
                     break;
 
                 default: