/* * Copyright (C) 2024 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.android.ondevicepersonalization.services.process; import static com.android.ondevicepersonalization.services.PhFlags.KEY_IS_ART_IMAGE_LOADING_OPTIMIZATION_ENABLED; import static com.android.ondevicepersonalization.services.PhFlags.KEY_TRUSTED_PARTNER_APPS_LIST; import android.adservices.ondevicepersonalization.Constants; import android.adservices.ondevicepersonalization.IsolatedServiceException; import android.adservices.ondevicepersonalization.aidl.IIsolatedService; import android.adservices.ondevicepersonalization.aidl.IIsolatedServiceCallback; import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.os.Bundle; import androidx.concurrent.futures.CallbackToFutureAdapter; import com.android.federatedcompute.internal.util.AbstractServiceBinder; import com.android.modules.utils.build.SdkLevel; import com.android.odp.module.common.Clock; import com.android.odp.module.common.MonotonicClock; import com.android.odp.module.common.PackageUtils; import com.android.ondevicepersonalization.internal.util.LoggerFactory; import com.android.ondevicepersonalization.services.FlagsFactory; import com.android.ondevicepersonalization.services.OdpServiceException; import com.android.ondevicepersonalization.services.OnDevicePersonalizationApplication; import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors; import com.android.ondevicepersonalization.services.util.AllowListUtils; import com.google.common.util.concurrent.FluentFuture; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import java.util.Objects; /** Utilities for running remote isolated services in a shared isolated process (SIP). Note that * this runner is only selected when the shared_isolated_process_feature_enabled flag is enabled. */ public class SharedIsolatedProcessRunner implements ProcessRunner { private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); private static final String TAG = SharedIsolatedProcessRunner.class.getSimpleName(); // SIP that hosts services from all trusted partners, as well as internal isolated services. public static final String TRUSTED_PARTNER_APPS_SIP = "trusted_partner_apps_sip"; // SIP that hosts unknown remote services. public static final String UNKNOWN_APPS_SIP = "unknown_apps_sip"; private final Context mApplicationContext; private final Injector mInjector; static class Injector { Clock getClock() { return MonotonicClock.getInstance(); } ListeningExecutorService getExecutor() { return OnDevicePersonalizationExecutors.getBackgroundExecutor(); } } SharedIsolatedProcessRunner( @NonNull Context applicationContext, @NonNull Injector injector) { mApplicationContext = Objects.requireNonNull(applicationContext); mInjector = Objects.requireNonNull(injector); } private static class LazyInstanceHolder { static final SharedIsolatedProcessRunner LAZY_INSTANCE = new SharedIsolatedProcessRunner( OnDevicePersonalizationApplication.getAppContext(), new Injector()); } /** Returns the global ProcessRunner. */ @NonNull public static SharedIsolatedProcessRunner getInstance() { return SharedIsolatedProcessRunner.LazyInstanceHolder.LAZY_INSTANCE; } /** Binds to a service and put it in one of ODP's shared isolated process. */ @Override @NonNull public ListenableFuture loadIsolatedService( @NonNull String taskName, @NonNull ComponentName componentName) { try { ListenableFuture> isolatedServiceFuture = mInjector.getExecutor().submit( () -> getIsolatedServiceBinder(componentName)); return FluentFuture.from(isolatedServiceFuture) .transformAsync( (isolatedService) -> { try { return Futures.immediateFuture(new IsolatedServiceInfo( mInjector.getClock().elapsedRealtime(), componentName, /* pluginController= */ null, isolatedService)); } catch (Exception e) { return Futures.immediateFailedFuture(e); } }, mInjector.getExecutor()) .catchingAsync( Exception.class, Futures::immediateFailedFuture, mInjector.getExecutor()); } catch (Exception e) { return Futures.immediateFailedFuture(e); } } /** Runs the remote isolated service in the shared isolated process. */ @NonNull @Override public ListenableFuture runIsolatedService( @NonNull IsolatedServiceInfo isolatedProcessInfo, int operationCode, @NonNull Bundle serviceParams) { return CallbackToFutureAdapter.getFuture( completer -> { isolatedProcessInfo.getIsolatedServiceBinder() .getService(Runnable::run) .onRequest( operationCode, serviceParams, new IIsolatedServiceCallback.Stub() { @Override public void onSuccess(Bundle result) { completer.set(result); } // TO-DO (323882182): Granular isolated servce failures. @Override public void onError( int errorCode, int isolatedServiceErrorCode) { if (isolatedServiceErrorCode > 0 && isolatedServiceErrorCode < 128) { completer.setException( new OdpServiceException( Constants.STATUS_SERVICE_FAILED, new IsolatedServiceException( isolatedServiceErrorCode))); } else { completer.setException( new OdpServiceException( Constants.STATUS_SERVICE_FAILED)); } } }); return null; }); } /** Unbinds from the remote isolated service. */ @NonNull @Override public ListenableFuture unloadIsolatedService( @NonNull IsolatedServiceInfo isolatedServiceInfo) { try { return (ListenableFuture) mInjector.getExecutor().submit( () -> isolatedServiceInfo.getIsolatedServiceBinder().unbindFromService()); } catch (Exception e) { return Futures.immediateFailedFuture(e); } } private AbstractServiceBinder getIsolatedServiceBinder( @NonNull ComponentName service) throws Exception { boolean isSipRequested = isSharedIsolatedProcessRequested(service); // null instance name results in regular isolated service being created. String instanceName = isSipRequested ? getSipInstanceName(service.getPackageName()) : null; int bindFlag = isSipRequested ? Context.BIND_SHARED_ISOLATED_PROCESS : Context.BIND_AUTO_CREATE; return AbstractServiceBinder.getIsolatedServiceBinderByServiceName( mApplicationContext, service.getClassName(), service.getPackageName(), instanceName, bindFlag, IIsolatedService.Stub::asInterface); } String getSipInstanceName(String packageName) { String partnerAppsList = (String) FlagsFactory.getFlags().getStableFlag(KEY_TRUSTED_PARTNER_APPS_LIST); String packageCertificate = null; try { packageCertificate = PackageUtils.getCertDigest(mApplicationContext, packageName); } catch (Exception e) { sLogger.d(TAG + ": not able to find certificate for package " + packageName, e); } boolean isPartnerApp = AllowListUtils.isAllowListed( packageName, packageCertificate, partnerAppsList); String sipInstanceName = isPartnerApp ? TRUSTED_PARTNER_APPS_SIP : UNKNOWN_APPS_SIP; return (boolean) FlagsFactory.getFlags() .getStableFlag(KEY_IS_ART_IMAGE_LOADING_OPTIMIZATION_ENABLED) ? sipInstanceName + "_disable_art_image_" : sipInstanceName; } boolean isSharedIsolatedProcessRequested(ComponentName service) throws Exception { if (!SdkLevel.isAtLeastU()) { return false; } PackageManager pm = mApplicationContext.getPackageManager(); ServiceInfo si = pm.getServiceInfo(service, PackageManager.GET_META_DATA); if ((si.flags & si.FLAG_ISOLATED_PROCESS) == 0) { throw new IllegalArgumentException("ODP client services should run in isolated processes."); } return (si.flags & si.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) != 0; } }