summaryrefslogtreecommitdiff
path: root/adservices/service-core/java/com/android/adservices/service/measurement/noising/SourceNoiseHandler.java
blob: 2c28fa51e515a2460d83138809247e25f1308620 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.adservices.service.measurement.noising;

import android.annotation.NonNull;
import android.net.Uri;

import com.android.adservices.service.Flags;
import com.android.adservices.service.measurement.Source;
import com.android.adservices.service.measurement.TriggerSpecs;
import com.android.adservices.service.measurement.reporting.EventReportWindowCalcDelegate;
import com.android.adservices.service.measurement.util.UnsignedLong;
import com.android.internal.annotations.VisibleForTesting;

import com.google.common.collect.ImmutableList;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;

/** Generates noised reports for the provided source. */
public class SourceNoiseHandler {
    private static final int PROBABILITY_DECIMAL_POINTS_LIMIT = 7;

    private final Flags mFlags;
    private final EventReportWindowCalcDelegate mEventReportWindowCalcDelegate;

    public SourceNoiseHandler(@NonNull Flags flags) {
        mFlags = flags;
        mEventReportWindowCalcDelegate = new EventReportWindowCalcDelegate(flags);
    }

    @VisibleForTesting
    SourceNoiseHandler(
            @NonNull Flags flags,
            @NonNull EventReportWindowCalcDelegate eventReportWindowCalcDelegate) {
        mFlags = flags;
        mEventReportWindowCalcDelegate = eventReportWindowCalcDelegate;
    }

    /** Multiplier is 1, when only one destination needs to be considered. */
    public static final int SINGLE_DESTINATION_IMPRESSION_NOISE_MULTIPLIER = 1;

    /**
     * Double-folds the number of states in order to allocate half to app destination and half to
     * web destination for fake reports generation.
     */
    public static final int DUAL_DESTINATION_IMPRESSION_NOISE_MULTIPLIER = 2;

    /**
     * Assign attribution mode based on random rate and generate fake reports if needed. Should only
     * be called for a new Source.
     *
     * @return fake reports to be stored in the datastore.
     */
    public List<Source.FakeReport> assignAttributionModeAndGenerateFakeReports(
            @NonNull Source source) {
        ThreadLocalRandom rand = ThreadLocalRandom.current();
        double value = rand.nextDouble();
        if (value > getRandomAttributionProbability(source)) {
            source.setAttributionMode(Source.AttributionMode.TRUTHFULLY);
            return Collections.emptyList();
        }

        List<Source.FakeReport> fakeReports;
        TriggerSpecs triggerSpecs = source.getTriggerSpecs();
        if (triggerSpecs == null) {
            if (isVtcDualDestinationModeWithPostInstallEnabled(source)) {
                // Source is 'EVENT' type, both app and web destination are set and install
                // exclusivity
                // window is provided. Pick one of the static reporting states randomly.
                fakeReports = generateVtcDualDestinationPostInstallFakeReports(source);
            } else {
                // There will at least be one (app or web) destination available
                ImpressionNoiseParams noiseParams = getImpressionNoiseParams(source);
                fakeReports =
                        ImpressionNoiseUtil.selectRandomStateAndGenerateReportConfigs(
                                        noiseParams, rand)
                                .stream()
                                .map(
                                        reportConfig ->
                                                new Source.FakeReport(
                                                        new UnsignedLong(
                                                                Long.valueOf(reportConfig[0])),
                                                        mEventReportWindowCalcDelegate
                                                                .getReportingTimeForNoising(
                                                                        source,
                                                                        reportConfig[1],
                                                                        isInstallDetectionEnabled(
                                                                                source)),
                                                        resolveFakeReportDestinations(
                                                                source, reportConfig[2])))
                                .collect(Collectors.toList());
            }
        } else {
            int destinationTypeMultiplier = source.getDestinationTypeMultiplier(mFlags);
            List<int[]> fakeReportConfigs =
                    ImpressionNoiseUtil.selectFlexEventReportRandomStateAndGenerateReportConfigs(
                            triggerSpecs, destinationTypeMultiplier, rand);
            fakeReports =
                    fakeReportConfigs.stream()
                            .map(
                                    reportConfig ->
                                            new Source.FakeReport(
                                                    triggerSpecs.getTriggerDataFromIndex(
                                                            reportConfig[0]),
                                                    mEventReportWindowCalcDelegate
                                                            .getReportingTimeForNoisingFlexEventApi(
                                                                    reportConfig[1],
                                                                    reportConfig[0],
                                                                    triggerSpecs),
                                                    resolveFakeReportDestinations(
                                                            source, reportConfig[2])))
                            .collect(Collectors.toList());
        }
        @Source.AttributionMode
        int attributionMode =
                fakeReports.isEmpty()
                        ? Source.AttributionMode.NEVER
                        : Source.AttributionMode.FALSELY;
        source.setAttributionMode(attributionMode);
        return fakeReports;
    }

