diff options
Diffstat (limited to 'adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportWindowCalcDelegate.java')
-rw-r--r-- | adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportWindowCalcDelegate.java | 264 |
1 files changed, 150 insertions, 114 deletions
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportWindowCalcDelegate.java b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportWindowCalcDelegate.java index 9d7564c43..93f791385 100644 --- a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportWindowCalcDelegate.java +++ b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/EventReportWindowCalcDelegate.java @@ -54,36 +54,42 @@ public class EventReportWindowCalcDelegate { } /** - * Max reports count based on conversion destination type and installation state. + * Max reports count given the Source object. * - * @param isInstallCase is app installed + * @param source the Source object * @return maximum number of reports allowed */ - public int getMaxReportCount(@NonNull Source source, boolean isInstallCase) { + public int getMaxReportCount(Source source) { + return getMaxReportCount(source, source.isInstallDetectionEnabled()); + } + + /** + * Max reports count based on conversion destination type. + * + * @param source the Source object + * @param destinationType destination type + * @return maximum number of reports allowed + */ + public int getMaxReportCount(@NonNull Source source, @EventSurfaceType int destinationType) { + return getMaxReportCount(source, isInstallCase(source, destinationType)); + } + + private int getMaxReportCount(@NonNull Source source, boolean isInstallCase) { // TODO(b/290101531): Cleanup flags if (mFlags.getMeasurementFlexLiteApiEnabled() && source.getMaxEventLevelReports() != null) { return source.getMaxEventLevelReports(); } - if (source.getSourceType() == Source.SourceType.EVENT - && mFlags.getMeasurementEnableVtcConfigurableMaxEventReports()) { - // Max VTC event reports are configurable - int configuredMaxReports = mFlags.getMeasurementVtcConfigurableMaxEventReportsCount(); + + if (source.getSourceType() == Source.SourceType.EVENT) { // Additional report essentially for first open + 1 post install conversion. If there // is already more than 1 report allowed, no need to have that additional report. - if (isInstallCase && configuredMaxReports == PrivacyParams.EVENT_SOURCE_MAX_REPORTS) { + if (isInstallCase && !source.hasWebDestinations() && isDefaultConfiguredVtc()) { return PrivacyParams.INSTALL_ATTR_EVENT_SOURCE_MAX_REPORTS; } - return configuredMaxReports; + return mFlags.getMeasurementVtcConfigurableMaxEventReportsCount(); } - if (isInstallCase) { - return source.getSourceType() == Source.SourceType.EVENT - ? PrivacyParams.INSTALL_ATTR_EVENT_SOURCE_MAX_REPORTS - : PrivacyParams.INSTALL_ATTR_NAVIGATION_SOURCE_MAX_REPORTS; - } - return source.getSourceType() == Source.SourceType.EVENT - ? PrivacyParams.EVENT_SOURCE_MAX_REPORTS - : PrivacyParams.NAVIGATION_SOURCE_MAX_REPORTS; + return PrivacyParams.NAVIGATION_SOURCE_MAX_REPORTS; } /** @@ -100,18 +106,14 @@ public class EventReportWindowCalcDelegate { // Cases where source could have both web and app destinations, there if the trigger // destination is an app, and it was installed, then installState should be considered true. - boolean isAppInstalled = isAppInstalled(source, destinationType); - List<Pair<Long, Long>> earlyReportingWindows = - getEarlyReportingWindows(source, isAppInstalled); - for (Pair<Long, Long> window : earlyReportingWindows) { + List<Pair<Long, Long>> reportingWindows = + getEffectiveReportingWindows(source, isInstallCase(source, destinationType)); + for (Pair<Long, Long> window : reportingWindows) { if (isWithinWindow(triggerTime, window)) { return window.second + mFlags.getMeasurementMinEventReportDelayMillis(); } } - Pair<Long, Long> finalWindow = getFinalReportingWindow(source, earlyReportingWindows); - if (isWithinWindow(triggerTime, finalWindow)) { - return finalWindow.second + mFlags.getMeasurementMinEventReportDelayMillis(); - } + return -1; } @@ -120,45 +122,60 @@ public class EventReportWindowCalcDelegate { } /** + * Enum shows trigger time and source window time relationship. It is used to generate different + * verbose debug reports. + */ + public enum MomentPlacement { + BEFORE, + AFTER, + WITHIN; + } + + /** + * @param source source for which the window is calculated + * @param triggerTime time for the trigger + * @param destinationType trigger destination type + * @return how trigger time falls in source windows + */ + public MomentPlacement fallsWithinWindow( + @NonNull Source source, long triggerTime, @EventSurfaceType int destinationType) { + List<Pair<Long, Long>> reportingWindows = + getEffectiveReportingWindows(source, isInstallCase(source, destinationType)); + if (triggerTime < reportingWindows.get(0).first) { + return MomentPlacement.BEFORE; + } + if (triggerTime >= reportingWindows.get(reportingWindows.size() - 1).second) { + return MomentPlacement.AFTER; + } + return MomentPlacement.WITHIN; + } + + /** * Return reporting time by index for noising based on the index * * @param windowIndex index of the reporting window for which * @return reporting time in milliseconds */ public long getReportingTimeForNoising( - @NonNull Source source, int windowIndex, boolean isInstallCase) { - List<Pair<Long, Long>> earlyWindows = getEarlyReportingWindows(source, isInstallCase); - Pair<Long, Long> finalWindow = getFinalReportingWindow(source, earlyWindows); - return windowIndex < earlyWindows.size() - ? earlyWindows.get(windowIndex).second + @NonNull Source source, int windowIndex) { + List<Pair<Long, Long>> reportingWindows = getEffectiveReportingWindows( + source, source.isInstallDetectionEnabled()); + Pair<Long, Long> finalWindow = reportingWindows.get(reportingWindows.size() - 1); + // TODO: (b/288646239) remove this check, confirming noising indexing accuracy. + return windowIndex < reportingWindows.size() + ? reportingWindows.get(windowIndex).second + mFlags.getMeasurementMinEventReportDelayMillis() : finalWindow.second + mFlags.getMeasurementMinEventReportDelayMillis(); } - private Pair<Long, Long> getFinalReportingWindow( - Source source, List<Pair<Long, Long>> earlyWindows) { - if (mFlags.getMeasurementFlexLiteApiEnabled() && source.hasManualEventReportWindows()) { - List<Pair<Long, Long>> windowList = source.parsedProcessedEventReportWindows(); - return windowList.get(windowList.size() - 1); - } - long secondToLastWindowEnd = - !earlyWindows.isEmpty() ? earlyWindows.get(earlyWindows.size() - 1).second : 0; - if (source.getProcessedEventReportWindow() != null) { - return new Pair<>(secondToLastWindowEnd, source.getProcessedEventReportWindow()); - } - return new Pair<>(secondToLastWindowEnd, source.getExpiryTime()); - } - /** - * Returns effective, i.e. the ones that occur before {@link - * Source#getProcessedEventReportWindow()}, event reporting windows count for noising cases. + * Returns effective, that is, the ones that occur before {@link + * Source#getEffectiveEventReportWindow()}, event reporting windows count for noising cases. * * @param source source for which the count is requested - * @param isInstallCase true of cool down window was specified */ - public int getReportingWindowCountForNoising(@NonNull Source source, boolean isInstallCase) { - // Early Count + lastWindow - return getEarlyReportingWindows(source, isInstallCase).size() + 1; + public int getReportingWindowCountForNoising(@NonNull Source source) { + return getEffectiveReportingWindows(source, source.isInstallDetectionEnabled()).size(); } /** @@ -213,47 +230,53 @@ public class EventReportWindowCalcDelegate { return -1L; } - private boolean isAppInstalled(Source source, int destinationType) { + private static boolean isInstallCase(Source source, @EventSurfaceType int destinationType) { return destinationType == EventSurfaceType.APP && source.isInstallAttributed(); } /** * If the flag is enabled and the specified report windows are valid, picks from flag controlled - * configurable early reporting windows. Otherwise, falls back to the statical {@link - * com.android.adservices.service.measurement.PrivacyParams} values. It curtails the windows - * that occur after {@link Source#getProcessedEventReportWindow()} because they would - * effectively be unusable. + * configurable early reporting windows. Otherwise, falls back to the values provided in + * {@code getDefaultEarlyReportingWindowEnds}, which can have install-related custom behaviour. + * It curtails the windows that occur after {@link Source#getEffectiveEventReportWindow()} + * because they would effectively be unusable. */ - private List<Pair<Long, Long>> getEarlyReportingWindows(Source source, boolean installState) { + private List<Pair<Long, Long>> getEffectiveReportingWindows(Source source, + boolean installState) { // TODO(b/290221611) Remove early reporting windows from code, only use them for flags. if (mFlags.getMeasurementFlexLiteApiEnabled() && source.hasManualEventReportWindows()) { - List<Pair<Long, Long>> windows = source.parsedProcessedEventReportWindows(); - // Select early windows only i.e. skip the last element - return windows.subList(0, windows.size() - 1); + return source.parsedProcessedEventReportWindows(); } - List<Long> earlyWindows; - List<Long> defaultEarlyWindows = - getDefaultEarlyReportingWindows(source.getSourceType(), installState); - earlyWindows = - getConfiguredOrDefaultEarlyReportingWindows( - source.getSourceType(), defaultEarlyWindows, true); + List<Long> defaultEarlyWindowEnds = + getDefaultEarlyReportingWindowEnds( + source.getSourceType(), + installState && !source.hasWebDestinations()); + List<Long> earlyWindowEnds = + getConfiguredOrDefaultEarlyReportingWindowEnds( + source.getSourceType(), defaultEarlyWindowEnds); // Add source event time to windows - earlyWindows = - earlyWindows.stream() + earlyWindowEnds = + earlyWindowEnds.stream() .map((x) -> source.getEventTime() + x) .collect(Collectors.toList()); List<Pair<Long, Long>> windowList = new ArrayList<>(); - long windowStart = 0; + long windowStart = 0L; Pair<Long, Long> finalWindow = - getFinalReportingWindow(source, createStartEndWindow(earlyWindows)); - for (long windowEnd : earlyWindows) { - if (finalWindow.second <= windowEnd) { - continue; + getFinalReportingWindow(source, earlyWindowEnds); + + for (long windowEnd : earlyWindowEnds) { + // Start time of `finalWindow` is either 0 or one of `earlyWindowEnds` times; stop + // iterating if we see it, and add `finalWindow`. + if (windowStart == finalWindow.first) { + break; } - windowList.add(new Pair<>(windowStart, windowEnd)); + windowList.add(Pair.create(windowStart, windowEnd)); windowStart = windowEnd; } + + windowList.add(finalWindow); + return ImmutableList.copyOf(windowList); } @@ -261,13 +284,13 @@ public class EventReportWindowCalcDelegate { * Returns the default early reporting windows * * @param sourceType Source's Type - * @param installState Install State of the source + * @param installAttributionEnabled whether windows for install attribution should be provided * @return a list of windows */ - public static List<Long> getDefaultEarlyReportingWindows( - Source.SourceType sourceType, boolean installState) { + public static List<Long> getDefaultEarlyReportingWindowEnds( + Source.SourceType sourceType, boolean installAttributionEnabled) { long[] earlyWindows; - if (installState) { + if (installAttributionEnabled) { earlyWindows = sourceType == Source.SourceType.EVENT ? INSTALL_ATTR_EVENT_EARLY_REPORTING_WINDOW_MILLISECONDS @@ -281,53 +304,34 @@ public class EventReportWindowCalcDelegate { return asList(earlyWindows); } - private List<Pair<Long, Long>> createStartEndWindow(List<Long> windowEnds) { - List<Pair<Long, Long>> windows = new ArrayList<>(); - long start = 0; - for (Long end : windowEnds) { - windows.add(new Pair<>(start, end)); - start = end; - } - return windows; - } - /** * Returns default or configured (via flag) early reporting windows for the SourceType * * @param sourceType Source's Type * @param defaultEarlyWindows default value for early windows - * @param checkEnableFlag set true if configurable window flag should be checked * @return list of windows */ - public List<Long> getConfiguredOrDefaultEarlyReportingWindows( - Source.SourceType sourceType, List<Long> defaultEarlyWindows, boolean checkEnableFlag) { - // TODO(b/290101531): Cleanup flags - if (checkEnableFlag && !mFlags.getMeasurementEnableConfigurableEventReportingWindows()) { - return defaultEarlyWindows; + public List<Long> getConfiguredOrDefaultEarlyReportingWindowEnds( + Source.SourceType sourceType, List<Long> defaultEarlyWindowEnds) { + // `defaultEarlyWindowEnds` may contain custom install-related logic, which we only apply if + // the configurable report windows (and max reports) are in their default state. Without + // this check, we may construct default-value report windows without the custom + // install-related logic applied. + if ((sourceType == Source.SourceType.EVENT && isDefaultConfiguredVtc()) + || (sourceType == Source.SourceType.NAVIGATION && isDefaultConfiguredCtc())) { + return defaultEarlyWindowEnds; } String earlyReportingWindowsString = pickEarlyReportingWindowsConfig(mFlags, sourceType); - if (earlyReportingWindowsString == null) { - LoggerFactory.getMeasurementLogger() - .d("Invalid configurable early reporting windows; null"); - return defaultEarlyWindows; - } - if (earlyReportingWindowsString.isEmpty()) { // No early reporting windows specified. It needs to be handled separately because - // splitting an empty string results into an array containing a single element, - // i.e. "". We want to handle it as an array having no element. - - if (Source.SourceType.EVENT.equals(sourceType)) { - // We need to add a reporting window at 2d for post-install case. Non-install case - // has no early reporting window by default. - return defaultEarlyWindows; - } + // splitting an empty string results in an array containing a single empty string. We + // want to handle it as an empty array. return Collections.emptyList(); } - ImmutableList.Builder<Long> earlyWindows = new ImmutableList.Builder<>(); + ImmutableList.Builder<Long> earlyWindowEnds = new ImmutableList.Builder<>(); String[] split = earlyReportingWindowsString.split(EARLY_REPORTING_WINDOWS_CONFIG_DELIMITER); if (split.length > MAX_CONFIGURABLE_EVENT_REPORT_EARLY_REPORTING_WINDOWS) { @@ -335,22 +339,54 @@ public class EventReportWindowCalcDelegate { .d( "Invalid configurable early reporting window; more than allowed size: " + MAX_CONFIGURABLE_EVENT_REPORT_EARLY_REPORTING_WINDOWS); - return defaultEarlyWindows; + return defaultEarlyWindowEnds; } - for (String window : split) { + for (String windowEnd : split) { try { - earlyWindows.add(TimeUnit.SECONDS.toMillis(Long.parseLong(window))); + earlyWindowEnds.add(TimeUnit.SECONDS.toMillis(Long.parseLong(windowEnd))); } catch (NumberFormatException e) { LoggerFactory.getMeasurementLogger() .d(e, "Configurable early reporting window parsing failed."); - return defaultEarlyWindows; + return defaultEarlyWindowEnds; + } + } + return earlyWindowEnds.build(); + } + + private Pair<Long, Long> getFinalReportingWindow( + Source source, List<Long> earlyWindowEnds) { + // The latest end-time we can associate with a report for this source + long effectiveExpiry = Math.min( + source.getEffectiveEventReportWindow(), source.getExpiryTime()); + // Find the latest end-time that can start a window ending at effectiveExpiry + for (int i = earlyWindowEnds.size() - 1; i >= 0; i--) { + long windowEnd = earlyWindowEnds.get(i); + if (windowEnd < effectiveExpiry) { + return Pair.create(windowEnd, effectiveExpiry); } } - return earlyWindows.build(); + return Pair.create(0L, effectiveExpiry); + } + + /** Indicates whether VTC report windows and max reports are default configured, which can + * affect custom install-related attribution. + */ + public boolean isDefaultConfiguredVtc() { + return mFlags.getMeasurementEventReportsVtcEarlyReportingWindows().isEmpty() + && mFlags.getMeasurementVtcConfigurableMaxEventReportsCount() == 1; + } + + /** Indicates whether CTC report windows are default configured, which can affect custom + * install-related attribution. + */ + private boolean isDefaultConfiguredCtc() { + return mFlags.getMeasurementEventReportsCtcEarlyReportingWindows().equals( + Flags.MEASUREMENT_EVENT_REPORTS_CTC_EARLY_REPORTING_WINDOWS); } - private String pickEarlyReportingWindowsConfig(Flags flags, Source.SourceType sourceType) { + private static String pickEarlyReportingWindowsConfig(Flags flags, + Source.SourceType sourceType) { return sourceType == Source.SourceType.EVENT ? flags.getMeasurementEventReportsVtcEarlyReportingWindows() : flags.getMeasurementEventReportsCtcEarlyReportingWindows(); |