Only sync adapters with access can see an account - framework am: 5cb2973495
am: 721402e75a
Change-Id: I361b009afa6c6e658157e6e04bf88096e8331fd0
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
44
core/java/android/accounts/AccountManagerInternal.java
Normal file
44
core/java/android/accounts/AccountManagerInternal.java
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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. -->
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user