Implement Account Discovery API.
Inludes temporary flow for notifications filtered by accountType
Bug: 33046496
Test: cts tests, manual tests.
Change-Id: I2d767030e851579a0666efd7e243a1239af740c7
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index b27fa24..def0ff9 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -53,9 +53,12 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.SuppressWarnings;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
@@ -864,11 +867,17 @@
*
* @param account The account for which visibility data should be returned.
*
- * @return Map from uid to visibility for given account
+ * @return Map from uid to visibility for given account.
*/
public Map<Integer, Integer> getUidsAndVisibilityForAccount(Account account) {
- // TODO implement.
- return null;
+ try {
+ @SuppressWarnings("unchecked")
+ Map<Integer, Integer> result = (Map<Integer, Integer>) mService
+ .getUidsAndVisibilityForAccount(account);
+ return result;
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
}
/**
@@ -2110,10 +2119,23 @@
synchronized (mAccountsUpdatedListeners) {
try {
if (mAccountsUpdatedListeners.containsKey(listener)) {
- listener.onAccountsUpdated(accountsCopy);
+ Set<String> types = mAccountsUpdatedListenersTypes.get(listener);
+ if (types != null) {
+ // filter by account type;
+ ArrayList<Account> filtered = new ArrayList<>();
+ for (Account account : accountsCopy) {
+ if (types.contains(account.type)) {
+ filtered.add(account);
+ }
+ }
+ listener.onAccountsUpdated(
+ filtered.toArray(new Account[filtered.size()]));
+ } else {
+ listener.onAccountsUpdated(accountsCopy);
+ }
}
} catch (SQLException e) {
- // Better luck next time. If the problem was disk-full,
+ // Better luck next time. If the problem was disk-full,
// the STORAGE_OK intent will re-trigger the update.
Log.e(TAG, "Can't update accounts", e);
}
@@ -2759,6 +2781,9 @@
private final HashMap<OnAccountsUpdateListener, Handler> mAccountsUpdatedListeners =
Maps.newHashMap();
+ private final HashMap<OnAccountsUpdateListener, Set<String> > mAccountsUpdatedListenersTypes =
+ Maps.newHashMap();
+
/**
* BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent
* so that it can read the updated list of accounts and send them to the listener
@@ -2784,7 +2809,7 @@
* accounts of any type related to the caller. This method is equivalent to
* addOnAccountsUpdatedListener(listener, handler, updateImmediately, null)
*
- * @see #addOnAccountsUpdatedListener(OnAccountsUpdateListener, Handler, boolean, Handler,
+ * @see #addOnAccountsUpdatedListener(OnAccountsUpdateListener, Handler, boolean,
* String[])
*/
public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener,
@@ -2828,7 +2853,10 @@
final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty();
mAccountsUpdatedListeners.put(listener, handler);
-
+ if (accountTypes != null) {
+ mAccountsUpdatedListenersTypes.put(listener,
+ new HashSet<String>(Arrays.asList(accountTypes)));
+ }
if (wasEmpty) {
// Register a broadcast receiver to monitor account changes
@@ -2870,6 +2898,7 @@
return;
}
mAccountsUpdatedListeners.remove(listener);
+ mAccountsUpdatedListenersTypes.remove(listener);
if (mAccountsUpdatedListeners.isEmpty()) {
mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
}
diff --git a/core/java/android/accounts/ChooseAccountActivity.java b/core/java/android/accounts/ChooseAccountActivity.java
index 242b3ea..16a45ba 100644
--- a/core/java/android/accounts/ChooseAccountActivity.java
+++ b/core/java/android/accounts/ChooseAccountActivity.java
@@ -52,7 +52,9 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
+ // TODO This activity is only used by getAuthTokenByFeatures and can not see
+ // VISIBILITY_USER_MANAGED_NOT_VISIBLE accounts. It should be moved to account managed
+ // service.
mAccounts = getIntent().getParcelableArrayExtra(AccountManager.KEY_ACCOUNTS);
mAccountManagerResponse =
getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_MANAGER_RESPONSE);
diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
index 8c71f50..95fdfef 100644
--- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java
+++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
@@ -40,7 +40,9 @@
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
/**
@@ -110,7 +112,7 @@
private static final String KEY_INSTANCE_STATE_EXISTING_ACCOUNTS = "existingAccounts";
private static final String KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME = "selectedAccountName";
private static final String KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT = "selectedAddAccount";
- private static final String KEY_INSTANCE_STATE_ACCOUNT_LIST = "accountList";
+ private static final String KEY_INSTANCE_STATE_ACCOUNT_LIST = "accountAndVisibilityList";
private static final int SELECTED_ITEM_NONE = -1;
@@ -120,7 +122,11 @@
private boolean mSelectedAddNewAccount = false;
private String mDescriptionOverride;
- private ArrayList<Account> mAccounts;
+ private Map<Account, Integer> mAccounts;
+ // TODO Redesign flow to show NOT_VISIBLE accounts
+ // and display a warning if they are selected.
+ // Currently NOT_VISBILE accounts are not shown at all.
+ private ArrayList<Account> mPossiblyVisibleAccounts;
private int mPendingRequest = REQUEST_NULL;
private Parcelable[] mExistingAccounts = null;
private int mSelectedItemIndex;
@@ -164,12 +170,12 @@
savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS);
// Makes sure that any user selection is preserved across orientation changes.
- mSelectedAccountName = savedInstanceState.getString(
- KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME);
-
- mSelectedAddNewAccount = savedInstanceState.getBoolean(
- KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
- mAccounts = savedInstanceState.getParcelableArrayList(KEY_INSTANCE_STATE_ACCOUNT_LIST);
+ mSelectedAccountName =
+ savedInstanceState.getString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME);
+ mSelectedAddNewAccount =
+ savedInstanceState.getBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
+ mAccounts = (Map<Account, Integer>) savedInstanceState
+ .getSerializable(KEY_INSTANCE_STATE_ACCOUNT_LIST);
} else {
mPendingRequest = REQUEST_NULL;
mExistingAccounts = null;
@@ -220,9 +226,15 @@
}
}
- String[] listItems = getListOfDisplayableOptions(mAccounts);
- mSelectedItemIndex = getItemIndexToSelect(
- mAccounts, mSelectedAccountName, mSelectedAddNewAccount);
+ mPossiblyVisibleAccounts = new ArrayList<>(mAccounts.size());
+ for (Map.Entry<Account, Integer> entry : mAccounts.entrySet()) {
+ if (AccountManager.VISIBILITY_NOT_VISIBLE != entry.getValue()) {
+ mPossiblyVisibleAccounts.add(entry.getKey());
+ }
+ }
+ String[] listItems = getListOfDisplayableOptions(mPossiblyVisibleAccounts);
+ mSelectedItemIndex = getItemIndexToSelect(mPossiblyVisibleAccounts, mSelectedAccountName,
+ mSelectedAddNewAccount);
super.onCreate(savedInstanceState);
setContentView(R.layout.choose_type_and_account);
@@ -250,15 +262,18 @@
outState.putParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS, mExistingAccounts);
}
if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
- if (mSelectedItemIndex == mAccounts.size()) {
+ if (mSelectedItemIndex == mPossiblyVisibleAccounts.size()) {
outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, true);
} else {
outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
outState.putString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME,
- mAccounts.get(mSelectedItemIndex).name);
+ mPossiblyVisibleAccounts.get(mSelectedItemIndex).name);
}
}
- outState.putParcelableArrayList(KEY_INSTANCE_STATE_ACCOUNT_LIST, mAccounts);
+ // should be HashMap by default.
+ HashMap<Account, Integer> accountsHashMap = (mAccounts instanceof HashMap)
+ ? (HashMap) mAccounts : new HashMap<Account, Integer>(mAccounts);
+ outState.putSerializable(KEY_INSTANCE_STATE_ACCOUNT_LIST, accountsHashMap);
}
public void onCancelButtonClicked(View view) {
@@ -266,11 +281,11 @@
}
public void onOkButtonClicked(View view) {
- if (mSelectedItemIndex == mAccounts.size()) {
+ if (mSelectedItemIndex == mPossiblyVisibleAccounts.size()) {
// Selected "Add New Account" option
startChooseAccountTypeActivity();
} else if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
- onAccountSelected(mAccounts.get(mSelectedItemIndex));
+ onAccountSelected(mPossiblyVisibleAccounts.get(mSelectedItemIndex));
}
}
@@ -321,6 +336,7 @@
}
if (accountName == null || accountType == null) {
+ // new account was added.
Account[] currentAccounts = AccountManager.get(this).getAccountsForPackage(
mCallingPackage, mCallingUid);
Set<Account> preExistingAccounts = new HashSet<Account>();
@@ -328,6 +344,7 @@
preExistingAccounts.add((Account) accountParcel);
}
for (Account account : currentAccounts) {
+ // New account is visible to the app - return it.
if (!preExistingAccounts.contains(account)) {
accountName = account.name;
accountType = account.type;
@@ -409,13 +426,27 @@
}
private void setResultAndFinish(final String accountName, final String accountType) {
+ // Mark account as visible since user chose it.
+ Account account = new Account(accountName, accountType);
+ Integer oldVisibility = mAccounts.get(account);
+ // oldVisibility is null if new account was added
+ if (oldVisibility == null) {
+ Map<Account, Integer> accountsAndVisibility = AccountManager.get(this)
+ .getAccountsAndVisibilityForPackage(mCallingPackage, null /* type */);
+ oldVisibility = accountsAndVisibility.get(account);
+ }
+ if (oldVisibility != null
+ && oldVisibility == AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE) {
+ AccountManager.get(this).setAccountVisibility(account, mCallingUid,
+ AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
+ }
Bundle bundle = new Bundle();
bundle.putString(AccountManager.KEY_ACCOUNT_NAME, accountName);
bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);
setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: "
- + "selected account " + accountName + ", " + accountType);
+ Log.v(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: selected account "
+ + accountName + ", " + accountType);
}
finish();
}
@@ -474,25 +505,28 @@
}
/**
- * Create a list of Account objects for each account that is acceptable. Filter out
- * accounts that don't match the allowable types, if provided, or that don't match the
- * allowable accounts, if provided.
+ * Create a list of Account objects for each account that is acceptable. Filter out accounts
+ * that don't match the allowable types, if provided, or that don't match the allowable
+ * accounts, if provided.
*/
- private ArrayList<Account> getAcceptableAccountChoices(AccountManager accountManager) {
- final Account[] accounts = accountManager.getAccountsForPackage(mCallingPackage,
- mCallingUid);
- ArrayList<Account> accountsToPopulate = new ArrayList<Account>(accounts.length);
- for (Account account : accounts) {
- if (mSetOfAllowableAccounts != null && !mSetOfAllowableAccounts.contains(account)) {
- continue;
- }
- if (mSetOfRelevantAccountTypes != null
- && !mSetOfRelevantAccountTypes.contains(account.type)) {
- continue;
- }
- accountsToPopulate.add(account);
- }
- return accountsToPopulate;
+ private Map<Account, Integer> getAcceptableAccountChoices(AccountManager accountManager) {
+ Map<Account, Integer> accountsAndVisibility =
+ accountManager.getAccountsAndVisibilityForPackage(mCallingPackage, null /* type */);
+
+ Map<Account, Integer> accountsToPopulate =
+ new HashMap<Account, Integer>(accountsAndVisibility.size());
+ for (Map.Entry<Account, Integer> entry : accountsAndVisibility.entrySet()) {
+ if (mSetOfAllowableAccounts != null
+ && !mSetOfAllowableAccounts.contains(entry.getKey())) {
+ continue;
+ }
+ if (mSetOfRelevantAccountTypes != null
+ && !mSetOfRelevantAccountTypes.contains(entry.getKey().type)) {
+ continue;
+ }
+ accountsToPopulate.put(entry.getKey(), entry.getValue());
+ }
+ return accountsToPopulate;
}
/**
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 66c3ca3..63a0919 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -108,8 +108,8 @@
void isCredentialsUpdateSuggested(in IAccountManagerResponse response, in Account account,
String statusToken);
- /* Allows Authenticator to get UIDs of packages which registered to receive updates about given account type.*/
- int[] getRequestingUidsForType(String accountType);
+ /* Returns Map<Integer, Integer> from UID to visibility with all values stored for given account*/
+ Map getUidsAndVisibilityForAccount(in Account account);
boolean addAccountExplicitlyWithVisibility(in Account account, String password, in Bundle extras,
in Map visibility);
@@ -117,7 +117,7 @@
boolean setAccountVisibility(in Account a, int uid, int newVisibility);
int getAccountVisibility(in Account a, int uid);
- /* Type may be null returns Map <Account, Integer>*/
+ /* Type may be null returns Map <Account, Integer>*/
Map getAccountsAndVisibilityForPackage(in String packageName, in String accountType);
/* Check if the package in a user can access an account */
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 0a6c62f..bfa3f04 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -63,8 +63,8 @@
import android.content.pm.Signature;
import android.content.pm.UserInfo;
import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
+import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
@@ -76,6 +76,7 @@
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.os.StrictMode;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
@@ -111,15 +112,16 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
-import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
@@ -174,17 +176,18 @@
private static final String PRE_N_DATABASE_NAME = "accounts.db";
private static final Intent ACCOUNTS_CHANGED_INTENT;
+ private static final int SIGNATURE_CHECK_MISMATCH = 0;
+ private static final int SIGNATURE_CHECK_MATCH = 1;
+ private static final int SIGNATURE_CHECK_UID_MATCH = 2;
+
static {
ACCOUNTS_CHANGED_INTENT = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
}
-
private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>();
private final AtomicInteger mNotificationIds = new AtomicInteger(1);
- private static final String NEW_ACCOUNT_VISIBLE = "android.accounts.NEW_ACCOUNT_VISIBLE";
-
static class UserAccounts {
private final int userId;
final AccountsDb accountsDb;
@@ -204,6 +207,11 @@
/** protected by the {@link #cacheLock} */
private final TokenCache accountTokenCaches = new TokenCache();
+ /** protected by the {@link #cacheLock} */
+ // TODO use callback to set up the map.
+ private final Map<String, LinkedHashSet<String>> mApplicationAccountRequestMappings =
+ new HashMap<>();
+
/**
* protected by the {@link #cacheLock}
*
@@ -261,8 +269,6 @@
sThis.set(this);
- addRequestsForPreInstalledApplications();
-
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
intentFilter.addDataScheme("package");
@@ -285,7 +291,7 @@
@Override
public void run() {
purgeOldGrantsAll();
- // TODO remove visibility entries.
+ // Notify authenticator about removed app?
}
};
mHandler.post(purgingRunnable);
@@ -294,29 +300,6 @@
}
}, intentFilter);
- IntentFilter packageAddedOrChangedFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- packageAddedOrChangedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- packageAddedOrChangedFilter.addDataScheme("package");
- mContext.registerReceiverAsUser(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context1, Intent intent) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- int uidOfInstalledApplication =
- intent.getIntExtra(Intent.EXTRA_UID, -1);
- if(uidOfInstalledApplication != -1) {
- registerAccountTypesSupported(
- uidOfInstalledApplication,
- getUserAccounts(
- UserHandle.getUserId(uidOfInstalledApplication)));
- }
- }
- });
- }
- }, UserHandle.ALL, packageAddedOrChangedFilter, null, null);
-
IntentFilter userFilter = new IntentFilter();
userFilter.addAction(Intent.ACTION_USER_REMOVED);
mContext.registerReceiverAsUser(new BroadcastReceiver() {
@@ -380,11 +363,13 @@
final long identity = Binder.clearCallingIdentity();
try {
for (String packageName : packageNames) {
- if (mPackageManager.checkPermission(
- Manifest.permission.GET_ACCOUNTS, packageName)
- != PackageManager.PERMISSION_GRANTED) {
- continue;
- }
+ // if app asked for permission we need to cancel notification even
+ // for O+ applications.
+ if (mPackageManager.checkPermission(
+ Manifest.permission.GET_ACCOUNTS,
+ packageName) != PackageManager.PERMISSION_GRANTED) {
+ continue;
+ }
if (accounts == null) {
accounts = getAccountsAsUser(null, userId, "android");
@@ -443,112 +428,376 @@
}
@Override
- public boolean addAccountExplicitlyWithVisibility(Account account, String password, Bundle extras,
- Map uidToVisibility) {
- // TODO implementation
- return false;
+ public boolean addAccountExplicitlyWithVisibility(Account account, String password,
+ Bundle extras, Map uidToVisibility) {
+ Bundle.setDefusable(extras, true);
+
+ final int callingUid = Binder.getCallingUid();
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "addAccountExplicitly: " + account + ", caller's uid " + callingUid
+ + ", pid " + Binder.getCallingPid());
+ }
+ Preconditions.checkNotNull(account, "account cannot be null");
+ int userId = UserHandle.getCallingUserId();
+ if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
+ String msg = String.format("uid %s cannot explicitly add accounts of type: %s",
+ callingUid, account.type);
+ throw new SecurityException(msg);
+ }
+ /*
+ * Child users are not allowed to add accounts. Only the accounts that are shared by the
+ * parent profile can be added to child profile.
+ *
+ * TODO: Only allow accounts that were shared to be added by a limited user.
+ */
+ // fails if the account already exists
+ long identityToken = clearCallingIdentity();
+ try {
+ UserAccounts accounts = getUserAccounts(userId);
+ return addAccountInternal(accounts, account, password, extras, callingUid,
+ (Map<Integer, Integer>) uidToVisibility);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
}
@Override
public Map<Account, Integer> getAccountsAndVisibilityForPackage(String packageName,
String accountType) {
- // TODO Implement.
- return new HashMap<Account, Integer>();
+ int callingUid = Binder.getCallingUid();
+ boolean isSystemUid = UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
+ List<String> managedTypes =
+ getTypesForCaller(callingUid, UserHandle.getUserId(callingUid), isSystemUid);
+
+ if ((accountType != null && !managedTypes.contains(accountType))
+ || (accountType == null && !isSystemUid)) {
+ throw new SecurityException(
+ "getAccountsAndVisibilityForPackage() called from unauthorized uid "
+ + callingUid + " with packageName=" + packageName);
+ }
+ if (accountType != null) {
+ managedTypes = new ArrayList<String>();
+ managedTypes.add(accountType);
+ }
+
+ return getAccountsAndVisibilityForPackage(packageName, managedTypes, callingUid,
+ getUserAccounts(UserHandle.getUserId(callingUid)));
+ }
+
+ /*
+ * accountTypes may not be null
+ */
+ private Map<Account, Integer> getAccountsAndVisibilityForPackage(String packageName,
+ List<String> accountTypes, Integer callingUid, UserAccounts accounts) {
+ int uid = 0;
+ try {
+ uid = mPackageManager.getPackageUidAsUser(packageName,
+ UserHandle.getUserId(callingUid));
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "Package not found " + e.getMessage());
+ return new HashMap<>();
+ }
+
+ Map<Account, Integer> result = new HashMap<>();
+ for (String accountType : accountTypes) {
+ synchronized (accounts.cacheLock) {
+ final Account[] accountsOfType = accounts.accountCache.get(accountType);
+ if (accountsOfType != null) {
+ for (Account account : accountsOfType) {
+ result.put(account,
+ resolveAccountVisibility(account, uid, packageName, accounts));
+ }
+ }
+ }
+ }
+ return filterSharedAccounts(accounts, result, callingUid, packageName);
}
@Override
- public int[] getRequestingUidsForType(String accountType) {
+ public Map<Integer, Integer> getUidsAndVisibilityForAccount(Account account) {
+ if (account == null) throw new IllegalArgumentException("account is null");
int callingUid = Binder.getCallingUid();
- if (!isAccountManagedByCaller(accountType, callingUid, UserHandle.getUserId(callingUid))) {
- String msg = String.format(
- "uid %s cannot get secrets for accounts of type: %s",
- callingUid,
- accountType);
+ int userId = UserHandle.getUserId(callingUid);
+ UserAccounts accounts = getUserAccounts(userId);
+ if (!isAccountManagedByCaller(account.type, callingUid, userId)
+ && !isSystemUid(callingUid)) {
+ String msg =
+ String.format("uid %s cannot get secrets for account %s", callingUid, account);
throw new SecurityException(msg);
}
- // TODO Implement.
- return new int[]{};
+ return getUidsAndVisibilityForAccount(account, accounts);
+ }
+
+ /**
+ * Returns all UIDs and visibility values, which were set for given account
+ *
+ * @param account account
+ * @param accounts UserAccount that currently hosts the account and application
+ *
+ * @return Map from uid to visibility.
+ */
+ private Map<Integer, Integer> getUidsAndVisibilityForAccount(Account account,
+ UserAccounts accounts) {
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+ try {
+ return accounts.accountsDb.findAllVisibilityValuesForAccount(account);
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+
}
@Override
public int getAccountVisibility(Account a, int uid) {
- // TODO Implement.
- return 0;
+ if (a == null) throw new IllegalArgumentException("account is null");
+ int callingUid = Binder.getCallingUid();
+ if (!isAccountManagedByCaller(a.type, callingUid, UserHandle.getUserId(callingUid))
+ && !isSystemUid(callingUid)) {
+ String msg = String.format(
+ "uid %s cannot get secrets for accounts of type: %s",
+ callingUid,
+ a.type);
+ throw new SecurityException(msg);
+ }
+ return getAccountVisibility(a, uid, getUserAccounts(UserHandle.getUserId(callingUid)));
+ }
+
+ /**
+ * Method gets visibility for given account and UID from the database
+ *
+ * @param account The account to check visibility of
+ * @param uid UID to check visibility of
+ * @param accounts UserAccount that currently hosts the account and application
+ *
+ * @return Visibility value, AccountManager.VISIBILITY_UNDEFINED if no value was stored.
+ *
+ */
+ private int getAccountVisibility(Account account, int uid, UserAccounts accounts) {
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+ try {
+ Integer visibility = accounts.accountsDb.findAccountVisibility(account, uid);
+ return visibility != null ? visibility : AccountManager.VISIBILITY_UNDEFINED;
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+
+ /**
+ * Method which handles default values for Account visibility.
+ *
+ * @param account The account to check visibility.
+ * @param uid UID to check visibility.
+ * @param packageName Package name to check visibility - the method assumes that it has the same
+ * uid as specified in the parameter.
+ * @param accounts UserAccount that currently hosts the account and application
+ *
+ * @return Visibility value, the method never returns AccountManager.VISIBILITY_UNDEFINED
+ *
+ */
+ private Integer resolveAccountVisibility(Account account, int uid, String packageName,
+ UserAccounts accounts) {
+ if (packageName == null) {
+ packageName = getPackageNameForUid(uid);
+ }
+ // System visibility can not be restricted.
+ if (UserHandle.isSameApp(uid, Process.SYSTEM_UID)) {
+ return AccountManager.VISIBILITY_VISIBLE;
+ }
+
+ int signatureCheckResult =
+ checkPackageSignature(account.type, uid, accounts.userId);
+
+ // Authenticator can not restrict visibility to itself.
+ if (signatureCheckResult == SIGNATURE_CHECK_UID_MATCH) {
+ return AccountManager.VISIBILITY_VISIBLE; // Authenticator can always see the account
+ }
+
+ // Return stored value if it was set.
+ int visibility = getAccountVisibility(account, uid, accounts);
+
+ if (AccountManager.VISIBILITY_UNDEFINED != visibility) {
+ return visibility;
+ }
+
+ if (isPermittedForPackage(packageName, accounts.userId,
+ Manifest.permission.GET_ACCOUNTS_PRIVILEGED)) {
+ return AccountManager.VISIBILITY_VISIBLE;
+
+ }
+ // Profile owner gets visibility by default.
+ if(isProfileOwner(uid)) {
+ return AccountManager.VISIBILITY_VISIBLE;
+ }
+ // Apps with READ_CONTACTS permission get visibility by default even post O.
+ boolean canReadContacts = checkReadContactsPermission(packageName, accounts.userId);
+
+ boolean preO = isPreOApplication(packageName);
+ if ((signatureCheckResult != SIGNATURE_CHECK_MISMATCH)
+ || (preO && checkGetAccountsPermission(packageName, accounts.userId))
+ || canReadContacts) {
+ // Use legacy for preO apps with GET_ACCOUNTS permission or pre/postO with signature
+ // match.
+ visibility = getAccountVisibility(account,
+ AccountManager.UID_KEY_DEFAULT_LEGACY_VISIBILITY, accounts);
+ if (AccountManager.VISIBILITY_UNDEFINED == visibility) {
+ visibility = AccountManager.VISIBILITY_USER_MANAGED_VISIBLE;
+ }
+ } else {
+ visibility = getAccountVisibility(account, AccountManager.UID_KEY_DEFAULT_VISIBILITY,
+ accounts);
+ if (AccountManager.VISIBILITY_UNDEFINED == visibility) {
+ visibility = AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE;
+ }
+ }
+ return visibility;
+ }
+
+ /**
+ * Checks targetSdk for a package;
+ *
+ * @param packageName Package Name
+ *
+ * @return True if package's target SDK is below {@link android.os.Build.VERSION_CODES#O}, or
+ * undefined
+ */
+ private boolean isPreOApplication(String packageName) {
+ try {
+ long identityToken = clearCallingIdentity();
+ ApplicationInfo applicationInfo;
+ try {
+ applicationInfo = mPackageManager.getApplicationInfo(packageName, 0);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+
+ if (applicationInfo != null) {
+ int version = applicationInfo.targetSdkVersion;
+ return version < android.os.Build.VERSION_CODES.O;
+ }
+ return true;
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "Package not found " + e.getMessage());
+ return true;
+ }
}
@Override
- public boolean setAccountVisibility(Account a, int uid, int visibility) {
- // TODO Implement.
- return false;
- }
-
- /**
- * Registers the requested login account types requested by all the applications already
- * installed on the device.
- */
- private void addRequestsForPreInstalledApplications() {
- List<PackageInfo> allInstalledPackages = mPackageManager.getInstalledPackages(0);
- for(PackageInfo pi : allInstalledPackages) {
- int currentUid = pi.applicationInfo.uid;
- if(currentUid != -1) {
- registerAccountTypesSupported(currentUid,
- getUserAccounts(UserHandle.getUserId(currentUid)));
- }
+ public boolean setAccountVisibility(Account a, int uid, int newVisibility) {
+ if (a == null) throw new IllegalArgumentException("account is null");
+ int callingUid = Binder.getCallingUid();
+ if (!isAccountManagedByCaller(a.type, callingUid, UserHandle.getUserId(callingUid))
+ && !isSystemUid(callingUid)) {
+ String msg = String.format(
+ "uid %s cannot get secrets for accounts of type: %s",
+ callingUid,
+ a.type);
+ throw new SecurityException(msg);
}
+ return setAccountVisibility(a, uid, newVisibility, true /* notify */,
+ getUserAccounts(UserHandle.getUserId(callingUid)));
}
/**
- * Registers an application, represented by a UID, to support account types detailed in the
- * applications manifest as well as allowing it to opt for notifications.
+ * Gives a certain UID, represented a application, access to an account. This method
+ * is called indirectly by the Authenticator.
*
- * @param uid UID of application
- * @param ua UserAccount that currently hosts the account and application
+ * @param account Account to update visibility
+ * @param uid to add visibility of the Account
+ * @param newVisibility new visibility
+ * @param notify if the flag is set applications will get notification about visibility change
+ * @param accounts UserAccount that currently hosts the account and application
+ *
+ * @return True if account visibility was changed.
*/
- private void registerAccountTypesSupported(int uid, UserAccounts ua) {
- return;
- // TODO clean up the code, manifest entry is deprecated
- /*
- String interestedPackages = null;
- try {
- String[] allPackages = mPackageManager.getPackagesForUid(uid);
- if (allPackages != null) {
- for (String aPackage : allPackages) {
- ApplicationInfo ai = mPackageManager.getApplicationInfo(aPackage,
- PackageManager.GET_META_DATA);
- Bundle b = ai.metaData;
- if (b == null) {
- return;
+ private boolean setAccountVisibility(Account account, int uid, int newVisibility,
+ boolean notify, UserAccounts accounts) {
+ synchronized (accounts.cacheLock) {
+ LinkedHashSet<String> interestedPackages;
+ if (notify) {
+ if (uid < 0) {
+ interestedPackages = getRequestingPackageNames(account.type, accounts);
+ } else {
+ interestedPackages = new LinkedHashSet<>();
+ String[] subPackages = mPackageManager.getPackagesForUid(uid);
+ if (subPackages != null) {
+ Collections.addAll(interestedPackages, subPackages);
}
- interestedPackages = b.getString(AccountManager.SUPPORTED_ACCOUNT_TYPES);
+ }
+ } else {
+ // Notifications will not be send.
+ interestedPackages = new LinkedHashSet<>();
+ }
+ Integer[] interestedPackagesVisibility = new Integer[interestedPackages.size()];
+
+ final long accountId = accounts.accountsDb.findDeAccountId(account);
+ if (accountId < 0) {
+ return false;
+ }
+ int index = 0;
+ for (String packageName : interestedPackages) {
+ interestedPackagesVisibility[index++] =
+ resolveAccountVisibility(account, uid, packageName, accounts);
+ }
+
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+ try {
+ if (!accounts.accountsDb.setAccountVisibility(accountId, uid, newVisibility)) {
+ return false;
+ }
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+
+ index = 0;
+ for (String packageName : interestedPackages) {
+ int visibility = resolveAccountVisibility(account, uid, packageName, accounts);
+ if (visibility != interestedPackagesVisibility[index++]) {
+ sendNotification(packageName, account, accounts.userId);
}
}
- } catch (PackageManager.NameNotFoundException e) {
- Log.d("NameNotFoundException", e.getMessage());
+ if (notify) {
+ sendAccountsChangedBroadcast(accounts.userId);
+ }
+ return true;
}
- if (interestedPackages != null) {
- // TODO request visibility
- // requestAccountVisibility(interestedPackages.split(";"), uid, ua);
- }
- */
}
/**
* Sends a direct intent to a package, notifying it of a visible account change.
*
- * @param desiredPackage to send Account to
- * @param visibleAccount to send to package
+ * @param packageName to send Account to
+ * @param account to send to package
+ * @param userId User
*/
- private void sendNotification(String desiredPackage, Account visibleAccount) {
- // TODO replace with callback
- /*
- Intent intent = new Intent();
- intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
- intent.setAction(AccountManager.ACTION_VISIBLE_ACCOUNTS_CHANGED);
- intent.setPackage(desiredPackage);
- // TODO update documentation, add account extra if new account became visible
- // intent.putExtra("android.accounts.KEY_ACCOUNT", (Account) visibleAccount);
- mContext.sendBroadcast(intent);
- */
+ private void sendNotification(String packageName, Account account, int userId) {
+ // TODO send notification so apps subscribed in runtime.
+ }
+
+ private void sendNotification(Account account, UserAccounts accounts) {
+ LinkedHashSet<String> interestedPackages = getRequestingPackageNames(account.type,
+ accounts);
+ for (String packageName : interestedPackages) {
+ try {
+ final int uid = mPackageManager.getPackageUidAsUser(packageName, accounts.userId);
+ int visibility = resolveAccountVisibility(account, uid, packageName, accounts);
+ if (visibility != AccountManager.VISIBILITY_NOT_VISIBLE) {
+ sendNotification(packageName, account, accounts.userId);
+ }
+ } catch (NameNotFoundException e) {
+ // ignore
+ }
+ }
+ }
+
+ LinkedHashSet<String> getRequestingPackageNames(String accountType, UserAccounts accouns) {
+ // TODO return packages registered to get notifications.
+ return new LinkedHashSet<String>();
+ }
+
+ private void sendAccountsChangedBroadcast(int userId) {
+ Log.i(TAG, "the accounts changed, sending broadcast of "
+ + ACCOUNTS_CHANGED_INTENT.getAction());
+ mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId));
}
@Override
@@ -688,6 +937,12 @@
accounts.userDataCache.remove(account);
accounts.authTokenCache.remove(account);
accounts.accountTokenCaches.remove(account);
+ LinkedHashSet<String> interestedPackages =
+ getRequestingPackageNames(account.type, accounts);
+ for (String packageName : interestedPackages) {
+ sendNotification(packageName, null, accounts.userId);
+ }
+
} else {
ArrayList<String> accountNames = accountNamesByType.get(account.type);
if (accountNames == null) {
@@ -792,7 +1047,7 @@
AccountsDb.TABLE_ACCOUNTS);
for (Account account : accountsToRemove) {
- removeAccountInternal(accounts, account, Process.myUid());
+ removeAccountInternal(accounts, account, Process.SYSTEM_UID);
}
}
}
@@ -1049,7 +1304,7 @@
private boolean isCrossUser(int callingUid, int userId) {
return (userId != UserHandle.getCallingUserId()
- && callingUid != Process.myUid()
+ && callingUid != Process.SYSTEM_UID
&& mContext.checkCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
!= PackageManager.PERMISSION_GRANTED);
@@ -1057,42 +1312,7 @@
@Override
public boolean addAccountExplicitly(Account account, String password, Bundle extras) {
- Bundle.setDefusable(extras, true);
- // clears the visible list functionality for this account because this method allows
- // default account access to all applications for account.
-
- final int callingUid = Binder.getCallingUid();
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "addAccountExplicitly: " + account
- + ", caller's uid " + callingUid
- + ", pid " + Binder.getCallingPid());
- }
- if (account == null) throw new IllegalArgumentException("account is null");
- int userId = UserHandle.getCallingUserId();
- if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
- String msg = String.format(
- "uid %s cannot explicitly add accounts of type: %s",
- callingUid,
- account.type);
- throw new SecurityException(msg);
- }
-
- /*
- * Child users are not allowed to add accounts. Only the accounts that are
- * shared by the parent profile can be added to child profile.
- *
- * TODO: Only allow accounts that were shared to be added by
- * a limited user.
- */
-
- // fails if the account already exists
- long identityToken = clearCallingIdentity();
- try {
- UserAccounts accounts = getUserAccounts(userId);
- return addAccountInternal(accounts, account, password, extras, callingUid);
- } finally {
- restoreCallingIdentity(identityToken);
- }
+ return addAccountExplicitlyWithVisibility(account, password, extras, null);
}
@Override
@@ -1233,6 +1453,8 @@
// TODO: Anything to do if if succedded?
// TODO: If it failed: Show error notification? Should we remove the shadow
// account to avoid retries?
+ // TODO: what we do with the visibility?
+
super.onResult(result);
}
@@ -1250,7 +1472,7 @@
}
private boolean addAccountInternal(UserAccounts accounts, Account account, String password,
- Bundle extras, int callingUid) {
+ Bundle extras, int callingUid, Map<Integer, Integer> uidToVisibility) {
Bundle.setDefusable(extras, true);
if (account == null) {
return false;
@@ -1290,10 +1512,17 @@
}
}
}
+
+ if (uidToVisibility != null) {
+ for (Entry<Integer, Integer> entry : uidToVisibility.entrySet()) {
+ setAccountVisibility(account, entry.getKey() /* uid */,
+ entry.getValue() /* visibility */, false /* notify */, accounts);
+ }
+ }
accounts.accountsDb.setTransactionSuccessful();
- logRecord(AccountsDb.DEBUG_ACTION_ACCOUNT_ADD, AccountsDb.TABLE_ACCOUNTS,
- accountId, accounts, callingUid);
+ logRecord(AccountsDb.DEBUG_ACTION_ACCOUNT_ADD, AccountsDb.TABLE_ACCOUNTS, accountId,
+ accounts, callingUid);
insertAccountIntoCacheLocked(accounts, account);
} finally {
@@ -1304,8 +1533,10 @@
addAccountToLinkedRestrictedUsers(account, accounts.userId);
}
+ sendNotification(account, accounts);
// Only send LOGIN_ACCOUNTS_CHANGED when the database changed.
sendAccountsChangedBroadcast(accounts.userId);
+
return true;
}
@@ -1482,6 +1713,10 @@
synchronized (accounts.cacheLock) {
accounts.accountsDb.beginTransaction();
Account renamedAccount = new Account(newName, accountToRename.type);
+ if ((accounts.accountsDb.findCeAccountId(renamedAccount) >= 0)) {
+ Log.e(TAG, "renameAccount failed - account with new name already exists");
+ return null;
+ }
try {
final long accountId = accounts.accountsDb.findDeAccountId(accountToRename);
if (accountId >= 0) {
@@ -1493,6 +1728,9 @@
Log.e(TAG, "renameAccount failed");
return null;
}
+ } else {
+ Log.e(TAG, "renameAccount failed - old account does not exist");
+ return null;
}
} finally {
accounts.accountsDb.endTransaction();
@@ -1535,6 +1773,9 @@
}
}
}
+
+ // Notify authenticator.
+ sendNotification(resultAccount, accounts);
sendAccountsChangedBroadcast(accounts.userId);
}
return resultAccount;
@@ -1733,6 +1974,24 @@
+ " is still locked. CE data will be removed later");
}
synchronized (accounts.cacheLock) {
+ LinkedHashSet<String> interestedPackages =
+ accounts.mApplicationAccountRequestMappings.get(account.type);
+ if (interestedPackages == null) {
+ interestedPackages = new LinkedHashSet<>();
+ }
+ int[] visibilityForInterestedPackages = new int[interestedPackages.size()];
+ int index = 0;
+ for (String packageName : interestedPackages) {
+ int visibility = AccountManager.VISIBILITY_NOT_VISIBLE;
+ try {
+ final int uid = mPackageManager.getPackageUidAsUser(packageName,
+ UserHandle.getUserId(callingUid));
+ visibility = resolveAccountVisibility(account, uid, packageName, accounts);
+ } catch (NameNotFoundException e) {
+ // ignore
+ }
+ visibilityForInterestedPackages[index++] = visibility;
+ }
accounts.accountsDb.beginTransaction();
// Set to a dummy value, this will only be used if the database
// transaction succeeds.
@@ -1756,6 +2015,17 @@
}
if (isChanged) {
removeAccountFromCacheLocked(accounts, account);
+ index = 0;
+ for (String packageName : interestedPackages) {
+ if ((visibilityForInterestedPackages[index]
+ != AccountManager.VISIBILITY_NOT_VISIBLE)
+ && (visibilityForInterestedPackages[index]
+ != AccountManager.VISIBILITY_UNDEFINED)) {
+ sendNotification(packageName, account, accounts.userId);
+ }
+ ++index;
+ }
+
// Only broadcast LOGIN_ACCOUNTS_CHANGED if a change occured.
sendAccountsChangedBroadcast(accounts.userId);
String action = userUnlocked ? AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE
@@ -2022,18 +2292,13 @@
accounts.accountsDb.endTransaction();
if (isChanged) {
// Send LOGIN_ACCOUNTS_CHANGED only if the something changed.
+ sendNotification(account, accounts);
sendAccountsChangedBroadcast(accounts.userId);
}
}
}
}
- private void sendAccountsChangedBroadcast(int userId) {
- Log.i(TAG, "the accounts changed, sending broadcast of "
- + ACCOUNTS_CHANGED_INTENT.getAction());
- mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId));
- }
-
@Override
public void clearPassword(Account account) {
final int callingUid = Binder.getCallingUid();
@@ -2412,8 +2677,10 @@
checkKeyIntent(
Binder.getCallingUid(),
intent);
- doNotification(mAccounts,
- account, result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE),
+ doNotification(
+ mAccounts,
+ account,
+ result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE),
intent, "android", accounts.userId);
}
}
@@ -3298,7 +3565,8 @@
if (response == null) throw new IllegalArgumentException("response is null");
if (accountType == null) throw new IllegalArgumentException("accountType is null");
int userId = UserHandle.getCallingUserId();
- if (!isAccountManagedByCaller(accountType, callingUid, userId) && !isSystemUid(callingUid)) {
+ if (!isAccountManagedByCaller(accountType, callingUid, userId)
+ && !isSystemUid(callingUid)) {
String msg = String.format(
"uid %s cannot edit authenticator properites for account type: %s",
callingUid,
@@ -3341,23 +3609,50 @@
Preconditions.checkArgumentInRange(userId, 0, Integer.MAX_VALUE, "user must be concrete");
try {
- final int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
+ int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
return hasAccountAccess(account, packageName, uid);
} catch (NameNotFoundException e) {
+ Log.d(TAG, "Package not found " + e.getMessage());
return false;
}
}
+ // Returns package with oldest target SDK for given UID.
+ private String getPackageNameForUid(int uid) {
+ String[] packageNames = mPackageManager.getPackagesForUid(uid);
+ if (ArrayUtils.isEmpty(packageNames)) {
+ return null;
+ }
+ // For app op checks related to permissions all packages in the UID
+ // have the same app op state, so doesn't matter which one we pick.
+ // Update: due to visibility changes we want to use package with oldest target SDK,
+
+ String packageName = packageNames[0];
+ int oldestVersion = Integer.MAX_VALUE;
+ for (String name : packageNames) {
+ try {
+ ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(name, 0);
+ if (applicationInfo != null) {
+ int version = applicationInfo.targetSdkVersion;
+ if (version < oldestVersion) {
+ oldestVersion = version;
+ packageName = name;
+ }
+ }
+ } catch (NameNotFoundException e) {
+ // skip
+ }
+ }
+ return packageName;
+ }
+
private boolean hasAccountAccess(@NonNull Account account, @Nullable String packageName,
int uid) {
if (packageName == null) {
- String[] packageNames = mPackageManager.getPackagesForUid(uid);
- if (ArrayUtils.isEmpty(packageNames)) {
+ packageName = getPackageNameForUid(uid);
+ if (packageName == null) {
return false;
}
- // For app op checks related to permissions all packages in the UID
- // have the same app op state, so doesn't matter which one we pick.
- packageName = packageNames[0];
}
// Use null token which means any token. Having a token means the package
@@ -3366,27 +3661,12 @@
return true;
}
// In addition to the permissions required to get an auth token we also allow
- // the account to be accessed by holders of the get accounts permissions.
- return checkUidPermission(Manifest.permission.GET_ACCOUNTS_PRIVILEGED, uid, packageName)
- || checkUidPermission(Manifest.permission.GET_ACCOUNTS, uid, packageName);
- }
+ // the account to be accessed by apps for which user or authenticator granted visibility.
- private boolean checkUidPermission(String permission, int uid, String opPackageName) {
- final long identity = Binder.clearCallingIdentity();
- try {
- IPackageManager pm = ActivityThread.getPackageManager();
- if (pm.checkUidPermission(permission, uid) != PackageManager.PERMISSION_GRANTED) {
- return false;
- }
- final int opCode = AppOpsManager.permissionToOpCode(permission);
- return (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOpNoThrow(
- opCode, uid, opPackageName) == AppOpsManager.MODE_ALLOWED);
- } catch (RemoteException e) {
- /* ignore - local call */
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- return false;
+ int visibility = resolveAccountVisibility(account, uid, packageName,
+ getUserAccounts(UserHandle.getUserId(uid)));
+ return (visibility == AccountManager.VISIBILITY_VISIBLE
+ || visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
}
@Override
@@ -3482,21 +3762,28 @@
private volatile ArrayList<Account> mAccountsWithFeatures = null;
private volatile int mCurrentAccount = 0;
private final int mCallingUid;
+ private final String mPackageName;
- public GetAccountsByTypeAndFeatureSession(UserAccounts accounts,
- IAccountManagerResponse response, String type, String[] features, int callingUid) {
+ public GetAccountsByTypeAndFeatureSession(
+ UserAccounts accounts,
+ IAccountManagerResponse response,
+ String type,
+ String[] features,
+ int callingUid,
+ String packageName) {
super(accounts, response, type, false /* expectActivityLaunch */,
true /* stripAuthTokenFromResult */, null /* accountName */,
false /* authDetailsRequired */);
mCallingUid = callingUid;
mFeatures = features;
+ mPackageName = packageName;
}
@Override
public void run() throws RemoteException {
synchronized (mAccounts.cacheLock) {
mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType, mCallingUid,
- null);
+ mPackageName, false /* include managed not visible*/);
}
// check whether each account matches the requested features
mAccountsWithFeatures = new ArrayList<>(mAccountsOfType.length);
@@ -3569,7 +3856,6 @@
}
}
-
@Override
protected String toDebugString(long now) {
return super.toDebugString(now) + ", getAccountsByTypeAndFeatures"
@@ -3595,8 +3881,9 @@
return getAccountsInternal(
accounts,
callingUid,
- null, // packageName
- visibleAccountTypes);
+ opPackageName,
+ visibleAccountTypes,
+ false /* includeUserManagedNotVisible */);
} finally {
restoreCallingIdentity(identityToken);
}
@@ -3638,7 +3925,9 @@
if (userAccounts == null) continue;
synchronized (userAccounts.cacheLock) {
Account[] accounts = getAccountsFromCacheLocked(userAccounts, null,
- Binder.getCallingUid(), null);
+ Binder.getCallingUid(),
+ null,
+ false /* incl managed not visible*/);
for (int a = 0; a < accounts.length; a++) {
runningAccounts.add(new AccountAndUser(accounts[a], userId));
}
@@ -3652,7 +3941,9 @@
@Override
@NonNull
public Account[] getAccountsAsUser(String type, int userId, String opPackageName) {
- return getAccountsAsUser(type, userId, null, -1, opPackageName);
+ // Need calling package
+ return getAccountsAsUser(type, userId, null /* callingPackage */, -1, opPackageName,
+ false /* includeUserManagedNotVisible */);
}
@NonNull
@@ -3661,11 +3952,12 @@
int userId,
String callingPackage,
int packageUid,
- String opPackageName) {
+ String opPackageName,
+ boolean includeUserManagedNotVisible) {
int callingUid = Binder.getCallingUid();
// Only allow the system process to read accounts of other users
if (userId != UserHandle.getCallingUserId()
- && callingUid != Process.myUid()
+ && callingUid != Process.SYSTEM_UID
&& mContext.checkCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
!= PackageManager.PERMISSION_GRANTED) {
@@ -3678,9 +3970,15 @@
+ ", caller's uid " + Binder.getCallingUid()
+ ", pid " + Binder.getCallingPid());
}
- // If the original calling app was using the framework account chooser activity, we'll
- // be passed in the original caller's uid here, which is what should be used for filtering.
- if (packageUid != -1 && UserHandle.isSameApp(callingUid, Process.myUid())) {
+
+ // If the original calling app was using account choosing activity
+ // provided by the framework or authenticator we'll passing in
+ // the original caller's uid here, which is what should be used for filtering.
+ List<String> managedTypes =
+ getTypesManagedByCaller(callingUid, UserHandle.getUserId(callingUid));
+ if (packageUid != -1 &&
+ ((UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)
+ || (type != null && managedTypes.contains(type))))) {
callingUid = packageUid;
opPackageName = callingPackage;
}
@@ -3688,7 +3986,7 @@
opPackageName);
if (visibleAccountTypes.isEmpty()
|| (type != null && !visibleAccountTypes.contains(type))) {
- return new Account[0];
+ return new Account[]{};
} else if (visibleAccountTypes.contains(type)) {
// Prune the list down to just the requested type.
visibleAccountTypes = new ArrayList<>();
@@ -3703,7 +4001,8 @@
accounts,
callingUid,
callingPackage,
- visibleAccountTypes);
+ visibleAccountTypes,
+ includeUserManagedNotVisible);
} finally {
restoreCallingIdentity(identityToken);
}
@@ -3714,12 +4013,14 @@
UserAccounts userAccounts,
int callingUid,
String callingPackage,
- List<String> visibleAccountTypes) {
+ List<String> visibleAccountTypes,
+ boolean includeUserManagedNotVisible) {
synchronized (userAccounts.cacheLock) {
ArrayList<Account> visibleAccounts = new ArrayList<>();
for (String visibleType : visibleAccountTypes) {
Account[] accountsForType = getAccountsFromCacheLocked(
- userAccounts, visibleType, callingUid, callingPackage);
+ userAccounts, visibleType, callingUid, callingPackage,
+ includeUserManagedNotVisible);
if (accountsForType != null) {
visibleAccounts.addAll(Arrays.asList(accountsForType));
}
@@ -3810,29 +4111,34 @@
@NonNull
public Account[] getAccountsForPackage(String packageName, int uid, String opPackageName) {
int callingUid = Binder.getCallingUid();
- if (!UserHandle.isSameApp(callingUid, Process.myUid())) {
+ if (!UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)) {
throw new SecurityException("getAccountsForPackage() called from unauthorized uid "
+ callingUid + " with uid=" + uid);
}
return getAccountsAsUser(null, UserHandle.getCallingUserId(), packageName, uid,
- opPackageName);
+ opPackageName, true /* includeUserManagedNotVisible */);
}
@Override
@NonNull
public Account[] getAccountsByTypeForPackage(String type, String packageName,
String opPackageName) {
+ int callingUid = Binder.getCallingUid();
+ int userId = UserHandle.getCallingUserId();
int packageUid = -1;
try {
- packageUid = AppGlobals.getPackageManager().getPackageUid(
- packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES,
- UserHandle.getCallingUserId());
- } catch (RemoteException re) {
+ packageUid = mPackageManager.getPackageUidAsUser(packageName, userId);
+ } catch (NameNotFoundException re) {
Slog.e(TAG, "Couldn't determine the packageUid for " + packageName + re);
return new Account[0];
}
- return getAccountsAsUser(type, UserHandle.getCallingUserId(), packageName,
- packageUid, opPackageName);
+ if (!UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)
+ && !isAccountManagedByCaller(type, callingUid, userId)) {
+ return new Account[0];
+ }
+
+ return getAccountsAsUser(type, userId,
+ packageName, packageUid, opPackageName, true /* includeUserManagedNotVisible */);
}
@Override
@@ -3872,7 +4178,8 @@
if (features == null || features.length == 0) {
Account[] accounts;
synchronized (userAccounts.cacheLock) {
- accounts = getAccountsFromCacheLocked(userAccounts, type, callingUid, null);
+ accounts = getAccountsFromCacheLocked(
+ userAccounts, type, callingUid, opPackageName, false);
}
Bundle result = new Bundle();
result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
@@ -3884,7 +4191,8 @@
response,
type,
features,
- callingUid).bind();
+ callingUid,
+ opPackageName).bind();
} finally {
restoreCallingIdentity(identityToken);
}
@@ -3903,6 +4211,7 @@
if (Objects.equals(account.getAccessId(), token)) {
// An app just accessed the account. At this point it knows about
// it and there is not need to hide this account from the app.
+ // Do we need to update account visibility here?
if (!hasAccountAccess(account, null, uid)) {
updateAppPermission(account, AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE,
uid, true);
@@ -4429,7 +4738,7 @@
userAccounts.accountsDb.dumpDeAccountsTable(fout);
} else {
Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */,
- Process.myUid(), null);
+ Process.SYSTEM_UID, null, false);
fout.println("Accounts: " + accounts.length);
for (Account account : accounts) {
fout.println(" " + account);
@@ -4522,6 +4831,24 @@
}
}
+ private boolean isPermittedForPackage(String packageName, int userId, String... permissions) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ IPackageManager pm = ActivityThread.getPackageManager();
+ for (String perm : permissions) {
+ if (pm.checkPermission(perm, packageName, userId)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ }
+ } catch (RemoteException e) {
+ /* ignore - local call */
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return false;
+ }
+
private boolean isPermitted(String opPackageName, int callingUid, String... permissions) {
for (String perm : permissions) {
if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) {
@@ -4556,6 +4883,7 @@
userPackageManager = mContext.createPackageContextAsUser(
"android", 0, new UserHandle(callingUserId)).getPackageManager();
} catch (NameNotFoundException e) {
+ Log.d(TAG, "Package not found " + e.getMessage());
return false;
}
@@ -4569,6 +4897,7 @@
return true;
}
} catch (PackageManager.NameNotFoundException e) {
+ Log.d(TAG, "Package not found " + e.getMessage());
return false;
}
}
@@ -4623,6 +4952,53 @@
}
}
+ // Method checks visibility for applications targeing API level below {@link
+ // android.os.Build.VERSION_CODES#O},
+ // returns true if the the app has GET_ACCOUNTS or GET_ACCOUNTS_PRIVELEGED permission.
+ private boolean checkGetAccountsPermission(String packageName, int userId) {
+ return isPermittedForPackage(packageName, userId, Manifest.permission.GET_ACCOUNTS,
+ Manifest.permission.GET_ACCOUNTS_PRIVILEGED);
+ }
+
+ private boolean checkReadContactsPermission(String packageName, int userId) {
+ return isPermittedForPackage(packageName, userId, Manifest.permission.READ_CONTACTS);
+ }
+
+ /**
+ * Method checks package uid and signature with Authenticator which manages accountType.
+ *
+ * @return SIGNATURE_CHECK_UID_MATCH for uid match, SIGNATURE_CHECK_MATCH for signature match,
+ * SIGNATURE_CHECK_MISMATCH otherwise.
+ */
+ private int checkPackageSignature(String accountType, int callingUid, int userId) {
+ if (accountType == null) {
+ return SIGNATURE_CHECK_MISMATCH;
+ }
+
+ long identityToken = Binder.clearCallingIdentity();
+ Collection<RegisteredServicesCache.ServiceInfo<AuthenticatorDescription>> serviceInfos;
+ try {
+ serviceInfos = mAuthenticatorCache.getAllServices(userId);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ // Check for signature match with Authenticator.
+ for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo
+ : serviceInfos) {
+ if (accountType.equals(serviceInfo.type.type)) {
+ if (serviceInfo.uid == callingUid) {
+ return SIGNATURE_CHECK_UID_MATCH;
+ }
+ final int sigChk = mPackageManager.checkSignatures(serviceInfo.uid, callingUid);
+ if (sigChk == PackageManager.SIGNATURE_MATCH) {
+ return SIGNATURE_CHECK_MATCH;
+ }
+ }
+ }
+ return SIGNATURE_CHECK_MISMATCH;
+ }
+
+ // returns true for applications with the same signature as authenticator.
private boolean isAccountManagedByCaller(String accountType, int callingUid, int userId) {
if (accountType == null) {
return false;
@@ -4633,10 +5009,7 @@
private List<String> getTypesVisibleToCaller(int callingUid, int userId,
String opPackageName) {
- boolean isPermitted =
- isPermitted(opPackageName, callingUid, Manifest.permission.GET_ACCOUNTS,
- Manifest.permission.GET_ACCOUNTS_PRIVILEGED);
- return getTypesForCaller(callingUid, userId, isPermitted);
+ return getTypesForCaller(callingUid, userId, true /* isOtherwisePermitted*/);
}
private List<String> getTypesManagedByCaller(int callingUid, int userId) {
@@ -4655,8 +5028,8 @@
}
for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo :
serviceInfos) {
- final int sigChk = mPackageManager.checkSignatures(serviceInfo.uid, callingUid);
- if (isOtherwisePermitted || sigChk == PackageManager.SIGNATURE_MATCH) {
+ if (isOtherwisePermitted || (mPackageManager.checkSignatures(serviceInfo.uid,
+ callingUid) == PackageManager.SIGNATURE_MATCH)) {
managedAccountTypes.add(serviceInfo.type.type);
}
}
@@ -4738,7 +5111,7 @@
!= 0) {
return true;
}
- } catch (PackageManager.NameNotFoundException e) {
+ } catch (NameNotFoundException e) {
Log.w(TAG, String.format("Could not find package [%s]", name), e);
}
}
@@ -4929,15 +5302,41 @@
return newAccountsForType[oldLength];
}
- private Account[] filterSharedAccounts(UserAccounts userAccounts, Account[] unfiltered,
- int callingUid, String callingPackage) {
+ private Account[] filterAccounts(UserAccounts accounts, Account[] unfiltered, int callingUid,
+ String callingPackage, boolean includeManagedNotVisible) {
+ // filter based on visibility.
+ Map<Account, Integer> firstPass = new HashMap<>();
+ for (Account account : unfiltered) {
+ int visibility =
+ resolveAccountVisibility(account, callingUid, callingPackage, accounts);
+ if ((visibility == AccountManager.VISIBILITY_VISIBLE
+ || visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE)
+ || (includeManagedNotVisible
+ && (visibility
+ == AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE))) {
+ firstPass.put(account, visibility);
+ }
+ }
+ Map<Account, Integer> secondPass =
+ filterSharedAccounts(accounts, firstPass, callingUid, callingPackage);
+
+ Account[] filtered = new Account[secondPass.size()];
+ filtered = secondPass.keySet().toArray(filtered);
+ return filtered;
+ }
+
+ private Map<Account, Integer> filterSharedAccounts(UserAccounts userAccounts,
+ Map<Account, Integer> unfiltered, int callingUid, String callingPackage) {
+ // first part is to filter shared accounts.
+ // unfiltered type check is not necessary.
if (getUserManager() == null || userAccounts == null || userAccounts.userId < 0
- || callingUid == Process.myUid()) {
+ || callingUid == Process.SYSTEM_UID) {
return unfiltered;
}
UserInfo user = getUserManager().getUserInfo(userAccounts.userId);
if (user != null && user.isRestricted()) {
- String[] packages = mPackageManager.getPackagesForUid(callingUid);
+ String[] packages =
+ mPackageManager.getPackagesForUid(callingUid);
// If any of the packages is a visible listed package, return the full set,
// otherwise return non-shared accounts only.
// This might be a temporary way to specify a visible list
@@ -4948,9 +5347,10 @@
return unfiltered;
}
}
- ArrayList<Account> allowed = new ArrayList<>();
Account[] sharedAccounts = getSharedAccountsAsUser(userAccounts.userId);
- if (sharedAccounts == null || sharedAccounts.length == 0) return unfiltered;
+ if (ArrayUtils.isEmpty(sharedAccounts)) {
+ return unfiltered;
+ }
String requiredAccountType = "";
try {
// If there's an explicit callingPackage specified, check if that package
@@ -4970,11 +5370,14 @@
}
}
}
- } catch (NameNotFoundException nnfe) {
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "Package not found " + e.getMessage());
}
- for (Account account : unfiltered) {
+ Map<Account, Integer> filtered = new HashMap<>();
+ for (Map.Entry<Account, Integer> entry : unfiltered.entrySet()) {
+ Account account = entry.getKey();
if (account.type.equals(requiredAccountType)) {
- allowed.add(account);
+ filtered.put(account, entry.getValue());
} else {
boolean found = false;
for (Account shared : sharedAccounts) {
@@ -4984,12 +5387,10 @@
}
}
if (!found) {
- allowed.add(account);
+ filtered.put(account, entry.getValue());
}
}
}
- Account[] filtered = new Account[allowed.size()];
- allowed.toArray(filtered);
return filtered;
} else {
return unfiltered;
@@ -5001,14 +5402,14 @@
* that the package is not allowed to access.
*/
protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType,
- int callingUid, String callingPackage) {
+ int callingUid, String callingPackage, boolean includeManagedNotVisible) {
if (accountType != null) {
final Account[] accounts = userAccounts.accountCache.get(accountType);
if (accounts == null) {
return EMPTY_ACCOUNT_ARRAY;
} else {
- return filterSharedAccounts(userAccounts, Arrays.copyOf(accounts, accounts.length),
- callingUid, callingPackage);
+ return filterAccounts(userAccounts, Arrays.copyOf(accounts, accounts.length),
+ callingUid, callingPackage, includeManagedNotVisible);
}
} else {
int totalLength = 0;
@@ -5025,7 +5426,8 @@
accountsOfType.length);
totalLength += accountsOfType.length;
}
- return filterSharedAccounts(userAccounts, accounts, callingUid, callingPackage);
+ return filterAccounts(userAccounts, accounts, callingUid, callingPackage,
+ includeManagedNotVisible);
}
}
@@ -5253,19 +5655,21 @@
if (userId == 0) {
// Migrate old file, if it exists, to the new location.
// Make sure the new file doesn't already exist. A dummy file could have been
- // accidentally created in the old location, causing the new one to become corrupted
- // as well.
+ // accidentally created in the old location,
+ // causing the new one to become corrupted as well.
File oldFile = new File(systemDir, PRE_N_DATABASE_NAME);
if (oldFile.exists() && !databaseFile.exists()) {
// Check for use directory; create if it doesn't exist, else renameTo will fail
File userDir = Environment.getUserSystemDirectory(userId);
if (!userDir.exists()) {
if (!userDir.mkdirs()) {
- throw new IllegalStateException("User dir cannot be created: " + userDir);
+ throw new IllegalStateException(
+ "User dir cannot be created: " + userDir);
}
}
if (!oldFile.renameTo(databaseFile)) {
- throw new IllegalStateException("User dir cannot be migrated: " + databaseFile);
+ throw new IllegalStateException(
+ "User dir cannot be migrated: " + databaseFile);
}
}
}
diff --git a/services/core/java/com/android/server/accounts/AccountsDb.java b/services/core/java/com/android/server/accounts/AccountsDb.java
index 5ca74711..85e4b5f 100644
--- a/services/core/java/com/android/server/accounts/AccountsDb.java
+++ b/services/core/java/com/android/server/accounts/AccountsDb.java
@@ -355,7 +355,7 @@
boolean deleteAuthtokensByAccountIdAndType(long accountId, String authtokenType) {
SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
return db.delete(CE_TABLE_AUTHTOKENS,
- AUTHTOKENS_ACCOUNTS_ID + "=?" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
+ AUTHTOKENS_ACCOUNTS_ID + "=?" + " AND " + AUTHTOKENS_TYPE + "=?",
new String[]{String.valueOf(accountId), authtokenType}) > 0;
}
@@ -946,12 +946,13 @@
/**
* Returns a map from uid to visibility value.
*/
- Map<Integer, Integer> findAccountVisibilityForAccountId(long accountId) {
+ Map<Integer, Integer> findAllVisibilityValuesForAccount(Account account) {
SQLiteDatabase db = mDeDatabase.getReadableDatabase();
Map<Integer, Integer> result = new HashMap<>();
- final Cursor cursor = db.query(TABLE_VISIBILITY,
- new String[] {VISIBILITY_UID, VISIBILITY_VALUE}, VISIBILITY_ACCOUNTS_ID + "=? ",
- new String[] {String.valueOf(accountId)}, null, null, null);
+ final Cursor cursor =
+ db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_UID, VISIBILITY_VALUE},
+ SELECTION_ACCOUNTS_ID_BY_ACCOUNT,
+ new String[] {account.name, account.type}, null, null, null);
try {
while (cursor.moveToNext()) {
result.put(cursor.getInt(0), cursor.getInt(1));
@@ -1306,4 +1307,4 @@
return new AccountsDb(deDatabaseHelper, context, preNDatabaseFile);
}
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index 969cb36..213fb27 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -1263,25 +1263,6 @@
}
@SmallTest
- public void testHasFeaturesReadAccountsNotPermitted() throws Exception {
- unlockSystemUser();
- when(mMockContext.checkCallingOrSelfPermission(anyString())).thenReturn(
- PackageManager.PERMISSION_DENIED);
- when(mMockPackageManager.checkSignatures(anyInt(), anyInt()))
- .thenReturn(PackageManager.SIGNATURE_NO_MATCH);
- try {
- mAms.hasFeatures(
- mMockAccountManagerResponse, // response
- AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, // account
- new String[] {"feature1", "feature2"}, // features
- "testPackage"); // opPackageName
- fail("SecurityException expected. But no exception was thrown.");
- } catch (SecurityException e) {
- // SecurityException is expected.
- }
- }
-
- @SmallTest
public void testHasFeaturesReturnNullResult() throws Exception {
unlockSystemUser();
final CountDownLatch latch = new CountDownLatch(1);
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
index 8591dae..2e045ff 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
@@ -374,7 +374,7 @@
mAccountsDb.setAccountVisibility(accId, uid2, 3);
assertEquals(mAccountsDb.findAccountVisibility(accId, uid2), Integer.valueOf(3));
- Map<Integer, Integer> vis = mAccountsDb.findAccountVisibilityForAccountId(accId);
+ Map<Integer, Integer> vis = mAccountsDb.findAllVisibilityValuesForAccount(account);
assertEquals(vis.size(), 2);
assertEquals(vis.get(uid1), Integer.valueOf(1));
assertEquals(vis.get(uid2), Integer.valueOf(3));