From e5cc81ce075aeecf9f4340f5d73c57e4f0e56fac Mon Sep 17 00:00:00 2001 From: Sanjana Sunil Date: Fri, 17 Dec 2021 07:32:22 +0000 Subject: [PATCH] Add support for spawning multiple service processes Modify activity manager code to allow for multiple processes to be spawned for the same service (non-isolated) having different instance names. A new API is introduced similar to bindService where the app UID associated with the supplemental process service is used as the instance name. The main changes involve distinguishing it from isolated service processes with instance name, removing a check that allows only isolated services to have instance names and using distinguishing process names in the ServiceRecord for the different processes running that service. Bug: 203670795 Bug: 209058402 Test: Manual, installing two Supplemental client apps and attempting to load code from each and examining new processes created. Automated tests are WIP. Change-Id: I2e349412338e6f0cc9cabaefb77fe2413f5850e0 --- core/java/android/app/ContextImpl.java | 11 ++-- core/java/android/app/IActivityManager.aidl | 2 +- services/api/current.txt | 1 + .../com/android/server/am/ActiveServices.java | 43 +++++++++++---- .../server/am/ActivityManagerLocal.java | 23 ++++++++ .../server/am/ActivityManagerService.java | 53 +++++++++++++++++-- .../com/android/server/am/ServiceRecord.java | 11 +++- 7 files changed, 122 insertions(+), 22 deletions(-) diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index f3315a8dc089e..8181a74e8f95e 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1997,7 +1997,8 @@ class ContextImpl extends Context { private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, String instanceName, Handler handler, Executor executor, UserHandle user) { - // Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser. + // Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser and + // ActivityManagerService.LocalService.startAndBindSupplementalProcessService IServiceConnection sd; if (conn == null) { throw new IllegalArgumentException("connection is null"); @@ -2023,10 +2024,10 @@ class ContextImpl extends Context { flags |= BIND_WAIVE_PRIORITY; } service.prepareToLeaveProcess(this); - int res = ActivityManager.getService().bindIsolatedService( - mMainThread.getApplicationThread(), getActivityToken(), service, - service.resolveTypeIfNeeded(getContentResolver()), - sd, flags, instanceName, getOpPackageName(), user.getIdentifier()); + int res = ActivityManager.getService().bindServiceInstance( + mMainThread.getApplicationThread(), getActivityToken(), service, + service.resolveTypeIfNeeded(getContentResolver()), + sd, flags, instanceName, getOpPackageName(), user.getIdentifier()); if (res < 0) { throw new SecurityException( "Not allowed to bind to service " + service); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 82c419affac2b..e4ef12c250aba 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -169,7 +169,7 @@ interface IActivityManager { int bindService(in IApplicationThread caller, in IBinder token, in Intent service, in String resolvedType, in IServiceConnection connection, int flags, in String callingPackage, int userId); - int bindIsolatedService(in IApplicationThread caller, in IBinder token, in Intent service, + int bindServiceInstance(in IApplicationThread caller, in IBinder token, in Intent service, in String resolvedType, in IServiceConnection connection, int flags, in String instanceName, in String callingPackage, int userId); void updateServiceGroup(in IServiceConnection connection, int group, int importance); diff --git a/services/api/current.txt b/services/api/current.txt index 50f00524676cc..dcf7e64479dac 100644 --- a/services/api/current.txt +++ b/services/api/current.txt @@ -39,6 +39,7 @@ package com.android.server.am { public interface ActivityManagerLocal { method public boolean canStartForegroundService(int, int, @NonNull String); + method public boolean startAndBindSupplementalProcessService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int) throws android.os.TransactionTooLargeException; } } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index b5c0a67be20fa..9353dd832bbab 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -2721,7 +2721,8 @@ public final class ActiveServices { int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service, String resolvedType, final IServiceConnection connection, int flags, - String instanceName, String callingPackage, final int userId) + String instanceName, boolean isSupplementalProcessService, String callingPackage, + final int userId) throws TransactionTooLargeException { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service + " type=" + resolvedType + " conn=" + connection.asBinder() @@ -2805,10 +2806,9 @@ public final class ActiveServices { final boolean isBindExternal = (flags & Context.BIND_EXTERNAL_SERVICE) != 0; final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0; - ServiceLookupResult res = - retrieveServiceLocked(service, instanceName, resolvedType, callingPackage, - callingPid, callingUid, userId, true, - callerFg, isBindExternal, allowInstant); + ServiceLookupResult res = retrieveServiceLocked(service, instanceName, + isSupplementalProcessService, resolvedType, callingPackage, callingPid, callingUid, + userId, true, callerFg, isBindExternal, allowInstant); if (res == null) { return 0; } @@ -3228,6 +3228,20 @@ public final class ActiveServices { int callingPid, int callingUid, int userId, boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal, boolean allowInstant) { + return retrieveServiceLocked(service, instanceName, false, resolvedType, callingPackage, + callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal, + allowInstant); + } + + private ServiceLookupResult retrieveServiceLocked(Intent service, + String instanceName, boolean isSupplementalProcessService, String resolvedType, + String callingPackage, int callingPid, int callingUid, int userId, + boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal, + boolean allowInstant) { + if (isSupplementalProcessService && instanceName == null) { + throw new IllegalArgumentException("No instanceName provided for supplemental process"); + } + ServiceRecord r = null; if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service + " type=" + resolvedType + " callingUid=" + callingUid); @@ -3249,7 +3263,6 @@ public final class ActiveServices { if (instanceName == null) { comp = service.getComponent(); } else { - // This is for isolated services final ComponentName realComp = service.getComponent(); if (realComp == null) { throw new IllegalArgumentException("Can't use custom instance name '" + instanceName @@ -3304,12 +3317,19 @@ public final class ActiveServices { return null; } if (instanceName != null - && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) { + && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0 + && !isSupplementalProcessService) { throw new IllegalArgumentException("Can't use instance name '" + instanceName - + "' with non-isolated service '" + sInfo.name + "'"); + + "' with non-isolated non-supplemental service '" + sInfo.name + "'"); } - ComponentName className = new ComponentName( - sInfo.applicationInfo.packageName, sInfo.name); + if (isSupplementalProcessService + && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) { + throw new IllegalArgumentException("Service cannot be both supplemental and " + + "isolated"); + } + + ComponentName className = new ComponentName(sInfo.applicationInfo.packageName, + sInfo.name); ComponentName name = comp != null ? comp : className; if (!mAm.validateAssociationAllowedLocked(callingPackage, callingUid, name.getPackageName(), sInfo.applicationInfo.uid)) { @@ -3392,7 +3412,8 @@ public final class ActiveServices { = new Intent.FilterComparison(service.cloneFilter()); final ServiceRestarter res = new ServiceRestarter(); r = new ServiceRecord(mAm, className, name, definingPackageName, - definingUid, filter, sInfo, callingFromFg, res); + definingUid, filter, sInfo, callingFromFg, res, + isSupplementalProcessService); res.setService(r); smap.mServicesByInstanceName.put(name, r); smap.mServicesByIntent.put(filter, r); diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java index 9a1bfddcec07a..d9ee7d974864b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerLocal.java +++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java @@ -18,6 +18,9 @@ package com.android.server.am; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.TransactionTooLargeException; /** * Interface for in-process calls into @@ -58,4 +61,24 @@ public interface ActivityManagerLocal { * @hide */ void tempAllowWhileInUsePermissionInFgs(int uid, long durationMs); + + /** + * Starts a supplemental process service and binds to it. You can through the arguments here + * have the system bring up multiple concurrent processes hosting their own instance of that + * service. The userAppUid you provide here identifies the different instances - each + * unique uid is attributed to a supplemental process. + * + * @param service Identifies the supplemental process service to connect to. The Intent must + * specify an explicit component name. This value cannot be null. + * @param conn Receives information as the service is started and stopped. + * This must be a valid ServiceConnection object; it must not be null. + * @param userAppUid Uid of the app for which the supplemental process needs to be spawned. + * @return {@code true} if the system is in the process of bringing up a + * service that your client has permission to bind to; {@code false} + * if the system couldn't find the service or if your client doesn't + * have permission to bind to it. + */ + boolean startAndBindSupplementalProcessService(@NonNull Intent service, + @NonNull ServiceConnection conn, int userAppUid) throws TransactionTooLargeException; + } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 902659c278188..d80af21acd447 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -218,6 +218,7 @@ import android.content.IIntentSender; import android.content.Intent; import android.content.IntentFilter; import android.content.LocusId; +import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.ActivityPresentationInfo; import android.content.pm.ApplicationInfo; @@ -12314,13 +12315,25 @@ public class ActivityManagerService extends IActivityManager.Stub public int bindService(IApplicationThread caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags, String callingPackage, int userId) throws TransactionTooLargeException { - return bindIsolatedService(caller, token, service, resolvedType, connection, flags, + return bindServiceInstance(caller, token, service, resolvedType, connection, flags, null, callingPackage, userId); } - public int bindIsolatedService(IApplicationThread caller, IBinder token, Intent service, + /** + * Binds to a service with a given instanceName, creating it if it does not already exist. + * If the instanceName field is not supplied, binding to the service occurs as usual. + */ + public int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags, String instanceName, String callingPackage, int userId) throws TransactionTooLargeException { + return bindServiceInstance(caller, token, service, resolvedType, connection, flags, + instanceName, false, callingPackage, userId); + } + + private int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service, + String resolvedType, IServiceConnection connection, int flags, String instanceName, + boolean isSupplementalProcessService, String callingPackage, int userId) + throws TransactionTooLargeException { enforceNotIsolatedCaller("bindService"); // Refuse possible leaked file descriptors @@ -12332,6 +12345,10 @@ public class ActivityManagerService extends IActivityManager.Stub throw new IllegalArgumentException("callingPackage cannot be null"); } + if (isSupplementalProcessService && instanceName == null) { + throw new IllegalArgumentException("No instance name provided for isolated process"); + } + // Ensure that instanceName, which is caller provided, does not contain // unusual characters. if (instanceName != null) { @@ -12345,8 +12362,8 @@ public class ActivityManagerService extends IActivityManager.Stub } synchronized(this) { - return mServices.bindServiceLocked(caller, token, service, - resolvedType, connection, flags, instanceName, callingPackage, userId); + return mServices.bindServiceLocked(caller, token, service, resolvedType, connection, + flags, instanceName, isSupplementalProcessService, callingPackage, userId); } } @@ -15822,6 +15839,34 @@ public class ActivityManagerService extends IActivityManager.Stub processName, abiOverride, uid, crashHandler); } + @Override + public boolean startAndBindSupplementalProcessService(Intent service, + ServiceConnection conn, int userAppUid) throws TransactionTooLargeException { + if (service == null) { + throw new IllegalArgumentException("intent is null"); + } + if (conn == null) { + throw new IllegalArgumentException("connection is null"); + } + if (service.getComponent() == null) { + throw new IllegalArgumentException("service must specify explicit component"); + } + if (!UserHandle.isApp(userAppUid)) { + throw new IllegalArgumentException("uid is not within application range"); + } + + Handler handler = mContext.getMainThreadHandler(); + int flags = Context.BIND_AUTO_CREATE; + + final IServiceConnection sd = mContext.getServiceDispatcher(conn, handler, flags); + service.prepareToLeaveProcess(mContext); + return ActivityManagerService.this.bindServiceInstance( + mContext.getIApplicationThread(), mContext.getActivityToken(), service, + service.resolveTypeIfNeeded(mContext.getContentResolver()), sd, flags, + Integer.toString(userAppUid), /*isSupplementalProcessService*/ true, + mContext.getOpPackageName(), UserHandle.getUserId(userAppUid)) != 0; + } + @Override public void onUserRemoved(@UserIdInt int userId) { // Clean up any ActivityTaskManager state (by telling it the user is stopped) diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 24e7ba4a32b96..da78e2d7504ef 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -570,6 +570,14 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN ComponentName instanceName, String definingPackageName, int definingUid, Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg, Runnable restarter) { + this(ams, name, instanceName, definingPackageName, definingUid, intent, sInfo, callerIsFg, + restarter, false); + } + + ServiceRecord(ActivityManagerService ams, ComponentName name, + ComponentName instanceName, String definingPackageName, int definingUid, + Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg, + Runnable restarter, boolean isSupplementalProcessService) { this.ams = ams; this.name = name; this.instanceName = instanceName; @@ -580,7 +588,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN serviceInfo = sInfo; appInfo = sInfo.applicationInfo; packageName = sInfo.applicationInfo.packageName; - if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) { + if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0 + || isSupplementalProcessService) { processName = sInfo.processName + ":" + instanceName.getClassName(); } else { processName = sInfo.processName;