Merge "Batch calls to the Metadata Syncer" into main
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
index 1f98334..c3b7087 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
@@ -16,15 +16,7 @@
 
 package com.android.server.appfunctions;
 
-import android.annotation.NonNull;
-import android.os.UserHandle;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
-
 import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
@@ -41,50 +33,5 @@
                     /* unit= */ TimeUnit.SECONDS,
                     /* workQueue= */ new LinkedBlockingQueue<>());
 
-    /** A map of per-user executors for queued work. */
-    @GuardedBy("sLock")
-    private static final SparseArray<ExecutorService> mPerUserExecutorsLocked = new SparseArray<>();
-
-    private static final Object sLock = new Object();
-
-    /**
-     * Returns a per-user executor for queued metadata sync request.
-     *
-     * <p>The work submitted to these executor (Sync request) needs to be synchronous per user hence
-     * the use of a single thread.
-     *
-     * <p>Note: Use a different executor if not calling {@code submitSyncRequest} on a {@code
-     * MetadataSyncAdapter}.
-     */
-    // TODO(b/357551503): Restrict the scope of this executor to the MetadataSyncAdapter itself.
-    public static ExecutorService getPerUserSyncExecutor(@NonNull UserHandle user) {
-        synchronized (sLock) {
-            ExecutorService executor = mPerUserExecutorsLocked.get(user.getIdentifier(), null);
-            if (executor == null) {
-                executor = Executors.newSingleThreadExecutor();
-                mPerUserExecutorsLocked.put(user.getIdentifier(), executor);
-            }
-            return executor;
-        }
-    }
-
-    /**
-     * Shuts down and removes the per-user executor for queued work.
-     *
-     * <p>This should be called when the user is removed.
-     */
-    public static void shutDownAndRemoveUserExecutor(@NonNull UserHandle user)
-            throws InterruptedException {
-        ExecutorService executor;
-        synchronized (sLock) {
-            executor = mPerUserExecutorsLocked.get(user.getIdentifier());
-            mPerUserExecutorsLocked.remove(user.getIdentifier());
-        }
-        if (executor != null) {
-            executor.shutdown();
-            var unused = executor.awaitTermination(30, TimeUnit.SECONDS);
-        }
-    }
-
     private AppFunctionExecutors() {}
 }
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 469a1a2..165a945 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -95,12 +95,7 @@
     public void onUserStopping(@NonNull TargetUser user) {
         Objects.requireNonNull(user);
 
-        try {
-            AppFunctionExecutors.shutDownAndRemoveUserExecutor(user.getUserHandle());
-            MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
-        } catch (InterruptedException e) {
-            Slog.e(TAG, "Unable to remove data for: " + user.getUserHandle(), e);
-        }
+        MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
     }
 
     @Override
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index e29b6e4..759f02e 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -42,6 +42,7 @@
 import android.util.ArraySet;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.infra.AndroidFuture;
 import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults;
