More mult-user API work.

- You can now use android:singleUser with receivers and providers.
- New API to send ordered broadcasts as a user.
- New Process.myUserHandle() API.

For now I am trying out "user handle" as the name for the numbers
representing users.

Change-Id: I754c713ab172494bb4251bc7a37a17324a2e235e
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 @@
   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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
         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 @@
     }
 
     @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 @@
     }
 
     @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 @@
 
     /** @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 @@
             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 @@
         }
     }
 
+    /** 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 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 @@
             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 @@
             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 @@
     }
 
     @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 @@
     }
 
     @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 @@
 
     /** @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 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 @@
      * {@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 @@
             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 @@
             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 @@
                 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 @@
     /** 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 @@
         pathPermissions = orig.pathPermissions;
         multiprocess = orig.multiprocess;
         initOrder = orig.initOrder;
+        flags = orig.flags;
         isSyncable = orig.isSyncable;
     }
     
@@ -112,6 +127,7 @@
         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 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 @@
         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 @@
      * 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 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 @@
         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 @@
                 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 @@
             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 @@
                 Binder.restoreCallingIdentity(origId);
             }
 
+            boolean singleton;
             if (!providerRunning) {
                 try {
                     cpi = AppGlobals.getPackageManager().
@@ -6187,7 +6199,9 @@
                 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 @@
                             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 @@
         }
     }
 
-    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 @@
         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 @@
         // 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.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 @@
                 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 @@
 
             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 @@
                             + 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 @@
                             + " 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 @@
                 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 @@
 
             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 @@
     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 @@
     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 @@
         uid = cpr.uid;
         appInfo = cpr.appInfo;
         name = cpr.name;
+        singleton = cpr.singleton;
         noReleaseNeeded = cpr.noReleaseNeeded;
     }
 
@@ -150,6 +153,9 @@
             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 @@
 
     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 @@
             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 @@
             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 @@
             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 @@
             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 @@
     }
 
     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 @@
     }
 
     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 @@
     }
 
     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 @@
             }
         }
     }
-
-
 }
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 @@
     }
 
     @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 @@
     }
 
     @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.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 @@
 
     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 @@
         });
         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..9295cf48
--- /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 @@
 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 @@
     }
 
     @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