Add APIs for interacting across users.

- Expose the existing Context.sendBroadcast() as
  Context.sendBroadcastAsUser().
- Add new android:singleUser attribute for services.
- Add new INTERACT_ACROSS_USERS_FULL permission for full
  system-level access to cross-user interface (allows
  sendBroadcastAsUser() to send to any receiver).
- Add new INTERACT_ACROSS_USERS_FULL permission for
  more restricted cross-user interaction: this is required
  for android:singleUser, and allows you to use
  sendBroadcastAsUser() but only to send to your own
  receivers.

Change-Id: I0de88f6718e9505f4de72e3f45d29c0f503b76e9
diff --git a/services/java/com/android/server/AppWidgetServiceImpl.java b/services/java/com/android/server/AppWidgetServiceImpl.java
index 46b968a..48f967c 100644
--- a/services/java/com/android/server/AppWidgetServiceImpl.java
+++ b/services/java/com/android/server/AppWidgetServiceImpl.java
@@ -499,7 +499,7 @@
                 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED);
                 intent.setComponent(p.info.provider);
                 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId);
-                mContext.sendBroadcast(intent, mUserId);
+                mContext.sendBroadcastToUser(intent, mUserId);
                 if (p.instances.size() == 0) {
                     // cancel the future updates
                     cancelBroadcasts(p);
@@ -507,7 +507,7 @@
                     // send the broacast saying that the provider is not in use any more
                     intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED);
                     intent.setComponent(p.info.provider);
-                    mContext.sendBroadcast(intent, mUserId);
+                    mContext.sendBroadcastToUser(intent, mUserId);
                 }
             }
         }
@@ -880,7 +880,7 @@
             intent.setComponent(p.info.provider);
             intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId);
             intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
-            mContext.sendBroadcast(intent, mUserId);
+            mContext.sendBroadcastToUser(intent, mUserId);
         }
     }
 
@@ -1205,7 +1205,7 @@
     void sendEnableIntentLocked(Provider p) {
         Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
         intent.setComponent(p.info.provider);
-        mContext.sendBroadcast(intent, mUserId);
+        mContext.sendBroadcastToUser(intent, mUserId);
     }
 
     void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) {
@@ -1213,7 +1213,7 @@
             Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
             intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
             intent.setComponent(p.info.provider);
-            mContext.sendBroadcast(intent, mUserId);
+            mContext.sendBroadcastToUser(intent, mUserId);
         }
     }
 
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 26ebb98..3b4200a 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -11140,9 +11140,8 @@
     private ServiceLookupResult retrieveServiceLocked(Intent service,
             String resolvedType, int callingPid, int callingUid, int userId) {
         ServiceRecord r = null;
-        if (DEBUG_SERVICE)
-            Slog.v(TAG, "retrieveServiceLocked: " + service + " type=" + resolvedType
-                    + " callingUid=" + callingUid);
+        if (DEBUG_SERVICE) Slog.v(TAG, "retrieveServiceLocked: " + service
+                + " type=" + resolvedType + " callingUid=" + callingUid);
 
         if (service.getComponent() != null) {
             r = mServiceMap.getServiceByName(service.getComponent(), userId);
@@ -11163,14 +11162,29 @@
                           ": not found");
                     return null;
                 }
-                if (userId > 0) {
-                    if (isSingleton(sInfo.processName, sInfo.applicationInfo)) {
-                        userId = 0;
-                    }
-                    sInfo.applicationInfo = getAppInfoForUser(sInfo.applicationInfo, userId);
-                }
                 ComponentName name = new ComponentName(
                         sInfo.applicationInfo.packageName, sInfo.name);
