Handle AppSearch RESULT_OUT_OF_SPACE error am: 754687a8fc

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/modules/AppSearch/+/17140094

Change-Id: Ieec590d2ca94442036ba8380b86fcc3db83e2f0b
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/service/java/com/android/server/appsearch/AppSearchModule.java b/service/java/com/android/server/appsearch/AppSearchModule.java
index aeafec8..41ec91b 100644
--- a/service/java/com/android/server/appsearch/AppSearchModule.java
+++ b/service/java/com/android/server/appsearch/AppSearchModule.java
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.os.Environment;
 import android.os.UserHandle;
-import android.provider.DeviceConfig;
 import android.util.Log;
 
 import com.android.server.SystemService;
@@ -48,7 +47,8 @@
 
     public static final class Lifecycle extends SystemService {
         private AppSearchManagerService mAppSearchManagerService;
-        @Nullable private ContactsIndexerManagerService mContactsIndexerManagerService;
+        @Nullable
+        private ContactsIndexerManagerService mContactsIndexerManagerService;
 
         public Lifecycle(Context context) {
             super(context);
diff --git a/service/java/com/android/server/appsearch/contactsindexer/AppSearchHelper.java b/service/java/com/android/server/appsearch/contactsindexer/AppSearchHelper.java
index 54b273b..3175ad7 100644
--- a/service/java/com/android/server/appsearch/contactsindexer/AppSearchHelper.java
+++ b/service/java/com/android/server/appsearch/contactsindexer/AppSearchHelper.java
@@ -218,7 +218,7 @@
                 public void onResult(AppSearchBatchResult<String, Void> result) {
                     int numDocsSucceeded = result.getSuccesses().size();
                     int numDocsFailed = result.getFailures().size();
-                    updateStats.mContactsUpdateCount += numDocsSucceeded;
+                    updateStats.mContactsUpdateSucceededCount += numDocsSucceeded;
                     updateStats.mContactsUpdateFailedCount += numDocsFailed;
                     if (result.isSuccess()) {
                         Log.v(TAG,
@@ -234,9 +234,6 @@
                             updateStats.mUpdateStatuses.add(failure.getResultCode());
                         }
                         Log.w(TAG, numDocsFailed + " documents failed to be added in AppSearch.");
-                        // TODO(b/222187514) we can only have 20,000(default) contacts stored.
-                        //  In order to save the latest contacts, we need to remove the oldest ones
-                        //  in this ELSE. RESULT_OUT_OF_SPACE is the error code for this case.
                         future.completeExceptionally(new AppSearchException(
                                 firstFailure.getResultCode(), firstFailure.getErrorMessage()));
                     }
@@ -290,7 +287,7 @@
                             }
                         }
                     }
-                    updateStats.mContactsDeleteCount += numSuccesses;
+                    updateStats.mContactsDeleteSucceededCount += numSuccesses;
                     updateStats.mContactsDeleteFailedCount += numFailures;
                     if (firstFailure != null) {
                         Log.w(TAG, "Failed to delete "
diff --git a/service/java/com/android/server/appsearch/contactsindexer/ContactsIndexerConfig.java b/service/java/com/android/server/appsearch/contactsindexer/ContactsIndexerConfig.java
index 41baf07..8089c7d 100644
--- a/service/java/com/android/server/appsearch/contactsindexer/ContactsIndexerConfig.java
+++ b/service/java/com/android/server/appsearch/contactsindexer/ContactsIndexerConfig.java
@@ -26,24 +26,57 @@
  * @hide
  */
 public class ContactsIndexerConfig {
-    private static final String CONTACTS_INDEXER_ENABLED = "contacts_indexer_enabled";
-    private static final String CONTACTS_INSTANT_INDEXING_LIMIT = "contacts_instant_indexing_limit";
-    private static final String CONTACTS_FULL_UPDATE_INTERVAL_MILLIS
+    // LIMIT of -1 means no upper bound (see https://www.sqlite.org/lang_select.html)
+    public static final int UPDATE_LIMIT_NONE = -1;
+
+    private static final String KEY_CONTACTS_INSTANT_INDEXING_LIMIT =
+            "contacts_instant_indexing_limit";
+    public static final String KEY_CONTACTS_INDEXER_ENABLED = "contacts_indexer_enabled";
+    public static final String KEY_CONTACTS_FULL_UPDATE_INTERVAL_MILLIS
             = "contacts_full_update_interval_millis";
+    public static final String KEY_CONTACTS_FULL_UPDATE_LIMIT =
+            "contacts_indexer_full_update_limit";
+    public static final String KEY_CONTACTS_DELTA_UPDATE_LIMIT =
+            "contacts_indexer_delta_update_limit";
 
     public static boolean isContactsIndexerEnabled() {
-        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_APPSEARCH, CONTACTS_INDEXER_ENABLED,
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_APPSEARCH,
+                KEY_CONTACTS_INDEXER_ENABLED,
                 /*defaultValue=*/ true);
     }
 
     public static int getContactsInstantIndexingLimit() {
         return DeviceConfig.getInt(DeviceConfig.NAMESPACE_APPSEARCH,
-                CONTACTS_INSTANT_INDEXING_LIMIT, /*defaultValue=*/ 1000);
+                KEY_CONTACTS_INSTANT_INDEXING_LIMIT, /*defaultValue=*/ 1000);
     }
 
     public static long getContactsFullUpdateIntervalMillis() {
         return DeviceConfig.getLong(DeviceConfig.NAMESPACE_APPSEARCH,
-                CONTACTS_FULL_UPDATE_INTERVAL_MILLIS,
+                KEY_CONTACTS_FULL_UPDATE_INTERVAL_MILLIS,
                 /*defaultValue=*/ TimeUnit.DAYS.toMillis(30));
     }
+
+    /**
+     * Returns the maximum number of CP2 contacts indexed during a full update.
+     *
+     * <p>The value will be used as a LIMIT for querying CP2 during full update.
+     */
+    public static int getContactsFullUpdateLimit() {
+        return DeviceConfig.getInt(DeviceConfig.NAMESPACE_APPSEARCH,
+                KEY_CONTACTS_FULL_UPDATE_LIMIT,
+                /*defaultValue=*/ 10_000);
+    }
+
+    /**
+     * Returns the maximum number of CP2 contacts indexed during a delta update.
+     *
+     * <p>The value will be used as a LIMIT for querying CP2 during the delta update.
+     */
+    public static int getContactsDeltaUpdateLimit() {
+        // TODO(b/227419499) Based on the metrics, we can tweak this number. Right now it is same
+        //  as the instant indexing limit, which is 1,000. From our stats in GMSCore, 95th
+        //  percentile for number of contacts on the device is around 2000 contacts.
+        return DeviceConfig.getInt(DeviceConfig.NAMESPACE_APPSEARCH,
+                KEY_CONTACTS_DELTA_UPDATE_LIMIT, /*defaultValue=*/ 1000);
+    }
 }
