diff options
| author | 2016-08-24 02:08:19 +0000 | |
|---|---|---|
| committer | 2016-08-24 02:08:19 +0000 | |
| commit | eeeebd346a06cc1af2366b933355ac49717136fa (patch) | |
| tree | 339064f0d5ac77c08fff0adf1ce8de73173cf5ba | |
| parent | 73ce3f621917e87893440f68bc6b5b5d5b160e25 (diff) | |
| parent | 721402e75ad4dfdd62a86b2216499609b35333de (diff) | |
Only sync adapters with access can see an account - framework am: 5cb2973495
am: 721402e75a
Change-Id: I361b009afa6c6e658157e6e04bf88096e8331fd0
11 files changed, 711 insertions, 106 deletions
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 5cf59bc4760d..a3e2ebfb9b22 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -18,6 +18,7 @@ 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; @@ -28,6 +29,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.IntentSender; import android.content.res.Resources; import android.database.SQLException; import android.os.Build; @@ -265,6 +267,15 @@ public class AccountManager { "android.accounts.AccountAuthenticator"; 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. + * + * @hide + */ + public static final String ACCOUNT_ACCESS_TOKEN = + "com.android.abbfd278-af8b-415d-af8b-7571d5dab133"; + private final Context mContext; private final IAccountManager mService; private final Handler mMainHandler; @@ -2964,4 +2975,49 @@ public class AccountManager { } }.start(); } + + /** + * Gets whether a given package under a user has access to an account. + * Can be called only from the system UID. + * + * @param account The account for which to check. + * @param packageName The package for which to check. + * @param userHandle The user for which to check. + * @return True if the package can access the account. + * + * @hide + */ + public boolean hasAccountAccess(@NonNull Account account, @NonNull String packageName, + @NonNull UserHandle userHandle) { + try { + return mService.hasAccountAccess(account, packageName, userHandle); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Creates an intent to request access to a given account for a UID. + * The returned intent should be stated for a result where {@link + * Activity#RESULT_OK} result means access was granted whereas {@link + * Activity#RESULT_CANCELED} result means access wasn't granted. Can + * be called only from the system UID. + * + * @param account The account for which to request. + * @param packageName The package name which to request. + * @param userHandle The user for which to request. + * @return The intent to request account access or null if the package + * doesn't exist. + * + * @hide + */ + public IntentSender createRequestAccountAccessIntentSenderAsUser(@NonNull Account account, + @NonNull String packageName, @NonNull UserHandle userHandle) { + try { + return mService.createRequestAccountAccessIntentSenderAsUser(account, packageName, + userHandle); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/accounts/AccountManagerInternal.java b/core/java/android/accounts/AccountManagerInternal.java new file mode 100644 index 000000000000..d777643950e6 --- /dev/null +++ b/core/java/android/accounts/AccountManagerInternal.java @@ -0,0 +1,44 @@ +/* + * 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; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.os.RemoteCallback; + +/** + * Account manager local system service interface. + * + * @hide Only for use within the system server. + */ +public abstract class AccountManagerInternal { + + /** + * 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 + * {@link AccountManager#KEY_BOOLEAN_RESULT} key. + * + * @param account The account for which to request. + * @param packageName The package name for which to request. + * @param userId Concrete user id for which to request. + * @param callback A callback for receiving the result. + */ + public abstract void requestAccountAccess(@NonNull Account account, + @NonNull String packageName, @IntRange(from = 0) int userId, + @NonNull RemoteCallback callback); +} diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java index 12b2b9ccf148..8d0ce58d3358 100644 --- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java +++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java @@ -35,12 +35,10 @@ import java.io.IOException; */ public class GrantCredentialsPermissionActivity extends Activity implements View.OnClickListener { public static final String EXTRAS_ACCOUNT = "account"; - public static final String EXTRAS_AUTH_TOKEN_LABEL = "authTokenLabel"; public static final String EXTRAS_AUTH_TOKEN_TYPE = "authTokenType"; public static final String EXTRAS_RESPONSE = "response"; - public static final String EXTRAS_ACCOUNT_TYPE_LABEL = "accountTypeLabel"; - public static final String EXTRAS_PACKAGES = "application"; public static final String EXTRAS_REQUESTING_UID = "uid"; + private Account mAccount; private String mAuthTokenType; private int mUid; @@ -109,7 +107,11 @@ public class GrantCredentialsPermissionActivity extends Activity implements View } } }; - AccountManager.get(this).getAuthTokenLabel(mAccount.type, mAuthTokenType, callback, null); + + if (!AccountManager.ACCOUNT_ACCESS_TOKEN.equals(mAuthTokenType)) { + AccountManager.get(this).getAuthTokenLabel(mAccount.type, + mAuthTokenType, callback, null); + } findViewById(R.id.allow_button).setOnClickListener(this); findViewById(R.id.deny_button).setOnClickListener(this); diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl index 7199288426f2..56a6488088b5 100644 --- a/core/java/android/accounts/IAccountManager.aidl +++ b/core/java/android/accounts/IAccountManager.aidl @@ -19,8 +19,10 @@ package android.accounts; import android.accounts.IAccountManagerResponse; import android.accounts.Account; import android.accounts.AuthenticatorDescription; +import android.content.IntentSender; import android.os.Bundle; - +import android.os.RemoteCallback; +import android.os.UserHandle; /** * Central application service that provides account management. @@ -102,4 +104,10 @@ interface IAccountManager { /* Check if credentials update is suggested */ void isCredentialsUpdateSuggested(in IAccountManagerResponse response, in Account account, String statusToken); + + /* Check if the package in a user can access an account */ + boolean hasAccountAccess(in Account account, String packageName, in UserHandle userHandle); + /* Crate an intent to request account access for package and a given user id */ + IntentSender createRequestAccountAccessIntentSenderAsUser(in Account account, + String packageName, in UserHandle userHandle); } diff --git a/core/java/android/content/SyncAdapterType.java b/core/java/android/content/SyncAdapterType.java index 8a16ac94522e..6ef7fd214069 100644 --- a/core/java/android/content/SyncAdapterType.java +++ b/core/java/android/content/SyncAdapterType.java @@ -16,6 +16,7 @@ package android.content; +import android.annotation.Nullable; import android.text.TextUtils; import android.os.Parcelable; import android.os.Parcel; @@ -33,6 +34,7 @@ public class SyncAdapterType implements Parcelable { private final boolean isAlwaysSyncable; private final boolean allowParallelSyncs; private final String settingsActivity; + private final String packageName; public SyncAdapterType(String authority, String accountType, boolean userVisible, boolean supportsUploading) { @@ -50,6 +52,7 @@ public class SyncAdapterType implements Parcelable { this.allowParallelSyncs = false; this.settingsActivity = null; this.isKey = false; + this.packageName = null; } /** @hide */ @@ -57,7 +60,8 @@ public class SyncAdapterType implements Parcelable { boolean supportsUploading, boolean isAlwaysSyncable, boolean allowParallelSyncs, - String settingsActivity) { + String settingsActivity, + String packageName) { if (TextUtils.isEmpty(authority)) { throw new IllegalArgumentException("the authority must not be empty: " + authority); } @@ -72,6 +76,7 @@ public class SyncAdapterType implements Parcelable { this.allowParallelSyncs = allowParallelSyncs; this.settingsActivity = settingsActivity; this.isKey = false; + this.packageName = packageName; } private SyncAdapterType(String authority, String accountType) { @@ -89,6 +94,7 @@ public class SyncAdapterType implements Parcelable { this.allowParallelSyncs = false; this.settingsActivity = null; this.isKey = true; + this.packageName = null; } public boolean supportsUploading() { @@ -148,6 +154,16 @@ public class SyncAdapterType implements Parcelable { return settingsActivity; } + /** + * The package hosting the sync adapter. + * @return The package name. + * + * @hide + */ + public @Nullable String getPackageName() { + return packageName; + } + public static SyncAdapterType newKey(String authority, String accountType) { return new SyncAdapterType(authority, accountType); } @@ -181,6 +197,7 @@ public class SyncAdapterType implements Parcelable { + ", isAlwaysSyncable=" + isAlwaysSyncable + ", allowParallelSyncs=" + allowParallelSyncs + ", settingsActivity=" + settingsActivity + + ", packageName=" + packageName + "}"; } } @@ -201,6 +218,7 @@ public class SyncAdapterType implements Parcelable { dest.writeInt(isAlwaysSyncable ? 1 : 0); dest.writeInt(allowParallelSyncs ? 1 : 0); dest.writeString(settingsActivity); + dest.writeString(packageName); } public SyncAdapterType(Parcel source) { @@ -211,6 +229,7 @@ public class SyncAdapterType implements Parcelable { source.readInt() != 0, source.readInt() != 0, source.readInt() != 0, + source.readString(), source.readString()); } diff --git a/core/java/android/content/SyncAdaptersCache.java b/core/java/android/content/SyncAdaptersCache.java index 6704b75dff7f..ddbdb8a7a559 100644 --- a/core/java/android/content/SyncAdaptersCache.java +++ b/core/java/android/content/SyncAdaptersCache.java @@ -81,7 +81,7 @@ public class SyncAdaptersCache extends RegisteredServicesCache<SyncAdapterType> sa.getString(com.android.internal.R.styleable .SyncAdapter_settingsActivity); return new SyncAdapterType(authority, accountType, userVisible, supportsUploading, - isAlwaysSyncable, allowParallelSyncs, settingsActivity); + isAlwaysSyncable, allowParallelSyncs, settingsActivity, packageName); } finally { sa.recycle(); } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ef0c0a7d0fc9..382c6c43a64d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1317,6 +1317,7 @@ android:protectionLevel="dangerous" android:description="@string/permdesc_getAccounts" android:label="@string/permlab_getAccounts" /> + <uses-permission android:name="android.permission.GET_ACCOUNTS"/> <!-- @SystemApi Allows applications to call into AccountAuthenticators. <p>Not for use by third-party applications. --> diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 9108acf9c40a..5055562ed9b0 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -22,6 +22,7 @@ import android.accounts.Account; import android.accounts.AccountAndUser; import android.accounts.AccountAuthenticatorResponse; import android.accounts.AccountManager; +import android.accounts.AccountManagerInternal; import android.accounts.AuthenticatorDescription; import android.accounts.CantAddAccountActivity; import android.accounts.GrantCredentialsPermissionActivity; @@ -29,11 +30,14 @@ 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.app.ActivityManager; import android.app.ActivityManagerNative; +import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -46,9 +50,11 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.IntentSender; import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -72,11 +78,14 @@ import android.os.Looper; import android.os.Message; import android.os.Parcel; import android.os.Process; +import android.os.RemoteCallback; import android.os.RemoteException; +import android.os.ServiceManager; 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; @@ -86,6 +95,7 @@ import android.util.SparseBooleanArray; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.content.PackageMonitor; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; @@ -237,6 +247,13 @@ public class AccountManagerService + " AND " + ACCOUNTS_NAME + "=?" + " AND " + ACCOUNTS_TYPE + "=?"; + private static final String COUNT_OF_MATCHING_GRANTS_ANY_TOKEN = "" + + "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS + + " WHERE " + GRANTS_ACCOUNTS_ID + "=" + ACCOUNTS_ID + + " AND " + GRANTS_GRANTEE_UID + "=?" + + " AND " + ACCOUNTS_NAME + "=?" + + " AND " + ACCOUNTS_TYPE + "=?"; + private static final String SELECTION_AUTHTOKENS_BY_ACCOUNT = AUTHTOKENS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)"; @@ -376,6 +393,118 @@ public class AccountManagerService } } }, UserHandle.ALL, userFilter, null, null); + + LocalServices.addService(AccountManagerInternal.class, new AccountManagerInternalImpl()); + + // Need to cancel account request notifications if the update/install can access the account + new PackageMonitor() { + @Override + public void onPackageAdded(String packageName, int uid) { + // Called on a handler, and running as the system + cancelAccountAccessRequestNotificationIfNeeded(uid, true); + } + + @Override + public void onPackageUpdateFinished(String packageName, int uid) { + // Called on a handler, and running as the system + cancelAccountAccessRequestNotificationIfNeeded(uid, true); + } + }.register(mContext, mMessageHandler.getLooper(), UserHandle.ALL, true); + + // Cancel account request notification if an app op was preventing the account access + mAppOpsManager.startWatchingMode(AppOpsManager.OP_GET_ACCOUNTS, null, + new AppOpsManager.OnOpChangedInternalListener() { + @Override + public void onOpChanged(int op, String packageName) { + try { + final int userId = ActivityManager.getCurrentUser(); + final int uid = mPackageManager.getPackageUidAsUser(packageName, userId); + final int mode = mAppOpsManager.checkOpNoThrow( + AppOpsManager.OP_GET_ACCOUNTS, uid, packageName); + if (mode == AppOpsManager.MODE_ALLOWED) { + final long identity = Binder.clearCallingIdentity(); + try { + cancelAccountAccessRequestNotificationIfNeeded(packageName, uid, true); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } catch (NameNotFoundException e) { + /* ignore */ + } + } + }); + + // Cancel account request notification if a permission was preventing the account access + mPackageManager.addOnPermissionsChangeListener( + (int uid) -> { + Account[] accounts = null; + String[] packageNames = mPackageManager.getPackagesForUid(uid); + if (packageNames != null) { + final int userId = UserHandle.getUserId(uid); + final long identity = Binder.clearCallingIdentity(); + try { + for (String packageName : packageNames) { + if (mContext.getPackageManager().checkPermission( + Manifest.permission.GET_ACCOUNTS, packageName) + != PackageManager.PERMISSION_GRANTED) { + continue; + } + + if (accounts == null) { + accounts = getAccountsAsUser(null, userId, "android"); + if (ArrayUtils.isEmpty(accounts)) { + return; + } + } + + for (Account account : accounts) { + cancelAccountAccessRequestNotificationIfNeeded( + account, uid, packageName, true); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + }); + } + + private void cancelAccountAccessRequestNotificationIfNeeded(int uid, + boolean checkAccess) { + Account[] accounts = getAccountsAsUser(null, UserHandle.getUserId(uid), "android"); + for (Account account : accounts) { + cancelAccountAccessRequestNotificationIfNeeded(account, uid, checkAccess); + } + } + + private void cancelAccountAccessRequestNotificationIfNeeded(String packageName, int uid, + boolean checkAccess) { + Account[] accounts = getAccountsAsUser(null, UserHandle.getUserId(uid), "android"); + for (Account account : accounts) { + cancelAccountAccessRequestNotificationIfNeeded(account, uid, packageName, checkAccess); + } + } + + private void cancelAccountAccessRequestNotificationIfNeeded(Account account, int uid, + boolean checkAccess) { + String[] packageNames = mPackageManager.getPackagesForUid(uid); + if (packageNames != null) { + for (String packageName : packageNames) { + cancelAccountAccessRequestNotificationIfNeeded(account, uid, + packageName, checkAccess); + } + } + } + + private void cancelAccountAccessRequestNotificationIfNeeded(Account account, + int uid, String packageName, boolean checkAccess) { + if (!checkAccess || hasAccountAccess(account, packageName, + UserHandle.getUserHandleForUid(uid))) { + cancelNotification(getCredentialPermissionNotificationId(account, + AccountManager.ACCOUNT_ACCESS_TOKEN, uid), packageName, + UserHandle.getUserHandleForUid(uid)); + } } @Override @@ -1723,6 +1852,21 @@ public class AccountManagerService } finally { Binder.restoreCallingIdentity(id); } + + if (isChanged) { + synchronized (accounts.credentialsPermissionNotificationIds) { + for (Pair<Pair<Account, String>, Integer> key + : accounts.credentialsPermissionNotificationIds.keySet()) { + if (account.equals(key.first.first) + && AccountManager.ACCOUNT_ACCESS_TOKEN.equals(key.first.second)) { + final int uid = (Integer) key.second; + mMessageHandler.post(() -> cancelAccountAccessRequestNotificationIfNeeded( + account, uid, false)); + } + } + } + } + return isChanged; } @@ -2319,9 +2463,11 @@ public class AccountManagerService if (result.containsKey(AccountManager.KEY_AUTH_TOKEN_LABEL)) { Intent intent = newGrantCredentialsPermissionIntent( account, + null, callerUid, new AccountAuthenticatorResponse(this), - authTokenType); + authTokenType, + true); Bundle bundle = new Bundle(); bundle.putParcelable(AccountManager.KEY_INTENT, intent); onResult(bundle); @@ -2372,7 +2518,7 @@ public class AccountManagerService intent); doNotification(mAccounts, account, result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE), - intent, accounts.userId); + intent, "android", accounts.userId); } } super.onResult(result); @@ -2403,7 +2549,7 @@ public class AccountManagerService } private void createNoCredentialsPermissionNotification(Account account, Intent intent, - int userId) { + String packageName, int userId) { int uid = intent.getIntExtra( GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, -1); String authTokenType = intent.getStringExtra( @@ -2431,20 +2577,23 @@ public class AccountManagerService PendingIntent.FLAG_CANCEL_CURRENT, null, user)) .build(); installNotification(getCredentialPermissionNotificationId( - account, authTokenType, uid), n, user); + account, authTokenType, uid), n, packageName, user.getIdentifier()); } - private Intent newGrantCredentialsPermissionIntent(Account account, int uid, - AccountAuthenticatorResponse response, String authTokenType) { + private Intent newGrantCredentialsPermissionIntent(Account account, String packageName, + int uid, AccountAuthenticatorResponse response, String authTokenType, + boolean startInNewTask) { Intent intent = new Intent(mContext, GrantCredentialsPermissionActivity.class); - // See FLAG_ACTIVITY_NEW_TASK docs for limitations and benefits of the flag. - // Since it was set in Eclair+ we can't change it without breaking apps using - // the intent from a non-Activity context. - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addCategory( - String.valueOf(getCredentialPermissionNotificationId(account, authTokenType, uid))); + if (startInNewTask) { + // See FLAG_ACTIVITY_NEW_TASK docs for limitations and benefits of the flag. + // Since it was set in Eclair+ we can't change it without breaking apps using + // the intent from a non-Activity context. This is the default behavior. + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + intent.addCategory(String.valueOf(getCredentialPermissionNotificationId(account, + authTokenType, uid) + (packageName != null ? packageName : ""))); intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT, account); intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE, authTokenType); intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_RESPONSE, response); @@ -3295,6 +3444,118 @@ public class AccountManagerService } @Override + public boolean hasAccountAccess(@NonNull Account account, @NonNull String packageName, + @NonNull UserHandle userHandle) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Can be called only by system UID"); + } + Preconditions.checkNotNull(account, "account cannot be null"); + Preconditions.checkNotNull(packageName, "packageName cannot be null"); + Preconditions.checkNotNull(userHandle, "userHandle cannot be null"); + + final int userId = userHandle.getIdentifier(); + + 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); + } catch (NameNotFoundException e) { + return false; + } + } + + 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; + } + + @Override + public IntentSender createRequestAccountAccessIntentSenderAsUser(@NonNull Account account, + @NonNull String packageName, @NonNull UserHandle userHandle) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Can be called only by system UID"); + } + + Preconditions.checkNotNull(account, "account cannot be null"); + Preconditions.checkNotNull(packageName, "packageName cannot be null"); + Preconditions.checkNotNull(userHandle, "userHandle cannot be null"); + + final int userId = userHandle.getIdentifier(); + + Preconditions.checkArgumentInRange(userId, 0, Integer.MAX_VALUE, "user must be concrete"); + + final int uid; + try { + uid = mPackageManager.getPackageUidAsUser(packageName, userId); + } catch (NameNotFoundException e) { + Slog.e(TAG, "Unknown package " + packageName); + return null; + } + + Intent intent = newRequestAccountAccessIntent(account, packageName, uid, null); + + return PendingIntent.getActivityAsUser( + mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, + null, new UserHandle(userId)).getIntentSender(); + } + + private Intent newRequestAccountAccessIntent(Account account, String packageName, + int uid, RemoteCallback callback) { + return newGrantCredentialsPermissionIntent(account, packageName, uid, + new AccountAuthenticatorResponse(new IAccountAuthenticatorResponse.Stub() { + @Override + public void onResult(Bundle value) throws RemoteException { + handleAuthenticatorResponse(true); + } + + @Override + public void onRequestContinued() { + /* ignore */ + } + + @Override + public void onError(int errorCode, String errorMessage) throws RemoteException { + handleAuthenticatorResponse(false); + } + + private void handleAuthenticatorResponse(boolean accessGranted) throws RemoteException { + cancelNotification(getCredentialPermissionNotificationId(account, + AccountManager.ACCOUNT_ACCESS_TOKEN, uid), packageName, + UserHandle.getUserHandleForUid(uid)); + if (callback != null) { + Bundle result = new Bundle(); + result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, accessGranted); + callback.sendResult(result); + } + } + }), AccountManager.ACCOUNT_ACCESS_TOKEN, false); + } + + @Override public boolean someUserHasAccount(@NonNull final Account account) { if (!UserHandle.isSameApp(Process.SYSTEM_UID, Binder.getCallingUid())) { throw new SecurityException("Only system can check for accounts across users"); @@ -4934,7 +5195,7 @@ public class AccountManagerService } private void doNotification(UserAccounts accounts, Account account, CharSequence message, - Intent intent, int userId) { + Intent intent, String packageName, final int userId) { long identityToken = clearCallingIdentity(); try { if (Log.isLoggable(TAG, Log.VERBOSE)) { @@ -4944,12 +5205,12 @@ public class AccountManagerService if (intent.getComponent() != null && GrantCredentialsPermissionActivity.class.getName().equals( intent.getComponent().getClassName())) { - createNoCredentialsPermissionNotification(account, intent, userId); + createNoCredentialsPermissionNotification(account, intent, packageName, userId); } else { + Context contextForUser = getContextForUser(new UserHandle(userId)); final Integer notificationId = getSigninRequiredNotificationId(accounts, account); intent.addCategory(String.valueOf(notificationId)); - UserHandle user = new UserHandle(userId); - Context contextForUser = getContextForUser(user); + final String notificationTitleFormat = contextForUser.getText(R.string.notification_title).toString(); Notification n = new Notification.Builder(contextForUser) @@ -4961,9 +5222,9 @@ public class AccountManagerService .setContentText(message) .setContentIntent(PendingIntent.getActivityAsUser( mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, - null, user)) + null, new UserHandle(userId))) .build(); - installNotification(notificationId, n, user); + installNotification(notificationId, n, packageName, userId); } } finally { restoreCallingIdentity(identityToken); @@ -4971,18 +5232,40 @@ public class AccountManagerService } @VisibleForTesting - protected void installNotification(final int notificationId, final Notification n, + protected void installNotification(int notificationId, final Notification notification, UserHandle user) { - ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) - .notifyAsUser(null, notificationId, n, user); + installNotification(notificationId, notification, "android", user.getIdentifier()); + } + + private void installNotification(int notificationId, final Notification notification, + String packageName, int userId) { + final long token = clearCallingIdentity(); + try { + INotificationManager notificationManager = NotificationManager.getService(); + try { + notificationManager.enqueueNotificationWithTag(packageName, packageName, null, + notificationId, notification, new int[1], userId); + } catch (RemoteException e) { + /* ignore - local call */ + } + } finally { + Binder.restoreCallingIdentity(token); + } } @VisibleForTesting protected void cancelNotification(int id, UserHandle user) { + cancelNotification(id, mContext.getPackageName(), user); + } + + protected void cancelNotification(int id, String packageName, UserHandle user) { long identityToken = clearCallingIdentity(); try { - ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) - .cancelAsUser(null, id, user); + INotificationManager service = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + service.cancelNotificationWithTag(packageName, null, id, user.getIdentifier()); + } catch (RemoteException e) { + /* ignore - local call */ } finally { restoreCallingIdentity(identityToken); } @@ -5043,18 +5326,40 @@ public class AccountManagerService private boolean permissionIsGranted( Account account, String authTokenType, int callerUid, int userId) { - final boolean isPrivileged = isPrivileged(callerUid); - final boolean fromAuthenticator = account != null - && isAccountManagedByCaller(account.type, callerUid, userId); - final boolean hasExplicitGrants = account != null - && hasExplicitlyGrantedPermission(account, authTokenType, callerUid); + if (UserHandle.getAppId(callerUid) == Process.SYSTEM_UID) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Access to " + account + " granted calling uid is system"); + } + return true; + } + + if (isPrivileged(callerUid)) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Access to " + account + " granted calling uid " + + callerUid + " privileged"); + } + return true; + } + if (account != null && isAccountManagedByCaller(account.type, callerUid, userId)) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Access to " + account + " granted calling uid " + + callerUid + " manages the account"); + } + return true; + } + if (account != null && hasExplicitlyGrantedPermission(account, authTokenType, callerUid)) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Access to " + account + " granted calling uid " + + callerUid + " user granted access"); + } + return true; + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid " - + callerUid + ", " + account - + ": is authenticator? " + fromAuthenticator - + ", has explicit permission? " + hasExplicitGrants); + Log.v(TAG, "Access to " + account + " not granted for uid " + callerUid); } - return fromAuthenticator || hasExplicitGrants || isPrivileged; + + return false; } private boolean isAccountVisibleToCaller(String accountType, int callingUid, int userId, @@ -5144,10 +5449,20 @@ public class AccountManagerService UserAccounts accounts = getUserAccountsForCaller(); synchronized (accounts.cacheLock) { final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); - String[] args = { String.valueOf(callerUid), authTokenType, - account.name, account.type}; - final boolean permissionGranted = - DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args) != 0; + + final String query; + final String[] args; + + if (authTokenType != null) { + query = COUNT_OF_MATCHING_GRANTS; + args = new String[] {String.valueOf(callerUid), authTokenType, + account.name, account.type}; + } else { + query = COUNT_OF_MATCHING_GRANTS_ANY_TOKEN; + args = new String[] {String.valueOf(callerUid), account.name, + account.type}; + } + final boolean permissionGranted = DatabaseUtils.longForQuery(db, query, args) != 0; if (!permissionGranted && ActivityManager.isRunningInTestHarness()) { // TODO: Skip this check when running automated tests. Replace this // with a more general solution. @@ -5288,6 +5603,8 @@ public class AccountManagerService } cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid), UserHandle.of(accounts.userId)); + + cancelAccountAccessRequestNotificationIfNeeded(account, uid, true); } } @@ -5605,4 +5922,45 @@ public class AccountManagerService } } } + + private final class AccountManagerInternalImpl extends AccountManagerInternal { + @Override + public void requestAccountAccess(@NonNull Account account, @NonNull String packageName, + @IntRange(from = 0) int userId, @NonNull RemoteCallback callback) { + if (account == null) { + Slog.w(TAG, "account cannot be null"); + return; + } + if (packageName == null) { + Slog.w(TAG, "packageName cannot be null"); + return; + } + if (userId < UserHandle.USER_SYSTEM) { + Slog.w(TAG, "user id must be concrete"); + return; + } + if (callback == null) { + Slog.w(TAG, "callback cannot be null"); + return; + } + + if (hasAccountAccess(account, packageName, new UserHandle(userId))) { + Bundle result = new Bundle(); + result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); + callback.sendResult(result); + return; + } + + final int uid; + try { + uid = mPackageManager.getPackageUidAsUser(packageName, userId); + } catch (NameNotFoundException e) { + Slog.e(TAG, "Unknown package " + packageName); + return; + } + + Intent intent = newRequestAccountAccessIntent(account, packageName, uid, callback); + doNotification(mUsers.get(userId), account, null, intent, packageName, userId); + } + } } diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 01b23939815c..12955f5771d2 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -482,7 +482,6 @@ public final class ContentService extends IContentService.Stub { SyncManager syncManager = getSyncManager(); if (syncManager != null) { syncManager.scheduleSync(account, userId, uId, authority, extras, - 0 /* no delay */, 0 /* no delay */, false /* onlyThoseWithUnkownSyncableState */); } } finally { @@ -547,11 +546,8 @@ public final class ContentService extends IContentService.Stub { getSyncManager().updateOrAddPeriodicSync(info, runAtTime, flextime, extras); } else { - long beforeRuntimeMillis = (flextime) * 1000; - long runtimeMillis = runAtTime * 1000; syncManager.scheduleSync( request.getAccount(), userId, callerUid, request.getProvider(), extras, - beforeRuntimeMillis, runtimeMillis, false /* onlyThoseWithUnknownSyncableState */); } } finally { @@ -841,7 +837,7 @@ public final class ContentService extends IContentService.Stub { try { SyncManager syncManager = getSyncManager(); if (syncManager != null) { - return syncManager.getIsSyncable( + return syncManager.computeSyncable( account, userId, providerName); } } finally { diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 39ddc3a98317..e0aac7b941a0 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -19,6 +19,7 @@ package com.android.server.content; import android.accounts.Account; import android.accounts.AccountAndUser; import android.accounts.AccountManager; +import android.accounts.AccountManagerInternal; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.AppGlobals; @@ -64,6 +65,7 @@ import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.PowerManager; +import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -79,6 +81,7 @@ import android.util.Log; import android.util.Pair; import android.util.Slog; +import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; import com.android.server.job.JobSchedulerInternal; import com.google.android.collect.Lists; @@ -133,6 +136,8 @@ import java.util.Set; public class SyncManager { static final String TAG = "SyncManager"; + private static final boolean DEBUG_ACCOUNT_ACCESS = false; + /** Delay a sync due to local changes this long. In milliseconds */ private static final long LOCAL_SYNC_DELAY; @@ -194,6 +199,11 @@ public class SyncManager { private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm"; private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock"; + + private static final int SYNC_OP_STATE_VALID = 0; + private static final int SYNC_OP_STATE_INVALID = 1; + private static final int SYNC_OP_STATE_INVALID_NO_ACCOUNT_ACCESS = 2; + private Context mContext; private static final AccountAndUser[] INITIAL_ACCOUNTS_ARRAY = new AccountAndUser[0]; @@ -310,6 +320,10 @@ public class SyncManager { private final UserManager mUserManager; + private final AccountManager mAccountManager; + + private final AccountManagerInternal mAccountManagerInternal; + private List<UserInfo> getAllUsers() { return mUserManager.getUsers(); } @@ -490,8 +504,6 @@ public class SyncManager { @Override public void onSyncRequest(SyncStorageEngine.EndPoint info, int reason, Bundle extras) { scheduleSync(info.account, info.userId, reason, info.provider, extras, - 0 /* no flexMillis */, - 0 /* run immediately */, false); } }); @@ -522,8 +534,7 @@ public class SyncManager { if (!removed) { scheduleSync(null, UserHandle.USER_ALL, SyncOperation.REASON_SERVICE_CHANGED, - type.authority, null, 0 /* no delay */, 0 /* no delay */, - false /* onlyThoseWithUnkownSyncableState */); + type.authority, null, false /* onlyThoseWithUnkownSyncableState */); } } }, mSyncHandler); @@ -562,6 +573,9 @@ public class SyncManager { } mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mAccountManager = (AccountManager) mContext.getSystemService(Context.ACCOUNT_SERVICE); + mAccountManagerInternal = LocalServices.getService(AccountManagerInternal.class); + mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService( BatteryStats.SERVICE_NAME)); @@ -655,7 +669,7 @@ public class SyncManager { return mSyncStorageEngine; } - public int getIsSyncable(Account account, int userId, String providerName) { + private int getIsSyncable(Account account, int userId, String providerName) { int isSyncable = mSyncStorageEngine.getIsSyncable(account, userId, providerName); UserInfo userInfo = UserManager.get(mContext).getUserInfo(userId); @@ -666,22 +680,22 @@ public class SyncManager { RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo = mSyncAdapters.getServiceInfo( SyncAdapterType.newKey(providerName, account.type), userId); - if (syncAdapterInfo == null) return isSyncable; + if (syncAdapterInfo == null) return AuthorityInfo.NOT_SYNCABLE; PackageInfo pInfo = null; try { pInfo = AppGlobals.getPackageManager().getPackageInfo( syncAdapterInfo.componentName.getPackageName(), 0, userId); - if (pInfo == null) return isSyncable; + if (pInfo == null) return AuthorityInfo.NOT_SYNCABLE; } catch (RemoteException re) { // Shouldn't happen. - return isSyncable; + return AuthorityInfo.NOT_SYNCABLE; } if (pInfo.restrictedAccountType != null && pInfo.restrictedAccountType.equals(account.type)) { return isSyncable; } else { - return 0; + return AuthorityInfo.NOT_SYNCABLE; } } @@ -733,13 +747,10 @@ 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 beforeRuntimeMillis milliseconds before runtimeMillis that this sync can run. - * @param runtimeMillis maximum milliseconds in the future to wait before performing sync. * @param onlyThoseWithUnkownSyncableState Only sync authorities that have unknown state. */ public void scheduleSync(Account requestedAccount, int userId, int reason, - String requestedAuthority, Bundle extras, long beforeRuntimeMillis, - long runtimeMillis, boolean onlyThoseWithUnkownSyncableState) { + String requestedAuthority, Bundle extras, boolean onlyThoseWithUnkownSyncableState) { final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); if (extras == null) { extras = new Bundle(); @@ -749,17 +760,27 @@ public class SyncManager { + requestedAuthority); } - AccountAndUser[] accounts; - if (requestedAccount != null && userId != UserHandle.USER_ALL) { - accounts = new AccountAndUser[] { new AccountAndUser(requestedAccount, userId) }; + AccountAndUser[] accounts = null; + if (requestedAccount != null) { + if (userId != UserHandle.USER_ALL) { + accounts = new AccountAndUser[]{new AccountAndUser(requestedAccount, userId)}; + } else { + for (AccountAndUser runningAccount : mRunningAccounts) { + if (requestedAccount.equals(runningAccount.account)) { + accounts = ArrayUtils.appendElement(AccountAndUser.class, + accounts, runningAccount); + } + } + } } else { accounts = mRunningAccounts; - if (accounts.length == 0) { - if (isLoggable) { - Slog.v(TAG, "scheduleSync: no accounts configured, dropping"); - } - return; + } + + if (ArrayUtils.isEmpty(accounts)) { + if (isLoggable) { + Slog.v(TAG, "scheduleSync: no accounts configured, dropping"); } + return; } final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false); @@ -808,29 +829,41 @@ public class SyncManager { } for (String authority : syncableAuthorities) { - int isSyncable = getIsSyncable(account.account, account.userId, - authority); + int isSyncable = computeSyncable(account.account, account.userId, authority); + if (isSyncable == AuthorityInfo.NOT_SYNCABLE) { continue; } - final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo; - syncAdapterInfo = mSyncAdapters.getServiceInfo( - SyncAdapterType.newKey(authority, account.account.type), account.userId); + + final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo = + mSyncAdapters.getServiceInfo(SyncAdapterType.newKey(authority, + account.account.type), account.userId); if (syncAdapterInfo == null) { continue; } + final int owningUid = syncAdapterInfo.uid; - final String owningPackage = syncAdapterInfo.componentName.getPackageName(); - try { - if (ActivityManagerNative.getDefault().getAppStartMode(owningUid, - owningPackage) == ActivityManager.APP_START_MODE_DISABLED) { - Slog.w(TAG, "Not scheduling job " + syncAdapterInfo.uid + ":" - + syncAdapterInfo.componentName - + " -- package not allowed to start"); - continue; + + if (isSyncable == AuthorityInfo.SYNCABLE_NO_ACCOUNT_ACCESS) { + if (isLoggable) { + Slog.v(TAG, " Not scheduling sync operation: " + + "isSyncable == SYNCABLE_NO_ACCOUNT_ACCESS"); } - } catch (RemoteException e) { + Bundle finalExtras = new Bundle(extras); + mAccountManagerInternal.requestAccountAccess(account.account, + syncAdapterInfo.componentName.getPackageName(), + UserHandle.getUserId(owningUid), + new RemoteCallback((Bundle result) -> { + if (result != null + && result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)) { + scheduleSync(account.account, userId, reason, authority, + finalExtras, onlyThoseWithUnkownSyncableState); + } + } + )); + continue; } + final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs(); final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable(); if (isSyncable < 0 && isAlwaysSyncable) { @@ -838,6 +871,7 @@ public class SyncManager { account.account, account.userId, authority, AuthorityInfo.SYNCABLE); isSyncable = AuthorityInfo.SYNCABLE; } + if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) { continue; } @@ -863,6 +897,9 @@ public class SyncManager { account.account, authority, account.userId); long delayUntil = mSyncStorageEngine.getDelayUntilTime(info); + + final String owningPackage = syncAdapterInfo.componentName.getPackageName(); + if (isSyncable < 0) { // Initialisation sync. Bundle newExtras = new Bundle(); @@ -887,8 +924,6 @@ public class SyncManager { if (isLoggable) { Slog.v(TAG, "scheduleSync:" + " delay until " + delayUntil - + " run by " + runtimeMillis - + " flexMillis " + beforeRuntimeMillis + ", source " + source + ", account " + account + ", authority " + authority @@ -904,6 +939,56 @@ public class SyncManager { } } + public int computeSyncable(Account account, int userId, String authority) { + final int status = getIsSyncable(account, userId, authority); + if (status == AuthorityInfo.NOT_SYNCABLE) { + return AuthorityInfo.NOT_SYNCABLE; + } + final SyncAdapterType type = SyncAdapterType.newKey(authority, account.type); + final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo = + mSyncAdapters.getServiceInfo(type, userId); + if (syncAdapterInfo == null) { + return AuthorityInfo.NOT_SYNCABLE; + } + final int owningUid = syncAdapterInfo.uid; + final String owningPackage = syncAdapterInfo.componentName.getPackageName(); + try { + if (ActivityManagerNative.getDefault().getAppStartMode(owningUid, + owningPackage) == ActivityManager.APP_START_MODE_DISABLED) { + Slog.w(TAG, "Not scheduling job " + syncAdapterInfo.uid + ":" + + syncAdapterInfo.componentName + + " -- package not allowed to start"); + return AuthorityInfo.NOT_SYNCABLE; + } + } catch (RemoteException e) { + /* ignore - local call */ + } + if (!canAccessAccount(account, owningPackage, owningUid)) { + Log.w(TAG, "Access to " + account + " denied for package " + + owningPackage + " in UID " + syncAdapterInfo.uid); + return AuthorityInfo.SYNCABLE_NO_ACCOUNT_ACCESS; + } + + return status; + } + + private boolean canAccessAccount(Account account, String packageName, int uid) { + if (mAccountManager.hasAccountAccess(account, packageName, + UserHandle.getUserHandleForUid(uid))) { + return true; + } + // We relax the account access rule to also include the system apps as + // they are trusted and we want to minimize the cases where the user + // involvement is required to grant access to the synced account. + try { + mContext.getPackageManager().getApplicationInfoAsUser(packageName, + PackageManager.MATCH_SYSTEM_ONLY, UserHandle.getUserId(uid)); + return true; + } catch (NameNotFoundException e) { + return false; + } + } + private void removeSyncsForAuthority(EndPoint info) { verifyJobScheduler(); List<SyncOperation> ops = getAllPendingSyncs(); @@ -960,8 +1045,6 @@ public class SyncManager { final Bundle extras = new Bundle(); extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true); scheduleSync(account, userId, reason, authority, extras, - LOCAL_SYNC_DELAY /* earliest run time */, - 2 * LOCAL_SYNC_DELAY /* latest sync time. */, false /* onlyThoseWithUnkownSyncableState */); } @@ -1421,7 +1504,6 @@ public class SyncManager { mContext.getOpPackageName()); for (Account account : accounts) { scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null, - 0 /* no delay */, 0 /* No flexMillis */, true /* onlyThoseWithUnknownSyncableState */); } } @@ -2530,13 +2612,18 @@ public class SyncManager { } } - if (isOperationValid(op)) { - if (!dispatchSyncOperation(op)) { + final int syncOpState = computeSyncOpState(op); + switch (syncOpState) { + case SYNC_OP_STATE_INVALID_NO_ACCOUNT_ACCESS: + case SYNC_OP_STATE_INVALID: { mSyncJobService.callJobFinished(op.jobId, false); - } - } else { + } return; + } + + if (!dispatchSyncOperation(op)) { mSyncJobService.callJobFinished(op.jobId, false); } + setAuthorityPendingState(op.target); } @@ -2596,8 +2683,7 @@ public class SyncManager { if (syncTargets != null) { scheduleSync(syncTargets.account, syncTargets.userId, - SyncOperation.REASON_ACCOUNTS_UPDATED, syncTargets.provider, null, 0, 0, - true); + SyncOperation.REASON_ACCOUNTS_UPDATED, syncTargets.provider, null, true); } } @@ -2665,6 +2751,26 @@ public class SyncManager { SyncStorageEngine.SOURCE_PERIODIC, extras, syncAdapterInfo.type.allowParallelSyncs(), true, SyncOperation.NO_JOB_ID, pollFrequencyMillis, flexMillis); + + final int syncOpState = computeSyncOpState(op); + switch (syncOpState) { + case SYNC_OP_STATE_INVALID_NO_ACCOUNT_ACCESS: { + mAccountManagerInternal.requestAccountAccess(op.target.account, + op.owningPackage, UserHandle.getUserId(op.owningUid), + new RemoteCallback((Bundle result) -> { + if (result != null + && result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)) { + updateOrAddPeriodicSync(target, pollFrequency, flex, extras); + } + } + )); + } return; + + case SYNC_OP_STATE_INVALID: { + return; + } + } + scheduleSyncOperationH(op); mSyncStorageEngine.reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); } @@ -2725,29 +2831,38 @@ public class SyncManager { /** * Determine if a sync is no longer valid and should be dropped. */ - private boolean isOperationValid(SyncOperation op) { + private int computeSyncOpState(SyncOperation op) { final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); int state; final EndPoint target = op.target; - boolean syncEnabled = mSyncStorageEngine.getMasterSyncAutomatically(target.userId); + // Drop the sync if the account of this operation no longer exists. AccountAndUser[] accounts = mRunningAccounts; if (!containsAccountAndUser(accounts, target.account, target.userId)) { if (isLoggable) { Slog.v(TAG, " Dropping sync operation: account doesn't exist."); } - return false; + return SYNC_OP_STATE_INVALID; } // Drop this sync request if it isn't syncable. - state = getIsSyncable(target.account, target.userId, target.provider); - if (state == 0) { + state = computeSyncable(target.account, target.userId, target.provider); + if (state == AuthorityInfo.SYNCABLE_NO_ACCOUNT_ACCESS) { if (isLoggable) { - Slog.v(TAG, " Dropping sync operation: isSyncable == 0."); + Slog.v(TAG, " Dropping sync operation: " + + "isSyncable == SYNCABLE_NO_ACCOUNT_ACCESS"); } - return false; + return SYNC_OP_STATE_INVALID_NO_ACCOUNT_ACCESS; } - syncEnabled = syncEnabled && mSyncStorageEngine.getSyncAutomatically( - target.account, target.userId, target.provider); + if (state != AuthorityInfo.SYNCABLE) { + if (isLoggable) { + Slog.v(TAG, " Dropping sync operation: isSyncable != SYNCABLE"); + } + return SYNC_OP_STATE_INVALID; + } + + final boolean syncEnabled = mSyncStorageEngine.getMasterSyncAutomatically(target.userId) + && mSyncStorageEngine.getSyncAutomatically(target.account, + target.userId, target.provider); // We ignore system settings that specify the sync is invalid if: // 1) It's manual - we try it anyway. When/if it fails it will be rescheduled. @@ -2760,9 +2875,9 @@ public class SyncManager { if (isLoggable) { Slog.v(TAG, " Dropping sync operation: disallowed by settings/network."); } - return false; + return SYNC_OP_STATE_INVALID; } - return true; + return SYNC_OP_STATE_VALID; } private boolean dispatchSyncOperation(SyncOperation op) { diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java index bc3fc6a47aef..64849aa321da 100644 --- a/services/core/java/com/android/server/content/SyncStorageEngine.java +++ b/services/core/java/com/android/server/content/SyncStorageEngine.java @@ -234,6 +234,12 @@ public class SyncStorageEngine extends Handler { */ public static final int SYNCABLE_NOT_INITIALIZED = 2; + /** + * The adapter is syncable but does not have access to the synced account and needs a + * user access approval. + */ + public static final int SYNCABLE_NO_ACCOUNT_ACCESS = 3; + final EndPoint target; final int ident; boolean enabled; |