Only sync adapters with access can see an account - settings
This change ensures that when the user tries to toggle a sync for a
sync adapter that doesn't have access to an account we show UI for
the user to approve the sync.
bug:28163381
Change-Id: I59802df6614572cf0eaf4b72a177beb111b87b34
diff --git a/src/com/android/settings/accounts/AccountSyncSettings.java b/src/com/android/settings/accounts/AccountSyncSettings.java
index fc760e6..8ceb5e5 100644
--- a/src/com/android/settings/accounts/AccountSyncSettings.java
+++ b/src/com/android/settings/accounts/AccountSyncSettings.java
@@ -28,9 +28,12 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentSender;
import android.content.SyncAdapterType;
import android.content.SyncInfo;
import android.content.SyncStatusInfo;
+import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.UserInfo;
import android.os.Binder;
@@ -58,7 +61,6 @@
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Date;
import java.util.List;
@@ -73,6 +75,7 @@
private static final int REALLY_REMOVE_DIALOG = 100;
private static final int FAILED_REMOVAL_DIALOG = 101;
private static final int CANT_DO_ONETIME_SYNC_DIALOG = 102;
+
private TextView mUserId;
private TextView mProviderId;
private ImageView mProviderIcon;
@@ -234,13 +237,15 @@
mAuthenticatorHelper.stopListeningToAccountUpdates();
}
- private void addSyncStateSwitch(Account account, String authority) {
+ private void addSyncStateSwitch(Account account, String authority,
+ String packageName, int uid) {
SyncStateSwitchPreference item = (SyncStateSwitchPreference) getCachedPreference(authority);
if (item == null) {
- item = new SyncStateSwitchPreference(getPrefContext(), account, authority);
+ item = new SyncStateSwitchPreference(getPrefContext(), account, authority,
+ packageName, uid);
getPreferenceScreen().addPreference(item);
} else {
- item.setup(account, authority);
+ item.setup(account, authority, packageName, uid);
}
item.setPersistent(false);
final ProviderInfo providerInfo = getPackageManager().resolveContentProviderAsUser(
@@ -323,20 +328,56 @@
}
@Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode == Activity.RESULT_OK) {
+ final int uid = requestCode;
+ final int count = getPreferenceScreen().getPreferenceCount();
+ for (int i = 0; i < count; i++) {
+ Preference preference = getPreferenceScreen().getPreference(i);
+ if (preference instanceof SyncStateSwitchPreference) {
+ SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) preference;
+ if (syncPref.getUid() == uid) {
+ onPreferenceTreeClick(syncPref);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ @Override
public boolean onPreferenceTreeClick(Preference preference) {
+ if (getActivity() == null) {
+ return false;
+ }
if (preference instanceof SyncStateSwitchPreference) {
SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) preference;
String authority = syncPref.getAuthority();
Account account = syncPref.getAccount();
final int userId = mUserHandle.getIdentifier();
+ String packageName = syncPref.getPackageName();
+
boolean syncAutomatically = ContentResolver.getSyncAutomaticallyAsUser(account,
authority, userId);
if (syncPref.isOneTimeSyncMode()) {
+ // If the sync adapter doesn't have access to the account we either
+ // request access by starting an activity if possible or kick off the
+ // sync which will end up posting an access request notification.
+ if (requestAccountAccessIfNeeded(packageName)) {
+ return true;
+ }
requestOrCancelSync(account, authority, true);
} else {
boolean syncOn = syncPref.isChecked();
boolean oldSyncState = syncAutomatically;
if (syncOn != oldSyncState) {
+ // Toggling this switch triggers sync but we may need a user approval.
+ // If the sync adapter doesn't have access to the account we either
+ // request access by starting an activity if possible or kick off the
+ // sync which will end up posting an access request notification.
+ if (syncOn && requestAccountAccessIfNeeded(packageName)) {
+ return true;
+ }
// if we're enabling sync, this will request a sync as well
ContentResolver.setSyncAutomaticallyAsUser(account, authority, syncOn, userId);
// if the master sync switch is off, the request above will
@@ -353,6 +394,36 @@
}
}
+ private boolean requestAccountAccessIfNeeded(String packageName) {
+ if (packageName == null) {
+ return false;
+ }
+
+ final int uid;
+ try {
+ uid = getContext().getPackageManager().getPackageUidAsUser(
+ packageName, mUserHandle.getIdentifier());
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Invalid sync ", e);
+ return false;
+ }
+
+ AccountManager accountManager = getContext().getSystemService(AccountManager.class);
+ if (!accountManager.hasAccountAccess(mAccount, packageName, mUserHandle)) {
+ IntentSender intent = accountManager.createRequestAccountAccessIntentSenderAsUser(
+ mAccount, packageName, mUserHandle);
+ if (intent != null) {
+ try {
+ startIntentSenderForResult(intent, uid, null, 0, 0, 0, null);
+ return true;
+ } catch (IntentSender.SendIntentException e) {
+ Log.e(TAG, "Error requesting account access", e);
+ }
+ }
+ }
+ return false;
+ }
+
private void startSyncForEnabledProviders() {
requestOrCancelSyncForEnabledProviders(true /* start them */);
final Activity activity = getActivity();
@@ -520,7 +591,7 @@
SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
mUserHandle.getIdentifier());
- ArrayList<String> authorities = new ArrayList<String>();
+ ArrayList<SyncAdapterType> authorities = new ArrayList<>();
for (int i = 0, n = syncAdapters.length; i < n; i++) {
final SyncAdapterType sa = syncAdapters[i];
// Only keep track of sync adapters for this account
@@ -530,7 +601,7 @@
Log.d(TAG, "updateAccountSwitches: added authority " + sa.authority
+ " to accountType " + sa.accountType);
}
- authorities.add(sa.authority);
+ authorities.add(sa);
} else {
// keep track of invisible sync adapters, so sync now forces
// them to sync as well.
@@ -543,15 +614,23 @@
}
cacheRemoveAllPrefs(getPreferenceScreen());
for (int j = 0, m = authorities.size(); j < m; j++) {
- final String authority = authorities.get(j);
+ final SyncAdapterType syncAdapter = authorities.get(j);
// We could check services here....
- int syncState = ContentResolver.getIsSyncableAsUser(mAccount, authority,
+ int syncState = ContentResolver.getIsSyncableAsUser(mAccount, syncAdapter.authority,
mUserHandle.getIdentifier());
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.d(TAG, " found authority " + authority + " " + syncState);
+ Log.d(TAG, " found authority " + syncAdapter.authority + " " + syncState);
}
if (syncState > 0) {
- addSyncStateSwitch(mAccount, authority);
+ final int uid;
+ try {
+ uid = getContext().getPackageManager().getPackageUidAsUser(
+ syncAdapter.getPackageName(), mUserHandle.getIdentifier());
+ addSyncStateSwitch(mAccount, syncAdapter.authority,
+ syncAdapter.getPackageName(), uid);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "No uid for package" + syncAdapter.getPackageName(), e);
+ }
}
}
removeCachedPrefs(getPreferenceScreen());
diff --git a/src/com/android/settings/accounts/SyncStateSwitchPreference.java b/src/com/android/settings/accounts/SyncStateSwitchPreference.java
index c6632d0..058fedd 100644
--- a/src/com/android/settings/accounts/SyncStateSwitchPreference.java
+++ b/src/com/android/settings/accounts/SyncStateSwitchPreference.java
@@ -36,6 +36,8 @@
private boolean mFailed = false;
private Account mAccount;
private String mAuthority;
+ private String mPackageName;
+ private int mUid;
/**
* A mode for this preference where clicking does a one-time sync instead of
@@ -47,16 +49,21 @@
super(context, attrs, 0, R.style.SyncSwitchPreference);
mAccount = null;
mAuthority = null;
+ mPackageName = null;
+ mUid = 0;
}
- public SyncStateSwitchPreference(Context context, Account account, String authority) {
+ public SyncStateSwitchPreference(Context context, Account account, String authority,
+ String packageName, int uid) {
super(context, null, 0, R.style.SyncSwitchPreference);
- setup(account, authority);
+ setup(account, authority, packageName, uid);
}
- public void setup(Account account, String authority) {
+ public void setup(Account account, String authority, String packageName, int uid) {
mAccount = account;
mAuthority = authority;
+ mPackageName = packageName;
+ mUid = uid;
notifyChanged();
}
@@ -152,4 +159,12 @@
public String getAuthority() {
return mAuthority;
}
+
+ public String getPackageName() {
+ return mPackageName;
+ };
+
+ public int getUid() {
+ return mUid;
+ };
}