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 = FluentFuture.from(runServiceFuture) 222 .transform( 223 val -> { 224 StatsUtils.writeServiceRequestMetrics( 225 Constants.API_NAME_SERVICE_ON_WEB_TRIGGER, 226 val, mInjector.getClock(), 227 Constants.STATUS_SUCCESS, mStartServiceTimeMillis); 228 return val; 229 }, 230 mInjector.getExecutor() 231 ) 232 .catchingAsync( 233 Exception.class, 234 e -> { 235 StatsUtils.writeServiceRequestMetrics( 236 Constants.API_NAME_SERVICE_ON_WEB_TRIGGER, /* result= */ null, 237 mInjector.getClock(), 238 Constants.STATUS_INTERNAL_ERROR, mStartServiceTimeMillis); 239 return Futures.immediateFailedFuture(e); 240 }, 241 mInjector.getExecutor() 242 ); 243 } 244 245 @Override getServiceFlowResultFuture( ListenableFuture<Bundle> runServiceFuture)246 public ListenableFuture<WebTriggerOutputParcel> getServiceFlowResultFuture( 247 ListenableFuture<Bundle> runServiceFuture) { 248 return FluentFuture.from(runServiceFuture) 249 .transform( 250 result -> result.getParcelable( 251 Constants.EXTRA_RESULT, WebTriggerOutputParcel.class), 252 mInjector.getExecutor()) 253 .transform( 254 result -> { 255 writeToLog(mServiceParcel, result); 256 return result; 257 }, 258 mInjector.getExecutor()) 259 .withTimeout( 260 mInjector.getFlags().getIsolatedServiceDeadlineSeconds(), 261 TimeUnit.SECONDS, 262 mInjector.getScheduledExecutor() 263 ); 264 } 265 266 @Override returnResultThroughCallback( ListenableFuture<WebTriggerOutputParcel> serviceFlowResultFuture)267 public void returnResultThroughCallback( 268 ListenableFuture<WebTriggerOutputParcel> serviceFlowResultFuture) { 269 Futures.addCallback( 270 serviceFlowResultFuture, 271 new FutureCallback<WebTriggerOutputParcel>() { 272 @Override 273 public void onSuccess(WebTriggerOutputParcel result) { 274 sendSuccessResult(result); 275 } 276 277 @Override 278 public void onFailure(Throwable t) { 279 sLogger.w(TAG + ": Request failed.", t); 280 sendErrorResult(Constants.STATUS_INTERNAL_ERROR); 281 } 282 }, 283 mInjector.getExecutor()); 284 } 285 286 @Override cleanUpServiceParams()287 public void cleanUpServiceParams() { 288 mModelServiceProvider.unBindFromModelService(); 289 } 290 writeToLog( MeasurementWebTriggerEventParamsParcel wtparams, WebTriggerOutputParcel result)291 private void writeToLog( 292 MeasurementWebTriggerEventParamsParcel wtparams, 293 WebTriggerOutputParcel result) { 294 sLogger.d(TAG + ": writeToLog() started."); 295 var unused = FluentFuture.from( 296 LogUtils.writeLogRecords( 297 mContext, 298 mServiceParcel.getAppPackageName(), 299 wtparams.getIsolatedService(), 300 result.getRequestLogRecord(), 301 result.getEventLogRecords())) 302 .transform(v -> null, MoreExecutors.newDirectExecutorService()); 303 } 304 getGlobalKillSwitch()305 private boolean getGlobalKillSwitch() { 306 long origId = Binder.clearCallingIdentity(); 307 boolean globalKillSwitch = mInjector.getFlags().getGlobalKillSwitch(); 308 Binder.restoreCallingIdentity(origId); 309 return globalKillSwitch; 310 } 311 sendSuccessResult(WebTriggerOutputParcel result)312 private void sendSuccessResult(WebTriggerOutputParcel result) { 313 int responseCode = Constants.STATUS_SUCCESS; 314 try { 315 mCallback.onSuccess( 316 new CalleeMetadata.Builder() 317 .setServiceEntryTimeMillis(mServiceEntryTimeMillis) 318 .setCallbackInvokeTimeMillis(SystemClock.elapsedRealtime()).build()); 319 } catch (RemoteException e) { 320 responseCode = Constants.STATUS_INTERNAL_ERROR; 321 sLogger.w(TAG + ": Callback error", e); 322 } finally { 323 // TODO(b/327683908) - define enum for notifyMeasurementApi 324 } 325 } 326 sendErrorResult(int errorCode)327 private void sendErrorResult(int errorCode) { 328 try { 329 mCallback.onError( 330 errorCode, 331 new CalleeMetadata.Builder() 332 .setServiceEntryTimeMillis(mServiceEntryTimeMillis) 333 .setCallbackInvokeTimeMillis(SystemClock.elapsedRealtime()).build()); 334 } catch (RemoteException e) { 335 sLogger.w(TAG + ": Callback error", e); 336 } finally { 337 // TODO(b/327683908) - define enum for notifyMeasurementApi 338 } 339 } 340 } 341