• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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