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