1 /* 2 * Copyright 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.server.appsearch.external.localstorage.converter; 18 19 import android.annotation.NonNull; 20 import android.util.ArraySet; 21 22 import com.android.server.appsearch.external.localstorage.SchemaCache; 23 24 import com.google.android.icing.proto.SchemaTypeConfigProto; 25 26 import java.util.List; 27 import java.util.Map; 28 import java.util.Set; 29 30 /** 31 * Utilities for working with {@link SearchSpecToProtoConverter} and {@link 32 * SearchSuggestionSpecToProtoConverter}. 33 * 34 * @hide 35 */ 36 public class SearchSpecToProtoConverterUtil { SearchSpecToProtoConverterUtil()37 private SearchSpecToProtoConverterUtil() {} 38 39 /** 40 * Add prefix to the given namespace filters that user want to search over and find the 41 * intersection set with those prefixed namespace candidates that are stored in AppSearch. 42 * 43 * @param prefixes Set of database prefix which the caller want to access. 44 * @param namespaceMap The cached Map of {@code <Prefix, Set<PrefixedNamespace>>} stores all 45 * prefixed namespace filters which are stored in AppSearch. 46 * @param inputNamespaceFilters The set contains all desired but un-prefixed namespace filters 47 * of user. If the inputNamespaceFilters is empty, all existing prefixedCandidates will be 48 * added to the prefixedTargetFilters. 49 */ generateTargetNamespaceFilters( @onNull Set<String> prefixes, @NonNull Map<String, Set<String>> namespaceMap, @NonNull List<String> inputNamespaceFilters)50 static Set<String> generateTargetNamespaceFilters( 51 @NonNull Set<String> prefixes, 52 @NonNull Map<String, Set<String>> namespaceMap, 53 @NonNull List<String> inputNamespaceFilters) { 54 // Convert namespace filters to prefixed namespace filters 55 Set<String> targetPrefixedNamespaceFilters = new ArraySet<>(); 56 for (String prefix : prefixes) { 57 // Step1: find all prefixed namespace candidates that are stored in AppSearch. 58 Set<String> prefixedNamespaceCandidates = namespaceMap.get(prefix); 59 if (prefixedNamespaceCandidates == null) { 60 // This is should never happen. All prefixes should be verified before reach 61 // here. 62 continue; 63 } 64 // Step2: get the intersection of user searching filters and those candidates which are 65 // stored in AppSearch. 66 addIntersectedFilters( 67 prefix, 68 prefixedNamespaceCandidates, 69 inputNamespaceFilters, 70 targetPrefixedNamespaceFilters); 71 } 72 return targetPrefixedNamespaceFilters; 73 } 74 75 /** 76 * Add prefix to the given schema filters that user want to search over and find the 77 * intersection set with those prefixed schema candidates that are stored in AppSearch. 78 * 79 * @param prefixes Set of database prefix which the caller want to access. 80 * @param schemaCache The SchemaCache instance held in AppSearch. 81 * @param inputSchemaFilters The set contains all desired but un-prefixed namespace filters of 82 * user. If the inputSchemaFilters is empty, all existing prefixedCandidates will be added 83 * to the prefixedTargetFilters. 84 */ generateTargetSchemaFilters( @onNull Set<String> prefixes, @NonNull SchemaCache schemaCache, @NonNull List<String> inputSchemaFilters)85 static Set<String> generateTargetSchemaFilters( 86 @NonNull Set<String> prefixes, 87 @NonNull SchemaCache schemaCache, 88 @NonNull List<String> inputSchemaFilters) { 89 Set<String> targetPrefixedSchemaFilters = new ArraySet<>(); 90 // Append prefix to input schema filters and get the intersection of existing schema filter. 91 for (String prefix : prefixes) { 92 // Step1: find all prefixed schema candidates that are stored in AppSearch. 93 Map<String, SchemaTypeConfigProto> prefixedSchemaMap = 94 schemaCache.getSchemaMapForPrefix(prefix); 95 Set<String> prefixedSchemaCandidates = prefixedSchemaMap.keySet(); 96 // Step2: get the intersection of user searching filters (after polymorphism 97 // expansion) and those candidates which are stored in AppSearch. 98 addIntersectedPolymorphicSchemaFilters( 99 prefix, 100 prefixedSchemaCandidates, 101 schemaCache, 102 inputSchemaFilters, 103 targetPrefixedSchemaFilters); 104 } 105 return targetPrefixedSchemaFilters; 106 } 107 108 /** 109 * Find the intersection set of candidates existing in AppSearch and user specified filters. 110 * 111 * @param prefix The package and database's identifier. 112 * @param prefixedCandidates The set contains all prefixed candidates which are existing in a 113 * database. 114 * @param inputFilters The set contains all desired but un-prefixed filters of user. If the 115 * inputFilters is empty, all prefixedCandidates will be added to the prefixedTargetFilters. 116 * @param prefixedTargetFilters The output set contains all desired prefixed filters which are 117 * existing in the database. 118 */ addIntersectedFilters( @onNull String prefix, @NonNull Set<String> prefixedCandidates, @NonNull List<String> inputFilters, @NonNull Set<String> prefixedTargetFilters)119 private static void addIntersectedFilters( 120 @NonNull String prefix, 121 @NonNull Set<String> prefixedCandidates, 122 @NonNull List<String> inputFilters, 123 @NonNull Set<String> prefixedTargetFilters) { 124 if (inputFilters.isEmpty()) { 125 // Client didn't specify certain schemas to search over, add all candidates. 126 prefixedTargetFilters.addAll(prefixedCandidates); 127 } else { 128 // Client specified some filters to search over, check and only add those are 129 // existing in the database. 130 for (int i = 0; i < inputFilters.size(); i++) { 131 String prefixedTargetFilter = prefix + inputFilters.get(i); 132 if (prefixedCandidates.contains(prefixedTargetFilter)) { 133 prefixedTargetFilters.add(prefixedTargetFilter); 134 } 135 } 136 } 137 } 138 139 /** 140 * Find the schema intersection set of candidates existing in AppSearch and user specified 141 * schema filters after polymorphism expansion. 142 * 143 * @param prefix The package and database's identifier. 144 * @param prefixedCandidates The set contains all prefixed candidates which are existing in a 145 * database. 146 * @param schemaCache The SchemaCache instance held in AppSearch. 147 * @param inputFilters The set contains all desired but un-prefixed filters of user. If the 148 * inputFilters is empty, all prefixedCandidates will be added to the prefixedTargetFilters. 149 * @param prefixedTargetFilters The output set contains all desired prefixed filters which are 150 * existing in the database. 151 */ addIntersectedPolymorphicSchemaFilters( @onNull String prefix, @NonNull Set<String> prefixedCandidates, @NonNull SchemaCache schemaCache, @NonNull List<String> inputFilters, @NonNull Set<String> prefixedTargetFilters)152 private static void addIntersectedPolymorphicSchemaFilters( 153 @NonNull String prefix, 154 @NonNull Set<String> prefixedCandidates, 155 @NonNull SchemaCache schemaCache, 156 @NonNull List<String> inputFilters, 157 @NonNull Set<String> prefixedTargetFilters) { 158 if (inputFilters.isEmpty()) { 159 // Client didn't specify certain schemas to search over, add all candidates. 160 // Polymorphism expansion is not necessary here, since expanding the set of all 161 // schema types will result in the same set of schema types. 162 prefixedTargetFilters.addAll(prefixedCandidates); 163 return; 164 } 165 166 Set<String> currentPrefixedTargetFilters = new ArraySet<>(); 167 for (int i = 0; i < inputFilters.size(); i++) { 168 String prefixedTargetSchemaFilter = prefix + inputFilters.get(i); 169 if (prefixedCandidates.contains(prefixedTargetSchemaFilter)) { 170 currentPrefixedTargetFilters.add(prefixedTargetSchemaFilter); 171 } 172 } 173 // Expand schema filters by polymorphism. 174 currentPrefixedTargetFilters = 175 schemaCache.getSchemaTypesWithDescendants(prefix, currentPrefixedTargetFilters); 176 prefixedTargetFilters.addAll(currentPrefixedTargetFilters); 177 } 178 } 179