Merge "Update VisibilityStore to handle role-based system visibility." into sc-dev
diff --git a/service/java/com/android/server/appsearch/AppSearchConfig.java b/service/java/com/android/server/appsearch/AppSearchConfig.java
new file mode 100644
index 0000000..6e81afc
--- /dev/null
+++ b/service/java/com/android/server/appsearch/AppSearchConfig.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.OnPropertiesChangedListener;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * It contains all the keys for the flags, as well as caches some of latest flag values from
+ * DeviceConfig.
+ *
+ * <p>Though the latest flag values can always be retrieved by calling {@code
+ * DeviceConfig.getProperty}, we want to cache some of those values. For example, the sampling
+ * intervals for logging, they are needed for each api call and it would be a little expensive to
+ * call
+ * {@code DeviceConfig.getProperty} every time.
+ *
+ * <p>Listener is registered to DeviceConfig keep the cached value up to date.
+ *
+ * <p>This class is thread-safe.
+ *
+ * @hide
+ */
+public final class AppSearchConfig implements AutoCloseable {
+    /**
+     * It would be used as default min time interval between samples in millis if there is no value
+     * set for {@link AppSearchConfig#KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS} in DeviceConfig.
+     */
+    @VisibleForTesting
+    static final long DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 50;
+
+    /**
+     * It would be used as default sampling interval if there is no value
+     * set for {@link AppSearchConfig#KEY_SAMPLING_INTERVAL_DEFAULT} in DeviceConfig.
+     */
+    @VisibleForTesting
+    static final int DEFAULT_SAMPLING_INTERVAL = 10;
+
+    /*
+     * Keys for ALL the flags stored in DeviceConfig.
+     */
+    public static final String KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS =
+            "min_time_interval_between_samples_millis";
+    public static final String KEY_SAMPLING_INTERVAL_DEFAULT = "sampling_interval_default";
+    public static final String KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS =
+            "sampling_interval_for_batch_call_stats";
+    public static final String KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS =
+            "sampling_interval_for_put_document_stats";
+
+    // Array contains all the corresponding keys for the cached values.
+    private static final String[] KEYS_TO_ALL_CACHED_VALUES = {
+            KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+            KEY_SAMPLING_INTERVAL_DEFAULT,
+            KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
+            KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS
+    };
+
+    // Lock needed for all the operations in this class.
+    private final Object mLock = new Object();
+
+    /**
+     * Bundle to hold all the cached flag values corresponding to
+     * {@link AppSearchConfig#KEYS_TO_ALL_CACHED_VALUES}.
+     */
+    @GuardedBy("mLock")
+    private final Bundle mBundleLocked = new Bundle();
+
+
+    @GuardedBy("mLock")
+    private boolean mIsClosedLocked = false;
+
+    /** Listener to update cached flag values from DeviceConfig. */
+    private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
+            properties -> {
+                if (!properties.getNamespace().equals(DeviceConfig.NAMESPACE_APPSEARCH)) {
+                    return;
+                }
+
+                updateCachedValues(properties);
+            };
+
+    /**
+     * Creates an instance of {@link AppSearchConfig}.
+     *
+     * @param executor used to fetch and cache the flag values from DeviceConfig during creation or
+     *                 config change.
+     */
+    @NonNull
+    public static AppSearchConfig create(@NonNull Executor executor) {
+        Objects.requireNonNull(executor);
+        AppSearchConfig configManager = new AppSearchConfig();
+        configManager.initialize(executor);
+        return configManager;
+    }
+
+    private AppSearchConfig() {
+    }
+
+    /**
+     * Initializes the {@link AppSearchConfig}
+     *
+     * <p>It fetches the custom properties from DeviceConfig if available.
+     *
+     * @param executor listener would be run on to handle P/H flag change.
+     */
+    private void initialize(@NonNull Executor executor) {
+        executor.execute(() -> {
+            // Attach the callback to get updates on those properties.
+            DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APPSEARCH,
+                    executor,
+                    mOnDeviceConfigChangedListener);
+
+            DeviceConfig.Properties properties = DeviceConfig.getProperties(
+                    DeviceConfig.NAMESPACE_APPSEARCH, KEYS_TO_ALL_CACHED_VALUES);
+            updateCachedValues(properties);
+        });
+    }
+
+    // TODO(b/173532925) check this will be called. If we have a singleton instance for this
+    //  class, probably we don't need it.
+    @Override
+    public void close() {
+        synchronized (mLock) {
+            if (mIsClosedLocked) {
+                return;
+            }
+
+            DeviceConfig.removeOnPropertiesChangedListener(mOnDeviceConfigChangedListener);
+            mIsClosedLocked = true;
+        }
+    }
+
+    /** Returns cached value for minTimeIntervalBetweenSamplesMillis. */
+    public long getCachedMinTimeIntervalBetweenSamplesMillis() {
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            return mBundleLocked.getLong(KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+                    DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS);
+        }
+    }
+
+    /**
+     * Returns cached value for default sampling interval for all the stats NOT listed in
+     * the configuration.
+     *
+     * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
+     */
+    public int getCachedSamplingIntervalDefault() {
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_DEFAULT, DEFAULT_SAMPLING_INTERVAL);
+        }
+    }
+
+    /**
+     * Returns cached value for sampling interval for batch calls.
+     *
+     * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
+     */
+    public int getCachedSamplingIntervalForBatchCallStats() {
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
+                    getCachedSamplingIntervalDefault());
+        }
+    }
+
+    /**
+     * Returns cached value for sampling interval for putDocument.
+     *
+     * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
+     */
+    public int getCachedSamplingIntervalForPutDocumentStats() {
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
+                    getCachedSamplingIntervalDefault());
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void throwIfClosedLocked() {
+        if (mIsClosedLocked) {
+            throw new IllegalStateException("Trying to use a closed AppSearchConfig instance.");
+        }
+    }
+
+    private void updateCachedValues(@NonNull DeviceConfig.Properties properties) {
+        for (String key : properties.getKeyset()) {
+            updateCachedValue(key, properties);
+        }
+    }
+
+    private void updateCachedValue(@NonNull String key,
+            @NonNull DeviceConfig.Properties properties) {
+        if (properties.getString(key, /*defaultValue=*/ null) == null) {
+            // Key is missing or value is just null. That is not expected if the key is
+            // defined in the configuration.
+            //
+            // We choose NOT to put the default value in the bundle.
+            // Instead, we let the getters handle what default value should be returned.
+            //
+            // Also we keep the old value in the bundle. So getters can still
+            // return last valid value.
+            return;
+        }
+
+        switch (key) {
+            case KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS:
+                synchronized (mLock) {
+                    mBundleLocked.putLong(key,
+                            properties.getLong(key,
+                                    DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS));
+                }
+                break;
+            case KEY_SAMPLING_INTERVAL_DEFAULT:
+            case KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS:
+            case KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS:
+                synchronized (mLock) {
+                    mBundleLocked.putInt(key, properties.getInt(key, DEFAULT_SAMPLING_INTERVAL));
+                }
+                break;
+            default:
+                break;
+        }
+    }
+}
diff --git a/service/java/com/android/server/appsearch/stats/PlatformLogger.java b/service/java/com/android/server/appsearch/stats/PlatformLogger.java
index abcf161..7d0ce41 100644
--- a/service/java/com/android/server/appsearch/stats/PlatformLogger.java
+++ b/service/java/com/android/server/appsearch/stats/PlatformLogger.java
@@ -72,7 +72,7 @@
      *
      * <p> We can have correct extrapolated number by adding those counts back when we log
      * the same type of stats next time. E.g. the true count of an event could be estimated as:
