1 /*
2  * Copyright 2020 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 static com.android.server.appsearch.external.localstorage.util.PrefixUtil.getDatabaseName;
20 import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.getPackageName;
21 import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.removePrefixesFromDocument;
22 
23 import android.annotation.NonNull;
24 import android.app.appsearch.AppSearchResult;
25 import android.app.appsearch.GenericDocument;
26 import android.app.appsearch.SearchResult;
27 import android.app.appsearch.SearchResultPage;
28 import android.app.appsearch.exceptions.AppSearchException;
29 
30 import com.android.server.appsearch.external.localstorage.AppSearchConfig;
31 import com.android.server.appsearch.external.localstorage.SchemaCache;
32 
33 import com.google.android.icing.proto.DocumentProto;
34 import com.google.android.icing.proto.SchemaTypeConfigProto;
35 import com.google.android.icing.proto.SearchResultProto;
36 import com.google.android.icing.proto.SnippetMatchProto;
37 import com.google.android.icing.proto.SnippetProto;
38 
39 import java.util.ArrayList;
40 import java.util.List;
41 import java.util.Map;
42 
43 /**
44  * Translates a {@link SearchResultProto} into {@link SearchResult}s.
45  *
46  * @hide
47  */
48 public class SearchResultToProtoConverter {
SearchResultToProtoConverter()49     private SearchResultToProtoConverter() {}
50 
51     /**
52      * Translate a {@link SearchResultProto} into {@link SearchResultPage}.
53      *
54      * @param proto The {@link SearchResultProto} containing results.
55      * @param schemaCache The SchemaCache instance held in AppSearch.
56      * @return {@link SearchResultPage} of results.
57      */
58     @NonNull
toSearchResultPage( @onNull SearchResultProto proto, @NonNull SchemaCache schemaCache, @NonNull AppSearchConfig config)59     public static SearchResultPage toSearchResultPage(
60             @NonNull SearchResultProto proto,
61             @NonNull SchemaCache schemaCache,
62             @NonNull AppSearchConfig config)
63             throws AppSearchException {
64         List<SearchResult> results = new ArrayList<>(proto.getResultsCount());
65         for (int i = 0; i < proto.getResultsCount(); i++) {
66             SearchResult result =
67                     toUnprefixedSearchResult(proto.getResults(i), schemaCache, config);
68             results.add(result);
69         }
70         return new SearchResultPage(proto.getNextPageToken(), results);
71     }
72 
73     /**
74      * Translate a {@link SearchResultProto.ResultProto} into {@link SearchResult}. The package and
75      * database prefix will be removed from {@link GenericDocument}.
76      *
77      * @param proto The proto to be converted.
78      * @param schemaCache The SchemaCache instance held in AppSearch.
79      * @return A {@link SearchResult}.
80      */
81     @NonNull
toUnprefixedSearchResult( @onNull SearchResultProto.ResultProto proto, @NonNull SchemaCache schemaCache, @NonNull AppSearchConfig config)82     private static SearchResult toUnprefixedSearchResult(
83             @NonNull SearchResultProto.ResultProto proto,
84             @NonNull SchemaCache schemaCache,
85             @NonNull AppSearchConfig config)
86             throws AppSearchException {
87 
88         DocumentProto.Builder documentBuilder = proto.getDocument().toBuilder();
89         String prefix = removePrefixesFromDocument(documentBuilder);
90         Map<String, SchemaTypeConfigProto> schemaTypeMap =
91                 schemaCache.getSchemaMapForPrefix(prefix);
92         GenericDocument document =
93                 GenericDocumentToProtoConverter.toGenericDocument(
94                         documentBuilder, prefix, schemaTypeMap, config);
95         SearchResult.Builder builder =
96                 new SearchResult.Builder(getPackageName(prefix), getDatabaseName(prefix))
97                         .setGenericDocument(document)
98                         .setRankingSignal(proto.getScore());
99         for (int i = 0; i < proto.getAdditionalScoresCount(); i++) {
100             builder.addInformationalRankingSignal(proto.getAdditionalScores(i));
101         }
102         if (proto.hasSnippet()) {
103             for (int i = 0; i < proto.getSnippet().getEntriesCount(); i++) {
104                 SnippetProto.EntryProto entry = proto.getSnippet().getEntries(i);
105                 for (int j = 0; j < entry.getSnippetMatchesCount(); j++) {
106                     SearchResult.MatchInfo matchInfo =
107                             toMatchInfo(entry.getSnippetMatches(j), entry.getPropertyName());
108                     builder.addMatchInfo(matchInfo);
109                 }
110             }
111         }
112         for (int i = 0; i < proto.getJoinedResultsCount(); i++) {
113             SearchResultProto.ResultProto joinedResultProto = proto.getJoinedResults(i);
114 
115             if (joinedResultProto.getJoinedResultsCount() != 0) {
116                 throw new AppSearchException(
117                         AppSearchResult.RESULT_INTERNAL_ERROR,
118                         "Nesting joined results within joined results not allowed.");
119             }
120 
121             builder.addJoinedResult(
122                     toUnprefixedSearchResult(joinedResultProto, schemaCache, config));
123         }
124         return builder.build();
125     }
126 
toMatchInfo( @onNull SnippetMatchProto snippetMatchProto, @NonNull String propertyPath)127     private static SearchResult.MatchInfo toMatchInfo(
128             @NonNull SnippetMatchProto snippetMatchProto, @NonNull String propertyPath) {
129         int exactMatchPosition = snippetMatchProto.getExactMatchUtf16Position();
130         return new SearchResult.MatchInfo.Builder(propertyPath)
131                 .setExactMatchRange(
132                         new SearchResult.MatchRange(
133                                 exactMatchPosition,
134                                 exactMatchPosition + snippetMatchProto.getExactMatchUtf16Length()))
135                 .setSubmatchRange(
136                         new SearchResult.MatchRange(
137                                 exactMatchPosition,
138                                 exactMatchPosition + snippetMatchProto.getSubmatchUtf16Length()))
139                 .setSnippetRange(
140                         new SearchResult.MatchRange(
141                                 snippetMatchProto.getWindowUtf16Position(),
142                                 snippetMatchProto.getWindowUtf16Position()
143                                         + snippetMatchProto.getWindowUtf16Length()))
144                 .build();
145     }
146 }
147