diff --git a/api/current.xml b/api/current.xml
index fcebc736af310..c6d68ef9d08a8 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -122,6 +122,28 @@
visibility="public"
>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ mSessions = new LinkedHashMap();
- private static final int NOTIFICATION_ID = 234;
+ private final AtomicInteger mNotificationIds = new AtomicInteger(1);
+
+ private final HashMap, Integer>, Integer>
+ mCredentialsPermissionNotificationIds =
+ new HashMap, Integer>, Integer>();
+ private final HashMap mSigninRequiredNotificationIds =
+ new HashMap();
+ private static AtomicReference sThis =
+ new AtomicReference();
+
+ 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 @@ public class AccountManagerService extends IAccountManager.Stub {
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 class AccountManagerService extends IAccountManager.Stub {
}
public String getUserData(Account account, String key) {
+ checkAuthenticateAccountsPermission(account);
long identityToken = clearCallingIdentity();
try {
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
@@ -207,7 +254,6 @@ public class AccountManagerService extends IAccountManager.Stub {
cursor.close();
}
} finally {
- db.setTransactionSuccessful();
db.endTransaction();
}
} finally {
@@ -235,6 +281,7 @@ public class AccountManagerService extends IAccountManager.Stub {
}
public Account[] getAccounts() {
+ checkReadAccountsPermission();
long identityToken = clearCallingIdentity();
try {
return getAccountsByType(null);
@@ -244,6 +291,7 @@ public class AccountManagerService extends IAccountManager.Stub {
}
public Account[] getAccountsByType(String accountType) {
+ checkReadAccountsPermission();
long identityToken = clearCallingIdentity();
try {
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
@@ -269,6 +317,8 @@ public class AccountManagerService extends IAccountManager.Stub {
}
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 class AccountManagerService extends IAccountManager.Stub {
}
public void removeAccount(Account account) {
+ checkManageAccountsPermission();
long identityToken = clearCallingIdentity();
try {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
@@ -330,6 +381,7 @@ public class AccountManagerService extends IAccountManager.Stub {
}
public void invalidateAuthToken(String accountType, String authToken) {
+ checkManageAccountsPermission();
long identityToken = clearCallingIdentity();
try {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
@@ -404,12 +456,12 @@ public class AccountManagerService extends IAccountManager.Stub {
}
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 class AccountManagerService extends IAccountManager.Stub {
}
public void setAuthToken(Account account, String authTokenType, String authToken) {
+ checkAuthenticateAccountsPermission(account);
long identityToken = clearCallingIdentity();
try {
cacheAuthToken(account, authTokenType, authToken);
@@ -428,6 +481,7 @@ public class AccountManagerService extends IAccountManager.Stub {
}
public void setPassword(Account account, String password) {
+ checkAuthenticateAccountsPermission(account);
long identityToken = clearCallingIdentity();
try {
ContentValues values = new ContentValues();
@@ -446,6 +500,7 @@ public class AccountManagerService extends IAccountManager.Stub {
}
public void clearPassword(Account account) {
+ checkManageAccountsPermission();
long identityToken = clearCallingIdentity();
try {
setPassword(account, null);
@@ -455,6 +510,7 @@ public class AccountManagerService extends IAccountManager.Stub {
}
public void setUserData(Account account, String key, String value) {
+ checkAuthenticateAccountsPermission(account);
long identityToken = clearCallingIdentity();
try {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
@@ -487,26 +543,39 @@ public class AccountManagerService extends IAccountManager.Stub {
}
}
+ 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 class AccountManagerService extends IAccountManager.Stub {
}
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 @@ public class AccountManagerService extends IAccountManager.Stub {
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 @@ public class AccountManagerService extends IAccountManager.Stub {
}
}
+ 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 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, Integer> key =
+ new Pair, Integer>(
+ new Pair(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 class AccountManagerService extends IAccountManager.Stub {
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 class AccountManagerService extends IAccountManager.Stub {
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 class AccountManagerService extends IAccountManager.Stub {
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 class AccountManagerService extends IAccountManager.Stub {
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 class AccountManagerService extends IAccountManager.Stub {
}
public void getAccountsByTypeAndFeatures(IAccountManagerResponse response,
String type, String[] features) {
+ checkReadAccountsPermission();
if (type == null) {
if (response != null) {
try {
@@ -929,7 +1102,12 @@ public class AccountManagerService extends IAccountManager.Stub {
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 @@ public class AccountManagerService extends IAccountManager.Stub {
+ 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 @@ public class AccountManagerService extends IAccountManager.Stub {
+ 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 @@ public class AccountManagerService extends IAccountManager.Stub {
+ " 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 @@ public class AccountManagerService extends IAccountManager.Stub {
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 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.
+ *
+ * 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.
+ *
+ * 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 bb7f9401e80a5..da8173f5f47b7 100644
--- a/core/java/android/accounts/Constants.java
+++ b/core/java/android/accounts/Constants.java
@@ -40,6 +40,7 @@ public class Constants {
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 0000000000000..f92d43f68e9bc
--- /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 {
+ 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 46a714468fb5a..7d4de392b6ef6 100644
--- a/core/java/android/accounts/IAccountAuthenticator.aidl
+++ b/core/java/android/accounts/IAccountAuthenticator.aidl
@@ -48,6 +48,11 @@ oneway interface IAccountAuthenticator {
void getAuthToken(in IAccountAuthenticatorResponse response, in Account account,
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
*/
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index bb94372b09a23..342de2b43186a 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -114,10 +114,12 @@ public abstract class RegisteredServicesCache {
public static class ServiceInfo {
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 @@ public abstract class RegisteredServicesCache {
if (v == null) {
return null;
}
- return new ServiceInfo(v, componentName);
+ final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo;
+ final ApplicationInfo applicationInfo = serviceInfo.applicationInfo;
+ final int uid = applicationInfo.uid;
+ return new ServiceInfo(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 0000000000000..bf25306540fd5
--- /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 {
+ 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 other;
+ try {
+ other = (Pair) 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 Pair create(A a, B b) {
+ return new Pair(a, b);
+ }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 027fb2314af0f..9d55dab95f79c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -25,7 +25,7 @@
-
+
@@ -52,7 +52,7 @@
-
+
@@ -309,6 +309,14 @@
android:description="@string/permdesc_bluetooth"
android:label="@string/permlab_bluetooth" />
+
+
+
@@ -330,6 +338,28 @@
android:description="@string/permdesc_getAccounts"
android:label="@string/permlab_getAccounts" />
+
+
+
+
+
+
+
+
+
@@ -1093,6 +1123,11 @@
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 0000000000000..fe1c22ed5b2d4
--- /dev/null
+++ b/core/res/res/layout/grant_credentials_permission.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 3f749e88b6701..d9fec3d7373bb 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -809,7 +809,7 @@
read frame buffer
- Allows application to
+ Allows application to
read the content of the frame buffer.
@@ -969,12 +969,40 @@
Allows an application to change
the phone\'s time zone.
+
+ act as the AccountManagerService
+
+ Allows an
+ application to make calls to AccountAuthenticators
+
discover known accountsAllows an application to get
the list of accounts known by the phone.
+
+ act as an account authenticator
+
+ Allows an application
+ to use the account authenticator capabilities of the
+ AccountManager, including creating accounts and getting and
+ setting their passwords.
+
+
+ manage the accounts list
+
+ Allows an application to
+ perform operations like adding, and removing accounts and deleting
+ their password.
+
+
+ use the authentication
+ credentials of an account
+
+ Allows an application to
+ request authentication tokens.
+
view network state
@@ -1797,7 +1825,7 @@
USB debugging connectedA computer is connected to your phone.
-
+
@@ -2048,4 +2076,19 @@
not checked
+ 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.
+
+ 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.
+
+ Allow
+ Deny
+ Permission Requested
+ for account %s
+
diff --git a/tests/AndroidTests/AndroidManifest.xml b/tests/AndroidTests/AndroidManifest.xml
index 845f547496412..d94327a1759b2 100644
--- a/tests/AndroidTests/AndroidManifest.xml
+++ b/tests/AndroidTests/AndroidManifest.xml
@@ -52,6 +52,7 @@
+