Implement AppSearch.deleteByTypes()

Bug: 147636343
Test: atest CtsAppSearchTestCases FrameworksCoreTests:android.app.appsearch FrameworksServicesTests:com.android.server.appsearch.impl
Change-Id: I053b33744c1cec8f307ca975314c3b9415d48b68
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
index 9ac5eff..75dc064 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
@@ -354,6 +354,27 @@
         return getFutureOrThrow(future);
     }
 
+    /**
+     * Deletes {@link android.app.appsearch.AppSearch.Document}s by schema type.
+     *
+     * <p>You should not call this method directly; instead, use the
+     * {@code AppSearch#deleteByType()} API provided by JetPack.
+     *
+     * @param schemaTypes Schema types whose documents to delete.
+     * @return An {@link AppSearchBatchResult} mapping each schema type to a {@code null} success if
+     *     deletion was successful, to a {@code null} failure if the type did not exist, or to a
+     *     {@code throwable} failure if deletion failed for another reason.
+     */
+    public AppSearchBatchResult<String, Void> deleteByTypes(@NonNull List<String> schemaTypes) {
+        AndroidFuture<AppSearchBatchResult> future = new AndroidFuture<>();
+        try {
+            mService.deleteByTypes(schemaTypes, future);
+        } catch (RemoteException e) {
+            future.completeExceptionally(e);
+        }
+        return getFutureOrThrow(future);
+    }
+
     /** Deletes all documents owned by the calling app. */
     public AppSearchResult<Void> deleteAll() {
         AndroidFuture<AppSearchResult> future = new AndroidFuture<>();
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
index c2d0f78..68de4f0 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
@@ -88,6 +88,21 @@
     void delete(in List<String> uris, in AndroidFuture<AppSearchBatchResult> callback);
 
     /**
+     * Deletes documents by schema type.
+     *
+     * @param schemaTypes The schema types of the documents to delete
+     * @param callback
+     *     {@link AndroidFuture}&lt;{@link AppSearchBatchResult}&lt;{@link String}, {@link Void}&gt;&gt;.
+     *     If the call fails to start, {@code callback} will be completed exceptionally. Otherwise,
+     *     {@code callback} will be completed with an
+     *     {@link AppSearchBatchResult}&lt;{@link String}, {@link Void}&gt;
+     *     where the keys are schema types. If a schema type doesn't exist, it will be reported as a
+     *     failure where the {@code throwable} is {@code null}.
+     */
+    void deleteByTypes(
+        in List<String> schemaTypes, in AndroidFuture<AppSearchBatchResult> callback);
+
+    /**
      * Deletes all documents belonging to the calling app.
      *
      * @param callback {@link AndroidFuture}&lt;{@link AppSearchResult}&lt;{@link Void}&gt;&gt;.
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 27c1419..16948b2 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -214,6 +214,41 @@
         }
 
         @Override
+        public void deleteByTypes(
+                List<String> schemaTypes, AndroidFuture<AppSearchBatchResult> callback) {
+            Preconditions.checkNotNull(schemaTypes);
+            Preconditions.checkNotNull(callback);
+            int callingUid = Binder.getCallingUidOrThrow();
+            int callingUserId = UserHandle.getUserId(callingUid);
+            long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
+                AppSearchBatchResult.Builder<String, Void> resultBuilder =
+                        new AppSearchBatchResult.Builder<>();
+                for (int i = 0; i < schemaTypes.size(); i++) {
+                    String schemaType = schemaTypes.get(i);
+                    try {
+                        if (!impl.deleteByType(callingUid, schemaType)) {
+                            resultBuilder.setFailure(
+                                    schemaType,
+                                    AppSearchResult.RESULT_NOT_FOUND,
+                                    /*errorMessage=*/ null);
+                        } else {
+                            resultBuilder.setSuccess(schemaType, /*value=*/ null);
+                        }
+                    } catch (Throwable t) {
+                        resultBuilder.setResult(schemaType, throwableToFailedResult(t));
+                    }
+                }
+                callback.complete(resultBuilder.build());
+            } catch (Throwable t) {
+                callback.completeExceptionally(t);
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        @Override
         public void deleteAll(@NonNull AndroidFuture<AppSearchResult> callback) {
             Preconditions.checkNotNull(callback);
             int callingUid = Binder.getCallingUidOrThrow();
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java
index a267e13..4358d20 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java
@@ -230,6 +230,13 @@
         return mFakeIcing.delete(uri);
     }
 
+    /** Deletes all documents having the given {@code schemaType}. */
+    public boolean deleteByType(int callingUid, @NonNull String schemaType) {
+        String typePrefix = getTypePrefix(callingUid);
+        String qualifiedType = typePrefix + schemaType;
+        return mFakeIcing.deleteByType(qualifiedType);
+    }
+
     /**
      * Deletes all documents owned by the calling app.
      *
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java
index 6703559..da15734 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java
@@ -157,6 +157,25 @@
         }
     }
 
+    /**
+     * Deletes all documents having the given type.
+     *
+     * @return true if any documents were deleted.
+     */
+    public boolean deleteByType(@NonNull String type) {
+        boolean deletedAny = false;
+        for (int i = 0; i < mDocStore.size(); i++) {
+            DocumentProto document = mDocStore.valueAt(i);
+            if (type.equals(document.getSchema())) {
+                mDocStore.removeAt(i);
+                mUriToDocIdMap.remove(document.getUri());
+                i--;
+                deletedAny = true;
+            }
+        }
+        return deletedAny;
+    }
+
     private void indexDocument(int docId, DocumentProto document) {
         for (PropertyProto property : document.getPropertiesList()) {
             for (String stringValue : property.getStringValuesList()) {