diff --git a/service/java/com/android/server/appsearch/contactsindexer/ContactsIndexerImpl.java b/service/java/com/android/server/appsearch/contactsindexer/ContactsIndexerImpl.java
index 61d5c79..da1f6f1 100644
--- a/service/java/com/android/server/appsearch/contactsindexer/ContactsIndexerImpl.java
+++ b/service/java/com/android/server/appsearch/contactsindexer/ContactsIndexerImpl.java
@@ -139,6 +139,7 @@
         CompletableFuture<Void> batchRemoveFuture = CompletableFuture.completedFuture(null);
         int startIndex = 0;
         int unWantedSize = unWantedIds.size();
+        updateStats.mTotalContactsToBeDeleted += unWantedSize;
         while (startIndex < unWantedSize) {
             int endIndex = Math.min(startIndex + NUM_DELETED_CONTACTS_PER_BATCH_FOR_APPSEARCH,
                     unWantedSize);
@@ -162,6 +163,7 @@
         int startIndex = 0;
         int wantedIdListSize = wantedContactIds.size();
         CompletableFuture<Void> future = CompletableFuture.completedFuture(null);
+        updateStats.mTotalContactsToBeUpdated += wantedIdListSize;
 
         //
         // Batch reading the contacts from CP2, and index the created documents to AppSearch
@@ -171,9 +173,6 @@
                     wantedIdListSize);
             Collection<String> currentContactIds = wantedContactIds.subList(startIndex, endIndex);
             // Read NUM_CONTACTS_PER_BATCH contacts every time from CP2.
-            // TODO(b/203605504) log the total latency for the query once we have the logger
-            //  configured. Since a big "IN" might cause a slowdown. Also we can make
-            //  NUM_CONTACTS_PER_BATCH configurable.
             String selection = ContactsContract.Data.CONTACT_ID + " IN (" + TextUtils.join(
                     /*delimiter=*/ ",", currentContactIds) + ")";
             startIndex = endIndex;
@@ -202,7 +201,6 @@
                 // The ContactsProvider sometimes propagates RuntimeExceptions to us
                 // for when their database fails to open. Behave as if there was no
                 // ContactsProvider, and flag that we were not successful.
-                // TODO(b/203605504) log the error once we have the logger configured.
                 Log.e(TAG, "ContentResolver.query threw an exception.", e);
                 updateStats.mUpdateStatuses.add(AppSearchResult.RESULT_INTERNAL_ERROR);
                 return CompletableFuture.failedFuture(e);
@@ -213,6 +211,15 @@
     }
 
     /**
+     * Cancels the {@link #updatePersonCorpusAsync(List, List, ContactsUpdateStats)} in case of
+     * error. This will clean up the states in the batcher so it can get ready for the following
+     * updates.
+     */
+    void cancelUpdatePersonCorpus() {
+        mBatcher.clearBatchedContacts();
+    }
+
+    /**
      * Reads through cursor, converts the contacts to AppSearch documents, and indexes the
      * documents into AppSearch.
      *
@@ -366,6 +373,11 @@
             return mPendingIndexContacts.size();
         }
 
+        void clearBatchedContacts() {
+            mPendingDiffContactBuilders.clear();
+            mPendingIndexContacts.clear();
+        }
+
         public void add(@NonNull PersonBuilderHelper builderHelper,
                 @NonNull ContactsUpdateStats updateStats) {
             Objects.requireNonNull(builderHelper);
@@ -440,11 +452,11 @@
                                             contactsToBeIndexed.add(person);
                                         } else {
                                             // Fingerprint is same. So this update is skipped.
-                                            ++updateStats.mContactsSkippedCount;
+                                            ++updateStats.mContactsUpdateSkippedCount;
                                         }
                                     } else {
                                         // New contact.
-                                        ++updateStats.mContactsInsertedCount;
+                                        ++updateStats.mNewContactsToBeUpdated;
                                         contactsToBeIndexed.add(builderHelper.buildPerson());
                                     }
                                 }
@@ -457,10 +469,13 @@
         /** Flushes the contacts batched in {@link #mPendingIndexContacts} to AppSearch. */
         private CompletableFuture<Void> flushPendingIndexAsync(
                 @NonNull ContactsUpdateStats updateStats) {
-            CompletableFuture<Void> future =
-                    mAppSearchHelper.indexContactsAsync(mPendingIndexContacts, updateStats);
-            mPendingIndexContacts.clear();
-            return future;
+            if (mPendingIndexContacts.size() > 0) {
+                CompletableFuture<Void> future =
+                        mAppSearchHelper.indexContactsAsync(mPendingIndexContacts, updateStats);
+                mPendingIndexContacts.clear();
+                return future;
+            }
+            return CompletableFuture.completedFuture(null);
         }
     }
 }
diff --git a/service/java/com/android/server/appsearch/contactsindexer/ContactsIndexerManagerService.java b/service/java/com/android/server/appsearch/contactsindexer/ContactsIndexerManagerService.java
index 75032ad..f83133a 100644
--- a/service/java/com/android/server/appsearch/contactsindexer/ContactsIndexerManagerService.java
+++ b/service/java/com/android/server/appsearch/contactsindexer/ContactsIndexerManagerService.java
@@ -33,6 +33,7 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalManagerRegistry;
 import com.android.server.SystemService;
 import com.android.server.appsearch.AppSearchModule;
