1 /*
2  * Copyright (C) 2022 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 android.adservices.ondevicepersonalization.Constants;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.os.Bundle;
25 
26 
27 import androidx.concurrent.futures.CallbackToFutureAdapter;
28 
29 import com.android.odp.module.common.Clock;
30 import com.android.odp.module.common.MonotonicClock;
31 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
32 import com.android.ondevicepersonalization.libraries.plugin.FailureType;
33 import com.android.ondevicepersonalization.libraries.plugin.PluginCallback;
34 import com.android.ondevicepersonalization.libraries.plugin.PluginController;
35 import com.android.ondevicepersonalization.libraries.plugin.PluginInfo;
36 import com.android.ondevicepersonalization.libraries.plugin.PluginManager;
37 import com.android.ondevicepersonalization.libraries.plugin.impl.PluginManagerImpl;
38 import com.android.ondevicepersonalization.services.OdpServiceException;
39 import com.android.ondevicepersonalization.services.OnDevicePersonalizationApplication;
40 
41 import com.google.common.collect.ImmutableList;
42 import com.google.common.util.concurrent.Futures;
43 import com.google.common.util.concurrent.ListenableFuture;
44 
45 import java.util.Objects;
46 
47 /** Utilities to support loading and executing plugins. */
48 public class PluginProcessRunner implements ProcessRunner {
49     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
50     private static final String TAG = "ProcessUtils";
51     private static final String ENTRY_POINT_CLASS =
52             "com.android.ondevicepersonalization.services.process.OnDevicePersonalizationPlugin";
53 
54     public static final String PARAM_CLASS_NAME_KEY = "param.classname";
55     public static final String PARAM_OPERATION_KEY = "param.operation";
56     public static final String PARAM_SERVICE_INPUT = "param.service_input";
57 
58     @NonNull private final Context mApplicationContext;
59 
60     private static volatile PluginManager sPluginManager;
61 
62     static class Injector {
getClock()63         Clock getClock() {
64             return MonotonicClock.getInstance();
65         }
66     }
67 
68     private final Injector mInjector;
69 
70     /** Creates a ProcessRunner. */
PluginProcessRunner( @onNull Context applicationContext, @NonNull Injector injector)71     PluginProcessRunner(
72             @NonNull Context applicationContext,
73             @NonNull Injector injector) {
74         mApplicationContext = Objects.requireNonNull(applicationContext);
75         mInjector = Objects.requireNonNull(injector);
76     }
77 
78 
79     private static class PluginProcessRunnerLazyInstanceHolder {
80         static final PluginProcessRunner LAZY_INSTANCE =
81                 new PluginProcessRunner(
82                         OnDevicePersonalizationApplication.getAppContext(),
83                         new Injector());
84     }
85 
86     /** Returns the global ProcessRunner */
getInstance()87     @NonNull public static PluginProcessRunner getInstance() {
88         return PluginProcessRunnerLazyInstanceHolder.LAZY_INSTANCE;
89     }
90 
91     /** Loads a service in an isolated process */
loadIsolatedService( @onNull String taskName, @NonNull ComponentName componentName)92     @Override @NonNull public ListenableFuture<IsolatedServiceInfo> loadIsolatedService(
93             @NonNull String taskName, @NonNull ComponentName componentName) {
94         try {
95             String packageName = Objects.requireNonNull(componentName.getPackageName());
96             sLogger.d(TAG + ": loadIsolatedService: " + packageName);
97             return loadPlugin(
98                     mInjector.getClock().elapsedRealtime(),
99                     componentName,
100                     createPluginController(
101                         createPluginId(packageName, taskName),
102                         getPluginManager(mApplicationContext),
103                         packageName));
104         } catch (Exception e) {
105             return Futures.immediateFailedFuture(e);
106         }
107     }
108 
109     /** Executes a service loaded in an isolated process */
runIsolatedService( @onNull IsolatedServiceInfo isolatedProcessInfo, int operationCode, @NonNull Bundle serviceParams)110     @Override @NonNull public ListenableFuture<Bundle> runIsolatedService(
111             @NonNull IsolatedServiceInfo isolatedProcessInfo,
112             int operationCode,
113             @NonNull Bundle serviceParams) {
114         sLogger.d(TAG + ": runIsolatedService: " + isolatedProcessInfo.getComponentName()
115                 + " op: " + operationCode);
116         Bundle pluginParams = new Bundle();
117         pluginParams.putString(
118                 PARAM_CLASS_NAME_KEY,
119                 isolatedProcessInfo.getComponentName().getClassName());
120         pluginParams.putInt(PARAM_OPERATION_KEY, operationCode);
121         pluginParams.putParcelable(PARAM_SERVICE_INPUT, serviceParams);
122         return executePlugin(isolatedProcessInfo.getPluginController(), pluginParams);
123     }
124 
125     /** Unloads a service loaded in an isolated process */
unloadIsolatedService( @onNull IsolatedServiceInfo isolatedServiceInfo)126     @Override @NonNull public ListenableFuture<Void> unloadIsolatedService(
127             @NonNull IsolatedServiceInfo isolatedServiceInfo) {
128         return unloadPlugin(isolatedServiceInfo.getPluginController());
129     }
130 
131     @NonNull
getPluginManager(@onNull Context applicationContext)132     static PluginManager getPluginManager(@NonNull Context applicationContext) {
133         if (sPluginManager == null) {
134             synchronized (PluginProcessRunner.class) {
135                 if (sPluginManager == null) {
136                     sPluginManager = new PluginManagerImpl(applicationContext);
137                 }
138             }
139         }
140         return sPluginManager;
141     }
142 
createPluginController( String taskName, @NonNull PluginManager pluginManager, @Nullable String apkName)143     @NonNull static PluginController createPluginController(
144             String taskName, @NonNull PluginManager pluginManager, @Nullable String apkName)
145             throws Exception {
146         PluginInfo info = PluginInfo.createJvmInfo(
147                 taskName, getArchiveList(apkName), ENTRY_POINT_CLASS);
148         return Objects.requireNonNull(pluginManager.createPluginController(info));
149     }
150 
loadPlugin( long startTimeMillis, @NonNull ComponentName componentName, @NonNull PluginController pluginController)151     @NonNull static ListenableFuture<IsolatedServiceInfo> loadPlugin(
152             long startTimeMillis,
153             @NonNull ComponentName componentName,
154             @NonNull PluginController pluginController) {
155         return CallbackToFutureAdapter.getFuture(
156             completer -> {
157                 try {
158                     sLogger.d(TAG + ": loadPlugin");
159                     pluginController.load(new PluginCallback() {
160                         @Override public void onSuccess(Bundle bundle) {
161                             try {
162                                 completer.set(
163                                         new IsolatedServiceInfo(
164                                             startTimeMillis, componentName,
165                                             pluginController, /* isolatedServiceBinder= */ null));
166                             } catch (OdpServiceException e) {
167                                 completer.setException(e);
168                             }
169 
170                         }
171                         @Override public void onFailure(FailureType failure) {
172                             completer.setException(new OdpServiceException(
173                                     Constants.STATUS_INTERNAL_ERROR,
174                                     String.format("loadPlugin failed. %s", failure.toString())));
175                         }
176                     });
177                 } catch (Exception e) {
178                     completer.setException(e);
179                 }
180                 return "loadPlugin";
181             }
182         );
183     }
184 
185     @NonNull static ListenableFuture<Bundle> executePlugin(
186             @NonNull PluginController pluginController, @NonNull Bundle pluginParams) {
187         return CallbackToFutureAdapter.getFuture(
188             completer -> {
189                 try {
190                     sLogger.d(TAG + ": executePlugin");
191                     pluginController.execute(pluginParams, new PluginCallback() {
192                         @Override public void onSuccess(Bundle bundle) {
193                             completer.set(bundle);
194                         }
195                         @Override public void onFailure(FailureType failure) {
196                             completer.setException(new OdpServiceException(
197                                     Constants.STATUS_SERVICE_FAILED,
198                                     String.format("executePlugin failed: %s", failure.toString())));
199                         }
200                     });
201                 } catch (Exception e) {
202                     completer.setException(e);
203                 }
204                 return "executePlugin";
205             }
206         );
207     }
208 
209     @NonNull static ListenableFuture<Void> unloadPlugin(
210             @NonNull PluginController pluginController) {
211         return CallbackToFutureAdapter.getFuture(
212             completer -> {
213                 try {
214                     sLogger.d(TAG + ": unloadPlugin");
215                     pluginController.unload(new PluginCallback() {
216                         @Override public void onSuccess(Bundle bundle) {
217                             completer.set(null);
218                         }
219                         @Override public void onFailure(FailureType failure) {
220                             completer.setException(new OdpServiceException(
221                                     Constants.STATUS_INTERNAL_ERROR,
222                                     String.format("executePlugin failed: %s", failure.toString())));
223                         }
224                     });
225                 } catch (Exception e) {
226                     completer.setException(e);
227                 }
228                 return "executePlugin";
229             }
230         );
231     }
232 
233     @NonNull static ImmutableList<PluginInfo.ArchiveInfo> getArchiveList(
234             @Nullable String apkName) {
235         if (apkName == null) {
236             return ImmutableList.of();
237         }
238         ImmutableList.Builder<PluginInfo.ArchiveInfo> archiveInfoBuilder = ImmutableList.builder();
239         archiveInfoBuilder.add(
240                 PluginInfo.ArchiveInfo.builder().setPackageName(apkName).build());
241         return archiveInfoBuilder.build();
242     }
243 
244     static String createPluginId(String vendorPackageName, String taskName) {
245         // TODO(b/249345663) Perform any validation needed on the input.
246         return vendorPackageName + "-" + taskName;
247     }
248 }
249