• 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.Bundle;
21 import android.provider.DeviceConfig;
22 import android.provider.DeviceConfig.OnPropertiesChangedListener;
23 
24 import com.android.internal.annotations.GuardedBy;
25 import com.android.internal.annotations.VisibleForTesting;
26 
27 import java.util.Objects;
28 import java.util.concurrent.Executor;
29 
30 /**
31  * It contains all the keys for the flags, as well as caches some of latest flag values from
32  * DeviceConfig.
33  *
34  * <p>Though the latest flag values can always be retrieved by calling {@code
35  * DeviceConfig.getProperty}, we want to cache some of those values. For example, the sampling
36  * intervals for logging, they are needed for each api call and it would be a little expensive to
37  * call
38  * {@code 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 AppSearchConfig implements AutoCloseable {
47     private static volatile AppSearchConfig sConfig;
48 
49     /**
50      * It would be used as default min time interval between samples in millis if there is no value
51      * set for {@link AppSearchConfig#KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS} in DeviceConfig.
52      */
53     @VisibleForTesting
54     static final long DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 50;
55 
56     /**
57      * It would be used as default sampling interval if there is no value
58      * set for {@link AppSearchConfig#KEY_SAMPLING_INTERVAL_DEFAULT} in DeviceConfig.
59      */
60     @VisibleForTesting
61     static final int DEFAULT_SAMPLING_INTERVAL = 10;
62 
63     @VisibleForTesting
64     static final int DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES = 512 * 1024; // 512KiB
65     @VisibleForTesting
66     static final int DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT = 20_000;
67     @VisibleForTesting
68     static final int DEFAULT_BYTES_OPTIMIZE_THRESHOLD = 1 * 1024 * 1024; // 1 MiB
69     @VisibleForTesting
70     static final int DEFAULT_TIME_OPTIMIZE_THRESHOLD_MILLIS = Integer.MAX_VALUE;
71     @VisibleForTesting
72     static final int DEFAULT_DOC_COUNT_OPTIMIZE_THRESHOLD = 10_000;
73 
74     /*
75      * Keys for ALL the flags stored in DeviceConfig.
76      */
77     public static final String KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS =
78             "min_time_interval_between_samples_millis";
79     public static final String KEY_SAMPLING_INTERVAL_DEFAULT = "sampling_interval_default";
80     public static final String KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS =
81             "sampling_interval_for_batch_call_stats";
82     public static final String KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS =
83             "sampling_interval_for_put_document_stats";
84     public static final String KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS =
85             "sampling_interval_for_initialize_stats";
86     public static final String KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS =
87             "sampling_interval_for_search_stats";
88     public static final String KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS =
89             "sampling_interval_for_global_search_stats";
90     public static final String KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS =
91             "sampling_interval_for_optimize_stats";
92     public static final String KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES =
93             "limit_config_max_document_size_bytes";
94     public static final String KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT =
95             "limit_config_max_document_docunt";
96     public static final String KEY_BYTES_OPTIMIZE_THRESHOLD = "bytes_optimize_threshold";
97     public static final String KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS = "time_optimize_threshold";
98     public static final String KEY_DOC_COUNT_OPTIMIZE_THRESHOLD = "doc_count_optimize_threshold";
99 
100     // Array contains all the corresponding keys for the cached values.
101     private static final String[] KEYS_TO_ALL_CACHED_VALUES = {
102             KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
103             KEY_SAMPLING_INTERVAL_DEFAULT,
104             KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
105             KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
106             KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS,
107             KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS,
108             KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS,
109             KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS,
110             KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES,
111             KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT,
112             KEY_BYTES_OPTIMIZE_THRESHOLD,
113             KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS,
114             KEY_DOC_COUNT_OPTIMIZE_THRESHOLD
115     };
116 
117     // Lock needed for all the operations in this class.
118     private final Object mLock = new Object();
119 
120     /**
121      * Bundle to hold all the cached flag values corresponding to
122      * {@link AppSearchConfig#KEYS_TO_ALL_CACHED_VALUES}.
123      */
124     @GuardedBy("mLock")
125     private final Bundle mBundleLocked = new Bundle();
126 
127 
128     @GuardedBy("mLock")
129     private boolean mIsClosedLocked = false;
130 
131     /** Listener to update cached flag values from DeviceConfig. */
132     private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
133             properties -> {
134                 if (!properties.getNamespace().equals(DeviceConfig.NAMESPACE_APPSEARCH)) {
135                     return;
136                 }
137 
138                 updateCachedValues(properties);
139             };
140 
AppSearchConfig()141     private AppSearchConfig() {
142     }
143 
144     /**
145      * Creates an instance of {@link AppSearchConfig}.
146      *
147      * @param executor used to fetch and cache the flag values from DeviceConfig during creation or
148      *                 config change.
149      */
150     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
151     @NonNull
create(@onNull Executor executor)152     public static AppSearchConfig create(@NonNull Executor executor) {
153         Objects.requireNonNull(executor);
154         AppSearchConfig configManager = new AppSearchConfig();
155         configManager.initialize(executor);
156         return configManager;
157     }
158 
159     /**
160      * Gets an instance of {@link AppSearchConfig} to be used.
161      *
162      * <p>If no instance has been initialized yet, a new one will be created. Otherwise, the
163      * existing instance will be returned.
164      */
165     @NonNull
getInstance(@onNull Executor executor)166     public static AppSearchConfig getInstance(@NonNull Executor executor) {
167         Objects.requireNonNull(executor);
168         if (sConfig == null) {
169             synchronized (AppSearchConfig.class) {
170                 if (sConfig == null) {
171                     sConfig = create(executor);
172                 }
173             }
174         }
175         return sConfig;
176     }
177 
178     /**
179      * Initializes the {@link AppSearchConfig}
180      *
181      * <p>It fetches the custom properties from DeviceConfig if available.
182      *
183      * @param executor listener would be run on to handle P/H flag change.
184      */
initialize(@onNull Executor executor)185     private void initialize(@NonNull Executor executor) {
186         executor.execute(() -> {
187             // Attach the callback to get updates on those properties.
188             DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APPSEARCH,
189                     executor,
190                     mOnDeviceConfigChangedListener);
191 
192             DeviceConfig.Properties properties = DeviceConfig.getProperties(
193                     DeviceConfig.NAMESPACE_APPSEARCH, KEYS_TO_ALL_CACHED_VALUES);
194             updateCachedValues(properties);
195         });
196     }
197 
198     // TODO(b/173532925) check this will be called. If we have a singleton instance for this
199     //  class, probably we don't need it.
200     @Override
close()201     public void close() {
202         synchronized (mLock) {
203             if (mIsClosedLocked) {
204                 return;
205             }
206 
207             DeviceConfig.removeOnPropertiesChangedListener(mOnDeviceConfigChangedListener);
208             mIsClosedLocked = true;
209         }
210     }
211 
212     /** Returns cached value for minTimeIntervalBetweenSamplesMillis. */
getCachedMinTimeIntervalBetweenSamplesMillis()213     public long getCachedMinTimeIntervalBetweenSamplesMillis() {
214         synchronized (mLock) {
215             throwIfClosedLocked();
216             return mBundleLocked.getLong(KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
217                     DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS);
218         }
219     }
220 
221     /**
222      * Returns cached value for default sampling interval for all the stats NOT listed in
223      * the configuration.
224      *
225      * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
226      */
getCachedSamplingIntervalDefault()227     public int getCachedSamplingIntervalDefault() {
228         synchronized (mLock) {
229             throwIfClosedLocked();
230             return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_DEFAULT, DEFAULT_SAMPLING_INTERVAL);
231         }
232     }
233 
234     /**
235      * Returns cached value for sampling interval for batch calls.
236      *
237      * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
238      */
getCachedSamplingIntervalForBatchCallStats()239     public int getCachedSamplingIntervalForBatchCallStats() {
240         synchronized (mLock) {
241             throwIfClosedLocked();
242             return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
243                     getCachedSamplingIntervalDefault());
244         }
245     }
246 
247     /**
248      * Returns cached value for sampling interval for putDocument.
249      *
250      * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
251      */
getCachedSamplingIntervalForPutDocumentStats()252     public int getCachedSamplingIntervalForPutDocumentStats() {
253         synchronized (mLock) {
254             throwIfClosedLocked();
255             return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
256                     getCachedSamplingIntervalDefault());
257         }
258     }
259 
260     /**
261      * Returns cached value for sampling interval for initialize.
262      *
263      * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
264      */
getCachedSamplingIntervalForInitializeStats()265     public int getCachedSamplingIntervalForInitializeStats() {
266         synchronized (mLock) {
267             throwIfClosedLocked();
268             return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS,
269                     getCachedSamplingIntervalDefault());
270         }
271     }
272 
273     /**
274      * Returns cached value for sampling interval for search.
275      *
276      * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
277      */
getCachedSamplingIntervalForSearchStats()278     public int getCachedSamplingIntervalForSearchStats() {
279         synchronized (mLock) {
280             throwIfClosedLocked();
281             return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS,
282                     getCachedSamplingIntervalDefault());
283         }
284     }
285 
286     /**
287      * Returns cached value for sampling interval for globalSearch.
288      *
289      * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
290      */
getCachedSamplingIntervalForGlobalSearchStats()291     public int getCachedSamplingIntervalForGlobalSearchStats() {
292         synchronized (mLock) {
293             throwIfClosedLocked();
294             return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS,
295                     getCachedSamplingIntervalDefault());
296         }
297     }
298 
299     /**
300      * Returns cached value for sampling interval for optimize.
301      *
302      * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
303      */
getCachedSamplingIntervalForOptimizeStats()304     public int getCachedSamplingIntervalForOptimizeStats() {
305         synchronized (mLock) {
306             throwIfClosedLocked();
307             return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS,
308                     getCachedSamplingIntervalDefault());
309         }
310     }
311 
312     /** Returns the maximum serialized size an indexed document can be, in bytes. */
getCachedLimitConfigMaxDocumentSizeBytes()313     public int getCachedLimitConfigMaxDocumentSizeBytes() {
314         synchronized (mLock) {
315             throwIfClosedLocked();
316             return mBundleLocked.getInt(KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES,
317                     DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES);
318         }
319     }
320 
321     /** Returns the maximum number of active docs allowed per package. */
getCachedLimitConfigMaxDocumentCount()322     public int getCachedLimitConfigMaxDocumentCount() {
323         synchronized (mLock) {
324             throwIfClosedLocked();
325             return mBundleLocked.getInt(KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT,
326                     DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT);
327         }
328     }
329 
330     /**
331      * Returns the cached optimize byte size threshold.
332      *
333      * An AppSearch Optimize job will be triggered if the bytes size of garbage resource exceeds
334      * this threshold.
335      */
getCachedBytesOptimizeThreshold()336     int getCachedBytesOptimizeThreshold() {
337         synchronized (mLock) {
338             throwIfClosedLocked();
339             return mBundleLocked.getInt(KEY_BYTES_OPTIMIZE_THRESHOLD,
340                     DEFAULT_BYTES_OPTIMIZE_THRESHOLD);
341         }
342     }
343 
344     /**
345      * Returns the cached optimize time interval threshold.
346      *
347      * An AppSearch Optimize job will be triggered if the time since last optimize job exceeds
348      * this threshold.
349      */
getCachedTimeOptimizeThresholdMs()350     int getCachedTimeOptimizeThresholdMs() {
351         synchronized (mLock) {
352             throwIfClosedLocked();
353             return mBundleLocked.getInt(KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS,
354                     DEFAULT_TIME_OPTIMIZE_THRESHOLD_MILLIS);
355         }
356     }
357 
358     /**
359      * Returns the cached optimize document count threshold threshold.
360      *
361      * An AppSearch Optimize job will be triggered if the number of document of garbage resource
362      * exceeds this threshold.
363      */
getCachedDocCountOptimizeThreshold()364     int getCachedDocCountOptimizeThreshold() {
365         synchronized (mLock) {
366             throwIfClosedLocked();
367             return mBundleLocked.getInt(KEY_DOC_COUNT_OPTIMIZE_THRESHOLD,
368                     DEFAULT_DOC_COUNT_OPTIMIZE_THRESHOLD);
369         }
370     }
371 
372     @GuardedBy("mLock")
throwIfClosedLocked()373     private void throwIfClosedLocked() {
374         if (mIsClosedLocked) {
375             throw new IllegalStateException("Trying to use a closed AppSearchConfig instance.");
376         }
377     }
378 
updateCachedValues(@onNull DeviceConfig.Properties properties)379     private void updateCachedValues(@NonNull DeviceConfig.Properties properties) {
380         for (String key : properties.getKeyset()) {
381             updateCachedValue(key, properties);
382         }
383     }
384 
updateCachedValue(@onNull String key, @NonNull DeviceConfig.Properties properties)385     private void updateCachedValue(@NonNull String key,
386             @NonNull DeviceConfig.Properties properties) {
387         if (properties.getString(key, /*defaultValue=*/ null) == null) {
388             // Key is missing or value is just null. That is not expected if the key is
389             // defined in the configuration.
390             //
391             // We choose NOT to put the default value in the bundle.
392             // Instead, we let the getters handle what default value should be returned.
393             //
394             // Also we keep the old value in the bundle. So getters can still
395             // return last valid value.
396             return;
397         }
398 
399         switch (key) {
400             case KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS:
401                 synchronized (mLock) {
402                     mBundleLocked.putLong(key,
403                             properties.getLong(key,
404                                     DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS));
405                 }
406                 break;
407             case KEY_SAMPLING_INTERVAL_DEFAULT:
408             case KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS:
409             case KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS:
410             case KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS:
411             case KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS:
412             case KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS:
413             case KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS:
414                 synchronized (mLock) {
415                     mBundleLocked.putInt(key, properties.getInt(key, DEFAULT_SAMPLING_INTERVAL));
416                 }
417                 break;
418             case KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES:
419                 synchronized (mLock) {
420                     mBundleLocked.putInt(
421                             key,
422                             properties.getInt(key, DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES));
423                 }
424                 break;
425             case KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT:
426                 synchronized (mLock) {
427                     mBundleLocked.putInt(
428                             key,
429                             properties.getInt(key, DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT));
430                 }
431                 break;
432             case KEY_BYTES_OPTIMIZE_THRESHOLD:
433                 synchronized (mLock) {
434                     mBundleLocked.putInt(key, properties.getInt(key,
435                             DEFAULT_BYTES_OPTIMIZE_THRESHOLD));
436                 }
437                 break;
438             case KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS:
439                 synchronized (mLock) {
440                     mBundleLocked.putInt(key, properties.getInt(key,
441                             DEFAULT_TIME_OPTIMIZE_THRESHOLD_MILLIS));
442                 }
443                 break;
444             case KEY_DOC_COUNT_OPTIMIZE_THRESHOLD:
445                 synchronized (mLock) {
446                     mBundleLocked.putInt(key, properties.getInt(key,
447                             DEFAULT_DOC_COUNT_OPTIMIZE_THRESHOLD));
448                 }
449                 break;
450             default:
451                 break;
452         }
453     }
454 }
455