add account manager permission checking
diff --git a/api/current.xml b/api/current.xml
index fcebc73..c6d68ef 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -122,6 +122,28 @@
  visibility="public"
 >
 </field>
+<field name="ACCOUNT_MANAGER_SERVICE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.permission.ACCOUNT_MANAGER_SERVICE&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="AUTHENTICATE_ACCOUNTS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.permission.AUTHENTICATE_ACCOUNTS&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="BATTERY_STATS"
  type="java.lang.String"
  transient="false"
@@ -573,6 +595,17 @@
  visibility="public"
 >
 </field>
+<field name="MANAGE_ACCOUNTS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.permission.MANAGE_ACCOUNTS&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="MANAGE_APP_TOKENS"
  type="java.lang.String"
  transient="false"
@@ -1079,6 +1112,17 @@
  visibility="public"
 >
 </field>
+<field name="USE_CREDENTIALS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.permission.USE_CREDENTIALS&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="VIBRATE"
  type="java.lang.String"
  transient="false"
@@ -15929,6 +15973,8 @@
  deprecated="not deprecated"
  visibility="public"
 >
+<parameter name="context" type="android.content.Context">
+</parameter>
 </constructor>
 <method name="addAccount"
  return="android.os.Bundle"
@@ -16023,6 +16069,19 @@
 <exception name="NetworkErrorException" type="android.accounts.NetworkErrorException">
 </exception>
 </method>
+<method name="getAuthTokenLabel"
+ return="java.lang.String"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="authTokenType" type="java.lang.String">
+</parameter>
+</method>
 <method name="getIAccountAuthenticator"
  return="android.accounts.IAccountAuthenticator"
  abstract="false"
@@ -17335,6 +17394,17 @@
  visibility="public"
 >
 </field>
+<field name="AUTH_TOKEN_LABEL_KEY"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;authTokenLabelKey&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="BOOLEAN_RESULT_KEY"
  type="java.lang.String"
  transient="false"
@@ -17715,6 +17785,23 @@
 <exception name="RemoteException" type="android.os.RemoteException">
 </exception>
 </method>
+<method name="getAuthTokenLabel"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="response" type="android.accounts.IAccountAuthenticatorResponse">
+</parameter>
+<parameter name="authTokenType" type="java.lang.String">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
 <method name="hasFeatures"
  return="void"
  abstract="true"
@@ -138591,6 +138678,62 @@
 >
 </method>
 </class>