@@ -199,7 +200,8 @@
     }
 
     class LocalService {
-        void doFullUpdateForUser(@UserIdInt int userId, CancellationSignal signal) {
+        void doFullUpdateForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) {
+            Objects.requireNonNull(signal);
             synchronized (mContactsIndexersLocked) {
                 ContactsIndexerUserInstance instance = mContactsIndexersLocked.get(userId);
                 if (instance != null) {
diff --git a/service/java/com/android/server/appsearch/contactsindexer/ContactsIndexerUserInstance.java b/service/java/com/android/server/appsearch/contactsindexer/ContactsIndexerUserInstance.java
index 533e9f0..29fb34d 100644
--- a/service/java/com/android/server/appsearch/contactsindexer/ContactsIndexerUserInstance.java
+++ b/service/java/com/android/server/appsearch/contactsindexer/ContactsIndexerUserInstance.java
@@ -17,6 +17,7 @@
 package com.android.server.appsearch.contactsindexer;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.appsearch.AppSearchResult;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -63,6 +64,7 @@
     // Used for batching/throttling the contact change notification so we won't schedule too many
     // delta updates.
     private final AtomicBoolean mDeltaUpdatePending = new AtomicBoolean(/*initialValue=*/ false);
+
     private final AppSearchHelper mAppSearchHelper;
     private final ContactsIndexerImpl mContactsIndexerImpl;
 
@@ -92,7 +94,8 @@
     @VisibleForTesting
     @NonNull
     /*package*/ static ContactsIndexerUserInstance createInstance(@NonNull Context context,
-            @NonNull File contactsDir, @NonNull ExecutorService executorService) {
+            @NonNull File contactsDir,
+            @NonNull ExecutorService executorService) {
         Objects.requireNonNull(context);
         Objects.requireNonNull(contactsDir);
         Objects.requireNonNull(executorService);
@@ -109,11 +112,11 @@
     /**
      * Constructs a {@link ContactsIndexerUserInstance}.
      *
-     * @param context                 Context object passed from
-     *                                {@link ContactsIndexerManagerService}
-     * @param dataDir                 data directory for storing contacts indexer state.
-     * @param singleThreadedExecutor  an {@link ExecutorService} with at most one thread to ensure
-     *                                the thread safety of this class.
+     * @param context                Context object passed from
+     *                               {@link ContactsIndexerManagerService}
+     * @param dataDir                data directory for storing contacts indexer state.
+     * @param singleThreadedExecutor an {@link ExecutorService} with at most one thread to ensure
+     *                               the thread safety of this class.
      */
     private ContactsIndexerUserInstance(@NonNull Context context, @NonNull File dataDir,
             @NonNull AppSearchHelper appSearchHelper,
@@ -192,9 +195,7 @@
      *
      * @param signal Used to indicate if the full update task should be cancelled.
      */
-    public void doFullUpdateAsync(@NonNull CancellationSignal signal) {
-        Objects.requireNonNull(signal);
-        // TODO(b/222126568): log stats
+    public void doFullUpdateAsync(@Nullable CancellationSignal signal) {
         mSingleThreadedExecutor.execute(() -> {
             ContactsUpdateStats updateStats = new ContactsUpdateStats();
             doFullUpdateInternalAsync(signal, updateStats);
@@ -203,18 +204,19 @@
 
     @VisibleForTesting
     CompletableFuture<Void> doFullUpdateInternalAsync(
-            @NonNull CancellationSignal signal, @NonNull ContactsUpdateStats updateStats) {
+            @Nullable CancellationSignal signal, @NonNull ContactsUpdateStats updateStats) {
         // TODO(b/203605504): handle cancellation signal to abort the job.
         long currentTimeMillis = System.currentTimeMillis();
         updateStats.mUpdateType = ContactsUpdateStats.FULL_UPDATE;
-        updateStats.mUpdateStartTimeMillis = currentTimeMillis;
+        updateStats.mUpdateAndDeleteStartTimeMillis = currentTimeMillis;
 
         List<String> cp2ContactIds = new ArrayList<>();
         // Get a list of all contact IDs from CP2. Ignore the return value which denotes the
         // most recent updated timestamp.
         // TODO(b/203605504): reconsider whether the most recent
         //  updated and deleted timestamps are useful.
-        ContactsProviderUtil.getUpdatedContactIds(mContext, /*sinceFilter=*/ 0, cp2ContactIds,
+        ContactsProviderUtil.getUpdatedContactIds(mContext, /*sinceFilter=*/ 0,
+                ContactsIndexerConfig.getContactsFullUpdateLimit(), cp2ContactIds,
                 updateStats);
         return mAppSearchHelper.getAllContactIdsAsync()
                 .thenCompose(appsearchContactIds -> {
@@ -230,6 +232,8 @@
                 }).handle((x, t) -> {
                     if (t != null) {
                         Log.w(TAG, "Failed to perform full update", t);
+                        // Just clear all the remaining contacts in case of error.
+                        mContactsIndexerImpl.cancelUpdatePersonCorpus();
                         if (updateStats.mUpdateStatuses.isEmpty()
                                 && updateStats.mDeleteStatuses.isEmpty()) {
                             // Somehow this error is not reflected in the stats, and
@@ -282,9 +286,10 @@
                 ContactsUpdateStats updateStats = new ContactsUpdateStats();
                 // TODO(b/226489369): apply instant indexing limit on CP2 changes also?
                 // TODO(b/222126568): refactor doDeltaUpdateAsync() to return a future value of
-                // ContactsUpdateStats so that it can be checked and logged here, instead of the
-                // placeholder exceptionally() block that only logs to the console.
-                doDeltaUpdateAsync(/*indexingLimit=*/ -1, updateStats).exceptionally(t -> {
+                //  ContactsUpdateStats so that it can be checked and logged here, instead of the
+                //  placeholder exceptionally() block that only logs to the console.
+                doDeltaUpdateAsync(ContactsIndexerConfig.getContactsDeltaUpdateLimit(),
+                        updateStats).exceptionally(t -> {
                     Log.d(TAG, "Failed to index CP2 change", t);
                     return null;
                 });
@@ -310,7 +315,7 @@
         // flag is reset.
         mDeltaUpdatePending.set(false);
         updateStats.mUpdateType = ContactsUpdateStats.DELTA_UPDATE;
-        updateStats.mUpdateStartTimeMillis = System.currentTimeMillis();
+        updateStats.mUpdateAndDeleteStartTimeMillis = System.currentTimeMillis();
         long lastDeltaUpdateTimestampMillis = mSettings.getLastDeltaUpdateTimestampMillis();
         long lastDeltaDeleteTimestampMillis = mSettings.getLastDeltaDeleteTimestampMillis();
         Log.d(TAG, "previous timestamps --"
@@ -337,6 +342,8 @@
                 .handle((x, t) -> {
                     if (t != null) {
                         Log.w(TAG, "Failed to perform delta update", t);
+                        // Just clear all the remaining contacts in case of error.
+                        mContactsIndexerImpl.cancelUpdatePersonCorpus();
                         if (updateStats.mUpdateStatuses.isEmpty()
                                 && updateStats.mDeleteStatuses.isEmpty()) {
                             // Somehow this error is not reflected in the stats, and
@@ -350,7 +357,7 @@
                     Log.d(TAG, "updated timestamps --"
                             + " lastDeltaUpdateTimestampMillis: "
                             + mostRecentContactLastUpdateTimestampMillis
-                            + " lastDeltaDeleeteTimestampMillis: "
+                            + " lastDeltaDeleteTimestampMillis: "
                             + mostRecentContactDeletedTimestampMillis);
                     mSettings.setLastDeltaUpdateTimestampMillis(
                             mostRecentContactLastUpdateTimestampMillis);
@@ -358,6 +365,17 @@
                             mostRecentContactDeletedTimestampMillis);
                     persistSettings();
                     logStats(updateStats);
+                    if (updateStats.mUpdateStatuses.contains(AppSearchResult.RESULT_OUT_OF_SPACE)) {
+                        // Some indexing failed due to OUT_OF_SPACE from AppSearch. We can simply
+                        // schedule a full update so we can trim the Person corpus in AppSearch
+                        // to make some room for delta update. We need to monitor the failure
+                        // count and reasons for indexing during full update to see if that limit
+                        // (10,000) is too big right now, considering we are sharing this limit
+                        // with any AppSearch clients, e.g. ShortcutManager, in the system server.
+                        ContactsIndexerMaintenanceService.scheduleFullUpdateJob(mContext,
+                                mContext.getUser().getIdentifier());
+                    }
+
                     return null;
                 });
     }
@@ -366,7 +384,7 @@
     private void logStats(@NonNull ContactsUpdateStats updateStats) {
         int totalUpdateLatency =
                 (int) (System.currentTimeMillis()
-                        - updateStats.mUpdateStartTimeMillis);
+                        - updateStats.mUpdateAndDeleteStartTimeMillis);
         // Finalize status code for update and delete.
         if (updateStats.mUpdateStatuses.isEmpty()) {
             // SUCCESS if no error found.
@@ -376,6 +394,17 @@
             // SUCCESS if no error found.
             updateStats.mDeleteStatuses.add(AppSearchResult.RESULT_OK);
         }
+
+        // Get the accurate count for failed cases. The current failed count doesn't include
+        // the contacts skipped due to failures in previous batches. Once a batch fails, all the
+        // following batches will be skipped. The contacts in those batches should be counted as
+        // failure as well.
+        updateStats.mContactsUpdateFailedCount =
+                updateStats.mTotalContactsToBeUpdated - updateStats.mContactsUpdateSucceededCount
+                        - updateStats.mContactsUpdateSkippedCount;
+        updateStats.mContactsDeleteFailedCount =
+                updateStats.mTotalContactsToBeDeleted - updateStats.mContactsDeleteSucceededCount;
+
         int[] updateStatusArr = new int[updateStats.mUpdateStatuses.size()];
         int[] deleteStatusArr = new int[updateStats.mDeleteStatuses.size()];
         int updateIdx = 0;
@@ -394,13 +423,12 @@
                 totalUpdateLatency,
                 updateStatusArr,
                 deleteStatusArr,
-                updateStats.mContactsInsertedCount,
-                updateStats.mContactsUpdateCount,
-                updateStats.mContactsDeleteCount,
-                updateStats.mContactsSkippedCount,
+                updateStats.mNewContactsToBeUpdated,
+                updateStats.mContactsUpdateSucceededCount,
+                updateStats.mContactsDeleteSucceededCount,
+                updateStats.mContactsUpdateSkippedCount,
                 updateStats.mContactsUpdateFailedCount,
                 updateStats.mContactsDeleteFailedCount);
-
     }
 
     /**
diff --git a/service/java/com/android/server/appsearch/contactsindexer/ContactsProviderUtil.java b/service/java/com/android/server/appsearch/contactsindexer/ContactsProviderUtil.java
index ed154df..ff32bf1 100644
--- a/service/java/com/android/server/appsearch/contactsindexer/ContactsProviderUtil.java
+++ b/service/java/com/android/server/appsearch/contactsindexer/ContactsProviderUtil.java
@@ -126,25 +126,15 @@
     }
 
     /**
-     * Gets the ids for updated contacts from certain timestamp.
+     * Returns a list of IDs, within given limit, of contacts updated since given timestamp.
      *
      * @param sinceFilter timestamp (milliseconds since epoch) from which ids of recently updated
      *                    contacts should be returned.
      * @param contactIds  the Set passed in to hold the recently updated contacts.
+     * @param limit       the maximum number of contacts fetched from CP2. No limit will be set if
+     *                    the value is {@link ContactsIndexerConfig#UPDATE_LIMIT_NONE}.
      * @return the timestamp for the contact most recently updated.
      */
-    public static long getUpdatedContactIds(@NonNull Context context, long sinceFilter,
-            @NonNull List<String> contactIds, @Nullable ContactsUpdateStats updateStats) {
-        // LIMIT of -1 means no upper bound (see https://www.sqlite.org/lang_select.html)
-        return getUpdatedContactIds(context, sinceFilter, /*limit=*/-1, contactIds,
-                updateStats);
-    }
-
-    /**
-     * Returns a list of IDs, within given limit, of contacts updated since given timestamp.
-     *
-     * <p>List of contact IDs are returned in the order of increasing contact last update timestamp.
-     */
     public static long getUpdatedContactIds(@NonNull Context context, long sinceFilter, int limit,
             @NonNull List<String> contactIds, @Nullable ContactsUpdateStats updateStats) {
         Objects.requireNonNull(context);
@@ -156,31 +146,37 @@
         Uri.Builder contactsUriBuilder = Contacts.CONTENT_URI.buildUpon().appendQueryParameter(
                 ContactsContract.DIRECTORY_PARAM_KEY,
                 String.valueOf(ContactsContract.Directory.DEFAULT));
-        if (limit > 0) {
+        String orderBy = null;
+        if (limit >= 0) {
             contactsUriBuilder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
                     String.valueOf(limit));
+            orderBy = UPDATE_ORDER_BY;
         }
         try (Cursor cursor = context.getContentResolver().query(
                 contactsUriBuilder.build(),
                 UPDATE_SELECTION,
                 UPDATE_SINCE, selectionArgs,
-                UPDATE_ORDER_BY)) {
+                orderBy)) {
             if (cursor == null) {
                 Log.w(TAG, "Failed to get a list of contacts updated since " + sinceFilter);
                 return newTimestamp;
             }
 
             int contactIdIndex = cursor.getColumnIndex(Contacts._ID);
-            int timestampIndex = cursor.getColumnIndex(Contacts.CONTACT_LAST_UPDATED_TIMESTAMP);
+            int timestampIndex = cursor.getColumnIndex(
+                    Contacts.CONTACT_LAST_UPDATED_TIMESTAMP);
             int numContacts = 0;
             while (cursor.moveToNext()) {
+                // Just in case the LIMIT parameter doesn't work in the query to CP2.
+                if (limit >= 0 && numContacts >= limit) {
+                    break;
+                }
+
                 long contactId = cursor.getLong(contactIdIndex);
                 contactIds.add(String.valueOf(contactId));
                 numContacts++;
                 newTimestamp = Math.max(newTimestamp, cursor.getLong(timestampIndex));
             }
-            // Reverse the IDs in the list to be in increasing contact last updated order.
-            Collections.reverse(contactIds);
 
             Log.v(TAG, "Returning " + numContacts + " updated contacts since " + sinceFilter);
         } catch (SecurityException |
diff --git a/service/java/com/android/server/appsearch/contactsindexer/ContactsUpdateStats.java b/service/java/com/android/server/appsearch/contactsindexer/ContactsUpdateStats.java
index 3cab94e..6557183 100644
--- a/service/java/com/android/server/appsearch/contactsindexer/ContactsUpdateStats.java
+++ b/service/java/com/android/server/appsearch/contactsindexer/ContactsUpdateStats.java
@@ -70,34 +70,53 @@
     @AppSearchResult.ResultCode
     Set<Integer> mDeleteStatuses = new ArraySet<>();
 
-    long mUpdateStartTimeMillis;
+    // Start time in millis for update and delete.
+    long mUpdateAndDeleteStartTimeMillis;
 
-    // # of contacts failed to be inserted or updated.
+
+    //
+    // Update for both old and new contacts(a.k.a insertion).
+    //
+    // # of old and new contacts failed to be updated.
     int mContactsUpdateFailedCount;
-    // # of contacts failed to be deleted.
+    // # of old and new contacts succeeds to be updated.
+    int mContactsUpdateSucceededCount;
+    // # of contacts update skipped due to NO significant change during the update.
+    int mContactsUpdateSkippedCount;
+    // Total # of old and new contacts to be updated.
+    // It should equal to
+    // mContactsUpdateFailedCount + mContactsUpdateSucceededCount + mContactsUpdateSkippedCount
+    int mTotalContactsToBeUpdated;
+    // Among the succeeded and failed contacts updates, how many of them are for the new contacts
+    // currently NOT available in AppSearch.
+    int mNewContactsToBeUpdated;
+
+    //
+    // Deletion for old documents.
+    //
+    // # of old contacts failed to be deleted.
     int mContactsDeleteFailedCount;
-
-    // # of new contacts to be inserted
-    int mContactsInsertedCount;
-    // # of contacts skipped due to no significant change
-    int mContactsSkippedCount;
-    // # of contacts successfully inserted or updated.
-    int mContactsUpdateCount;
-
-    // # of contacts deleted.
-    int mContactsDeleteCount;
+    // # of old contacts succeeds to be deleted.
+    int mContactsDeleteSucceededCount;
+    // Total # of old contacts to be deleted. It should equal to
+    // mContactsDeleteFailedCount + mContactsDeleteSucceededCount
+    int mTotalContactsToBeDeleted;
 
     public void clear() {
         mUpdateType = UNKNOWN_UPDATE_TYPE;
         mUpdateStatuses.clear();
         mDeleteStatuses.clear();
-        mUpdateStartTimeMillis = 0;
+        mUpdateAndDeleteStartTimeMillis = 0;
+        // Update for old and new contacts
         mContactsUpdateFailedCount = 0;
+        mContactsUpdateSucceededCount = 0;
+        mContactsUpdateSkippedCount = 0;
+        mNewContactsToBeUpdated = 0;
+        mTotalContactsToBeUpdated = 0;
+        // delete for old contacts
         mContactsDeleteFailedCount = 0;
-        mContactsInsertedCount = 0;
-        mContactsSkippedCount = 0;
-        mContactsUpdateCount = 0;
-        mContactsDeleteCount = 0;
+        mContactsDeleteSucceededCount = 0;
+        mTotalContactsToBeDeleted = 0;
     }
 
     @NonNull
@@ -105,12 +124,14 @@
         return "UpdateType: " + mUpdateType
                 + ", UpdateStatus: " + mUpdateStatuses.toString()
                 + ", DeleteStatus: " + mDeleteStatuses.toString()
-                + ", UpdateStartTimeMillis: " + mUpdateStartTimeMillis
-                + ", UpdateFailedCount: " + mContactsUpdateFailedCount
-                + ", deleteFailedCount: " + mContactsDeleteFailedCount
-                + ", ContactsInsertedCount: " + mContactsInsertedCount
-                + ", ContactsSkippedCount: " + mContactsSkippedCount
-                + ", ContactsUpdateCount: " + mContactsUpdateCount
-                + ", ContactsDeleteCount: " + mContactsDeleteCount;
+                + ", UpdateAndDeleteStartTimeMillis: " + mUpdateAndDeleteStartTimeMillis
+                + ", ContactsUpdateFailedCount: " + mContactsUpdateFailedCount
+                + ", ContactsUpdateSucceededCount: " + mContactsUpdateSucceededCount
+                + ", NewContactsToBeUpdated: " + mNewContactsToBeUpdated
+                + ", ContactsUpdateSkippedCount: " + mContactsUpdateSkippedCount
+                + ", TotalContactsToBeUpdated: " + mTotalContactsToBeUpdated
+                + ", ContactsDeleteFailedCount: " + mContactsDeleteFailedCount
+                + ", ContactsDeleteSucceededCount: " + mContactsDeleteSucceededCount
+                + ", TotalContactsToBeDeleted: " + mTotalContactsToBeDeleted;
     }
 }
\ No newline at end of file
diff --git a/service/java/com/android/server/appsearch/contactsindexer/PersonBuilderHelper.java b/service/java/com/android/server/appsearch/contactsindexer/PersonBuilderHelper.java
index 0cddb53..73102c5 100644
--- a/service/java/com/android/server/appsearch/contactsindexer/PersonBuilderHelper.java
+++ b/service/java/com/android/server/appsearch/contactsindexer/PersonBuilderHelper.java
@@ -42,8 +42,6 @@
  *
  * @hide
  */
-// TODO(b/203605504) We can also make it only generates a list of contact points. And move the
-//  building of a Person out to the caller.
 public final class PersonBuilderHelper {
     static final String TAG = "PersonBuilderHelper";
     static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
diff --git a/testing/contactsindexertests/src/com/android/server/appsearch/contactsindexer/ContactsIndexerImplTest.java b/testing/contactsindexertests/src/com/android/server/appsearch/contactsindexer/ContactsIndexerImplTest.java
index 4f267b0..64dad40 100644
--- a/testing/contactsindexertests/src/com/android/server/appsearch/contactsindexer/ContactsIndexerImplTest.java
+++ b/testing/contactsindexertests/src/com/android/server/appsearch/contactsindexer/ContactsIndexerImplTest.java
@@ -97,7 +97,8 @@
         List<String> unWantedContactIds = new ArrayList<>();
 
         lastUpdatedTimestamp = ContactsProviderUtil.getUpdatedContactIds(mContext,
-                lastUpdatedTimestamp, wantedContactIds, /*stats=*/ null);
+                lastUpdatedTimestamp, ContactsIndexerConfig.UPDATE_LIMIT_NONE,
+                wantedContactIds, /*stats=*/ null);
         lastDeletedTimestamp = ContactsProviderUtil.getDeletedContactIds(mContext,
                 lastDeletedTimestamp, unWantedContactIds, /*stats=*/ null);
         indexerImpl.updatePersonCorpusAsync(wantedContactIds, unWantedContactIds,
diff --git a/testing/contactsindexertests/src/com/android/server/appsearch/contactsindexer/ContactsIndexerUserInstanceTest.java b/testing/contactsindexertests/src/com/android/server/appsearch/contactsindexer/ContactsIndexerUserInstanceTest.java
index 75b4992..a8139b1 100644
--- a/testing/contactsindexertests/src/com/android/server/appsearch/contactsindexer/ContactsIndexerUserInstanceTest.java
+++ b/testing/contactsindexertests/src/com/android/server/appsearch/contactsindexer/ContactsIndexerUserInstanceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.appsearch.contactsindexer;
 
+import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -23,12 +25,19 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
+import android.app.UiAutomation;
 import android.app.appsearch.AppSearchManager;
 import android.app.appsearch.AppSearchResult;
 import android.app.appsearch.AppSearchSessionShim;
+import android.app.appsearch.GlobalSearchSessionShim;
 import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.observer.DocumentChangeInfo;
+import android.app.appsearch.observer.ObserverCallback;
+import android.app.appsearch.observer.ObserverSpec;
+import android.app.appsearch.observer.SchemaChangeInfo;
 import android.app.appsearch.testutil.AppSearchSessionShimImpl;
+import android.app.appsearch.testutil.GlobalSearchSessionShimImpl;
 import android.app.job.JobScheduler;
 import android.content.ContentResolver;
 import android.content.ContentUris;
@@ -37,11 +46,14 @@
 import android.os.CancellationSignal;
 import android.os.PersistableBundle;
 import android.provider.ContactsContract;
+import android.provider.DeviceConfig;
 import android.test.ProviderTestCase2;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.server.appsearch.FrameworkAppSearchConfig;
 import com.android.server.appsearch.contactsindexer.appsearchtypes.Person;
 
 import org.junit.After;
@@ -56,8 +68,10 @@
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
 import javax.annotation.Nullable;
@@ -72,7 +86,6 @@
     public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
 
     private final ExecutorService mSingleThreadedExecutor = Executors.newSingleThreadExecutor();
-
     private ContextWrapper mContextWrapper;
     private File mContactsDir;
     private File mSettingsFile;
@@ -92,11 +105,9 @@
         // Setup the file path to the persisted data
         mContactsDir = new File(mTemporaryFolder.newFolder(), "appsearch/contacts");
         mSettingsFile = new File(mContactsDir, ContactsIndexerSettings.SETTINGS_FILE_NAME);
-
         mContextWrapper = new ContextWrapper(ApplicationProvider.getApplicationContext());
         mContextWrapper.setContentResolver(getMockContentResolver());
         mContext = mContextWrapper;
-
         mSpecForQueryAllContacts = new SearchSpec.Builder().addFilterSchemas(
                 Person.SCHEMA_TYPE).addProjection(Person.SCHEMA_TYPE,
                 Arrays.asList(Person.PERSON_PROPERTY_NAME))
@@ -105,7 +116,6 @@
 
         mInstance = ContactsIndexerUserInstance.createInstance(mContext, mContactsDir,
                 mSingleThreadedExecutor);
-
         mUpdateStats = new ContactsUpdateStats();
     }
 
@@ -222,10 +232,12 @@
         assertThat(mUpdateStats.mContactsUpdateFailedCount).isEqualTo(0);
         // NOT_FOUND does not count as error.
         assertThat(mUpdateStats.mContactsDeleteFailedCount).isEqualTo(0);
-        assertThat(mUpdateStats.mContactsInsertedCount).isEqualTo(250);
-        assertThat(mUpdateStats.mContactsSkippedCount).isEqualTo(0);
-        assertThat(mUpdateStats.mContactsUpdateCount).isEqualTo(250);
-        assertThat(mUpdateStats.mContactsDeleteCount).isEqualTo(0);
+        assertThat(mUpdateStats.mNewContactsToBeUpdated).isEqualTo(250);
+        assertThat(mUpdateStats.mContactsUpdateSkippedCount).isEqualTo(0);
+        assertThat(mUpdateStats.mTotalContactsToBeUpdated).isEqualTo(250);
+        assertThat(mUpdateStats.mContactsUpdateSucceededCount).isEqualTo(250);
+        assertThat(mUpdateStats.mTotalContactsToBeDeleted).isEqualTo(0);
+        assertThat(mUpdateStats.mContactsDeleteSucceededCount).isEqualTo(0);
     }
 
     @Test
@@ -281,10 +293,23 @@
         assertThat(contactIds.size()).isEqualTo(6);
         assertThat(contactIds).containsNoneOf("2", "3", "5", "7");
 
-        // TODO(b/222126568): verify state using logged events instead
         PersistableBundle settingsBundle = ContactsIndexerSettings.readBundle(mSettingsFile);
         assertThat(settingsBundle.getLong(ContactsIndexerSettings.LAST_DELTA_UPDATE_TIMESTAMP_KEY))
                 .isAtLeast(timeBeforeDeltaChangeNotification);
+        // check stats
+        assertThat(mUpdateStats.mUpdateType).isEqualTo(ContactsUpdateStats.DELTA_UPDATE);
+        assertThat(mUpdateStats.mUpdateStatuses).hasSize(1);
+        assertThat(mUpdateStats.mUpdateStatuses).containsExactly(AppSearchResult.RESULT_OK);
+        assertThat(mUpdateStats.mDeleteStatuses).hasSize(1);
+        assertThat(mUpdateStats.mDeleteStatuses).containsExactly(AppSearchResult.RESULT_OK);
+        assertThat(mUpdateStats.mContactsUpdateFailedCount).isEqualTo(0);
+        assertThat(mUpdateStats.mContactsDeleteFailedCount).isEqualTo(0);
+        assertThat(mUpdateStats.mNewContactsToBeUpdated).isEqualTo(10);
+        assertThat(mUpdateStats.mContactsUpdateSkippedCount).isEqualTo(0);
+        assertThat(mUpdateStats.mTotalContactsToBeUpdated).isEqualTo(10);
+        assertThat(mUpdateStats.mContactsUpdateSucceededCount).isEqualTo(10);
+        assertThat(mUpdateStats.mTotalContactsToBeDeleted).isEqualTo(4);
+        assertThat(mUpdateStats.mContactsDeleteSucceededCount).isEqualTo(4);
     }
 
     @Test
@@ -330,12 +355,15 @@
         assertThat(mUpdateStats.mDeleteStatuses).hasSize(1);
         assertThat(mUpdateStats.mDeleteStatuses).containsExactly(AppSearchResult.RESULT_OK);
         assertThat(mUpdateStats.mContactsUpdateFailedCount).isEqualTo(0);
-        // NOT_FOUND does not count as error.
-        assertThat(mUpdateStats.mContactsDeleteFailedCount).isEqualTo(0);
-        assertThat(mUpdateStats.mContactsInsertedCount).isEqualTo(6);
-        assertThat(mUpdateStats.mContactsSkippedCount).isEqualTo(0);
-        assertThat(mUpdateStats.mContactsUpdateCount).isEqualTo(6);
-        assertThat(mUpdateStats.mContactsDeleteCount).isEqualTo(0);
+        // 4 contacts deleted in CP2, but we don't have those in AppSearch. So we will get
+        // NOT_FOUND. We don't treat the NOT_FOUND as failures, so the status code is still OK.
+        assertThat(mUpdateStats.mContactsDeleteFailedCount).isEqualTo(4);
+        assertThat(mUpdateStats.mNewContactsToBeUpdated).isEqualTo(6);
+        assertThat(mUpdateStats.mContactsUpdateSkippedCount).isEqualTo(0);
+        assertThat(mUpdateStats.mTotalContactsToBeUpdated).isEqualTo(6);
+        assertThat(mUpdateStats.mContactsUpdateSucceededCount).isEqualTo(6);
+        assertThat(mUpdateStats.mTotalContactsToBeDeleted).isEqualTo(4);
+        assertThat(mUpdateStats.mContactsDeleteSucceededCount).isEqualTo(0);
     }
 
     @Test
@@ -390,10 +418,83 @@
         assertThat(mUpdateStats.mContactsUpdateFailedCount).isEqualTo(0);
         // NOT_FOUND does not count as error.
         assertThat(mUpdateStats.mContactsDeleteFailedCount).isEqualTo(0);
-        assertThat(mUpdateStats.mContactsInsertedCount).isEqualTo(5);
-        assertThat(mUpdateStats.mContactsSkippedCount).isEqualTo(0);
-        assertThat(mUpdateStats.mContactsUpdateCount).isEqualTo(5);
-        assertThat(mUpdateStats.mContactsDeleteCount).isEqualTo(4);
+        assertThat(mUpdateStats.mNewContactsToBeUpdated).isEqualTo(5);
+        assertThat(mUpdateStats.mContactsUpdateSkippedCount).isEqualTo(0);
+        assertThat(mUpdateStats.mTotalContactsToBeUpdated).isEqualTo(5);
+        assertThat(mUpdateStats.mContactsUpdateSucceededCount).isEqualTo(5);
+        assertThat(mUpdateStats.mTotalContactsToBeDeleted).isEqualTo(4);
+        assertThat(mUpdateStats.mContactsDeleteSucceededCount).isEqualTo(4);
+    }
+
+    @Test
+    public void testDeltaUpdate_outOfSpaceError_fullUpdateScheduled() throws Exception {
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        int maxDocumentCountBeforeTest = FrameworkAppSearchConfig.getInstance(
+                mSingleThreadedExecutor).getCachedLimitConfigMaxDocumentCount();
+        try {
+            uiAutomation.adoptShellPermissionIdentity(WRITE_DEVICE_CONFIG);
+            int totalContactCount = 250;
+            int maxDocumentCount = 100;
+            // Override the configs in AppSearch. This is hard to be mocked since we are not testing
+            // AppSearch here.
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                    FrameworkAppSearchConfig.KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT,
+                    String.valueOf(maxDocumentCount), false);
+            // Cancel any existing jobs.
+            ContactsIndexerMaintenanceService.cancelFullUpdateJob(mContext, mContext.getUserId());
+
+            JobScheduler mockJobScheduler = mock(JobScheduler.class);
+            mContextWrapper.setJobScheduler(mockJobScheduler);
+
+            // We are trying to index 250 contacts, but our max documentCount is 100. So we would
+            // index 100 contacts, reach the limit, and trigger a full update.
+            CountDownLatch latch = new CountDownLatch(maxDocumentCount);
+            GlobalSearchSessionShim shim =
+                    GlobalSearchSessionShimImpl.createGlobalSearchSessionAsync(mContext).get();
+            ObserverCallback callback = new ObserverCallback() {
+                @Override
+                public void onSchemaChanged(SchemaChangeInfo changeInfo) {
+                    // Do nothing
+                }
+
+                @Override
+                public void onDocumentChanged(DocumentChangeInfo changeInfo) {
+                    for (int i = 0; i < changeInfo.getChangedDocumentIds().size(); i++) {
+                        latch.countDown();
+                    }
+                }
+            };
+            shim.registerObserverCallback(mContext.getPackageName(),
+                    new ObserverSpec.Builder().addFilterSchemas("builtin:Person").build(),
+                    mSingleThreadedExecutor,
+                    callback);
+
+            long timeBeforeDeltaChangeNotification = System.currentTimeMillis();
+            // Insert contacts to trigger delta update.
+            ContentResolver resolver = mContext.getContentResolver();
+            ContentValues dummyValues = new ContentValues();
+            for (int i = 0; i < totalContactCount; i++) {
+                resolver.insert(ContactsContract.Contacts.CONTENT_URI, dummyValues);
+            }
+
+            executeAndWaitForCompletion(
+                    mInstance.doDeltaUpdateAsync(ContactsIndexerConfig.UPDATE_LIMIT_NONE,
+                            mUpdateStats),
+                    mSingleThreadedExecutor);
+            latch.await(30L, TimeUnit.SECONDS);
+
+            // Verify the full update job is scheduled due to out_of_space.
+            verify(mockJobScheduler).schedule(any());
+            PersistableBundle settingsBundle = ContactsIndexerSettings.readBundle(mSettingsFile);
+            assertThat(
+                    settingsBundle.getLong(ContactsIndexerSettings.LAST_DELTA_UPDATE_TIMESTAMP_KEY))
+                    .isAtLeast(timeBeforeDeltaChangeNotification);
+        } finally {
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                    FrameworkAppSearchConfig.KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT,
+                    String.valueOf(maxDocumentCountBeforeTest), false);
+            uiAutomation.dropShellPermissionIdentity();
+        }
     }
 
     /**
@@ -403,17 +504,17 @@
      * executor, and wait for its execution. The second step is to wait for the completion of the
      * stage itself.
      */
-    private void executeAndWaitForCompletion(CompletionStage<Void> stage, ExecutorService executor)
+    private <T> T executeAndWaitForCompletion(CompletionStage<T> stage, ExecutorService executor)
             throws Exception {
-        AtomicReference<CompletableFuture<Void>> future = new AtomicReference<>(
+        AtomicReference<CompletableFuture<T>> future = new AtomicReference<>(
                 CompletableFuture.completedFuture(null));
         executor.submit(() -> {
             // Chain the given stage inside the runnable task so that it executes on the executor.
-            CompletableFuture<Void> chainedFuture = future.get().thenCompose(x -> stage);
+            CompletableFuture<T> chainedFuture = future.get().thenCompose(x -> stage);
             future.set(chainedFuture);
         }).get();
         // Wait for the task to complete on the executor, and wait for the stage to complete also.
-        future.get().get();
+        return future.get().get();
     }
 
     static final class ContextWrapper extends android.content.ContextWrapper {
diff --git a/testing/contactsindexertests/src/com/android/server/appsearch/contactsindexer/ContactsProviderUtilTest.java b/testing/contactsindexertests/src/com/android/server/appsearch/contactsindexer/ContactsProviderUtilTest.java
index bbe2fe0..93b1c07 100644
--- a/testing/contactsindexertests/src/com/android/server/appsearch/contactsindexer/ContactsProviderUtilTest.java
+++ b/testing/contactsindexertests/src/com/android/server/appsearch/contactsindexer/ContactsProviderUtilTest.java
@@ -68,7 +68,7 @@
 
         List<String> ids = new ArrayList<>();
         long lastUpdatedTime = ContactsProviderUtil.getUpdatedContactIds(mContext,
-                /*sinceFilter=*/ 0, ids, /*stats=*/ null);
+                /*sinceFilter=*/ 0, ContactsIndexerConfig.UPDATE_LIMIT_NONE, ids, /*stats=*/ null);
 
         assertThat(lastUpdatedTime).isEqualTo(
                 getProvider().getMostRecentContactUpdateTimestampMillis());
@@ -86,7 +86,7 @@
         List<String> ids = new ArrayList<>();
         long lastUpdatedTime = ContactsProviderUtil.getUpdatedContactIds(mContext,
                 /*sinceFilter=*/ getProvider().getMostRecentContactUpdateTimestampMillis(),
-                ids, /*stats=*/ null);
+                ContactsIndexerConfig.UPDATE_LIMIT_NONE, ids, /*stats=*/ null);
 
         assertThat(lastUpdatedTime).isEqualTo(
                 getProvider().getMostRecentContactUpdateTimestampMillis());
@@ -108,7 +108,8 @@
 
         List<String> ids = new ArrayList<>();
         long lastUpdatedTime = ContactsProviderUtil.getUpdatedContactIds(mContext,
-                /*sinceFilter=*/ firstUpdateTimestamp, ids, /*stats=*/ null);
+                /*sinceFilter=*/ firstUpdateTimestamp, ContactsIndexerConfig.UPDATE_LIMIT_NONE,
+                ids, /*stats=*/ null);
 
         assertThat(lastUpdatedTime).isEqualTo(
                 getProvider().getMostRecentContactUpdateTimestampMillis());