- add verbose logging to the authenticator
- fix a bug in AccountManager.getAuthTokenByFeatures() where getAuthToken()
  isn't called after adding the account when necessary
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index 8bc7428..c0c4c17 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -24,6 +24,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.Manifest;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Arrays;
 
 /**
  * Abstract base class for creating AccountAuthenticators.
@@ -103,6 +107,8 @@
  * writing activities to handle these requests.
  */
 public abstract class AbstractAccountAuthenticator {
+    private static final String TAG = "AccountAuthenticator";
+
     private final Context mContext;
 
     public AbstractAccountAuthenticator(Context context) {
@@ -111,19 +117,34 @@
 
     private class Transport extends IAccountAuthenticator.Stub {
         public void addAccount(IAccountAuthenticatorResponse response, String accountType,
-                String authTokenType, String[] requiredFeatures, Bundle options)
+                String authTokenType, String[] features, Bundle options)
                 throws RemoteException {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "addAccount: accountType " + accountType
+                        + ", authTokenType " + authTokenType
+                        + ", features " + (features == null ? "[]" : Arrays.toString(features)));
+            }
             checkBinderPermission();
             try {
                 final Bundle result = AbstractAccountAuthenticator.this.addAccount(
                     new AccountAuthenticatorResponse(response),
-                        accountType, authTokenType, requiredFeatures, options);
+                        accountType, authTokenType, features, options);
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    result.keySet(); // force it to be unparcelled
+                    Log.v(TAG, "addAccount: result " + AccountManager.sanitizeResult(result));
+                }
                 if (result != null) {
                     response.onResult(result);
                 }
             } catch (NetworkErrorException e) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "addAccount", e);
+                }
                 response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage());
             } catch (UnsupportedOperationException e) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "addAccount", e);
+                }
                 response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
                         "addAccount not supported");
             }
@@ -131,16 +152,30 @@
 
         public void confirmCredentials(IAccountAuthenticatorResponse response,
                 Account account, Bundle options) throws RemoteException {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "confirmCredentials: " + account);
+            }
             checkBinderPermission();
             try {
                 final Bundle result = AbstractAccountAuthenticator.this.confirmCredentials(
                     new AccountAuthenticatorResponse(response), account, options);
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    result.keySet(); // force it to be unparcelled
+                    Log.v(TAG, "confirmCredentials: result "
+                            + AccountManager.sanitizeResult(result));
+                }
                 if (result != null) {
                     response.onResult(result);
                 }
             } catch (NetworkErrorException e) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "confirmCredentials", e);
+                }
                 response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage());
             } catch (UnsupportedOperationException e) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "confirmCredentials", e);
+                }
                 response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
                         "confirmCredentials not supported");
             }
@@ -149,16 +184,32 @@
         public void getAuthTokenLabel(IAccountAuthenticatorResponse response,
                 String authTokenType)
                 throws RemoteException {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "getAuthTokenLabel: authTokenType " + authTokenType);
+            }
             checkBinderPermission();
             try {
                 Bundle result = new Bundle();
                 result.putString(AccountManager.KEY_AUTH_TOKEN_LABEL,
                         AbstractAccountAuthenticator.this.getAuthTokenLabel(authTokenType));
-                response.onResult(result);
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    result.keySet(); // force it to be unparcelled
+                    Log.v(TAG, "getAuthTokenLabel: result "
+                            + AccountManager.sanitizeResult(result));
+                }
+                if (result != null) {
+                    response.onResult(result);
+                }
             } catch (IllegalArgumentException e) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "getAuthTokenLabel", e);
+                }
                 response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS,
                         "unknown authTokenType");
             } catch (UnsupportedOperationException e) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "getAuthTokenLabel", e);
+                }
                 response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
                         "getAuthTokenTypeLabel not supported");
             }
@@ -167,35 +218,64 @@
         public void getAuthToken(IAccountAuthenticatorResponse response,
                 Account account, String authTokenType, Bundle loginOptions)
                 throws RemoteException {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "getAuthToken: " + account
+                        + ", authTokenType " + authTokenType);
+            }
             checkBinderPermission();
             try {
                 final Bundle result = AbstractAccountAuthenticator.this.getAuthToken(
                         new AccountAuthenticatorResponse(response), account,
                         authTokenType, loginOptions);
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    result.keySet(); // force it to be unparcelled
+                    Log.v(TAG, "getAuthToken: result " + AccountManager.sanitizeResult(result));
+                }
                 if (result != null) {
                     response.onResult(result);
                 }
             } catch (UnsupportedOperationException e) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "getAuthToken", e);
+                }
                 response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
                         "getAuthToken not supported");
             } catch (NetworkErrorException e) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "getAuthToken", e);
