Add external services, a way to run isolated processes as a different package.

This adds android:externalService boolean attribute to <service>. If that
attribute is true, then bindService() may be called with
BIND_EXTERNAL_SERVICE to create the new service process under the calling
package's name and uid. The service will execute the code from the package in
which it is declared, but will appear to run as the calling application.

External services may only be used if android:exported="false" and
android:isolatedProcess="true".

Bug: 22084679
Bug: 21643067
Change-Id: I3c3a5f0ef58738316c5efeab9044e43e09220d01
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 84f6f3d..e9d83eb 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -329,6 +329,15 @@
     public static final int BIND_NOT_VISIBLE = 0x40000000;
 
     /**
+     * Flag for {@link #bindService}: The service being bound is an
+     * {@link android.R.attr#isolatedProcess isolated},
+     * {@link android.R.attr#externalService external} service.  This binds the service into the
+     * calling application's package, rather than the package in which the service is declared.
+     * @hide
+     */
+    public static final int BIND_EXTERNAL_SERVICE = 0x80000000;
+
+    /**
      * Returns an AssetManager instance for the application's package.
      * <p>
      * <strong>Note:</strong> Implementations of this method should return
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index a0df610..6d360d7 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3886,6 +3886,11 @@
             s.info.flags |= ServiceInfo.FLAG_ISOLATED_PROCESS;
         }
         if (sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestService_externalService,
+                false)) {
+            s.info.flags |= ServiceInfo.FLAG_EXTERNAL_SERVICE;
+        }
+        if (sa.getBoolean(
                 com.android.internal.R.styleable.AndroidManifestService_singleUser,
                 false)) {
             s.info.flags |= ServiceInfo.FLAG_SINGLE_USER;
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 74e5c2a..eecf0de 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -49,6 +49,14 @@
     public static final int FLAG_ISOLATED_PROCESS = 0x0002;
 
     /**
+     * Bit in {@link #flags}: If set, the service can be bound and run in the
+     * calling application's package, rather than the package in which it is
+     * declared.  Set from {@link android.R.attr#externalService} attribute.
+     * @hide
+     */
+    public static final int FLAG_EXTERNAL_SERVICE = 0x0004;
+
+    /**
      * Bit in {@link #flags}: If set, a single instance of the service will
      * run for all users on the device.  Set from the
      * {@link android.R.attr#singleUser} attribute.
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 58a77e8..21e92ba 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1748,6 +1748,10 @@
         <attr name="isolatedProcess" format="boolean" />
         <attr name="singleUser" />
         <attr name="encryptionAware" />
+        <!-- @hide If the service is an {@link #isolatedProcess} service, this permits a client to
+             bind to the service as if it were running it its own package.  The service must also be
+             {@link #exported} if this flag is set. -->
+        <attr name="externalService" format="boolean" />
     </declare-styleable>
 
     <!-- The <code>receiver</code> tag declares an
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index acea461..c883b1f 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2689,6 +2689,7 @@
     <public type="attr" name="tickMarkTint" />
     <public type="attr" name="tickMarkTintMode" />
     <public type="attr" name="canPerformGestures" />
+    <public type="attr" name="externalService" />
 
     <public type="style" name="Theme.Material.Light.DialogWhenLarge.DarkActionBar" />
     <public type="style" name="Widget.Material.SeekBar.Discrete" />
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d12eadb..7ba6338 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -61,6 +61,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
@@ -326,7 +327,7 @@
 
         ServiceLookupResult res =
             retrieveServiceLocked(service, resolvedType, callingPackage,
-                    callingPid, callingUid, userId, true, callerFg);
+                    callingPid, callingUid, userId, true, callerFg, false);
         if (res == null) {
             return null;
         }
@@ -549,7 +550,7 @@
 
         // If this service is active, make sure it is stopped.
         ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, null,
-                Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false);
+                Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false);
         if (r != null) {
             if (r.record != null) {
                 final long origId = Binder.clearCallingIdentity();
@@ -598,7 +599,7 @@
     IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) {
         ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, callingPackage,
                 Binder.getCallingPid(), Binder.getCallingUid(),
-                UserHandle.getCallingUserId(), false, false);
+                UserHandle.getCallingUserId(), false, false, false);
 
         IBinder ret = null;
         if (r != null) {
@@ -831,10 +832,11 @@
         }
 
         final boolean callerFg = callerApp.setSchedGroup != Process.THREAD_GROUP_BG_NONINTERACTIVE;
+        final boolean isBindExternal = (flags & Context.BIND_EXTERNAL_SERVICE) != 0;
 
         ServiceLookupResult res =
-            retrieveServiceLocked(service, resolvedType, callingPackage,
-                    Binder.getCallingPid(), Binder.getCallingUid(), userId, true, callerFg);
+            retrieveServiceLocked(service, resolvedType, callingPackage, Binder.getCallingPid(),
+                    Binder.getCallingUid(), userId, true, callerFg, isBindExternal);
         if (res == null) {
             return 0;
         }
@@ -1192,7 +1194,7 @@
 
     private ServiceLookupResult retrieveServiceLocked(Intent service,
             String resolvedType, String callingPackage, int callingPid, int callingUid, int userId,
-            boolean createIfNeeded, boolean callingFromFg) {
+            boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal) {
         ServiceRecord r = null;
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service
                 + " type=" + resolvedType + " callingUid=" + callingUid);
@@ -1205,10 +1207,16 @@
         if (comp != null) {
             r = smap.mServicesByName.get(comp);
         }
-        if (r == null) {
+        if (r == null && !isBindExternal) {
             Intent.FilterComparison filter = new Intent.FilterComparison(service);
             r = smap.mServicesByIntent.get(filter);
         }
+        if (r != null && (r.serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0
+                && !callingPackage.equals(r.packageName)) {
+            // If an external service is running within its own package, other packages
+            // should not bind to that instance.
+            r = null;
+        }
         if (r == null) {
             try {
                 // TODO: come back and remove this assumption to triage all services
@@ -1225,6 +1233,37 @@
                 }
                 ComponentName name = new ComponentName(
                         sInfo.applicationInfo.packageName, sInfo.name);
+                if ((sInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0) {
+                    if (isBindExternal) {
+                        if (!sInfo.exported) {
+                            throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + name +
+                                    " is not exported");
+                        }
+                        if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) {
+                            throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + name +
+                                    " is not an isolatedProcess");
+                        }
+                        // Run the service under the calling package's application.
+                        ApplicationInfo aInfo = AppGlobals.getPackageManager().getApplicationInfo(
+                                callingPackage, ActivityManagerService.STOCK_PM_FLAGS, userId);
+                        if (aInfo == null) {
+                            throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " +
+                                    "could not resolve client package " + callingPackage);
+                        }
+                        sInfo = new ServiceInfo(sInfo);
+                        sInfo.applicationInfo = new ApplicationInfo(sInfo.applicationInfo);
+                        sInfo.applicationInfo.packageName = aInfo.packageName;
+                        sInfo.applicationInfo.uid = aInfo.uid;
+                        name = new ComponentName(aInfo.packageName, name.getClassName());
+                        service.setComponent(name);
+                    } else {
+                        throw new SecurityException("BIND_EXTERNAL_SERVICE required for " +
+                                name);
+                    }
+                } else if (isBindExternal) {
+                    throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + name +
+                            " is not an externalService");
+                }
                 if (userId > 0) {
                     if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo,
                             sInfo.name, sInfo.flags)