-     * SUM(sampling_ratio * (num_skipped_sample + 1)) as est_count
+     * SUM(sampling_interval * (num_skipped_sample + 1)) as est_count
      *
      * <p>The key to the SparseArray is {@link CallStats.CallType}
      */
@@ -105,42 +105,42 @@
         // logging again.
         private final long mMinTimeIntervalBetweenSamplesMillis;
 
-        // Default sampling ratio for all types of stats
-        private final int mDefaultSamplingRatio;
+        // Default sampling interval for all types of stats
+        private final int mDefaultSamplingInterval;
 
         /**
-         * Sampling ratios for different types of stats
+         * Sampling intervals for different types of stats
          *
          * <p>This SparseArray is passed by client and is READ-ONLY. The key to that SparseArray is
          * {@link CallStats.CallType}
          *
-         * <p>If sampling ratio is missing for certain stats type,
-         * {@link Config#mDefaultSamplingRatio} will be used.
+         * <p>If sampling interval is missing for certain stats type,
+         * {@link Config#mDefaultSamplingInterval} will be used.
          *
-         * <p>E.g. sampling ratio=10 means that one out of every 10 stats was logged. If sampling
-         * ratio is 1, we will log each sample and it acts as if the sampling is disabled.
+         * <p>E.g. sampling interval=10 means that one out of every 10 stats was logged. If sampling
+         * interval is 1, we will log each sample and it acts as if the sampling is disabled.
          */
         @NonNull
