Use AppSearchConfig in PlatformLogger

bug: 173532925
Test: atest CtsAppSearchTestCases FrameworksCoreTests:android.app.appsearch
FrameworksServicesTests:AppSearchImplTest
FrameworksServicesTests:com.android.server.appsearch.stats.PlatformLoggerTest
FrameworksMockingServicesTests:com.android.server.appsearch.AppSearchConfigTest

Change-Id: I3589f0071d456e2167cd207e83acaf1d884f9992
diff --git a/service/java/com/android/server/appsearch/AppSearchConfig.java b/service/java/com/android/server/appsearch/AppSearchConfig.java
index 6e81afc..d5271a6 100644
--- a/service/java/com/android/server/appsearch/AppSearchConfig.java
+++ b/service/java/com/android/server/appsearch/AppSearchConfig.java
@@ -44,6 +44,8 @@
  * @hide
  */
 public final class AppSearchConfig implements AutoCloseable {
+    private static volatile AppSearchConfig sConfig;
+
     /**
      * 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.
@@ -101,12 +103,16 @@
                 updateCachedValues(properties);
             };
 
+    private AppSearchConfig() {
+    }
+
     /**
      * Creates an instance of {@link AppSearchConfig}.
      *
      * @param executor used to fetch and cache the flag values from DeviceConfig during creation or
      *                 config change.
      */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @NonNull
     public static AppSearchConfig create(@NonNull Executor executor) {
         Objects.requireNonNull(executor);
@@ -115,7 +121,23 @@
         return configManager;
     }
 
-    private AppSearchConfig() {
+    /**
+     * Gets an instance of {@link AppSearchConfig} to be used.
+     *
+     * <p>If no instance has been initialized yet, a new one will be created. Otherwise, the
+     * existing instance will be returned.
+     */
+    @NonNull
+    public static AppSearchConfig getInstance(@NonNull Executor executor) {
+        Objects.requireNonNull(executor);
+        if (sConfig == null) {
+            synchronized (AppSearchConfig.class) {
+                if (sConfig == null) {
+                    sConfig = create(executor);
+                }
+            }
+        }
+        return sConfig;
     }
 
     /**
diff --git a/service/java/com/android/server/appsearch/AppSearchManagerService.java b/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 0709ff5..a3b89b0 100644
--- a/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -224,7 +224,7 @@
             if (ImplInstanceManager.getAppSearchDir(userHandle).exists()) {
                 // Only clear the package's data if AppSearch exists for this user.
                 PlatformLogger logger = mLoggerInstanceManager.getOrCreatePlatformLogger(mContext,
-                        userHandle);
+                        userHandle, AppSearchConfig.getInstance(EXECUTOR));
                 AppSearchImpl impl = mImplInstanceManager.getOrCreateAppSearchImpl(mContext,
                         userHandle, logger);
                 //TODO(b/145759910) clear visibility setting for package.
@@ -1147,7 +1147,8 @@
                 try {
                     verifyUserUnlocked(callingUser);
                     logger = mLoggerInstanceManager.getOrCreatePlatformLogger(
-                            mContext, callingUser);
+                            mContext, callingUser,
+                            AppSearchConfig.getInstance(EXECUTOR));
                     mImplInstanceManager.getOrCreateAppSearchImpl(mContext, callingUser, logger);
                     ++operationSuccessCount;
                     invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
@@ -1313,7 +1314,8 @@
             try {
                 verifyUserUnlocked(userHandle);
                 PlatformLogger logger = mLoggerInstanceManager.getOrCreatePlatformLogger(
-                        mContext, userHandle);
+                        mContext, userHandle,
+                        AppSearchConfig.getInstance(EXECUTOR));
                 AppSearchImpl impl = mImplInstanceManager.getOrCreateAppSearchImpl(
                         mContext, userHandle, logger);
                 stats.dataSize += impl.getStorageInfoForPackage(packageName).getSizeBytes();
@@ -1341,7 +1343,8 @@
                     return;
                 }
                 PlatformLogger logger = mLoggerInstanceManager.getOrCreatePlatformLogger(
-                        mContext, userHandle);
+                        mContext, userHandle,
+                        AppSearchConfig.getInstance(EXECUTOR));
                 AppSearchImpl impl = mImplInstanceManager.getOrCreateAppSearchImpl(
                         mContext, userHandle, logger);
                 for (int i = 0; i < packagesForUid.length; i++) {
@@ -1370,7 +1373,8 @@
                     return;
                 }
                 PlatformLogger logger = mLoggerInstanceManager.getOrCreatePlatformLogger(
-                        mContext, userHandle);
+                        mContext, userHandle,
+                        AppSearchConfig.getInstance(EXECUTOR));
                 AppSearchImpl impl =
                         mImplInstanceManager.getOrCreateAppSearchImpl(mContext, userHandle, logger);
                 for (int i = 0; i < packagesForUser.size(); i++) {
diff --git a/service/java/com/android/server/appsearch/stats/LoggerInstanceManager.java b/service/java/com/android/server/appsearch/stats/LoggerInstanceManager.java
index cb65e42..ea00f50 100644
--- a/service/java/com/android/server/appsearch/stats/LoggerInstanceManager.java
+++ b/service/java/com/android/server/appsearch/stats/LoggerInstanceManager.java
@@ -20,9 +20,9 @@
 import android.content.Context;
 import android.os.UserHandle;
 import android.util.ArrayMap;
-import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.appsearch.AppSearchConfig;
 import com.android.server.appsearch.AppSearchManagerService;
 
 import java.util.Map;
@@ -34,12 +34,6 @@
  * <p>These instances are managed per unique device-user.
  */
 public final class LoggerInstanceManager {
-    // TODO(b/173532925) flags to control those three
-    // So probably we can't pass those three in the constructor but need to fetch the latest value
-    // every time we need them in the logger.
-    private static final int MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 100;
-    private static final int DEFAULT_SAMPLING_RATIO = 10;
-
     private static volatile LoggerInstanceManager sLoggerInstanceManager;
 
     @GuardedBy("mInstancesLocked")
@@ -70,23 +64,19 @@
     /**
      * Gets an instance of PlatformLogger for the given user, or creates one if none exists.
      *
-     * @param context The context
-     * @param userHandle  The multi-user handle of the device user calling AppSearch
+     * @param context    The context
+     * @param userHandle The multi-user handle of the device user calling AppSearch
      * @return An initialized {@link PlatformLogger} for this user
      */
     @NonNull
     public PlatformLogger getOrCreatePlatformLogger(
-            @NonNull Context context, @NonNull UserHandle userHandle) {
+            @NonNull Context context, @NonNull UserHandle userHandle,
+            @NonNull AppSearchConfig config) {
         Objects.requireNonNull(userHandle);
         synchronized (mInstancesLocked) {
             PlatformLogger instance = mInstancesLocked.get(userHandle);
             if (instance == null) {
-                instance = new PlatformLogger(context, userHandle, new PlatformLogger.Config(
-                        MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
-                        DEFAULT_SAMPLING_RATIO,
-                        // TODO(b/173532925) re-enable sampling ratios for different stats types
-                        // once we have P/H flag manager setup in ag/13977824
-                        /*samplingRatios=*/ new SparseIntArray()));
+                instance = new PlatformLogger(context, userHandle, config);
                 mInstancesLocked.put(userHandle, instance);
             }
             return instance;
diff --git a/service/java/com/android/server/appsearch/stats/PlatformLogger.java b/service/java/com/android/server/appsearch/stats/PlatformLogger.java
index c857fb6..37b67b2 100644
--- a/service/java/com/android/server/appsearch/stats/PlatformLogger.java
+++ b/service/java/com/android/server/appsearch/stats/PlatformLogger.java
@@ -29,6 +29,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.appsearch.AppSearchConfig;
 import com.android.server.appsearch.external.localstorage.AppSearchLogger;
 import com.android.server.appsearch.external.localstorage.stats.CallStats;
 import com.android.server.appsearch.external.localstorage.stats.InitializeStats;
@@ -60,15 +61,15 @@
     // User we're logging for.
     private final UserHandle mUserHandle;
 
-    // Configuration for the logger
-    private final Config mConfig;
+    // Manager holding the configuration flags
+    private final AppSearchConfig mConfig;
 
     private final Random mRng = new Random();
     private final Object mLock = new Object();
 
     /**
      * SparseArray to track how many stats we skipped due to
-     * {@link Config#mMinTimeIntervalBetweenSamplesMillis}.
+     * {@link AppSearchConfig#getCachedMinTimeIntervalBetweenSamplesMillis()}.
      *
      * <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:
@@ -98,53 +99,6 @@
     private long mLastPushTimeMillisLocked = 0;
 
     /**
-     * Class to configure the {@link PlatformLogger}
-     */
-    public static final class Config {
-        // Minimum time interval (in millis) since last message logged to Westworld before
-        // logging again.
-        private final long mMinTimeIntervalBetweenSamplesMillis;
-
-        // Default sampling interval for all types of stats
-        private final int mDefaultSamplingInterval;
-
-        /**
-         * 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 interval is missing for certain stats type,
-         * {@link Config#mDefaultSamplingInterval} will be used.
-         *
-         * <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 mSamplingIntervals;
-
-        /**
-         * Configuration for {@link PlatformLogger}
-         *
-         * @param minTimeIntervalBetweenSamplesMillis minimum time interval apart in Milliseconds
-         *                                            required for two consecutive stats logged
-         * @param defaultSamplingInterval             default sampling interval
-         * @param samplingIntervals                   SparseArray to customize sampling interval for
-         *                                            different stat types
-         */
-        public Config(long minTimeIntervalBetweenSamplesMillis,
-                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_INTERVAL_FOR_PUT_DOCUMENTS).
-            mMinTimeIntervalBetweenSamplesMillis = minTimeIntervalBetweenSamplesMillis;
-            mDefaultSamplingInterval = defaultSamplingInterval;
-            mSamplingIntervals = samplingIntervals;
-        }
-    }
-
-    /**
      * Helper class to hold platform specific stats for Westworld.
      */
     static final class ExtraStats {
@@ -166,7 +120,8 @@
      * Westworld constructor
      */
     public PlatformLogger(
-            @NonNull Context context, @NonNull UserHandle userHandle, @NonNull Config config) {
+            @NonNull Context context, @NonNull UserHandle userHandle,
+            @NonNull AppSearchConfig config) {
         mContext = Objects.requireNonNull(context);
         mUserHandle = Objects.requireNonNull(userHandle);
         mConfig = Objects.requireNonNull(config);
@@ -428,9 +383,12 @@
             packageUid = getPackageUidAsUserLocked(packageName);
         }
 
-        int samplingInterval = mConfig.mSamplingIntervals.get(callType,
-                mConfig.mDefaultSamplingInterval);
-
+        // The sampling ratio here might be different from the one used in
+        // shouldLogForTypeLocked if there is a config change in the middle.
+        // Since it is only one sample, we can just ignore this difference.
+        // Or we can retrieve samplingRatio at beginning and pass along
+        // as function parameter, but it will make code less cleaner with some duplication.
+        int samplingInterval = getSamplingIntervalFromConfig(callType);
         int skippedSampleCount = mSkippedSampleCountLocked.get(callType,
                 /*valueOfKeyIfNotFound=*/ 0);
         mSkippedSampleCountLocked.put(callType, 0);
@@ -450,9 +408,7 @@
     // rate limiting.
     @VisibleForTesting
     boolean shouldLogForTypeLocked(@CallStats.CallType int callType) {
-        int samplingInterval = mConfig.mSamplingIntervals.get(callType,
-                mConfig.mDefaultSamplingInterval);
-
+        int samplingInterval = getSamplingIntervalFromConfig(callType);
         // Sampling
         if (!shouldSample(samplingInterval)) {
             return false;
@@ -462,7 +418,7 @@
         // Check the timestamp to see if it is too close to last logged sample
         long currentTimeMillis = SystemClock.elapsedRealtime();
         if (mLastPushTimeMillisLocked
-                > currentTimeMillis - mConfig.mMinTimeIntervalBetweenSamplesMillis) {
+                > currentTimeMillis - mConfig.getCachedMinTimeIntervalBetweenSamplesMillis()) {
             int count = mSkippedSampleCountLocked.get(callType, /*valueOfKeyIfNotFound=*/ 0);
             ++count;
             mSkippedSampleCountLocked.put(callType, count);
@@ -502,6 +458,32 @@
         return packageUid;
     }
 
+    /** Returns sampling ratio for stats type specified form {@link AppSearchConfig}. */
+    private int getSamplingIntervalFromConfig(@CallStats.CallType int statsType) {
+        switch (statsType) {
+            case CallStats.CALL_TYPE_PUT_DOCUMENTS:
+            case CallStats.CALL_TYPE_GET_DOCUMENTS:
+            case CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_ID:
+            case CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_SEARCH:
+                return mConfig.getCachedSamplingIntervalForBatchCallStats();
+            case CallStats.CALL_TYPE_PUT_DOCUMENT:
+                return mConfig.getCachedSamplingIntervalForPutDocumentStats();
+            case CallStats.CALL_TYPE_UNKNOWN:
+            case CallStats.CALL_TYPE_INITIALIZE:
+            case CallStats.CALL_TYPE_SET_SCHEMA:
+            case CallStats.CALL_TYPE_GET_DOCUMENT:
+            case CallStats.CALL_TYPE_REMOVE_DOCUMENT_BY_ID:
+            case CallStats.CALL_TYPE_SEARCH:
+            case CallStats.CALL_TYPE_OPTIMIZE:
+            case CallStats.CALL_TYPE_FLUSH:
+            case CallStats.CALL_TYPE_GLOBAL_SEARCH:
+            case CallStats.CALL_TYPE_REMOVE_DOCUMENT_BY_SEARCH:
+                // TODO(b/173532925) Some of them above will have dedicated sampling ratio config
+            default:
+                return mConfig.getCachedSamplingIntervalDefault();
+        }
+    }
+
     //
     // Functions below are used for tests only
     //