Refactor how AppSearchResult and AppSearchBatchResult are parcelled.

Mainline modules seem to be metalava'd differently, causing the CREATOR,
describeContents(), and other parcelling methods to become part of the
public API.

Additionally, AppSearchResult and AppSearchBatchResult were forked from
Jetpack with parcelling added, and manually maintained.

Additionally, the parcelling wouldn't work properly if the keys and
values are not themselves parcelable, but this was not clearly
documented or called out.

This CL fixes these problems by creating parcelable wrappers around the
upstream Jetpack AppSearchResult and AppSearchBatchResult, usable only
when they contain parcelable keys and values.

Bug: 146218515
Test: Presubmit
Change-Id: I828a22dc6dae3e46f5d167f1a9d12627ba15d9fd
diff --git a/framework/java/android/app/appsearch/AppSearchBatchResult.aidl b/framework/java/android/app/appsearch/AppSearchBatchResult.aidl
deleted file mode 100644
index 4686de8..0000000
--- a/framework/java/android/app/appsearch/AppSearchBatchResult.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright 2020, 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 android.app.appsearch;
-
-/** {@hide} */
-parcelable AppSearchBatchResult;
\ No newline at end of file
diff --git a/framework/java/android/app/appsearch/AppSearchManager.java b/framework/java/android/app/appsearch/AppSearchManager.java
index c4dd1a0..bc3c79f 100644
--- a/framework/java/android/app/appsearch/AppSearchManager.java
+++ b/framework/java/android/app/appsearch/AppSearchManager.java
@@ -18,6 +18,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.SystemService;
+import android.app.appsearch.aidl.IAppSearchManager;
 import android.content.Context;
 
 import com.android.internal.util.Preconditions;
diff --git a/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java b/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
index ec0533e..7dc527a 100644
--- a/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
+++ b/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
@@ -17,6 +17,7 @@
 
 import android.annotation.SystemApi;
 import android.app.SystemServiceRegistry;
