diff --git a/api/current.txt b/api/current.txt
index 8018950589336..ba330c63fa666 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 22e454f9dc2fd..ba05ee7c927d2 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 74fce628d72c8..3c8a29047ae62 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 8597993ffecf1..af8b213cc5c0d 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 6b950e0eaa396..7738132f186f8 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 f8898c14add14..3ce7c781ca17c 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 7ee84ab734b95..1aaceb4a431c3 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -48,6 +48,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.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 9253f24bff24f..d636713be94ae 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" />
+
+
+
+
+
+
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index f971d390719db..8bc1e79c6573e 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). -->
+
+
@@ -3679,5 +3679,6 @@
-
+
+
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b757fe88bfd28..b369744af9ec8 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.
+
+ interact across users
+
+ Allows the app to perform actions
+ across different users on the device. Malicious apps may use this to violate
+ the protection between users.
+
+
+ full license to interact across users
+
+ Allows all possible interactions across
+ users.
+
retrieve details of running apps
diff --git a/services/java/com/android/server/AppWidgetServiceImpl.java b/services/java/com/android/server/AppWidgetServiceImpl.java
index 46b968ad22d13..48f967c4a4be3 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 26ebb98ee3640..3b4200ab02a1c 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;
}
- if (userId > 0) {
- if (isSingleton(sInfo.processName, sInfo.applicationInfo)) {
- userId = 0;
- }
- sInfo.applicationInfo = getAppInfoForUser(sInfo.applicationInfo, userId);
- }
ComponentName name = new ComponentName(
sInfo.applicationInfo.packageName, sInfo.name);
+ if (userId > 0) {
+ 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);
+ }
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 b49bc220ac03e..4e6d0fad05b27 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 47b8c0aa98479..c6d46fce656c7 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 dd560fc5e7b93..799b609be3109 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 bf583e1ca7746..92c66764a6029 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 de3b6d15b02fc..6f00095d1acf0 100644
--- a/tests/ActivityTests/AndroidManifest.xml
+++ b/tests/ActivityTests/AndroidManifest.xml
@@ -20,6 +20,7 @@
+
@@ -27,5 +28,10 @@
+
+
+
+
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 ae42e29c5aac3..bcef2d9d93548 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 0000000000000..c40582a9c8ce6
--- /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 0000000000000..9890483bf6095
--- /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);
+ }
+}