Expose per-user APIs for content services.

Bug: 15466880
Change-Id: Ib5a030e78559307627fe0d2e80ce6f1a7825109d
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 13b922c..cb48e58 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -621,10 +621,15 @@
     }
 
     @Override
-    public ProviderInfo resolveContentProvider(String name,
-                                               int flags) {
+    public ProviderInfo resolveContentProvider(String name, int flags) {
+        return resolveContentProviderAsUser(name, flags, mContext.getUserId());
+    }
+
+    /** @hide **/
+    @Override
+    public ProviderInfo resolveContentProviderAsUser(String name, int flags, int userId) {
         try {
-            return mPM.resolveContentProvider(name, flags, mContext.getUserId());
+            return mPM.resolveContentProvider(name, flags, userId);
         } catch (RemoteException e) {
             throw new RuntimeException("Package manager has died", e);
         }
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 392bfbc..be70411 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -1751,6 +1751,15 @@
      * @param extras any extras to pass to the SyncAdapter.
      */
     public static void requestSync(Account account, String authority, Bundle extras) {
+        requestSyncAsUser(account, authority, UserHandle.getCallingUserId(), extras);
+    }
+
+    /**
+     * @see #requestSync(Account, String, Bundle)
+     * @hide
+     */
+    public static void requestSyncAsUser(Account account, String authority, int userId,
+            Bundle extras) {
         if (extras == null) {
             throw new IllegalArgumentException("Must specify extras.");
         }
@@ -1760,7 +1769,11 @@
                 .setExtras(extras)
                 .syncOnce()     // Immediate sync.
                 .build();
-        requestSync(request);
+        try {
+            getContentService().syncAsUser(request, userId);
+        } catch(RemoteException e) {
+            // Shouldn't happen.
+        }
     }
 
     /**
@@ -1839,6 +1852,17 @@
     }
 
     /**
+     * @see #cancelSync(Account, String)
+     * @hide
+     */
+    public static void cancelSyncAsUser(Account account, String authority, int userId) {
+        try {
+            getContentService().cancelSyncAsUser(account, authority, null, userId);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
      * Get information about the SyncAdapters that are known to the system.
      * @return an array of SyncAdapters that have registered with the system
      */
@@ -1851,6 +1875,18 @@
     }
 
     /**
+     * @see #getSyncAdapterTypes()
+     * @hide
+     */
+    public static SyncAdapterType[] getSyncAdapterTypesAsUser(int userId) {
+        try {
+            return getContentService().getSyncAdapterTypesAsUser(userId);
+        } catch (RemoteException e) {
+            throw new RuntimeException("the ContentService should always be reachable", e);
+        }
+    }
+
+    /**
      * Check if the provider should be synced when a network tickle is received
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#READ_SYNC_SETTINGS}.
@@ -1868,6 +1904,19 @@
     }
 
     /**
+     * @see #getSyncAutomatically(Account, String)
+     * @hide
+     */
+    public static boolean getSyncAutomaticallyAsUser(Account account, String authority,
+            int userId) {
+        try {
+            return getContentService().getSyncAutomaticallyAsUser(account, authority, userId);
+        } catch (RemoteException e) {
+            throw new RuntimeException("the ContentService should always be reachable", e);
+        }
+    }
+
+    /**
      * Set whether or not the provider is synced when it receives a network tickle.
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}.
@@ -2032,6 +2081,18 @@
     }
 
     /**
+     * @see #getIsSyncable(Account, String)
+     * @hide
+     */
+    public static int getIsSyncableAsUser(Account account, String authority, int userId) {
+        try {
+            return getContentService().getIsSyncableAsUser(account, authority, userId);
+        } catch (RemoteException e) {
+            throw new RuntimeException("the ContentService should always be reachable", e);
+        }
+    }
+
+    /**
      * Set whether this account/provider is syncable.
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}.
@@ -2063,6 +2124,18 @@
     }
 
     /**
+     * @see #getMasterSyncAutomatically()
+     * @hide
+     */
+    public static boolean getMasterSyncAutomaticallyAsUser(int userId) {
+        try {
+            return getContentService().getMasterSyncAutomaticallyAsUser(userId);
+        } catch (RemoteException e) {
+            throw new RuntimeException("the ContentService should always be reachable", e);
+        }
+    }
+
+    /**
      * Sets the master auto-sync setting that applies to all the providers and accounts.
      * If this is false then the per-provider auto-sync setting is ignored.
      * <p>This method requires the caller to hold the permission
@@ -2147,6 +2220,18 @@
     }
 
     /**
+     * @see #getCurrentSyncs()
+     * @hide
+     */
+    public static List<SyncInfo> getCurrentSyncsAsUser(int userId) {
+        try {
+            return getContentService().getCurrentSyncsAsUser(userId);
+        } catch (RemoteException e) {
+            throw new RuntimeException("the ContentService should always be reachable", e);
+        }
+    }
+
+    /**
      * Returns the status that matches the authority.
      * @param account the account whose setting we are querying
      * @param authority the provider whose behavior is being queried
@@ -2162,6 +2247,19 @@
     }
 
     /**
+     * @see #getSyncStatus(Account, String)
+     * @hide
+     */
+    public static SyncStatusInfo getSyncStatusAsUser(Account account, String authority,
+            int userId) {
+        try {
+            return getContentService().getSyncStatusAsUser(account, authority, null, userId);
+        } catch (RemoteException e) {
+            throw new RuntimeException("the ContentService should always be reachable", e);
+        }
+    }
+
+    /**
      * Return true if the pending status is true of any matching authorities.
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#READ_SYNC_STATS}.
diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl
index 373f2fb..1e96713 100644
--- a/core/java/android/content/IContentService.aidl
+++ b/core/java/android/content/IContentService.aidl
@@ -60,7 +60,9 @@
      * Start a sync given a request.
      */
     void sync(in SyncRequest request);
+    void syncAsUser(in SyncRequest request, int userId);
     void cancelSync(in Account account, String authority, in ComponentName cname);
+    void cancelSyncAsUser(in Account account, String authority, in ComponentName cname, int userId);
 
     /** Cancel a sync, providing information about the sync to be cancelled. */
      void cancelRequest(in SyncRequest request);
@@ -71,6 +73,7 @@
      * @return true if the provider should be synced when a network tickle is received
      */
     boolean getSyncAutomatically(in Account account, String providerName);
+    boolean getSyncAutomaticallyAsUser(in Account account, String providerName, int userId);
 
     /**
      * Set whether or not the provider is synced when it receives a network tickle.
@@ -114,6 +117,7 @@
      * @return >0 if it is syncable, 0 if not, and <0 if the state isn't known yet.
      */
     int getIsSyncable(in Account account, String providerName);
+    int getIsSyncableAsUser(in Account account, String providerName, int userId);
 
     /**
      * Set whether this account/provider is syncable.
@@ -124,14 +128,17 @@
     void setMasterSyncAutomatically(boolean flag);
 
     boolean getMasterSyncAutomatically();
+    boolean getMasterSyncAutomaticallyAsUser(int userId);
 
     List<SyncInfo> getCurrentSyncs();
+    List<SyncInfo> getCurrentSyncsAsUser(int userId);
 
     /**
      * Returns the types of the SyncAdapters that are registered with the system.
      * @return Returns the types of the SyncAdapters that are registered with the system.
      */
     SyncAdapterType[] getSyncAdapterTypes();
+    SyncAdapterType[] getSyncAdapterTypesAsUser(int userId);
 
     /**
      * Returns true if there is currently a operation for the given account/authority or service
@@ -152,6 +159,8 @@
      * non-null.
      */
     SyncStatusInfo getSyncStatus(in Account account, String authority, in ComponentName cname);
+    SyncStatusInfo getSyncStatusAsUser(in Account account, String authority, in ComponentName cname,
+            int userId);
 
     /**
      * Return true if the pending status is true of any matching authorities.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 720315d..9ac433f 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2478,6 +2478,19 @@
             int flags);
 
     /**
+     * Find a single content provider by its base path name.
+     *
+     * @param name The name of the provider to find.
+     * @param flags Additional option flags.  Currently should always be 0.
+     * @param userId The user id.
+     *
+     * @return ContentProviderInfo Information about the provider, if found,
+     *         else null.
+     * @hide
+     */
+    public abstract ProviderInfo resolveContentProviderAsUser(String name, int flags, int userId);
+
+    /**
      * Retrieve content provider information.
      *
      * <p><em>Note: unlike most other methods, an empty result set is indicated
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 3b55bfc..a6485e9 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -172,11 +172,8 @@
             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,
-                    "no permission to observe other users' provider view");
-        }
+        enforceCrossUserPermission(userHandle,
+                "no permission to observe other users' provider view");
 
         if (userHandle < 0) {
             if (userHandle == UserHandle.USER_CURRENT) {
@@ -346,7 +343,15 @@
      * @param request The request object. Validation of this object is done by its builder.
      */
     public void sync(SyncRequest request) {
-        int userId = UserHandle.getCallingUserId();
+        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.
@@ -399,11 +404,30 @@
      */
     @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 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();
@@ -456,9 +480,23 @@
      */
     @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();
@@ -470,10 +508,20 @@
 
     @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();
@@ -588,9 +636,18 @@
     }
 
     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 {
@@ -627,10 +684,20 @@
 
     @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();
@@ -679,10 +746,19 @@
     }
 
     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();
         long identityToken = clearCallingIdentity();
         try {
             return getSyncManager().getSyncStorageEngine().getCurrentSyncsCopy(userId);
@@ -692,13 +768,24 @@
     }
 
     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 {
@@ -772,6 +859,21 @@
     }
 
     /**
+     * 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
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 1d10729..59036da 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -305,6 +305,12 @@
         throw new UnsupportedOperationException();
     }
 
+    /** @hide */
+    @Override
+    public ProviderInfo resolveContentProviderAsUser(String name, int flags, int userId) {
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public List<ProviderInfo> queryContentProviders(String processName, int uid, int flags) {
         throw new UnsupportedOperationException();