    /** @return Probability of selecting random state for attribution */
    public double getRandomAttributionProbability(@NonNull Source source) {
        if (source.getTriggerSpecs() != null
                || mFlags.getMeasurementEnableConfigurableEventReportingWindows()
                || mFlags.getMeasurementEnableVtcConfigurableMaxEventReports()
                || (mFlags.getMeasurementFlexLiteApiEnabled()
                        && (source.getMaxEventLevelReports() != null
                                || source.hasManualEventReportWindows()))) {
            return convertToDoubleAndLimitDecimal(source.getFlipProbability(mFlags));
        }
        // TODO(b/290117352): Remove Hardcoded noise values

        // Both destinations are set and install attribution is supported
        if (!shouldReportCoarseDestinations(source)
                && source.hasWebDestinations()
                && isInstallDetectionEnabled(source)) {
            return source.getSourceType() == Source.SourceType.EVENT
                    ? convertToDoubleAndLimitDecimal(
                            mFlags.getMeasurementInstallAttrDualDestinationEventNoiseProbability())
                    : convertToDoubleAndLimitDecimal(
                            mFlags.getMeasurementInstallAttrDualDestinationNavigationNoiseProbability());
        }

        // Both destinations are set but install attribution isn't supported
        if (!shouldReportCoarseDestinations(source)
                && source.hasAppDestinations()
                && source.hasWebDestinations()) {
            return source.getSourceType() == Source.SourceType.EVENT
                    ? convertToDoubleAndLimitDecimal(
                            mFlags.getMeasurementDualDestinationEventNoiseProbability())
                    : convertToDoubleAndLimitDecimal(
                            mFlags.getMeasurementDualDestinationNavigationNoiseProbability());
        }

        // App destination is set and install attribution is supported
        if (isInstallDetectionEnabled(source)) {
            return source.getSourceType() == Source.SourceType.EVENT
                    ? convertToDoubleAndLimitDecimal(
                            mFlags.getMeasurementInstallAttrEventNoiseProbability())
                    : convertToDoubleAndLimitDecimal(
                            mFlags.getMeasurementInstallAttrNavigationNoiseProbability());
        }

        // One of the destinations is available without install attribution support
        return source.getSourceType() == Source.SourceType.EVENT
                ? convertToDoubleAndLimitDecimal(mFlags.getMeasurementEventNoiseProbability())
                : convertToDoubleAndLimitDecimal(mFlags.getMeasurementNavigationNoiseProbability());
    }

    private double convertToDoubleAndLimitDecimal(double probability) {
        return BigDecimal.valueOf(probability)
                .setScale(PROBABILITY_DECIMAL_POINTS_LIMIT, RoundingMode.HALF_UP)
                .doubleValue();
    }

    private boolean isVtcDualDestinationModeWithPostInstallEnabled(Source source) {
        return !shouldReportCoarseDestinations(source)
                && !source.hasManualEventReportWindows()
                && source.getMaxEventLevelReports() == null
                && source.getSourceType() == Source.SourceType.EVENT
                && source.hasWebDestinations()
                && isInstallDetectionEnabled(source);
    }

    /**
     * Either both app and web destinations can be available or one of them will be available. When
     * both destinations are available, we double the number of states at noise generation to be
     * able to randomly choose one of them for fake report creation. We don't add the multiplier
     * when only one of them is available. In that case, choose the one that's non-null.
     *
     * @param destinationIdentifier destination identifier, can be 0 (app) or 1 (web)
     * @return app or web destination {@link Uri}
     */
    private List<Uri> resolveFakeReportDestinations(Source source, int destinationIdentifier) {
        if (shouldReportCoarseDestinations(source)) {
            ImmutableList.Builder<Uri> destinations = new ImmutableList.Builder<>();
            Optional.ofNullable(source.getAppDestinations()).ifPresent(destinations::addAll);
            Optional.ofNullable(source.getWebDestinations()).ifPresent(destinations::addAll);
            return destinations.build();
        }

        if (source.hasAppDestinations() && source.hasWebDestinations()) {
            return destinationIdentifier % DUAL_DESTINATION_IMPRESSION_NOISE_MULTIPLIER == 0
                    ? source.getAppDestinations()
                    : source.getWebDestinations();
        }

        return source.hasAppDestinations()
                ? source.getAppDestinations()
                : source.getWebDestinations();
    }

    /** Check if install detection is enabled for the source. */
    public static boolean isInstallDetectionEnabled(@NonNull Source source) {
        return source.getInstallCooldownWindow() > 0 && source.hasAppDestinations();
    }

    private boolean shouldReportCoarseDestinations(Source source) {
        return mFlags.getMeasurementEnableCoarseEventReportDestinations()
                && source.hasCoarseEventReportDestinations();
    }

    private List<Source.FakeReport> generateVtcDualDestinationPostInstallFakeReports(
            Source source) {
        int[][][] fakeReportsConfig =
                ImpressionNoiseUtil.DUAL_DESTINATION_POST_INSTALL_FAKE_REPORT_CONFIG;
        int randomIndex = new Random().nextInt(fakeReportsConfig.length);
        int[][] reportsConfig = fakeReportsConfig[randomIndex];
        return Arrays.stream(reportsConfig)
                .map(
                        reportConfig ->
                                new Source.FakeReport(
                                        new UnsignedLong(Long.valueOf(reportConfig[0])),
                                        mEventReportWindowCalcDelegate.getReportingTimeForNoising(
                                                source,
                                                /* window index */ reportConfig[1],
                                                isInstallDetectionEnabled(source)),
                                        resolveFakeReportDestinations(source, reportConfig[2])))
                .collect(Collectors.toList());
    }

    @VisibleForTesting
    ImpressionNoiseParams getImpressionNoiseParams(Source source) {
        int destinationTypeMultiplier = source.getDestinationTypeMultiplier(mFlags);
        return new ImpressionNoiseParams(
                mEventReportWindowCalcDelegate.getMaxReportCount(
                        source, isInstallDetectionEnabled(source)),
                source.getTriggerDataCardinality(),
                mEventReportWindowCalcDelegate.getReportingWindowCountForNoising(
                        source, isInstallDetectionEnabled(source)),
                destinationTypeMultiplier);
    }
}