+                if (userId > 0) {
+                    if (isSingleton(sInfo.processName, sInfo.applicationInfo)
+                            || (sInfo.flags&ServiceInfo.FLAG_SINGLE_USER) != 0) {
+                        userId = 0;
+                    } else if ((sInfo.flags&ServiceInfo.FLAG_SINGLE_USER) != 0) {
+                        if (checkComponentPermission(
+                                android.Manifest.permission.INTERACT_ACROSS_USERS,
+                                callingPid, callingUid, -1, true)
+                                == PackageManager.PERMISSION_GRANTED) {
+                            userId = 0;
+                        } else {
+                            String msg = "Permission Denial: Service " + name
+                                    + " requests FLAG_SINGLE_USER, but app does not hold "
+                                    + android.Manifest.permission.INTERACT_ACROSS_USERS;
+                            Slog.w(TAG, msg);
+                            throw new SecurityException(msg);
+                        }
+                    }
+                    sInfo = new ServiceInfo(sInfo);
+                    sInfo.applicationInfo = getAppInfoForUser(sInfo.applicationInfo, userId);
+                }
                 r = mServiceMap.getServiceByName(name, userId);
                 if (r == null) {
                     Intent.FilterComparison filter = new Intent.FilterComparison(
@@ -11531,11 +11545,11 @@
         }
 
         final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
-        final String appName = r.processName;
+        final String procName = r.processName;
         ProcessRecord app;
 
         if (!isolated) {
-            app = getProcessRecordLocked(appName, r.appInfo.uid);
+            app = getProcessRecordLocked(procName, r.appInfo.uid);
             if (DEBUG_MU)
                 Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid + " app=" + app);
             if (app != null && app.thread != null) {
@@ -11563,7 +11577,7 @@
         // Not running -- get it started, and enqueue this service record
         // to be executed when the app comes up.
         if (app == null) {
-            if ((app=startProcessLocked(appName, r.appInfo, true, intentFlags,
+            if ((app=startProcessLocked(procName, r.appInfo, true, intentFlags,
                     "service", r.name, false, isolated)) == null) {
                 Slog.w(TAG, "Unable to launch app "
                         + r.appInfo.packageName + "/"
@@ -12668,6 +12682,7 @@
     public Intent registerReceiver(IApplicationThread caller, String callerPackage,
             IIntentReceiver receiver, IntentFilter filter, String permission) {
         enforceNotIsolatedCaller("registerReceiver");
+        int callingUid;
         synchronized(this) {
             ProcessRecord callerApp = null;
             if (caller != null) {
@@ -12683,8 +12698,10 @@
                     throw new SecurityException("Given caller package " + callerPackage
                             + " is not running in process " + callerApp);
                 }
+                callingUid = callerApp.info.uid;
             } else {
                 callerPackage = null;
+                callingUid = Binder.getCallingUid();
             }
 
             List allSticky = null;
@@ -12729,7 +12746,8 @@
                 }
                 mRegisteredReceivers.put(receiver.asBinder(), rl);
             }
-            BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, permission);
+            BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
+                    permission, callingUid);
             rl.add(bf);
             if (!bf.debugCheck()) {
                 Slog.w(TAG, "==> For Dynamic broadast");
@@ -12748,7 +12766,7 @@
                     BroadcastQueue queue = broadcastQueueForIntent(intent);
                     BroadcastRecord r = new BroadcastRecord(queue, intent, null,
                             null, -1, -1, null, receivers, null, 0, null, null,
-                            false, true, true);
+                            false, true, true, false);
                     queue.enqueueParallelBroadcastLocked(r);
                     queue.scheduleBroadcastsLocked();
                 }
@@ -12840,7 +12858,34 @@
         if ((resultTo != null) && !ordered) {
             Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");
         }
-        
+
+        boolean onlySendToCaller = false;
+
+        // If the caller is trying to send this broadcast to a different
+        // user, verify that is allowed.
+        if (UserId.getUserId(callingUid) != userId) {
+            if (checkComponentPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    callingPid, callingUid, -1, true)
+                    != PackageManager.PERMISSION_GRANTED) {
+                if (checkComponentPermission(
+                        android.Manifest.permission.INTERACT_ACROSS_USERS,
+                        callingPid, callingUid, -1, true)
+                        == PackageManager.PERMISSION_GRANTED) {
+                    onlySendToCaller = true;
+                } else {
+                    String msg = "Permission Denial: " + intent.getAction()
+                            + " broadcast from " + callerPackage
+                            + " asks to send as user " + userId
+                            + " but is calling from user " + UserId.getUserId(callingUid)
+                            + "; this requires "
+                            + android.Manifest.permission.INTERACT_ACROSS_USERS;
+                    Slog.w(TAG, msg);
+                    throw new SecurityException(msg);
+                }
+            }
+        }
+
         // Handle special intents: if this broadcast is from the package
         // manager about a package being removed, we need to remove all of
         // its activities from the history stack.
@@ -13042,7 +13087,7 @@
             BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
                     callerPackage, callingPid, callingUid, requiredPermission,
                     registeredReceivers, resultTo, resultCode, resultData, map,
-                    ordered, sticky, false);
+                    ordered, sticky, false, onlySendToCaller);
             if (DEBUG_BROADCAST) Slog.v(
                     TAG, "Enqueueing parallel broadcast " + r);
             final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r);
@@ -13132,7 +13177,7 @@
             BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
                     callerPackage, callingPid, callingUid, requiredPermission,
                     receivers, resultTo, resultCode, resultData, map, ordered,
