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; 18 19 import com.android.adservices.service.measurement.util.UnsignedLong; 20 21 import org.json.JSONArray; 22 import org.json.JSONException; 23 import org.json.JSONObject; 24 25 import java.util.ArrayList; 26 import java.util.List; 27 import java.util.Locale; 28 import java.util.Objects; 29 import java.util.stream.Collectors; 30 import java.util.stream.LongStream; 31 32 /** 33 * A class wrapper for the trigger specification from the input argument during source registration 34 */ 35 public class TriggerSpec { 36 private List<UnsignedLong> mTriggerData; 37 private Long mEventReportWindowsStart; 38 private List<Long> mEventReportWindowsEnd; 39 private SummaryOperatorType mSummaryWindowOperator; 40 private List<Long> mSummaryBuckets; 41 42 @Override equals(Object obj)43 public boolean equals(Object obj) { 44 if (!(obj instanceof TriggerSpec)) { 45 return false; 46 } 47 TriggerSpec t = (TriggerSpec) obj; 48 return Objects.equals(mTriggerData, t.mTriggerData) 49 && mEventReportWindowsStart.equals(t.mEventReportWindowsStart) 50 && mEventReportWindowsEnd.equals(t.mEventReportWindowsEnd) 51 && mSummaryWindowOperator == t.mSummaryWindowOperator 52 && mSummaryBuckets.equals(t.mSummaryBuckets); 53 } 54 55 @Override hashCode()56 public int hashCode() { 57 return Objects.hash( 58 mTriggerData, 59 mEventReportWindowsStart, 60 mEventReportWindowsEnd, 61 mSummaryWindowOperator, 62 mSummaryBuckets); 63 } 64 65 /** 66 * @return Trigger Data 67 */ getTriggerData()68 public List<UnsignedLong> getTriggerData() { 69 return mTriggerData; 70 } 71 72 /** 73 * @return Event Report Windows Start 74 */ getEventReportWindowsStart()75 public Long getEventReportWindowsStart() { 76 return mEventReportWindowsStart; 77 } 78 79 /** @return Event Report Windows End */ getEventReportWindowsEnd()80 public List<Long> getEventReportWindowsEnd() { 81 return mEventReportWindowsEnd; 82 } 83 84 /** @return Summary Window Operator */ getSummaryWindowOperator()85 public SummaryOperatorType getSummaryWindowOperator() { 86 return mSummaryWindowOperator; 87 } 88 89 /** 90 * @return Summary Bucket 91 */ getSummaryBuckets()92 public List<Long> getSummaryBuckets() { 93 return mSummaryBuckets; 94 } 95 96 /** 97 * Encode the parameter to JSON 98 * 99 * @return json object encode this class 100 */ encodeJSON()101 public JSONObject encodeJSON() throws JSONException { 102 JSONObject json = new JSONObject(); 103 json.put( 104 "trigger_data", 105 new JSONArray( 106 mTriggerData.stream() 107 .map(UnsignedLong::toString) 108 .collect(Collectors.toList()))); 109 JSONObject windows = new JSONObject(); 110 windows.put("start_time", mEventReportWindowsStart); 111 windows.put("end_times", new JSONArray(mEventReportWindowsEnd)); 112 json.put("event_report_windows", windows); 113 json.put( 114 "summary_window_operator", 115 mSummaryWindowOperator.name().toLowerCase(Locale.ENGLISH)); 116 json.put("summary_buckets", new JSONArray(mSummaryBuckets)); 117 return json; 118 } 119 120 /** Util function to check if the provided list values are in strictly increasing order. */ isStrictIncreasing(List<T> list)121 public static <T extends Comparable<T>> boolean isStrictIncreasing(List<T> list) { 122 if (list.size() < 2) { 123 return true; 124 } 125 for (int i = 1; i < list.size(); i++) { 126 if (list.get(i).compareTo(list.get(i - 1)) <= 0) { 127 return false; 128 } 129 } 130 return true; 131 } 132 133 /** The choice of the summary operator with the reporting window */ 134 public enum SummaryOperatorType { 135 COUNT, 136 VALUE_SUM 137 } 138 getLongListFromJSON(JSONObject json, String key)139 private static List<Long> getLongListFromJSON(JSONObject json, String key) 140 throws JSONException { 141 return getLongListFromJSON(json.getJSONArray(key)); 142 } 143 144 /** 145 * Parses long JSONArray into List<Long> 146 * 147 * @param jsonArray the JSON Array 148 * @return the parsed List<Long> 149 */ getLongListFromJSON(JSONArray jsonArray)150 public static List<Long> getLongListFromJSON(JSONArray jsonArray) throws JSONException { 151 List<Long> result = new ArrayList<>(); 152 for (int i = 0; i < jsonArray.length(); i++) { 153 result.add(jsonArray.getLong(i)); 154 } 155 return result; 156 } 157 getTriggerDataArrayFromJSON(JSONObject json, String key)158 private static List<UnsignedLong> getTriggerDataArrayFromJSON(JSONObject json, String key) 159 throws JSONException { 160 return getTriggerDataArrayFromJSON(json.getJSONArray(key)); 161 } 162 163 /** 164 * Parses long JSONArray into List<UnsignedLong> 165 * 166 * @param jsonArray the JSON Array 167 * @return a list of UnsignedLong 168 */ getTriggerDataArrayFromJSON(JSONArray jsonArray)169 public static List<UnsignedLong> getTriggerDataArrayFromJSON(JSONArray jsonArray) 170 throws JSONException { 171 List<UnsignedLong> result = new ArrayList<>(); 172 for (int i = 0; i < jsonArray.length(); i++) { 173 result.add(new UnsignedLong(jsonArray.getString(i))); 174 } 175 return result; 176 } 177 178 /** */ 179 public static final class Builder { 180 private final TriggerSpec mBuilding; 181 Builder(JSONObject jsonObject)182 public Builder(JSONObject jsonObject) throws JSONException, IllegalArgumentException { 183 mBuilding = new TriggerSpec(); 184 mBuilding.mSummaryWindowOperator = SummaryOperatorType.COUNT; 185 mBuilding.mEventReportWindowsStart = 0L; 186 mBuilding.mSummaryBuckets = new ArrayList<>(); 187 mBuilding.mEventReportWindowsEnd = new ArrayList<>(); 188 189 this.setTriggerData( 190 getTriggerDataArrayFromJSON( 191 jsonObject, TriggerSpecs.FlexEventReportJsonKeys.TRIGGER_DATA)); 192 if (!jsonObject.isNull(TriggerSpecs.FlexEventReportJsonKeys.EVENT_REPORT_WINDOWS)) { 193 JSONObject jsonReportWindows = 194 jsonObject.getJSONObject( 195 TriggerSpecs.FlexEventReportJsonKeys.EVENT_REPORT_WINDOWS); 196 if (!jsonReportWindows.isNull( 197 TriggerSpecs.FlexEventReportJsonKeys.START_TIME)) { 198 this.setEventReportWindowsStart( 199 jsonReportWindows.getLong( 200 TriggerSpecs.FlexEventReportJsonKeys.START_TIME)); 201 } 202 this.setEventReportWindowsEnd( 203 getLongListFromJSON( 204 jsonReportWindows, 205 TriggerSpecs.FlexEventReportJsonKeys.END_TIMES)); 206 } 207 208 if (!jsonObject.isNull( 209 TriggerSpecs.FlexEventReportJsonKeys.SUMMARY_WINDOW_OPERATOR)) { 210 this.setSummaryWindowOperator( 211 SummaryOperatorType.valueOf( 212 jsonObject 213 .getString( 214 TriggerSpecs.FlexEventReportJsonKeys 215 .SUMMARY_WINDOW_OPERATOR) 216 .toUpperCase(Locale.ROOT))); 217 } 218 if (!jsonObject.isNull(TriggerSpecs.FlexEventReportJsonKeys.SUMMARY_BUCKETS)) { 219 this.setSummaryBuckets( 220 getLongListFromJSON( 221 jsonObject, 222 TriggerSpecs.FlexEventReportJsonKeys.SUMMARY_BUCKETS)); 223 } 224 } 225 Builder( JSONObject jsonObject, long defaultStart, List<Long> defaultWindowEnds, int maxEventLevelReports)226 public Builder( 227 JSONObject jsonObject, 228 long defaultStart, 229 List<Long> defaultWindowEnds, 230 int maxEventLevelReports) throws JSONException, IllegalArgumentException { 231 mBuilding = new TriggerSpec(); 232 mBuilding.mSummaryWindowOperator = SummaryOperatorType.COUNT; 233 mBuilding.mEventReportWindowsStart = defaultStart; 234 mBuilding.mSummaryBuckets = new ArrayList<>(); 235 mBuilding.mEventReportWindowsEnd = defaultWindowEnds; 236 237 this.setTriggerData( 238 getTriggerDataArrayFromJSON( 239 jsonObject, TriggerSpecs.FlexEventReportJsonKeys.TRIGGER_DATA)); 240 if (!jsonObject.isNull(TriggerSpecs.FlexEventReportJsonKeys.EVENT_REPORT_WINDOWS)) { 241 JSONObject jsonReportWindows = 242 jsonObject.getJSONObject( 243 TriggerSpecs.FlexEventReportJsonKeys.EVENT_REPORT_WINDOWS); 244 if (!jsonReportWindows.isNull( 245 TriggerSpecs.FlexEventReportJsonKeys.START_TIME)) { 246 this.setEventReportWindowsStart( 247 jsonReportWindows.getLong( 248 TriggerSpecs.FlexEventReportJsonKeys.START_TIME)); 249 } 250 251 this.setEventReportWindowsEnd( 252 getLongListFromJSON( 253 jsonReportWindows, 254 TriggerSpecs.FlexEventReportJsonKeys.END_TIMES)); 255 } 256 257 if (!jsonObject.isNull( 258 TriggerSpecs.FlexEventReportJsonKeys.SUMMARY_WINDOW_OPERATOR)) { 259 this.setSummaryWindowOperator( 260 SummaryOperatorType.valueOf( 261 jsonObject 262 .getString( 263 TriggerSpecs.FlexEventReportJsonKeys 264 .SUMMARY_WINDOW_OPERATOR) 265 .toUpperCase(Locale.ENGLISH))); 266 } 267 if (!jsonObject.isNull(TriggerSpecs.FlexEventReportJsonKeys.SUMMARY_BUCKETS)) { 268 List<Long> summaryBuckets = 269 getLongListFromJSON( 270 jsonObject, 271 TriggerSpecs.FlexEventReportJsonKeys.SUMMARY_BUCKETS); 272 this.setSummaryBuckets(summaryBuckets.subList( 273 0, Math.min(summaryBuckets.size(), maxEventLevelReports))); 274 } else { 275 this.setSummaryBuckets( 276 LongStream.range(1, maxEventLevelReports + 1) 277 .boxed() 278 .collect(Collectors.toList())); 279 } 280 } 281 282 /** See {@link TriggerSpec#getTriggerData()} ()}. */ setTriggerData(List<UnsignedLong> triggerData)283 public Builder setTriggerData(List<UnsignedLong> triggerData) { 284 mBuilding.mTriggerData = triggerData; 285 return this; 286 } 287 288 /** See {@link TriggerSpec#getEventReportWindowsStart()} ()}. */ setEventReportWindowsStart(Long eventReportWindowsStart)289 public Builder setEventReportWindowsStart(Long eventReportWindowsStart) { 290 mBuilding.mEventReportWindowsStart = eventReportWindowsStart; 291 return this; 292 } 293 294 /** See {@link TriggerSpec#getEventReportWindowsEnd()} ()}. */ setEventReportWindowsEnd(List<Long> eventReportWindowsEnd)295 public Builder setEventReportWindowsEnd(List<Long> eventReportWindowsEnd) { 296 mBuilding.mEventReportWindowsEnd = eventReportWindowsEnd; 297 return this; 298 } 299 300 /** See {@link TriggerSpec#getSummaryWindowOperator()} ()}. */ setSummaryWindowOperator(SummaryOperatorType summaryWindowOperator)301 public Builder setSummaryWindowOperator(SummaryOperatorType summaryWindowOperator) { 302 mBuilding.mSummaryWindowOperator = summaryWindowOperator; 303 return this; 304 } 305 306 /** See {@link TriggerSpec#getSummaryBucket()} ()}. */ setSummaryBuckets(List<Long> summaryBuckets)307 public Builder setSummaryBuckets(List<Long> summaryBuckets) { 308 mBuilding.mSummaryBuckets = summaryBuckets; 309 return this; 310 } 311 312 /** Build the {@link TriggerSpec}. */ build()313 public TriggerSpec build() { 314 return mBuilding; 315 } 316 } 317 } 318