-        private final SparseIntArray mSamplingRatios;
+        private final SparseIntArray mSamplingIntervals;
 
         /**
          * Configuration for {@link PlatformLogger}
          *
          * @param minTimeIntervalBetweenSamplesMillis minimum time interval apart in Milliseconds
          *                                            required for two consecutive stats logged
-         * @param defaultSamplingRatio                default sampling ratio
-         * @param samplingRatios                      SparseArray to customize sampling ratio for
+         * @param defaultSamplingInterval             default sampling interval
+         * @param samplingIntervals                   SparseArray to customize sampling interval for
          *                                            different stat types
          */
         public Config(long minTimeIntervalBetweenSamplesMillis,
-                int defaultSamplingRatio,
-                @NonNull SparseIntArray samplingRatios) {
+                int defaultSamplingInterval,
+                @NonNull SparseIntArray samplingIntervals) {
             // TODO(b/173532925) Probably we can get rid of those three after we have p/h flags
             // for them.
-            // e.g. we can just call DeviceConfig.get(SAMPLING_RATIO_FOR_PUT_DOCUMENTS).
+            // e.g. we can just call DeviceConfig.get(SAMPLING_INTERVAL_FOR_PUT_DOCUMENTS).
             mMinTimeIntervalBetweenSamplesMillis = minTimeIntervalBetweenSamplesMillis;
-            mDefaultSamplingRatio = defaultSamplingRatio;
-            mSamplingRatios = samplingRatios;
+            mDefaultSamplingInterval = defaultSamplingInterval;
+            mSamplingIntervals = samplingIntervals;
         }
     }
 
