• 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.reporting;
18 
19 import static com.android.adservices.service.measurement.PrivacyParams.EVENT_EARLY_REPORTING_WINDOW_MILLISECONDS;
20 import static com.android.adservices.service.measurement.PrivacyParams.INSTALL_ATTR_EVENT_EARLY_REPORTING_WINDOW_MILLISECONDS;
21 import static com.android.adservices.service.measurement.PrivacyParams.INSTALL_ATTR_NAVIGATION_EARLY_REPORTING_WINDOW_MILLISECONDS;
22 import static com.android.adservices.service.measurement.PrivacyParams.MAX_CONFIGURABLE_EVENT_REPORT_EARLY_REPORTING_WINDOWS;
23 import static com.android.adservices.service.measurement.PrivacyParams.NAVIGATION_EARLY_REPORTING_WINDOW_MILLISECONDS;
24 
25 import android.annotation.NonNull;
26 
27 import com.android.adservices.LogUtil;
28 import com.android.adservices.service.Flags;
29 import com.android.adservices.service.measurement.EventSurfaceType;
30 import com.android.adservices.service.measurement.PrivacyParams;
31 import com.android.adservices.service.measurement.ReportSpec;
32 import com.android.adservices.service.measurement.Source;
33 import com.android.adservices.service.measurement.Trigger;
34 
35 import com.google.common.collect.ImmutableList;
36 
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.List;
40 import java.util.concurrent.TimeUnit;
41 import java.util.stream.Collectors;
42 import java.util.stream.LongStream;
43 
44 /** Does event report window related calculations, e.g. count, reporting time. */
45 public class EventReportWindowCalcDelegate {
46     private static final long ONE_HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1);
47     private static final String EARLY_REPORTING_WINDOWS_CONFIG_DELIMITER = ",";
48 
49     private final Flags mFlags;
50 
EventReportWindowCalcDelegate(@onNull Flags flags)51     public EventReportWindowCalcDelegate(@NonNull Flags flags) {
52         mFlags = flags;
53     }
54 
55     /**
56      * Max reports count based on conversion destination type and installation state.
57      *
58      * @return maximum number of reports allowed
59      * @param isInstallCase is app installed
60      */
getMaxReportCount(@onNull Source source, boolean isInstallCase)61     public int getMaxReportCount(@NonNull Source source, boolean isInstallCase) {
62         if (source.getSourceType() == Source.SourceType.EVENT
63                 && mFlags.getMeasurementEnableVtcConfigurableMaxEventReports()) {
64             // Max VTC event reports are configurable
65             int configuredMaxReports = mFlags.getMeasurementVtcConfigurableMaxEventReportsCount();
66             // Additional report essentially for first open + 1 post install conversion. If there
67             // is already more than 1 report allowed, no need to have that additional report.
68             if (isInstallCase && configuredMaxReports == PrivacyParams.EVENT_SOURCE_MAX_REPORTS) {
69                 return PrivacyParams.INSTALL_ATTR_EVENT_SOURCE_MAX_REPORTS;
70             }
71             return configuredMaxReports;
72         }
73 
74         if (isInstallCase) {
75             return source.getSourceType() == Source.SourceType.EVENT
76                     ? PrivacyParams.INSTALL_ATTR_EVENT_SOURCE_MAX_REPORTS
77                     : PrivacyParams.INSTALL_ATTR_NAVIGATION_SOURCE_MAX_REPORTS;
78         }
79         return source.getSourceType() == Source.SourceType.EVENT
80                 ? PrivacyParams.EVENT_SOURCE_MAX_REPORTS
81                 : PrivacyParams.NAVIGATION_SOURCE_MAX_REPORTS;
82     }
83 
84     /**
85      * Calculates the reporting time based on the {@link Trigger} time, {@link Source}'s expiry and
86      * trigger destination type.
87      *
88      * @return the reporting time
89      */
getReportingTime( @onNull Source source, long triggerTime, @EventSurfaceType int destinationType)90     public long getReportingTime(
91             @NonNull Source source, long triggerTime, @EventSurfaceType int destinationType) {
92         if (triggerTime < source.getEventTime()) {
93             return -1;
94         }
95 
96         // Cases where source could have both web and app destinations, there if the trigger
97         // destination is an app, and it was installed, then installState should be considered true.
98         boolean isAppInstalled = isAppInstalled(source, destinationType);
99         List<Long> reportingWindows = getEarlyReportingWindows(source, isAppInstalled);
100         for (Long window : reportingWindows) {
101             if (triggerTime <= window) {
102                 return window + ONE_HOUR_IN_MILLIS;
103             }
104         }
105         return source.getEventReportWindow() + ONE_HOUR_IN_MILLIS;
106     }
107 
108     /**
109      * Return reporting time by index for noising based on the index
110      *
111      * @param windowIndex index of the reporting window for which
112      * @return reporting time in milliseconds
113      */
getReportingTimeForNoising( @onNull Source source, int windowIndex, boolean isInstallCase)114     public long getReportingTimeForNoising(
115             @NonNull Source source, int windowIndex, boolean isInstallCase) {
116         List<Long> windowList = getEarlyReportingWindows(source, isInstallCase);
117         return windowIndex < windowList.size()
118                 ? windowList.get(windowIndex) + ONE_HOUR_IN_MILLIS
119                 : source.getEventReportWindow() + ONE_HOUR_IN_MILLIS;
120     }
121 
122     /**
123      * Returns effective, i.e. the ones that occur before {@link Source#getEventReportWindow()},
124      * event reporting windows count for noising cases.
125      *
126      * @param source source for which the count is requested
127      * @param isInstallCase true of cool down window was specified
128      */
getReportingWindowCountForNoising(@onNull Source source, boolean isInstallCase)129     public int getReportingWindowCountForNoising(@NonNull Source source, boolean isInstallCase) {
130         // Early Count + expiry
131         return getEarlyReportingWindows(source, isInstallCase).size() + 1;
132     }
133 
134     /**
135      * Returns reporting time for noising with flex event API.
136      *
137      * @param windowIndex window index corresponding to which the reporting time should be returned
138      * @param triggerDataIndex trigger data state index
139      * @param reportSpec flex event report spec
140      */
getReportingTimeForNoisingFlexEventAPI( int windowIndex, int triggerDataIndex, ReportSpec reportSpec)141     public long getReportingTimeForNoisingFlexEventAPI(
142             int windowIndex, int triggerDataIndex, ReportSpec reportSpec) {
143         return reportSpec.getWindowEndTime(triggerDataIndex, windowIndex) + ONE_HOUR_IN_MILLIS;
144     }
145 
isAppInstalled(Source source, int destinationType)146     private boolean isAppInstalled(Source source, int destinationType) {
147         return destinationType == EventSurfaceType.APP && source.isInstallAttributed();
148     }
149 
150     /**
151      * If the flag is enabled and the specified report windows are valid, picks from flag controlled
152      * configurable early reporting windows. Otherwise, falls back to the statical {@link
153      * com.android.adservices.service.measurement.PrivacyParams} values. It curtails the windows
154      * that occur after {@link Source#getEventReportWindow()} because they would effectively be
155      * unusable.
156      */
getEarlyReportingWindows(Source source, boolean installState)157     private List<Long> getEarlyReportingWindows(Source source, boolean installState) {
158         List<Long> earlyWindows;
159         List<Long> defaultEarlyWindows = getDefaultEarlyReportingWindows(source, installState);
160         earlyWindows = getConfiguredOrDefaultEarlyReportingWindows(source, defaultEarlyWindows);
161 
162         List<Long> windowList = new ArrayList<>();
163         for (long windowDelta : earlyWindows) {
164             long window = source.getEventTime() + windowDelta;
165             if (source.getEventReportWindow() <= window) {
166                 continue;
167             }
168             windowList.add(window);
169         }
170         return ImmutableList.copyOf(windowList);
171     }
172 
getDefaultEarlyReportingWindows(Source source, boolean installState)173     private static List<Long> getDefaultEarlyReportingWindows(Source source, boolean installState) {
174         long[] earlyWindows;
175         if (installState) {
176             earlyWindows =
177                     source.getSourceType() == Source.SourceType.EVENT
178                             ? INSTALL_ATTR_EVENT_EARLY_REPORTING_WINDOW_MILLISECONDS
179                             : INSTALL_ATTR_NAVIGATION_EARLY_REPORTING_WINDOW_MILLISECONDS;
180         } else {
181             earlyWindows =
182                     source.getSourceType() == Source.SourceType.EVENT
183                             ? EVENT_EARLY_REPORTING_WINDOW_MILLISECONDS
184                             : NAVIGATION_EARLY_REPORTING_WINDOW_MILLISECONDS;
185         }
186         return LongStream.of(earlyWindows).boxed().collect(Collectors.toList());
187     }
188 
getConfiguredOrDefaultEarlyReportingWindows( Source source, List<Long> defaultEarlyWindows)189     private List<Long> getConfiguredOrDefaultEarlyReportingWindows(
190             Source source, List<Long> defaultEarlyWindows) {
191         if (!mFlags.getMeasurementEnableConfigurableEventReportingWindows()) {
192             return defaultEarlyWindows;
193         }
194 
195         String earlyReportingWindowsString =
196                 pickEarlyReportingWindowsConfig(mFlags, source.getSourceType());
197 
198         if (earlyReportingWindowsString == null) {
199             LogUtil.d("Invalid configurable early reporting windows; null");
200             return defaultEarlyWindows;
201         }
202 
203         if (earlyReportingWindowsString.isEmpty()) {
204             // No early reporting windows specified. It needs to be handled separately because
205             // splitting an empty string results into an array containing a single element,
206             // i.e. "". We want to handle it as an array having no element.
207 
208             if (Source.SourceType.EVENT.equals(source.getSourceType())) {
209                 // We need to add a reporting window at 2d for post-install case. Non-install case
210                 // has no early reporting window by default.
211                 return defaultEarlyWindows;
212             }
213             return Collections.emptyList();
214         }
215 
216         ImmutableList.Builder<Long> earlyWindows = new ImmutableList.Builder<>();
217         String[] split =
218                 earlyReportingWindowsString.split(EARLY_REPORTING_WINDOWS_CONFIG_DELIMITER);
219         if (split.length > MAX_CONFIGURABLE_EVENT_REPORT_EARLY_REPORTING_WINDOWS) {
220             LogUtil.d(
221                     "Invalid configurable early reporting window; more than allowed size: "
222                             + MAX_CONFIGURABLE_EVENT_REPORT_EARLY_REPORTING_WINDOWS);
223             return defaultEarlyWindows;
224         }
225 
226         for (String window : split) {
227             try {
228                 earlyWindows.add(TimeUnit.SECONDS.toMillis(Long.parseLong(window)));
229             } catch (NumberFormatException e) {
230                 LogUtil.d(e, "Configurable early reporting window parsing failed.");
231                 return defaultEarlyWindows;
232             }
233         }
234         return earlyWindows.build();
235     }
236 
pickEarlyReportingWindowsConfig(Flags flags, Source.SourceType sourceType)237     private String pickEarlyReportingWindowsConfig(Flags flags, Source.SourceType sourceType) {
238         return sourceType == Source.SourceType.EVENT
239                 ? flags.getMeasurementEventReportsVtcEarlyReportingWindows()
240                 : flags.getMeasurementEventReportsCtcEarlyReportingWindows();
241     }
242 }
243