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
diff --git a/Android.mk b/Android.mk
index 534b5e5..a894e08 100644
--- a/Android.mk
+++ b/Android.mk
@@ -63,6 +63,7 @@
 	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 7b83a30..6c16e32 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 @@
  * 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<Account> 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 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 @@
         }
         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 void writeToParcel(Parcel dest, int flags) {
         dest.writeString(name);
         dest.writeString(type);
+        dest.writeStrongInterface(mAccessTracker);
     }
 
     public static final Creator<Account> CREATOR = new Creator<Account>() {
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 26e7dac..1eb63e0 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -18,7 +18,6 @@
 
 import static android.Manifest.permission.GET_ACCOUNTS;
 
-import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.Size;
@@ -180,6 +179,14 @@
     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.
      */
@@ -264,13 +271,13 @@
     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;
@@ -919,7 +926,9 @@
             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();
     }
@@ -2379,6 +2388,7 @@
                                     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) {
@@ -2404,9 +2414,13 @@
                                         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
@@ -2453,7 +2467,9 @@
                         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 d777643..0ac7197 100644
--- a/core/java/android/accounts/AccountManagerInternal.java
+++ b/core/java/android/accounts/AccountManagerInternal.java
@@ -28,6 +28,21 @@
 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}
      * containing the result which will be a boolean value mapped to the
@@ -38,7 +53,24 @@
      * @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 8d0ce58..38eab29 100644
--- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java
+++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
@@ -108,7 +108,7 @@
             }
         };
 
-        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 0000000..e12b3d1
--- /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 b3320d6..daa1b93 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -1877,6 +1877,7 @@
         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 b152372..a275773 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.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;
@@ -120,11 +122,11 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -285,7 +287,7 @@
         private final Object cacheLock = new Object();
         /** protected by the {@link #cacheLock} */
         private final HashMap<String, Account[]> accountCache =
-                new LinkedHashMap<String, Account[]>();
+                new LinkedHashMap<>();
         /** protected by the {@link #cacheLock} */
         private final Map<Account, Map<String, String>> userDataCache = new HashMap<>();
         /** protected by the {@link #cacheLock} */
@@ -337,6 +339,8 @@
 
     private final SparseArray<UserAccounts> mUsers = new SparseArray<>();
     private final SparseBooleanArray mLocalUnlockedUsers = new SparseBooleanArray();
+    private CopyOnWriteArrayList<AccountManagerInternal.OnAppPermissionChangeListener>
+            mAppPermissionChangeListeners = new CopyOnWriteArrayList<>();
 
     private static AtomicReference<AccountManagerService> sThis = new AtomicReference<>();
     private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{};
@@ -560,7 +564,7 @@
         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));
         }
     }
@@ -1196,7 +1200,8 @@
                     final ArrayList<String> 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);
                 }
@@ -1977,6 +1982,8 @@
             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) {
@@ -2324,7 +2331,7 @@
                 for (Pair<Pair<Account, String>, 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;
                         mHandler.post(() -> cancelAccountAccessRequestNotificationIfNeeded(
                                 account, uid, false));
@@ -3887,22 +3894,36 @@
         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 {
@@ -3978,7 +3999,7 @@
 
             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();
@@ -3986,7 +4007,7 @@
                     callback.sendResult(result);
                 }
             }
-        }), AccountManager.ACCOUNT_ACCESS_TOKEN, false);
+        }), AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE, false);
     }
 
     @Override
@@ -5939,6 +5960,12 @@
 
             cancelAccountAccessRequestNotificationIfNeeded(account, uid, true);
         }
+
+        // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
+        for (AccountManagerInternal.OnAppPermissionChangeListener listener
+                : mAppPermissionChangeListeners) {
+            mHandler.post(() -> listener.onAppPermissionChanged(account, uid));
+        }
     }
 
     /**
@@ -5968,9 +5995,16 @@
             } 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) {
+            mHandler.post(() -> listener.onAppPermissionChanged(account, uid));
+        }
     }
 
     static final private String stringArrayToString(String[] value) {
@@ -6009,7 +6043,7 @@
         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);
     }
 
@@ -6626,6 +6660,33 @@
         }
     }
 
+    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,
@@ -6647,7 +6708,8 @@
                 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);
@@ -6663,7 +6725,22 @@
             }
 
             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 12955f5..4e236d1 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 @@
             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 @@
             } 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 ef3c4b2..22bc60d 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 @@
             @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 @@
                 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 @@
         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 @@
                         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 @@
      * @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 @@
                                 if (result != null
                                         && result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)) {
                                     scheduleSync(account.account, userId, reason, authority,
-                                            finalExtras, onlyThoseWithUnkownSyncableState);
+                                            finalExtras, targetSyncState);
                                 }
                             }
                         ));
@@ -903,9 +913,10 @@
                     isSyncable = AuthorityInfo.SYNCABLE;
                 }
 
-                if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) {
+                if (targetSyncState != AuthorityInfo.UNDEFINED && targetSyncState != isSyncable) {
                     continue;
                 }
+
                 if (!syncAdapterInfo.type.supportsUploading() && uploadOnly) {
                     continue;
                 }
@@ -931,7 +942,7 @@
 
                 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 @@
                                     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 @@
         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 @@
                 mContext.getOpPackageName());
         for (Account account : accounts) {
             scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null,
-                    true /* onlyThoseWithUnknownSyncableState */);
+                    AuthorityInfo.NOT_INITIALIZED);
         }
     }
 
@@ -2714,7 +2725,8 @@
 
             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 8289bae..069ae73 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 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