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();