From 91d37f43c73d76a54679bb84a71a6339bccb1e24 Mon Sep 17 00:00:00 2001 From: Svet Ganov Date: Sat, 3 Sep 2016 19:36:26 -0700 Subject: [PATCH] [DO NOT MERGE] While-list apps to access account if already saw it Sync adapters without an account access cannot run until the user approves the account access (for the case the account access is not allowed by other policy such as being singed with the same cert as the authenticator). However, if the sync adapter package already got the account from another app which means it already saw the account we white-list the sync adapter app to access the account as it already saw it - the bird is out of the cage. bug:31162498 Change-Id: I2b72f3b0d6307561ed68db2f2e9c900b15e8d098 --- Android.mk | 1 + core/java/android/accounts/Account.java | 47 +++++++ .../java/android/accounts/AccountManager.java | 36 ++++-- .../accounts/AccountManagerInternal.java | 34 ++++- .../GrantCredentialsPermissionActivity.java | 2 +- .../accounts/IAccountAccessTracker.aidl | 26 ++++ .../java/android/content/ContentResolver.java | 1 + .../accounts/AccountManagerService.java | 117 +++++++++++++++--- .../server/content/ContentService.java | 4 +- .../android/server/content/SyncManager.java | 38 ++++-- .../server/content/SyncStorageEngine.java | 6 + 11 files changed, 265 insertions(+), 47 deletions(-) create mode 100644 core/java/android/accounts/IAccountAccessTracker.aidl diff --git a/Android.mk b/Android.mk index 4ca3e229d87b2..2d5285438392a 100644 --- a/Android.mk +++ b/Android.mk @@ -63,6 +63,7 @@ LOCAL_SRC_FILES += \ core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl \ core/java/android/accounts/IAccountManager.aidl \ core/java/android/accounts/IAccountManagerResponse.aidl \ + core/java/android/accounts/IAccountAccessTracker.aidl \ core/java/android/accounts/IAccountAuthenticator.aidl \ core/java/android/accounts/IAccountAuthenticatorResponse.aidl \ core/java/android/app/IActivityContainer.aidl \ diff --git a/core/java/android/accounts/Account.java b/core/java/android/accounts/Account.java index 7b83a3076db33..6c16e322e9784 100644 --- a/core/java/android/accounts/Account.java +++ b/core/java/android/accounts/Account.java @@ -16,9 +16,17 @@ package android.accounts; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcelable; import android.os.Parcel; +import android.os.RemoteException; import android.text.TextUtils; +import android.util.ArraySet; +import android.util.Log; +import com.android.internal.annotations.GuardedBy; + +import java.util.Set; /** * Value type that represents an Account in the {@link AccountManager}. This object is @@ -26,8 +34,14 @@ import android.text.TextUtils; * suitable for use as the key of a {@link java.util.Map} */ public class Account implements Parcelable { + private static final String TAG = "Account"; + + @GuardedBy("sAccessedAccounts") + private static final Set sAccessedAccounts = new ArraySet<>(); + public final String name; public final String type; + private final @Nullable IAccountAccessTracker mAccessTracker; public boolean equals(Object o) { if (o == this) return true; @@ -44,6 +58,20 @@ public class Account implements Parcelable { } public Account(String name, String type) { + this(name, type, null); + } + + /** + * @hide + */ + public Account(@NonNull Account other, @Nullable IAccountAccessTracker accessTracker) { + this(other.name, other.type, accessTracker); + } + + /** + * @hide + */ + public Account(String name, String type, IAccountAccessTracker accessTracker) { if (TextUtils.isEmpty(name)) { throw new IllegalArgumentException("the name must not be empty: " + name); } @@ -52,11 +80,29 @@ public class Account implements Parcelable { } this.name = name; this.type = type; + this.mAccessTracker = accessTracker; } public Account(Parcel in) { this.name = in.readString(); this.type = in.readString(); + this.mAccessTracker = IAccountAccessTracker.Stub.asInterface(in.readStrongBinder()); + if (mAccessTracker != null) { + synchronized (sAccessedAccounts) { + if (sAccessedAccounts.add(this)) { + try { + mAccessTracker.onAccountAccessed(); + } catch (RemoteException e) { + Log.e(TAG, "Error noting account access", e); + } + } + } + } + } + + /** @hide */ + public IAccountAccessTracker getAccessTracker() { + return mAccessTracker; } public int describeContents() { @@ -66,6 +112,7 @@ public class Account implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeString(type); + dest.writeStrongInterface(mAccessTracker); } public static final Creator CREATOR = new Creator() { diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index ed08a70bfd8d7..632e4b9436265 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -18,7 +18,6 @@ package android.accounts; import static android.Manifest.permission.GET_ACCOUNTS; -import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.Size; @@ -179,6 +178,14 @@ public class AccountManager { */ public static final String KEY_ACCOUNT_TYPE = "accountType"; + /** + * Bundle key used for the {@link IAccountAccessTracker} account access tracker + * used for noting the account was accessed when unmarshalled from a parcel. + * + * @hide + */ + public static final String KEY_ACCOUNT_ACCESS_TRACKER = "accountAccessTracker"; + /** * Bundle key used for the auth token value in results * from {@link #getAuthToken} and friends. @@ -268,13 +275,13 @@ public class AccountManager { public static final String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator"; /** - * Token for the special case where a UID has access only to an account - * but no authenticator specific auth tokens. + * Token type for the special case where a UID has access only to an account + * but no authenticator specific auth token types. * * @hide */ - public static final String ACCOUNT_ACCESS_TOKEN = - "com.android.abbfd278-af8b-415d-af8b-7571d5dab133"; + public static final String ACCOUNT_ACCESS_TOKEN_TYPE = + "com.android.AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE"; private final Context mContext; private final IAccountManager mService; @@ -814,7 +821,9 @@ public class AccountManager { public Account bundleToResult(Bundle bundle) throws AuthenticatorException { String name = bundle.getString(KEY_ACCOUNT_NAME); String type = bundle.getString(KEY_ACCOUNT_TYPE); - return new Account(name, type); + IAccountAccessTracker tracker = IAccountAccessTracker.Stub.asInterface( + bundle.getBinder(KEY_ACCOUNT_ACCESS_TRACKER)); + return new Account(name, type, tracker); } }.start(); } @@ -2270,6 +2279,7 @@ public class AccountManager { result.putString(KEY_ACCOUNT_NAME, null); result.putString(KEY_ACCOUNT_TYPE, null); result.putString(KEY_AUTHTOKEN, null); + result.putBinder(KEY_ACCOUNT_ACCESS_TRACKER, null); try { mResponse.onResult(result); } catch (RemoteException e) { @@ -2295,9 +2305,13 @@ public class AccountManager { public void onResult(Bundle value) throws RemoteException { Account account = new Account( value.getString(KEY_ACCOUNT_NAME), - value.getString(KEY_ACCOUNT_TYPE)); - mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions, - mActivity, mMyCallback, mHandler); + value.getString(KEY_ACCOUNT_TYPE), + IAccountAccessTracker.Stub.asInterface( + value.getBinder( + KEY_ACCOUNT_ACCESS_TRACKER))); + mFuture = getAuthToken(account, mAuthTokenType, + mLoginOptions, mActivity, mMyCallback, + mHandler); } @Override @@ -2344,7 +2358,9 @@ public class AccountManager { setException(new AuthenticatorException("account not in result")); return; } - final Account account = new Account(accountName, accountType); + final IAccountAccessTracker tracker = IAccountAccessTracker.Stub.asInterface( + result.getBinder(KEY_ACCOUNT_ACCESS_TRACKER)); + final Account account = new Account(accountName, accountType, tracker); mNumAccounts = 1; getAuthToken(account, mAuthTokenType, null /* options */, mActivity, mMyCallback, mHandler); diff --git a/core/java/android/accounts/AccountManagerInternal.java b/core/java/android/accounts/AccountManagerInternal.java index d777643950e68..0ac719777e20d 100644 --- a/core/java/android/accounts/AccountManagerInternal.java +++ b/core/java/android/accounts/AccountManagerInternal.java @@ -27,6 +27,21 @@ import android.os.RemoteCallback; */ public abstract class AccountManagerInternal { + /** + * Listener for explicit UID account access grant changes. + */ + public interface OnAppPermissionChangeListener { + + /** + * Called when the explicit grant state for a given UID to + * access an account changes. + * + * @param account The account + * @param uid The UID for which the grant changed + */ + public void onAppPermissionChanged(Account account, int uid); + } + /** * Requests that a given package is given access to an account. * The provided callback will be invoked with a {@link android.os.Bundle} @@ -38,7 +53,24 @@ public abstract class AccountManagerInternal { * @param userId Concrete user id for which to request. * @param callback A callback for receiving the result. */ - public abstract void requestAccountAccess(@NonNull Account account, + public abstract void requestAccountAccess(@NonNull Account account, @NonNull String packageName, @IntRange(from = 0) int userId, @NonNull RemoteCallback callback); + + /** + * Check whether the given UID has access to the account. + * + * @param account The account + * @param uid The UID + * @return Whether the UID can access the account + */ + public abstract boolean hasAccountAccess(@NonNull Account account, @IntRange(from = 0) int uid); + + /** + * Adds a listener for explicit UID account access grant changes. + * + * @param listener The listener + */ + public abstract void addOnAppPermissionChangeListener( + @NonNull OnAppPermissionChangeListener listener); } diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java index 8d0ce58d33581..38eab29050225 100644 --- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java +++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java @@ -108,7 +108,7 @@ public class GrantCredentialsPermissionActivity extends Activity implements View } }; - if (!AccountManager.ACCOUNT_ACCESS_TOKEN.equals(mAuthTokenType)) { + if (!AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE.equals(mAuthTokenType)) { AccountManager.get(this).getAuthTokenLabel(mAccount.type, mAuthTokenType, callback, null); } diff --git a/core/java/android/accounts/IAccountAccessTracker.aidl b/core/java/android/accounts/IAccountAccessTracker.aidl new file mode 100644 index 0000000000000..e12b3d1e21c78 --- /dev/null +++ b/core/java/android/accounts/IAccountAccessTracker.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2016 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; + +/** + * Interface to track which apps accessed an account + * + * @hide + */ +oneway interface IAccountAccessTracker { + void onAccountAccessed(); +} diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index b3320d6f69760..daa1b93889ccc 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -1877,6 +1877,7 @@ public abstract class ContentResolver { if (extras != null) { String accountName = extras.getString(SYNC_EXTRAS_ACCOUNT); if (!TextUtils.isEmpty(accountName)) { + // TODO: No references to Google in AOSP account = new Account(accountName, "com.google"); } extras.remove(SYNC_EXTRAS_ACCOUNT); diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 2f96b20e512a9..b86da67715d69 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -26,12 +26,14 @@ import android.accounts.AccountManagerInternal; import android.accounts.AuthenticatorDescription; import android.accounts.CantAddAccountActivity; import android.accounts.GrantCredentialsPermissionActivity; +import android.accounts.IAccountAccessTracker; import android.accounts.IAccountAuthenticator; import android.accounts.IAccountAuthenticatorResponse; import android.accounts.IAccountManager; import android.accounts.IAccountManagerResponse; import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.ActivityThread; @@ -85,7 +87,6 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; -import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -125,6 +126,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -283,7 +285,7 @@ public class AccountManagerService private final Object cacheLock = new Object(); /** protected by the {@link #cacheLock} */ private final HashMap accountCache = - new LinkedHashMap(); + new LinkedHashMap<>(); /** protected by the {@link #cacheLock} */ private final HashMap> userDataCache = new HashMap>(); @@ -322,6 +324,8 @@ public class AccountManagerService private final SparseArray mUsers = new SparseArray<>(); private final SparseBooleanArray mLocalUnlockedUsers = new SparseBooleanArray(); + private CopyOnWriteArrayList + mAppPermissionChangeListeners = new CopyOnWriteArrayList<>(); private static AtomicReference sThis = new AtomicReference<>(); private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{}; @@ -502,7 +506,7 @@ public class AccountManagerService if (!checkAccess || hasAccountAccess(account, packageName, UserHandle.getUserHandleForUid(uid))) { cancelNotification(getCredentialPermissionNotificationId(account, - AccountManager.ACCOUNT_ACCESS_TOKEN, uid), packageName, + AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE, uid), packageName, UserHandle.getUserHandleForUid(uid)); } } @@ -695,7 +699,8 @@ public class AccountManagerService final ArrayList accountNames = cur.getValue(); final Account[] accountsForType = new Account[accountNames.size()]; for (int i = 0; i < accountsForType.length; i++) { - accountsForType[i] = new Account(accountNames.get(i), accountType); + accountsForType[i] = new Account(accountNames.get(i), accountType, + new AccountAccessTracker()); } accounts.accountCache.put(accountType, accountsForType); } @@ -1505,6 +1510,8 @@ public class AccountManagerService Bundle result = new Bundle(); result.putString(AccountManager.KEY_ACCOUNT_NAME, resultingAccount.name); result.putString(AccountManager.KEY_ACCOUNT_TYPE, resultingAccount.type); + result.putBinder(AccountManager.KEY_ACCOUNT_ACCESS_TRACKER, + resultingAccount.getAccessTracker().asBinder()); try { response.onResult(result); } catch (RemoteException e) { @@ -1857,7 +1864,7 @@ public class AccountManagerService for (Pair, Integer> key : accounts.credentialsPermissionNotificationIds.keySet()) { if (account.equals(key.first.first) - && AccountManager.ACCOUNT_ACCESS_TOKEN.equals(key.first.second)) { + && AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE.equals(key.first.second)) { final int uid = (Integer) key.second; mMessageHandler.post(() -> cancelAccountAccessRequestNotificationIfNeeded( account, uid, false)); @@ -3457,22 +3464,36 @@ public class AccountManagerService Preconditions.checkArgumentInRange(userId, 0, Integer.MAX_VALUE, "user must be concrete"); try { - final int uid = mPackageManager.getPackageUidAsUser(packageName, userId); - // Use null token which means any token. Having a token means the package - // is trusted by the authenticator, hence it is fine to access the account. - if (permissionIsGranted(account, null, uid, userId)) { - 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); + return hasAccountAccess(account, packageName, uid); } catch (NameNotFoundException e) { return false; } } + private boolean hasAccountAccess(@NonNull Account account, @Nullable String packageName, + int uid) { + if (packageName == null) { + String[] packageNames = mPackageManager.getPackagesForUid(uid); + if (ArrayUtils.isEmpty(packageNames)) { + 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 + // is trusted by the authenticator, hence it is fine to access the account. + if (permissionIsGranted(account, null, uid, UserHandle.getUserId(uid))) { + 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); + } + private boolean checkUidPermission(String permission, int uid, String opPackageName) { final long identity = Binder.clearCallingIdentity(); try { @@ -3548,7 +3569,7 @@ public class AccountManagerService private void handleAuthenticatorResponse(boolean accessGranted) throws RemoteException { cancelNotification(getCredentialPermissionNotificationId(account, - AccountManager.ACCOUNT_ACCESS_TOKEN, uid), packageName, + AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE, uid), packageName, UserHandle.getUserHandleForUid(uid)); if (callback != null) { Bundle result = new Bundle(); @@ -3556,7 +3577,7 @@ public class AccountManagerService callback.sendResult(result); } } - }), AccountManager.ACCOUNT_ACCESS_TOKEN, false); + }), AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE, false); } @Override @@ -5610,6 +5631,12 @@ public class AccountManagerService cancelAccountAccessRequestNotificationIfNeeded(account, uid, true); } + + // Listeners are a final CopyOnWriteArrayList, hence no lock needed. + for (AccountManagerInternal.OnAppPermissionChangeListener listener + : mAppPermissionChangeListeners) { + mMessageHandler.post(() -> listener.onAppPermissionChanged(account, uid)); + } } /** @@ -5642,9 +5669,16 @@ public class AccountManagerService } finally { db.endTransaction(); } + cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid), new UserHandle(accounts.userId)); } + + // Listeners are a final CopyOnWriteArrayList, hence no lock needed. + for (AccountManagerInternal.OnAppPermissionChangeListener listener + : mAppPermissionChangeListeners) { + mMessageHandler.post(() -> listener.onAppPermissionChanged(account, uid)); + } } static final private String stringArrayToString(String[] value) { @@ -5683,7 +5717,7 @@ public class AccountManagerService if (accountsForType != null) { System.arraycopy(accountsForType, 0, newAccountsForType, 0, oldLength); } - newAccountsForType[oldLength] = account; + newAccountsForType[oldLength] = new Account(account, new AccountAccessTracker()); accounts.accountCache.put(account.type, newAccountsForType); } @@ -5927,6 +5961,33 @@ public class AccountManagerService } } + private final class AccountAccessTracker extends IAccountAccessTracker.Stub { + @Override + public void onAccountAccessed() throws RemoteException { + final int uid = Binder.getCallingUid(); + if (UserHandle.getAppId(uid) == Process.SYSTEM_UID) { + return; + } + final int userId = UserHandle.getCallingUserId(); + final long identity = Binder.clearCallingIdentity(); + try { + for (Account account : getAccounts(userId, mContext.getOpPackageName())) { + IAccountAccessTracker accountTracker = account.getAccessTracker(); + if (accountTracker != null && asBinder() == accountTracker.asBinder()) { + // 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. + if (!hasAccountAccess(account, null, uid)) { + updateAppPermission(account, AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE, + uid, true); + } + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + private final class AccountManagerInternalImpl extends AccountManagerInternal { @Override public void requestAccountAccess(@NonNull Account account, @NonNull String packageName, @@ -5948,7 +6009,8 @@ public class AccountManagerService return; } - if (hasAccountAccess(account, packageName, new UserHandle(userId))) { + if (AccountManagerService.this.hasAccountAccess(account, packageName, + new UserHandle(userId))) { Bundle result = new Bundle(); result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); callback.sendResult(result); @@ -5964,7 +6026,22 @@ public class AccountManagerService } Intent intent = newRequestAccountAccessIntent(account, packageName, uid, callback); - doNotification(mUsers.get(userId), account, null, intent, packageName, userId); + final UserAccounts userAccounts; + synchronized (mUsers) { + userAccounts = mUsers.get(userId); + } + doNotification(userAccounts, account, null, intent, packageName, userId); + } + + @Override + public void addOnAppPermissionChangeListener(OnAppPermissionChangeListener listener) { + // Listeners are a final CopyOnWriteArrayList, hence no lock needed. + mAppPermissionChangeListeners.add(listener); + } + + @Override + public boolean hasAccountAccess(@NonNull Account account, @IntRange(from = 0) int uid) { + return AccountManagerService.this.hasAccountAccess(account, null, uid); } } } diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 12955f5771d2d..4e236d164a69f 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -482,7 +482,7 @@ public final class ContentService extends IContentService.Stub { SyncManager syncManager = getSyncManager(); if (syncManager != null) { syncManager.scheduleSync(account, userId, uId, authority, extras, - false /* onlyThoseWithUnkownSyncableState */); + SyncStorageEngine.AuthorityInfo.UNDEFINED); } } finally { restoreCallingIdentity(identityToken); @@ -548,7 +548,7 @@ public final class ContentService extends IContentService.Stub { } else { syncManager.scheduleSync( request.getAccount(), userId, callerUid, request.getProvider(), extras, - false /* onlyThoseWithUnknownSyncableState */); + SyncStorageEngine.AuthorityInfo.UNDEFINED); } } finally { restoreCallingIdentity(identityToken); diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 705eae677d2ba..8c714789ee2b3 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -504,7 +504,7 @@ public class SyncManager { @Override public void onSyncRequest(SyncStorageEngine.EndPoint info, int reason, Bundle extras) { scheduleSync(info.account, info.userId, reason, info.provider, extras, - false); + AuthorityInfo.UNDEFINED); } }); @@ -534,7 +534,7 @@ public class SyncManager { if (!removed) { scheduleSync(null, UserHandle.USER_ALL, SyncOperation.REASON_SERVICE_CHANGED, - type.authority, null, false /* onlyThoseWithUnkownSyncableState */); + type.authority, null, AuthorityInfo.UNDEFINED); } } }, mSyncHandler); @@ -576,6 +576,15 @@ public class SyncManager { mAccountManager = (AccountManager) mContext.getSystemService(Context.ACCOUNT_SERVICE); mAccountManagerInternal = LocalServices.getService(AccountManagerInternal.class); + mAccountManagerInternal.addOnAppPermissionChangeListener((Account account, int uid) -> { + // If the UID gained access to the account kick-off syncs lacking account access + if (mAccountManagerInternal.hasAccountAccess(account, uid)) { + scheduleSync(account, UserHandle.getUserId(uid), + SyncOperation.REASON_ACCOUNTS_UPDATED, + null, null, AuthorityInfo.SYNCABLE_NO_ACCOUNT_ACCESS); + } + }); + mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService( BatteryStats.SERVICE_NAME)); @@ -671,7 +680,7 @@ public class SyncManager { service.type.accountType, userHandle)) { if (!canAccessAccount(account, packageName, userId)) { mAccountManager.updateAppPermission(account, - AccountManager.ACCOUNT_ACCESS_TOKEN, service.uid, true); + AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE, service.uid, true); } } } @@ -778,10 +787,11 @@ public class SyncManager { * @param extras a Map of SyncAdapter-specific information to control * syncs of a specific provider. Can be null. Is ignored * if the url is null. - * @param onlyThoseWithUnkownSyncableState Only sync authorities that have unknown state. + * @param targetSyncState Only sync authorities that have the specified sync state. + * Use {@link AuthorityInfo#UNDEFINED} to sync all authorities. */ public void scheduleSync(Account requestedAccount, int userId, int reason, - String requestedAuthority, Bundle extras, boolean onlyThoseWithUnkownSyncableState) { + String requestedAuthority, Bundle extras, int targetSyncState) { final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); if (extras == null) { extras = new Bundle(); @@ -888,7 +898,7 @@ public class SyncManager { if (result != null && result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)) { scheduleSync(account.account, userId, reason, authority, - finalExtras, onlyThoseWithUnkownSyncableState); + finalExtras, targetSyncState); } } )); @@ -903,9 +913,10 @@ public class SyncManager { isSyncable = AuthorityInfo.SYNCABLE; } - if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) { + if (targetSyncState != AuthorityInfo.UNDEFINED && targetSyncState != isSyncable) { continue; } + if (!syncAdapterInfo.type.supportsUploading() && uploadOnly) { continue; } @@ -931,7 +942,7 @@ public class SyncManager { final String owningPackage = syncAdapterInfo.componentName.getPackageName(); - if (isSyncable < 0) { + if (isSyncable == AuthorityInfo.NOT_INITIALIZED) { // Initialisation sync. Bundle newExtras = new Bundle(); newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true); @@ -950,8 +961,8 @@ public class SyncManager { owningUid, owningPackage, reason, source, authority, newExtras, allowParallelSyncs) ); - } - if (!onlyThoseWithUnkownSyncableState) { + } else if (targetSyncState == AuthorityInfo.UNDEFINED + || targetSyncState == isSyncable) { if (isLoggable) { Slog.v(TAG, "scheduleSync:" + " delay until " + delayUntil @@ -1076,7 +1087,7 @@ public class SyncManager { final Bundle extras = new Bundle(); extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true); scheduleSync(account, userId, reason, authority, extras, - false /* onlyThoseWithUnkownSyncableState */); + AuthorityInfo.UNDEFINED); } public SyncAdapterType[] getSyncAdapterTypes(int userId) { @@ -1535,7 +1546,7 @@ public class SyncManager { mContext.getOpPackageName()); for (Account account : accounts) { scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null, - true /* onlyThoseWithUnknownSyncableState */); + AuthorityInfo.NOT_INITIALIZED); } } @@ -2714,7 +2725,8 @@ public class SyncManager { if (syncTargets != null) { scheduleSync(syncTargets.account, syncTargets.userId, - SyncOperation.REASON_ACCOUNTS_UPDATED, syncTargets.provider, null, true); + SyncOperation.REASON_ACCOUNTS_UPDATED, syncTargets.provider, + null, AuthorityInfo.NOT_INITIALIZED); } } diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java index 8289bae8793bd..069ae73943197 100644 --- a/services/core/java/com/android/server/content/SyncStorageEngine.java +++ b/services/core/java/com/android/server/content/SyncStorageEngine.java @@ -211,6 +211,12 @@ public class SyncStorageEngine extends Handler { public static class AuthorityInfo { // Legal values of getIsSyncable + + /** + * The syncable state is undefined. + */ + public static final int UNDEFINED = -2; + /** * Default state for a newly installed adapter. An uninitialized adapter will receive an * initialization sync which are governed by a different set of rules to that of regular