+                }
                 response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage());
             }
         }
 
         public void updateCredentials(IAccountAuthenticatorResponse response, Account account,
                 String authTokenType, Bundle loginOptions) throws RemoteException {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "updateCredentials: " + account
+                        + ", authTokenType " + authTokenType);
+            }
             checkBinderPermission();
             try {
                 final Bundle result = AbstractAccountAuthenticator.this.updateCredentials(
                     new AccountAuthenticatorResponse(response), account,
                         authTokenType, loginOptions);
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    result.keySet(); // force it to be unparcelled
+                    Log.v(TAG, "updateCredentials: result "
+                            + AccountManager.sanitizeResult(result));
+                }
                 if (result != null) {
                     response.onResult(result);
                 }
             } catch (NetworkErrorException e) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "updateCredentials", e);
+                }
                 response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage());
             } catch (UnsupportedOperationException e) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "updateCredentials", e);
+                }
                 response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
                         "updateCredentials not supported");
             }
diff --git a/core/java/android/accounts/AccountAuthenticatorResponse.java b/core/java/android/accounts/AccountAuthenticatorResponse.java
index 7c09fbf..614e139 100644
--- a/core/java/android/accounts/AccountAuthenticatorResponse.java
+++ b/core/java/android/accounts/AccountAuthenticatorResponse.java
@@ -20,11 +20,14 @@
 import android.os.Parcelable;
 import android.os.Parcel;
 import android.os.RemoteException;
+import android.util.Log;
 
 /**
  * Object used to communicate responses back to the AccountManager
  */
 public class AccountAuthenticatorResponse implements Parcelable {
+    private static final String TAG = "AccountAuthenticator";
+
     private IAccountAuthenticatorResponse mAccountAuthenticatorResponse;
 
     /**
@@ -40,6 +43,11 @@
     }
 
     public void onResult(Bundle result) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            result.keySet(); // force it to be unparcelled
+            Log.v(TAG, "AccountAuthenticatorResponse.onResult: "
+                    + AccountManager.sanitizeResult(result));
+        }
         try {
             mAccountAuthenticatorResponse.onResult(result);
         } catch (RemoteException e) {
@@ -48,6 +56,9 @@
     }
 
     public void onRequestContinued() {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "AccountAuthenticatorResponse.onRequestContinued");
+        }
         try {
             mAccountAuthenticatorResponse.onRequestContinued();
         } catch (RemoteException e) {
@@ -56,6 +67,9 @@
     }
 
     public void onError(int errorCode, String errorMessage) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "AccountAuthenticatorResponse.onError: " + errorCode + ", " + errorMessage);
+        }
         try {
             mAccountAuthenticatorResponse.onError(errorCode, errorMessage);
         } catch (RemoteException e) {
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 1bb1d0f..8356029 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -29,6 +29,7 @@
 import android.os.Parcelable;
 import android.os.Build;
 import android.util.Log;
+import android.text.TextUtils;
 
 import java.io.IOException;
 import java.util.concurrent.Callable;
@@ -223,6 +224,19 @@
     }
 
     /**
+     * @hide for internal use only
+     */
+    public static Bundle sanitizeResult(Bundle result) {
+        if (result.containsKey(KEY_AUTHTOKEN)
+                && !TextUtils.isEmpty(result.getString(KEY_AUTHTOKEN))) {
+            final Bundle newResult = new Bundle(result);
+            newResult.putString(KEY_AUTHTOKEN, "<omitted for logging purposes>");
+            return newResult;
+        }
+        return result;
+    }
+
+    /**
      * Gets an AccountManager instance associated with a Context.
      * The {@link Context} will be used as long as the AccountManager is
      * active, so make sure to use a {@link Context} whose lifetime is
@@ -1447,6 +1461,7 @@
         final Bundle mAddAccountOptions;
         final Bundle mLoginOptions;
         final AccountManagerCallback<Bundle> mMyCallback;
+        private volatile int mNumAccounts = 0;
 
         public void doWork() throws RemoteException {
             getAccountsByTypeAndFeatures(mAccountType, mFeatures,
@@ -1466,6 +1481,8 @@
                                 return;
                             }
 
+                            mNumAccounts = accounts.length;
+
                             if (accounts.length == 0) {
                                 if (mActivity != null) {
                                     // no accounts, add one now. pretend that the user directly
@@ -1538,7 +1555,21 @@
 
         public void run(AccountManagerFuture<Bundle> future) {
             try {
-                set(future.getResult());
+                final Bundle result = future.getResult();
+                if (mNumAccounts == 0) {
+                    final String accountName = result.getString(KEY_ACCOUNT_NAME);
+                    final String accountType = result.getString(KEY_ACCOUNT_TYPE);
+                    if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
+                        setException(new AuthenticatorException("account not in result"));
+                        return;
+                    }
+                    final Account account = new Account(accountName, accountType);
+                    mNumAccounts = 1;
+                    getAuthToken(account, mAuthTokenType, null /* options */, mActivity,
+                            mMyCallback, mHandler);
+                    return;
+                }
+                set(result);
             } catch (OperationCanceledException e) {
                 cancel(true /* mayInterruptIfRUnning */);
             } catch (IOException e) {