@@ -52,8 +53,12 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
 
 /**
  * This class implements helper methods for synchronously interacting with AppSearch while
@@ -63,9 +68,14 @@
  */
 public class MetadataSyncAdapter {
     private static final String TAG = MetadataSyncAdapter.class.getSimpleName();
-    private final Executor mSyncExecutor;
+
+    private final ExecutorService mExecutor;
+
     private final AppSearchManager mAppSearchManager;
     private final PackageManager mPackageManager;
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private Future<AndroidFuture<Boolean>> mCurrentSyncTask;
 
     // Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility
     // by permissions.
@@ -73,12 +83,10 @@
     public static final int EXECUTE_APP_FUNCTIONS_TRUSTED = 10;
 
     public MetadataSyncAdapter(
-            @NonNull Executor syncExecutor,
-            @NonNull PackageManager packageManager,
-            @NonNull AppSearchManager appSearchManager) {
-        mSyncExecutor = Objects.requireNonNull(syncExecutor);
+            @NonNull PackageManager packageManager, @NonNull AppSearchManager appSearchManager) {
         mPackageManager = Objects.requireNonNull(packageManager);
         mAppSearchManager = Objects.requireNonNull(appSearchManager);
+        mExecutor = Executors.newSingleThreadExecutor();
     }
 
     /**
@@ -97,7 +105,7 @@
                                 AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB)
                         .build();
         AndroidFuture<Boolean> settableSyncStatus = new AndroidFuture<>();
-        mSyncExecutor.execute(
+        Callable<AndroidFuture<Boolean>> callableTask =
                 () -> {
                     try (FutureAppSearchSession staticMetadataSearchSession =
                                     new FutureAppSearchSessionImpl(
@@ -117,10 +125,28 @@
                     } catch (Exception ex) {
                         settableSyncStatus.completeExceptionally(ex);
                     }
-                });
+                    return settableSyncStatus;
+                };
+
+        synchronized (mLock) {
+            if (mCurrentSyncTask != null && !mCurrentSyncTask.isDone()) {
+                boolean cancel = mCurrentSyncTask.cancel(false);
+            }
+            mCurrentSyncTask = mExecutor.submit(callableTask);
+        }
+
         return settableSyncStatus;
     }
 
+    /** This method shuts down the {@link MetadataSyncAdapter} scheduler. */
+    public void shutDown() {
+        try {
+            var unused = mExecutor.awaitTermination(30, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            Slog.e(TAG, "Error shutting down MetadataSyncAdapter scheduler", e);
+        }
+    }
+
     @WorkerThread
     @VisibleForTesting
     void trySyncAppFunctionMetadataBlocking(
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
index f421527..e933ec1 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
@@ -55,10 +55,7 @@
                 PackageManager perUserPackageManager = userContext.getPackageManager();
                 if (perUserAppSearchManager != null) {
                     metadataSyncAdapter =
-                            new MetadataSyncAdapter(
-                                    AppFunctionExecutors.getPerUserSyncExecutor(user),
-                                    perUserPackageManager,
-                                    perUserAppSearchManager);
+                            new MetadataSyncAdapter(perUserPackageManager, perUserAppSearchManager);
                     sPerUserMetadataSyncAdapter.put(user.getIdentifier(), metadataSyncAdapter);
                     return metadataSyncAdapter;
                 }
@@ -74,7 +71,12 @@
      */
     public static void removeUserSyncAdapter(UserHandle user) {
         synchronized (sLock) {
-            sPerUserMetadataSyncAdapter.remove(user.getIdentifier());
+            MetadataSyncAdapter metadataSyncAdapter =
+                    sPerUserMetadataSyncAdapter.get(user.getIdentifier(), null);
+            if (metadataSyncAdapter != null) {
+                metadataSyncAdapter.shutDown();
+                sPerUserMetadataSyncAdapter.remove(user.getIdentifier());
+            }
         }
     }
 }
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
index c05c381..bc64e15 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
@@ -36,7 +36,6 @@
 import com.android.internal.infra.AndroidFuture
 import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults
 import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.MoreExecutors
 import java.util.concurrent.atomic.AtomicBoolean
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -46,7 +45,6 @@
 class MetadataSyncAdapterTest {
     private val context = InstrumentationRegistry.getInstrumentation().targetContext
     private val appSearchManager = context.getSystemService(AppSearchManager::class.java)
-    private val testExecutor = MoreExecutors.directExecutor()
     private val packageManager = context.packageManager
 
     @Test
@@ -138,8 +136,7 @@
             PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
         runtimeSearchSession.put(putDocumentsRequest).get()
         staticSearchSession.put(putDocumentsRequest).get()
-        val metadataSyncAdapter =
-            MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+        val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
 
         val submitSyncRequest =
             metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
@@ -180,8 +177,7 @@
         val putDocumentsRequest: PutDocumentsRequest =
             PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
         staticSearchSession.put(putDocumentsRequest).get()
-        val metadataSyncAdapter =
-            MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+        val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
 
         val submitSyncRequest =
             metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
@@ -236,8 +232,7 @@
         val putDocumentsRequest: PutDocumentsRequest =
             PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
         runtimeSearchSession.put(putDocumentsRequest).get()
-        val metadataSyncAdapter =
-            MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+        val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
 
         val submitSyncRequest =
             metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(