• 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.measurement.noising;
18 
19 import android.annotation.NonNull;
20 import android.net.Uri;
21 
22 import com.android.adservices.service.Flags;
23 import com.android.adservices.service.measurement.PrivacyParams;
24 import com.android.adservices.service.measurement.ReportSpec;
25 import com.android.adservices.service.measurement.Source;
26 import com.android.adservices.service.measurement.reporting.EventReportWindowCalcDelegate;
27 import com.android.adservices.service.measurement.util.UnsignedLong;
28 
29 import com.google.common.annotations.VisibleForTesting;
30 import com.google.common.collect.ImmutableList;
31 
32 import java.math.BigDecimal;
33 import java.math.RoundingMode;
34 import java.util.Arrays;
35 import java.util.Collections;
36 import java.util.List;
37 import java.util.Optional;
38 import java.util.Random;
39 import java.util.stream.Collectors;
40 
41 /** Generates noised reports for the provided source. */
42 public class SourceNoiseHandler {
43     private static final int PROBABILITY_DECIMAL_POINTS_LIMIT = 7;
44 
45     private final Flags mFlags;
46     private final EventReportWindowCalcDelegate mEventReportWindowCalcDelegate;
47 
SourceNoiseHandler(@onNull Flags flags)48     public SourceNoiseHandler(@NonNull Flags flags) {
49         mFlags = flags;
50         mEventReportWindowCalcDelegate = new EventReportWindowCalcDelegate(flags);
51     }
52 
53     @VisibleForTesting
SourceNoiseHandler( @onNull Flags flags, @NonNull EventReportWindowCalcDelegate eventReportWindowCalcDelegate)54     SourceNoiseHandler(
55             @NonNull Flags flags,
56             @NonNull EventReportWindowCalcDelegate eventReportWindowCalcDelegate) {
57         mFlags = flags;
58         mEventReportWindowCalcDelegate = eventReportWindowCalcDelegate;
59     }
60 
61     /** Multiplier is 1, when only one destination needs to be considered. */
62     public static final int SINGLE_DESTINATION_IMPRESSION_NOISE_MULTIPLIER = 1;
63 
64     /**
65      * Double-folds the number of states in order to allocate half to app destination and half to
66      * web destination for fake reports generation.
67      */
68     public static final int DUAL_DESTINATION_IMPRESSION_NOISE_MULTIPLIER = 2;
69 
70     /**
71      * Assign attribution mode based on random rate and generate fake reports if needed. Should only
72      * be called for a new Source.
73      *
74      * @return fake reports to be stored in the datastore.
75      */
assignAttributionModeAndGenerateFakeReports( @onNull Source source)76     public List<Source.FakeReport> assignAttributionModeAndGenerateFakeReports(
77             @NonNull Source source) {
78         Random rand = new Random();
79         double value = rand.nextDouble();
80         if (value > getRandomAttributionProbability(source)) {
81             source.setAttributionMode(Source.AttributionMode.TRUTHFULLY);
82             return Collections.emptyList();
83         }
84 
85         List<Source.FakeReport> fakeReports;
86         ReportSpec flexEventReportSpec = source.getFlexEventReportSpec();
87         if (flexEventReportSpec == null) {
88             if (isVtcDualDestinationModeWithPostInstallEnabled(source)) {
89                 // Source is 'EVENT' type, both app and web destination are set and install
90                 // exclusivity
91                 // window is provided. Pick one of the static reporting states randomly.
92                 fakeReports = generateVtcDualDestinationPostInstallFakeReports(source);
93             } else {
94                 // There will at least be one (app or web) destination available
95                 ImpressionNoiseParams noiseParams = getImpressionNoiseParams(source);
96                 fakeReports =
97                         ImpressionNoiseUtil.selectRandomStateAndGenerateReportConfigs(
98                                         noiseParams, rand)
99                                 .stream()
100                                 .map(
101                                         reportConfig ->
102                                                 new Source.FakeReport(
103                                                         new UnsignedLong(
104                                                                 Long.valueOf(reportConfig[0])),
105                                                         mEventReportWindowCalcDelegate
106                                                                 .getReportingTimeForNoising(
107                                                                         source,
108                                                                         reportConfig[1],
109                                                                         isInstallDetectionEnabled(
110                                                                                 source)),
111                                                         resolveFakeReportDestinations(
112                                                                 source, reportConfig[2])))
113                                 .collect(Collectors.toList());
114             }
115         } else {
116             int destinationTypeMultiplier = getDestinationTypeMultiplier(source);
117             List<int[]> fakeReportConfigs =
118                     ImpressionNoiseUtil.selectFlexEventReportRandomStateAndGenerateReportConfigs(
119                             flexEventReportSpec, destinationTypeMultiplier, rand);
120             fakeReports =
121                     fakeReportConfigs.stream()
122                             .map(
123                                     reportConfig ->
124                                             new Source.FakeReport(
125                                                     new UnsignedLong(
126                                                             Long.valueOf(
127                                                                     flexEventReportSpec
128                                                                             .getTriggerDataValue(
129                                                                                     reportConfig[
130                                                                                             0]))),
131                                                     mEventReportWindowCalcDelegate
132                                                             .getReportingTimeForNoisingFlexEventAPI(
133                                                                     reportConfig[1],
134                                                                     reportConfig[0],
135                                                                     flexEventReportSpec),
136                                                     resolveFakeReportDestinations(
137                                                             source, reportConfig[2])))
138                             .collect(Collectors.toList());
139         }
140         @Source.AttributionMode
141         int attributionMode =
142                 fakeReports.isEmpty()
143                         ? Source.AttributionMode.NEVER
144                         : Source.AttributionMode.FALSELY;
145         source.setAttributionMode(attributionMode);
146         return fakeReports;
147     }
148 
149     /** @return Probability of selecting random state for attribution */
getRandomAttributionProbability(@onNull Source source)150     public double getRandomAttributionProbability(@NonNull Source source) {
151         if (mFlags.getMeasurementEnableConfigurableEventReportingWindows()
152                 || mFlags.getMeasurementEnableVtcConfigurableMaxEventReports()) {
153             return calculateNoiseDynamically(source);
154         }
155 
156         // Both destinations are set and install attribution is supported
157         if (!shouldReportCoarseDestinations(source)
158                 && source.hasWebDestinations()
159                 && isInstallDetectionEnabled(source)) {
160             return source.getSourceType() == Source.SourceType.EVENT
161                     ? PrivacyParams.INSTALL_ATTR_DUAL_DESTINATION_EVENT_NOISE_PROBABILITY
162                     : PrivacyParams.INSTALL_ATTR_DUAL_DESTINATION_NAVIGATION_NOISE_PROBABILITY;
163         }
164 
165         // Both destinations are set but install attribution isn't supported
166         if (!shouldReportCoarseDestinations(source)
167                 && source.hasAppDestinations()
168                 && source.hasWebDestinations()) {
169             return source.getSourceType() == Source.SourceType.EVENT
170                     ? PrivacyParams.DUAL_DESTINATION_EVENT_NOISE_PROBABILITY
171                     : PrivacyParams.DUAL_DESTINATION_NAVIGATION_NOISE_PROBABILITY;
172         }
173 
174         // App destination is set and install attribution is supported
175         if (isInstallDetectionEnabled(source)) {
176             return source.getSourceType() == Source.SourceType.EVENT
177                     ? PrivacyParams.INSTALL_ATTR_EVENT_NOISE_PROBABILITY
178                     : PrivacyParams.INSTALL_ATTR_NAVIGATION_NOISE_PROBABILITY;
179         }
180 
181         // One of the destinations is available without install attribution support
182         return source.getSourceType() == Source.SourceType.EVENT
183                 ? PrivacyParams.EVENT_NOISE_PROBABILITY
184                 : PrivacyParams.NAVIGATION_NOISE_PROBABILITY;
185     }
186 
calculateNoiseDynamically(Source source)187     private double calculateNoiseDynamically(Source source) {
188         int triggerDataCardinality = source.getTriggerDataCardinality();
189         int reportingWindowCountForNoising =
190                 mEventReportWindowCalcDelegate.getReportingWindowCountForNoising(
191                         source, isInstallDetectionEnabled(source));
192         int maxReportCount =
193                 mEventReportWindowCalcDelegate.getMaxReportCount(
194                         source, isInstallDetectionEnabled(source));
195         int destinationMultiplier = getDestinationTypeMultiplier(source);
196         int numberOfStates =
197                 Combinatorics.getNumberOfStarsAndBarsSequences(
198                         /*numStars=*/ maxReportCount,
199                         /*numBars=*/ triggerDataCardinality
200                                 * reportingWindowCountForNoising
201                                 * destinationMultiplier);
202         double absoluteProbability = Combinatorics.getFlipProbability(numberOfStates);
203         return BigDecimal.valueOf(absoluteProbability)
204                 .setScale(PROBABILITY_DECIMAL_POINTS_LIMIT, RoundingMode.HALF_UP)
205                 .doubleValue();
206     }
207 
isVtcDualDestinationModeWithPostInstallEnabled(Source source)208     private boolean isVtcDualDestinationModeWithPostInstallEnabled(Source source) {
209         return !shouldReportCoarseDestinations(source)
210                 && source.getSourceType() == Source.SourceType.EVENT
211                 && source.hasWebDestinations()
212                 && isInstallDetectionEnabled(source);
213     }
214 
215     /**
216      * Get the destination type multiplier,
217      *
218      * @return number of the destination type
219      */
getDestinationTypeMultiplier(Source source)220     private int getDestinationTypeMultiplier(Source source) {
221         return !shouldReportCoarseDestinations(source)
222                         && source.hasAppDestinations()
223                         && source.hasWebDestinations()
224                 ? DUAL_DESTINATION_IMPRESSION_NOISE_MULTIPLIER
225                 : SINGLE_DESTINATION_IMPRESSION_NOISE_MULTIPLIER;
226     }
227 
228     /**
229      * Either both app and web destinations can be available or one of them will be available. When
230      * both destinations are available, we double the number of states at noise generation to be
231      * able to randomly choose one of them for fake report creation. We don't add the multiplier
232      * when only one of them is available. In that case, choose the one that's non-null.
233      *
234      * @param destinationIdentifier destination identifier, can be 0 (app) or 1 (web)
235      * @return app or web destination {@link Uri}
236      */
resolveFakeReportDestinations(Source source, int destinationIdentifier)237     private List<Uri> resolveFakeReportDestinations(Source source, int destinationIdentifier) {
238         if (shouldReportCoarseDestinations(source)) {
239             ImmutableList.Builder<Uri> destinations = new ImmutableList.Builder<>();
240             Optional.ofNullable(source.getAppDestinations()).ifPresent(destinations::addAll);
241             Optional.ofNullable(source.getWebDestinations()).ifPresent(destinations::addAll);
242             return destinations.build();
243         }
244 
245         if (source.hasAppDestinations() && source.hasWebDestinations()) {
246             return destinationIdentifier % DUAL_DESTINATION_IMPRESSION_NOISE_MULTIPLIER == 0
247                     ? source.getAppDestinations()
248                     : source.getWebDestinations();
249         }
250 
251         return source.hasAppDestinations()
252                 ? source.getAppDestinations()
253                 : source.getWebDestinations();
254     }
255 
isInstallDetectionEnabled(@onNull Source source)256     private boolean isInstallDetectionEnabled(@NonNull Source source) {
257         return source.getInstallCooldownWindow() > 0 && source.hasAppDestinations();
258     }
259 
shouldReportCoarseDestinations(Source source)260     private boolean shouldReportCoarseDestinations(Source source) {
261         return mFlags.getMeasurementEnableCoarseEventReportDestinations()
262                 && source.getCoarseEventReportDestinations();
263     }
264 
generateVtcDualDestinationPostInstallFakeReports( Source source)265     private List<Source.FakeReport> generateVtcDualDestinationPostInstallFakeReports(
266             Source source) {
267         int[][][] fakeReportsConfig =
268                 ImpressionNoiseUtil.DUAL_DESTINATION_POST_INSTALL_FAKE_REPORT_CONFIG;
269         int randomIndex = new Random().nextInt(fakeReportsConfig.length);
270         int[][] reportsConfig = fakeReportsConfig[randomIndex];
271         return Arrays.stream(reportsConfig)
272                 .map(
273                         reportConfig ->
274                                 new Source.FakeReport(
275                                         new UnsignedLong(Long.valueOf(reportConfig[0])),
276                                         mEventReportWindowCalcDelegate.getReportingTimeForNoising(
277                                                 source,
278                                                 /* window index */ reportConfig[1],
279                                                 isInstallDetectionEnabled(source)),
280                                         resolveFakeReportDestinations(source, reportConfig[2])))
281                 .collect(Collectors.toList());
282     }
283 
284     @VisibleForTesting
getImpressionNoiseParams(Source source)285     ImpressionNoiseParams getImpressionNoiseParams(Source source) {
286         int destinationTypeMultiplier = getDestinationTypeMultiplier(source);
287         return new ImpressionNoiseParams(
288                 mEventReportWindowCalcDelegate.getMaxReportCount(
289                         source, isInstallDetectionEnabled(source)),
290                 source.getTriggerDataCardinality(),
291                 mEventReportWindowCalcDelegate.getReportingWindowCountForNoising(
292                         source, isInstallDetectionEnabled(source)),
293                 destinationTypeMultiplier);
294     }
295 }
296