+import android.app.appsearch.aidl.IAppSearchManager;
 import android.content.Context;
 
 /**
diff --git a/framework/java/android/app/appsearch/AppSearchMigrationHelper.java b/framework/java/android/app/appsearch/AppSearchMigrationHelper.java
index 0089c6d..353051c 100644
--- a/framework/java/android/app/appsearch/AppSearchMigrationHelper.java
+++ b/framework/java/android/app/appsearch/AppSearchMigrationHelper.java
@@ -23,6 +23,9 @@
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.annotation.WorkerThread;
+import android.app.appsearch.aidl.AppSearchResultParcel;
+import android.app.appsearch.aidl.IAppSearchManager;
+import android.app.appsearch.aidl.IAppSearchResultCallback;
 import android.app.appsearch.exceptions.AppSearchException;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -105,8 +108,8 @@
                     mUserId,
                     new IAppSearchResultCallback.Stub() {
                         @Override
-                        public void onResult(AppSearchResult result) {
-                            future.complete(result);
+                        public void onResult(AppSearchResultParcel resultParcel) {
+                            future.complete(resultParcel.getResult());
                         }
                     });
             AppSearchResult<Void> result = future.get();
@@ -145,8 +148,8 @@
             mService.putDocumentsFromFile(mPackageName, mDatabaseName, fileDescriptor, mUserId,
                     new IAppSearchResultCallback.Stub() {
                         @Override
-                        public void onResult(AppSearchResult result) {
-                            future.complete(result);
+                        public void onResult(AppSearchResultParcel resultParcel) {
+                            future.complete(resultParcel.getResult());
                         }
                     });
             AppSearchResult<List<Bundle>> result = future.get();
diff --git a/framework/java/android/app/appsearch/AppSearchSession.java b/framework/java/android/app/appsearch/AppSearchSession.java
index c112d0e..5910130 100644
--- a/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/framework/java/android/app/appsearch/AppSearchSession.java
@@ -19,6 +19,11 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.appsearch.aidl.AppSearchBatchResultParcel;
+import android.app.appsearch.aidl.AppSearchResultParcel;
+import android.app.appsearch.aidl.IAppSearchBatchResultCallback;
+import android.app.appsearch.aidl.IAppSearchManager;
+import android.app.appsearch.aidl.IAppSearchResultCallback;
 import android.app.appsearch.exceptions.AppSearchException;
 import android.app.appsearch.util.SchemaMigrationUtil;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -86,13 +91,15 @@
             @NonNull Consumer<AppSearchResult<AppSearchSession>> callback) {
         try {
             mService.initialize(mUserId, new IAppSearchResultCallback.Stub() {
-                public void onResult(AppSearchResult result) {
+                @Override
+                public void onResult(AppSearchResultParcel resultParcel) {
                     executor.execute(() -> {
+                        AppSearchResult<Void> result = resultParcel.getResult();
                         if (result.isSuccess()) {
                             callback.accept(
                                     AppSearchResult.newSuccessfulResult(AppSearchSession.this));
                         } else {
-                            callback.accept(result);
+                            callback.accept(AppSearchResult.newFailedResult(result));
                         }
                     });
                 }
@@ -191,15 +198,16 @@
                     mDatabaseName,
                     mUserId,
                     new IAppSearchResultCallback.Stub() {
-                        public void onResult(AppSearchResult result) {
+                        @Override
+                        public void onResult(AppSearchResultParcel resultParcel) {
                             executor.execute(() -> {
+                                AppSearchResult<Bundle> result = resultParcel.getResult();
                                 if (result.isSuccess()) {
-                                    Bundle responseBundle = (Bundle) result.getResultValue();
                                     GetSchemaResponse response =
-                                            new GetSchemaResponse(responseBundle);
+                                            new GetSchemaResponse(result.getResultValue());
                                     callback.accept(AppSearchResult.newSuccessfulResult(response));
                                 } else {
-                                    callback.accept(result);
+                                    callback.accept(AppSearchResult.newFailedResult(result));
                                 }
                             });
                         }
@@ -227,15 +235,17 @@
                     mDatabaseName,
                     mUserId,
                     new IAppSearchResultCallback.Stub() {
-                        public void onResult(AppSearchResult result) {
+                        @Override
+                        public void onResult(AppSearchResultParcel resultParcel) {
                             executor.execute(() -> {
+                                AppSearchResult<List<String>> result = resultParcel.getResult();
                                 if (result.isSuccess()) {
                                     Set<String> namespaces =
-                                            new ArraySet<>((List<String>) result.getResultValue());
+                                            new ArraySet<>(result.getResultValue());
                                     callback.accept(
                                             AppSearchResult.newSuccessfulResult(namespaces));
                                 } else {
-                                    callback.accept(result);
+                                    callback.accept(AppSearchResult.newFailedResult(result));
                                 }
                             });
                         }
@@ -280,13 +290,14 @@
                     /*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(),
                     new IAppSearchBatchResultCallback.Stub() {
                         @Override
-                        public void onResult(AppSearchBatchResult result) {
-                            executor.execute(() -> callback.onResult(result));
+                        public void onResult(AppSearchBatchResultParcel resultParcel) {
+                            executor.execute(() -> callback.onResult(resultParcel.getResult()));
                         }
 
                         @Override
-                        public void onSystemError(AppSearchResult result) {
-                            executor.execute(() -> sendSystemErrorToCallback(result, callback));
+                        public void onSystemError(AppSearchResultParcel resultParcel) {
+                            executor.execute(() -> sendSystemErrorToCallback(
+                                    resultParcel.getResult(), callback));
                         }
                     });
             mIsMutated = true;
@@ -341,15 +352,17 @@
                     mUserId,
                     new IAppSearchBatchResultCallback.Stub() {
                         @Override
-                        public void onResult(AppSearchBatchResult result) {
+                        public void onResult(AppSearchBatchResultParcel resultParcel) {
                             executor.execute(() -> {
+                                AppSearchBatchResult<String, Bundle> result =
+                                        resultParcel.getResult();
                                 AppSearchBatchResult.Builder<String, GenericDocument>
                                         documentResultBuilder =
                                         new AppSearchBatchResult.Builder<>();
 
                                 // Translate successful results
                                 for (Map.Entry<String, Bundle> bundleEntry :
-                                        ((Map<String, Bundle>) result.getSuccesses()).entrySet()) {
+                                        result.getSuccesses().entrySet()) {
                                     GenericDocument document;
                                     try {
                                         document = new GenericDocument(bundleEntry.getValue());
@@ -380,8 +393,9 @@
                         }
 
                         @Override
-                        public void onSystemError(AppSearchResult result) {
-                            executor.execute(() -> sendSystemErrorToCallback(result, callback));
+                        public void onSystemError(AppSearchResultParcel result) {
+                            executor.execute(
+                                    () -> sendSystemErrorToCallback(result.getResult(), callback));
                         }
                     });
         } catch (RemoteException e) {
@@ -492,8 +506,9 @@
                     /*systemUsage=*/ false,
                     mUserId,
                     new IAppSearchResultCallback.Stub() {
-                        public void onResult(AppSearchResult result) {
-                            executor.execute(() -> callback.accept(result));
+                        @Override
+                        public void onResult(AppSearchResultParcel resultParcel) {
+                            executor.execute(() -> callback.accept(resultParcel.getResult()));
                         }
                     });
             mIsMutated = true;
