1 /* 2 * Copyright (C) 2022 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.util; 18 19 import android.annotation.NonNull; 20 21 import com.android.adservices.service.measurement.FilterMap; 22 23 import org.json.JSONArray; 24 import org.json.JSONException; 25 import org.json.JSONObject; 26 27 import java.util.ArrayList; 28 import java.util.HashSet; 29 import java.util.List; 30 import java.util.Set; 31 32 /** Filtering utilities for measurement. */ 33 public final class Filter { Filter()34 private Filter() { } 35 36 /** 37 * Checks whether source filter and trigger filter are matched. When a key is only present in 38 * source or trigger, ignore that key. When a key is present both in source and trigger, the key 39 * matches if the intersection of values is not empty. 40 * 41 * @param sourceFilter the {@code FilterMap} in attribution source. 42 * @param triggerFilters a list of {@code FilterMap}, the trigger filter set. 43 * @param isFilter true for filters, false for not_filters. 44 * @return return true when all keys shared by source filter and trigger filter are matched. 45 */ isFilterMatch( FilterMap sourceFilter, List<FilterMap> triggerFilters, boolean isFilter)46 public static boolean isFilterMatch( 47 FilterMap sourceFilter, List<FilterMap> triggerFilters, boolean isFilter) { 48 if (sourceFilter.getAttributionFilterMap().isEmpty() || triggerFilters.isEmpty()) { 49 return true; 50 } 51 for (FilterMap filterMap : triggerFilters) { 52 if (isFilterMatch(sourceFilter, filterMap, isFilter)) { 53 return true; 54 } 55 } 56 return false; 57 } 58 isFilterMatch( FilterMap sourceFilter, FilterMap triggerFilter, boolean isFilter)59 private static boolean isFilterMatch( 60 FilterMap sourceFilter, FilterMap triggerFilter, boolean isFilter) { 61 for (String key : triggerFilter.getAttributionFilterMap().keySet()) { 62 if (!sourceFilter.getAttributionFilterMap().containsKey(key)) { 63 continue; 64 } 65 // Finds the intersection of two value lists. 66 List<String> sourceValues = sourceFilter.getAttributionFilterMap().get(key); 67 List<String> triggerValues = triggerFilter.getAttributionFilterMap().get(key); 68 if (!matchFilterValues(sourceValues, triggerValues, isFilter)) { 69 return false; 70 } 71 } 72 return true; 73 } 74 matchFilterValues(List<String> sourceValues, List<String> triggerValues, boolean isFilter)75 private static boolean matchFilterValues(List<String> sourceValues, List<String> triggerValues, 76 boolean isFilter) { 77 if (triggerValues.isEmpty()) { 78 return isFilter ? sourceValues.isEmpty() : !sourceValues.isEmpty(); 79 } 80 Set<String> intersection = new HashSet<>(sourceValues); 81 intersection.retainAll(triggerValues); 82 return isFilter ? !intersection.isEmpty() : intersection.isEmpty(); 83 } 84 85 /** 86 * Deserializes the provided {@link JSONArray} of filters into filter set. 87 * 88 * @param filters serialized filter set 89 * @return deserialized filter set 90 * @throws JSONException if the deserialization fails 91 */ 92 @NonNull deserializeFilterSet(@onNull JSONArray filters)93 public static List<FilterMap> deserializeFilterSet(@NonNull JSONArray filters) 94 throws JSONException { 95 List<FilterMap> filterSet = new ArrayList<>(); 96 for (int i = 0; i < filters.length(); i++) { 97 FilterMap filterMap = 98 new FilterMap.Builder().buildFilterData(filters.getJSONObject(i)).build(); 99 filterSet.add(filterMap); 100 } 101 return filterSet; 102 } 103 104 /** 105 * Builds {@link JSONArray} our of the list of {@link List<FilterMap>} provided by serializing 106 * it recursively. 107 * 108 * @param filterMaps to be serialized 109 * @return serialized filter maps 110 */ 111 @NonNull serializeFilterSet(@onNull List<FilterMap> filterMaps)112 public static JSONArray serializeFilterSet(@NonNull List<FilterMap> filterMaps) { 113 JSONArray serializedFilterMaps = new JSONArray(); 114 for (FilterMap sourceNotFilter : filterMaps) { 115 serializedFilterMaps.put(sourceNotFilter.serializeAsJson()); 116 } 117 return serializedFilterMaps; 118 } 119 120 /** 121 * Filters can be available in either {@link JSONObject} format or {@link JSONArray} format. For 122 * consistency across the board, this method wraps the {@link JSONObject} into {@link 123 * JSONArray}. 124 * 125 * @param json json where to look for the filter object 126 * @param key key with which the filter object is associated 127 * @return wrapped {@link JSONArray} 128 * @throws JSONException when creation of {@link JSONArray} fails 129 */ 130 @NonNull maybeWrapFilters(@onNull JSONObject json, @NonNull String key)131 public static JSONArray maybeWrapFilters(@NonNull JSONObject json, @NonNull String key) 132 throws JSONException { 133 JSONObject maybeFilterMap = json.optJSONObject(key); 134 if (maybeFilterMap != null) { 135 JSONArray filterSet = new JSONArray(); 136 filterSet.put(maybeFilterMap); 137 return filterSet; 138 } 139 return json.getJSONArray(key); 140 } 141 } 142