1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ondevicepersonalization.services.process; 18 19 import static com.android.ondevicepersonalization.services.FlagsConstants.KEY_IS_ART_IMAGE_LOADING_OPTIMIZATION_ENABLED; 20 import static com.android.ondevicepersonalization.services.FlagsConstants.KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED; 21 import static com.android.ondevicepersonalization.services.FlagsConstants.KEY_TRUSTED_PARTNER_APPS_LIST; 22 23 import android.adservices.ondevicepersonalization.Constants; 24 import android.adservices.ondevicepersonalization.IsolatedServiceException; 25 import android.adservices.ondevicepersonalization.aidl.IIsolatedService; 26 import android.adservices.ondevicepersonalization.aidl.IIsolatedServiceCallback; 27 import android.annotation.NonNull; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.pm.PackageManager; 31 import android.content.pm.ServiceInfo; 32 import android.os.Binder; 33 import android.os.Bundle; 34 35 import androidx.concurrent.futures.CallbackToFutureAdapter; 36 37 import com.android.federatedcompute.internal.util.AbstractServiceBinder; 38 import com.android.modules.utils.build.SdkLevel; 39 import com.android.odp.module.common.Clock; 40 import com.android.odp.module.common.MonotonicClock; 41 import com.android.odp.module.common.PackageUtils; 42 import com.android.ondevicepersonalization.internal.util.ExceptionInfo; 43 import com.android.ondevicepersonalization.internal.util.LoggerFactory; 44 import com.android.ondevicepersonalization.services.OdpServiceException; 45 import com.android.ondevicepersonalization.services.OnDevicePersonalizationApplication; 46 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors; 47 import com.android.ondevicepersonalization.services.StableFlags; 48 import com.android.ondevicepersonalization.services.data.errors.AggregatedErrorCodesLogger; 49 import com.android.ondevicepersonalization.services.util.AllowListUtils; 50 51 import com.google.common.annotations.VisibleForTesting; 52 import com.google.common.util.concurrent.FluentFuture; 53 import com.google.common.util.concurrent.Futures; 54 import com.google.common.util.concurrent.ListenableFuture; 55 import com.google.common.util.concurrent.ListeningExecutorService; 56 57 import java.util.Objects; 58 import java.util.concurrent.TimeoutException; 59 60 /** 61 * A process runner that runs an isolated service by binding to it. It runs the service in a shared 62 * isolated process if the shared_isolated_process_feature_enabled flag is enabled and the selected 63 * isolated service opts in to running in a shared isolated process. 64 */ 65 public class IsolatedServiceBindingRunner implements ProcessRunner { 66 67 private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); 68 69 private static final String TAG = IsolatedServiceBindingRunner.class.getSimpleName(); 70 71 // SIP that hosts services from all trusted partners, as well as internal isolated services. 72 public static final String TRUSTED_PARTNER_APPS_SIP = "trusted_partner_apps_sip"; 73 74 // SIP that hosts unknown remote services. 75 public static final String UNKNOWN_APPS_SIP = "unknown_apps_sip"; 76 77 private final Context mApplicationContext; 78 private final Injector mInjector; 79 80 @VisibleForTesting 81 static class Injector { getClock()82 Clock getClock() { 83 return MonotonicClock.getInstance(); 84 } 85 getExecutor()86 ListeningExecutorService getExecutor() { 87 return OnDevicePersonalizationExecutors.getBackgroundExecutor(); 88 } 89 } 90 91 /** Creates a ProcessRunner. */ IsolatedServiceBindingRunner()92 IsolatedServiceBindingRunner() { 93 this(OnDevicePersonalizationApplication.getAppContext(), new Injector()); 94 } 95 96 @VisibleForTesting IsolatedServiceBindingRunner(@onNull Context applicationContext, @NonNull Injector injector)97 IsolatedServiceBindingRunner(@NonNull Context applicationContext, @NonNull Injector injector) { 98 mApplicationContext = Objects.requireNonNull(applicationContext); 99 mInjector = Objects.requireNonNull(injector); 100 } 101 102 /** Binds to a service and put it in one of ODP's shared isolated process. */ 103 @Override loadIsolatedService( @onNull String taskName, @NonNull ComponentName componentName)104 @NonNull public ListenableFuture<IsolatedServiceInfo> loadIsolatedService( 105 @NonNull String taskName, @NonNull ComponentName componentName) { 106 try { 107 ListenableFuture<AbstractServiceBinder<IIsolatedService>> isolatedServiceFuture = 108 mInjector.getExecutor().submit( 109 () -> getIsolatedServiceBinder(componentName)); 110 111 return FluentFuture.from(isolatedServiceFuture) 112 .transformAsync( 113 (isolatedService) -> { 114 return Futures.immediateFuture( 115 new IsolatedServiceInfo( 116 mInjector.getClock().elapsedRealtime(), 117 componentName, 118 isolatedService)); 119 }, 120 mInjector.getExecutor()) 121 .catchingAsync( 122 Exception.class, 123 e -> { 124 sLogger.d( 125 TAG 126 + ": loading of isolated service failed for " 127 + componentName, 128 e); 129 // Return OdpServiceException if the exception thrown was not 130 // already an OdpServiceException. 131 if (e instanceof OdpServiceException) { 132 return Futures.immediateFailedFuture(e); 133 } 134 return Futures.immediateFailedFuture( 135 new OdpServiceException( 136 Constants.STATUS_ISOLATED_SERVICE_LOADING_FAILED, e)); 137 }, 138 mInjector.getExecutor()); 139 } catch (Exception e) { 140 return Futures.immediateFailedFuture( 141 new OdpServiceException(Constants.STATUS_ISOLATED_SERVICE_LOADING_FAILED, e)); 142 } 143 } 144 145 /** Runs the remote isolated service in the shared isolated process. */ 146 @NonNull 147 @Override runIsolatedService( @onNull IsolatedServiceInfo isolatedProcessInfo, int operationCode, @NonNull Bundle serviceParams)148 public ListenableFuture<Bundle> runIsolatedService( 149 @NonNull IsolatedServiceInfo isolatedProcessInfo, int operationCode, 150 @NonNull Bundle serviceParams) { 151 IIsolatedService service; 152 try { 153 service = isolatedProcessInfo.getIsolatedServiceBinder().getService(Runnable::run); 154 } catch (Exception e) { 155 // Failure in loading/connecting to the IsolatedService vs actual issue 156 // in running the IsolatedService code via the onRequest call below. 157 sLogger.d(TAG + ": unable to get the IsolatedService binder.", e); 158 return Futures.immediateFailedFuture( 159 new OdpServiceException(Constants.STATUS_ISOLATED_SERVICE_LOADING_FAILED)); 160 } 161 162 ListenableFuture<Bundle> callbackFuture = 163 CallbackToFutureAdapter.getFuture( 164 completer -> { 165 service.onRequest( 166 operationCode, 167 serviceParams, 168 new IIsolatedServiceCallback.Stub() { 169 @Override 170 public void onSuccess(Bundle result) { 171 completer.set(result); 172 } 173 174 @Override 175 public void onError( 176 int errorCode, 177 int isolatedServiceErrorCode, 178 byte[] serializedExceptionInfo) { 179 Exception cause = 180 ExceptionInfo.fromByteArray( 181 serializedExceptionInfo); 182 if (isolatedServiceErrorCode > 0) { 183 final long token = Binder.clearCallingIdentity(); 184 try { 185 ListenableFuture<?> unused = 186 AggregatedErrorCodesLogger 187 .logIsolatedServiceErrorCode( 188 isolatedServiceErrorCode, 189 isolatedProcessInfo 190 .getComponentName(), 191 mApplicationContext); 192 } finally { 193 Binder.restoreCallingIdentity(token); 194 } 195 cause = 196 new IsolatedServiceException( 197 isolatedServiceErrorCode, cause); 198 } 199 completer.setException( 200 new OdpServiceException( 201 Constants.STATUS_SERVICE_FAILED, 202 cause)); 203 } 204 }); 205 // used for debugging purpose only. 206 return "IsolatedService.onRequest"; 207 }); 208 return FluentFuture.from(callbackFuture) 209 .catchingAsync( 210 Throwable.class, // Catch FutureGarbageCollectedException 211 e -> { 212 return (e instanceof IsolatedServiceException 213 || e instanceof OdpServiceException) 214 ? Futures.immediateFailedFuture(e) 215 : Futures.immediateFailedFuture( 216 new TimeoutException( 217 "Callback to future adapter was garbage" 218 + " collected.")); 219 }, 220 mInjector.getExecutor()); 221 } 222 223 /** Unbinds from the remote isolated service. */ 224 @NonNull 225 @Override 226 public ListenableFuture<Void> unloadIsolatedService( 227 @NonNull IsolatedServiceInfo isolatedServiceInfo) { 228 try { 229 return (ListenableFuture<Void>) mInjector.getExecutor().submit( 230 () -> isolatedServiceInfo.getIsolatedServiceBinder().unbindFromService()); 231 } catch (Exception e) { 232 return Futures.immediateFailedFuture(e); 233 } 234 } 235 236 private AbstractServiceBinder<IIsolatedService> getIsolatedServiceBinder( 237 @NonNull ComponentName service) throws Exception { 238 PackageManager pm = mApplicationContext.getPackageManager(); 239 sLogger.d(TAG + ": Package manager = " + pm); 240 ServiceInfo si = pm.getServiceInfo(service, PackageManager.GET_META_DATA); 241 checkIsolatedService(service, si); 242 boolean isSipRequested = isSharedIsolatedProcessRequested(si); 243 244 // null instance name results in regular isolated service being created. 245 String instanceName = isSipRequested ? getSipInstanceName(service.getPackageName()) : null; 246 int bindFlag = isSipRequested 247 ? Context.BIND_SHARED_ISOLATED_PROCESS 248 : Context.BIND_AUTO_CREATE; 249 250 return AbstractServiceBinder.getIsolatedServiceBinderByServiceName( 251 mApplicationContext, 252 service.getClassName(), service.getPackageName(), 253 instanceName, bindFlag, IIsolatedService.Stub::asInterface); 254 } 255 256 @VisibleForTesting 257 String getSipInstanceName(String packageName) { 258 String partnerAppsList = 259 (String) StableFlags.get(KEY_TRUSTED_PARTNER_APPS_LIST); 260 String packageCertificate = null; 261 try { 262 packageCertificate = PackageUtils.getCertDigest(mApplicationContext, packageName); 263 } catch (Exception e) { 264 sLogger.d(TAG + ": not able to find certificate for package " + packageName, e); 265 } 266 boolean isPartnerApp = AllowListUtils.isAllowListed( 267 packageName, packageCertificate, partnerAppsList); 268 String sipInstanceName = isPartnerApp ? TRUSTED_PARTNER_APPS_SIP : UNKNOWN_APPS_SIP; 269 return (boolean) StableFlags.get(KEY_IS_ART_IMAGE_LOADING_OPTIMIZATION_ENABLED) 270 ? sipInstanceName + "_disable_art_image_" : sipInstanceName; 271 } 272 273 @VisibleForTesting 274 static void checkIsolatedService(ComponentName service, ServiceInfo si) 275 throws OdpServiceException { 276 if ((si.flags & si.FLAG_ISOLATED_PROCESS) == 0) { 277 sLogger.e( 278 TAG, "ODP client service not configured to run in isolated process " + service); 279 throw new OdpServiceException( 280 Constants.STATUS_MANIFEST_PARSING_FAILED, 281 "ODP client services should run in isolated processes."); 282 } 283 } 284 285 @VisibleForTesting 286 static boolean isSharedIsolatedProcessRequested(ServiceInfo si) { 287 if (!SdkLevel.isAtLeastU()) { 288 return false; 289 } 290 if (!(boolean) StableFlags.get(KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED)) { 291 return false; 292 } 293 294 return (si.flags & si.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) != 0; 295 } 296 } 297