@@ -150,14 +150,14 @@
     static final class ExtraStats {
         // UID for the calling package of the stats.
         final int mPackageUid;
-        // sampling ratio for the call type of the stats.
-        final int mSamplingRatio;
+        // sampling interval for the call type of the stats.
+        final int mSamplingInterval;
         // number of samplings skipped before the current one for the same call type.
         final int mSkippedSampleCount;
 
-        ExtraStats(int packageUid, int samplingRatio, int skippedSampleCount) {
+        ExtraStats(int packageUid, int samplingInterval, int skippedSampleCount) {
             mPackageUid = packageUid;
-            mSamplingRatio = samplingRatio;
+            mSamplingInterval = samplingInterval;
             mSkippedSampleCount = skippedSampleCount;
         }
     }
@@ -224,7 +224,7 @@
      *
      * @return removed UID for the package, or {@code INVALID_UID} if package was not previously
      * cached.
-    */
+     */
     public int removeCachedUidForPackage(@NonNull String packageName) {
         // TODO(b/173532925) This needs to be called when we get PACKAGE_REMOVED intent
         Objects.requireNonNull(packageName);
@@ -243,7 +243,7 @@
         try {
             int hashCodeForDatabase = calculateHashCodeMd5(database);
             AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_CALL_STATS_REPORTED,
-                    extraStats.mSamplingRatio,
+                    extraStats.mSamplingInterval,
                     extraStats.mSkippedSampleCount,
                     extraStats.mPackageUid,
                     hashCodeForDatabase,
@@ -275,7 +275,7 @@
         try {
             int hashCodeForDatabase = calculateHashCodeMd5(database);
             AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_PUT_DOCUMENT_STATS_REPORTED,
-                    extraStats.mSamplingRatio,
+                    extraStats.mSamplingInterval,
                     extraStats.mSkippedSampleCount,
                     extraStats.mPackageUid,
                     hashCodeForDatabase,
@@ -312,7 +312,7 @@
         try {
             int hashCodeForDatabase = calculateHashCodeMd5(database);
             AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_QUERY_STATS_REPORTED,
-                    extraStats.mSamplingRatio,
+                    extraStats.mSamplingInterval,
                     extraStats.mSkippedSampleCount,
                     extraStats.mPackageUid,
                     hashCodeForDatabase,
@@ -355,7 +355,7 @@
         ExtraStats extraStats = createExtraStatsLocked(/*packageName=*/ null,
                 CallStats.CALL_TYPE_INITIALIZE);
         AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_INITIALIZE_STATS_REPORTED,
-                extraStats.mSamplingRatio,
+                extraStats.mSamplingInterval,
                 extraStats.mSkippedSampleCount,
                 extraStats.mPackageUid,
                 stats.getStatusCode(),
@@ -428,14 +428,14 @@
             packageUid = getPackageUidAsUserLocked(packageName);
         }
 
-        int samplingRatio = mConfig.mSamplingRatios.get(callType,
-                mConfig.mDefaultSamplingRatio);
+        int samplingInterval = mConfig.mSamplingIntervals.get(callType,
+                mConfig.mDefaultSamplingInterval);
 
         int skippedSampleCount = mSkippedSampleCountLocked.get(callType,
                 /*valueOfKeyIfNotFound=*/ 0);
         mSkippedSampleCountLocked.put(callType, 0);
 
-        return new ExtraStats(packageUid, samplingRatio, skippedSampleCount);
+        return new ExtraStats(packageUid, samplingInterval, skippedSampleCount);
     }
 
     /**
@@ -450,11 +450,11 @@
     // rate limiting.
     @VisibleForTesting
     boolean shouldLogForTypeLocked(@CallStats.CallType int callType) {
-        int samplingRatio = mConfig.mSamplingRatios.get(callType,
-                mConfig.mDefaultSamplingRatio);
+        int samplingInterval = mConfig.mSamplingIntervals.get(callType,
+                mConfig.mDefaultSamplingInterval);
 
         // Sampling
-        if (!shouldSample(samplingRatio)) {
+        if (!shouldSample(samplingInterval)) {
             return false;
         }
 
@@ -475,15 +475,15 @@
     /**
      * Checks if the stats should be "sampled"
      *
-     * @param samplingRatio sampling ratio
+     * @param samplingInterval sampling interval
      * @return if the stats should be sampled
      */
-    private boolean shouldSample(int samplingRatio) {
-        if (samplingRatio <= 0) {
+    private boolean shouldSample(int samplingInterval) {
+        if (samplingInterval <= 0) {
             return false;
         }
 
-        return mRng.nextInt((int) samplingRatio) == 0;
+        return mRng.nextInt((int) samplingInterval) == 0;
     }
 
     /**
diff --git a/testing/mockingservicestests/src/com/android/server/appsearch/AppSearchConfigTest.java b/testing/mockingservicestests/src/com/android/server/appsearch/AppSearchConfigTest.java
new file mode 100644
index 0000000..c486028
--- /dev/null
+++ b/testing/mockingservicestests/src/com/android/server/appsearch/AppSearchConfigTest.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch;
+
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.provider.DeviceConfig;
+
+import com.android.server.testables.TestableDeviceConfig;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Tests for {@link AppSearchConfig}.
+ *
+ * <p>Build/Install/Run: atest FrameworksMockingServicesTests:AppSearchConfigTest
+ */
+public class AppSearchConfigTest {
+    @Rule
+    public final TestableDeviceConfig.TestableDeviceConfigRule
+            mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+    @Test
+    public void testDefaultValues_allCachedValue() {
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        assertThat(appSearchConfig.getCachedMinTimeIntervalBetweenSamplesMillis()).isEqualTo(
+                AppSearchConfig.DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS);
+        assertThat(appSearchConfig.getCachedSamplingIntervalDefault()).isEqualTo(
+                AppSearchConfig.DEFAULT_SAMPLING_INTERVAL);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForBatchCallStats()).isEqualTo(
+                AppSearchConfig.DEFAULT_SAMPLING_INTERVAL);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForPutDocumentStats()).isEqualTo(
+                AppSearchConfig.DEFAULT_SAMPLING_INTERVAL);
+    }
+
+    @Test
+    public void testCustomizedValue_minTimeIntervalBetweenSamplesMillis() {
+        final long minTimeIntervalBetweenSamplesMillis = -1;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+                Long.toString(minTimeIntervalBetweenSamplesMillis),
+                false);
+
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        assertThat(appSearchConfig.getCachedMinTimeIntervalBetweenSamplesMillis()).isEqualTo(
+                minTimeIntervalBetweenSamplesMillis);
+    }
+
+    @Test
+    public void testCustomizedValueOverride_minTimeIntervalBetweenSamplesMillis() {
+        long minTimeIntervalBetweenSamplesMillis = -1;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+                Long.toString(minTimeIntervalBetweenSamplesMillis),
+                false);
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        minTimeIntervalBetweenSamplesMillis = -2;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+                Long.toString(minTimeIntervalBetweenSamplesMillis),
+                false);
+
+        assertThat(appSearchConfig.getCachedMinTimeIntervalBetweenSamplesMillis()).isEqualTo(
+                minTimeIntervalBetweenSamplesMillis);
+    }
+
+    @Test
+    public void testCustomizedValue_allSamplingIntervals() {
+        final int samplingIntervalDefault = -1;
+        final int samplingIntervalPutDocumentStats = -2;
+        final int samplingIntervalBatchCallStats = -3;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+                Integer.toString(samplingIntervalDefault),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
+                Integer.toString(samplingIntervalPutDocumentStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
+                Integer.toString(samplingIntervalBatchCallStats),
+                false);
+
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        assertThat(appSearchConfig.getCachedSamplingIntervalDefault()).isEqualTo(
+                samplingIntervalDefault);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForPutDocumentStats()).isEqualTo(
+                samplingIntervalPutDocumentStats);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForBatchCallStats()).isEqualTo(
+                samplingIntervalBatchCallStats);
+    }
+
+    @Test
+    public void testCustomizedValueOverride_allSamplingIntervals() {
+        int samplingIntervalDefault = -1;
+        int samplingIntervalPutDocumentStats = -2;
+        int samplingIntervalBatchCallStats = -3;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+                Integer.toString(samplingIntervalDefault),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
+                Integer.toString(samplingIntervalPutDocumentStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
+                Integer.toString(samplingIntervalBatchCallStats),
+                false);
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        // Overrides
+        samplingIntervalDefault = -4;
+        samplingIntervalPutDocumentStats = -5;
+        samplingIntervalBatchCallStats = -6;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+                Integer.toString(samplingIntervalDefault),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
+                Integer.toString(samplingIntervalPutDocumentStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
+                Integer.toString(samplingIntervalBatchCallStats),
+                false);
+
+        assertThat(appSearchConfig.getCachedSamplingIntervalDefault()).isEqualTo(
+                samplingIntervalDefault);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForPutDocumentStats()).isEqualTo(
+                samplingIntervalPutDocumentStats);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForBatchCallStats()).isEqualTo(
+                samplingIntervalBatchCallStats);
+    }
+
+    /**
+     * Tests if we fall back to {@link AppSearchConfig#DEFAULT_SAMPLING_INTERVAL} if both default
+     * sampling
+     * interval and custom value are not set in DeviceConfig, and there is some other sampling
+     * interval
+     * set.
+     */
+    @Test
+    public void testFallbackToDefaultSamplingValue_useHardCodedDefault() {
+        final int samplingIntervalPutDocumentStats = -1;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
+                Integer.toString(samplingIntervalPutDocumentStats),
+                false);
+
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        assertThat(appSearchConfig.getCachedSamplingIntervalForPutDocumentStats()).isEqualTo(
+                samplingIntervalPutDocumentStats);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForBatchCallStats()).isEqualTo(
+                AppSearchConfig.DEFAULT_SAMPLING_INTERVAL);
+    }
+
+    // Tests if we fall back to configured default sampling interval if custom value is not set in
+    // DeviceConfig.
+    @Test
+    public void testFallbackDefaultSamplingValue_useConfiguredDefault() {
+        final int samplingIntervalPutDocumentStats = -1;
+        final int samplingIntervalDefault = -2;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
+                Integer.toString(samplingIntervalPutDocumentStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+                Integer.toString(samplingIntervalDefault),
+                false);
+
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        assertThat(appSearchConfig.getCachedSamplingIntervalForPutDocumentStats()).isEqualTo(
+                samplingIntervalPutDocumentStats);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForBatchCallStats()).isEqualTo(
+                samplingIntervalDefault);
+    }
+
+    // Tests that cached values should reflect latest values in DeviceConfig.
+    @Test
+    public void testFallbackDefaultSamplingValue_defaultValueChanged() {
+        int samplingIntervalPutDocumentStats = -1;
+        int samplingIntervalDefault = -2;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
+                Integer.toString(samplingIntervalPutDocumentStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+                Integer.toString(samplingIntervalDefault),
+                false);
+
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        // Sampling values changed.
+        samplingIntervalPutDocumentStats = -3;
+        samplingIntervalDefault = -4;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
+                Integer.toString(samplingIntervalPutDocumentStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+                Integer.toString(samplingIntervalDefault),
+                false);
+
+        assertThat(appSearchConfig.getCachedSamplingIntervalForPutDocumentStats()).isEqualTo(
+                samplingIntervalPutDocumentStats);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForBatchCallStats()).isEqualTo(
+                samplingIntervalDefault);
+    }
+
+    // Tests default sampling interval won't affect custom sampling intervals if they are set.
+    @Test
+    public void testShouldNotFallBack_ifValueConfigured() {
+        int samplingIntervalDefault = -1;
+        int samplingIntervalBatchCallStats = -2;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+                Integer.toString(samplingIntervalDefault),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
+                Integer.toString(samplingIntervalBatchCallStats),
+                false);
+
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        // Default sampling interval changed.
+        samplingIntervalDefault = -3;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+                Integer.toString(samplingIntervalDefault),
+                false);
+
+        assertThat(appSearchConfig.getCachedSamplingIntervalForBatchCallStats()).isEqualTo(
+                samplingIntervalBatchCallStats);
+    }
+
+    @Test
+    public void testNotUsable_afterClose() {
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        appSearchConfig.close();
+
+        Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
+                IllegalStateException.class,
+                () -> appSearchConfig.getCachedMinTimeIntervalBetweenSamplesMillis());
+        Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
+                IllegalStateException.class,
+                () -> appSearchConfig.getCachedSamplingIntervalDefault());
+        Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
+                IllegalStateException.class,
+                () -> appSearchConfig.getCachedSamplingIntervalForBatchCallStats());
+        Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
+                IllegalStateException.class,
+                () -> appSearchConfig.getCachedSamplingIntervalForPutDocumentStats());
+    }
+}
diff --git a/testing/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java b/testing/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java
index 747dd1d..734f05a 100644
--- a/testing/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java
+++ b/testing/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java
@@ -46,7 +46,7 @@
 
 public class PlatformLoggerTest {
     private static final int TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 100;
-    private static final int TEST_DEFAULT_SAMPLING_RATIO = 10;
+    private static final int TEST_DEFAULT_SAMPLING_INTERVAL = 10;
     private static final String TEST_PACKAGE_NAME = "packageName";
     private MockPackageManager mMockPackageManager = new MockPackageManager();
     private Context mContext;
@@ -72,63 +72,63 @@
     }
 
     @Test
