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