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