-    public void testCreateExtraStatsLocked_nullSamplingRatioMap_returnsDefaultSamplingRatio() {
+    public void testCreateExtraStatsLocked_nullSamplingIntervalMap_returnsDefault() {
         PlatformLogger logger = new PlatformLogger(
                 ApplicationProvider.getApplicationContext(),
                 UserHandle.of(UserHandle.USER_NULL),
                 new PlatformLogger.Config(
                         TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
-                        TEST_DEFAULT_SAMPLING_RATIO,
-                        /*samplingRatios=*/ new SparseIntArray()));
+                        TEST_DEFAULT_SAMPLING_INTERVAL,
+                        /*samplingIntervals=*/ new SparseIntArray()));
 
-        // Make sure default sampling ratio is used if samplingMap is not provided.
+        // Make sure default sampling interval is used if samplingMap is not provided.
         assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
-                CallStats.CALL_TYPE_UNKNOWN).mSamplingRatio).isEqualTo(
-                TEST_DEFAULT_SAMPLING_RATIO);
+                CallStats.CALL_TYPE_UNKNOWN).mSamplingInterval).isEqualTo(
+                TEST_DEFAULT_SAMPLING_INTERVAL);
         assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
-                CallStats.CALL_TYPE_INITIALIZE).mSamplingRatio).isEqualTo(
-                TEST_DEFAULT_SAMPLING_RATIO);
+                CallStats.CALL_TYPE_INITIALIZE).mSamplingInterval).isEqualTo(
+                TEST_DEFAULT_SAMPLING_INTERVAL);
         assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
