summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt4
-rw-r--r--core/java/android/accounts/AccountManagerService.java2
-rw-r--r--core/java/android/app/ContextImpl.java3
-rw-r--r--core/java/android/content/Context.java8
-rw-r--r--core/java/android/content/ContextWrapper.java5
-rw-r--r--core/java/android/content/pm/PackageParser.java14
-rw-r--r--core/java/android/content/pm/ServiceInfo.java7
-rw-r--r--core/res/AndroidManifest.xml19
-rw-r--r--core/res/res/values/attrs_manifest.xml10
-rw-r--r--core/res/res/values/public.xml5
-rwxr-xr-xcore/res/res/values/strings.xml13
-rw-r--r--services/java/com/android/server/AppWidgetServiceImpl.java10
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java73
-rw-r--r--services/java/com/android/server/am/BroadcastFilter.java4
-rw-r--r--services/java/com/android/server/am/BroadcastQueue.java25
-rw-r--r--services/java/com/android/server/am/BroadcastRecord.java4
-rw-r--r--test-runner/src/android/test/mock/MockContext.java3
-rw-r--r--tests/ActivityTests/AndroidManifest.xml6
-rw-r--r--tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java49
-rw-r--r--tests/ActivityTests/src/com/google/android/test/activity/SingleUserService.java31
-rw-r--r--tests/ActivityTests/src/com/google/android/test/activity/UserTarget.java29
21 files changed, 275 insertions, 49 deletions
diff --git a/api/current.txt b/api/current.txt
index bf33b79..78ed837 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -62,6 +62,7 @@ package android {
field public static final java.lang.String INJECT_EVENTS = "android.permission.INJECT_EVENTS";
field public static final java.lang.String INSTALL_LOCATION_PROVIDER = "android.permission.INSTALL_LOCATION_PROVIDER";
field public static final java.lang.String INSTALL_PACKAGES = "android.permission.INSTALL_PACKAGES";
+ field public static final java.lang.String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS";
field public static final java.lang.String INTERNAL_SYSTEM_WINDOW = "android.permission.INTERNAL_SYSTEM_WINDOW";
field public static final java.lang.String INTERNET = "android.permission.INTERNET";
field public static final java.lang.String KILL_BACKGROUND_PROCESSES = "android.permission.KILL_BACKGROUND_PROCESSES";
@@ -893,6 +894,7 @@ package android {
field public static final int shownWeekCount = 16843585; // 0x1010341
field public static final int shrinkColumns = 16843082; // 0x101014a
field public static final deprecated int singleLine = 16843101; // 0x101015d
+ field public static final int singleUser = 16843711; // 0x10103bf
field public static final int smallIcon = 16843422; // 0x101029e
field public static final int smallScreens = 16843396; // 0x1010284
field public static final int smoothScrollbar = 16843313; // 0x1010231
@@ -5317,6 +5319,7 @@ package android.content {
method public abstract void revokeUriPermission(android.net.Uri, int);
method public abstract void sendBroadcast(android.content.Intent);
method public abstract void sendBroadcast(android.content.Intent, java.lang.String);
+ method public void sendBroadcastToUser(android.content.Intent, int);
method public abstract void sendOrderedBroadcast(android.content.Intent, java.lang.String);
method public abstract void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public abstract void sendStickyBroadcast(android.content.Intent);
@@ -6700,6 +6703,7 @@ package android.content.pm {
method public void dump(android.util.Printer, java.lang.String);
field public static final android.os.Parcelable.Creator CREATOR;
field public static final int FLAG_ISOLATED_PROCESS = 2; // 0x2
+ field public static final int FLAG_SINGLE_USER = 4; // 0x4
field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1
field public int flags;
field public java.lang.String permission;
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 22e454f..ba05ee7 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -896,7 +896,7 @@ public class AccountManagerService
private void sendAccountsChangedBroadcast(int userId) {
Log.i(TAG, "the accounts changed, sending broadcast of "
+ ACCOUNTS_CHANGED_INTENT.getAction());
- mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT, userId);
+ mContext.sendBroadcastToUser(ACCOUNTS_CHANGED_INTENT, userId);
}
public void clearPassword(Account account) {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 74fce62..3c8a290 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -966,9 +966,8 @@ class ContextImpl extends Context {
}
}
- /** @hide */
@Override
- public void sendBroadcast(Intent intent, int userId) {
+ public void sendBroadcastToUser(Intent intent, int userId) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
intent.setAllowFds(false);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 8597993..af8b213 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -988,12 +988,14 @@ public abstract class Context {
public abstract void sendBroadcast(Intent intent);
/**
- * Same as #sendBroadcast(Intent intent), but for a specific user. Used by the system only.
+ * Same as #sendBroadcast(Intent intent), but for a specific user. This broadcast
+ * can only be sent to receivers that are part of the calling application. It
+ * requires holding the {@link android.Manifest.permission#INTERACT_ACROSS_USERS}
+ * permission.
* @param intent the intent to broadcast
* @param userId user to send the intent to
- * @hide
*/
- public void sendBroadcast(Intent intent, int userId) {
+ public void sendBroadcastToUser(Intent intent, int userId) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 6b950e0..7738132 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -312,10 +312,9 @@ public class ContextWrapper extends Context {
mBase.sendBroadcast(intent);
}
- /** @hide */
@Override
- public void sendBroadcast(Intent intent, int userId) {
- mBase.sendBroadcast(intent, userId);
+ public void sendBroadcastToUser(Intent intent, int userId) {
+ mBase.sendBroadcastToUser(intent, userId);
}
@Override
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index f8898c1..3ce7c78 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2703,7 +2703,7 @@ public class PackageParser {
return null;
}
- final boolean setExported = sa.hasValue(
+ boolean setExported = sa.hasValue(
com.android.internal.R.styleable.AndroidManifestService_exported);
if (setExported) {
s.info.exported = sa.getBoolean(
@@ -2729,6 +2729,18 @@ public class PackageParser {
false)) {
s.info.flags |= ServiceInfo.FLAG_ISOLATED_PROCESS;
}
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestService_singleUser,
+ false)) {
+ s.info.flags |= ServiceInfo.FLAG_SINGLE_USER;
+ if (s.info.exported) {
+ Slog.w(TAG, "Service exported request ignored due to singleUser: "
+ + s.className + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ s.info.exported = false;
+ }
+ setExported = true;
+ }
sa.recycle();
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 7ee84ab..1aaceb4 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -49,6 +49,13 @@ public class ServiceInfo extends ComponentInfo
public static final int FLAG_ISOLATED_PROCESS = 0x0002;
/**
+ * Bit in {@link #flags}: If set, a single instance of the service will
+ * run for all users on the device. Set from the
+ * {@link android.R.attr#singleUser} attribute.
+ */
+ public static final int FLAG_SINGLE_USER = 0x0004;
+
+ /**
* Options that have been set in the service declaration in the
* manifest.
* These include:
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 9253f24..d636713 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -760,6 +760,25 @@
android:label="@string/permlab_getTasks"
android:description="@string/permdesc_getTasks" />
+ <!-- Allows an application to call APIs that allow it to do interactions
+ across the users on the device, using singleton services and
+ user-targeted broadcasts. This permission is not available to
+ third party applications. -->
+ <permission android:name="android.permission.INTERACT_ACROSS_USERS"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="signature|system|development"
+ android:label="@string/permlab_interactAcrossUsers"
+ android:description="@string/permdesc_interactAcrossUsers" />
+
+ <!-- @hide Fuller form of {@link android.Manifest.permission#INTERACT_ACROSS_USERS}
+ that removes restrictions on where broadcasts can be sent and allows other
+ types of interactions. -->
+ <permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="signature"
+ android:label="@string/permlab_interactAcrossUsersFull"
+ android:description="@string/permdesc_interactAcrossUsersFull" />
+
<!-- Allows an application to get full detailed information about
recently running tasks, with full fidelity to the real state.
@hide -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index f971d39..8bc1e79 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1275,6 +1275,16 @@
that is isolated from the rest of the system. The only communication
with it is through the Service API (binding and starting). -->
<attr name="isolatedProcess" format="boolean" />
+ <!-- If set to true, a single instance of this service will run for
+ all users. That instance will run as user 0, the default/primary
+ user. When the app running in processes for other users interacts
+ with this service (by binding to it, starting it, etc) they will
+ always interact with the instance running for user 0. Enabling
+ single user mode forces "exported" of the service to be false, to
+ avoid introducing multi-user security bugs. You must hold the
+ permission {@link android.Manifest.permission#INTERACT_ACROSS_USERS} in order
+ to use this feature. -->
+ <attr name="singleUser" format="boolean" />
</declare-styleable>
<!-- The <code>receiver</code> tag declares an
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index b9b8a1b..4881a94 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3660,7 +3660,7 @@
<public type="style" name="Widget.DeviceDefault.Light.MediaRouteButton" id="0x010301d8" />
<!-- ===============================================================
- Resources added in version 17 of the platform (Jelly Bean MRx?)
+ Resources added in version 17 of the platform (Jelly Bean MR1)
=============================================================== -->
<eat-comment />
<public type="attr" name="supportsRtl" />
@@ -3679,5 +3679,6 @@
<public type="attr" name="layout_alignParentEnd" />
<public type="attr" name="listPreferredItemPaddingStart" />
<public type="attr" name="listPreferredItemPaddingEnd" />
-
+ <public type="attr" name="singleUser" />
+
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b757fe8..b369744 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -543,6 +543,19 @@
about currently and recently running tasks. This may allow the app to
discover information about which applications are used on the device.</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+ <string name="permlab_interactAcrossUsers">interact across users</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_interactAcrossUsers">Allows the app to perform actions
+ across different users on the device. Malicious apps may use this to violate
+ the protection between users.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+ <string name="permlab_interactAcrossUsersFull">full license to interact across users</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_interactAcrossUsersFull">Allows all possible interactions across
+ users.</string>
+
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=50] -->
<string name="permlab_getDetailedTasks">retrieve details of running apps</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
diff --git a/services/java/com/android/server/AppWidgetServiceImpl.java b/services/java/com/android/server/AppWidgetServiceImpl.java
index 46b968a..48f967c 100644
--- a/services/java/com/android/server/AppWidgetServiceImpl.java
+++ b/services/java/com/android/server/AppWidgetServiceImpl.java
@@ -499,7 +499,7 @@ class AppWidgetServiceImpl {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED);
intent.setComponent(p.info.provider);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId);
- mContext.sendBroadcast(intent, mUserId);
+ mContext.sendBroadcastToUser(intent, mUserId);
if (p.instances.size() == 0) {
// cancel the future updates
cancelBroadcasts(p);
@@ -507,7 +507,7 @@ class AppWidgetServiceImpl {
// send the broacast saying that the provider is not in use any more
intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED);
intent.setComponent(p.info.provider);
- mContext.sendBroadcast(intent, mUserId);
+ mContext.sendBroadcastToUser(intent, mUserId);
}
}
}
@@ -880,7 +880,7 @@ class AppWidgetServiceImpl {
intent.setComponent(p.info.provider);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
- mContext.sendBroadcast(intent, mUserId);
+ mContext.sendBroadcastToUser(intent, mUserId);
}
}
@@ -1205,7 +1205,7 @@ class AppWidgetServiceImpl {
void sendEnableIntentLocked(Provider p) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
intent.setComponent(p.info.provider);
- mContext.sendBroadcast(intent, mUserId);
+ mContext.sendBroadcastToUser(intent, mUserId);
}
void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) {
@@ -1213,7 +1213,7 @@ class AppWidgetServiceImpl {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
intent.setComponent(p.info.provider);
- mContext.sendBroadcast(intent, mUserId);
+ mContext.sendBroadcastToUser(intent, mUserId);
}
}
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 26ebb98..3b4200a 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -11140,9 +11140,8 @@ public final class ActivityManagerService extends ActivityManagerNative
private ServiceLookupResult retrieveServiceLocked(Intent service,
String resolvedType, int callingPid, int callingUid, int userId) {
ServiceRecord r = null;
- if (DEBUG_SERVICE)
- Slog.v(TAG, "retrieveServiceLocked: " + service + " type=" + resolvedType
- + " callingUid=" + callingUid);
+ if (DEBUG_SERVICE) Slog.v(TAG, "retrieveServiceLocked: " + service
+ + " type=" + resolvedType + " callingUid=" + callingUid);
if (service.getComponent() != null) {
r = mServiceMap.getServiceByName(service.getComponent(), userId);
@@ -11163,14 +11162,29 @@ public final class ActivityManagerService extends ActivityManagerNative
": not found");
return null;
}
+ ComponentName name = new ComponentName(
+ sInfo.applicationInfo.packageName, sInfo.name);
if (userId > 0) {
- if (isSingleton(sInfo.processName, sInfo.applicationInfo)) {
+ if (isSingleton(sInfo.processName, sInfo.applicationInfo)
+ || (sInfo.flags&ServiceInfo.FLAG_SINGLE_USER) != 0) {
userId = 0;
+ } else if ((sInfo.flags&ServiceInfo.FLAG_SINGLE_USER) != 0) {
+ if (checkComponentPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ callingPid, callingUid, -1, true)
+ == PackageManager.PERMISSION_GRANTED) {
+ userId = 0;
+ } else {
+ String msg = "Permission Denial: Service " + name
+ + " requests FLAG_SINGLE_USER, but app does not hold "
+ + android.Manifest.permission.INTERACT_ACROSS_USERS;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
}
+ sInfo = new ServiceInfo(sInfo);
sInfo.applicationInfo = getAppInfoForUser(sInfo.applicationInfo, userId);
}
- ComponentName name = new ComponentName(
- sInfo.applicationInfo.packageName, sInfo.name);
r = mServiceMap.getServiceByName(name, userId);
if (r == null) {
Intent.FilterComparison filter = new Intent.FilterComparison(
@@ -11531,11 +11545,11 @@ public final class ActivityManagerService extends ActivityManagerNative
}
final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
- final String appName = r.processName;
+ final String procName = r.processName;
ProcessRecord app;
if (!isolated) {
- app = getProcessRecordLocked(appName, r.appInfo.uid);
+ app = getProcessRecordLocked(procName, r.appInfo.uid);
if (DEBUG_MU)
Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid + " app=" + app);
if (app != null && app.thread != null) {
@@ -11563,7 +11577,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// Not running -- get it started, and enqueue this service record
// to be executed when the app comes up.
if (app == null) {
- if ((app=startProcessLocked(appName, r.appInfo, true, intentFlags,
+ if ((app=startProcessLocked(procName, r.appInfo, true, intentFlags,
"service", r.name, false, isolated)) == null) {
Slog.w(TAG, "Unable to launch app "
+ r.appInfo.packageName + "/"
@@ -12668,6 +12682,7 @@ public final class ActivityManagerService extends ActivityManagerNative
public Intent registerReceiver(IApplicationThread caller, String callerPackage,
IIntentReceiver receiver, IntentFilter filter, String permission) {
enforceNotIsolatedCaller("registerReceiver");
+ int callingUid;
synchronized(this) {
ProcessRecord callerApp = null;
if (caller != null) {
@@ -12683,8 +12698,10 @@ public final class ActivityManagerService extends ActivityManagerNative
throw new SecurityException("Given caller package " + callerPackage
+ " is not running in process " + callerApp);
}
+ callingUid = callerApp.info.uid;
} else {
callerPackage = null;
+ callingUid = Binder.getCallingUid();
}
List allSticky = null;
@@ -12729,7 +12746,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
mRegisteredReceivers.put(receiver.asBinder(), rl);
}
- BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, permission);
+ BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
+ permission, callingUid);
rl.add(bf);
if (!bf.debugCheck()) {
Slog.w(TAG, "==> For Dynamic broadast");
@@ -12748,7 +12766,7 @@ public final class ActivityManagerService extends ActivityManagerNative
BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, null,
null, -1, -1, null, receivers, null, 0, null, null,
- false, true, true);
+ false, true, true, false);
queue.enqueueParallelBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
}
@@ -12840,7 +12858,34 @@ public final class ActivityManagerService extends ActivityManagerNative
if ((resultTo != null) && !ordered) {
Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");
}
-
+
+ boolean onlySendToCaller = false;
+
+ // If the caller is trying to send this broadcast to a different
+ // user, verify that is allowed.
+ if (UserId.getUserId(callingUid) != userId) {
+ if (checkComponentPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ callingPid, callingUid, -1, true)
+ != PackageManager.PERMISSION_GRANTED) {
+ if (checkComponentPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ callingPid, callingUid, -1, true)
+ == PackageManager.PERMISSION_GRANTED) {
+ onlySendToCaller = true;
+ } else {
+ String msg = "Permission Denial: " + intent.getAction()
+ + " broadcast from " + callerPackage
+ + " asks to send as user " + userId
+ + " but is calling from user " + UserId.getUserId(callingUid)
+ + "; this requires "
+ + android.Manifest.permission.INTERACT_ACROSS_USERS;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ }
+ }
+
// Handle special intents: if this broadcast is from the package
// manager about a package being removed, we need to remove all of
// its activities from the history stack.
@@ -13042,7 +13087,7 @@ public final class ActivityManagerService extends ActivityManagerNative
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
callerPackage, callingPid, callingUid, requiredPermission,
registeredReceivers, resultTo, resultCode, resultData, map,
- ordered, sticky, false);
+ ordered, sticky, false, onlySendToCaller);
if (DEBUG_BROADCAST) Slog.v(
TAG, "Enqueueing parallel broadcast " + r);
final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r);
@@ -13132,7 +13177,7 @@ public final class ActivityManagerService extends ActivityManagerNative
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
callerPackage, callingPid, callingUid, requiredPermission,
receivers, resultTo, resultCode, resultData, map, ordered,
- sticky, false);
+ sticky, false, onlySendToCaller);
if (DEBUG_BROADCAST) Slog.v(
TAG, "Enqueueing ordered broadcast " + r
+ ": prev had " + queue.mOrderedBroadcasts.size());
diff --git a/services/java/com/android/server/am/BroadcastFilter.java b/services/java/com/android/server/am/BroadcastFilter.java
index b49bc22..4e6d0fa 100644
--- a/services/java/com/android/server/am/BroadcastFilter.java
+++ b/services/java/com/android/server/am/BroadcastFilter.java
@@ -27,13 +27,15 @@ class BroadcastFilter extends IntentFilter {
final ReceiverList receiverList;
final String packageName;
final String requiredPermission;
+ final int owningUid;
BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList,
- String _packageName, String _requiredPermission) {
+ String _packageName, String _requiredPermission, int _owningUid) {
super(_filter);
receiverList = _receiverList;
packageName = _packageName;
requiredPermission = _requiredPermission;
+ owningUid = _owningUid;
}
public void dump(PrintWriter pw, String prefix) {
diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java
index 47b8c0a..c6d46fc 100644
--- a/services/java/com/android/server/am/BroadcastQueue.java
+++ b/services/java/com/android/server/am/BroadcastQueue.java
@@ -369,7 +369,17 @@ public class BroadcastQueue {
private final void deliverToRegisteredReceiverLocked(BroadcastRecord r,
BroadcastFilter filter, boolean ordered) {
boolean skip = false;
- if (filter.requiredPermission != null) {
+ if (r.onlySendToCaller) {
+ if (!UserId.isSameApp(r.callingUid, filter.owningUid)) {
+ Slog.w(TAG, "Permission Denial: broadcasting "
+ + r.intent.toString()
+ + " from " + r.callerPackage + " (pid="
+ + r.callingPid + ", uid=" + r.callingUid + ")"
+ + " not allowed to go to different app " + filter.owningUid);
+ skip = true;
+ }
+ }
+ if (!skip && filter.requiredPermission != null) {
int perm = mService.checkComponentPermission(filter.requiredPermission,
r.callingPid, r.callingUid, -1, true);
if (perm != PackageManager.PERMISSION_GRANTED) {
@@ -382,7 +392,7 @@ public class BroadcastQueue {
skip = true;
}
}
- if (r.requiredPermission != null) {
+ if (!skip && r.requiredPermission != null) {
int perm = mService.checkComponentPermission(r.requiredPermission,
filter.receiverList.pid, filter.receiverList.uid, -1, true);
if (perm != PackageManager.PERMISSION_GRANTED) {
@@ -651,6 +661,17 @@ public class BroadcastQueue {
(ResolveInfo)nextReceiver;
boolean skip = false;
+ if (r.onlySendToCaller) {
+ if (!UserId.isSameApp(r.callingUid, info.activityInfo.applicationInfo.uid)) {
+ Slog.w(TAG, "Permission Denial: broadcasting "
+ + r.intent.toString()
+ + " from " + r.callerPackage + " (pid="
+ + r.callingPid + ", uid=" + r.callingUid + ")"
+ + " not allowed to go to different app "
+ + info.activityInfo.applicationInfo.uid);
+ skip = true;
+ }
+ }
int perm = mService.checkComponentPermission(info.activityInfo.permission,
r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
info.activityInfo.exported);
diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java
index dd560fc..799b609 100644
--- a/services/java/com/android/server/am/BroadcastRecord.java
+++ b/services/java/com/android/server/am/BroadcastRecord.java
@@ -44,6 +44,7 @@ class BroadcastRecord extends Binder {
final boolean ordered; // serialize the send to receivers?
final boolean sticky; // originated from existing sticky data?
final boolean initialSticky; // initial broadcast from register to sticky?
+ final boolean onlySendToCaller; // only allow receipt by sender's components?
final String requiredPermission; // a permission the caller has required
final List receivers; // contains BroadcastFilter and ResolveInfo
IIntentReceiver resultTo; // who receives final result if non-null
@@ -167,7 +168,7 @@ class BroadcastRecord extends Binder {
int _callingPid, int _callingUid, String _requiredPermission,
List _receivers, IIntentReceiver _resultTo, int _resultCode,
String _resultData, Bundle _resultExtras, boolean _serialized,
- boolean _sticky, boolean _initialSticky) {
+ boolean _sticky, boolean _initialSticky, boolean _onlySendToCaller) {
queue = _queue;
intent = _intent;
callerApp = _callerApp;
@@ -183,6 +184,7 @@ class BroadcastRecord extends Binder {
ordered = _serialized;
sticky = _sticky;
initialSticky = _initialSticky;
+ onlySendToCaller = _onlySendToCaller;
nextReceiver = 0;
state = IDLE;
}
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index bf583e1..92c6676 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -284,9 +284,8 @@ public class MockContext extends Context {
throw new UnsupportedOperationException();
}
- /** @hide */
@Override
- public void sendBroadcast(Intent intent, int userId) {
+ public void sendBroadcastToUser(Intent intent, int userId) {
throw new UnsupportedOperationException();
}
diff --git a/tests/ActivityTests/AndroidManifest.xml b/tests/ActivityTests/AndroidManifest.xml
index de3b6d1..6f00095 100644
--- a/tests/ActivityTests/AndroidManifest.xml
+++ b/tests/ActivityTests/AndroidManifest.xml
@@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.REORDER_TASKS" />
<uses-permission android:name="android.permission.REMOVE_TASKS" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<application android:label="ActivityTest">
<activity android:name="ActivityTestMain">
<intent-filter>
@@ -27,5 +28,10 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+ <service android:name="SingleUserService"
+ android:singleUser="true" android:exported="true">
+ </service>
+ <receiver android:name="UserTarget">
+ </receiver>
</application>
</manifest>
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
index ae42e29..bcef2d9 100644
--- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
+++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
@@ -16,37 +16,31 @@
package com.google.android.test.activity;
-import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.app.ActivityManager;
-import android.app.ActivityThread;
import android.app.AlertDialog;
-import android.app.Application;
import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.os.Bundle;
-import android.graphics.BitmapFactory;
+import android.os.IBinder;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.ScrollView;
-import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.res.CompatibilityInfo;
-import android.util.DisplayMetrics;
import android.util.Log;
public class ActivityTestMain extends Activity {
+ static final String TAG = "ActivityTest";
+
ActivityManager mAm;
private void addThumbnail(LinearLayout container, Bitmap bm,
@@ -114,6 +108,37 @@ public class ActivityTestMain extends Activity {
return true;
}
});
+ menu.add("Bind!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override public boolean onMenuItemClick(MenuItem item) {
+ Intent intent = new Intent(ActivityTestMain.this, SingleUserService.class);
+ ServiceConnection conn = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.i(TAG, "Service connected " + name + " " + service);
+ }
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.i(TAG, "Service disconnected " + name);
+ }
+ };
+ bindService(intent, conn, Context.BIND_AUTO_CREATE);
+ return true;
+ }
+ });
+ menu.add("Start!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override public boolean onMenuItemClick(MenuItem item) {
+ Intent intent = new Intent(ActivityTestMain.this, SingleUserService.class);
+ startService(intent);
+ return true;
+ }
+ });
+ menu.add("Send!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override public boolean onMenuItemClick(MenuItem item) {
+ Intent intent = new Intent(ActivityTestMain.this, UserTarget.class);
+ sendBroadcastToUser(intent, 1);
+ return true;
+ }
+ });
return true;
}
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/SingleUserService.java b/tests/ActivityTests/src/com/google/android/test/activity/SingleUserService.java
new file mode 100644
index 0000000..c40582a
--- /dev/null
+++ b/tests/ActivityTests/src/com/google/android/test/activity/SingleUserService.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.test.activity;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+
+public class SingleUserService extends Service {
+ Binder mBinder = new Binder();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+}
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/UserTarget.java b/tests/ActivityTests/src/com/google/android/test/activity/UserTarget.java
new file mode 100644
index 0000000..9890483
--- /dev/null
+++ b/tests/ActivityTests/src/com/google/android/test/activity/UserTarget.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.test.activity;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class UserTarget extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i("ActivityTest", "Received: " + intent);
+ }
+}