• 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.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