-                CallStats.CALL_TYPE_SEARCH).mSamplingRatio).isEqualTo(
-                TEST_DEFAULT_SAMPLING_RATIO);
+                CallStats.CALL_TYPE_SEARCH).mSamplingInterval).isEqualTo(
+                TEST_DEFAULT_SAMPLING_INTERVAL);
         assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
-                CallStats.CALL_TYPE_FLUSH).mSamplingRatio).isEqualTo(
-                TEST_DEFAULT_SAMPLING_RATIO);
+                CallStats.CALL_TYPE_FLUSH).mSamplingInterval).isEqualTo(
+                TEST_DEFAULT_SAMPLING_INTERVAL);
     }
 
 
     @Test
-    public void testCreateExtraStatsLocked_with_samplingRatioMap_returnsConfiguredSamplingRatio() {
-        int putDocumentSamplingRatio = 1;
-        int querySamplingRatio = 2;
-        final SparseIntArray samplingRatios = new SparseIntArray();
-        samplingRatios.put(CallStats.CALL_TYPE_PUT_DOCUMENT, putDocumentSamplingRatio);
-        samplingRatios.put(CallStats.CALL_TYPE_SEARCH, querySamplingRatio);
+    public void testCreateExtraStatsLocked_with_samplingIntervalMap_returnsConfigured() {
+        int putDocumentSamplingInterval = 1;
+        int querySamplingInterval = 2;
+        final SparseIntArray samplingIntervals = new SparseIntArray();
+        samplingIntervals.put(CallStats.CALL_TYPE_PUT_DOCUMENT, putDocumentSamplingInterval);
+        samplingIntervals.put(CallStats.CALL_TYPE_SEARCH, querySamplingInterval);
         PlatformLogger logger = new PlatformLogger(
                 ApplicationProvider.getApplicationContext(),
                 UserHandle.of(UserHandle.USER_NULL),
                 new PlatformLogger.Config(
                         TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
-                        TEST_DEFAULT_SAMPLING_RATIO,
-                        samplingRatios));
+                        TEST_DEFAULT_SAMPLING_INTERVAL,
+                        samplingIntervals));
 
