Only sync adapters with access can see an account - framework am: 5cb2973495

am: 721402e75a

Change-Id: I361b009afa6c6e658157e6e04bf88096e8331fd0
This commit is contained in:
Svetoslav Ganov
2016-08-24 02:08:19 +00:00
committed by android-build-merger
11 changed files with 712 additions and 107 deletions

View File

@@ -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();
}
}
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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());
}

View File

@@ -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();
}

View File

@@ -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. -->

View File

@@ -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);
@@ -3294,6 +3443,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())) {
@@ -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 (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid "
+ callerUid + ", " + account
+ ": is authenticator? " + fromAuthenticator
+ ", has explicit permission? " + hasExplicitGrants);
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;
}
return fromAuthenticator || hasExplicitGrants || isPrivileged;
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, "Access to " + account + " not granted for uid " + callerUid);
}
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);
}
}
}

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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;