summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt14
-rw-r--r--core/java/android/app/ActivityManager.java12
-rw-r--r--core/java/android/app/ContextImpl.java60
-rw-r--r--core/java/android/content/ContentProviderClient.java13
-rw-r--r--core/java/android/content/Context.java58
-rw-r--r--core/java/android/content/ContextWrapper.java22
-rw-r--r--core/java/android/content/pm/ActivityInfo.java10
-rw-r--r--core/java/android/content/pm/PackageParser.java31
-rw-r--r--core/java/android/content/pm/ProviderInfo.java22
-rw-r--r--core/java/android/content/pm/ServiceInfo.java5
-rw-r--r--core/java/android/os/Process.java15
-rw-r--r--core/res/res/values/attrs_manifest.xml30
-rw-r--r--services/java/com/android/server/am/ActiveServices.java20
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java75
-rwxr-xr-xservices/java/com/android/server/am/ActivityStack.java3
-rw-r--r--services/java/com/android/server/am/BroadcastQueue.java48
-rw-r--r--services/java/com/android/server/am/ContentProviderRecord.java8
-rw-r--r--services/java/com/android/server/am/ProviderMap.java65
-rw-r--r--test-runner/src/android/test/mock/MockContext.java17
-rw-r--r--tests/ActivityTests/AndroidManifest.xml6
-rw-r--r--tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java45
-rw-r--r--tests/ActivityTests/src/com/google/android/test/activity/SingleUserProvider.java66
-rw-r--r--tests/ActivityTests/src/com/google/android/test/activity/SingleUserReceiver.java32
-rw-r--r--tests/ActivityTests/src/com/google/android/test/activity/UserTarget.java5
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java12
25 files changed, 531 insertions, 163 deletions
diff --git a/api/current.txt b/api/current.txt
index f594205..c5768db 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5075,6 +5075,7 @@ package android.content {
public class ContentProviderClient {
method public android.content.ContentProviderResult[] applyBatch(java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException, android.os.RemoteException;
method public int bulkInsert(android.net.Uri, android.content.ContentValues[]) throws android.os.RemoteException;
+ method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle) throws android.os.RemoteException;
method public int delete(android.net.Uri, java.lang.String, java.lang.String[]) throws android.os.RemoteException;
method public android.content.ContentProvider getLocalContentProvider();
method public java.lang.String[] getStreamTypes(android.net.Uri, java.lang.String) throws android.os.RemoteException;
@@ -5319,9 +5320,10 @@ 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 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 sendOrderedBroadcastToUser(android.content.Intent, int, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public abstract void sendStickyBroadcast(android.content.Intent);
method public abstract void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public abstract void setTheme(int);
@@ -5453,8 +5455,10 @@ package android.content {
method public void revokeUriPermission(android.net.Uri, int);
method public void sendBroadcast(android.content.Intent);
method public void sendBroadcast(android.content.Intent, java.lang.String);
+ method public void sendBroadcastToUser(android.content.Intent, int);
method public void sendOrderedBroadcast(android.content.Intent, java.lang.String);
method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
+ method public void sendOrderedBroadcastToUser(android.content.Intent, int, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public void sendStickyBroadcast(android.content.Intent);
method public void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public void setTheme(int);
@@ -6235,6 +6239,7 @@ package android.content.pm {
field public static final int FLAG_HARDWARE_ACCELERATED = 512; // 0x200
field public static final int FLAG_MULTIPROCESS = 1; // 0x1
field public static final int FLAG_NO_HISTORY = 128; // 0x80
+ field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
field public static final int FLAG_STATE_NOT_NEEDED = 16; // 0x10
field public static final int LAUNCH_MULTIPLE = 0; // 0x0
field public static final int LAUNCH_SINGLE_INSTANCE = 3; // 0x3
@@ -6657,7 +6662,9 @@ package android.content.pm {
ctor public ProviderInfo(android.content.pm.ProviderInfo);
method public int describeContents();
field public static final android.os.Parcelable.Creator CREATOR;
+ field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
field public java.lang.String authority;
+ field public int flags;
field public boolean grantUriPermissions;
field public int initOrder;
field public deprecated boolean isSyncable;
@@ -6703,7 +6710,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_SINGLE_USER = 1073741824; // 0x40000000
field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1
field public int flags;
field public java.lang.String permission;
@@ -16248,6 +16255,7 @@ package android.os {
method public static final int myPid();
method public static final int myTid();
method public static final int myUid();
+ method public static final int myUserHandle();
method public static final void sendSignal(int, int);
method public static final void setThreadPriority(int, int) throws java.lang.IllegalArgumentException, java.lang.SecurityException;
method public static final void setThreadPriority(int) throws java.lang.IllegalArgumentException, java.lang.SecurityException;
@@ -21143,8 +21151,10 @@ package android.test.mock {
method public void revokeUriPermission(android.net.Uri, int);
method public void sendBroadcast(android.content.Intent);
method public void sendBroadcast(android.content.Intent, java.lang.String);
+ method public void sendBroadcastToUser(android.content.Intent, int);
method public void sendOrderedBroadcast(android.content.Intent, java.lang.String);
method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
+ method public void sendOrderedBroadcastToUser(android.content.Intent, int, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public void sendStickyBroadcast(android.content.Intent);
method public void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public void setTheme(int);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 92b6f72..1bbc49a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1840,6 +1840,18 @@ public class ActivityManager {
return PackageManager.PERMISSION_DENIED;
}
+ /** @hide */
+ public static int checkUidPermission(String permission, int uid) {
+ try {
+ return AppGlobals.getPackageManager()
+ .checkUidPermission(permission, uid);
+ } catch (RemoteException e) {
+ // Should never happen, but if it does... deny!
+ Slog.e(TAG, "PackageManager is dead?!?", e);
+ }
+ return PackageManager.PERMISSION_DENIED;
+ }
+
/**
* Returns the usage statistics of each installed package.
*
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 3c8a290..f46388f 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -967,18 +967,6 @@ class ContextImpl extends Context {
}
@Override
- public void sendBroadcastToUser(Intent intent, int userId) {
- String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
- try {
- intent.setAllowFds(false);
- ActivityManagerNative.getDefault().broadcastIntent(mMainThread.getApplicationThread(),
- intent, resolvedType, null, Activity.RESULT_OK, null, null, null, false, false,
- userId);
- } catch (RemoteException e) {
- }
- }
-
- @Override
public void sendBroadcast(Intent intent, String receiverPermission) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
@@ -1039,6 +1027,50 @@ class ContextImpl extends Context {
}
@Override
+ public void sendBroadcastToUser(Intent intent, int userHandle) {
+ String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+ try {
+ intent.setAllowFds(false);
+ ActivityManagerNative.getDefault().broadcastIntent(mMainThread.getApplicationThread(),
+ intent, resolvedType, null, Activity.RESULT_OK, null, null, null, false, false,
+ userHandle);
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public void sendOrderedBroadcastToUser(Intent intent, int userHandle,
+ BroadcastReceiver resultReceiver, Handler scheduler,
+ int initialCode, String initialData, Bundle initialExtras) {
+ IIntentReceiver rd = null;
+ if (resultReceiver != null) {
+ if (mPackageInfo != null) {
+ if (scheduler == null) {
+ scheduler = mMainThread.getHandler();
+ }
+ rd = mPackageInfo.getReceiverDispatcher(
+ resultReceiver, getOuterContext(), scheduler,
+ mMainThread.getInstrumentation(), false);
+ } else {
+ if (scheduler == null) {
+ scheduler = mMainThread.getHandler();
+ }
+ rd = new LoadedApk.ReceiverDispatcher(
+ resultReceiver, getOuterContext(), scheduler, null, false).getIIntentReceiver();
+ }
+ }
+ String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+ try {
+ intent.setAllowFds(false);
+ ActivityManagerNative.getDefault().broadcastIntent(
+ mMainThread.getApplicationThread(), intent, resolvedType, rd,
+ initialCode, initialData, initialExtras, null,
+ true, false, userHandle);
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
public void sendStickyBroadcast(Intent intent) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
@@ -1197,7 +1229,7 @@ class ContextImpl extends Context {
/** @hide */
@Override
- public boolean bindService(Intent service, ServiceConnection conn, int flags, int userId) {
+ public boolean bindService(Intent service, ServiceConnection conn, int flags, int userHandle) {
IServiceConnection sd;
if (conn == null) {
throw new IllegalArgumentException("connection is null");
@@ -1219,7 +1251,7 @@ class ContextImpl extends Context {
int res = ActivityManagerNative.getDefault().bindService(
mMainThread.getApplicationThread(), getActivityToken(),
service, service.resolveTypeIfNeeded(getContentResolver()),
- sd, flags, userId);
+ sd, flags, userHandle);
if (res < 0) {
throw new SecurityException(
"Not allowed to bind to service " + service);
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 5c315ce..204f963 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -231,6 +231,19 @@ public class ContentProviderClient {
}
}
+ /** See {@link ContentProvider#call(String, String, Bundle)} */
+ public Bundle call(String method, String arg, Bundle extras)
+ throws RemoteException {
+ try {
+ return mContentProvider.call(method, arg, extras);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ }
+ }
+
/**
* Call this to indicate to the system that the associated {@link ContentProvider} is no
* longer needed by this {@link ContentProviderClient}.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index af8b213..fca1d55 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -988,18 +988,6 @@ public abstract class Context {
public abstract void sendBroadcast(Intent intent);
/**
- * 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
- */
- public void sendBroadcastToUser(Intent intent, int userId) {
- throw new RuntimeException("Not implemented. Must override in a subclass.");
- }
-
- /**
* Broadcast the given intent to all interested BroadcastReceivers, allowing
* an optional required permission to be enforced. This
* call is asynchronous; it returns immediately, and you will continue
@@ -1097,6 +1085,48 @@ public abstract class Context {
Bundle initialExtras);
/**
+ * Same as {@link #sendBroadcast(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 userHandle User to send the intent to.
+ * @see #sendBroadcast(Intent)
+ */
+ public abstract void sendBroadcastToUser(Intent intent, int userHandle);
+
+ /**
+ * Same as
+ * {@link #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)},
+ * 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.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param userHandle User to send the intent to.
+ * @param resultReceiver Your own BroadcastReceiver to treat as the final
+ * receiver of the broadcast.
+ * @param scheduler A custom Handler with which to schedule the
+ * resultReceiver callback; if null it will be
+ * scheduled in the Context's main thread.
+ * @param initialCode An initial value for the result code. Often
+ * Activity.RESULT_OK.
+ * @param initialData An initial value for the result data. Often
+ * null.
+ * @param initialExtras An initial value for the result extras. Often
+ * null.
+ *
+ * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+ */
+ public abstract void sendOrderedBroadcastToUser(Intent intent, int userHandle,
+ BroadcastReceiver resultReceiver, Handler scheduler,
+ int initialCode, String initialData, Bundle initialExtras);
+
+ /**
* Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the
* Intent you are sending stays around after the broadcast is complete,
* so that others can quickly retrieve that data through the return
@@ -1403,11 +1433,11 @@ public abstract class Context {
int flags);
/**
- * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userId
+ * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle
* argument for use by system server and other multi-user aware code.
* @hide
*/
- public boolean bindService(Intent service, ServiceConnection conn, int flags, int userId) {
+ public boolean bindService(Intent service, ServiceConnection conn, int flags, int userHandle) {
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 7738132..af6bc11 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -313,11 +313,6 @@ public class ContextWrapper extends Context {
}
@Override
- public void sendBroadcastToUser(Intent intent, int userId) {
- mBase.sendBroadcastToUser(intent, userId);
- }
-
- @Override
public void sendBroadcast(Intent intent, String receiverPermission) {
mBase.sendBroadcast(intent, receiverPermission);
}
@@ -339,6 +334,19 @@ public class ContextWrapper extends Context {
}
@Override
+ public void sendBroadcastToUser(Intent intent, int userHandle) {
+ mBase.sendBroadcastToUser(intent, userHandle);
+ }
+
+ @Override
+ public void sendOrderedBroadcastToUser(Intent intent, int userHandle,
+ BroadcastReceiver resultReceiver, Handler scheduler,
+ int initialCode, String initialData, Bundle initialExtras) {
+ mBase.sendOrderedBroadcastToUser(intent, userHandle, resultReceiver,
+ scheduler, initialCode, initialData, initialExtras);
+ }
+
+ @Override
public void sendStickyBroadcast(Intent intent) {
mBase.sendStickyBroadcast(intent);
}
@@ -395,8 +403,8 @@ public class ContextWrapper extends Context {
/** @hide */
@Override
- public boolean bindService(Intent service, ServiceConnection conn, int flags, int userId) {
- return mBase.bindService(service, conn, flags, userId);
+ public boolean bindService(Intent service, ServiceConnection conn, int flags, int userHandle) {
+ return mBase.bindService(service, conn, flags, userHandle);
}
@Override
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 35e5610..3035729 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -172,6 +172,14 @@ public class ActivityInfo extends ComponentInfo
*/
public static final int FLAG_IMMERSIVE = 0x0400;
/**
+ * Bit in {@link #flags}: If set, a single instance of the receiver will
+ * run for all users on the device. Set from the
+ * {@link android.R.attr#singleUser} attribute. Note that this flag is
+ * only relevent for ActivityInfo structures that are describiner receiver
+ * components; it is not applied to activities.
+ */
+ public static final int FLAG_SINGLE_USER = 0x40000000;
+ /**
* Options that have been set in the activity declaration in the
* manifest.
* These include:
@@ -181,7 +189,7 @@ public class ActivityInfo extends ComponentInfo
* {@link #FLAG_STATE_NOT_NEEDED}, {@link #FLAG_EXCLUDE_FROM_RECENTS},
* {@link #FLAG_ALLOW_TASK_REPARENTING}, {@link #FLAG_NO_HISTORY},
* {@link #FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS},
- * {@link #FLAG_HARDWARE_ACCELERATED}
+ * {@link #FLAG_HARDWARE_ACCELERATED}, {@link #FLAG_SINGLE_USER}.
*/
public int flags;
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 3ce7c78..faeb082 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2040,7 +2040,7 @@ public class PackageParser {
return null;
}
- final boolean setExported = sa.hasValue(
+ boolean setExported = sa.hasValue(
com.android.internal.R.styleable.AndroidManifestActivity_exported);
if (setExported) {
a.info.exported = sa.getBoolean(
@@ -2166,6 +2166,21 @@ public class PackageParser {
a.info.configChanges = 0;
}
+ if (receiver) {
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestActivity_singleUser,
+ false)) {
+ a.info.flags |= ServiceInfo.FLAG_SINGLE_USER;
+ if (a.info.exported) {
+ Slog.w(TAG, "Activity exported request ignored due to singleUser: "
+ + a.className + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ a.info.exported = false;
+ }
+ setExported = true;
+ }
+ }
+
sa.recycle();
if (receiver && (owner.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
@@ -2475,6 +2490,20 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestProvider_initOrder,
0);
+ p.info.flags = 0;
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestProvider_singleUser,
+ false)) {
+ p.info.flags |= ProviderInfo.FLAG_SINGLE_USER;
+ if (p.info.exported) {
+ Slog.w(TAG, "Provider exported request ignored due to singleUser: "
+ + p.className + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ p.info.exported = false;
+ }
+ }
+
sa.recycle();
if ((owner.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java
index ec01775..a534176 100644
--- a/core/java/android/content/pm/ProviderInfo.java
+++ b/core/java/android/content/pm/ProviderInfo.java
@@ -73,7 +73,21 @@ public final class ProviderInfo extends ComponentInfo
/** Used to control initialization order of single-process providers
* running in the same process. Higher goes first. */
public int initOrder = 0;
-
+
+ /**
+ * Bit in {@link #flags}: If set, a single instance of the provider will
+ * run for all users on the device. Set from the
+ * {@link android.R.attr#singleUser} attribute.
+ */
+ public static final int FLAG_SINGLE_USER = 0x40000000;
+
+ /**
+ * Options that have been set in the provider declaration in the
+ * manifest.
+ * These include: {@link #FLAG_SINGLE_USER}.
+ */
+ public int flags = 0;
+
/**
* Whether or not this provider is syncable.
* @deprecated This flag is now being ignored. The current way to make a provider
@@ -95,6 +109,7 @@ public final class ProviderInfo extends ComponentInfo
pathPermissions = orig.pathPermissions;
multiprocess = orig.multiprocess;
initOrder = orig.initOrder;
+ flags = orig.flags;
isSyncable = orig.isSyncable;
}
@@ -112,6 +127,7 @@ public final class ProviderInfo extends ComponentInfo
out.writeTypedArray(pathPermissions, parcelableFlags);
out.writeInt(multiprocess ? 1 : 0);
out.writeInt(initOrder);
+ out.writeInt(flags);
out.writeInt(isSyncable ? 1 : 0);
}
@@ -127,8 +143,7 @@ public final class ProviderInfo extends ComponentInfo
};
public String toString() {
- return "ContentProviderInfo{name=" + authority + " className=" + name
- + " isSyncable=" + (isSyncable ? "true" : "false") + "}";
+ return "ContentProviderInfo{name=" + authority + " className=" + name + "}";
}
private ProviderInfo(Parcel in) {
@@ -141,6 +156,7 @@ public final class ProviderInfo extends ComponentInfo
pathPermissions = in.createTypedArray(PathPermission.CREATOR);
multiprocess = in.readInt() != 0;
initOrder = in.readInt();
+ flags = in.readInt();
isSyncable = in.readInt() != 0;
}
}
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 1aaceb4..796c2a4 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -53,13 +53,14 @@ public class ServiceInfo extends ComponentInfo
* run for all users on the device. Set from the
* {@link android.R.attr#singleUser} attribute.
*/
- public static final int FLAG_SINGLE_USER = 0x0004;
+ public static final int FLAG_SINGLE_USER = 0x40000000;
/**
* Options that have been set in the service declaration in the
* manifest.
* These include:
- * {@link #FLAG_STOP_WITH_TASK}, {@link #FLAG_ISOLATED_PROCESS}.
+ * {@link #FLAG_STOP_WITH_TASK}, {@link #FLAG_ISOLATED_PROCESS},
+ * {@link #FLAG_SINGLE_USER}.
*/
public int flags;
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index a04ad93..93860aa 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -640,11 +640,24 @@ public class Process {
public static final native int myTid();
/**
- * Returns the identifier of this process's user.
+ * Returns the identifier of this process's uid. This is the kernel uid
+ * that the process is running under, which is the identity of its
+ * app-specific sandbox. It is different from {@link #myUserHandle} in that
+ * a uid identifies a specific app sandbox in a specific user.
*/
public static final native int myUid();
/**
+ * Returns the identifier of this process's user handle. This is the
+ * user the process is running under. It is distinct from
+ * {@link #myUid()} in that a particular user will have multiple
+ * distinct apps running under it each with their own uid.
+ */
+ public static final int myUserHandle() {
+ return UserId.getUserId(myUid());
+ }
+
+ /**
* Returns whether the current process is in an isolated sandbox.
* @hide
*/
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 8bc1e79..1c3318d 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -284,7 +284,19 @@
who do not know their particular component name) and for all
content providers. -->
<attr name="exported" format="boolean" />
-
+
+ <!-- If set to true, a single instance of this component will run for
+ all users. That instance will run as user 0, the default/primary
+ user. When the app running is in processes for other users and interacts
+ with this component (by binding to a service for example) those processes will
+ always interact with the instance running for user 0. Enabling
+ single user mode forces "exported" of the component to be false, to
+ help avoid introducing multi-user security bugs. You must hold the
+ permission {@link android.Manifest.permission#INTERACT_ACROSS_USERS} in order
+ to use this feature. This flag can only be used with services,
+ receivers, and providers; it can not be used with activities. -->
+ <attr name="singleUser" format="boolean" />
+
<!-- Specify a specific process that the associated code is to run in.
Use with the application tag (to supply a default process for all
application components), or with the activity, receiver, service,
@@ -1194,6 +1206,7 @@
component specific values). -->
<attr name="enabled" />
<attr name="exported" />
+ <attr name="singleUser" />
</declare-styleable>
<!-- Attributes that can be supplied in an AndroidManifest.xml
@@ -1275,16 +1288,7 @@
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" />
+ <attr name="singleUser" />
</declare-styleable>
<!-- The <code>receiver</code> tag declares an
@@ -1318,8 +1322,9 @@
component specific values). -->
<attr name="enabled" />
<attr name="exported" />
+ <attr name="singleUser" />
</declare-styleable>
-
+
<!-- The <code>activity</code> tag declares an
{@link android.app.Activity} class that is available
as part of the package's application components, implementing
@@ -1372,6 +1377,7 @@
<attr name="hardwareAccelerated" />
<attr name="uiOptions" />
<attr name="parentActivityName" />
+ <attr name="singleUser" />
</declare-styleable>
<!-- The <code>activity-alias</code> tag declares a new
diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java
index fbbff4c..df6c51e 100644
--- a/services/java/com/android/server/am/ActiveServices.java
+++ b/services/java/com/android/server/am/ActiveServices.java
@@ -478,7 +478,8 @@ public class ActiveServices {
if (res.record == null) {
return -1;
}
- if (mAm.isSingleton(res.record.processName, res.record.appInfo)) {
+ if (mAm.isSingleton(res.record.processName, res.record.appInfo,
+ res.record.serviceInfo.name, res.record.serviceInfo.flags)) {
userId = 0;
res = retrieveServiceLocked(service, resolvedType, Binder.getCallingPid(),
Binder.getCallingUid(), 0);
@@ -787,22 +788,9 @@ public class ActiveServices {
ComponentName name = new ComponentName(
sInfo.applicationInfo.packageName, sInfo.name);
if (userId > 0) {
- if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo)
- || (sInfo.flags&ServiceInfo.FLAG_SINGLE_USER) != 0) {
+ if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo,
+ sInfo.name, sInfo.flags)) {
userId = 0;
- } else if ((sInfo.flags&ServiceInfo.FLAG_SINGLE_USER) != 0) {
- if (mAm.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 = mAm.getAppInfoForUser(sInfo.applicationInfo, userId);
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 98d3482..05f38a5 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -5936,15 +5936,26 @@ public final class ActivityManagerService extends ActivityManagerNative
Slog.v(TAG_MU, "generateApplicationProvidersLocked, app.info.uid = " + app.uid);
int userId = app.userId;
if (providers != null) {
- final int N = providers.size();
+ int N = providers.size();
for (int i=0; i<N; i++) {
ProviderInfo cpi =
(ProviderInfo)providers.get(i);
+ boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo,
+ cpi.name, cpi.flags);
+ if (singleton && UserId.getUserId(app.uid) != 0) {
+ // This is a singleton provider, but a user besides the
+ // default user is asking to initialize a process it runs
+ // in... well, no, it doesn't actually run in this process,
+ // it runs in the process of the default user. Get rid of it.
+ providers.remove(i);
+ N--;
+ continue;
+ }
ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
ContentProviderRecord cpr = mProviderMap.getProviderByClass(comp, userId);
if (cpr == null) {
- cpr = new ContentProviderRecord(this, cpi, app.info, comp);
+ cpr = new ContentProviderRecord(this, cpi, app.info, comp, singleton);
mProviderMap.putProviderByClass(comp, cpr);
}
if (DEBUG_MU)
@@ -6177,6 +6188,7 @@ public final class ActivityManagerService extends ActivityManagerNative
Binder.restoreCallingIdentity(origId);
}
+ boolean singleton;
if (!providerRunning) {
try {
cpi = AppGlobals.getPackageManager().
@@ -6187,7 +6199,9 @@ public final class ActivityManagerService extends ActivityManagerNative
if (cpi == null) {
return null;
}
- if (isSingleton(cpi.processName, cpi.applicationInfo)) {
+ singleton = isSingleton(cpi.processName, cpi.applicationInfo,
+ cpi.name, cpi.flags);
+ if (singleton) {
userId = 0;
}
cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId);
@@ -6222,7 +6236,7 @@ public final class ActivityManagerService extends ActivityManagerNative
return null;
}
ai = getAppInfoForUser(ai, userId);
- cpr = new ContentProviderRecord(this, cpi, ai, comp);
+ cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton);
} catch (RemoteException ex) {
// pm is in same process, this will never happen.
}
@@ -10515,17 +10529,31 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- boolean isSingleton(String componentProcessName, ApplicationInfo aInfo) {
+ boolean isSingleton(String componentProcessName, ApplicationInfo aInfo,
+ String className, int flags) {
boolean result = false;
if (UserId.getAppId(aInfo.uid) >= Process.FIRST_APPLICATION_UID) {
- result = false;
+ if ((flags&ServiceInfo.FLAG_SINGLE_USER) != 0) {
+ if (ActivityManager.checkUidPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ aInfo.uid) != PackageManager.PERMISSION_GRANTED) {
+ ComponentName comp = new ComponentName(aInfo.packageName, className);
+ String msg = "Permission Denial: Component " + comp.flattenToShortString()
+ + " requests FLAG_SINGLE_USER, but app does not hold "
+ + android.Manifest.permission.INTERACT_ACROSS_USERS;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ result = true;
+ }
} else if (componentProcessName == aInfo.packageName) {
result = (aInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0;
} else if ("system".equals(componentProcessName)) {
result = true;
}
if (DEBUG_MU) {
- Slog.v(TAG, "isSingleton(" + componentProcessName + ", " + aInfo + ") = " + result);
+ Slog.v(TAG, "isSingleton(" + componentProcessName + ", " + aInfo
+ + ", " + className + ", 0x" + Integer.toHexString(flags) + ") = " + result);
}
return result;
}
@@ -11131,30 +11159,15 @@ public final class ActivityManagerService extends ActivityManagerNative
List receivers = null;
List<BroadcastFilter> registeredReceivers = null;
try {
- if (intent.getComponent() != null) {
- // Broadcast is going to one specific receiver class...
- ActivityInfo ai = AppGlobals.getPackageManager().
- getReceiverInfo(intent.getComponent(), STOCK_PM_FLAGS, userId);
- if (ai != null) {
- receivers = new ArrayList();
- ResolveInfo ri = new ResolveInfo();
- if (isSingleton(ai.processName, ai.applicationInfo)) {
- ri.activityInfo = getActivityInfoForUser(ai, 0);
- } else {
- ri.activityInfo = getActivityInfoForUser(ai, userId);
- }
- receivers.add(ri);
- }
- } else {
- // Need to resolve the intent to interested receivers...
- if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)
- == 0) {
- receivers =
- AppGlobals.getPackageManager().queryIntentReceivers(
- intent, resolvedType, STOCK_PM_FLAGS, userId);
- }
- registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false,
- userId);
+ // Need to resolve the intent to interested receivers...
+ if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+ == 0) {
+ receivers = AppGlobals.getPackageManager().queryIntentReceivers(
+ intent, resolvedType, STOCK_PM_FLAGS, userId);
+ }
+ if (intent.getComponent() == null) {
+ registeredReceivers = mReceiverResolver.queryIntent(intent,
+ resolvedType, false, userId);
}
} catch (RemoteException ex) {
// pm is in same process, this will never happen.
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index c9a633e..196a259 100755
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -3014,7 +3014,8 @@ final class ActivityStack {
// Collect information about the target of the Intent.
ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags,
profileFile, profileFd, userId);
- if (aInfo != null && mService.isSingleton(aInfo.processName, aInfo.applicationInfo)) {
+ if (aInfo != null && mService.isSingleton(aInfo.processName, aInfo.applicationInfo,
+ null, 0)) {
userId = 0;
}
aInfo = mService.getActivityInfoForUser(aInfo, userId);
diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java
index c6d46fc..76ddb96 100644
--- a/services/java/com/android/server/am/BroadcastQueue.java
+++ b/services/java/com/android/server/am/BroadcastQueue.java
@@ -20,12 +20,15 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import android.app.ActivityManager;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.IIntentReceiver;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -249,7 +252,7 @@ public class BroadcastQueue {
finishReceiverLocked(br, br.resultCode, br.resultData,
br.resultExtras, br.resultAbort, true);
scheduleBroadcastsLocked();
- // We need to reset the state if we fails to start the receiver.
+ // We need to reset the state if we failed to start the receiver.
br.state = BroadcastRecord.IDLE;
throw new RuntimeException(e.getMessage());
}
@@ -659,6 +662,9 @@ public class BroadcastQueue {
ResolveInfo info =
(ResolveInfo)nextReceiver;
+ ComponentName component = new ComponentName(
+ info.activityInfo.applicationInfo.packageName,
+ info.activityInfo.name);
boolean skip = false;
if (r.onlySendToCaller) {
@@ -667,6 +673,7 @@ public class BroadcastQueue {
+ r.intent.toString()
+ " from " + r.callerPackage + " (pid="
+ r.callingPid + ", uid=" + r.callingUid + ")"
+ + " to " + component.flattenToShortString()
+ " not allowed to go to different app "
+ info.activityInfo.applicationInfo.uid);
skip = true;
@@ -682,16 +689,14 @@ public class BroadcastQueue {
+ " from " + r.callerPackage + " (pid=" + r.callingPid
+ ", uid=" + r.callingUid + ")"
+ " is not exported from uid " + info.activityInfo.applicationInfo.uid
- + " due to receiver " + info.activityInfo.packageName
- + "/" + info.activityInfo.name);
+ + " due to receiver " + component.flattenToShortString());
} else {
Slog.w(TAG, "Permission Denial: broadcasting "
+ r.intent.toString()
+ " from " + r.callerPackage + " (pid=" + r.callingPid
+ ", uid=" + r.callingUid + ")"
+ " requires " + info.activityInfo.permission
- + " due to receiver " + info.activityInfo.packageName
- + "/" + info.activityInfo.name);
+ + " due to receiver " + component.flattenToShortString());
}
skip = true;
}
@@ -707,13 +712,33 @@ public class BroadcastQueue {
if (perm != PackageManager.PERMISSION_GRANTED) {
Slog.w(TAG, "Permission Denial: receiving "
+ r.intent + " to "
- + info.activityInfo.applicationInfo.packageName
+ + component.flattenToShortString()
+ " requires " + r.requiredPermission
+ " due to sender " + r.callerPackage
+ " (uid " + r.callingUid + ")");
skip = true;
}
}
+ boolean isSingleton = false;
+ try {
+ isSingleton = mService.isSingleton(info.activityInfo.processName,
+ info.activityInfo.applicationInfo,
+ info.activityInfo.name, info.activityInfo.flags);
+ } catch (SecurityException e) {
+ Slog.w(TAG, e.getMessage());
+ skip = true;
+ }
+ if ((info.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
+ if (ActivityManager.checkUidPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ info.activityInfo.applicationInfo.uid)
+ != PackageManager.PERMISSION_GRANTED) {
+ Slog.w(TAG, "Permission Denial: Receiver " + component.flattenToShortString()
+ + " requests FLAG_SINGLE_USER, but app does not hold "
+ + android.Manifest.permission.INTERACT_ACROSS_USERS);
+ skip = true;
+ }
+ }
if (r.curApp != null && r.curApp.crashing) {
// If the target process is crashing, just skip it.
if (DEBUG_BROADCAST) Slog.v(TAG,
@@ -736,14 +761,9 @@ public class BroadcastQueue {
r.state = BroadcastRecord.APP_RECEIVE;
String targetProcess = info.activityInfo.processName;
- r.curComponent = new ComponentName(
- info.activityInfo.applicationInfo.packageName,
- info.activityInfo.name);
- if (r.callingUid != Process.SYSTEM_UID) {
- boolean isSingleton = mService.isSingleton(info.activityInfo.processName,
- info.activityInfo.applicationInfo);
- int targetUserId = isSingleton ? 0 : UserId.getUserId(r.callingUid);
- info.activityInfo = mService.getActivityInfoForUser(info.activityInfo,targetUserId);
+ r.curComponent = component;
+ if (r.callingUid != Process.SYSTEM_UID && isSingleton) {
+ info.activityInfo = mService.getActivityInfoForUser(info.activityInfo, 0);
}
r.curReceiver = info.activityInfo;
if (DEBUG_MU && r.callingUid > UserId.PER_USER_RANGE) {
diff --git a/services/java/com/android/server/am/ContentProviderRecord.java b/services/java/com/android/server/am/ContentProviderRecord.java
index fb21b06..c80d63a 100644
--- a/services/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/java/com/android/server/am/ContentProviderRecord.java
@@ -38,6 +38,7 @@ class ContentProviderRecord {
final int uid;
final ApplicationInfo appInfo;
final ComponentName name;
+ final boolean singleton;
public IContentProvider provider;
public boolean noReleaseNeeded;
// All attached clients
@@ -54,12 +55,13 @@ class ContentProviderRecord {
String shortStringName;
public ContentProviderRecord(ActivityManagerService _service, ProviderInfo _info,
- ApplicationInfo ai, ComponentName _name) {
+ ApplicationInfo ai, ComponentName _name, boolean _singleton) {
service = _service;
info = _info;
uid = ai.uid;
appInfo = ai;
name = _name;
+ singleton = _singleton;
noReleaseNeeded = uid == 0 || uid == Process.SYSTEM_UID;
}
@@ -69,6 +71,7 @@ class ContentProviderRecord {
uid = cpr.uid;
appInfo = cpr.appInfo;
name = cpr.name;
+ singleton = cpr.singleton;
noReleaseNeeded = cpr.noReleaseNeeded;
}
@@ -150,6 +153,9 @@ class ContentProviderRecord {
pw.print(prefix); pw.print("uid="); pw.print(uid);
pw.print(" provider="); pw.println(provider);
}
+ if (singleton) {
+ pw.print(prefix); pw.print("singleton="); pw.println(singleton);
+ }
pw.print(prefix); pw.print("authority="); pw.println(info.authority);
if (full) {
if (info.isSyncable || info.multiprocess || info.initOrder != 0) {
diff --git a/services/java/com/android/server/am/ProviderMap.java b/services/java/com/android/server/am/ProviderMap.java
index d148ec3..f4d0f9b 100644
--- a/services/java/com/android/server/am/ProviderMap.java
+++ b/services/java/com/android/server/am/ProviderMap.java
@@ -44,9 +44,9 @@ public class ProviderMap {
private static final boolean DBG = false;
- private final HashMap<String, ContentProviderRecord> mGlobalByName
+ private final HashMap<String, ContentProviderRecord> mSingletonByName
= new HashMap<String, ContentProviderRecord>();
- private final HashMap<ComponentName, ContentProviderRecord> mGlobalByClass
+ private final HashMap<ComponentName, ContentProviderRecord> mSingletonByClass
= new HashMap<ComponentName, ContentProviderRecord>();
private final SparseArray<HashMap<String, ContentProviderRecord>> mProvidersByNamePerUser
@@ -63,7 +63,7 @@ public class ProviderMap {
Slog.i(TAG, "getProviderByName: " + name + " , callingUid = " + Binder.getCallingUid());
}
// Try to find it in the global list
- ContentProviderRecord record = mGlobalByName.get(name);
+ ContentProviderRecord record = mSingletonByName.get(name);
if (record != null) {
return record;
}
@@ -81,7 +81,7 @@ public class ProviderMap {
Slog.i(TAG, "getProviderByClass: " + name + ", callingUid = " + Binder.getCallingUid());
}
// Try to find it in the global list
- ContentProviderRecord record = mGlobalByClass.get(name);
+ ContentProviderRecord record = mSingletonByClass.get(name);
if (record != null) {
return record;
}
@@ -95,8 +95,8 @@ public class ProviderMap {
Slog.i(TAG, "putProviderByName: " + name + " , callingUid = " + Binder.getCallingUid()
+ ", record uid = " + record.appInfo.uid);
}
- if (record.appInfo.uid < Process.FIRST_APPLICATION_UID) {
- mGlobalByName.put(name, record);
+ if (record.singleton) {
+ mSingletonByName.put(name, record);
} else {
final int userId = UserId.getUserId(record.appInfo.uid);
getProvidersByName(userId).put(name, record);
@@ -108,8 +108,8 @@ public class ProviderMap {
Slog.i(TAG, "putProviderByClass: " + name + " , callingUid = " + Binder.getCallingUid()
+ ", record uid = " + record.appInfo.uid);
}
- if (record.appInfo.uid < Process.FIRST_APPLICATION_UID) {
- mGlobalByClass.put(name, record);
+ if (record.singleton) {
+ mSingletonByClass.put(name, record);
} else {
final int userId = UserId.getUserId(record.appInfo.uid);
getProvidersByClass(userId).put(name, record);
@@ -117,10 +117,10 @@ public class ProviderMap {
}
void removeProviderByName(String name, int optionalUserId) {
- if (mGlobalByName.containsKey(name)) {
+ if (mSingletonByName.containsKey(name)) {
if (DBG)
Slog.i(TAG, "Removing from globalByName name=" + name);
- mGlobalByName.remove(name);
+ mSingletonByName.remove(name);
} else {
// TODO: Verify this works, i.e., the caller happens to be from the correct user
if (DBG)
@@ -132,10 +132,10 @@ public class ProviderMap {
}
void removeProviderByClass(ComponentName name, int optionalUserId) {
- if (mGlobalByClass.containsKey(name)) {
+ if (mSingletonByClass.containsKey(name)) {
if (DBG)
Slog.i(TAG, "Removing from globalByClass name=" + name);
- mGlobalByClass.remove(name);
+ mSingletonByClass.remove(name);
} else {
if (DBG)
Slog.i(TAG,
@@ -197,37 +197,30 @@ public class ProviderMap {
}
void dumpProvidersLocked(PrintWriter pw, boolean dumpAll) {
- boolean needSep = false;
- if (mGlobalByClass.size() > 0) {
- if (needSep)
- pw.println(" ");
- pw.println(" Published content providers (by class):");
- dumpProvidersByClassLocked(pw, dumpAll, mGlobalByClass);
+ if (mSingletonByClass.size() > 0) {
+ pw.println("");
+ pw.println(" Published single-user content providers (by class):");
+ dumpProvidersByClassLocked(pw, dumpAll, mSingletonByClass);
}
- if (mProvidersByClassPerUser.size() > 1) {
- pw.println("");
- for (int i = 0; i < mProvidersByClassPerUser.size(); i++) {
- HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.valueAt(i);
- pw.println(" User " + mProvidersByClassPerUser.keyAt(i) + ":");
- dumpProvidersByClassLocked(pw, dumpAll, map);
- pw.println(" ");
- }
- } else if (mProvidersByClassPerUser.size() == 1) {
- HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.valueAt(0);
+ pw.println("");
+ for (int i = 0; i < mProvidersByClassPerUser.size(); i++) {
+ HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.valueAt(i);
+ pw.println(" Published user " + mProvidersByClassPerUser.keyAt(i)
+ + " content providers (by class):");
dumpProvidersByClassLocked(pw, dumpAll, map);
+ pw.println(" ");
}
- needSep = true;
if (dumpAll) {
- pw.println(" ");
- pw.println(" Authority to provider mappings:");
- dumpProvidersByNameLocked(pw, mGlobalByName);
+ pw.println("");
+ pw.println(" Single-user authority to provider mappings:");
+ dumpProvidersByNameLocked(pw, mSingletonByName);
for (int i = 0; i < mProvidersByNamePerUser.size(); i++) {
- if (i > 0) {
- pw.println(" User " + mProvidersByNamePerUser.keyAt(i) + ":");
- }
+ pw.println("");
+ pw.println(" User " + mProvidersByNamePerUser.keyAt(i)
+ + " authority to provider mappings:");
dumpProvidersByNameLocked(pw, mProvidersByNamePerUser.valueAt(i));
}
}
@@ -328,6 +321,4 @@ public class ProviderMap {
}
}
}
-
-
}
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index 92c6676..9acffa3 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -285,11 +285,6 @@ public class MockContext extends Context {
}
@Override
- public void sendBroadcastToUser(Intent intent, int userId) {
- throw new UnsupportedOperationException();
- }
-
- @Override
public void sendBroadcast(Intent intent, String receiverPermission) {
throw new UnsupportedOperationException();
}
@@ -308,6 +303,18 @@ public class MockContext extends Context {
}
@Override
+ public void sendBroadcastToUser(Intent intent, int userId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void sendOrderedBroadcastToUser(Intent intent, int userId,
+ BroadcastReceiver resultReceiver, Handler scheduler,
+ int initialCode, String initialData, Bundle initialExtras) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public void sendStickyBroadcast(Intent intent) {
throw new UnsupportedOperationException();
}
diff --git a/tests/ActivityTests/AndroidManifest.xml b/tests/ActivityTests/AndroidManifest.xml
index 6f00095..9dfe4a1 100644
--- a/tests/ActivityTests/AndroidManifest.xml
+++ b/tests/ActivityTests/AndroidManifest.xml
@@ -33,5 +33,11 @@
</service>
<receiver android:name="UserTarget">
</receiver>
+ <receiver android:name="SingleUserReceiver"
+ android:singleUser="true" android:exported="true" >
+ </receiver>
+ <provider android:name="SingleUserProvider"
+ android:authorities="com.google.android.test.activity.single_user"
+ android:singleUser="true" android:exported="true" />
</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 bcef2d9..0ec1f13 100644
--- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
+++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
@@ -22,16 +22,20 @@ import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentProviderClient;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.RemoteException;
import android.graphics.Bitmap;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.ScrollView;
+import android.widget.Toast;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -43,6 +47,18 @@ public class ActivityTestMain extends Activity {
ActivityManager mAm;
+ class BroadcastResultReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Bundle res = getResultExtras(true);
+ int user = res.getInt("user", -1);
+ Toast.makeText(ActivityTestMain.this,
+ "Receiver executed as user "
+ + (user >= 0 ? Integer.toString(user) : "unknown"),
+ Toast.LENGTH_LONG).show();
+ }
+ }
+
private void addThumbnail(LinearLayout container, Bitmap bm,
final ActivityManager.RecentTaskInfo task,
final ActivityManager.TaskThumbnails thumbs, final int subIndex) {
@@ -134,8 +150,35 @@ public class ActivityTestMain extends Activity {
});
menu.add("Send!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override public boolean onMenuItemClick(MenuItem item) {
+ Intent intent = new Intent(ActivityTestMain.this, SingleUserReceiver.class);
+ sendOrderedBroadcast(intent, null, new BroadcastResultReceiver(),
+ null, Activity.RESULT_OK, null, null);
+ return true;
+ }
+ });
+ menu.add("Call!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override public boolean onMenuItemClick(MenuItem item) {
+ ContentProviderClient cpl = getContentResolver().acquireContentProviderClient(
+ SingleUserProvider.AUTHORITY);
+ Bundle res = null;
+ try {
+ res = cpl.call("getuser", null, null);
+ } catch (RemoteException e) {
+ }
+ int user = res != null ? res.getInt("user", -1) : -1;
+ Toast.makeText(ActivityTestMain.this,
+ "Provider executed as user "
+ + (user >= 0 ? Integer.toString(user) : "unknown"),
+ Toast.LENGTH_LONG).show();
+ cpl.release();
+ return true;
+ }
+ });
+ menu.add("Send to user 1!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override public boolean onMenuItemClick(MenuItem item) {
Intent intent = new Intent(ActivityTestMain.this, UserTarget.class);
- sendBroadcastToUser(intent, 1);
+ sendOrderedBroadcastToUser(intent, 1, new BroadcastResultReceiver(),
+ null, Activity.RESULT_OK, null, null);
return true;
}
});
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/SingleUserProvider.java b/tests/ActivityTests/src/com/google/android/test/activity/SingleUserProvider.java
new file mode 100644
index 0000000..83785e4
--- /dev/null
+++ b/tests/ActivityTests/src/com/google/android/test/activity/SingleUserProvider.java
@@ -0,0 +1,66 @@
+/*
+ * 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.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Process;
+
+public class SingleUserProvider extends ContentProvider {
+ static final String AUTHORITY = "com.google.android.test.activity.single_user";
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public Bundle call(String method, String arg, Bundle extras) {
+ Bundle res = new Bundle();
+ res.putInt("user", Process.myUserHandle());
+ return res;
+ }
+}
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/SingleUserReceiver.java b/tests/ActivityTests/src/com/google/android/test/activity/SingleUserReceiver.java
new file mode 100644
index 0000000..9295cf4
--- /dev/null
+++ b/tests/ActivityTests/src/com/google/android/test/activity/SingleUserReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * 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.os.Bundle;
+import android.os.Process;
+
+public class SingleUserReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Bundle res = getResultExtras(true);
+ res.putInt("user", Process.myUserHandle());
+ setResultExtras(res);
+ }
+}
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/UserTarget.java b/tests/ActivityTests/src/com/google/android/test/activity/UserTarget.java
index 9890483..9c6a9f1 100644
--- a/tests/ActivityTests/src/com/google/android/test/activity/UserTarget.java
+++ b/tests/ActivityTests/src/com/google/android/test/activity/UserTarget.java
@@ -19,11 +19,16 @@ package com.google.android.test.activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.os.Bundle;
+import android.os.Process;
import android.util.Log;
public class UserTarget extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("ActivityTest", "Received: " + intent);
+ Bundle res = getResultExtras(true);
+ res.putInt("user", Process.myUserHandle());
+ setResultExtras(res);
}
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 3ae660d..c4a6906 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -1187,6 +1187,18 @@ public final class BridgeContext extends Context {
}
@Override
+ public void sendBroadcastToUser(Intent intent, int userId) {
+ // pass
+ }
+
+ @Override
+ public void sendOrderedBroadcastToUser(Intent intent, int userId,
+ BroadcastReceiver resultReceiver, Handler scheduler,
+ int initialCode, String initialData, Bundle initialExtras) {
+ // pass
+ }
+
+ @Override
public void sendStickyBroadcast(Intent arg0) {
// pass