• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.adservices.service.adselection;
18 
19 import static android.adservices.adselection.ReportEventRequest.FLAG_REPORTING_DESTINATION_BUYER;
20 import static android.adservices.adselection.ReportEventRequest.FLAG_REPORTING_DESTINATION_COMPONENT_SELLER;
21 import static android.adservices.adselection.ReportEventRequest.FLAG_REPORTING_DESTINATION_SELLER;
22 import static android.adservices.adselection.ReportEventRequest.REPORT_EVENT_MAX_INTERACTION_DATA_SIZE_B;
23 import static android.adservices.common.AdServicesStatusUtils.STATUS_BACKGROUND_CALLER;
24 import static android.adservices.common.AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED;
25 import static android.adservices.common.AdServicesStatusUtils.STATUS_INVALID_ARGUMENT;
26 import static android.adservices.common.AdServicesStatusUtils.STATUS_RATE_LIMIT_REACHED;
27 import static android.adservices.common.AdServicesStatusUtils.STATUS_UNAUTHORIZED;
28 
29 import static com.android.adservices.service.common.AppManifestConfigCall.API_AD_SELECTION;
30 import static com.android.adservices.service.common.FledgeAuthorizationFilter.AdTechNotAllowedException;
31 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__REPORT_INTERACTION;
32 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__EVENT_REPORTER_NOTIFY_FAILURE_FILTER_EXCEPTION_BACKGROUND_CALLER;
33 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__EVENT_REPORTER_NOTIFY_FAILURE_FILTER_EXCEPTION_CALLER_NOT_ALLOWED;
34 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__EVENT_REPORTER_NOTIFY_FAILURE_FILTER_EXCEPTION_RATE_LIMIT_REACHED;
35 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__EVENT_REPORTER_NOTIFY_FAILURE_FILTER_EXCEPTION_UNAUTHORIZED;
36 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__EVENT_REPORTER_NOTIFY_FAILURE_INTERNAL_ERROR;
37 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__EVENT_REPORTER_NOTIFY_FAILURE_INVALID_ARGUMENT;
38 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__EVENT_REPORTER_NOTIFY_FAILURE_TO_CALLER_FAILED;
39 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__EVENT_REPORTER_NOTIFY_SUCCESS_TO_CALLER_FAILED;
40 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__REPORT_INTERACTION;
41 
42 import static java.util.Locale.ENGLISH;
43 
44 import android.adservices.adselection.ReportEventRequest;
45 import android.adservices.adselection.ReportInteractionCallback;
46 import android.adservices.adselection.ReportInteractionInput;
47 import android.adservices.common.AdServicesStatusUtils;
48 import android.adservices.common.FledgeErrorResponse;
49 import android.annotation.NonNull;
50 import android.net.Uri;
51 import android.os.Build;
52 import android.os.RemoteException;
53 import android.os.Trace;
54 
55 import androidx.annotation.RequiresApi;
56 
57 import com.android.adservices.LoggerFactory;
58 import com.android.adservices.data.adselection.AdSelectionEntryDao;
59 import com.android.adservices.errorlogging.ErrorLogUtil;
60 import com.android.adservices.service.DebugFlags;
61 import com.android.adservices.service.Flags;
62 import com.android.adservices.service.common.AdSelectionServiceFilter;
63 import com.android.adservices.service.common.FledgeAuthorizationFilter;
64 import com.android.adservices.service.common.Throttler;
65 import com.android.adservices.service.common.httpclient.AdServicesHttpsClient;
66 import com.android.adservices.service.devapi.DevContext;
67 import com.android.adservices.service.exception.FilterException;
68 import com.android.adservices.service.profiling.Tracing;
69 import com.android.adservices.service.stats.AdServicesLogger;
70 import com.android.adservices.service.stats.AdsRelevanceStatusUtils;
71 import com.android.internal.util.Preconditions;
72 
73 import com.google.common.util.concurrent.FluentFuture;
74 import com.google.common.util.concurrent.Futures;
75 import com.google.common.util.concurrent.ListenableFuture;
76 import com.google.common.util.concurrent.ListeningExecutorService;
77 import com.google.common.util.concurrent.MoreExecutors;
78 
79 import java.nio.charset.StandardCharsets;
80 import java.util.ArrayList;
81 import java.util.List;
82 import java.util.Objects;
83 import java.util.concurrent.ExecutorService;
84 
85 /** Encapsulates the Event Reporting logic */
86 @RequiresApi(Build.VERSION_CODES.S)
87 public abstract class EventReporter {
88     public static final String NO_MATCH_FOUND_IN_AD_SELECTION_DB =
89             "Could not find a match in the database for this adSelectionId and callerPackageName!";
90     public static final String INTERACTION_DATA_SIZE_MAX_EXCEEDED = "Event data max size exceeded!";
91     public static final String INTERACTION_KEY_SIZE_MAX_EXCEEDED = "Event key max size exceeded!";
92     static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
93     static final int LOGGING_API_NAME = AD_SERVICES_API_CALLED__API_NAME__REPORT_INTERACTION;
94 
95     @ReportEventRequest.ReportingDestination
96     private static final int[] POSSIBLE_DESTINATIONS =
97             new int[] {
98                 FLAG_REPORTING_DESTINATION_SELLER,
99                 FLAG_REPORTING_DESTINATION_BUYER,
100                 FLAG_REPORTING_DESTINATION_COMPONENT_SELLER
101             };
102 
103     private static final int CEL_PPAPI_NAME =
104             AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__REPORT_INTERACTION;
105 
106     @NonNull private final AdSelectionEntryDao mAdSelectionEntryDao;
107     @NonNull private final AdServicesHttpsClient mAdServicesHttpsClient;
108     @NonNull final ListeningExecutorService mLightweightExecutorService;
109     @NonNull private final ListeningExecutorService mBackgroundExecutorService;
110     @NonNull final AdServicesLogger mAdServicesLogger;
111     @NonNull final Flags mFlags;
112     @NonNull final DebugFlags mDebugFlags;
113     @NonNull private final AdSelectionServiceFilter mAdSelectionServiceFilter;
114     private int mCallerUid;
115     @NonNull private final FledgeAuthorizationFilter mFledgeAuthorizationFilter;
116     @NonNull private final DevContext mDevContext;
117     private final boolean mShouldUseUnifiedTables;
118 
EventReporter( @onNull AdSelectionEntryDao adSelectionEntryDao, @NonNull AdServicesHttpsClient adServicesHttpsClient, @NonNull ExecutorService lightweightExecutorService, @NonNull ExecutorService backgroundExecutorService, @NonNull AdServicesLogger adServicesLogger, @NonNull Flags flags, @NonNull DebugFlags debugFlags, @NonNull AdSelectionServiceFilter adSelectionServiceFilter, int callerUid, @NonNull FledgeAuthorizationFilter fledgeAuthorizationFilter, @NonNull DevContext devContext, boolean shouldUseUnifiedTables)119     public EventReporter(
120             @NonNull AdSelectionEntryDao adSelectionEntryDao,
121             @NonNull AdServicesHttpsClient adServicesHttpsClient,
122             @NonNull ExecutorService lightweightExecutorService,
123             @NonNull ExecutorService backgroundExecutorService,
124             @NonNull AdServicesLogger adServicesLogger,
125             @NonNull Flags flags,
126             @NonNull DebugFlags debugFlags,
127             @NonNull AdSelectionServiceFilter adSelectionServiceFilter,
128             int callerUid,
129             @NonNull FledgeAuthorizationFilter fledgeAuthorizationFilter,
130             @NonNull DevContext devContext,
131             boolean shouldUseUnifiedTables) {
132         Objects.requireNonNull(adSelectionEntryDao);
133         Objects.requireNonNull(adServicesHttpsClient);
134         Objects.requireNonNull(lightweightExecutorService);
135         Objects.requireNonNull(backgroundExecutorService);
136         Objects.requireNonNull(adServicesLogger);
137         Objects.requireNonNull(flags);
138         Objects.requireNonNull(debugFlags);
139         Objects.requireNonNull(adSelectionServiceFilter);
140         Objects.requireNonNull(fledgeAuthorizationFilter);
141         Objects.requireNonNull(devContext);
142 
143         mAdSelectionEntryDao = adSelectionEntryDao;
144         mAdServicesHttpsClient = adServicesHttpsClient;
145         mLightweightExecutorService = MoreExecutors.listeningDecorator(lightweightExecutorService);
146         mBackgroundExecutorService = MoreExecutors.listeningDecorator(backgroundExecutorService);
147         mAdServicesLogger = adServicesLogger;
148         mFlags = flags;
149         mDebugFlags = debugFlags;
150         mAdSelectionServiceFilter = adSelectionServiceFilter;
151         mCallerUid = callerUid;
152         mFledgeAuthorizationFilter = fledgeAuthorizationFilter;
153         mDevContext = devContext;
154         mShouldUseUnifiedTables = shouldUseUnifiedTables;
155     }
156 
157     /**
158      * Run the interaction report logic asynchronously. Searches the {@code
159      * registered_ad_interactions} database for matches based on the provided {@code adSelectionId},
160      * {@code interactionKey}, {@code destinations} that we get from {@link ReportInteractionInput}
161      * Then, attaches {@code interactionData} to each found Uri and performs a POST request.
162      *
163      * <p>After validating the inputParams and request context, invokes {@link
164      * ReportInteractionCallback#onSuccess()} before continuing with reporting. If we encounter a
165      * failure during request validation, we invoke {@link
166      * ReportInteractionCallback#onFailure(FledgeErrorResponse)} and exit early.
167      */
reportInteraction( @onNull ReportInteractionInput input, @NonNull ReportInteractionCallback callback)168     public abstract void reportInteraction(
169             @NonNull ReportInteractionInput input, @NonNull ReportInteractionCallback callback);
170 
filterAndValidateRequest(ReportInteractionInput input)171     void filterAndValidateRequest(ReportInteractionInput input) {
172         long registeredAdBeaconsMaxInteractionKeySizeB =
173                 mFlags.getFledgeReportImpressionRegisteredAdBeaconsMaxInteractionKeySizeB();
174 
175         try {
176             Trace.beginSection(Tracing.VALIDATE_REQUEST);
177             sLogger.v("Starting filtering and validation.");
178             mAdSelectionServiceFilter.filterRequest(
179                     null,
180                     input.getCallerPackageName(),
181                     mFlags.getEnforceForegroundStatusForFledgeReportInteraction(),
182                     true,
183                     !mDebugFlags.getConsentNotificationDebugMode(),
184                     mCallerUid,
185                     LOGGING_API_NAME,
186                     Throttler.ApiKey.FLEDGE_API_REPORT_INTERACTION,
187                     mDevContext);
188             validateAdSelectionIdAndCallerPackageNameExistence(
189                     input.getAdSelectionId(), input.getCallerPackageName());
190             Preconditions.checkArgument(
191                     input.getInteractionKey().getBytes(StandardCharsets.UTF_8).length
192                             <= registeredAdBeaconsMaxInteractionKeySizeB,
193                     INTERACTION_KEY_SIZE_MAX_EXCEEDED);
194             Preconditions.checkArgument(
195                     input.getInteractionData().getBytes(StandardCharsets.UTF_8).length
196                             <= REPORT_EVENT_MAX_INTERACTION_DATA_SIZE_B,
197                     INTERACTION_DATA_SIZE_MAX_EXCEEDED);
198         } finally {
199             sLogger.v("Completed filtering and validation.");
200             Trace.endSection();
201         }
202     }
203 
getReportingUris(ReportInteractionInput input)204     FluentFuture<List<Uri>> getReportingUris(ReportInteractionInput input) {
205         sLogger.v(
206                 "Fetching ad selection entry ID %d for caller \"%s\"",
207                 input.getAdSelectionId(), input.getCallerPackageName());
208         long adSelectionId = input.getAdSelectionId();
209         int destinationsBitField = input.getReportingDestinations();
210         String interactionKey = input.getInteractionKey();
211 
212         return FluentFuture.from(
213                         mBackgroundExecutorService.submit(
214                                 () -> {
215                                     List<Uri> resultingReportingUris = new ArrayList<>();
216                                     for (int destination : POSSIBLE_DESTINATIONS) {
217                                         if (bitExists(destination, destinationsBitField)) {
218                                             if (mAdSelectionEntryDao
219                                                     .doesRegisteredAdInteractionExist(
220                                                             adSelectionId,
221                                                             interactionKey,
222                                                             destination)) {
223                                                 sLogger.v(
224                                                         "Found registered ad beacons for"
225                                                                 + " id:%s, key:%s and dest:%s",
226                                                         adSelectionId, interactionKey, destination);
227                                                 resultingReportingUris.add(
228                                                         mAdSelectionEntryDao
229                                                                 .getRegisteredAdInteractionUri(
230                                                                         adSelectionId,
231                                                                         interactionKey,
232                                                                         destination));
233                                             } else {
234                                                 sLogger.w(
235                                                         "Registered ad beacon URIs not found for"
236                                                                 + " id:%s, key:%s and dest:%s",
237                                                         adSelectionId, interactionKey, destination);
238                                             }
239                                         }
240                                     }
241                                     return resultingReportingUris;
242                                 }))
243                 .transformAsync(this::filterReportingUris, mLightweightExecutorService);
244     }
245 
filterReportingUris(List<Uri> reportingUris)246     private FluentFuture<List<Uri>> filterReportingUris(List<Uri> reportingUris) {
247         return FluentFuture.from(
248                 mLightweightExecutorService.submit(
249                         () -> {
250                             if (mFlags.getDisableFledgeEnrollmentCheck()) {
251                                 return reportingUris;
252                             } else {
253                                 // Do enrollment check and only add Uris that pass enrollment
254                                 ArrayList<Uri> validatedUris = new ArrayList<>();
255 
256                                 for (Uri uri : reportingUris) {
257                                     try {
258                                         mFledgeAuthorizationFilter.assertAdTechFromUriEnrolled(
259                                                 uri, LOGGING_API_NAME, API_AD_SELECTION);
260                                         validatedUris.add(uri);
261                                     } catch (AdTechNotAllowedException exception) {
262                                         sLogger.d(
263                                                 String.format(
264                                                         ENGLISH,
265                                                         "Enrollment check failed! Skipping"
266                                                                 + " reporting for %s:",
267                                                         uri));
268                                     }
269                                 }
270                                 sLogger.v("Validated uris: %s", validatedUris);
271                                 return validatedUris;
272                             }
273                         }));
274     }
275 
276     ListenableFuture<List<Void>> reportUris(List<Uri> reportingUris, ReportInteractionInput input) {
277         List<ListenableFuture<Void>> reportingFuturesList = new ArrayList<>();
278         String eventData = input.getInteractionData();
279 
280         for (Uri uri : reportingUris) {
281             sLogger.v("Uri to report the event: %s.", uri);
282             reportingFuturesList.add(
283                     mAdServicesHttpsClient.postPlainText(uri, eventData, mDevContext));
284         }
285         return Futures.allAsList(reportingFuturesList);
286     }
287 
288     void notifySuccessToCaller(@NonNull ReportInteractionCallback callback) {
289         try {
290             callback.onSuccess();
291         } catch (RemoteException e) {
292             ErrorLogUtil.e(
293                     e,
294                     AD_SERVICES_ERROR_REPORTED__ERROR_CODE__EVENT_REPORTER_NOTIFY_SUCCESS_TO_CALLER_FAILED,
295                     CEL_PPAPI_NAME);
296             sLogger.e(e, "Unable to send successful result to the callback");
297         }
298     }
299 
300     void notifyFailureToCaller(
301             @NonNull String callerAppPackageName,
302             @NonNull ReportInteractionCallback callback,
303             @NonNull Throwable t) {
304         int resultCode;
305 
306         boolean isFilterException = t instanceof FilterException;
307 
308         if (isFilterException) {
309             resultCode = FilterException.getResultCode(t);
310         } else if (t instanceof IllegalArgumentException) {
311             resultCode = AdServicesStatusUtils.STATUS_INVALID_ARGUMENT;
312         } else {
313             resultCode = AdServicesStatusUtils.STATUS_INTERNAL_ERROR;
314         }
315         logExceptionCel(t, resultCode);
316         // Skip logging if a FilterException occurs.
317         // AdSelectionServiceFilter ensures the failing assertion is logged internally.
318         // Note: Failure is logged before the callback to ensure deterministic testing.
319         if (!isFilterException) {
320             mAdServicesLogger.logFledgeApiCallStats(
321                     LOGGING_API_NAME, callerAppPackageName, resultCode, /*latencyMs=*/ 0);
322         }
323 
324         try {
325             callback.onFailure(
326                     new FledgeErrorResponse.Builder()
327                             .setStatusCode(resultCode)
328                             .setErrorMessage(t.getMessage())
329                             .build());
330         } catch (RemoteException e) {
331             // A ReportEventDisabledImpl can reach below from a binder thread.
332             AdsRelevanceStatusUtils.logCelInsideBinderThread(
333                     e,
334                     AD_SERVICES_ERROR_REPORTED__ERROR_CODE__EVENT_REPORTER_NOTIFY_FAILURE_TO_CALLER_FAILED,
335                     CEL_PPAPI_NAME);
336             sLogger.e(e, "Unable to send failed result to the callback");
337         }
338     }
339 
340     private void validateAdSelectionIdAndCallerPackageNameExistence(
341             long adSelectionId, String callerPackageName) {
342         if (mFlags.getFledgeAuctionServerEnabledForReportEvent() || mShouldUseUnifiedTables) {
343             Preconditions.checkArgument(
344                     mAdSelectionEntryDao.doesAdSelectionIdAndCallerPackageNameExists(
345                             adSelectionId, callerPackageName),
346                     NO_MATCH_FOUND_IN_AD_SELECTION_DB);
347 
348         } else {
349             Preconditions.checkArgument(
350                     mAdSelectionEntryDao
351                             .doesAdSelectionMatchingCallerPackageNameExistInOnDeviceTable(
352                                     adSelectionId, callerPackageName),
353                     NO_MATCH_FOUND_IN_AD_SELECTION_DB);
354         }
355     }
356 
357     private boolean bitExists(
358             @ReportEventRequest.ReportingDestination int bit,
359             @ReportEventRequest.ReportingDestination int bitSet) {
360         return (bit & bitSet) != 0;
361     }
362 
363     private void logExceptionCel(Throwable exception, int resultCode) {
364         int celEnum =
365                 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__EVENT_REPORTER_NOTIFY_FAILURE_INTERNAL_ERROR;
366         switch (resultCode) {
367             case STATUS_BACKGROUND_CALLER:
368                 celEnum =
369                         AD_SERVICES_ERROR_REPORTED__ERROR_CODE__EVENT_REPORTER_NOTIFY_FAILURE_FILTER_EXCEPTION_BACKGROUND_CALLER;
370                 break;
371             case STATUS_CALLER_NOT_ALLOWED:
372                 celEnum =
373                         AD_SERVICES_ERROR_REPORTED__ERROR_CODE__EVENT_REPORTER_NOTIFY_FAILURE_FILTER_EXCEPTION_CALLER_NOT_ALLOWED;
374                 break;
375             case STATUS_UNAUTHORIZED:
376                 celEnum =
377                         AD_SERVICES_ERROR_REPORTED__ERROR_CODE__EVENT_REPORTER_NOTIFY_FAILURE_FILTER_EXCEPTION_UNAUTHORIZED;
378                 break;
379             case STATUS_RATE_LIMIT_REACHED:
380                 celEnum =
381                         AD_SERVICES_ERROR_REPORTED__ERROR_CODE__EVENT_REPORTER_NOTIFY_FAILURE_FILTER_EXCEPTION_RATE_LIMIT_REACHED;
382                 break;
383             case STATUS_INVALID_ARGUMENT:
384                 celEnum =
385                         AD_SERVICES_ERROR_REPORTED__ERROR_CODE__EVENT_REPORTER_NOTIFY_FAILURE_INVALID_ARGUMENT;
386                 break;
387         }
388         // A ReportEventDisabledImpl can reach below from a binder thread.
389         AdsRelevanceStatusUtils.logCelInsideBinderThread(exception, celEnum, CEL_PPAPI_NAME);
390     }
391 }
392