+<class name="Pair"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="Pair"
+ type="android.util.Pair"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="first" type="F">
+</parameter>
+<parameter name="second" type="S">
+</parameter>
+</constructor>
+<method name="create"
+ return="android.util.Pair&lt;A, B&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="a" type="A">
+</parameter>
+<parameter name="b" type="B">
+</parameter>
+</method>
+<field name="first"
+ type="F"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="second"
+ type="S"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
 <class name="PrintStreamPrinter"
  extends="java.lang.Object"
  abstract="false"
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index 474755c..3ce3ca3 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -18,6 +18,11 @@
 
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.os.Binder;
+import android.util.Log;
+import android.content.pm.PackageManager;
+import android.content.Context;
+import android.Manifest;
 
 /**
  * Base class for creating AccountAuthenticators. This implements the IAccountAuthenticator
@@ -25,10 +30,17 @@
  * AccountAuthenticators.
  */
 public abstract class AbstractAccountAuthenticator {
+    private final Context mContext;
+
+    public AbstractAccountAuthenticator(Context context) {
+        mContext = context;
+    }
+
     class Transport extends IAccountAuthenticator.Stub {
         public void addAccount(IAccountAuthenticatorResponse response, String accountType,
                 String authTokenType, String[] requiredFeatures, Bundle options)
                 throws RemoteException {
+            checkBinderPermission();
             final Bundle result;
             try {
                 result = AbstractAccountAuthenticator.this.addAccount(
@@ -49,6 +61,7 @@
 
         public void confirmPassword(IAccountAuthenticatorResponse response,
                 Account account, String password) throws RemoteException {
+            checkBinderPermission();
             boolean result;
             try {
                 result = AbstractAccountAuthenticator.this.confirmPassword(
@@ -69,6 +82,7 @@
 
         public void confirmCredentials(IAccountAuthenticatorResponse response,
                 Account account) throws RemoteException {
+            checkBinderPermission();
             final Bundle result;
             try {
                 result = AbstractAccountAuthenticator.this.confirmCredentials(
@@ -83,9 +97,28 @@
             }
         }
 
+        public void getAuthTokenLabel(IAccountAuthenticatorResponse response,
+                String authTokenType)
+                throws RemoteException {
+            checkBinderPermission();
+            try {
+                Bundle result = new Bundle();
+                result.putString(Constants.AUTH_TOKEN_LABEL_KEY,
+                        AbstractAccountAuthenticator.this.getAuthTokenLabel(authTokenType));
+                response.onResult(result);
+            } catch (IllegalArgumentException e) {
+                response.onError(Constants.ERROR_CODE_BAD_ARGUMENTS,
+                        "unknown authTokenType");
+            } catch (UnsupportedOperationException e) {
+                response.onError(Constants.ERROR_CODE_UNSUPPORTED_OPERATION,
+                        "getAuthTokenTypeLabel not supported");
+            }
+        }
+
         public void getAuthToken(IAccountAuthenticatorResponse response,
                 Account account, String authTokenType, Bundle loginOptions)
                 throws RemoteException {
+            checkBinderPermission();
             try {
                 final Bundle result = AbstractAccountAuthenticator.this.getAuthToken(
                         new AccountAuthenticatorResponse(response), account,
@@ -103,6 +136,7 @@
 
         public void updateCredentials(IAccountAuthenticatorResponse response, Account account,
                 String authTokenType, Bundle loginOptions) throws RemoteException {
+            checkBinderPermission();
             final Bundle result;
             try {
                 result = AbstractAccountAuthenticator.this.updateCredentials(
@@ -120,6 +154,7 @@
 
         public void editProperties(IAccountAuthenticatorResponse response,
                 String accountType) throws RemoteException {
+            checkBinderPermission();
             final Bundle result;
             try {
                 result = AbstractAccountAuthenticator.this.editProperties(
@@ -136,6 +171,7 @@
 
         public void hasFeatures(IAccountAuthenticatorResponse response,
                 Account account, String[] features) throws RemoteException {
+            checkBinderPermission();
             final Bundle result;
             try {
                 result = AbstractAccountAuthenticator.this.hasFeatures(
@@ -154,6 +190,14 @@
         }
     }
 
+    private void checkBinderPermission() {
+        final int uid = Binder.getCallingUid();
+        final String perm = Manifest.permission.ACCOUNT_MANAGER_SERVICE;
+        if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("caller uid " + uid + " lacks " + perm);
+        }
+    }
+
     Transport mTransport = new Transport();
 
     /**
@@ -189,6 +233,7 @@
     public abstract Bundle getAuthToken(AccountAuthenticatorResponse response,
             Account account, String authTokenType, Bundle loginOptions)
             throws NetworkErrorException;
+    public abstract String getAuthTokenLabel(String authTokenType);
     public abstract Bundle updateCredentials(AccountAuthenticatorResponse response,
             Account account, String authTokenType, Bundle loginOptions);
     public abstract Bundle hasFeatures(AccountAuthenticatorResponse response,
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 5182f2e..502abbb 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -652,6 +652,13 @@
                     // that we see
                     mActivity.startActivity(intent);
                     // leave the Future running to wait for the real response to this request
+                } else if (bundle.getBoolean("retry")) {
+                    try {
+                        doWork();
+                    } catch (RemoteException e) {
+                        // this will only happen if the system process is dead, which means
+                        // we will be dying ourselves
+                    }
                 } else {
                     set(bundle);
                 }
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 4f617c4..0c941be 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -21,6 +21,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.RegisteredServicesCache;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.database.sqlite.SQLiteDatabase;
@@ -33,18 +35,25 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.Binder;
+import android.os.SystemProperties;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 import android.app.PendingIntent;
 import android.app.NotificationManager;
 import android.app.Notification;
+import android.Manifest;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.LinkedHashMap;
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.R;
@@ -63,7 +72,7 @@
 
     private static final int TIMEOUT_DELAY_MS = 1000 * 60;
     private static final String DATABASE_NAME = "accounts.db";
-    private static final int DATABASE_VERSION = 2;
+    private static final int DATABASE_VERSION = 3;
 
     private final Context mContext;
 
@@ -92,6 +101,11 @@
     private static final String AUTHTOKENS_TYPE = "type";
     private static final String AUTHTOKENS_AUTHTOKEN = "authtoken";
 
+    private static final String TABLE_GRANTS = "grants";
+    private static final String GRANTS_ACCOUNTS_ID = "accounts_id";
+    private static final String GRANTS_AUTH_TOKEN_TYPE = "auth_token_type";
+    private static final String GRANTS_GRANTEE_UID = "uid";
+
     private static final String TABLE_EXTRAS = "extras";
     private static final String EXTRAS_ID = "_id";
     private static final String EXTRAS_ACCOUNTS_ID = "accounts_id";
@@ -107,8 +121,37 @@
     private static final Intent ACCOUNTS_CHANGED_INTENT =
             new Intent(Constants.LOGIN_ACCOUNTS_CHANGED_ACTION);
 
+    private static final String COUNT_OF_MATCHING_GRANTS = ""
+            + "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS
+            + " WHERE " + GRANTS_ACCOUNTS_ID + "=" + ACCOUNTS_ID
+            + " AND " + GRANTS_GRANTEE_UID + "=?"
+            + " AND " + GRANTS_AUTH_TOKEN_TYPE + "=?"
+            + " AND " + ACCOUNTS_NAME + "=?"
+            + " AND " + ACCOUNTS_TYPE + "=?";
+
     private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>();
-    private static final int NOTIFICATION_ID = 234;
+    private final AtomicInteger mNotificationIds = new AtomicInteger(1);
+
+    private final HashMap<Pair<Pair<Account, String>, Integer>, Integer>
+            mCredentialsPermissionNotificationIds =
+            new HashMap<Pair<Pair<Account, String>, Integer>, Integer>();
+    private final HashMap<Account, Integer> mSigninRequiredNotificationIds =
+            new HashMap<Account, Integer>();
+    private static AtomicReference<AccountManagerService> sThis =
+            new AtomicReference<AccountManagerService>();
+
+    private static final boolean isDebuggableMonkeyBuild =
+            SystemProperties.getBoolean("ro.monkey", false)
+                    && SystemProperties.getBoolean("ro.debuggable", false);
+    /**
+     * This should only be called by system code. One should only call this after the service
+     * has started.
+     * @return a reference to the AccountManagerService instance
+     * @hide
+     */
+    public static AccountManagerService getSingleton() {
+        return sThis.get();
+    }
 
     public class AuthTokenKey {
         public final Account mAccount;
@@ -163,9 +206,12 @@
                 MESSAGE_CONNECTED, MESSAGE_DISCONNECTED);
 
         mSimWatcher = new SimWatcher(mContext);
+        sThis.set(this);
     }
 
     public String getPassword(Account account) {
+        checkAuthenticateAccountsPermission(account);
+
         long identityToken = clearCallingIdentity();
         try {
             SQLiteDatabase db = mOpenHelper.getReadableDatabase();
@@ -186,6 +232,7 @@
     }
 
     public String getUserData(Account account, String key) {
+        checkAuthenticateAccountsPermission(account);
         long identityToken = clearCallingIdentity();
         try {
             SQLiteDatabase db = mOpenHelper.getReadableDatabase();
@@ -207,7 +254,6 @@
                     cursor.close();
                 }
             } finally {
-                db.setTransactionSuccessful();
                 db.endTransaction();
             }
         } finally {
@@ -235,6 +281,7 @@
     }
 
     public Account[] getAccounts() {
+        checkReadAccountsPermission();
         long identityToken = clearCallingIdentity();
         try {
             return getAccountsByType(null);
@@ -244,6 +291,7 @@
     }
 
     public Account[] getAccountsByType(String accountType) {
+        checkReadAccountsPermission();
         long identityToken = clearCallingIdentity();
         try {
             SQLiteDatabase db = mOpenHelper.getReadableDatabase();
@@ -269,6 +317,8 @@
     }
 
     public boolean addAccount(Account account, String password, Bundle extras) {
+        checkAuthenticateAccountsPermission(account);
+
         // fails if the account already exists
         long identityToken = clearCallingIdentity();
         try {
@@ -318,6 +368,7 @@
     }
 
     public void removeAccount(Account account) {
+        checkManageAccountsPermission();
         long identityToken = clearCallingIdentity();
         try {
             final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
@@ -330,6 +381,7 @@
     }
 
     public void invalidateAuthToken(String accountType, String authToken) {
+        checkManageAccountsPermission();
         long identityToken = clearCallingIdentity();
         try {
             SQLiteDatabase db = mOpenHelper.getWritableDatabase();
@@ -404,12 +456,12 @@
             }
             return getAuthToken(db, accountId, authTokenType);
         } finally {
-            db.setTransactionSuccessful();
             db.endTransaction();
         }
     }
 
     public String peekAuthToken(Account account, String authTokenType) {
+        checkAuthenticateAccountsPermission(account);
         long identityToken = clearCallingIdentity();
         try {
             return readAuthTokenFromDatabase(account, authTokenType);
@@ -419,6 +471,7 @@
     }
 
     public void setAuthToken(Account account, String authTokenType, String authToken) {
+        checkAuthenticateAccountsPermission(account);
         long identityToken = clearCallingIdentity();
         try {
             cacheAuthToken(account, authTokenType, authToken);
@@ -428,6 +481,7 @@
     }
 
     public void setPassword(Account account, String password) {
+        checkAuthenticateAccountsPermission(account);
         long identityToken = clearCallingIdentity();
         try {
             ContentValues values = new ContentValues();
@@ -446,6 +500,7 @@
     }
 
     public void clearPassword(Account account) {
+        checkManageAccountsPermission();
         long identityToken = clearCallingIdentity();
         try {
             setPassword(account, null);
@@ -455,6 +510,7 @@
     }
 
     public void setUserData(Account account, String key, String value) {
+        checkAuthenticateAccountsPermission(account);
         long identityToken = clearCallingIdentity();
         try {
             SQLiteDatabase db = mOpenHelper.getWritableDatabase();
@@ -487,26 +543,39 @@
         }
     }
 
+    private void onResult(IAccountManagerResponse response, Bundle result) {
+        try {
+            response.onResult(result);
+        } catch (RemoteException e) {
+            // if the caller is dead then there is no one to care about remote
+            // exceptions
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "failure while notifying response", e);
+            }
+        }
+    }
+
     public void getAuthToken(IAccountManagerResponse response, final Account account,
             final String authTokenType, final boolean notifyOnAuthFailure,
             final boolean expectActivityLaunch, final Bundle loginOptions) {
+        checkBinderPermission(Manifest.permission.USE_CREDENTIALS);
+        final int callerUid = Binder.getCallingUid();
+        final boolean permissionGranted = permissionIsGranted(account, authTokenType, callerUid);
+
         long identityToken = clearCallingIdentity();
         try {
-            String authToken = readAuthTokenFromDatabase(account, authTokenType);
-            if (authToken != null) {
-                try {
+            // if the caller has permission, do the peek. otherwise go the more expensive
+            // route of starting a Session
+            if (permissionGranted) {
+                String authToken = readAuthTokenFromDatabase(account, authTokenType);
+                if (authToken != null) {
                     Bundle result = new Bundle();
                     result.putString(Constants.AUTHTOKEN_KEY, authToken);
                     result.putString(Constants.ACCOUNT_NAME_KEY, account.mName);
                     result.putString(Constants.ACCOUNT_TYPE_KEY, account.mType);
-                    response.onResult(result);
-                } catch (RemoteException e) {
-                    // if the caller is dead then there is no one to care about remote exceptions
-                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                        Log.v(TAG, "failure while notifying response", e);
-                    }
+                    onResult(response, result);
+                    return;
                 }
-                return;
             }
 
             new Session(response, account.mType, expectActivityLaunch) {
@@ -520,11 +589,27 @@
                 }
 
                 public void run() throws RemoteException {
-                    mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions);
+                    // If the caller doesn't have permission then create and return the
+                    // "grant permission" intent instead of the "getAuthToken" intent.
+                    if (!permissionGranted) {
+                        mAuthenticator.getAuthTokenLabel(this, authTokenType);
+                    } else {
+                        mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions);
+                    }
                 }
 
                 public void onResult(Bundle result) {
                     if (result != null) {
+                        if (result.containsKey(Constants.AUTH_TOKEN_LABEL_KEY)) {
+                            Intent intent = newGrantCredentialsPermissionIntent(account, callerUid,
+                                    new AccountAuthenticatorResponse(this),
+                                    authTokenType,
+                                    result.getString(Constants.AUTH_TOKEN_LABEL_KEY));
+                            Bundle bundle = new Bundle();
+                            bundle.putParcelable(Constants.INTENT_KEY, intent);
+                            onResult(bundle);
+                            return;
+                        }
                         String authToken = result.getString(Constants.AUTHTOKEN_KEY);
                         if (authToken != null) {
                             String name = result.getString(Constants.ACCOUNT_NAME_KEY);
@@ -539,7 +624,8 @@
 
                         Intent intent = result.getParcelable(Constants.INTENT_KEY);
                         if (intent != null && notifyOnAuthFailure) {
-                            doNotification(result.getString(Constants.AUTH_FAILED_MESSAGE_KEY),
+                            doNotification(
+                                    account, result.getString(Constants.AUTH_FAILED_MESSAGE_KEY),
                                     intent);
                         }
                     }
@@ -551,10 +637,92 @@
         }
     }
 
+    private void createNoCredentialsPermissionNotification(Account account, Intent intent) {
+        int uid = intent.getIntExtra(
+                GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, -1);
+        String authTokenType = intent.getStringExtra(
+                GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE);
+        String authTokenLabel = intent.getStringExtra(
+                GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_LABEL);
+
+        Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
+                0 /* when */);
+        final CharSequence subtitleFormatString =
+                mContext.getText(R.string.permission_request_notification_subtitle);
+        n.setLatestEventInfo(mContext,
+                mContext.getText(R.string.permission_request_notification_title),
+                String.format(subtitleFormatString.toString(), account.mName),
+                PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT));
+        ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
+                .notify(getCredentialPermissionNotificationId(account, authTokenType, uid), n);
+    }
+
+    private Intent newGrantCredentialsPermissionIntent(Account account, int uid,
+            AccountAuthenticatorResponse response, String authTokenType, String authTokenLabel) {
+        RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo =
+                mAuthenticatorCache.getServiceInfo(
+                        AuthenticatorDescription.newKey(account.mType));
+        if (serviceInfo == null) {
+            throw new IllegalArgumentException("unknown account type: " + account.mType);
+        }
+
+        final Context authContext;
+        try {
+            authContext = mContext.createPackageContext(
+                serviceInfo.type.packageName, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new IllegalArgumentException("unknown account type: " + account.mType);
+        }
+
+        Intent intent = new Intent(mContext, GrantCredentialsPermissionActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.addCategory(
+                String.valueOf(getCredentialPermissionNotificationId(account, authTokenType, uid)));
+        intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT, account);
+        intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_LABEL, authTokenLabel);
+        intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE, authTokenType);
+        intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_RESPONSE, response);
+        intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT_TYPE_LABEL,
+                        authContext.getString(serviceInfo.type.labelId));
+        intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_PACKAGES,
+                        mContext.getPackageManager().getPackagesForUid(uid));
+        intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, uid);
+        return intent;
+    }
+
+    private Integer getCredentialPermissionNotificationId(Account account, String authTokenType,
+            int uid) {
+        Integer id;
+        synchronized(mCredentialsPermissionNotificationIds) {
+            final Pair<Pair<Account, String>, Integer> key =
+                    new Pair<Pair<Account, String>, Integer>(
+                            new Pair<Account, String>(account, authTokenType), uid);
+            id = mCredentialsPermissionNotificationIds.get(key);
+            if (id == null) {
+                id = mNotificationIds.incrementAndGet();
+                mCredentialsPermissionNotificationIds.put(key, id);
+            }
+        }
+        return id;
+    }
+
+    private Integer getSigninRequiredNotificationId(Account account) {
+        Integer id;
+        synchronized(mSigninRequiredNotificationIds) {
+            id = mSigninRequiredNotificationIds.get(account);
+            if (id == null) {
+                id = mNotificationIds.incrementAndGet();
+                mSigninRequiredNotificationIds.put(account, id);
+            }
+        }
+        return id;
+    }
+
 
     public void addAcount(final IAccountManagerResponse response, final String accountType,
             final String authTokenType, final String[] requiredFeatures,
             final boolean expectActivityLaunch, final Bundle options) {
+        checkManageAccountsPermission();
         long identityToken = clearCallingIdentity();
         try {
             new Session(response, accountType, expectActivityLaunch) {
@@ -579,6 +747,7 @@
 
     public void confirmCredentials(IAccountManagerResponse response,
             final Account account, final boolean expectActivityLaunch) {
+        checkManageAccountsPermission();
         long identityToken = clearCallingIdentity();
         try {
             new Session(response, account.mType, expectActivityLaunch) {
@@ -597,6 +766,7 @@
 
     public void confirmPassword(IAccountManagerResponse response, final Account account,
             final String password) {
+        checkManageAccountsPermission();
         long identityToken = clearCallingIdentity();
         try {
             new Session(response, account.mType, false /* expectActivityLaunch */) {
@@ -616,6 +786,7 @@
     public void updateCredentials(IAccountManagerResponse response, final Account account,
             final String authTokenType, final boolean expectActivityLaunch,
             final Bundle loginOptions) {
+        checkManageAccountsPermission();
         long identityToken = clearCallingIdentity();
         try {
             new Session(response, account.mType, expectActivityLaunch) {
@@ -637,6 +808,7 @@
 
     public void editProperties(IAccountManagerResponse response, final String accountType,
             final boolean expectActivityLaunch) {
+        checkManageAccountsPermission();
         long identityToken = clearCallingIdentity();
         try {
             new Session(response, accountType, expectActivityLaunch) {
@@ -728,6 +900,7 @@
     }
     public void getAccountsByTypeAndFeatures(IAccountManagerResponse response,
             String type, String[] features) {
+        checkReadAccountsPermission();
         if (type == null) {
             if (response != null) {
                 try {
@@ -929,7 +1102,12 @@
         public void onResult(Bundle result) {
             mNumResults++;
             if (result != null && !TextUtils.isEmpty(result.getString(Constants.AUTHTOKEN_KEY))) {
-                cancelNotification();
+                String accountName = result.getString(Constants.ACCOUNT_NAME_KEY);
+                String accountType = result.getString(Constants.ACCOUNT_TYPE_KEY);
+                if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
+                    Account account = new Account(accountName, accountType);
+                    cancelNotification(getSigninRequiredNotificationId(account));
+                }
             }
             IAccountManagerResponse response;
             if (mExpectActivityLaunch && result != null
@@ -1026,6 +1204,8 @@
                     + AUTHTOKENS_AUTHTOKEN + " TEXT,  "
                     + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))");
 
+            createGrantsTable(db);
+
             db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( "
                     + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
                     + EXTRAS_ACCOUNTS_ID + " INTEGER, "
@@ -1037,6 +1217,10 @@
                     + META_KEY + " TEXT PRIMARY KEY NOT NULL, "
                     + META_VALUE + " TEXT)");
 
+            createAccountsDeletionTrigger(db);
+        }
+
+        private void createAccountsDeletionTrigger(SQLiteDatabase db) {
             db.execSQL(""
                     + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
                     + " BEGIN"
@@ -1044,22 +1228,34 @@
                     + "     WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
                     + "   DELETE FROM " + TABLE_EXTRAS
                     + "     WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
+                    + "   DELETE FROM " + TABLE_GRANTS
+                    + "     WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
                     + " END");
         }
 
+        private void createGrantsTable(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE " + TABLE_GRANTS + " (  "
+                    + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, "
+                    + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL,  "
+                    + GRANTS_GRANTEE_UID + " INTEGER NOT NULL,  "
+                    + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE
+                    +   "," + GRANTS_GRANTEE_UID + "))");
+        }
+
         @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
             Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
 
             if (oldVersion == 1) {
-                db.execSQL(""
-                        + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
-                        + " BEGIN"
-                        + "   DELETE FROM " + TABLE_AUTHTOKENS
-                        + "     WHERE " + AUTHTOKENS_ACCOUNTS_ID + " =OLD." + ACCOUNTS_ID + " ;"
-                        + "   DELETE FROM " + TABLE_EXTRAS
-                        + "     WHERE " + EXTRAS_ACCOUNTS_ID + " =OLD." + ACCOUNTS_ID + " ;"
-                        + " END");
+                // no longer need to do anything since the work is done
+                // when upgrading from version 2
+                oldVersion++;
+            }
+
+            if (oldVersion == 2) {
+                createGrantsTable(db);
+                db.execSQL("DROP TRIGGER " + TABLE_ACCOUNTS + "Delete");
+                createAccountsDeletionTrigger(db);
                 oldVersion++;
             }
         }
@@ -1156,31 +1352,171 @@
         mAuthenticatorCache.dump(fd, fout, args);
     }
 
-    private void doNotification(CharSequence message, Intent intent) {
+    private void doNotification(Account account, CharSequence message, Intent intent) {
         long identityToken = clearCallingIdentity();
         try {
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "doNotification: " + message + " intent:" + intent);
             }
 
-            Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
-                    0 /* when */);
-            n.setLatestEventInfo(mContext, mContext.getText(R.string.notification_title), message,
-                PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT));
-            ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
-                    .notify(NOTIFICATION_ID, n);
+            if (intent.getComponent() != null &&
+                    GrantCredentialsPermissionActivity.class.getName().equals(
+                            intent.getComponent().getClassName())) {
+                createNoCredentialsPermissionNotification(account, intent);
+            } else {
+                Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
+                        0 /* when */);
+                n.setLatestEventInfo(mContext, mContext.getText(R.string.notification_title),
+                        message, PendingIntent.getActivity(
+                        mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT));
+                ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
+                        .notify(getSigninRequiredNotificationId(account), n);
+            }
         } finally {
             restoreCallingIdentity(identityToken);
         }
     }
 
-    private void cancelNotification() {
+    private void cancelNotification(int id) {
         long identityToken = clearCallingIdentity();
         try {
             ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
-                .cancel(NOTIFICATION_ID);
+                .cancel(id);
         } finally {
             restoreCallingIdentity(identityToken);
         }
     }
+
+    private void checkBinderPermission(String permission) {
+        final int uid = Binder.getCallingUid();
+        if (mContext.checkCallingOrSelfPermission(permission) !=
+                PackageManager.PERMISSION_GRANTED) {
+            String msg = "caller uid " + uid + " lacks " + permission;
+            Log.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "caller uid " + uid + " has " + permission);
+        }
+    }
+
+    private boolean permissionIsGranted(Account account, String authTokenType, int callerUid) {
+        final boolean fromAuthenticator = hasAuthenticatorUid(account.mType, callerUid);
+        final boolean hasExplicitGrants = hasExplicitlyGrantedPermission(account, authTokenType);
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid "
+                    + callerUid + ", account " + account
+                    + ": is authenticator? " + fromAuthenticator
+                    + ", has explicit permission? " + hasExplicitGrants);
+        }
+        return fromAuthenticator || hasExplicitGrants;
+    }
+
+    private boolean hasAuthenticatorUid(String accountType, int callingUid) {
+        for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo :
+                mAuthenticatorCache.getAllServices()) {
+            if (serviceInfo.type.type.equals(accountType)) {
+                return serviceInfo.uid == callingUid;
+            }
+        }
+        return false;
+    }
+
+    private boolean hasExplicitlyGrantedPermission(Account account, String authTokenType) {
+        if (Binder.getCallingUid() == android.os.Process.SYSTEM_UID) {
+            return true;
+        }
+        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+        String[] args = {String.valueOf(Binder.getCallingUid()), authTokenType,
+                account.mName, account.mType};
+        final boolean permissionGranted =
+                DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args) != 0;
+        if (!permissionGranted && isDebuggableMonkeyBuild) {
+            // TODO: Skip this check when running automated tests. Replace this
+            // with a more general solution.
+            Log.w(TAG, "no credentials permission for usage of " + account + ", "
+                    + authTokenType + " by uid " + Binder.getCallingUid()
+                    + " but ignoring since this is a monkey build");
+            return true;
+        }
+        return permissionGranted;
+    }
+
+    private void checkCallingUidAgainstAuthenticator(Account account) {
+        final int uid = Binder.getCallingUid();
+        if (!hasAuthenticatorUid(account.mType, uid)) {
+            String msg = "caller uid " + uid + " is different than the authenticator's uid";
+            Log.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "caller uid " + uid + " is the same as the authenticator's uid");
+        }
+    }
+
+    private void checkAuthenticateAccountsPermission(Account account) {
+        checkBinderPermission(Manifest.permission.AUTHENTICATE_ACCOUNTS);
+        checkCallingUidAgainstAuthenticator(account);
+    }
+
+    private void checkReadAccountsPermission() {
+        checkBinderPermission(Manifest.permission.GET_ACCOUNTS);
+    }
+
+    private void checkManageAccountsPermission() {
+        checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS);
+    }
+
+    /**
+     * Allow callers with the given uid permission to get credentials for account/authTokenType.
+     * <p>
+     * Although this is public it can only be accessed via the AccountManagerService object
+     * which is in the system. This means we don't need to protect it with permissions.
+     * @hide
+     */
+    public void grantAppPermission(Account account, String authTokenType, int uid) {
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        db.beginTransaction();
+        try {
+            long accountId = getAccountId(db, account);
+            if (accountId >= 0) {
+                ContentValues values = new ContentValues();
+                values.put(GRANTS_ACCOUNTS_ID, accountId);
+                values.put(GRANTS_AUTH_TOKEN_TYPE, authTokenType);
+                values.put(GRANTS_GRANTEE_UID, uid);
+                db.insert(TABLE_GRANTS, GRANTS_ACCOUNTS_ID, values);
+                db.setTransactionSuccessful();
+            }
+        } finally {
+            db.endTransaction();
+        }
+        cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid));
+    }
+
+    /**
+     * Don't allow callers with the given uid permission to get credentials for
+     * account/authTokenType.
+     * <p>
+     * Although this is public it can only be accessed via the AccountManagerService object
+     * which is in the system. This means we don't need to protect it with permissions.
+     * @hide
+     */
+    public void revokeAppPermission(Account account, String authTokenType, int uid) {
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        db.beginTransaction();
+        try {
+            long accountId = getAccountId(db, account);
+            if (accountId >= 0) {
+                db.delete(TABLE_GRANTS,
+                        GRANTS_ACCOUNTS_ID + "=? AND " + GRANTS_AUTH_TOKEN_TYPE + "=? AND "
+                                + GRANTS_GRANTEE_UID + "=?",
+                        new String[]{String.valueOf(accountId), authTokenType,
+                                String.valueOf(uid)});
+                db.setTransactionSuccessful();
+            }
+        } finally {
+            db.endTransaction();
+        }
+        cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid));
+    }
 }
diff --git a/core/java/android/accounts/Constants.java b/core/java/android/accounts/Constants.java
index bb7f940..da8173f 100644
--- a/core/java/android/accounts/Constants.java
+++ b/core/java/android/accounts/Constants.java
@@ -40,6 +40,7 @@
     public static final String ACCOUNT_AUTHENTICATOR_RESPONSE_KEY = "accountAuthenticatorResponse";
     public static final String ACCOUNT_MANAGER_RESPONSE_KEY = "accountManagerResponse";
     public static final String AUTH_FAILED_MESSAGE_KEY = "authFailedMessage";
+    public static final String AUTH_TOKEN_LABEL_KEY = "authTokenLabelKey";
 
     public static final String AUTHENTICATOR_INTENT_ACTION =
             "android.accounts.AccountAuthenticator";
diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
new file mode 100644
index 0000000..f92d43f
--- /dev/null
+++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.accounts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.view.View;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import com.android.internal.R;
+
+/**
+ * @hide
+ */
+public class GrantCredentialsPermissionActivity extends Activity implements View.OnClickListener {
+    public static final String EXTRAS_ACCOUNT = "account";
+    public static final String EXTRAS_AUTH_TOKEN_LABEL = "authTokenLabel";
+    public static final String EXTRAS_AUTH_TOKEN_TYPE = "authTokenType";
+    public static final String EXTRAS_RESPONSE = "response";
+    public static final String EXTRAS_ACCOUNT_TYPE_LABEL = "accountTypeLabel";
+    public static final String EXTRAS_PACKAGES = "application";
+    public static final String EXTRAS_REQUESTING_UID = "uid";
+    private Account mAccount;
+    private String mAuthTokenType;
+    private int mUid;
+    private Bundle mResultBundle = null;
+
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().setContentView(R.layout.grant_credentials_permission);
+        mAccount = getIntent().getExtras().getParcelable(EXTRAS_ACCOUNT);
+        mAuthTokenType = getIntent().getExtras().getString(EXTRAS_AUTH_TOKEN_TYPE);
+        mUid = getIntent().getExtras().getInt(EXTRAS_REQUESTING_UID);
+        final String accountTypeLabel =
+                getIntent().getExtras().getString(EXTRAS_ACCOUNT_TYPE_LABEL);
+        final String[] packages = getIntent().getExtras().getStringArray(EXTRAS_PACKAGES);
+
+        findViewById(R.id.allow).setOnClickListener(this);
+        findViewById(R.id.deny).setOnClickListener(this);
+
+        TextView messageView = (TextView) getWindow().findViewById(R.id.message);
+        String authTokenLabel = getIntent().getExtras().getString(EXTRAS_AUTH_TOKEN_LABEL);
+        if (authTokenLabel.length() == 0) {
+            CharSequence grantCredentialsPermissionFormat = getResources().getText(
+                    R.string.grant_credentials_permission_message_desc);
+            messageView.setText(String.format(grantCredentialsPermissionFormat.toString(),
+                    mAccount.mName, accountTypeLabel));
+        } else {
+            CharSequence grantCredentialsPermissionFormat = getResources().getText(
+                    R.string.grant_credentials_permission_message_with_authtokenlabel_desc);
+            messageView.setText(String.format(grantCredentialsPermissionFormat.toString(),
+                    authTokenLabel, mAccount.mName, accountTypeLabel));
+        }
+
+        String[] packageLabels = new String[packages.length];
+        final PackageManager pm = getPackageManager();
+        for (int i = 0; i < packages.length; i++) {
+            try {
+                packageLabels[i] =
+                        pm.getApplicationLabel(pm.getApplicationInfo(packages[i], 0)).toString();
+            } catch (PackageManager.NameNotFoundException e) {
+                packageLabels[i] = packages[i];
+            }
+        }
+        ((ListView) findViewById(R.id.packages_list)).setAdapter(
+                new PackagesArrayAdapter(this, packageLabels));
+    }
+
+    public void onClick(View v) {
+        switch (v.getId()) {
+            case R.id.allow:
+                AccountManagerService.getSingleton().grantAppPermission(mAccount, mAuthTokenType,
+                        mUid);
+                Intent result = new Intent();
+                result.putExtra("retry", true);
+                setResult(RESULT_OK, result);
+                setAccountAuthenticatorResult(result.getExtras());
+                break;
+
+            case R.id.deny:
+                AccountManagerService.getSingleton().revokeAppPermission(mAccount, mAuthTokenType,
+                        mUid);
+                setResult(RESULT_CANCELED);
+                break;
+        }
+        finish();
+    }
+
+    public final void setAccountAuthenticatorResult(Bundle result) {
+        mResultBundle = result;
+    }
+
+    /**
+     * Sends the result or a Constants.ERROR_CODE_CANCELED error if a result isn't present.
+     */
+    public void finish() {
+        Intent intent = getIntent();
+        AccountAuthenticatorResponse accountAuthenticatorResponse =
+                intent.getParcelableExtra(EXTRAS_RESPONSE);
+        if (accountAuthenticatorResponse != null) {
+            // send the result bundle back if set, otherwise send an error.
+            if (mResultBundle != null) {
+                accountAuthenticatorResponse.onResult(mResultBundle);
+            } else {
+                accountAuthenticatorResponse.onError(Constants.ERROR_CODE_CANCELED, "canceled");
+            }
+        }
+        super.finish();
+    }
+
+    private static class PackagesArrayAdapter extends ArrayAdapter<String> {
+        protected LayoutInflater mInflater;
+        private static final int mResource = R.layout.simple_list_item_1;
+
+        public PackagesArrayAdapter(Context context, String[] items) {
+            super(context, mResource, items);
+            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        }
+
+        static class ViewHolder {
+            TextView label;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            // A ViewHolder keeps references to children views to avoid unneccessary calls
+            // to findViewById() on each row.
+            ViewHolder holder;
+
+            // When convertView is not null, we can reuse it directly, there is no need
+            // to reinflate it. We only inflate a new View when the convertView supplied
+            // by ListView is null.
+            if (convertView == null) {
+                convertView = mInflater.inflate(mResource, null);
+
+                // Creates a ViewHolder and store references to the two children views
+                // we want to bind data to.
+                holder = new ViewHolder();
+                holder.label = (TextView) convertView.findViewById(R.id.text1);
+
+                convertView.setTag(holder);
+            } else {
+                // Get the ViewHolder back to get fast access to the TextView
+                // and the ImageView.
+                holder = (ViewHolder) convertView.getTag();
+            }
+
+            holder.label.setText(getItem(position));
+
+            return convertView;
+        }
+    }
+}
diff --git a/core/java/android/accounts/IAccountAuthenticator.aidl b/core/java/android/accounts/IAccountAuthenticator.aidl
index 46a7144..7d4de39 100644
--- a/core/java/android/accounts/IAccountAuthenticator.aidl
+++ b/core/java/android/accounts/IAccountAuthenticator.aidl
@@ -49,6 +49,11 @@
         String authTokenType, in Bundle options);
 
     /**
+     * Gets the user-visible label of the given authtoken type.
+     */
+    void getAuthTokenLabel(in IAccountAuthenticatorResponse response, String authTokenType);
+
+    /**
      * prompts the user for a new password and writes it to the IAccountManager
      */
     void updateCredentials(in IAccountAuthenticatorResponse response, in Account account,
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index bb94372..342de2b 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -114,10 +114,12 @@
     public static class ServiceInfo<V> {
         public final V type;
         public final ComponentName componentName;
+        public final int uid;
 
-        private ServiceInfo(V type, ComponentName componentName) {
+        private ServiceInfo(V type, ComponentName componentName, int uid) {
             this.type = type;
             this.componentName = componentName;
+            this.uid = uid;
         }
 
         public String toString() {
@@ -223,7 +225,10 @@
             if (v == null) {
                 return null;
             }
-            return new ServiceInfo<V>(v, componentName);
+            final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo;
+            final ApplicationInfo applicationInfo = serviceInfo.applicationInfo;
+            final int uid = applicationInfo.uid;
+            return new ServiceInfo<V>(v, componentName, uid);
         } finally {
             if (parser != null) parser.close();
         }
diff --git a/core/java/android/util/Pair.java b/core/java/android/util/Pair.java
new file mode 100644
index 0000000..bf25306
--- /dev/null
+++ b/core/java/android/util/Pair.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+/**
+ * Container to ease passing around a tuple of two objects. This object provides a sensible
+ * implementation of equals(), returning true if equals() is true on each of the contained
+ * objects.
+ */
+public class Pair<F, S> {
+    public final F first;
+    public final S second;
+
+    /**
+     * Constructor for a Pair. If either are null then equals() and hashCode() will throw
+     * a NullPointerException.
+     * @param first the first object in the Pair
+     * @param second the second object in the pair
+     */
+    public Pair(F first, S second) {
+        this.first = first;
+        this.second = second;
+    }
+
+    /**
+     * Checks the two objects for equality by delegating to their respective equals() methods.
+     * @param o the Pair to which this one is to be checked for equality
+     * @return true if the underlying objects of the Pair are both considered equals()
+     */
+    public boolean equals(Object o) {
+        if (o == this) return true;
+        if (!(o instanceof Pair)) return false;
+        final Pair<F, S> other;
+        try {
+            other = (Pair<F, S>) o;
+        } catch (ClassCastException e) {
+            return false;
+        }
+        return first.equals(other.first) && second.equals(other.second);
+    }
+
+    /**
+     * Compute a hash code using the hash codes of the underlying objects
+     * @return a hashcode of the Pair
+     */
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + first.hashCode();
+        result = 31 * result + second.hashCode();
+        return result;
+    }
+
+    /**
+     * Convenience method for creating an appropriately typed pair.
+     * @param a the first object in the Pair
+     * @param b the second object in the pair
+     * @return a Pair that is templatized with the types of a and b
+     */
+    public static <A, B> Pair <A, B> create(A a, B b) {
+        return new Pair<A, B>(a, b);
+    }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 027fb23..9d55dab 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -25,7 +25,7 @@
     <!-- Special broadcasts that only the system can send -->
     <!-- ================================================ -->
     <eat-comment />
-    
+
     <protected-broadcast android:name="android.intent.action.SCREEN_OFF" />
     <protected-broadcast android:name="android.intent.action.SCREEN_ON" />
     <protected-broadcast android:name="android.intent.action.USER_PRESENT" />
@@ -52,7 +52,7 @@
     <protected-broadcast android:name="android.intent.action.AIRPLANE_MODE" />
     <protected-broadcast android:name="android.intent.action.NEW_OUTGOING_CALL" />
     <protected-broadcast android:name="android.intent.action.REBOOT" />
-    
+
     <!-- ====================================== -->
     <!-- Permissions for things that cost money -->
     <!-- ====================================== -->
@@ -309,6 +309,14 @@
         android:description="@string/permdesc_bluetooth"
         android:label="@string/permlab_bluetooth" />
 
+    <!-- Allows applications to call into AccountAuthenticators. Only
+    the system can get this permission. -->
+    <permission android:name="android.permission.ACCOUNT_MANAGER_SERVICE"
+        android:permissionGroup="android.permission-group.ACCOUNTS"
+        android:protectionLevel="signature"
+        android:description="@string/permdesc_accountManagerService"
+        android:label="@string/permlab_accountManagerService" />
+
     <!-- ================================== -->
     <!-- Permissions for accessing accounts -->
     <!-- ================================== -->
@@ -330,6 +338,28 @@
         android:description="@string/permdesc_getAccounts"
         android:label="@string/permlab_getAccounts" />
 
+    <!-- Allows an application to act as an AccountAuthenticator for
+         the AccountManager -->
+    <permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"
+        android:permissionGroup="android.permission-group.ACCOUNTS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_authenticateAccounts"
+        android:description="@string/permdesc_authenticateAccounts" />
+
+    <!-- Allows an application to request authtokens from the AccountManager -->
+    <permission android:name="android.permission.USE_CREDENTIALS"
+        android:permissionGroup="android.permission-group.ACCOUNTS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_useCredentials"
+        android:description="@string/permdesc_useCredentials" />
+
+    <!-- Allows an application to manage the list of accounts in the AccountManager -->
+    <permission android:name="android.permission.MANAGE_ACCOUNTS"
+        android:permissionGroup="android.permission-group.ACCOUNTS"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_manageAccounts"
+        android:description="@string/permdesc_manageAccounts" />
+
     <!-- ================================== -->
     <!-- Permissions for accessing hardware -->
     <!-- ================================== -->
@@ -1093,6 +1123,11 @@
                 android:exported="true">
         </activity>
 
+        <activity android:name="android.accounts.GrantCredentialsPermissionActivity"
+                android:excludeFromRecents="true"
+                android:exported="true">
+        </activity>
+
         <service android:name="com.android.server.LoadAverageService"
             android:exported="true" />
 
diff --git a/core/res/res/layout/grant_credentials_permission.xml b/core/res/res/layout/grant_credentials_permission.xml
new file mode 100644
index 0000000..fe1c22e
--- /dev/null
+++ b/core/res/res/layout/grant_credentials_permission.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/message" />
+    <Button android:id="@+id/allow"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/allow" />
+
+    <Button android:id="@+id/deny"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/deny" />
+
+    <ListView android:id="@+id/packages_list"
+       android:layout_width="fill_parent" android:layout_height="fill_parent"/>
+
+</LinearLayout>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 3f749e8..d9fec3d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -809,7 +809,7 @@
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_readFrameBuffer">read frame buffer</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_readFrameBuffer">Allows application to 
+    <string name="permdesc_readFrameBuffer">Allows application to
         read the content of the frame buffer.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -970,12 +970,40 @@
         the phone\'s time zone.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_accountManagerService">act as the AccountManagerService</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_accountManagerService">Allows an
+    application to make calls to AccountAuthenticators</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_getAccounts">discover known accounts</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_getAccounts">Allows an application to get
       the list of accounts known by the phone.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_authenticateAccounts">act as an account authenticator</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_authenticateAccounts">Allows an application
+    to use the account authenticator capabilities of the
+    AccountManager, including creating accounts and getting and
+    setting their passwords.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_manageAccounts">manage the accounts list</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_manageAccounts">Allows an application to
+    perform operations like adding, and removing accounts and deleting
+    their password.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_useCredentials">use the authentication
+    credentials of an account</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_useCredentials">Allows an application to
+    request authentication tokens.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_accessNetworkState">view network state</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_accessNetworkState">Allows an application to view
@@ -1797,7 +1825,7 @@
     <string name="adb_active_notification_title">USB debugging connected</string>
     <!-- Message of notification shown when ADB is actively connected to the phone. -->
     <string name="adb_active_notification_message">A computer is connected to your phone.</string>
-    
+
     <!-- Used to replace %s in urls retreived from the signin server with locales.  For Some        -->
     <!-- devices we don't support all the locales we ship to and need to replace the '%s' with a    -->
     <!-- locale string based on mcc values.  By default (0-length string) we don't replace the %s   -->
@@ -2048,4 +2076,19 @@
      <!-- Title for the unselected state of a CompoundButton. -->
      <string name="accessibility_compound_button_unselected">not checked</string>
 
+     <string name="grant_credentials_permission_message_desc">The
+     listed applications are requesting permission to access the login credentials for account %s from
+     %s. Do you wish to grant this permission? If so then your answer will be remembered and you will not be prompted
+     again.</string>
+
+     <string name="grant_credentials_permission_message_with_authtokenlabel_desc">The
+     listed applications are requesting permission to access the %s login credentials for account %s from
+     %s. Do you wish to grant this permission? If so then your answer will be remembered and you will not be prompted
+     again.</string>
+
+     <string name="allow">Allow</string>
+     <string name="deny">Deny</string>
+     <string name="permission_request_notification_title">Permission Requested</string>
+     <string name="permission_request_notification_subtitle">for account %s</string>
+
 </resources>
diff --git a/tests/AndroidTests/AndroidManifest.xml b/tests/AndroidTests/AndroidManifest.xml
index 845f547..d94327a 100644
--- a/tests/AndroidTests/AndroidManifest.xml
+++ b/tests/AndroidTests/AndroidManifest.xml
@@ -52,6 +52,7 @@
 
     <uses-permission android:name="com.android.unit_tests.permission.TEST_GRANTED" />
 
+    <uses-permission android:name="android.permission.USE_CREDENTIALS" />
     <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
     <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES" />
     <uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" />