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;
+    };
 }