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.webtrigger; 18 19 import android.adservices.ondevicepersonalization.CalleeMetadata; 20 import android.adservices.ondevicepersonalization.Constants; 21 import android.adservices.ondevicepersonalization.MeasurementWebTriggerEventParamsParcel; 22 import android.adservices.ondevicepersonalization.WebTriggerInputParcel; 23 import android.adservices.ondevicepersonalization.WebTriggerOutputParcel; 24 import android.adservices.ondevicepersonalization.aidl.IIsolatedModelService; 25 import android.adservices.ondevicepersonalization.aidl.IRegisterMeasurementEventCallback; 26 import android.annotation.NonNull; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.os.Binder; 30 import android.os.Bundle; 31 import android.os.RemoteException; 32 import android.os.SystemClock; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.odp.module.common.Clock; 36 import com.android.odp.module.common.MonotonicClock; 37 import com.android.odp.module.common.PackageUtils; 38 import com.android.ondevicepersonalization.internal.util.LoggerFactory; 39 import com.android.ondevicepersonalization.services.Flags; 40 import com.android.ondevicepersonalization.services.FlagsFactory; 41 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors; 42 import com.android.ondevicepersonalization.services.data.DataAccessPermission; 43 import com.android.ondevicepersonalization.services.data.DataAccessServiceImpl; 44 import com.android.ondevicepersonalization.services.data.user.UserPrivacyStatus; 45 import com.android.ondevicepersonalization.services.inference.IsolatedModelServiceProvider; 46 import com.android.ondevicepersonalization.services.manifest.AppManifestConfig; 47 import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper; 48 import com.android.ondevicepersonalization.services.policyengine.UserDataAccessor; 49 import com.android.ondevicepersonalization.services.serviceflow.ServiceFlow; 50 import com.android.ondevicepersonalization.services.util.LogUtils; 51 import com.android.ondevicepersonalization.services.util.StatsUtils; 52 53 import com.google.common.util.concurrent.FluentFuture; 54 import com.google.common.util.concurrent.FutureCallback; 55 import com.google.common.util.concurrent.Futures; 56 import com.google.common.util.concurrent.ListenableFuture; 57 import com.google.common.util.concurrent.ListeningExecutorService; 58 import com.google.common.util.concurrent.ListeningScheduledExecutorService; 59 import com.google.common.util.concurrent.MoreExecutors; 60 61 import java.util.Objects; 62 import java.util.concurrent.TimeUnit; 63 64 /** 65 * Handles a Web Trigger Registration. 66 */ 67 public class WebTriggerFlow implements ServiceFlow<WebTriggerOutputParcel> { 68 private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); 69 private static final String TAG = "WebTriggerFlow"; 70 private final IRegisterMeasurementEventCallback mCallback; 71 private final long mStartTimeMillis; 72 private final long mServiceEntryTimeMillis; 73 private long mStartServiceTimeMillis; 74 75 @VisibleForTesting 76 static class Injector { getExecutor()77 ListeningExecutorService getExecutor() { 78 return OnDevicePersonalizationExecutors.getBackgroundExecutor(); 79 } 80 getClock()81 Clock getClock() { 82 return MonotonicClock.getInstance(); 83 } 84 getFlags()85 Flags getFlags() { 86 return FlagsFactory.getFlags(); 87 } 88 getScheduledExecutor()89 ListeningScheduledExecutorService getScheduledExecutor() { 90 return OnDevicePersonalizationExecutors.getScheduledExecutor(); 91 } 92 93 } 94 95 @NonNull private final Bundle mParams; 96 @NonNull private final Context mContext; 97 @NonNull private final Injector mInjector; 98 @NonNull private IsolatedModelServiceProvider mModelServiceProvider; 99 private MeasurementWebTriggerEventParamsParcel mServiceParcel; 100 WebTriggerFlow( @onNull Bundle params, @NonNull Context context, @NonNull IRegisterMeasurementEventCallback callback, long startTimeMillis, long serviceEntryTimeMillis)101 public WebTriggerFlow( 102 @NonNull Bundle params, 103 @NonNull Context context, 104 @NonNull IRegisterMeasurementEventCallback callback, 105 long startTimeMillis, 106 long serviceEntryTimeMillis) { 107 this(params, context, callback, startTimeMillis, serviceEntryTimeMillis, new Injector()); 108 } 109 110 @VisibleForTesting WebTriggerFlow( @onNull Bundle params, @NonNull Context context, @NonNull IRegisterMeasurementEventCallback callback, long startTimeMillis, long serviceEntryTimeMillis, @NonNull Injector injector)111 WebTriggerFlow( 112 @NonNull Bundle params, 113 @NonNull Context context, 114 @NonNull IRegisterMeasurementEventCallback callback, 115 long startTimeMillis, 116 long serviceEntryTimeMillis, 117 @NonNull Injector injector) { 118 mParams = params; 119 mContext = Objects.requireNonNull(context); 120 mCallback = callback; 121 mStartTimeMillis = startTimeMillis; 122 mServiceEntryTimeMillis = serviceEntryTimeMillis; 123 mInjector = Objects.requireNonNull(injector); 124 } 125 126 // TO-DO: Add web trigger error codes. 127 @Override isServiceFlowReady()128 public boolean isServiceFlowReady() { 129 mStartServiceTimeMillis = mInjector.getClock().elapsedRealtime(); 130 131 try { 132 if (getGlobalKillSwitch()) { 133 sendErrorResult(Constants.STATUS_INTERNAL_ERROR); 134 return false; 135 } 136 137 if (!UserPrivacyStatus.getInstance().isMeasurementEnabled()) { 138 sLogger.d(TAG + ": User control is not given for measurement."); 139 sendErrorResult(Constants.STATUS_PERSONALIZATION_DISABLED); 140 return false; 141 } 142 143 mServiceParcel = Objects.requireNonNull( 144 mParams.getParcelable( 145 Constants.EXTRA_MEASUREMENT_WEB_TRIGGER_PARAMS, 146 MeasurementWebTriggerEventParamsParcel.class)); 147 148 Objects.requireNonNull(mServiceParcel.getDestinationUrl()); 149 Objects.requireNonNull(mServiceParcel.getAppPackageName()); 150 Objects.requireNonNull(mServiceParcel.getIsolatedService()); 151 Objects.requireNonNull(mServiceParcel.getIsolatedService().getPackageName()); 152 Objects.requireNonNull(mServiceParcel.getIsolatedService().getClassName()); 153 154 if (mServiceParcel.getDestinationUrl().toString().isBlank() 155 || mServiceParcel.getAppPackageName().isBlank() 156 || mServiceParcel.getIsolatedService().getPackageName().isBlank() 157 || mServiceParcel.getIsolatedService().getClassName().isBlank()) { 158 sendErrorResult(Constants.STATUS_INTERNAL_ERROR); 159 return false; 160 } 161 162 if (mServiceParcel.getCertDigest() != null 163 && !mServiceParcel.getCertDigest().isBlank()) { 164 String installedPackageCert = PackageUtils.getCertDigest( 165 mContext, mServiceParcel.getIsolatedService().getPackageName()); 166 if (!mServiceParcel.getCertDigest().equals(installedPackageCert)) { 167 sLogger.i(TAG + ": Dropping trigger event due to cert mismatch"); 168 sendErrorResult(Constants.STATUS_INTERNAL_ERROR); 169 return false; 170 } 171 } 172 173 AppManifestConfig config = Objects.requireNonNull( 174 AppManifestConfigHelper.getAppManifestConfig( 175 mContext, mServiceParcel.getIsolatedService().getPackageName())); 176 if (!mServiceParcel.getIsolatedService() 177 .getClassName() 178 .equals(config.getServiceName())) { 179 sLogger.d(TAG + ": service class not found"); 180 sendErrorResult(Constants.STATUS_CLASS_NOT_FOUND); 181 return false; 182 } 183 184 return true; 185 } catch (Exception e) { 186 sendErrorResult(Constants.STATUS_INTERNAL_ERROR); 187 return false; 188 } 189 } 190 191 @Override getService()192 public ComponentName getService() { 193 return mServiceParcel.getIsolatedService(); 194 } 195 196 @Override getServiceParams()197 public Bundle getServiceParams() { 198 Bundle serviceParams = new Bundle(); 199 serviceParams.putParcelable(Constants.EXTRA_INPUT, 200 new WebTriggerInputParcel.Builder( 201 mServiceParcel.getDestinationUrl(), mServiceParcel.getAppPackageName(), 202 mServiceParcel.getEventData()) 203 .build()); 204 serviceParams.putBinder(Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER, 205 new DataAccessServiceImpl( 206 mServiceParcel.getIsolatedService(), 207 mContext, /* localDataPermission */ DataAccessPermission.READ_WRITE, 208 /* eventDataPermission */ DataAccessPermission.READ_ONLY)); 209 serviceParams.putParcelable(Constants.EXTRA_USER_DATA, 210 new UserDataAccessor().getUserData()); 211 212 mModelServiceProvider = new IsolatedModelServiceProvider(); 213 IIsolatedModelService modelService = mModelServiceProvider.getModelService(mContext); 214 serviceParams.putBinder(Constants.EXTRA_MODEL_SERVICE_BINDER, modelService.asBinder()); 215 216 return serviceParams; 217 } 218 219 @Override uploadServiceFlowMetrics(ListenableFuture<Bundle> runServiceFuture)220 public void uploadServiceFlowMetrics(ListenableFuture<Bundle> runServiceFuture) { 221 var unused = 222 FluentFuture.from(runServiceFuture) 223 .transform( 224 val -> { 225 StatsUtils.writeServiceRequestMetrics( 226 Constants.API_NAME_SERVICE_ON_WEB_TRIGGER, 227 mServiceParcel.getIsolatedService().getPackageName(), 228 val, 229 mInjector.getClock(), 230 Constants.STATUS_SUCCESS, 231 mStartServiceTimeMillis); 232 return val; 233 }, 234 mInjector.getExecutor()) 235 .catchingAsync( 236 Exception.class, 237 e -> { 238 StatsUtils.writeServiceRequestMetrics( 239 Constants.API_NAME_SERVICE_ON_WEB_TRIGGER, 240 mServiceParcel.getIsolatedService().getPackageName(), 241 /* result= */ null, 242 mInjector.getClock(), 243 Constants.STATUS_INTERNAL_ERROR, 244 mStartServiceTimeMillis); 245 return Futures.immediateFailedFuture(e); 246 }, 247 mInjector.getExecutor()); 248 } 249 250 @Override getServiceFlowResultFuture( ListenableFuture<Bundle> runServiceFuture)251 public ListenableFuture<WebTriggerOutputParcel> getServiceFlowResultFuture( 252 ListenableFuture<Bundle> runServiceFuture) { 253 return FluentFuture.from(runServiceFuture) 254 .transform( 255 result -> result.getParcelable( 256 Constants.EXTRA_RESULT, WebTriggerOutputParcel.class), 257 mInjector.getExecutor()) 258 .transform( 259 result -> { 260 writeToLog(mServiceParcel, result); 261 return result; 262 }, 263 mInjector.getExecutor()) 264 .withTimeout( 265 mInjector.getFlags().getIsolatedServiceDeadlineSeconds(), 266 TimeUnit.SECONDS, 267 mInjector.getScheduledExecutor() 268 ); 269 } 270 271 @Override returnResultThroughCallback( ListenableFuture<WebTriggerOutputParcel> serviceFlowResultFuture)272 public void returnResultThroughCallback( 273 ListenableFuture<WebTriggerOutputParcel> serviceFlowResultFuture) { 274 Futures.addCallback( 275 serviceFlowResultFuture, 276 new FutureCallback<WebTriggerOutputParcel>() { 277 @Override 278 public void onSuccess(WebTriggerOutputParcel result) { 279 sendSuccessResult(result); 280 } 281 282 @Override 283 public void onFailure(Throwable t) { 284 sLogger.w(TAG + ": Request failed.", t); 285 sendErrorResult(Constants.STATUS_INTERNAL_ERROR); 286 } 287 }, 288 mInjector.getExecutor()); 289 } 290 291 @Override cleanUpServiceParams()292 public void cleanUpServiceParams() { 293 mModelServiceProvider.unBindFromModelService(); 294 } 295 writeToLog( MeasurementWebTriggerEventParamsParcel wtparams, WebTriggerOutputParcel result)296 private void writeToLog( 297 MeasurementWebTriggerEventParamsParcel wtparams, 298 WebTriggerOutputParcel result) { 299 sLogger.d(TAG + ": writeToLog() started."); 300 var unused = FluentFuture.from( 301 LogUtils.writeLogRecords( 302 Constants.TASK_TYPE_WEB_TRIGGER, 303 mContext, 304 mServiceParcel.getAppPackageName(), 305 wtparams.getIsolatedService(), 306 result.getRequestLogRecord(), 307 result.getEventLogRecords())) 308 .transform(v -> null, MoreExecutors.newDirectExecutorService()); 309 } 310 getGlobalKillSwitch()311 private boolean getGlobalKillSwitch() { 312 long origId = Binder.clearCallingIdentity(); 313 boolean globalKillSwitch = mInjector.getFlags().getGlobalKillSwitch(); 314 Binder.restoreCallingIdentity(origId); 315 return globalKillSwitch; 316 } 317 sendSuccessResult(WebTriggerOutputParcel result)318 private void sendSuccessResult(WebTriggerOutputParcel result) { 319 int responseCode = Constants.STATUS_SUCCESS; 320 try { 321 mCallback.onSuccess( 322 new CalleeMetadata.Builder() 323 .setServiceEntryTimeMillis(mServiceEntryTimeMillis) 324 .setCallbackInvokeTimeMillis(SystemClock.elapsedRealtime()).build()); 325 } catch (RemoteException e) { 326 responseCode = Constants.STATUS_INTERNAL_ERROR; 327 sLogger.w(TAG + ": Callback error", e); 328 } finally { 329 // TODO(b/327683908) - define enum for notifyMeasurementApi 330 } 331 } 332 sendErrorResult(int errorCode)333 private void sendErrorResult(int errorCode) { 334 try { 335 mCallback.onError( 336 errorCode, 337 new CalleeMetadata.Builder() 338 .setServiceEntryTimeMillis(mServiceEntryTimeMillis) 339 .setCallbackInvokeTimeMillis(SystemClock.elapsedRealtime()).build()); 340 } catch (RemoteException e) { 341 sLogger.w(TAG + ": Callback error", e); 342 } finally { 343 // TODO(b/327683908) - define enum for notifyMeasurementApi 344 } 345 } 346 } 347