-        // The default sampling ratio should be used if no sampling ratio is
+        // The default sampling interval should be used if no sampling interval is
         // provided for certain call type.
         assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
-                CallStats.CALL_TYPE_INITIALIZE).mSamplingRatio).isEqualTo(
-                TEST_DEFAULT_SAMPLING_RATIO);
+                CallStats.CALL_TYPE_INITIALIZE).mSamplingInterval).isEqualTo(
+                TEST_DEFAULT_SAMPLING_INTERVAL);
         assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
-                CallStats.CALL_TYPE_FLUSH).mSamplingRatio).isEqualTo(
-                TEST_DEFAULT_SAMPLING_RATIO);
+                CallStats.CALL_TYPE_FLUSH).mSamplingInterval).isEqualTo(
+                TEST_DEFAULT_SAMPLING_INTERVAL);
 
-        // The configured sampling ratio is used if sampling ratio is available
+        // The configured sampling interval is used if sampling interval is available
         // for certain call type.
         assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
-                CallStats.CALL_TYPE_PUT_DOCUMENT).mSamplingRatio).isEqualTo(
-                putDocumentSamplingRatio);
+                CallStats.CALL_TYPE_PUT_DOCUMENT).mSamplingInterval).isEqualTo(
+                putDocumentSamplingInterval);
         assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
-                CallStats.CALL_TYPE_SEARCH).mSamplingRatio).isEqualTo(
-                querySamplingRatio);
+                CallStats.CALL_TYPE_SEARCH).mSamplingInterval).isEqualTo(
+                querySamplingInterval);
     }
 
     @Test
@@ -202,16 +202,16 @@
     }
 
     @Test
