OSDN Git Service

Redact Account info from getCurrentSyncs
[android-x86/frameworks-base.git] / services / core / java / com / android / server / content / ContentService.java
index 023bf2b..f581a7f 100644 (file)
@@ -19,15 +19,19 @@ package com.android.server.content;
 import android.Manifest;
 import android.accounts.Account;
 import android.app.ActivityManager;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.IContentService;
+import android.content.Intent;
 import android.content.ISyncStatusObserver;
 import android.content.PeriodicSync;
 import android.content.SyncAdapterType;
 import android.content.SyncInfo;
 import android.content.SyncRequest;
 import android.content.SyncStatusInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.database.IContentObserver;
 import android.database.sqlite.SQLiteException;
 import android.net.Uri;
@@ -41,9 +45,9 @@ import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseIntArray;
+import com.android.server.LocalServices;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -151,6 +155,18 @@ public final class ContentService extends IContentService.Stub {
     /*package*/ ContentService(Context context, boolean factoryTest) {
         mContext = context;
         mFactoryTest = factoryTest;
+
+        // Let the package manager query for the sync adapters for a given authority
+        // as we grant default permissions to sync adapters for specific authorities.
+        PackageManagerInternal packageManagerInternal = LocalServices.getService(
+                PackageManagerInternal.class);
+        packageManagerInternal.setSyncAdapterPackagesprovider(
+                new PackageManagerInternal.SyncAdapterPackagesProvider() {
+            @Override
+            public String[] getPackages(String authority, int userId) {
+                return getSyncAdapterPackagesForAuthorityAsUser(authority, userId);
+            }
+        });
     }
 
     public void systemReady() {
@@ -161,8 +177,9 @@ public final class ContentService extends IContentService.Stub {
      * Register a content observer tied to a specific user's view of the provider.
      * @param userHandle the user whose view of the provider is to be observed.  May be
      *     the calling user without requiring any permission, otherwise the caller needs to
-     *     hold the INTERACT_ACROSS_USERS_FULL permission.  Pseudousers USER_ALL and
-     *     USER_CURRENT are properly handled; all other pseudousers are forbidden.
+     *     hold the INTERACT_ACROSS_USERS_FULL permission or hold a read uri grant to the uri.
+     *     Pseudousers USER_ALL and USER_CURRENT are properly handled; all other pseudousers
+     *     are forbidden.
      */
     @Override
     public void registerContentObserver(Uri uri, boolean notifyForDescendants,
@@ -171,9 +188,15 @@ public final class ContentService extends IContentService.Stub {
             throw new IllegalArgumentException("You must pass a valid uri and observer");
         }
 
-        final int callingUser = UserHandle.getCallingUserId();
-        if (callingUser != userHandle) {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+        final int uid = Binder.getCallingUid();
+        final int pid = Binder.getCallingPid();
+        final int callingUserHandle = UserHandle.getCallingUserId();
+        // Registering an observer for any user other than the calling user requires uri grant or
+        // cross user permission
+        if (callingUserHandle != userHandle &&
+                mContext.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                        != PackageManager.PERMISSION_GRANTED) {
+            enforceCrossUserPermission(userHandle,
                     "no permission to observe other users' provider view");
         }
 
@@ -188,7 +211,7 @@ public final class ContentService extends IContentService.Stub {
 
         synchronized (mRootNode) {
             mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode,
-                    Binder.getCallingUid(), Binder.getCallingPid(), userHandle);
+                    uid, pid, userHandle);
             if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri +
                     " with notifyForDescendants " + notifyForDescendants);
         }
@@ -214,8 +237,9 @@ public final class ContentService extends IContentService.Stub {
      * Notify observers of a particular user's view of the provider.
      * @param userHandle the user whose view of the provider is to be notified.  May be
      *     the calling user without requiring any permission, otherwise the caller needs to
-     *     hold the INTERACT_ACROSS_USERS_FULL permission.  Pseudousers USER_ALL and
-     *     USER_CURRENT are properly interpreted; no other pseudousers are allowed.
+     *     hold the INTERACT_ACROSS_USERS_FULL permission or hold a write uri grant to the uri.
+     *     Pseudousers USER_ALL and USER_CURRENT are properly interpreted; no other pseudousers are
+     *     allowed.
      */
     @Override
     public void notifyChange(Uri uri, IContentObserver observer,
@@ -226,11 +250,14 @@ public final class ContentService extends IContentService.Stub {
                     + " from observer " + observer + ", syncToNetwork " + syncToNetwork);
         }
 
-        // Notify for any user other than the caller's own requires permission.
+        final int uid = Binder.getCallingUid();
+        final int pid = Binder.getCallingPid();
         final int callingUserHandle = UserHandle.getCallingUserId();
-        if (userHandle != callingUserHandle) {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                    "no permission to notify other users");
+        // Notify for any user other than the caller requires uri grant or cross user permission
+        if (callingUserHandle != userHandle &&
+                mContext.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+                        != PackageManager.PERMISSION_GRANTED) {
+            enforceCrossUserPermission(userHandle, "no permission to notify other users");
         }
 
         // We passed the permission check; resolve pseudouser targets as appropriate
@@ -243,7 +270,6 @@ public final class ContentService extends IContentService.Stub {
             }
         }
 
-        final int uid = Binder.getCallingUid();
         // This makes it so that future permission checks will be in the context of this
         // process rather than the caller's process. We will restore this before returning.
         long identityToken = clearCallingIdentity();
@@ -257,7 +283,7 @@ public final class ContentService extends IContentService.Stub {
             for (int i=0; i<numCalls; i++) {
                 ObserverCall oc = calls.get(i);
                 try {
-                    oc.mObserver.onChange(oc.mSelfChange, uri);
+                    oc.mObserver.onChange(oc.mSelfChange, uri, userHandle);
                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
                         Log.v(TAG, "Notified " + oc.mObserver + " of " + "update at " + uri);
                     }
@@ -315,7 +341,6 @@ public final class ContentService extends IContentService.Stub {
         }
     }
 
-    @Override
     public void requestSync(Account account, String authority, Bundle extras) {
         ContentResolver.validateSyncExtrasBundle(extras);
         int userId = UserHandle.getCallingUserId();
@@ -345,47 +370,51 @@ public final class ContentService extends IContentService.Stub {
      * Depending on the request, we enqueue to suit in the SyncManager.
      * @param request The request object. Validation of this object is done by its builder.
      */
-    @Override
     public void sync(SyncRequest request) {
-        Bundle extras = request.getBundle();
-        long flextime = request.getSyncFlexTime();
-        long runAtTime = request.getSyncRunTime();
-        int userId = UserHandle.getCallingUserId();
-        int uId = Binder.getCallingUid();
+        syncAsUser(request, UserHandle.getCallingUserId());
+    }
 
+    /**
+     * If the user id supplied is different to the calling user, the caller must hold the
+     * INTERACT_ACROSS_USERS_FULL permission.
+     */
+    public void syncAsUser(SyncRequest request, int userId) {
+        enforceCrossUserPermission(userId, "no permission to request sync as user: " + userId);
+        int callerUid = Binder.getCallingUid();
         // This makes it so that future permission checks will be in the context of this
         // process rather than the caller's process. We will restore this before returning.
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
-            if (syncManager != null) {
-                if (request.hasAuthority()) {
-                    // Sync Adapter registered with the system - old API.
-                    final  Account account = request.getAccount();
-                    final String provider = request.getProvider();
-                    if (request.isPeriodic()) {
-                        mContext.enforceCallingOrSelfPermission(
-                                Manifest.permission.WRITE_SYNC_SETTINGS,
-                                "no permission to write the sync settings");
-                        if (runAtTime < 60) {
-                            Slog.w(TAG, "Requested poll frequency of " + runAtTime
-                                    + " seconds being rounded up to 60 seconds.");
-                            runAtTime = 60;
-                        }
-                        PeriodicSync syncToAdd =
-                                new PeriodicSync(account, provider, extras, runAtTime, flextime);
-                        getSyncManager().getSyncStorageEngine().addPeriodicSync(syncToAdd, userId);
-                    } else {
-                        long beforeRuntimeMillis = (flextime) * 1000;
-                        long runtimeMillis = runAtTime * 1000;
-                        syncManager.scheduleSync(
-                                account, userId, uId, provider, extras,
-                                beforeRuntimeMillis, runtimeMillis,
-                                false /* onlyThoseWithUnknownSyncableState */);
-                    }
-                } else {
-                    Log.w(TAG, "Unrecognised sync parameters, doing nothing.");
+            if (syncManager == null) {
+                return;
+            }
+
+            Bundle extras = request.getBundle();
+            long flextime = request.getSyncFlexTime();
+            long runAtTime = request.getSyncRunTime();
+            if (request.isPeriodic()) {
+                mContext.enforceCallingOrSelfPermission(
+                        Manifest.permission.WRITE_SYNC_SETTINGS,
+                        "no permission to write the sync settings");
+                SyncStorageEngine.EndPoint info;
+                info = new SyncStorageEngine.EndPoint(
+                        request.getAccount(), request.getProvider(), userId);
+                if (runAtTime < 60) {
+                    Slog.w(TAG, "Requested poll frequency of " + runAtTime
+                            + " seconds being rounded up to 60 seconds.");
+                    runAtTime = 60;
                 }
+                // Schedule periodic sync.
+                getSyncManager().getSyncStorageEngine()
+                    .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 {
             restoreCallingIdentity(identityToken);
@@ -395,39 +424,107 @@ public final class ContentService extends IContentService.Stub {
     /**
      * Clear all scheduled sync operations that match the uri and cancel the active sync
      * if they match the authority and account, if they are present.
-     * @param account filter the pending and active syncs to cancel using this account
-     * @param authority filter the pending and active syncs to cancel using this authority
+     *
+     * @param account filter the pending and active syncs to cancel using this account, or null.
+     * @param authority filter the pending and active syncs to cancel using this authority, or
+     * null.
+     * @param cname cancel syncs running on this service, or null for provider/account.
+     */
+    @Override
+    public void cancelSync(Account account, String authority, ComponentName cname) {
+        cancelSyncAsUser(account, authority, cname, UserHandle.getCallingUserId());
+    }
+
+    /**
+     * Clear all scheduled sync operations that match the uri and cancel the active sync
+     * if they match the authority and account, if they are present.
+     *
+     * <p> If the user id supplied is different to the calling user, the caller must hold the
+     * INTERACT_ACROSS_USERS_FULL permission.
+     *
+     * @param account filter the pending and active syncs to cancel using this account, or null.
+     * @param authority filter the pending and active syncs to cancel using this authority, or
+     * null.
+     * @param userId the user id for which to cancel sync operations.
+     * @param cname cancel syncs running on this service, or null for provider/account.
      */
     @Override
-    public void cancelSync(Account account, String authority) {
+    public void cancelSyncAsUser(Account account, String authority, ComponentName cname,
+            int userId) {
         if (authority != null && authority.length() == 0) {
             throw new IllegalArgumentException("Authority must be non-empty");
         }
-
-        int userId = UserHandle.getCallingUserId();
+        enforceCrossUserPermission(userId,
+                "no permission to modify the sync settings for user " + userId);
         // This makes it so that future permission checks will be in the context of this
         // process rather than the caller's process. We will restore this before returning.
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
             if (syncManager != null) {
-                syncManager.clearScheduledSyncOperations(account, userId, authority);
-                syncManager.cancelActiveSync(account, userId, authority);
+                SyncStorageEngine.EndPoint info;
+                if (cname == null) {
+                    info = new SyncStorageEngine.EndPoint(account, authority, userId);
+                } else {
+                    info = new SyncStorageEngine.EndPoint(cname, userId);
+                }
+                syncManager.clearScheduledSyncOperations(info);
+                syncManager.cancelActiveSync(info, null /* all syncs for this adapter */);
             }
         } finally {
             restoreCallingIdentity(identityToken);
         }
     }
 
+    public void cancelRequest(SyncRequest request) {
+        SyncManager syncManager = getSyncManager();
+        if (syncManager == null) return;
+        int userId = UserHandle.getCallingUserId();
+
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncStorageEngine.EndPoint info;
+            Bundle extras = new Bundle(request.getBundle());
+            Account account = request.getAccount();
+            String provider = request.getProvider();
+            info = new SyncStorageEngine.EndPoint(account, provider, userId);
+            if (request.isPeriodic()) {
+                // Remove periodic sync.
+                mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+                        "no permission to write the sync settings");
+                getSyncManager().getSyncStorageEngine().removePeriodicSync(info, extras);
+            }
+            // Cancel active syncs and clear pending syncs from the queue.
+            syncManager.cancelScheduledSyncOperation(info, extras);
+            syncManager.cancelActiveSync(info, extras);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
     /**
      * Get information about the SyncAdapters that are known to the system.
      * @return an array of SyncAdapters that have registered with the system
      */
     @Override
     public SyncAdapterType[] getSyncAdapterTypes() {
+        return getSyncAdapterTypesAsUser(UserHandle.getCallingUserId());
+    }
+
+    /**
+     * Get information about the SyncAdapters that are known to the system for a particular user.
+     *
+     * <p> If the user id supplied is different to the calling user, the caller must hold the
+     * INTERACT_ACROSS_USERS_FULL permission.
+     *
+     * @return an array of SyncAdapters that have registered with the system
+     */
+    @Override
+    public SyncAdapterType[] getSyncAdapterTypesAsUser(int userId) {
+        enforceCrossUserPermission(userId,
+                "no permission to read sync settings for user " + userId);
         // This makes it so that future permission checks will be in the context of this
         // process rather than the caller's process. We will restore this before returning.
-        final int userId = UserHandle.getCallingUserId();
         final long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
@@ -438,17 +535,42 @@ public final class ContentService extends IContentService.Stub {
     }
 
     @Override
+    public String[] getSyncAdapterPackagesForAuthorityAsUser(String authority, int userId) {
+        enforceCrossUserPermission(userId,
+                "no permission to read sync settings for user " + userId);
+        // This makes it so that future permission checks will be in the context of this
+        // process rather than the caller's process. We will restore this before returning.
+        final long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            return syncManager.getSyncAdapterPackagesForAuthorityAsUser(authority, userId);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    @Override
     public boolean getSyncAutomatically(Account account, String providerName) {
+        return getSyncAutomaticallyAsUser(account, providerName, UserHandle.getCallingUserId());
+    }
+
+    /**
+     * If the user id supplied is different to the calling user, the caller must hold the
+     * INTERACT_ACROSS_USERS_FULL permission.
+     */
+    @Override
+    public boolean getSyncAutomaticallyAsUser(Account account, String providerName, int userId) {
+        enforceCrossUserPermission(userId,
+                "no permission to read the sync settings for user " + userId);
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                 "no permission to read the sync settings");
 
-        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
             if (syncManager != null) {
-                return syncManager.getSyncStorageEngine().getSyncAutomatically(
-                        account, userId, providerName);
+                return syncManager.getSyncStorageEngine()
+                        .getSyncAutomatically(account, userId, providerName);
             }
         } finally {
             restoreCallingIdentity(identityToken);
@@ -458,19 +580,26 @@ public final class ContentService extends IContentService.Stub {
 
     @Override
     public void setSyncAutomatically(Account account, String providerName, boolean sync) {
+        setSyncAutomaticallyAsUser(account, providerName, sync, UserHandle.getCallingUserId());
+    }
+
+    @Override
+    public void setSyncAutomaticallyAsUser(Account account, String providerName, boolean sync,
+            int userId) {
         if (TextUtils.isEmpty(providerName)) {
             throw new IllegalArgumentException("Authority must be non-empty");
         }
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
+        enforceCrossUserPermission(userId,
+                "no permission to modify the sync settings for user " + userId);
 
-        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
             if (syncManager != null) {
-                syncManager.getSyncStorageEngine().setSyncAutomatically(
-                        account, userId, providerName, sync);
+                syncManager.getSyncStorageEngine().setSyncAutomatically(account, userId,
+                        providerName, sync);
             }
         } finally {
             restoreCallingIdentity(identityToken);
@@ -496,21 +625,22 @@ public final class ContentService extends IContentService.Stub {
                     + " seconds being rounded up to 60 seconds.");
             pollFrequency = 60;
         }
+        long defaultFlex = SyncStorageEngine.calculateDefaultFlexTime(pollFrequency);
 
         long identityToken = clearCallingIdentity();
         try {
-            // Add default flex time to this sync.
-            PeriodicSync syncToAdd =
-                    new PeriodicSync(account, authority, extras,
-                            pollFrequency,
-                            SyncStorageEngine.calculateDefaultFlexTime(pollFrequency));
-            getSyncManager().getSyncStorageEngine().addPeriodicSync(syncToAdd, userId);
+            SyncStorageEngine.EndPoint info =
+                    new SyncStorageEngine.EndPoint(account, authority, userId);
+            getSyncManager().getSyncStorageEngine()
+                .updateOrAddPeriodicSync(info,
+                        pollFrequency,
+                        defaultFlex,
+                        extras);
         } finally {
             restoreCallingIdentity(identityToken);
         }
     }
 
-    @Override
     public void removePeriodicSync(Account account, String authority, Bundle extras) {
         if (account == null) {
             throw new IllegalArgumentException("Account must not be null");
@@ -524,24 +654,18 @@ public final class ContentService extends IContentService.Stub {
         int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
-            PeriodicSync syncToRemove = new PeriodicSync(account, authority, extras,
-                    0 /* Not read for removal */, 0 /* Not read for removal */);
-            getSyncManager().getSyncStorageEngine().removePeriodicSync(syncToRemove, userId);
+            getSyncManager().getSyncStorageEngine()
+                .removePeriodicSync(
+                        new SyncStorageEngine.EndPoint(account, authority, userId),
+                        extras);
         } finally {
             restoreCallingIdentity(identityToken);
         }
     }
 
-    /**
-     * TODO: Implement.
-     * @param request Sync to remove.
-     */
-    public void removeSync(SyncRequest request) {
-
-    }
 
-    @Override
-    public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
+    public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName,
+            ComponentName cname) {
         if (account == null) {
             throw new IllegalArgumentException("Account must not be null");
         }
@@ -555,16 +679,25 @@ public final class ContentService extends IContentService.Stub {
         long identityToken = clearCallingIdentity();
         try {
             return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
-                    account, userId, providerName);
+                    new SyncStorageEngine.EndPoint(account, providerName, userId));
         } finally {
             restoreCallingIdentity(identityToken);
         }
     }
 
     public int getIsSyncable(Account account, String providerName) {
+        return getIsSyncableAsUser(account, providerName, UserHandle.getCallingUserId());
+    }
+
+    /**
+     * If the user id supplied is different to the calling user, the caller must hold the
+     * INTERACT_ACROSS_USERS_FULL permission.
+     */
+    public int getIsSyncableAsUser(Account account, String providerName, int userId) {
+        enforceCrossUserPermission(userId,
+                "no permission to read the sync settings for user " + userId);
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                 "no permission to read the sync settings");
-        int userId = UserHandle.getCallingUserId();
 
         long identityToken = clearCallingIdentity();
         try {
@@ -579,7 +712,6 @@ public final class ContentService extends IContentService.Stub {
         return -1;
     }
 
-    @Override
     public void setIsSyncable(Account account, String providerName, int syncable) {
         if (TextUtils.isEmpty(providerName)) {
             throw new IllegalArgumentException("Authority must not be empty");
@@ -602,10 +734,20 @@ public final class ContentService extends IContentService.Stub {
 
     @Override
     public boolean getMasterSyncAutomatically() {
+        return getMasterSyncAutomaticallyAsUser(UserHandle.getCallingUserId());
+    }
+
+    /**
+     * If the user id supplied is different to the calling user, the caller must hold the
+     * INTERACT_ACROSS_USERS_FULL permission.
+     */
+    @Override
+    public boolean getMasterSyncAutomaticallyAsUser(int userId) {
+        enforceCrossUserPermission(userId,
+                "no permission to read the sync settings for user " + userId);
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                 "no permission to read the sync settings");
 
-        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
@@ -620,10 +762,16 @@ public final class ContentService extends IContentService.Stub {
 
     @Override
     public void setMasterSyncAutomatically(boolean flag) {
+        setMasterSyncAutomaticallyAsUser(flag, UserHandle.getCallingUserId());
+    }
+
+    @Override
+    public void setMasterSyncAutomaticallyAsUser(boolean flag, int userId) {
+        enforceCrossUserPermission(userId,
+                "no permission to set the sync status for user " + userId);
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
 
-        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
@@ -635,73 +783,115 @@ public final class ContentService extends IContentService.Stub {
         }
     }
 
-    public boolean isSyncActive(Account account, String authority) {
+    public boolean isSyncActive(Account account, String authority, ComponentName cname) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
         int userId = UserHandle.getCallingUserId();
-
+        int callingUid = Binder.getCallingUid();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
-            if (syncManager != null) {
-                return syncManager.getSyncStorageEngine().isSyncActive(
-                        account, userId, authority);
+            if (syncManager == null) {
+                return false;
             }
+            return syncManager.getSyncStorageEngine().isSyncActive(
+                    new SyncStorageEngine.EndPoint(account, authority, userId));
         } finally {
             restoreCallingIdentity(identityToken);
         }
-        return false;
     }
 
     public List<SyncInfo> getCurrentSyncs() {
+        return getCurrentSyncsAsUser(UserHandle.getCallingUserId());
+    }
+
+    /**
+     * If the user id supplied is different to the calling user, the caller must hold the
+     * INTERACT_ACROSS_USERS_FULL permission.
+     */
+    public List<SyncInfo> getCurrentSyncsAsUser(int userId) {
+        enforceCrossUserPermission(userId,
+                "no permission to read the sync settings for user " + userId);
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
 
-        int userId = UserHandle.getCallingUserId();
+        final boolean canAccessAccounts =
+            mContext.checkCallingOrSelfPermission(Manifest.permission.GET_ACCOUNTS)
+                == PackageManager.PERMISSION_GRANTED;
         long identityToken = clearCallingIdentity();
         try {
-            return getSyncManager().getSyncStorageEngine().getCurrentSyncsCopy(userId);
+            return getSyncManager().getSyncStorageEngine()
+                .getCurrentSyncsCopy(userId, canAccessAccounts);
         } finally {
             restoreCallingIdentity(identityToken);
         }
     }
 
-    public SyncStatusInfo getSyncStatus(Account account, String authority) {
+    public SyncStatusInfo getSyncStatus(Account account, String authority, ComponentName cname) {
+        return getSyncStatusAsUser(account, authority, cname, UserHandle.getCallingUserId());
+    }
+
+    /**
+     * If the user id supplied is different to the calling user, the caller must hold the
+     * INTERACT_ACROSS_USERS_FULL permission.
+     */
+    public SyncStatusInfo getSyncStatusAsUser(Account account, String authority,
+            ComponentName cname, int userId) {
         if (TextUtils.isEmpty(authority)) {
             throw new IllegalArgumentException("Authority must not be empty");
         }
+
+        enforceCrossUserPermission(userId,
+                "no permission to read the sync stats for user " + userId);
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
 
-        int userId = UserHandle.getCallingUserId();
+        int callerUid = Binder.getCallingUid();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
-            if (syncManager != null) {
-                return syncManager.getSyncStorageEngine().getStatusByAccountAndAuthority(
-                        account, userId, authority);
+            if (syncManager == null) {
+                return null;
+            }
+            SyncStorageEngine.EndPoint info;
+            if (!(account == null || authority == null)) {
+                info = new SyncStorageEngine.EndPoint(account, authority, userId);
+            } else {
+                throw new IllegalArgumentException("Must call sync status with valid authority");
             }
+            return syncManager.getSyncStorageEngine().getStatusByAuthority(info);
         } finally {
             restoreCallingIdentity(identityToken);
         }
-        return null;
     }
 
-    public boolean isSyncPending(Account account, String authority) {
+    public boolean isSyncPending(Account account, String authority, ComponentName cname) {
+        return isSyncPendingAsUser(account, authority, cname, UserHandle.getCallingUserId());
+    }
+
+    @Override
+    public boolean isSyncPendingAsUser(Account account, String authority, ComponentName cname,
+            int userId) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
-
-        int userId = UserHandle.getCallingUserId();
+        enforceCrossUserPermission(userId,
+                "no permission to retrieve the sync settings for user " + userId);
+        int callerUid = Binder.getCallingUid();
         long identityToken = clearCallingIdentity();
+        SyncManager syncManager = getSyncManager();
+        if (syncManager == null) return false;
+
         try {
-            SyncManager syncManager = getSyncManager();
-            if (syncManager != null) {
-                return syncManager.getSyncStorageEngine().isSyncPending(account, userId, authority);
+            SyncStorageEngine.EndPoint info;
+            if (!(account == null || authority == null)) {
+                info = new SyncStorageEngine.EndPoint(account, authority, userId);
+            } else {
+                throw new IllegalArgumentException("Invalid authority specified");
             }
+            return syncManager.getSyncStorageEngine().isSyncPending(info);
         } finally {
             restoreCallingIdentity(identityToken);
         }
-        return false;
     }
 
     public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
@@ -735,6 +925,21 @@ public final class ContentService extends IContentService.Stub {
     }
 
     /**
+     * Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS_FULL
+     * permission, if the userHandle is not for the caller.
+     *
+     * @param userHandle the user handle of the user we want to act on behalf of.
+     * @param message the message to log on security exception.
+     */
+    private void enforceCrossUserPermission(int userHandle, String message) {
+        final int callingUser = UserHandle.getCallingUserId();
+        if (callingUser != userHandle) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+        }
+    }
+
+    /**
      * Hide this class since it is not part of api,
      * but current unittest framework requires it to be public
      * @hide