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