-    public void testShouldLogForTypeLocked_trueWhenSampleRatioIsOne() {
-        final int samplingRatio = 1;
+    public void testShouldLogForTypeLocked_trueWhenSampleIntervalIsOne() {
+        final int samplingInterval = 1;
         final String testPackageName = "packageName";
         PlatformLogger logger = new PlatformLogger(
                 ApplicationProvider.getApplicationContext(),
                 UserHandle.of(UserHandle.USER_NULL),
                 new PlatformLogger.Config(
                         TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
-                        samplingRatio,
-                        /*samplingRatios=*/ new SparseIntArray()));
+                        samplingInterval,
+                        /*samplingIntervals=*/ new SparseIntArray()));
 
         // Sample should always be logged for the first time if sampling is disabled(value is one).
         assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isTrue();
@@ -220,18 +220,18 @@
     }
 
     @Test
-    public void testShouldLogForTypeLocked_falseWhenSampleRatioIsNegative() {
-        final int samplingRatio = -1;
+    public void testShouldLogForTypeLocked_falseWhenSampleIntervalIsNegative() {
+        final int samplingInterval = -1;
         final String testPackageName = "packageName";
         PlatformLogger logger = new PlatformLogger(
                 ApplicationProvider.getApplicationContext(),
                 UserHandle.of(UserHandle.USER_NULL),
                 new PlatformLogger.Config(
                         TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
-                        samplingRatio,
-                        /*samplingRatios=*/ new SparseIntArray()));
+                        samplingInterval,
+                        /*samplingIntervals=*/ new SparseIntArray()));
 
-        // Makes sure sample will be excluded due to sampling if sample ratio is negative.
+        // Makes sure sample will be excluded due to sampling if sample interval is negative.
         assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isFalse();
         // Skipped count should be 0 since it doesn't pass the sampling.
         assertThat(logger.createExtraStatsLocked(testPackageName,
@@ -241,7 +241,7 @@
     @Test
     public void testShouldLogForTypeLocked_falseWhenWithinCoolOffInterval() {
         // Next sample won't be excluded due to sampling.
-        final int samplingRatio = 1;
+        final int samplingInterval = 1;
         // Next sample would guaranteed to be too close.
         final int minTimeIntervalBetweenSamplesMillis = Integer.MAX_VALUE;
         final String testPackageName = "packageName";
@@ -250,8 +250,8 @@
                 UserHandle.of(UserHandle.USER_NULL),
                 new PlatformLogger.Config(
                         minTimeIntervalBetweenSamplesMillis,
-                        samplingRatio,
-                        /*samplingRatios=*/ new SparseIntArray()));
+                        samplingInterval,
+                        /*samplingIntervals=*/ new SparseIntArray()));
         logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime());
 
         // Makes sure sample will be excluded due to rate limiting if samples are too close.
@@ -263,7 +263,7 @@
     @Test
     public void testShouldLogForTypeLocked_trueWhenOutsideOfCoolOffInterval() {
         // Next sample won't be excluded due to sampling.
-        final int samplingRatio = 1;
+        final int samplingInterval = 1;
         // Next sample would guaranteed to be included.
         final int minTimeIntervalBetweenSamplesMillis = 0;
         final String testPackageName = "packageName";
@@ -272,8 +272,8 @@
                 UserHandle.of(UserHandle.USER_NULL),
                 new PlatformLogger.Config(
                         minTimeIntervalBetweenSamplesMillis,
-                        samplingRatio,
-                        /*samplingRatios=*/ new SparseIntArray()));
+                        samplingInterval,
+                        /*samplingIntervals=*/ new SparseIntArray()));
         logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime());
 
         // Makes sure sample will be logged if it is not too close to previous sample.
@@ -292,8 +292,8 @@
                 mContext.getUser(),
                 new PlatformLogger.Config(
                         TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
-                        TEST_DEFAULT_SAMPLING_RATIO,
-                        /*samplingRatios=*/ new SparseIntArray()));
+                        TEST_DEFAULT_SAMPLING_INTERVAL,
+                        /*samplingIntervals=*/ new SparseIntArray()));
         mMockPackageManager.mockGetPackageUidAsUser(testPackageName, mContext.getUserId(), testUid);
 
         //