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.createPrefix; 20 import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.getPackageName; 21 import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.getPrefix; 22 import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.removePrefix; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.app.appsearch.JoinSpec; 27 import android.app.appsearch.SearchResult; 28 import android.app.appsearch.SearchSpec; 29 import android.app.appsearch.exceptions.AppSearchException; 30 import android.util.ArrayMap; 31 import android.util.ArraySet; 32 import android.util.Log; 33 34 import com.android.server.appsearch.external.localstorage.IcingOptionsConfig; 35 import com.android.server.appsearch.external.localstorage.visibilitystore.CallerAccess; 36 import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityChecker; 37 import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore; 38 import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityUtil; 39 40 import com.google.android.icing.proto.JoinSpecProto; 41 import com.google.android.icing.proto.PropertyWeight; 42 import com.google.android.icing.proto.ResultSpecProto; 43 import com.google.android.icing.proto.SchemaTypeConfigProto; 44 import com.google.android.icing.proto.ScoringSpecProto; 45 import com.google.android.icing.proto.SearchSpecProto; 46 import com.google.android.icing.proto.TermMatchType; 47 import com.google.android.icing.proto.TypePropertyMask; 48 import com.google.android.icing.proto.TypePropertyWeights; 49 50 import java.util.ArrayList; 51 import java.util.Iterator; 52 import java.util.List; 53 import java.util.Map; 54 import java.util.Objects; 55 import java.util.Set; 56 57 /** 58 * Translates a {@link SearchSpec} into icing search protos. 59 * 60 * @hide 61 */ 62 public final class SearchSpecToProtoConverter { 63 private static final String TAG = "AppSearchSearchSpecConv"; 64 private final String mQueryExpression; 65 private final SearchSpec mSearchSpec; 66 private final Set<String> mPrefixes; 67 /** 68 * The intersected prefixed namespaces that are existing in AppSearch and also accessible to the 69 * client. 70 */ 71 private final Set<String> mTargetPrefixedNamespaceFilters; 72 /** 73 * The intersected prefixed schema types that are existing in AppSearch and also accessible to 74 * the client. 75 */ 76 private final Set<String> mTargetPrefixedSchemaFilters; 77 78 /** 79 * The cached Map of {@code <Prefix, Set<PrefixedNamespace>>} stores all prefixed namespace 80 * filters which are stored in AppSearch. This is a field so that we can generate nested protos. 81 */ 82 private final Map<String, Set<String>> mNamespaceMap; 83 84 /** 85 * The cached Map of {@code <Prefix, Map<PrefixedSchemaType, schemaProto>>} stores all prefixed 86 * schema filters which are stored inAppSearch. This is a field so that we can generated nested 87 * protos. 88 */ 89 private final Map<String, Map<String, SchemaTypeConfigProto>> mSchemaMap; 90 91 /** Optional config flags in {@link SearchSpecProto}. */ 92 private final IcingOptionsConfig mIcingOptionsConfig; 93 94 /** 95 * The nested converter, which contains SearchSpec, ResultSpec, and ScoringSpec information 96 * about the nested query. This will remain null if there is no nested {@link JoinSpec}. 97 */ 98 @Nullable private SearchSpecToProtoConverter mNestedConverter = null; 99 100 /** 101 * Creates a {@link SearchSpecToProtoConverter} for given {@link SearchSpec}. 102 * 103 * @param queryExpression Query String to search. 104 * @param searchSpec The spec we need to convert from. 105 * @param prefixes Set of database prefix which the caller want to access. 106 * @param namespaceMap The cached Map of {@code <Prefix, Set<PrefixedNamespace>>} stores all 107 * prefixed namespace filters which are stored in AppSearch. 108 * @param schemaMap The cached Map of {@code <Prefix, Map<PrefixedSchemaType, schemaProto>>} 109 * stores all prefixed schema filters which are stored inAppSearch. 110 */ SearchSpecToProtoConverter( @onNull String queryExpression, @NonNull SearchSpec searchSpec, @NonNull Set<String> prefixes, @NonNull Map<String, Set<String>> namespaceMap, @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap, @NonNull IcingOptionsConfig icingOptionsConfig)111 public SearchSpecToProtoConverter( 112 @NonNull String queryExpression, 113 @NonNull SearchSpec searchSpec, 114 @NonNull Set<String> prefixes, 115 @NonNull Map<String, Set<String>> namespaceMap, 116 @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap, 117 @NonNull IcingOptionsConfig icingOptionsConfig) { 118 mQueryExpression = Objects.requireNonNull(queryExpression); 119 mSearchSpec = Objects.requireNonNull(searchSpec); 120 mPrefixes = Objects.requireNonNull(prefixes); 121 mNamespaceMap = Objects.requireNonNull(namespaceMap); 122 mSchemaMap = Objects.requireNonNull(schemaMap); 123 mIcingOptionsConfig = Objects.requireNonNull(icingOptionsConfig); 124 mTargetPrefixedNamespaceFilters = 125 SearchSpecToProtoConverterUtil.generateTargetNamespaceFilters( 126 prefixes, namespaceMap, searchSpec.getFilterNamespaces()); 127 // If the target namespace filter is empty, the user has nothing to search for. We can skip 128 // generate the target schema filter. 129 if (!mTargetPrefixedNamespaceFilters.isEmpty()) { 130 mTargetPrefixedSchemaFilters = 131 SearchSpecToProtoConverterUtil.generateTargetSchemaFilters( 132 prefixes, schemaMap, searchSpec.getFilterSchemas()); 133 } else { 134 mTargetPrefixedSchemaFilters = new ArraySet<>(); 135 } 136 137 JoinSpec joinSpec = searchSpec.getJoinSpec(); 138 if (joinSpec == null) { 139 return; 140 } 141 142 mNestedConverter = 143 new SearchSpecToProtoConverter( 144 joinSpec.getNestedQuery(), 145 joinSpec.getNestedSearchSpec(), 146 mPrefixes, 147 namespaceMap, 148 schemaMap, 149 mIcingOptionsConfig); 150 } 151 152 /** 153 * @return whether this search's target filters are empty. If any target filter is empty, we 154 * should skip send request to Icing. 155 * <p>The nestedConverter is not checked as {@link SearchResult}s from the nested query have 156 * to be joined to a {@link SearchResult} from the parent query. If the parent query has 157 * nothing to search, then so does the child query. 158 */ hasNothingToSearch()159 public boolean hasNothingToSearch() { 160 return mTargetPrefixedNamespaceFilters.isEmpty() || mTargetPrefixedSchemaFilters.isEmpty(); 161 } 162 163 /** 164 * For each target schema, we will check visibility store is that accessible to the caller. And 165 * remove this schemas if it is not allowed for caller to query. 166 * 167 * @param callerAccess Visibility access info of the calling app 168 * @param visibilityStore The {@link VisibilityStore} that store all visibility information. 169 * @param visibilityChecker Optional visibility checker to check whether the caller could access 170 * target schemas. Pass {@code null} will reject access for all documents which doesn't 171 * belong to the calling package. 172 */ removeInaccessibleSchemaFilter( @onNull CallerAccess callerAccess, @Nullable VisibilityStore visibilityStore, @Nullable VisibilityChecker visibilityChecker)173 public void removeInaccessibleSchemaFilter( 174 @NonNull CallerAccess callerAccess, 175 @Nullable VisibilityStore visibilityStore, 176 @Nullable VisibilityChecker visibilityChecker) { 177 removeInaccessibleSchemaFilterCached( 178 callerAccess, 179 visibilityStore, 180 /*inaccessibleSchemaPrefixes=*/ new ArraySet<>(), 181 /*accessibleSchemaPrefixes=*/ new ArraySet<>(), 182 visibilityChecker); 183 } 184 185 /** 186 * For each target schema, we will check visibility store is that accessible to the caller. And 187 * remove this schemas if it is not allowed for caller to query. This private version accepts 188 * two additional parameters to minimize the amount of calls to {@link 189 * VisibilityUtil#isSchemaSearchableByCaller}. 190 * 191 * @param callerAccess Visibility access info of the calling app 192 * @param visibilityStore The {@link VisibilityStore} that store all visibility information. 193 * @param visibilityChecker Optional visibility checker to check whether the caller could access 194 * target schemas. Pass {@code null} will reject access for all documents which doesn't 195 * belong to the calling package. 196 * @param inaccessibleSchemaPrefixes A set of schemas that are known to be inaccessible. This is 197 * helpful for reducing duplicate calls to {@link VisibilityUtil}. 198 * @param accessibleSchemaPrefixes A set of schemas that are known to be accessible. This is 199 * helpful for reducing duplicate calls to {@link VisibilityUtil}. 200 */ removeInaccessibleSchemaFilterCached( @onNull CallerAccess callerAccess, @Nullable VisibilityStore visibilityStore, @NonNull Set<String> inaccessibleSchemaPrefixes, @NonNull Set<String> accessibleSchemaPrefixes, @Nullable VisibilityChecker visibilityChecker)201 private void removeInaccessibleSchemaFilterCached( 202 @NonNull CallerAccess callerAccess, 203 @Nullable VisibilityStore visibilityStore, 204 @NonNull Set<String> inaccessibleSchemaPrefixes, 205 @NonNull Set<String> accessibleSchemaPrefixes, 206 @Nullable VisibilityChecker visibilityChecker) { 207 Iterator<String> targetPrefixedSchemaFilterIterator = 208 mTargetPrefixedSchemaFilters.iterator(); 209 while (targetPrefixedSchemaFilterIterator.hasNext()) { 210 String targetPrefixedSchemaFilter = targetPrefixedSchemaFilterIterator.next(); 211 String packageName = getPackageName(targetPrefixedSchemaFilter); 212 213 if (accessibleSchemaPrefixes.contains(targetPrefixedSchemaFilter)) { 214 continue; 215 } else if (inaccessibleSchemaPrefixes.contains(targetPrefixedSchemaFilter)) { 216 targetPrefixedSchemaFilterIterator.remove(); 217 } else if (!VisibilityUtil.isSchemaSearchableByCaller( 218 callerAccess, 219 packageName, 220 targetPrefixedSchemaFilter, 221 visibilityStore, 222 visibilityChecker)) { 223 targetPrefixedSchemaFilterIterator.remove(); 224 inaccessibleSchemaPrefixes.add(targetPrefixedSchemaFilter); 225 } else { 226 accessibleSchemaPrefixes.add(targetPrefixedSchemaFilter); 227 } 228 } 229 230 if (mNestedConverter != null) { 231 mNestedConverter.removeInaccessibleSchemaFilterCached( 232 callerAccess, 233 visibilityStore, 234 inaccessibleSchemaPrefixes, 235 accessibleSchemaPrefixes, 236 visibilityChecker); 237 } 238 } 239 240 /** Extracts {@link SearchSpecProto} information from a {@link SearchSpec}. */ 241 @NonNull toSearchSpecProto()242 public SearchSpecProto toSearchSpecProto() { 243 // set query to SearchSpecProto and override schema and namespace filter by 244 // targetPrefixedFilters which contains all existing and also accessible to the caller 245 // filters. 246 SearchSpecProto.Builder protoBuilder = 247 SearchSpecProto.newBuilder() 248 .setQuery(mQueryExpression) 249 .addAllNamespaceFilters(mTargetPrefixedNamespaceFilters) 250 .addAllSchemaTypeFilters(mTargetPrefixedSchemaFilters) 251 .setUseReadOnlySearch(mIcingOptionsConfig.getUseReadOnlySearch()); 252 253 @SearchSpec.TermMatch int termMatchCode = mSearchSpec.getTermMatch(); 254 TermMatchType.Code termMatchCodeProto = TermMatchType.Code.forNumber(termMatchCode); 255 if (termMatchCodeProto == null || termMatchCodeProto.equals(TermMatchType.Code.UNKNOWN)) { 256 throw new IllegalArgumentException("Invalid term match type: " + termMatchCode); 257 } 258 protoBuilder.setTermMatchType(termMatchCodeProto); 259 260 if (mNestedConverter != null && !mNestedConverter.hasNothingToSearch()) { 261 JoinSpecProto.NestedSpecProto nestedSpec = 262 JoinSpecProto.NestedSpecProto.newBuilder() 263 .setResultSpec( 264 mNestedConverter.toResultSpecProto(mNamespaceMap, mSchemaMap)) 265 .setScoringSpec(mNestedConverter.toScoringSpecProto()) 266 .setSearchSpec(mNestedConverter.toSearchSpecProto()) 267 .build(); 268 269 // This cannot be null, otherwise mNestedConverter would be null as well. 270 JoinSpec joinSpec = mSearchSpec.getJoinSpec(); 271 JoinSpecProto.Builder joinSpecProtoBuilder = 272 JoinSpecProto.newBuilder() 273 .setNestedSpec(nestedSpec) 274 .setParentPropertyExpression(JoinSpec.QUALIFIED_ID) 275 .setChildPropertyExpression(joinSpec.getChildPropertyExpression()) 276 .setAggregationScoringStrategy( 277 toAggregationScoringStrategy( 278 joinSpec.getAggregationScoringStrategy())); 279 280 protoBuilder.setJoinSpec(joinSpecProtoBuilder); 281 } 282 283 // TODO(b/208654892) Remove this field once EXPERIMENTAL_ICING_ADVANCED_QUERY is fully 284 // supported. 285 boolean turnOnIcingAdvancedQuery = 286 mSearchSpec.isNumericSearchEnabled() 287 || mSearchSpec.isVerbatimSearchEnabled() 288 || mSearchSpec.isListFilterQueryLanguageEnabled(); 289 if (turnOnIcingAdvancedQuery) { 290 protoBuilder.setSearchType( 291 SearchSpecProto.SearchType.Code.EXPERIMENTAL_ICING_ADVANCED_QUERY); 292 } 293 294 // Set enabled features 295 protoBuilder.addAllEnabledFeatures(mSearchSpec.getEnabledFeatures()); 296 297 return protoBuilder.build(); 298 } 299 300 /** 301 * Helper to convert to JoinSpecProto.AggregationScore. 302 * 303 * <p>{@link JoinSpec#AGGREGATION_SCORING_OUTER_RESULT_RANKING_SIGNAL} will be treated as 304 * undefined, which is the default behavior. 305 * 306 * @param aggregationScoringStrategy the scoring strategy to convert. 307 */ 308 @NonNull toAggregationScoringStrategy( @oinSpec.AggregationScoringStrategy int aggregationScoringStrategy)309 public static JoinSpecProto.AggregationScoringStrategy.Code toAggregationScoringStrategy( 310 @JoinSpec.AggregationScoringStrategy int aggregationScoringStrategy) { 311 switch (aggregationScoringStrategy) { 312 case JoinSpec.AGGREGATION_SCORING_AVG_RANKING_SIGNAL: 313 return JoinSpecProto.AggregationScoringStrategy.Code.AVG; 314 case JoinSpec.AGGREGATION_SCORING_MIN_RANKING_SIGNAL: 315 return JoinSpecProto.AggregationScoringStrategy.Code.MIN; 316 case JoinSpec.AGGREGATION_SCORING_MAX_RANKING_SIGNAL: 317 return JoinSpecProto.AggregationScoringStrategy.Code.MAX; 318 case JoinSpec.AGGREGATION_SCORING_SUM_RANKING_SIGNAL: 319 return JoinSpecProto.AggregationScoringStrategy.Code.SUM; 320 case JoinSpec.AGGREGATION_SCORING_RESULT_COUNT: 321 return JoinSpecProto.AggregationScoringStrategy.Code.COUNT; 322 default: 323 return JoinSpecProto.AggregationScoringStrategy.Code.NONE; 324 } 325 } 326 327 /** 328 * Extracts {@link ResultSpecProto} information from a {@link SearchSpec}. 329 * 330 * @param namespaceMap The cached Map of {@code <Prefix, Set<PrefixedNamespace>>} stores all 331 * existing prefixed namespace. 332 * @param schemaMap The cached Map of {@code <Prefix, Map<PrefixedSchemaType, schemaProto>>} 333 * stores all prefixed schema filters which are stored inAppSearch. 334 */ 335 @NonNull toResultSpecProto( @onNull Map<String, Set<String>> namespaceMap, @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap)336 public ResultSpecProto toResultSpecProto( 337 @NonNull Map<String, Set<String>> namespaceMap, 338 @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap) { 339 ResultSpecProto.Builder resultSpecBuilder = 340 ResultSpecProto.newBuilder() 341 .setNumPerPage(mSearchSpec.getResultCountPerPage()) 342 .setSnippetSpec( 343 ResultSpecProto.SnippetSpecProto.newBuilder() 344 .setNumToSnippet(mSearchSpec.getSnippetCount()) 345 .setNumMatchesPerProperty( 346 mSearchSpec.getSnippetCountPerProperty()) 347 .setMaxWindowUtf32Length(mSearchSpec.getMaxSnippetSize())) 348 .setNumTotalBytesPerPageThreshold( 349 mIcingOptionsConfig.getMaxPageBytesLimit()); 350 JoinSpec joinSpec = mSearchSpec.getJoinSpec(); 351 if (joinSpec != null) { 352 resultSpecBuilder.setMaxJoinedChildrenPerParentToReturn( 353 joinSpec.getMaxJoinedResultCount()); 354 } 355 356 // Rewrites the typePropertyMasks that exist in {@code prefixes}. 357 int groupingType = mSearchSpec.getResultGroupingTypeFlags(); 358 ResultSpecProto.ResultGroupingType resultGroupingType = 359 ResultSpecProto.ResultGroupingType.NONE; 360 switch (groupingType) { 361 case SearchSpec.GROUPING_TYPE_PER_PACKAGE: 362 addPerPackageResultGroupings( 363 mPrefixes, 364 mSearchSpec.getResultGroupingLimit(), 365 namespaceMap, 366 resultSpecBuilder); 367 resultGroupingType = ResultSpecProto.ResultGroupingType.NAMESPACE; 368 break; 369 case SearchSpec.GROUPING_TYPE_PER_NAMESPACE: 370 addPerNamespaceResultGroupings( 371 mPrefixes, 372 mSearchSpec.getResultGroupingLimit(), 373 namespaceMap, 374 resultSpecBuilder); 375 resultGroupingType = ResultSpecProto.ResultGroupingType.NAMESPACE; 376 break; 377 case SearchSpec.GROUPING_TYPE_PER_SCHEMA: 378 addPerSchemaResultGrouping( 379 mPrefixes, 380 mSearchSpec.getResultGroupingLimit(), 381 schemaMap, 382 resultSpecBuilder); 383 resultGroupingType = ResultSpecProto.ResultGroupingType.SCHEMA_TYPE; 384 break; 385 case SearchSpec.GROUPING_TYPE_PER_PACKAGE | SearchSpec.GROUPING_TYPE_PER_NAMESPACE: 386 addPerPackagePerNamespaceResultGroupings( 387 mPrefixes, 388 mSearchSpec.getResultGroupingLimit(), 389 namespaceMap, 390 resultSpecBuilder); 391 resultGroupingType = ResultSpecProto.ResultGroupingType.NAMESPACE; 392 break; 393 case SearchSpec.GROUPING_TYPE_PER_PACKAGE | SearchSpec.GROUPING_TYPE_PER_SCHEMA: 394 addPerPackagePerSchemaResultGroupings( 395 mPrefixes, 396 mSearchSpec.getResultGroupingLimit(), 397 schemaMap, 398 resultSpecBuilder); 399 resultGroupingType = ResultSpecProto.ResultGroupingType.SCHEMA_TYPE; 400 break; 401 case SearchSpec.GROUPING_TYPE_PER_NAMESPACE | SearchSpec.GROUPING_TYPE_PER_SCHEMA: 402 addPerNamespaceAndSchemaResultGrouping( 403 mPrefixes, 404 mSearchSpec.getResultGroupingLimit(), 405 namespaceMap, 406 schemaMap, 407 resultSpecBuilder); 408 resultGroupingType = ResultSpecProto.ResultGroupingType.NAMESPACE_AND_SCHEMA_TYPE; 409 break; 410 case SearchSpec.GROUPING_TYPE_PER_PACKAGE 411 | SearchSpec.GROUPING_TYPE_PER_NAMESPACE 412 | SearchSpec.GROUPING_TYPE_PER_SCHEMA: 413 addPerPackagePerNamespacePerSchemaResultGrouping( 414 mPrefixes, 415 mSearchSpec.getResultGroupingLimit(), 416 namespaceMap, 417 schemaMap, 418 resultSpecBuilder); 419 resultGroupingType = ResultSpecProto.ResultGroupingType.NAMESPACE_AND_SCHEMA_TYPE; 420 break; 421 default: 422 break; 423 } 424 resultSpecBuilder.setResultGroupType(resultGroupingType); 425 426 List<TypePropertyMask.Builder> typePropertyMaskBuilders = 427 TypePropertyPathToProtoConverter.toTypePropertyMaskBuilderList( 428 mSearchSpec.getProjections()); 429 // Rewrite filters to include a database prefix. 430 resultSpecBuilder.clearTypePropertyMasks(); 431 for (int i = 0; i < typePropertyMaskBuilders.size(); i++) { 432 String unprefixedType = typePropertyMaskBuilders.get(i).getSchemaType(); 433 boolean isWildcard = unprefixedType.equals(SearchSpec.PROJECTION_SCHEMA_TYPE_WILDCARD); 434 // Qualify the given schema types 435 for (String prefix : mPrefixes) { 436 String prefixedType = isWildcard ? unprefixedType : prefix + unprefixedType; 437 if (isWildcard || mTargetPrefixedSchemaFilters.contains(prefixedType)) { 438 resultSpecBuilder.addTypePropertyMasks( 439 typePropertyMaskBuilders.get(i).setSchemaType(prefixedType).build()); 440 } 441 } 442 } 443 444 return resultSpecBuilder.build(); 445 } 446 447 /** Extracts {@link ScoringSpecProto} information from a {@link SearchSpec}. */ 448 @NonNull toScoringSpecProto()449 public ScoringSpecProto toScoringSpecProto() { 450 ScoringSpecProto.Builder protoBuilder = ScoringSpecProto.newBuilder(); 451 452 @SearchSpec.Order int orderCode = mSearchSpec.getOrder(); 453 ScoringSpecProto.Order.Code orderCodeProto = 454 ScoringSpecProto.Order.Code.forNumber(orderCode); 455 if (orderCodeProto == null) { 456 throw new IllegalArgumentException("Invalid result ranking order: " + orderCode); 457 } 458 protoBuilder 459 .setOrderBy(orderCodeProto) 460 .setRankBy(toProtoRankingStrategy(mSearchSpec.getRankingStrategy())); 461 462 addTypePropertyWeights(mSearchSpec.getPropertyWeights(), protoBuilder); 463 464 protoBuilder.setAdvancedScoringExpression(mSearchSpec.getAdvancedRankingExpression()); 465 466 return protoBuilder.build(); 467 } 468 toProtoRankingStrategy( @earchSpec.RankingStrategy int rankingStrategyCode)469 private static ScoringSpecProto.RankingStrategy.Code toProtoRankingStrategy( 470 @SearchSpec.RankingStrategy int rankingStrategyCode) { 471 switch (rankingStrategyCode) { 472 case SearchSpec.RANKING_STRATEGY_NONE: 473 return ScoringSpecProto.RankingStrategy.Code.NONE; 474 case SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE: 475 return ScoringSpecProto.RankingStrategy.Code.DOCUMENT_SCORE; 476 case SearchSpec.RANKING_STRATEGY_CREATION_TIMESTAMP: 477 return ScoringSpecProto.RankingStrategy.Code.CREATION_TIMESTAMP; 478 case SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE: 479 return ScoringSpecProto.RankingStrategy.Code.RELEVANCE_SCORE; 480 case SearchSpec.RANKING_STRATEGY_USAGE_COUNT: 481 return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE1_COUNT; 482 case SearchSpec.RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP: 483 return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE1_LAST_USED_TIMESTAMP; 484 case SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_COUNT: 485 return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE2_COUNT; 486 case SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP: 487 return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE2_LAST_USED_TIMESTAMP; 488 case SearchSpec.RANKING_STRATEGY_ADVANCED_RANKING_EXPRESSION: 489 return ScoringSpecProto.RankingStrategy.Code.ADVANCED_SCORING_EXPRESSION; 490 case SearchSpec.RANKING_STRATEGY_JOIN_AGGREGATE_SCORE: 491 return ScoringSpecProto.RankingStrategy.Code.JOIN_AGGREGATE_SCORE; 492 default: 493 throw new IllegalArgumentException( 494 "Invalid result ranking strategy: " + rankingStrategyCode); 495 } 496 } 497 498 /** 499 * Returns a Map of namespace to prefixedNamespaces. This is NOT necessarily the same as the 500 * list of namespaces. If a namespace exists under different packages and/or different 501 * databases, they should still be grouped together. 502 * 503 * @param prefixes Prefixes that we should prepend to all our filters. 504 * @param namespaceMap The namespace map contains all prefixed existing namespaces. 505 */ getNamespaceToPrefixedNamespaces( @onNull Set<String> prefixes, @NonNull Map<String, Set<String>> namespaceMap)506 private static Map<String, List<String>> getNamespaceToPrefixedNamespaces( 507 @NonNull Set<String> prefixes, @NonNull Map<String, Set<String>> namespaceMap) { 508 Map<String, List<String>> namespaceToPrefixedNamespaces = new ArrayMap<>(); 509 for (String prefix : prefixes) { 510 Set<String> prefixedNamespaces = namespaceMap.get(prefix); 511 if (prefixedNamespaces == null) { 512 continue; 513 } 514 for (String prefixedNamespace : prefixedNamespaces) { 515 String namespace; 516 try { 517 namespace = removePrefix(prefixedNamespace); 518 } catch (AppSearchException e) { 519 // This should never happen. Skip this namespace if it does. 520 Log.e(TAG, "Prefixed namespace " + prefixedNamespace + " is malformed."); 521 continue; 522 } 523 List<String> groupedPrefixedNamespaces = 524 namespaceToPrefixedNamespaces.get(namespace); 525 if (groupedPrefixedNamespaces == null) { 526 groupedPrefixedNamespaces = new ArrayList<>(); 527 namespaceToPrefixedNamespaces.put(namespace, groupedPrefixedNamespaces); 528 } 529 groupedPrefixedNamespaces.add(prefixedNamespace); 530 } 531 } 532 return namespaceToPrefixedNamespaces; 533 } 534 535 /** 536 * Returns a map for package+namespace to prefixedNamespaces. This is NOT necessarily the same 537 * as the list of namespaces. If one package has multiple databases, each with the same 538 * namespace, then those should be grouped together. 539 * 540 * @param prefixes Prefixes that we should prepend to all our filters. 541 * @param namespaceMap The namespace map contains all prefixed existing namespaces. 542 */ getPackageAndNamespaceToPrefixedNamespaces( @onNull Set<String> prefixes, @NonNull Map<String, Set<String>> namespaceMap)543 private static Map<String, List<String>> getPackageAndNamespaceToPrefixedNamespaces( 544 @NonNull Set<String> prefixes, @NonNull Map<String, Set<String>> namespaceMap) { 545 Map<String, List<String>> packageAndNamespaceToNamespaces = new ArrayMap<>(); 546 for (String prefix : prefixes) { 547 Set<String> prefixedNamespaces = namespaceMap.get(prefix); 548 if (prefixedNamespaces == null) { 549 continue; 550 } 551 String packageName = getPackageName(prefix); 552 // Create a new prefix without the database name. This will allow us to group namespaces 553 // that have the same name and package but a different database name together. 554 String emptyDatabasePrefix = createPrefix(packageName, /*databaseName*/ ""); 555 for (String prefixedNamespace : prefixedNamespaces) { 556 String namespace; 557 try { 558 namespace = removePrefix(prefixedNamespace); 559 } catch (AppSearchException e) { 560 // This should never happen. Skip this namespace if it does. 561 Log.e(TAG, "Prefixed namespace " + prefixedNamespace + " is malformed."); 562 continue; 563 } 564 String emptyDatabasePrefixedNamespace = emptyDatabasePrefix + namespace; 565 List<String> namespaceList = 566 packageAndNamespaceToNamespaces.get(emptyDatabasePrefixedNamespace); 567 if (namespaceList == null) { 568 namespaceList = new ArrayList<>(); 569 packageAndNamespaceToNamespaces.put( 570 emptyDatabasePrefixedNamespace, namespaceList); 571 } 572 namespaceList.add(prefixedNamespace); 573 } 574 } 575 return packageAndNamespaceToNamespaces; 576 } 577 578 /** 579 * Returns a map of schema to prefixedSchemas. This is NOT necessarily the same as the list of 580 * schemas. If a schema exists under different packages and/or different databases, they should 581 * still be grouped together. 582 * 583 * @param prefixes Prefixes that we should prepend to all our filters. 584 * @param schemaMap The schema map contains all prefixed existing schema types. 585 */ getSchemaToPrefixedSchemas( @onNull Set<String> prefixes, @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap)586 private static Map<String, List<String>> getSchemaToPrefixedSchemas( 587 @NonNull Set<String> prefixes, 588 @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap) { 589 Map<String, List<String>> schemaToPrefixedSchemas = new ArrayMap<>(); 590 for (String prefix : prefixes) { 591 Map<String, SchemaTypeConfigProto> prefixedSchemas = schemaMap.get(prefix); 592 if (prefixedSchemas == null) { 593 continue; 594 } 595 for (String prefixedSchema : prefixedSchemas.keySet()) { 596 String schema; 597 try { 598 schema = removePrefix(prefixedSchema); 599 } catch (AppSearchException e) { 600 // This should never happen. Skip this schema if it does. 601 Log.e(TAG, "Prefixed schema " + prefixedSchema + " is malformed."); 602 continue; 603 } 604 List<String> groupedPrefixedSchemas = schemaToPrefixedSchemas.get(schema); 605 if (groupedPrefixedSchemas == null) { 606 groupedPrefixedSchemas = new ArrayList<>(); 607 schemaToPrefixedSchemas.put(schema, groupedPrefixedSchemas); 608 } 609 groupedPrefixedSchemas.add(prefixedSchema); 610 } 611 } 612 return schemaToPrefixedSchemas; 613 } 614 615 /** 616 * Returns a map for package+schema to prefixedSchemas. This is NOT necessarily the same as the 617 * list of schemas. If one package has multiple databases, each with the same schema, then those 618 * should be grouped together. 619 * 620 * @param prefixes Prefixes that we should prepend to all our filters. 621 * @param schemaMap The schema map contains all prefixed existing schema types. 622 */ getPackageAndSchemaToPrefixedSchemas( @onNull Set<String> prefixes, @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap)623 private static Map<String, List<String>> getPackageAndSchemaToPrefixedSchemas( 624 @NonNull Set<String> prefixes, 625 @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap) { 626 Map<String, List<String>> packageAndSchemaToSchemas = new ArrayMap<>(); 627 for (String prefix : prefixes) { 628 Map<String, SchemaTypeConfigProto> prefixedSchemas = schemaMap.get(prefix); 629 if (prefixedSchemas == null) { 630 continue; 631 } 632 String packageName = getPackageName(prefix); 633 // Create a new prefix without the database name. This will allow us to group schemas 634 // that have the same name and package but a different database name together. 635 String emptyDatabasePrefix = createPrefix(packageName, /*database*/ ""); 636 for (String prefixedSchema : prefixedSchemas.keySet()) { 637 String schema; 638 try { 639 schema = removePrefix(prefixedSchema); 640 } catch (AppSearchException e) { 641 // This should never happen. Skip this schema if it does. 642 Log.e(TAG, "Prefixed schema " + prefixedSchema + " is malformed."); 643 continue; 644 } 645 String emptyDatabasePrefixedSchema = emptyDatabasePrefix + schema; 646 List<String> schemaList = 647 packageAndSchemaToSchemas.get(emptyDatabasePrefixedSchema); 648 if (schemaList == null) { 649 schemaList = new ArrayList<>(); 650 packageAndSchemaToSchemas.put(emptyDatabasePrefixedSchema, schemaList); 651 } 652 schemaList.add(prefixedSchema); 653 } 654 } 655 return packageAndSchemaToSchemas; 656 } 657 658 /** 659 * Adds result groupings for each namespace in each package being queried for. 660 * 661 * @param prefixes Prefixes that we should prepend to all our filters 662 * @param maxNumResults The maximum number of results for each grouping to support. 663 * @param namespaceMap The namespace map contains all prefixed existing namespaces. 664 * @param resultSpecBuilder ResultSpecs as specified by client 665 */ addPerPackagePerNamespaceResultGroupings( @onNull Set<String> prefixes, int maxNumResults, @NonNull Map<String, Set<String>> namespaceMap, @NonNull ResultSpecProto.Builder resultSpecBuilder)666 private static void addPerPackagePerNamespaceResultGroupings( 667 @NonNull Set<String> prefixes, 668 int maxNumResults, 669 @NonNull Map<String, Set<String>> namespaceMap, 670 @NonNull ResultSpecProto.Builder resultSpecBuilder) { 671 Map<String, List<String>> packageAndNamespaceToNamespaces = 672 getPackageAndNamespaceToPrefixedNamespaces(prefixes, namespaceMap); 673 674 for (List<String> prefixedNamespaces : packageAndNamespaceToNamespaces.values()) { 675 List<ResultSpecProto.ResultGrouping.Entry> entries = 676 new ArrayList<>(prefixedNamespaces.size()); 677 for (int i = 0; i < prefixedNamespaces.size(); i++) { 678 entries.add( 679 ResultSpecProto.ResultGrouping.Entry.newBuilder() 680 .setNamespace(prefixedNamespaces.get(i)) 681 .build()); 682 } 683 resultSpecBuilder.addResultGroupings( 684 ResultSpecProto.ResultGrouping.newBuilder() 685 .addAllEntryGroupings(entries) 686 .setMaxResults(maxNumResults)); 687 } 688 } 689 690 /** 691 * Adds result groupings for each schema type in each package being queried for. 692 * 693 * @param prefixes Prefixes that we should prepend to all our filters. 694 * @param maxNumResults The maximum number of results for each grouping to support. 695 * @param schemaMap The schema map contains all prefixed existing schema types. 696 * @param resultSpecBuilder ResultSpecs as a specified by client. 697 */ addPerPackagePerSchemaResultGroupings( @onNull Set<String> prefixes, int maxNumResults, @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap, @NonNull ResultSpecProto.Builder resultSpecBuilder)698 private static void addPerPackagePerSchemaResultGroupings( 699 @NonNull Set<String> prefixes, 700 int maxNumResults, 701 @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap, 702 @NonNull ResultSpecProto.Builder resultSpecBuilder) { 703 Map<String, List<String>> packageAndSchemaToSchemas = 704 getPackageAndSchemaToPrefixedSchemas(prefixes, schemaMap); 705 706 for (List<String> prefixedSchemas : packageAndSchemaToSchemas.values()) { 707 List<ResultSpecProto.ResultGrouping.Entry> entries = 708 new ArrayList<>(prefixedSchemas.size()); 709 for (int i = 0; i < prefixedSchemas.size(); i++) { 710 entries.add( 711 ResultSpecProto.ResultGrouping.Entry.newBuilder() 712 .setSchema(prefixedSchemas.get(i)) 713 .build()); 714 } 715 resultSpecBuilder.addResultGroupings( 716 ResultSpecProto.ResultGrouping.newBuilder() 717 .addAllEntryGroupings(entries) 718 .setMaxResults(maxNumResults)); 719 } 720 } 721 722 /** 723 * Adds result groupings for each namespace and schema type being queried for. 724 * 725 * @param prefixes Prefixes that we should prepend to all our filters. 726 * @param maxNumResults The maximum number of results for each grouping to support. 727 * @param namespaceMap The namespace map contains all prefixed existing namespaces. 728 * @param schemaMap The schema map contains all prefixed existing schema types. 729 * @param resultSpecBuilder ResultSpec as specified by client. 730 */ addPerPackagePerNamespacePerSchemaResultGrouping( @onNull Set<String> prefixes, int maxNumResults, @NonNull Map<String, Set<String>> namespaceMap, @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap, @NonNull ResultSpecProto.Builder resultSpecBuilder)731 private static void addPerPackagePerNamespacePerSchemaResultGrouping( 732 @NonNull Set<String> prefixes, 733 int maxNumResults, 734 @NonNull Map<String, Set<String>> namespaceMap, 735 @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap, 736 @NonNull ResultSpecProto.Builder resultSpecBuilder) { 737 Map<String, List<String>> packageAndNamespaceToNamespaces = 738 getPackageAndNamespaceToPrefixedNamespaces(prefixes, namespaceMap); 739 Map<String, List<String>> packageAndSchemaToSchemas = 740 getPackageAndSchemaToPrefixedSchemas(prefixes, schemaMap); 741 742 for (List<String> prefixedNamespaces : packageAndNamespaceToNamespaces.values()) { 743 for (List<String> prefixedSchemas : packageAndSchemaToSchemas.values()) { 744 List<ResultSpecProto.ResultGrouping.Entry> entries = 745 new ArrayList<>(prefixedNamespaces.size() * prefixedSchemas.size()); 746 // Iterate through all namespaces. 747 for (int i = 0; i < prefixedNamespaces.size(); i++) { 748 String namespacePackage = getPackageName(prefixedNamespaces.get(i)); 749 // Iterate through all schemas. 750 for (int j = 0; j < prefixedSchemas.size(); j++) { 751 String schemaPackage = getPackageName(prefixedSchemas.get(j)); 752 if (namespacePackage.equals(schemaPackage)) { 753 entries.add( 754 ResultSpecProto.ResultGrouping.Entry.newBuilder() 755 .setNamespace(prefixedNamespaces.get(i)) 756 .setSchema(prefixedSchemas.get(j)) 757 .build()); 758 } 759 } 760 } 761 if (entries.size() > 0) { 762 resultSpecBuilder.addResultGroupings( 763 ResultSpecProto.ResultGrouping.newBuilder() 764 .addAllEntryGroupings(entries) 765 .setMaxResults(maxNumResults)); 766 } 767 } 768 } 769 } 770 771 /** 772 * Adds result groupings for each package being queried for. 773 * 774 * @param prefixes Prefixes that we should prepend to all our filters 775 * @param maxNumResults The maximum number of results for each grouping to support. 776 * @param namespaceMap The namespace map contains all prefixed existing namespaces. 777 * @param resultSpecBuilder ResultSpecs as specified by client 778 */ addPerPackageResultGroupings( @onNull Set<String> prefixes, int maxNumResults, @NonNull Map<String, Set<String>> namespaceMap, @NonNull ResultSpecProto.Builder resultSpecBuilder)779 private static void addPerPackageResultGroupings( 780 @NonNull Set<String> prefixes, 781 int maxNumResults, 782 @NonNull Map<String, Set<String>> namespaceMap, 783 @NonNull ResultSpecProto.Builder resultSpecBuilder) { 784 // Build up a map of package to namespaces. 785 Map<String, List<String>> packageToNamespacesMap = new ArrayMap<>(); 786 for (String prefix : prefixes) { 787 Set<String> prefixedNamespaces = namespaceMap.get(prefix); 788 if (prefixedNamespaces == null) { 789 continue; 790 } 791 String packageName = getPackageName(prefix); 792 List<String> packageNamespaceList = packageToNamespacesMap.get(packageName); 793 if (packageNamespaceList == null) { 794 packageNamespaceList = new ArrayList<>(); 795 packageToNamespacesMap.put(packageName, packageNamespaceList); 796 } 797 packageNamespaceList.addAll(prefixedNamespaces); 798 } 799 800 for (List<String> prefixedNamespaces : packageToNamespacesMap.values()) { 801 List<ResultSpecProto.ResultGrouping.Entry> entries = 802 new ArrayList<>(prefixedNamespaces.size()); 803 for (String namespace : prefixedNamespaces) { 804 entries.add( 805 ResultSpecProto.ResultGrouping.Entry.newBuilder() 806 .setNamespace(namespace) 807 .build()); 808 } 809 resultSpecBuilder.addResultGroupings( 810 ResultSpecProto.ResultGrouping.newBuilder() 811 .addAllEntryGroupings(entries) 812 .setMaxResults(maxNumResults)); 813 } 814 } 815 816 /** 817 * Adds result groupings for each namespace being queried for. 818 * 819 * @param prefixes Prefixes that we should prepend to all our filters 820 * @param maxNumResults The maximum number of results for each grouping to support. 821 * @param namespaceMap The namespace map contains all prefixed existing namespaces. 822 * @param resultSpecBuilder ResultSpecs as specified by client 823 */ addPerNamespaceResultGroupings( @onNull Set<String> prefixes, int maxNumResults, @NonNull Map<String, Set<String>> namespaceMap, @NonNull ResultSpecProto.Builder resultSpecBuilder)824 private static void addPerNamespaceResultGroupings( 825 @NonNull Set<String> prefixes, 826 int maxNumResults, 827 @NonNull Map<String, Set<String>> namespaceMap, 828 @NonNull ResultSpecProto.Builder resultSpecBuilder) { 829 Map<String, List<String>> namespaceToPrefixedNamespaces = 830 getNamespaceToPrefixedNamespaces(prefixes, namespaceMap); 831 832 for (List<String> prefixedNamespaces : namespaceToPrefixedNamespaces.values()) { 833 List<ResultSpecProto.ResultGrouping.Entry> entries = 834 new ArrayList<>(prefixedNamespaces.size()); 835 for (int i = 0; i < prefixedNamespaces.size(); i++) { 836 entries.add( 837 ResultSpecProto.ResultGrouping.Entry.newBuilder() 838 .setNamespace(prefixedNamespaces.get(i)) 839 .build()); 840 } 841 resultSpecBuilder.addResultGroupings( 842 ResultSpecProto.ResultGrouping.newBuilder() 843 .addAllEntryGroupings(entries) 844 .setMaxResults(maxNumResults)); 845 } 846 } 847 848 /** 849 * Adds result groupings for each schema type being queried for. 850 * 851 * @param prefixes Prefixes that we should prepend to all our filters. 852 * @param maxNumResults The maximum number of results for each grouping to support. 853 * @param schemaMap The schema map contains all prefixed existing schema types. 854 * @param resultSpecBuilder ResultSpec as specified by client. 855 */ addPerSchemaResultGrouping( @onNull Set<String> prefixes, int maxNumResults, @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap, @NonNull ResultSpecProto.Builder resultSpecBuilder)856 private static void addPerSchemaResultGrouping( 857 @NonNull Set<String> prefixes, 858 int maxNumResults, 859 @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap, 860 @NonNull ResultSpecProto.Builder resultSpecBuilder) { 861 Map<String, List<String>> schemaToPrefixedSchemas = 862 getSchemaToPrefixedSchemas(prefixes, schemaMap); 863 864 for (List<String> prefixedSchemas : schemaToPrefixedSchemas.values()) { 865 List<ResultSpecProto.ResultGrouping.Entry> entries = 866 new ArrayList<>(prefixedSchemas.size()); 867 for (int i = 0; i < prefixedSchemas.size(); i++) { 868 entries.add( 869 ResultSpecProto.ResultGrouping.Entry.newBuilder() 870 .setSchema(prefixedSchemas.get(i)) 871 .build()); 872 } 873 resultSpecBuilder.addResultGroupings( 874 ResultSpecProto.ResultGrouping.newBuilder() 875 .addAllEntryGroupings(entries) 876 .setMaxResults(maxNumResults)); 877 } 878 } 879 880 /** 881 * Adds result groupings for each namespace and schema type being queried for. 882 * 883 * @param prefixes Prefixes that we should prepend to all our filters. 884 * @param maxNumResults The maximum number of results for each grouping to support. 885 * @param namespaceMap The namespace map contains all prefixed existing namespaces. 886 * @param schemaMap The schema map contains all prefixed existing schema types. 887 * @param resultSpecBuilder ResultSpec as specified by client. 888 */ addPerNamespaceAndSchemaResultGrouping( @onNull Set<String> prefixes, int maxNumResults, @NonNull Map<String, Set<String>> namespaceMap, @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap, @NonNull ResultSpecProto.Builder resultSpecBuilder)889 private static void addPerNamespaceAndSchemaResultGrouping( 890 @NonNull Set<String> prefixes, 891 int maxNumResults, 892 @NonNull Map<String, Set<String>> namespaceMap, 893 @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap, 894 @NonNull ResultSpecProto.Builder resultSpecBuilder) { 895 Map<String, List<String>> namespaceToPrefixedNamespaces = 896 getNamespaceToPrefixedNamespaces(prefixes, namespaceMap); 897 Map<String, List<String>> schemaToPrefixedSchemas = 898 getSchemaToPrefixedSchemas(prefixes, schemaMap); 899 900 for (List<String> prefixedNamespaces : namespaceToPrefixedNamespaces.values()) { 901 for (List<String> prefixedSchemas : schemaToPrefixedSchemas.values()) { 902 List<ResultSpecProto.ResultGrouping.Entry> entries = 903 new ArrayList<>(prefixedNamespaces.size() * prefixedSchemas.size()); 904 // Iterate through all namespaces. 905 for (int i = 0; i < prefixedNamespaces.size(); i++) { 906 // Iterate through all schemas. 907 for (int j = 0; j < prefixedSchemas.size(); j++) { 908 try { 909 if (getPrefix(prefixedNamespaces.get(i)) 910 .equals(getPrefix(prefixedSchemas.get(j)))) { 911 entries.add( 912 ResultSpecProto.ResultGrouping.Entry.newBuilder() 913 .setNamespace(prefixedNamespaces.get(i)) 914 .setSchema(prefixedSchemas.get(j)) 915 .build()); 916 } 917 } catch (AppSearchException e) { 918 // This should never happen. Skip this schema if it does. 919 Log.e( 920 TAG, 921 "Prefixed string " 922 + prefixedNamespaces.get(i) 923 + " or " 924 + prefixedSchemas.get(j) 925 + " is malformed."); 926 continue; 927 } 928 } 929 } 930 if (entries.size() > 0) { 931 resultSpecBuilder.addResultGroupings( 932 ResultSpecProto.ResultGrouping.newBuilder() 933 .addAllEntryGroupings(entries) 934 .setMaxResults(maxNumResults)); 935 } 936 } 937 } 938 } 939 940 /** 941 * Adds {@link TypePropertyWeights} to {@link ScoringSpecProto}. 942 * 943 * <p>{@link TypePropertyWeights} are added to the {@link ScoringSpecProto} with database and 944 * package prefixing added to the schema type. 945 * 946 * @param typePropertyWeightsMap a map from unprefixed schema type to an inner-map of property 947 * paths to weight. 948 * @param scoringSpecBuilder scoring spec to add weights to. 949 */ addTypePropertyWeights( @onNull Map<String, Map<String, Double>> typePropertyWeightsMap, @NonNull ScoringSpecProto.Builder scoringSpecBuilder)950 private void addTypePropertyWeights( 951 @NonNull Map<String, Map<String, Double>> typePropertyWeightsMap, 952 @NonNull ScoringSpecProto.Builder scoringSpecBuilder) { 953 Objects.requireNonNull(scoringSpecBuilder); 954 Objects.requireNonNull(typePropertyWeightsMap); 955 956 for (Map.Entry<String, Map<String, Double>> typePropertyWeight : 957 typePropertyWeightsMap.entrySet()) { 958 for (String prefix : mPrefixes) { 959 String prefixedSchemaType = prefix + typePropertyWeight.getKey(); 960 if (mTargetPrefixedSchemaFilters.contains(prefixedSchemaType)) { 961 TypePropertyWeights.Builder typePropertyWeightsBuilder = 962 TypePropertyWeights.newBuilder().setSchemaType(prefixedSchemaType); 963 964 for (Map.Entry<String, Double> propertyWeight : 965 typePropertyWeight.getValue().entrySet()) { 966 typePropertyWeightsBuilder.addPropertyWeights( 967 PropertyWeight.newBuilder() 968 .setPath(propertyWeight.getKey()) 969 .setWeight(propertyWeight.getValue())); 970 } 971 972 scoringSpecBuilder.addTypePropertyWeights(typePropertyWeightsBuilder); 973 } 974 } 975 } 976 } 977 } 978