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.PhFlags.KEY_IS_ART_IMAGE_LOADING_OPTIMIZATION_ENABLED;
20 import static com.android.ondevicepersonalization.services.PhFlags.KEY_TRUSTED_PARTNER_APPS_LIST;
21 
22 import android.adservices.ondevicepersonalization.Constants;
23 import android.adservices.ondevicepersonalization.IsolatedServiceException;
24 import android.adservices.ondevicepersonalization.aidl.IIsolatedService;
25 import android.adservices.ondevicepersonalization.aidl.IIsolatedServiceCallback;
26 import android.annotation.NonNull;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ServiceInfo;
31 import android.os.Bundle;
32 
33 import androidx.concurrent.futures.CallbackToFutureAdapter;
34 
35 import com.android.federatedcompute.internal.util.AbstractServiceBinder;
36 import com.android.modules.utils.build.SdkLevel;
37 import com.android.odp.module.common.Clock;
38 import com.android.odp.module.common.MonotonicClock;
39 import com.android.odp.module.common.PackageUtils;
40 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
41 import com.android.ondevicepersonalization.services.FlagsFactory;
42 import com.android.ondevicepersonalization.services.OdpServiceException;
43 import com.android.ondevicepersonalization.services.OnDevicePersonalizationApplication;
44 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
45 import com.android.ondevicepersonalization.services.util.AllowListUtils;
46 
47 import com.google.common.util.concurrent.FluentFuture;
48 import com.google.common.util.concurrent.Futures;
49 import com.google.common.util.concurrent.ListenableFuture;
50 import com.google.common.util.concurrent.ListeningExecutorService;
51 
52 import java.util.Objects;
53 
54 /** Utilities for running remote isolated services in a shared isolated process (SIP). Note that
55  *  this runner is only selected when the shared_isolated_process_feature_enabled flag is enabled.
56  */
57 public class SharedIsolatedProcessRunner implements ProcessRunner  {
58 
59     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
60 
61     private static final String TAG = SharedIsolatedProcessRunner.class.getSimpleName();
62 
63     // SIP that hosts services from all trusted partners, as well as internal isolated services.
64     public static final String TRUSTED_PARTNER_APPS_SIP = "trusted_partner_apps_sip";
65 
66     // SIP that hosts unknown remote services.
67     public static final String UNKNOWN_APPS_SIP = "unknown_apps_sip";
68 
69     private final Context mApplicationContext;
70     private final Injector mInjector;
71 
72     static class Injector {
getClock()73         Clock getClock() {
74             return MonotonicClock.getInstance();
75         }
76 
getExecutor()77         ListeningExecutorService getExecutor() {
78             return OnDevicePersonalizationExecutors.getBackgroundExecutor();
79         }
80     }
SharedIsolatedProcessRunner( @onNull Context applicationContext, @NonNull Injector injector)81     SharedIsolatedProcessRunner(
82             @NonNull Context applicationContext,
83             @NonNull Injector injector) {
84         mApplicationContext = Objects.requireNonNull(applicationContext);
85         mInjector = Objects.requireNonNull(injector);
86     }
87 
88     private static class LazyInstanceHolder {
89         static final SharedIsolatedProcessRunner LAZY_INSTANCE =
90                 new SharedIsolatedProcessRunner(
91                         OnDevicePersonalizationApplication.getAppContext(),
92                         new Injector());
93     }
94 
95     /** Returns the global ProcessRunner. */
96     @NonNull
getInstance()97     public static SharedIsolatedProcessRunner getInstance() {
98         return SharedIsolatedProcessRunner.LazyInstanceHolder.LAZY_INSTANCE;
99     }
100 
101     /** Binds to a service and put it in one of ODP's shared isolated process. */
102     @Override
loadIsolatedService( @onNull String taskName, @NonNull ComponentName componentName)103     @NonNull public ListenableFuture<IsolatedServiceInfo> loadIsolatedService(
104             @NonNull String taskName, @NonNull ComponentName componentName) {
105         try {
106             ListenableFuture<AbstractServiceBinder<IIsolatedService>> isolatedServiceFuture =
107                     mInjector.getExecutor().submit(
108                             () -> getIsolatedServiceBinder(componentName));
109 
110             return FluentFuture.from(isolatedServiceFuture)
111                     .transformAsync(
112                             (isolatedService) -> {
113                                 try {
114                                     return Futures.immediateFuture(new IsolatedServiceInfo(
115                                             mInjector.getClock().elapsedRealtime(), componentName,
116                                             /* pluginController= */ null, isolatedService));
117                                 } catch (Exception e) {
118                                     return Futures.immediateFailedFuture(e);
119                                 }
120                             }, mInjector.getExecutor())
121                     .catchingAsync(
122                             Exception.class,
123                             Futures::immediateFailedFuture,
124                             mInjector.getExecutor());
125         } catch (Exception e) {
126             return Futures.immediateFailedFuture(e);
127         }
128     }
129 
130     /** Runs the remote isolated service in the shared isolated process. */
131     @NonNull
132     @Override
133     public ListenableFuture<Bundle> runIsolatedService(
134             @NonNull IsolatedServiceInfo isolatedProcessInfo, int operationCode,
135             @NonNull Bundle serviceParams) {
136         return CallbackToFutureAdapter.getFuture(
137                 completer -> {
138                     isolatedProcessInfo.getIsolatedServiceBinder()
139                             .getService(Runnable::run)
140                             .onRequest(
141                                     operationCode, serviceParams,
142                                     new IIsolatedServiceCallback.Stub() {
143                                         @Override public void onSuccess(Bundle result) {
144                                             completer.set(result);
145                                         }
146 
147                                         // TO-DO (323882182): Granular isolated servce failures.
148                                         @Override public void onError(
149                                                 int errorCode, int isolatedServiceErrorCode) {
150                                             if (isolatedServiceErrorCode > 0
151                                                         && isolatedServiceErrorCode < 128) {
152                                                 completer.setException(
153                                                         new OdpServiceException(
154                                                                 Constants.STATUS_SERVICE_FAILED,
155                                                                 new IsolatedServiceException(
156                                                                     isolatedServiceErrorCode)));
157                                             } else {
158                                                 completer.setException(
159                                                         new OdpServiceException(
160                                                                 Constants.STATUS_SERVICE_FAILED));
161                                             }
162                                         }
163                                     });
164                     return null;
165                 });
166     }
167 
168     /** Unbinds from the remote isolated service. */
169     @NonNull
170     @Override
171     public ListenableFuture<Void> unloadIsolatedService(
172             @NonNull IsolatedServiceInfo isolatedServiceInfo) {
173         try {
174             return (ListenableFuture<Void>) mInjector.getExecutor().submit(
175                     () -> isolatedServiceInfo.getIsolatedServiceBinder().unbindFromService());
176         } catch (Exception e) {
177             return Futures.immediateFailedFuture(e);
178         }
179     }
180 
181     private AbstractServiceBinder<IIsolatedService> getIsolatedServiceBinder(
182             @NonNull ComponentName service) throws Exception {
183         boolean isSipRequested = isSharedIsolatedProcessRequested(service);
184 
185         // null instance name results in regular isolated service being created.
186         String instanceName = isSipRequested ? getSipInstanceName(service.getPackageName()) : null;
187         int bindFlag = isSipRequested
188                 ? Context.BIND_SHARED_ISOLATED_PROCESS
189                 : Context.BIND_AUTO_CREATE;
190 
191         return AbstractServiceBinder.getIsolatedServiceBinderByServiceName(
192                 mApplicationContext,
193                 service.getClassName(), service.getPackageName(),
194                 instanceName, bindFlag, IIsolatedService.Stub::asInterface);
195     }
196 
197     String getSipInstanceName(String packageName) {
198         String partnerAppsList =
199                 (String) FlagsFactory.getFlags().getStableFlag(KEY_TRUSTED_PARTNER_APPS_LIST);
200         String packageCertificate = null;
201         try {
202             packageCertificate = PackageUtils.getCertDigest(mApplicationContext, packageName);
203         } catch (Exception e) {
204             sLogger.d(TAG + ": not able to find certificate for package " + packageName, e);
205         }
206         boolean isPartnerApp = AllowListUtils.isAllowListed(
207                 packageName, packageCertificate, partnerAppsList);
208         String sipInstanceName = isPartnerApp ? TRUSTED_PARTNER_APPS_SIP : UNKNOWN_APPS_SIP;
209         return (boolean) FlagsFactory.getFlags()
210                 .getStableFlag(KEY_IS_ART_IMAGE_LOADING_OPTIMIZATION_ENABLED)
211                     ? sipInstanceName + "_disable_art_image_" : sipInstanceName;
212     }
213 
214     boolean isSharedIsolatedProcessRequested(ComponentName service) throws Exception {
215         if (!SdkLevel.isAtLeastU()) {
216             return false;
217         }
218 
219         PackageManager pm = mApplicationContext.getPackageManager();
220         ServiceInfo si = pm.getServiceInfo(service, PackageManager.GET_META_DATA);
221 
222         if ((si.flags & si.FLAG_ISOLATED_PROCESS) == 0) {
223             throw new IllegalArgumentException("ODP client services should run in isolated processes.");
224         }
225 
226         return (si.flags & si.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) != 0;
227     }
228 }
229