-                    sticky, false);
+                    sticky, false, onlySendToCaller);
             if (DEBUG_BROADCAST) Slog.v(
                     TAG, "Enqueueing ordered broadcast " + r
                     + ": prev had " + queue.mOrderedBroadcasts.size());
diff --git a/services/java/com/android/server/am/BroadcastFilter.java b/services/java/com/android/server/am/BroadcastFilter.java
index b49bc22..4e6d0fa 100644
--- a/services/java/com/android/server/am/BroadcastFilter.java
+++ b/services/java/com/android/server/am/BroadcastFilter.java
@@ -27,13 +27,15 @@
     final ReceiverList receiverList;
     final String packageName;
     final String requiredPermission;
+    final int owningUid;
 
     BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList,
-            String _packageName, String _requiredPermission) {
+            String _packageName, String _requiredPermission, int _owningUid) {
         super(_filter);
         receiverList = _receiverList;
         packageName = _packageName;
         requiredPermission = _requiredPermission;
+        owningUid = _owningUid;
     }
     
     public void dump(PrintWriter pw, String prefix) {
diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java
index 47b8c0a..c6d46fc 100644
--- a/services/java/com/android/server/am/BroadcastQueue.java
+++ b/services/java/com/android/server/am/BroadcastQueue.java
@@ -369,7 +369,17 @@
     private final void deliverToRegisteredReceiverLocked(BroadcastRecord r,
             BroadcastFilter filter, boolean ordered) {
         boolean skip = false;
-        if (filter.requiredPermission != null) {
+        if (r.onlySendToCaller) {
+            if (!UserId.isSameApp(r.callingUid, filter.owningUid)) {
+                Slog.w(TAG, "Permission Denial: broadcasting "
+                        + r.intent.toString()
+                        + " from " + r.callerPackage + " (pid="
+                        + r.callingPid + ", uid=" + r.callingUid + ")"
+                        + " not allowed to go to different app " + filter.owningUid);
+                skip = true;
+            }
+        }
+        if (!skip && filter.requiredPermission != null) {
             int perm = mService.checkComponentPermission(filter.requiredPermission,
                     r.callingPid, r.callingUid, -1, true);
             if (perm != PackageManager.PERMISSION_GRANTED) {
@@ -382,7 +392,7 @@
                 skip = true;
             }
         }
-        if (r.requiredPermission != null) {
+        if (!skip && r.requiredPermission != null) {
             int perm = mService.checkComponentPermission(r.requiredPermission,
                     filter.receiverList.pid, filter.receiverList.uid, -1, true);
             if (perm != PackageManager.PERMISSION_GRANTED) {
@@ -651,6 +661,17 @@
                 (ResolveInfo)nextReceiver;
 
             boolean skip = false;
+            if (r.onlySendToCaller) {
+                if (!UserId.isSameApp(r.callingUid, info.activityInfo.applicationInfo.uid)) {
+                    Slog.w(TAG, "Permission Denial: broadcasting "
+                            + r.intent.toString()
+                            + " from " + r.callerPackage + " (pid="
+                            + r.callingPid + ", uid=" + r.callingUid + ")"
+                            + " not allowed to go to different app "
+                            + info.activityInfo.applicationInfo.uid);
+                    skip = true;
+                }
+            }
             int perm = mService.checkComponentPermission(info.activityInfo.permission,
                     r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
                     info.activityInfo.exported);
diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java
index dd560fc..799b609 100644
--- a/services/java/com/android/server/am/BroadcastRecord.java
+++ b/services/java/com/android/server/am/BroadcastRecord.java
@@ -44,6 +44,7 @@
     final boolean ordered;  // serialize the send to receivers?
     final boolean sticky;   // originated from existing sticky data?
     final boolean initialSticky; // initial broadcast from register to sticky?
+    final boolean onlySendToCaller; // only allow receipt by sender's components?
     final String requiredPermission; // a permission the caller has required
     final List receivers;   // contains BroadcastFilter and ResolveInfo
     IIntentReceiver resultTo; // who receives final result if non-null
@@ -167,7 +168,7 @@
             int _callingPid, int _callingUid, String _requiredPermission,
             List _receivers, IIntentReceiver _resultTo, int _resultCode,
             String _resultData, Bundle _resultExtras, boolean _serialized,
-            boolean _sticky, boolean _initialSticky) {
+            boolean _sticky, boolean _initialSticky, boolean _onlySendToCaller) {
         queue = _queue;
         intent = _intent;
         callerApp = _callerApp;
@@ -183,6 +184,7 @@
         ordered = _serialized;
         sticky = _sticky;
         initialSticky = _initialSticky;
+        onlySendToCaller = _onlySendToCaller;
         nextReceiver = 0;
         state = IDLE;
     }