summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Svetoslav Ganov <svetoslavganov@google.com> 2016-08-24 02:08:19 +0000
committer android-build-merger <android-build-merger@google.com> 2016-08-24 02:08:19 +0000
commiteeeebd346a06cc1af2366b933355ac49717136fa (patch)
tree339064f0d5ac77c08fff0adf1ce8de73173cf5ba
parent73ce3f621917e87893440f68bc6b5b5d5b160e25 (diff)
parent721402e75ad4dfdd62a86b2216499609b35333de (diff)
Only sync adapters with access can see an account - framework am: 5cb2973495
am: 721402e75a Change-Id: I361b009afa6c6e658157e6e04bf88096e8331fd0
-rw-r--r--core/java/android/accounts/AccountManager.java56
-rw-r--r--core/java/android/accounts/AccountManagerInternal.java44
-rw-r--r--core/java/android/accounts/GrantCredentialsPermissionActivity.java10
-rw-r--r--core/java/android/accounts/IAccountManager.aidl10
-rw-r--r--core/java/android/content/SyncAdapterType.java21
-rw-r--r--core/java/android/content/SyncAdaptersCache.java2
-rw-r--r--core/res/AndroidManifest.xml1
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java432
-rw-r--r--services/core/java/com/android/server/content/ContentService.java6
-rw-r--r--services/core/java/com/android/server/content/SyncManager.java229
-rw-r--r--services/core/java/com/android/server/content/SyncStorageEngine.java6
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;