• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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;
18 
19 import android.annotation.NonNull;
20 import android.os.Build;
21 import android.os.Bundle;
22 import android.provider.DeviceConfig;
23 import android.provider.DeviceConfig.OnPropertiesChangedListener;
24 
25 import com.android.internal.annotations.GuardedBy;
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.server.appsearch.external.localstorage.IcingOptionsConfig;
28 
29 import java.util.Objects;
30 import java.util.concurrent.Executor;
31 
32 /**
33  * Implementation of {@link AppSearchConfig} using {@link DeviceConfig}.
34  *
35  * <p>Though the latest flag values can always be retrieved by calling {@link
36  * DeviceConfig#getProperty}, we want to cache some of those values. For example, the sampling
37  * intervals for logging, they are needed for each api call and it would be a little expensive to
38  * call {@link DeviceConfig#getProperty} every time.
39  *
40  * <p>Listener is registered to DeviceConfig keep the cached value up to date.
41  *
42  * <p>This class is thread-safe.
43  *
44  * @hide
45  */
46 public final class FrameworkAppSearchConfig implements AppSearchConfig {
47     private static volatile FrameworkAppSearchConfig sConfig;
48 
49     /*
50      * Keys for ALL the flags stored in DeviceConfig.
51      */
52     public static final String KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS =
53             "min_time_interval_between_samples_millis";
54     public static final String KEY_SAMPLING_INTERVAL_DEFAULT = "sampling_interval_default";
55     public static final String KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS =
56             "sampling_interval_for_batch_call_stats";
57     public static final String KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS =
58             "sampling_interval_for_put_document_stats";
59     public static final String KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS =
60             "sampling_interval_for_initialize_stats";
61     public static final String KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS =
62             "sampling_interval_for_search_stats";
63     public static final String KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS =
64             "sampling_interval_for_global_search_stats";
65     public static final String KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS =
66             "sampling_interval_for_optimize_stats";
67     public static final String KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES =
68             "limit_config_max_document_size_bytes";
69     public static final String KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT =
70             "limit_config_max_document_count";
71     public static final String KEY_LIMIT_CONFIG_MAX_SUGGESTION_COUNT =
72             "limit_config_max_suggestion_count";
73     public static final String KEY_BYTES_OPTIMIZE_THRESHOLD = "bytes_optimize_threshold";
74     public static final String KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS = "time_optimize_threshold";
75     public static final String KEY_DOC_COUNT_OPTIMIZE_THRESHOLD = "doc_count_optimize_threshold";
76     public static final String KEY_MIN_TIME_OPTIMIZE_THRESHOLD_MILLIS =
77             "min_time_optimize_threshold";
78     public static final String KEY_API_CALL_STATS_LIMIT = "api_call_stats_limit";
79     public static final String KEY_DENYLIST = "denylist";
80     public static final String KEY_RATE_LIMIT_ENABLED = "rate_limit_enabled";
81     public static final String KEY_RATE_LIMIT_TASK_QUEUE_TOTAL_CAPACITY =
82             "rate_limit_task_queue_total_capacity";
83     public static final String KEY_RATE_LIMIT_TASK_QUEUE_PER_PACKAGE_CAPACITY_PERCENTAGE =
84             "rate_limit_task_queue_per_package_capacity_percentage";
85     public static final String KEY_RATE_LIMIT_API_COSTS = "rate_limit_api_costs";
86 
87     public static final String KEY_ICING_MAX_TOKEN_LENGTH = "icing_max_token_length";
88     public static final String KEY_ICING_INDEX_MERGE_SIZE = "icing_index_merge_size";
89     public static final String KEY_ICING_DOCUMENT_STORE_NAMESPACE_ID_FINGERPRINT =
90             "icing_document_store_namespace_id_fingerprint";
91     public static final String KEY_ICING_OPTIMIZE_REBUILD_INDEX_THRESHOLD =
92             "icing_optimize_rebuild_index_threshold";
93     public static final String KEY_ICING_COMPRESSION_LEVEL = "icing_compression_level";
94     public static final String KEY_ICING_USE_READ_ONLY_SEARCH = "icing_use_read_only_search";
95     public static final String KEY_ICING_USE_PRE_MAPPING_WITH_FILE_BACKED_VECTOR =
96             "icing_use_pre_mapping_with_file_backed_vector";
97     public static final String KEY_ICING_USE_PERSISTENT_HASHMAP = "icing_use_persistent_hashmap";
98     public static final String KEY_ICING_MAX_PAGE_BYTES_LIMIT = "icing_max_page_bytes_limit";
99 
100     /**
101      * This config does not need to be cached in FrameworkAppSearchConfig as it is only accessed
102      * statically. AppSearch retrieves this directly from DeviceConfig when needed.
103      */
104     public static final String KEY_USE_FIXED_EXECUTOR_SERVICE = "use_fixed_executor_service";
105 
106     // Array contains all the corresponding keys for the cached values.
107     private static final String[] KEYS_TO_ALL_CACHED_VALUES = {
108             KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
109             KEY_SAMPLING_INTERVAL_DEFAULT,
110             KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
111             KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
112             KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS,
113             KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS,
114             KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS,
115             KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS,
116             KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES,
117             KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT,
118             KEY_LIMIT_CONFIG_MAX_SUGGESTION_COUNT,
119             KEY_BYTES_OPTIMIZE_THRESHOLD,
120             KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS,
121             KEY_DOC_COUNT_OPTIMIZE_THRESHOLD,
122             KEY_MIN_TIME_OPTIMIZE_THRESHOLD_MILLIS,
123             KEY_API_CALL_STATS_LIMIT,
124             KEY_DENYLIST,
125             KEY_RATE_LIMIT_ENABLED,
126             KEY_RATE_LIMIT_TASK_QUEUE_TOTAL_CAPACITY,
127             KEY_RATE_LIMIT_TASK_QUEUE_PER_PACKAGE_CAPACITY_PERCENTAGE,
128             KEY_RATE_LIMIT_API_COSTS,
129             KEY_ICING_MAX_TOKEN_LENGTH,
130             KEY_ICING_INDEX_MERGE_SIZE,
131             KEY_ICING_DOCUMENT_STORE_NAMESPACE_ID_FINGERPRINT,
132             KEY_ICING_OPTIMIZE_REBUILD_INDEX_THRESHOLD,
133             KEY_ICING_COMPRESSION_LEVEL,
134             KEY_ICING_USE_READ_ONLY_SEARCH,
135             KEY_ICING_USE_PRE_MAPPING_WITH_FILE_BACKED_VECTOR,
136             KEY_ICING_USE_PERSISTENT_HASHMAP,
137             KEY_ICING_MAX_PAGE_BYTES_LIMIT
138     };
139 
140     // Lock needed for all the operations in this class.
141     private final Object mLock = new Object();
142 
143     /**
144      * Bundle to hold all the cached flag values corresponding to
145      * {@link FrameworkAppSearchConfig#KEYS_TO_ALL_CACHED_VALUES}.
146      */
147     @GuardedBy("mLock")
148     private final Bundle mBundleLocked = new Bundle();
149 
150     @GuardedBy("mLock")
151     private Denylist mDenylistLocked = Denylist.EMPTY_INSTANCE;
152 
153     @GuardedBy("mLock")
154     private AppSearchRateLimitConfig mRateLimitConfigLocked = AppSearchRateLimitConfig.create(
155             DEFAULT_RATE_LIMIT_TASK_QUEUE_TOTAL_CAPACITY,
156             DEFAULT_RATE_LIMIT_TASK_QUEUE_PER_PACKAGE_CAPACITY_PERCENTAGE,
157             DEFAULT_RATE_LIMIT_API_COSTS_STRING);
158 
159     @GuardedBy("mLock")
160     private boolean mIsClosedLocked = false;
161 
162     /** Listener to update cached flag values from DeviceConfig. */
163     private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
164             properties -> {
165                 if (!properties.getNamespace().equals(DeviceConfig.NAMESPACE_APPSEARCH)) {
166                     return;
167                 }
168 
169                 updateCachedValues(properties);
170             };
171 
FrameworkAppSearchConfig()172     private FrameworkAppSearchConfig() {
173     }
174 
175     /**
176      * Creates an instance of {@link FrameworkAppSearchConfig}.
177      *
178      * @param executor used to fetch and cache the flag values from DeviceConfig during creation or
179      *                 config change.
180      */
181     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
182     @NonNull
create(@onNull Executor executor)183     public static FrameworkAppSearchConfig create(@NonNull Executor executor) {
184         Objects.requireNonNull(executor);
185         FrameworkAppSearchConfig configManager = new FrameworkAppSearchConfig();
186         configManager.initialize(executor);
187         return configManager;
188     }
189 
190     /**
191      * Gets an instance of {@link FrameworkAppSearchConfig} to be used.
192      *
193      * <p>If no instance has been initialized yet, a new one will be created. Otherwise, the
194      * existing instance will be returned.
195      */
196     @NonNull
getInstance(@onNull Executor executor)197     public static FrameworkAppSearchConfig getInstance(@NonNull Executor executor) {
198         Objects.requireNonNull(executor);
199         if (sConfig == null) {
200             synchronized (FrameworkAppSearchConfig.class) {
201                 if (sConfig == null) {
202                     sConfig = create(executor);
203                 }
204             }
205         }
206         return sConfig;
207     }
208 
209     /**
210      * Returns whether or not to use a fixed executor service for AppSearch. This config is only
211      * queried statically and is therefore retrieved directly from DeviceConfig.
212      */
getUseFixedExecutorService()213     public static boolean getUseFixedExecutorService() {
214         return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_APPSEARCH,
215                 KEY_USE_FIXED_EXECUTOR_SERVICE, DEFAULT_USE_FIXED_EXECUTOR_SERVICE);
216     }
217 
218     /**
219      * Initializes the {@link FrameworkAppSearchConfig}
220      *
221      * <p>It fetches the custom properties from DeviceConfig if available.
222      *
223      * @param executor listener would be run on to handle P/H flag change.
224      */
initialize(@onNull Executor executor)225     private void initialize(@NonNull Executor executor) {
226         executor.execute(() -> {
227             // Attach the callback to get updates on those properties.
228             DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APPSEARCH,
229                     executor,
230                     mOnDeviceConfigChangedListener);
231 
232             DeviceConfig.Properties properties = DeviceConfig.getProperties(
233                     DeviceConfig.NAMESPACE_APPSEARCH, KEYS_TO_ALL_CACHED_VALUES);
234             updateCachedValues(properties);
235         });
236     }
237 
238     // TODO(b/173532925) check this will be called. If we have a singleton instance for this
239     //  class, probably we don't need it.
240     @Override
close()241     public void close() {
242         synchronized (mLock) {
243             if (mIsClosedLocked) {
244                 return;
245             }
246 
247             DeviceConfig.removeOnPropertiesChangedListener(mOnDeviceConfigChangedListener);
248             mIsClosedLocked = true;
249         }
250     }
251 
252     @Override
getCachedMinTimeIntervalBetweenSamplesMillis()253     public long getCachedMinTimeIntervalBetweenSamplesMillis() {
254         synchronized (mLock) {
255             throwIfClosedLocked();
256             return mBundleLocked.getLong(KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
257                     DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS);
258         }
259     }
260 
261     @Override
getCachedSamplingIntervalDefault()262     public int getCachedSamplingIntervalDefault() {
263         synchronized (mLock) {
264             throwIfClosedLocked();
265             return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_DEFAULT, DEFAULT_SAMPLING_INTERVAL);
266         }
267     }
268 
269     @Override
getCachedSamplingIntervalForBatchCallStats()270     public int getCachedSamplingIntervalForBatchCallStats() {
271         synchronized (mLock) {
272             throwIfClosedLocked();
273             return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
274                     getCachedSamplingIntervalDefault());
275         }
276     }
277 
278     @Override
getCachedSamplingIntervalForPutDocumentStats()279     public int getCachedSamplingIntervalForPutDocumentStats() {
280         synchronized (mLock) {
281             throwIfClosedLocked();
282             return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
283                     getCachedSamplingIntervalDefault());
284         }
285     }
286 
287     @Override
getCachedSamplingIntervalForInitializeStats()288     public int getCachedSamplingIntervalForInitializeStats() {
289         synchronized (mLock) {
290             throwIfClosedLocked();
291             return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS,
292                     getCachedSamplingIntervalDefault());
293         }
294     }
295 
296     @Override
getCachedSamplingIntervalForSearchStats()297     public int getCachedSamplingIntervalForSearchStats() {
298         synchronized (mLock) {
299             throwIfClosedLocked();
300             return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS,
301                     getCachedSamplingIntervalDefault());
302         }
303     }
304 
305     @Override
getCachedSamplingIntervalForGlobalSearchStats()306     public int getCachedSamplingIntervalForGlobalSearchStats() {
307         synchronized (mLock) {
308             throwIfClosedLocked();
309             return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS,
310                     getCachedSamplingIntervalDefault());
311         }
312     }
313 
314     @Override
getCachedSamplingIntervalForOptimizeStats()315     public int getCachedSamplingIntervalForOptimizeStats() {
316         synchronized (mLock) {
317             throwIfClosedLocked();
318             return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS,
319                     getCachedSamplingIntervalDefault());
320         }
321     }
322 
323     @Override
getMaxDocumentSizeBytes()324     public int getMaxDocumentSizeBytes() {
325         synchronized (mLock) {
326             throwIfClosedLocked();
327             return mBundleLocked.getInt(KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES,
328                     DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES);
329         }
330     }
331 
332     @Override
getMaxDocumentCount()333     public int getMaxDocumentCount() {
334         synchronized (mLock) {
335             throwIfClosedLocked();
336             return mBundleLocked.getInt(KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT,
337                     DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT);
338         }
339     }
340 
341     @Override
getMaxSuggestionCount()342     public int getMaxSuggestionCount() {
343         synchronized (mLock) {
344             throwIfClosedLocked();
345             return mBundleLocked.getInt(KEY_LIMIT_CONFIG_MAX_SUGGESTION_COUNT,
346                     DEFAULT_LIMIT_CONFIG_MAX_SUGGESTION_COUNT);
347         }
348     }
349 
350     @Override
getCachedBytesOptimizeThreshold()351     public int getCachedBytesOptimizeThreshold() {
352         synchronized (mLock) {
353             throwIfClosedLocked();
354             return mBundleLocked.getInt(KEY_BYTES_OPTIMIZE_THRESHOLD,
355                     DEFAULT_BYTES_OPTIMIZE_THRESHOLD);
356         }
357     }
358 
359     @Override
getCachedTimeOptimizeThresholdMs()360     public int getCachedTimeOptimizeThresholdMs() {
361         synchronized (mLock) {
362             throwIfClosedLocked();
363             return mBundleLocked.getInt(KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS,
364                     DEFAULT_TIME_OPTIMIZE_THRESHOLD_MILLIS);
365         }
366     }
367 
368     @Override
getCachedDocCountOptimizeThreshold()369     public int getCachedDocCountOptimizeThreshold() {
370         synchronized (mLock) {
371             throwIfClosedLocked();
372             return mBundleLocked.getInt(KEY_DOC_COUNT_OPTIMIZE_THRESHOLD,
373                     DEFAULT_DOC_COUNT_OPTIMIZE_THRESHOLD);
374         }
375     }
376 
377     @Override
getCachedMinTimeOptimizeThresholdMs()378     public int getCachedMinTimeOptimizeThresholdMs() {
379         synchronized (mLock) {
380             throwIfClosedLocked();
381             return mBundleLocked.getInt(KEY_MIN_TIME_OPTIMIZE_THRESHOLD_MILLIS,
382                     DEFAULT_MIN_TIME_OPTIMIZE_THRESHOLD_MILLIS);
383         }
384     }
385 
386     @Override
getCachedApiCallStatsLimit()387     public int getCachedApiCallStatsLimit() {
388         synchronized (mLock) {
389             throwIfClosedLocked();
390             return mBundleLocked.getInt(KEY_API_CALL_STATS_LIMIT,
391                     DEFAULT_API_CALL_STATS_LIMIT);
392         }
393     }
394 
395     @Override
getCachedDenylist()396     public Denylist getCachedDenylist() {
397         synchronized (mLock) {
398             throwIfClosedLocked();
399             return mDenylistLocked;
400         }
401     }
402 
403     @Override
getMaxTokenLength()404     public int getMaxTokenLength() {
405         synchronized (mLock) {
406             throwIfClosedLocked();
407             return mBundleLocked.getInt(KEY_ICING_MAX_TOKEN_LENGTH,
408                     IcingOptionsConfig.DEFAULT_MAX_TOKEN_LENGTH);
409         }
410     }
411 
412     @Override
getIndexMergeSize()413     public int getIndexMergeSize() {
414         synchronized (mLock) {
415             throwIfClosedLocked();
416             return mBundleLocked.getInt(KEY_ICING_INDEX_MERGE_SIZE,
417                     IcingOptionsConfig.DEFAULT_INDEX_MERGE_SIZE);
418         }
419     }
420 
421     @Override
getDocumentStoreNamespaceIdFingerprint()422     public boolean getDocumentStoreNamespaceIdFingerprint() {
423         synchronized (mLock) {
424             throwIfClosedLocked();
425             return mBundleLocked.getBoolean(KEY_ICING_DOCUMENT_STORE_NAMESPACE_ID_FINGERPRINT,
426                     IcingOptionsConfig.DEFAULT_DOCUMENT_STORE_NAMESPACE_ID_FINGERPRINT);
427         }
428     }
429 
430     @Override
getOptimizeRebuildIndexThreshold()431     public float getOptimizeRebuildIndexThreshold() {
432         synchronized (mLock) {
433             throwIfClosedLocked();
434             return mBundleLocked.getFloat(KEY_ICING_OPTIMIZE_REBUILD_INDEX_THRESHOLD,
435                     IcingOptionsConfig.DEFAULT_OPTIMIZE_REBUILD_INDEX_THRESHOLD);
436         }
437     }
438 
439     @Override
getCompressionLevel()440     public int getCompressionLevel() {
441         synchronized (mLock) {
442             throwIfClosedLocked();
443             return mBundleLocked.getInt(KEY_ICING_COMPRESSION_LEVEL,
444                     IcingOptionsConfig.DEFAULT_COMPRESSION_LEVEL);
445         }
446     }
447 
448     @Override
getAllowCircularSchemaDefinitions()449     public boolean getAllowCircularSchemaDefinitions() {
450         // TODO(b/282108040) add flag(default on) to cover this feature in case a bug is discovered.
451         synchronized (mLock) {
452             throwIfClosedLocked();
453             return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
454         }
455     }
456 
457     @Override
getUseReadOnlySearch()458     public boolean getUseReadOnlySearch() {
459         synchronized (mLock) {
460             throwIfClosedLocked();
461             return mBundleLocked.getBoolean(KEY_ICING_USE_READ_ONLY_SEARCH,
462                     DEFAULT_ICING_CONFIG_USE_READ_ONLY_SEARCH);
463         }
464     }
465 
466     @Override
getUsePreMappingWithFileBackedVector()467     public boolean getUsePreMappingWithFileBackedVector() {
468         synchronized (mLock) {
469             throwIfClosedLocked();
470             return mBundleLocked.getBoolean(KEY_ICING_USE_PRE_MAPPING_WITH_FILE_BACKED_VECTOR,
471                     IcingOptionsConfig.DEFAULT_USE_PREMAPPING_WITH_FILE_BACKED_VECTOR);
472         }
473     }
474 
475     @Override
getUsePersistentHashMap()476     public boolean getUsePersistentHashMap() {
477         synchronized (mLock) {
478             throwIfClosedLocked();
479             return mBundleLocked.getBoolean(KEY_ICING_USE_PERSISTENT_HASHMAP,
480                     IcingOptionsConfig.DEFAULT_USE_PERSISTENT_HASH_MAP);
481         }
482     }
483 
484     @Override
getMaxPageBytesLimit()485     public int getMaxPageBytesLimit() {
486         synchronized (mLock) {
487             throwIfClosedLocked();
488             return mBundleLocked.getInt(KEY_ICING_MAX_PAGE_BYTES_LIMIT,
489                     IcingOptionsConfig.DEFAULT_MAX_PAGE_BYTES_LIMIT);
490         }
491     }
492 
493     @Override
getCachedRateLimitEnabled()494     public boolean getCachedRateLimitEnabled() {
495         synchronized (mLock) {
496             throwIfClosedLocked();
497             return mBundleLocked.getBoolean(KEY_RATE_LIMIT_ENABLED, DEFAULT_RATE_LIMIT_ENABLED);
498         }
499     }
500 
501     @Override
getCachedRateLimitConfig()502     public AppSearchRateLimitConfig getCachedRateLimitConfig() {
503         synchronized (mLock) {
504             throwIfClosedLocked();
505             return mRateLimitConfigLocked;
506         }
507     }
508 
509     @GuardedBy("mLock")
throwIfClosedLocked()510     private void throwIfClosedLocked() {
511         if (mIsClosedLocked) {
512             throw new IllegalStateException("Trying to use a closed AppSearchConfig instance.");
513         }
514     }
515 
updateCachedValues(@onNull DeviceConfig.Properties properties)516     private void updateCachedValues(@NonNull DeviceConfig.Properties properties) {
517         for (String key : properties.getKeyset()) {
518             updateCachedValue(key, properties);
519         }
520         updateDerivedClasses();
521     }
522 
updateCachedValue(@onNull String key, @NonNull DeviceConfig.Properties properties)523     private void updateCachedValue(@NonNull String key,
524             @NonNull DeviceConfig.Properties properties) {
525         if (properties.getString(key, /*defaultValue=*/ null) == null) {
526             // Key is missing or value is just null. That is not expected if the key is
527             // defined in the configuration.
528             //
529             // We choose NOT to put the default value in the bundle.
530             // Instead, we let the getters handle what default value should be returned.
531             //
532             // Also we keep the old value in the bundle. So getters can still
533             // return last valid value.
534             return;
535         }
536 
537         switch (key) {
538             case KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS:
539                 synchronized (mLock) {
540                     mBundleLocked.putLong(key,
541                             properties.getLong(key,
542                                     DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS));
543                 }
544                 break;
545             case KEY_SAMPLING_INTERVAL_DEFAULT:
546             case KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS:
547             case KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS:
548             case KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS:
549             case KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS:
550             case KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS:
551             case KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS:
552                 synchronized (mLock) {
553                     mBundleLocked.putInt(key, properties.getInt(key, DEFAULT_SAMPLING_INTERVAL));
554                 }
555                 break;
556             case KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES:
557                 synchronized (mLock) {
558                     mBundleLocked.putInt(
559                             key,
560                             properties.getInt(key, DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES));
561                 }
562                 break;
563             case KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT:
564                 synchronized (mLock) {
565                     mBundleLocked.putInt(
566                             key,
567                             properties.getInt(key, DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT));
568                 }
569                 break;
570             case KEY_LIMIT_CONFIG_MAX_SUGGESTION_COUNT:
571                 synchronized (mLock) {
572                     mBundleLocked.putInt(
573                             key,
574                             properties.getInt(key, DEFAULT_LIMIT_CONFIG_MAX_SUGGESTION_COUNT));
575                 }
576                 break;
577             case KEY_BYTES_OPTIMIZE_THRESHOLD:
578                 synchronized (mLock) {
579                     mBundleLocked.putInt(key, properties.getInt(key,
580                             DEFAULT_BYTES_OPTIMIZE_THRESHOLD));
581                 }
582                 break;
583             case KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS:
584                 synchronized (mLock) {
585                     mBundleLocked.putInt(key, properties.getInt(key,
586                             DEFAULT_TIME_OPTIMIZE_THRESHOLD_MILLIS));
587                 }
588                 break;
589             case KEY_DOC_COUNT_OPTIMIZE_THRESHOLD:
590                 synchronized (mLock) {
591                     mBundleLocked.putInt(key, properties.getInt(key,
592                             DEFAULT_DOC_COUNT_OPTIMIZE_THRESHOLD));
593                 }
594                 break;
595             case KEY_MIN_TIME_OPTIMIZE_THRESHOLD_MILLIS:
596                 synchronized (mLock) {
597                     mBundleLocked.putInt(key, properties.getInt(key,
598                             DEFAULT_MIN_TIME_OPTIMIZE_THRESHOLD_MILLIS));
599                 }
600                 break;
601             case KEY_API_CALL_STATS_LIMIT:
602                 synchronized (mLock) {
603                     mBundleLocked.putInt(key,
604                             properties.getInt(key, DEFAULT_API_CALL_STATS_LIMIT));
605                 }
606                 break;
607             case KEY_DENYLIST:
608                 String denylistString = properties.getString(key, /* defaultValue= */ "");
609                 Denylist denylist =
610                         denylistString.isEmpty() ? Denylist.EMPTY_INSTANCE : Denylist.create(
611                                 denylistString);
612                 synchronized (mLock) {
613                     mDenylistLocked = denylist;
614                 }
615             case KEY_RATE_LIMIT_ENABLED:
616                 synchronized (mLock) {
617                     mBundleLocked.putBoolean(key, properties.getBoolean(key,
618                             DEFAULT_RATE_LIMIT_ENABLED));
619                 }
620                 break;
621             case KEY_RATE_LIMIT_TASK_QUEUE_TOTAL_CAPACITY:
622                 synchronized (mLock) {
623                     mBundleLocked.putInt(key, properties.getInt(key,
624                             DEFAULT_RATE_LIMIT_TASK_QUEUE_TOTAL_CAPACITY));
625                 }
626                 break;
627             case KEY_RATE_LIMIT_TASK_QUEUE_PER_PACKAGE_CAPACITY_PERCENTAGE:
628                 synchronized (mLock) {
629                     mBundleLocked.putFloat(key, properties.getFloat(key,
630                             DEFAULT_RATE_LIMIT_TASK_QUEUE_PER_PACKAGE_CAPACITY_PERCENTAGE));
631                 }
632                 break;
633             case KEY_RATE_LIMIT_API_COSTS:
634                 synchronized (mLock) {
635                     mBundleLocked.putString(key, properties.getString(key,
636                             DEFAULT_RATE_LIMIT_API_COSTS_STRING));
637                 }
638                 break;
639             case KEY_ICING_MAX_TOKEN_LENGTH:
640                 synchronized (mLock) {
641                     mBundleLocked.putInt(key, properties.getInt(key,
642                             IcingOptionsConfig.DEFAULT_MAX_TOKEN_LENGTH));
643                 }
644                 break;
645             case KEY_ICING_INDEX_MERGE_SIZE:
646                 synchronized (mLock) {
647                     mBundleLocked.putInt(key, properties.getInt(key,
648                             IcingOptionsConfig.DEFAULT_INDEX_MERGE_SIZE));
649                 }
650                 break;
651             case KEY_ICING_DOCUMENT_STORE_NAMESPACE_ID_FINGERPRINT:
652                 synchronized (mLock) {
653                     mBundleLocked.putBoolean(key, properties.getBoolean(key,
654                             IcingOptionsConfig.DEFAULT_DOCUMENT_STORE_NAMESPACE_ID_FINGERPRINT));
655                 }
656                 break;
657             case KEY_ICING_OPTIMIZE_REBUILD_INDEX_THRESHOLD:
658                 synchronized (mLock) {
659                     mBundleLocked.putFloat(key, properties.getFloat(key,
660                             IcingOptionsConfig.DEFAULT_OPTIMIZE_REBUILD_INDEX_THRESHOLD));
661                 }
662                 break;
663             case KEY_ICING_COMPRESSION_LEVEL:
664                 synchronized (mLock) {
665                     mBundleLocked.putInt(key, properties.getInt(key,
666                             IcingOptionsConfig.DEFAULT_COMPRESSION_LEVEL));
667                 }
668                 break;
669             case KEY_ICING_USE_READ_ONLY_SEARCH:
670                 synchronized (mLock) {
671                     mBundleLocked.putBoolean(key, properties.getBoolean(key,
672                             DEFAULT_ICING_CONFIG_USE_READ_ONLY_SEARCH));
673                 }
674                 break;
675             case KEY_ICING_USE_PRE_MAPPING_WITH_FILE_BACKED_VECTOR:
676                 synchronized (mLock) {
677                     mBundleLocked.putBoolean(key, properties.getBoolean(key,
678                             IcingOptionsConfig.DEFAULT_USE_PREMAPPING_WITH_FILE_BACKED_VECTOR));
679                 }
680                 break;
681             case KEY_ICING_USE_PERSISTENT_HASHMAP:
682                 synchronized (mLock) {
683                     mBundleLocked.putBoolean(key, properties.getBoolean(key,
684                             IcingOptionsConfig.DEFAULT_USE_PERSISTENT_HASH_MAP));
685                 }
686                 break;
687             case KEY_ICING_MAX_PAGE_BYTES_LIMIT:
688                 synchronized (mLock) {
689                     mBundleLocked.putInt(key, properties.getInt(key,
690                             IcingOptionsConfig.DEFAULT_MAX_PAGE_BYTES_LIMIT));
691                 }
692                 break;
693             default:
694                 break;
695         }
696     }
697 
updateDerivedClasses()698     private void updateDerivedClasses() {
699         if (getCachedRateLimitEnabled()) {
700             synchronized (mLock) {
701                 int taskQueueTotalCapacity = mBundleLocked.getInt(
702                         KEY_RATE_LIMIT_TASK_QUEUE_TOTAL_CAPACITY,
703                         DEFAULT_RATE_LIMIT_TASK_QUEUE_TOTAL_CAPACITY);
704                 float taskQueuePerPackagePercentage = mBundleLocked.getFloat(
705                         KEY_RATE_LIMIT_TASK_QUEUE_PER_PACKAGE_CAPACITY_PERCENTAGE,
706                         DEFAULT_RATE_LIMIT_TASK_QUEUE_PER_PACKAGE_CAPACITY_PERCENTAGE);
707                 String apiCostsString = mBundleLocked.getString(KEY_RATE_LIMIT_API_COSTS,
708                         DEFAULT_RATE_LIMIT_API_COSTS_STRING);
709                 mRateLimitConfigLocked = mRateLimitConfigLocked.rebuildIfNecessary(
710                         taskQueueTotalCapacity, taskQueuePerPackagePercentage, apiCostsString);
711             }
712         }
713     }
714 }
715