@@ -550,13 +565,14 @@
                     new ArrayList<>(request.getIds()), mUserId,
                     new IAppSearchBatchResultCallback.Stub() {
                         @Override
-                        public void onResult(AppSearchBatchResult result) {
-                            executor.execute(() -> callback.onResult(result));
+                        public void onResult(AppSearchBatchResultParcel resultParcel) {
+                            executor.execute(() -> callback.onResult(resultParcel.getResult()));
                         }
 
                         @Override
-                        public void onSystemError(AppSearchResult result) {
-                            executor.execute(() -> sendSystemErrorToCallback(result, callback));
+                        public void onSystemError(AppSearchResultParcel resultParcel) {
+                            executor.execute(() -> sendSystemErrorToCallback(
+                                    resultParcel.getResult(), callback));
                         }
                     });
             mIsMutated = true;
@@ -598,8 +614,9 @@
             mService.removeByQuery(mPackageName, mDatabaseName, queryExpression,
                     searchSpec.getBundle(), mUserId,
                     new IAppSearchResultCallback.Stub() {
-                        public void onResult(AppSearchResult result) {
-                            executor.execute(() -> callback.accept(result));
+                        @Override
+                        public void onResult(AppSearchResultParcel resultParcel) {
+                            executor.execute(() -> callback.accept(resultParcel.getResult()));
                         }
                     });
             mIsMutated = true;
@@ -629,15 +646,15 @@
                     mDatabaseName,
                     mUserId,
                     new IAppSearchResultCallback.Stub() {
-                        public void onResult(AppSearchResult result) {
+                        @Override
+                        public void onResult(AppSearchResultParcel resultParcel) {
                             executor.execute(() -> {
+                                AppSearchResult<Bundle> result = resultParcel.getResult();
                                 if (result.isSuccess()) {
-                                    Bundle responseBundle = (Bundle) result.getResultValue();
-                                    StorageInfo response =
-                                            new StorageInfo(responseBundle);
+                                    StorageInfo response = new StorageInfo(result.getResultValue());
                                     callback.accept(AppSearchResult.newSuccessfulResult(response));
                                 } else {
-                                    callback.accept(result);
+                                    callback.accept(AppSearchResult.newFailedResult(result));
                                 }
                             });
                         }
@@ -686,13 +703,14 @@
                     request.getVersion(),
                     mUserId,
                     new IAppSearchResultCallback.Stub() {
-                        public void onResult(AppSearchResult result) {
+                        @Override
+                        public void onResult(AppSearchResultParcel resultParcel) {
                             executor.execute(() -> {
+                                AppSearchResult<Bundle> result = resultParcel.getResult();
                                 if (result.isSuccess()) {
                                     try {
                                         SetSchemaResponse setSchemaResponse =
-                                                new SetSchemaResponse(
-                                                        (Bundle) result.getResultValue());
+                                                new SetSchemaResponse(result.getResultValue());
                                         if (!request.isForceOverride()) {
                                             // Throw exception if there is any deleted types or
                                             // incompatible types. That's the only case we swallowed
@@ -707,7 +725,7 @@
                                         callback.accept(AppSearchResult.throwableToFailedResult(t));
                                     }
                                 } else {
-                                    callback.accept(result);
+                                    callback.accept(AppSearchResult.newFailedResult(result));
                                 }
                             });
                         }
@@ -772,8 +790,9 @@
                         request.getVersion(),
                         mUserId,
                         new IAppSearchResultCallback.Stub() {
-                            public void onResult(AppSearchResult result) {
-                                setSchemaFuture.complete(result);
+                            @Override
+                            public void onResult(AppSearchResultParcel resultParcel) {
+                                setSchemaFuture.complete(resultParcel.getResult());
                             }
                         });
                 AppSearchResult<Bundle> setSchemaResult = setSchemaFuture.get();
@@ -823,8 +842,8 @@
                                 mUserId,
                                 new IAppSearchResultCallback.Stub() {
                                     @Override
-                                    public void onResult(AppSearchResult result) {
-                                        setSchema2Future.complete(result);
+                                    public void onResult(AppSearchResultParcel resultParcel) {
+                                        setSchema2Future.complete(resultParcel.getResult());
                                     }
                                 });
                         AppSearchResult<Bundle> setSchema2Result = setSchema2Future.get();
diff --git a/framework/java/android/app/appsearch/GlobalSearchSession.java b/framework/java/android/app/appsearch/GlobalSearchSession.java
index d49f472..7d246c2 100644
--- a/framework/java/android/app/appsearch/GlobalSearchSession.java
+++ b/framework/java/android/app/appsearch/GlobalSearchSession.java
@@ -16,10 +16,12 @@
 
 package android.app.appsearch;
 
-
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.appsearch.aidl.AppSearchResultParcel;
+import android.app.appsearch.aidl.IAppSearchManager;
+import android.app.appsearch.aidl.IAppSearchResultCallback;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -71,13 +73,15 @@
             @NonNull Consumer<AppSearchResult<GlobalSearchSession>> callback) {
         try {
             mService.initialize(mUserId, new IAppSearchResultCallback.Stub() {
-                public void onResult(AppSearchResult result) {
+                @Override
+                public void onResult(AppSearchResultParcel resultParcel) {
                     executor.execute(() -> {
+                        AppSearchResult<Void> result = resultParcel.getResult();
                         if (result.isSuccess()) {
                             callback.accept(
                                     AppSearchResult.newSuccessfulResult(GlobalSearchSession.this));
                         } else {
-                            callback.accept(result);
+                            callback.accept(AppSearchResult.newFailedResult(result));
                         }
                     });
                 }
@@ -159,8 +163,9 @@
                     /*systemUsage=*/ true,
                     mUserId,
                     new IAppSearchResultCallback.Stub() {
-                        public void onResult(AppSearchResult result) {
-                            executor.execute(() -> callback.accept(result));
+                        @Override
+                        public void onResult(AppSearchResultParcel resultParcel) {
+                            executor.execute(() -> callback.accept(resultParcel.getResult()));
                         }
                     });
             mIsMutated = true;
diff --git a/framework/java/android/app/appsearch/SearchResults.java b/framework/java/android/app/appsearch/SearchResults.java
index 531c984..4368672 100644
--- a/framework/java/android/app/appsearch/SearchResults.java
+++ b/framework/java/android/app/appsearch/SearchResults.java
@@ -20,6 +20,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.appsearch.aidl.AppSearchResultParcel;
+import android.app.appsearch.aidl.IAppSearchManager;
+import android.app.appsearch.aidl.IAppSearchResultCallback;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.util.Log;
@@ -139,18 +142,20 @@
             @NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) {
         return new IAppSearchResultCallback.Stub() {
-            public void onResult(AppSearchResult result) {
-                executor.execute(() -> invokeCallback(result, callback));
+            @Override
+            public void onResult(AppSearchResultParcel resultParcel) {
+                executor.execute(() -> invokeCallback(resultParcel.getResult(), callback));
             }
         };
     }
 
-    private void invokeCallback(AppSearchResult result,
+    private void invokeCallback(
+            @NonNull AppSearchResult<Bundle> searchResultPageResult,
             @NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) {
-        if (result.isSuccess()) {
+        if (searchResultPageResult.isSuccess()) {
             try {
                 SearchResultPage searchResultPage =
-                        new SearchResultPage((Bundle) result.getResultValue());
+                        new SearchResultPage(searchResultPageResult.getResultValue());
                 mNextPageToken = searchResultPage.getNextPageToken();
                 callback.accept(AppSearchResult.newSuccessfulResult(
                         searchResultPage.getResults()));
@@ -158,7 +163,7 @@
                 callback.accept(AppSearchResult.throwableToFailedResult(t));
             }
         } else {
-            callback.accept(result);
+            callback.accept(AppSearchResult.newFailedResult(searchResultPageResult));
         }
     }
 }
diff --git a/framework/java/android/app/appsearch/AppSearchResult.aidl b/framework/java/android/app/appsearch/aidl/AppSearchBatchResultParcel.aidl
similarity index 80%
copy from framework/java/android/app/appsearch/AppSearchResult.aidl
copy to framework/java/android/app/appsearch/aidl/AppSearchBatchResultParcel.aidl
index 37ce990..89908cd 100644
--- a/framework/java/android/app/appsearch/AppSearchResult.aidl
+++ b/framework/java/android/app/appsearch/aidl/AppSearchBatchResultParcel.aidl
@@ -1,5 +1,5 @@
 /**
- * Copyright 2020, The Android Open Source Project
+ * Copyright 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.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.app.appsearch;
+package android.app.appsearch.aidl;
 
 /** {@hide} */
-parcelable AppSearchResult<ValueType>;
\ No newline at end of file
+parcelable AppSearchBatchResultParcel<ValueType>;
diff --git a/framework/java/android/app/appsearch/aidl/AppSearchBatchResultParcel.java b/framework/java/android/app/appsearch/aidl/AppSearchBatchResultParcel.java
new file mode 100644
index 0000000..b0cc10c
--- /dev/null
+++ b/framework/java/android/app/appsearch/aidl/AppSearchBatchResultParcel.java
@@ -0,0 +1,97 @@
+/*
+ * 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 android.app.appsearch.aidl;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchResult;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Parcelable wrapper around {@link AppSearchBatchResult}.
+ *
+ * <p>{@link AppSearchBatchResult} can contain any type of key and value, including non-parcelable
+ * values. For the specific case of sending {@link AppSearchBatchResult} across Binder, this class
+ * wraps an {@link AppSearchBatchResult} that has String keys and Parcelable values. It provides
+ * parcelability of the whole structure.
+ *
+ * @param <ValueType> The type of result object for successful calls. Must be a parcelable type.
+ * @hide
+ */
+public final class AppSearchBatchResultParcel<ValueType> implements Parcelable {
+    private final AppSearchBatchResult<String, ValueType> mResult;
+
+    /** Creates a new {@link AppSearchBatchResultParcel} from the given result. */
+    public AppSearchBatchResultParcel(@NonNull AppSearchBatchResult<String, ValueType> result) {
+        mResult = Objects.requireNonNull(result);
+    }
+
+    private AppSearchBatchResultParcel(@NonNull Parcel in) {
+        Bundle bundle = in.readBundle();
+        AppSearchBatchResult.Builder<String, ValueType> builder =
+                new AppSearchBatchResult.Builder<>();
+        for (String key : bundle.keySet()) {
+            AppSearchResultParcel<ValueType> resultParcel = bundle.getParcelable(key);
+            builder.setResult(key, resultParcel.getResult());
+        }
+        mResult = builder.build();
+    }
+
+    @NonNull
+    public AppSearchBatchResult<String, ValueType> getResult() {
+        return mResult;
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        Bundle bundle = new Bundle();
+        for (Map.Entry<String, AppSearchResult<ValueType>> entry
+                : mResult.getAll().entrySet()) {
+            bundle.putParcelable(entry.getKey(), new AppSearchResultParcel<>(entry.getValue()));
+        }
+        dest.writeBundle(bundle);
+    }
+
+    /** @hide */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** @hide */
+    @NonNull
+    public static final Creator<AppSearchBatchResultParcel<?>> CREATOR =
+            new Creator<AppSearchBatchResultParcel<?>>() {
+                @NonNull
+                @Override
+                public AppSearchBatchResultParcel<?> createFromParcel(@NonNull Parcel in) {
+                    return new AppSearchBatchResultParcel<>(in);
+                }
+
+                @NonNull
+                @Override
+                public AppSearchBatchResultParcel<?>[] newArray(int size) {
+                    return new AppSearchBatchResultParcel<?>[size];
+                }
+            };
+}
diff --git a/framework/java/android/app/appsearch/AppSearchResult.aidl b/framework/java/android/app/appsearch/aidl/AppSearchResultParcel.aidl
similarity index 81%
rename from framework/java/android/app/appsearch/AppSearchResult.aidl
rename to framework/java/android/app/appsearch/aidl/AppSearchResultParcel.aidl
index 37ce990..4e35bd5 100644
--- a/framework/java/android/app/appsearch/AppSearchResult.aidl
+++ b/framework/java/android/app/appsearch/aidl/AppSearchResultParcel.aidl
@@ -1,5 +1,5 @@
 /**
- * Copyright 2020, The Android Open Source Project
+ * Copyright 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.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.app.appsearch;
+package android.app.appsearch.aidl;
 
 /** {@hide} */
-parcelable AppSearchResult<ValueType>;
\ No newline at end of file
+parcelable AppSearchResultParcel<ValueType>;
diff --git a/framework/java/android/app/appsearch/aidl/AppSearchResultParcel.java b/framework/java/android/app/appsearch/aidl/AppSearchResultParcel.java
new file mode 100644
index 0000000..8b137d3
--- /dev/null
+++ b/framework/java/android/app/appsearch/aidl/AppSearchResultParcel.java
@@ -0,0 +1,95 @@
+/*
+ * 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 android.app.appsearch.aidl;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchResult;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Parcelable wrapper around {@link AppSearchResult}.
+ *
+ * <p>{@link AppSearchResult} can contain any value, including non-parcelable values. For the
+ * specific case of sending {@link AppSearchResult} across Binder, this class wraps an
+ * {@link AppSearchResult} that contains a parcelable type and provides parcelability of the whole
+ * structure.
+ *
+ * @param <ValueType> The type of result object for successful calls. Must be a parcelable type.
+ * @hide
+ */
+public final class AppSearchResultParcel<ValueType> implements Parcelable {
+    private final AppSearchResult<ValueType> mResult;
+
+    /** Creates a new {@link AppSearchResultParcel} from the given result. */
+    public AppSearchResultParcel(@NonNull AppSearchResult<ValueType> result) {
+        mResult = Objects.requireNonNull(result);
+    }
+
+    private AppSearchResultParcel(@NonNull Parcel in) {
+        int resultCode = in.readInt();
+        ValueType resultValue = (ValueType) in.readValue(/*loader=*/ null);
+        String errorMessage = in.readString();
+        if (resultCode == AppSearchResult.RESULT_OK) {
+            mResult = AppSearchResult.newSuccessfulResult(resultValue);
+        } else {
+            mResult = AppSearchResult.newFailedResult(resultCode, errorMessage);
+        }
+    }
+
+    @NonNull
+    public AppSearchResult<ValueType> getResult() {
+        return mResult;
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mResult.getResultCode());
+        if (mResult.isSuccess()) {
+            dest.writeValue(mResult.getResultValue());
+        } else {
+            dest.writeValue(null);
+        }
+        dest.writeString(mResult.getErrorMessage());
+    }
+
+    /** @hide */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** @hide */
+    @NonNull
+    public static final Creator<AppSearchResultParcel<?>> CREATOR =
+            new Creator<AppSearchResultParcel<?>>() {
+                @NonNull
+                @Override
+                public AppSearchResultParcel<?> createFromParcel(@NonNull Parcel in) {
+                    return new AppSearchResultParcel<>(in);
+                }
+
+                @NonNull
+                @Override
+                public AppSearchResultParcel<?>[] newArray(int size) {
+                    return new AppSearchResultParcel<?>[size];
+                }
+            };
+}
diff --git a/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl b/framework/java/android/app/appsearch/aidl/IAppSearchBatchResultCallback.aidl
similarity index 70%
rename from framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl
rename to framework/java/android/app/appsearch/aidl/IAppSearchBatchResultCallback.aidl
index 64b331e..1fe19cc 100644
--- a/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl
+++ b/framework/java/android/app/appsearch/aidl/IAppSearchBatchResultCallback.aidl
@@ -13,13 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.app.appsearch;
+package android.app.appsearch.aidl;
 
-import android.app.appsearch.AppSearchBatchResult;
-import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.aidl.AppSearchBatchResultParcel;
+import android.app.appsearch.aidl.AppSearchResultParcel;
 
 /** {@hide} */
 oneway interface IAppSearchBatchResultCallback {
-    void onResult(in AppSearchBatchResult result);
-    void onSystemError(in AppSearchResult result);
+    void onResult(in AppSearchBatchResultParcel resultParcel);
+    void onSystemError(in AppSearchResultParcel resultParcel);
 }
diff --git a/framework/java/android/app/appsearch/IAppSearchManager.aidl b/framework/java/android/app/appsearch/aidl/IAppSearchManager.aidl
similarity index 98%
rename from framework/java/android/app/appsearch/IAppSearchManager.aidl
rename to framework/java/android/app/appsearch/aidl/IAppSearchManager.aidl
index 507bd68..6f7e82e 100644
--- a/framework/java/android/app/appsearch/IAppSearchManager.aidl
+++ b/framework/java/android/app/appsearch/aidl/IAppSearchManager.aidl
@@ -13,12 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.app.appsearch;
+package android.app.appsearch.aidl;
 
 import android.os.Bundle;
 
-import android.app.appsearch.IAppSearchBatchResultCallback;
-import android.app.appsearch.IAppSearchResultCallback;
+import android.app.appsearch.aidl.IAppSearchBatchResultCallback;
+import android.app.appsearch.aidl.IAppSearchResultCallback;
 import android.os.ParcelFileDescriptor;
 
 /** {@hide} */
@@ -200,7 +200,7 @@
     * @param searchSpecBundle SearchSpec bundle.
     * @param userId Id of the calling user.
     * @param callback {@link IAppSearchResultCallback#onResult} will be called with an
-    *        {@link AppSearchResult}&lt;{@code null}&gt;.
+    *        {@link AppSearchResult}&lt;{@code Void}&gt;.
     */
     void writeQueryResultsToFile(
         in String packageName,
diff --git a/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl b/framework/java/android/app/appsearch/aidl/IAppSearchResultCallback.aidl
similarity index 81%
rename from framework/java/android/app/appsearch/IAppSearchResultCallback.aidl
rename to framework/java/android/app/appsearch/aidl/IAppSearchResultCallback.aidl
index 299c995..097f0d1 100644
--- a/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl
+++ b/framework/java/android/app/appsearch/aidl/IAppSearchResultCallback.aidl
@@ -13,11 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.app.appsearch;
+package android.app.appsearch.aidl;
 
-import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.aidl.AppSearchResultParcel;
 
 /** {@hide} */
 oneway interface IAppSearchResultCallback {
-    void onResult(in AppSearchResult result);
+    void onResult(in AppSearchResultParcel resultParcel);
 }
diff --git a/framework/java/android/app/appsearch/AppSearchBatchResult.java b/framework/java/external/android/app/appsearch/AppSearchBatchResult.java
similarity index 75%
rename from framework/java/android/app/appsearch/AppSearchBatchResult.java
rename to framework/java/external/android/app/appsearch/AppSearchBatchResult.java
index 9ae0d62..aca0fc0 100644
--- a/framework/java/android/app/appsearch/AppSearchBatchResult.java
+++ b/framework/java/external/android/app/appsearch/AppSearchBatchResult.java
@@ -13,37 +13,35 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package android.app.appsearch;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.util.ArrayMap;
 
 import com.android.internal.util.Preconditions;
 
-import java.util.Collections;
 import java.util.Map;
 import java.util.Objects;
 
 /**
  * Provides results for AppSearch batch operations which encompass multiple documents.
  *
- * <p>Individual results of a batch operation are separated into two maps: one for successes and
- * one for failures. For successes, {@link #getSuccesses()} will return a map of keys to
- * instances of the value type. For failures, {@link #getFailures()} will return a map of keys to
- * {@link AppSearchResult} objects.
+ * <p>Individual results of a batch operation are separated into two maps: one for successes and one
+ * for failures. For successes, {@link #getSuccesses()} will return a map of keys to instances of
+ * the value type. For failures, {@link #getFailures()} will return a map of keys to {@link
+ * AppSearchResult} objects.
  *
  * <p>Alternatively, {@link #getAll()} returns a map of keys to {@link AppSearchResult} objects for
  * both successes and failures.
  *
+ * @param <KeyType> The type of the keys for which the results will be reported.
+ * @param <ValueType> The type of the result objects for successful results.
  * @see AppSearchSession#put
  * @see AppSearchSession#getByDocumentId
  * @see AppSearchSession#remove
  */
-public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelable {
+public final class AppSearchBatchResult<KeyType, ValueType> {
     @NonNull private final Map<KeyType, ValueType> mSuccesses;
     @NonNull private final Map<KeyType, AppSearchResult<ValueType>> mFailures;
     @NonNull private final Map<KeyType, AppSearchResult<ValueType>> mAll;
@@ -57,27 +55,6 @@
         mAll = all;
     }
 
-    private AppSearchBatchResult(@NonNull Parcel in) {
-        mAll = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
-        Map<KeyType, ValueType> successes = new ArrayMap<>();
-        Map<KeyType, AppSearchResult<ValueType>> failures = new ArrayMap<>();
-        for (Map.Entry<KeyType, AppSearchResult<ValueType>> entry : mAll.entrySet()) {
-            if (entry.getValue().isSuccess()) {
-                successes.put(entry.getKey(), entry.getValue().getResultValue());
-            } else {
-                failures.put(entry.getKey(), entry.getValue());
-            }
-        }
-        mSuccesses = Collections.unmodifiableMap(successes);
-        mFailures = Collections.unmodifiableMap(failures);
-    }
-
-    /** @hide */
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeMap(mAll);
-    }
-
     /** Returns {@code true} if this {@link AppSearchBatchResult} has no failures. */
     public boolean isSuccess() {
         return mFailures.isEmpty();
@@ -99,8 +76,8 @@
     }
 
     /**
-     * Returns a {@link Map} of keys mapped to instances of {@link AppSearchResult} for all
-     * failed individual results.
+     * Returns a {@link Map} of keys mapped to instances of {@link AppSearchResult} for all failed
+     * individual results.
      *
      * <p>The values of the {@link Map} will not be {@code null}.
      */
@@ -122,6 +99,7 @@
 
     /**
      * Asserts that this {@link AppSearchBatchResult} has no failures.
+     *
      * @hide
      */
     public void checkSuccess() {
@@ -136,33 +114,13 @@
         return "{\n  successes: " + mSuccesses + "\n  failures: " + mFailures + "\n}";
     }
 
-    /** @hide */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** @hide */
-    @NonNull
-    public static final Creator<AppSearchBatchResult> CREATOR =
-            new Creator<AppSearchBatchResult>() {
-        @NonNull
-        @Override
-        public AppSearchBatchResult createFromParcel(@NonNull Parcel in) {
-            return new AppSearchBatchResult(in);
-        }
-
-        @NonNull
-        @Override
-        public AppSearchBatchResult[] newArray(int size) {
-            return new AppSearchBatchResult[size];
-        }
-    };
-
     /**
      * Builder for {@link AppSearchBatchResult} objects.
      *
      * <p>Once {@link #build} is called, the instance can no longer be used.
+     *
+     * @param <KeyType> The type of the keys for which the results will be reported.
+     * @param <ValueType> The type of the result objects for successful results.
      */
     public static final class Builder<KeyType, ValueType> {
         private final Map<KeyType, ValueType> mSuccesses = new ArrayMap<>();
@@ -177,7 +135,7 @@
          *
          * @throws IllegalStateException if the builder has already been used.
          */
-        @SuppressWarnings("MissingGetterMatchingBuilder")  // See getSuccesses
+        @SuppressWarnings("MissingGetterMatchingBuilder") // See getSuccesses
         @NonNull
         public Builder<KeyType, ValueType> setSuccess(
                 @NonNull KeyType key, @Nullable ValueType result) {
@@ -193,7 +151,7 @@
          *
          * @throws IllegalStateException if the builder has already been used.
          */
-        @SuppressWarnings("MissingGetterMatchingBuilder")  // See getFailures
+        @SuppressWarnings("MissingGetterMatchingBuilder") // See getFailures
         @NonNull
         public Builder<KeyType, ValueType> setFailure(
                 @NonNull KeyType key,
@@ -211,7 +169,7 @@
          *
          * @throws IllegalStateException if the builder has already been used.
          */
-        @SuppressWarnings("MissingGetterMatchingBuilder")  // See getAll
+        @SuppressWarnings("MissingGetterMatchingBuilder") // See getAll
         @NonNull
         public Builder<KeyType, ValueType> setResult(
                 @NonNull KeyType key, @NonNull AppSearchResult<ValueType> result) {
diff --git a/framework/java/android/app/appsearch/AppSearchResult.java b/framework/java/external/android/app/appsearch/AppSearchResult.java
similarity index 82%
rename from framework/java/android/app/appsearch/AppSearchResult.java
rename to framework/java/external/android/app/appsearch/AppSearchResult.java
index b06e215..30c98b0 100644
--- a/framework/java/android/app/appsearch/AppSearchResult.java
+++ b/framework/java/external/android/app/appsearch/AppSearchResult.java
@@ -13,15 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package android.app.appsearch;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.appsearch.exceptions.AppSearchException;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.util.Log;
 
 import com.android.internal.util.Preconditions;
@@ -36,24 +33,26 @@
  *
  * @param <ValueType> The type of result object for successful calls.
  */
-public final class AppSearchResult<ValueType> implements Parcelable {
+public final class AppSearchResult<ValueType> {
     private static final String TAG = "AppSearchResult";
 
     /**
      * Result codes from {@link AppSearchSession} methods.
+     *
      * @hide
      */
-    @IntDef(value = {
-            RESULT_OK,
-            RESULT_UNKNOWN_ERROR,
-            RESULT_INTERNAL_ERROR,
-            RESULT_INVALID_ARGUMENT,
-            RESULT_IO_ERROR,
-            RESULT_OUT_OF_SPACE,
-            RESULT_NOT_FOUND,
-            RESULT_INVALID_SCHEMA,
-            RESULT_SECURITY_ERROR,
-    })
+    @IntDef(
+            value = {
+                RESULT_OK,
+                RESULT_UNKNOWN_ERROR,
+                RESULT_INTERNAL_ERROR,
+                RESULT_INVALID_ARGUMENT,
+                RESULT_IO_ERROR,
+                RESULT_OUT_OF_SPACE,
+                RESULT_NOT_FOUND,
+                RESULT_INVALID_SCHEMA,
+                RESULT_SECURITY_ERROR,
+            })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ResultCode {}
 
@@ -109,20 +108,6 @@
         mErrorMessage = errorMessage;
     }
 
-    private AppSearchResult(@NonNull Parcel in) {
-        mResultCode = in.readInt();
-        mResultValue = (ValueType) in.readValue(/*loader=*/ null);
-        mErrorMessage = in.readString();
-    }
-
-    /** @hide */
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mResultCode);
-        dest.writeValue(mResultValue);
-        dest.writeString(mErrorMessage);
-    }
-
     /** Returns {@code true} if {@link #getResultCode} equals {@link AppSearchResult#RESULT_OK}. */
     public boolean isSuccess() {
         return getResultCode() == RESULT_OK;
@@ -154,8 +139,8 @@
      *
      * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. The error
      * message may be {@code null} even if {@link #isSuccess} is {@code false}. See the
-     * documentation of the particular {@link AppSearchSession} call producing this
-     * {@link AppSearchResult} for what is returned by {@link #getErrorMessage}.
+     * documentation of the particular {@link AppSearchSession} call producing this {@link
+     * AppSearchResult} for what is returned by {@link #getErrorMessage}.
      */
     @Nullable
     public String getErrorMessage() {
@@ -190,40 +175,14 @@
         return "[FAILURE(" + mResultCode + ")]: " + mErrorMessage;
     }
 
-    /** @hide */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** @hide */
-    @NonNull
-    public static final Creator<AppSearchResult> CREATOR = new Creator<AppSearchResult>() {
-        @NonNull
-        @Override
-        public AppSearchResult createFromParcel(@NonNull Parcel in) {
-            return new AppSearchResult(in);
-        }
-
-        @NonNull
-        @Override
-        public AppSearchResult[] newArray(int size) {
-            return new AppSearchResult[size];
-        }
-    };
-
-    /**
-     * Creates a new successful {@link AppSearchResult}.
-     */
+    /** Creates a new successful {@link AppSearchResult}. */
     @NonNull
     public static <ValueType> AppSearchResult<ValueType> newSuccessfulResult(
             @Nullable ValueType value) {
         return new AppSearchResult<>(RESULT_OK, value, /*errorMessage=*/ null);
     }
 
-    /**
-     * Creates a new failed {@link AppSearchResult}.
-     */
+    /** Creates a new failed {@link AppSearchResult}. */
     @NonNull
     public static <ValueType> AppSearchResult<ValueType> newFailedResult(
             @ResultCode int resultCode, @Nullable String errorMessage) {
@@ -238,7 +197,8 @@
     @NonNull
     public static <ValueType> AppSearchResult<ValueType> newFailedResult(
             @NonNull AppSearchResult<?> otherFailedResult) {
-        Preconditions.checkState(!otherFailedResult.isSuccess(),
+        Preconditions.checkState(
+                !otherFailedResult.isSuccess(),
                 "Cannot convert a success result to a failed result");
         return AppSearchResult.newFailedResult(
                 otherFailedResult.getResultCode(), otherFailedResult.getErrorMessage());
diff --git a/service/java/com/android/server/appsearch/AppSearchManagerService.java b/service/java/com/android/server/appsearch/AppSearchManagerService.java
index a4188a2..0f42d85 100644
--- a/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -29,14 +29,16 @@
 import android.app.appsearch.AppSearchSchema;
 import android.app.appsearch.GenericDocument;
 import android.app.appsearch.GetSchemaResponse;
-import android.app.appsearch.IAppSearchBatchResultCallback;
-import android.app.appsearch.IAppSearchManager;
-import android.app.appsearch.IAppSearchResultCallback;
 import android.app.appsearch.PackageIdentifier;
 import android.app.appsearch.SearchResultPage;
 import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaResponse;
 import android.app.appsearch.StorageInfo;
+import android.app.appsearch.aidl.AppSearchBatchResultParcel;
+import android.app.appsearch.aidl.AppSearchResultParcel;
+import android.app.appsearch.aidl.IAppSearchBatchResultCallback;
+import android.app.appsearch.aidl.IAppSearchManager;
+import android.app.appsearch.aidl.IAppSearchResultCallback;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -869,7 +871,7 @@
         private void invokeCallbackOnResult(
                 IAppSearchResultCallback callback, AppSearchResult<?> result) {
             try {
-                callback.onResult(result);
+                callback.onResult(new AppSearchResultParcel<>(result));
             } catch (RemoteException e) {
                 Log.e(TAG, "Unable to send result to the callback", e);
             }
@@ -877,9 +879,9 @@
 
         /** Invokes the {@link IAppSearchBatchResultCallback} with the result. */
         private void invokeCallbackOnResult(
-                IAppSearchBatchResultCallback callback, AppSearchBatchResult<?, ?> result) {
+                IAppSearchBatchResultCallback callback, AppSearchBatchResult<String, ?> result) {
             try {
-                callback.onResult(result);
+                callback.onResult(new AppSearchBatchResultParcel<>(result));
             } catch (RemoteException e) {
                 Log.e(TAG, "Unable to send result to the callback", e);
             }
@@ -891,8 +893,9 @@
          * <p>The throwable is convert to a {@link AppSearchResult};
          */
         private void invokeCallbackOnError(IAppSearchResultCallback callback, Throwable throwable) {
+            AppSearchResult<?> result = throwableToFailedResult(throwable);
             try {
-                callback.onResult(throwableToFailedResult(throwable));
+                callback.onResult(new AppSearchResultParcel<>(result));
             } catch (RemoteException e) {
                 Log.e(TAG, "Unable to send result to the callback", e);
             }
@@ -905,8 +908,9 @@
          */
         private void invokeCallbackOnError(
                 @NonNull IAppSearchBatchResultCallback callback, @NonNull Throwable throwable) {
+            AppSearchResult<?> result = throwableToFailedResult(throwable);
             try {
-                callback.onSystemError(throwableToFailedResult(throwable));
+                callback.onSystemError(new AppSearchResultParcel<>(result));
             } catch (RemoteException e) {
                 Log.e(TAG, "Unable to send error to the callback", e);
             }