Update Framework from Jetpack.

* e2124eb: Switch from throwing AssertionError to using a Truth assertion.
* 2ff6b81: Remove FutureUtils.map.
* 40eb8b5: Add projection to the SearchSpec.

Also, ports GlobalSearchSessionTest to platform for the first time.

Bug: 170997047
Bug: 162450968
Bug: 175661706
Bug: 170539682
Test: AppSearchSessionTest, GlobalSearchSessionTest
Change-Id: I9bdfb69b4d36b194c2c4038fb053f6237af9412a
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java
index 05735cc..82c2d26 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java
@@ -45,33 +45,35 @@
 
     private final IAppSearchManager mService;
 
+    // The database name to search over. If null, this will search over all database names.
     @Nullable
     private final String mDatabaseName;
 
-    @UserIdInt
-    private final int mUserId;
-
     private final String mQueryExpression;
 
     private final SearchSpec mSearchSpec;
 
+    @UserIdInt
+    private final int mUserId;
+
     private final Executor mExecutor;
 
     private long mNextPageToken;
 
     private boolean mIsFirstLoad = true;
 
-    SearchResults(@NonNull IAppSearchManager service,
+    SearchResults(
+            @NonNull IAppSearchManager service,
             @Nullable String databaseName,
             @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec,
             @UserIdInt int userId,
             @NonNull @CallbackExecutor Executor executor) {
         mService = Objects.requireNonNull(service);
-        mUserId = userId;
-        mDatabaseName = Objects.requireNonNull(databaseName);
+        mDatabaseName = databaseName;
         mQueryExpression = Objects.requireNonNull(queryExpression);
         mSearchSpec = Objects.requireNonNull(searchSpec);
+        mUserId = userId;
         mExecutor = Objects.requireNonNull(executor);
     }
 
@@ -90,11 +92,14 @@
             if (mIsFirstLoad) {
                 mIsFirstLoad = false;
                 if (mDatabaseName == null) {
+                    // Global query, there's no one package-database combination to check.
                     mService.globalQuery(mQueryExpression, mSearchSpec.getBundle(), mUserId,
                             wrapCallback(callback));
                 } else {
-                    mService.query(mDatabaseName, mQueryExpression, mSearchSpec.getBundle(),
-                            mUserId, wrapCallback(callback));
+                    // Normal local query, pass in specified database.
+                    mService.query(
+                            mDatabaseName, mQueryExpression, mSearchSpec.getBundle(), mUserId,
+                            wrapCallback(callback));
                 }
             } else {
                 mService.getNextPage(mNextPageToken, mUserId, wrapCallback(callback));
@@ -104,6 +109,24 @@
         }
     }
 
+    @Override
+    public void close() {
+        try {
+            mService.invalidateNextPageToken(mNextPageToken, mUserId);
+        } catch (RemoteException e) {
+            Log.d(TAG, "Unable to close the SearchResults", e);
+        }
+    }
+
+    private IAppSearchResultCallback wrapCallback(
+            @NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) {
+        return new IAppSearchResultCallback.Stub() {
+            public void onResult(AppSearchResult result) {
+                mExecutor.execute(() -> invokeCallback(result, callback));
+            }
+        };
+    }
+
     private void invokeCallback(AppSearchResult result,
             @NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) {
         if (result.isSuccess()) {
@@ -120,23 +143,4 @@
             callback.accept(result);
         }
     }
-    @Override
-    public void close() {
-        mExecutor.execute(() -> {
-            try {
-                mService.invalidateNextPageToken(mNextPageToken, mUserId);
-            } catch (RemoteException e) {
-                Log.d(TAG, "Unable to close the SearchResults", e);
-            }
-        });
-    }
-
-    private IAppSearchResultCallback wrapCallback(
-            @NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) {
-        return new IAppSearchResultCallback.Stub() {
-            public void onResult(AppSearchResult result) {
-                mExecutor.execute(() -> invokeCallback(result, callback));
-            }
-        };
-    }
 }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
index 68e31f0..400b630 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
@@ -23,6 +23,7 @@
 import android.app.appsearch.exceptions.AppSearchException;
 import android.app.appsearch.exceptions.IllegalSearchSpecException;
 import android.os.Bundle;
+import android.util.ArrayMap;
 
 import com.android.internal.util.Preconditions;
 
@@ -33,6 +34,8 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * This class represents the specification logic for AppSearch. It can be used to set the type of
@@ -40,6 +43,15 @@
  */
 // TODO(sidchhabra) : AddResultSpec fields for Snippets etc.
 public final class SearchSpec {
+    /**
+     * Schema type to be used in {@link SearchSpec.Builder#addProjectionTypePropertyPath} to apply
+     * property paths to all results, excepting any types that have had their own, specific property
+     * paths set.
+     *
+     * @hide
+     */
+    public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
+
     static final String TERM_MATCH_TYPE_FIELD = "termMatchType";
     static final String SCHEMA_TYPE_FIELD = "schemaType";
     static final String NAMESPACE_FIELD = "namespace";
@@ -49,6 +61,7 @@
     static final String SNIPPET_COUNT_FIELD = "snippetCount";
     static final String SNIPPET_COUNT_PER_PROPERTY_FIELD = "snippetCountPerProperty";
     static final String MAX_SNIPPET_FIELD = "maxSnippet";
+    static final String PROJECTION_TYPE_PROPERTY_PATHS_FIELD = "projectionTypeFieldMasks";
 
     /** @hide */
     public static final int DEFAULT_NUM_PER_PAGE = 10;
@@ -206,12 +219,35 @@
         return mBundle.getInt(MAX_SNIPPET_FIELD);
     }
 
+    /**
+     * Returns a map from schema type to property paths to be used for projection.
+     *
+     * <p>If the map is empty, then all properties will be retrieved for all results.
+     *
+     * <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned by this
+     * function, rather than calling it multiple times.
+     *
+     * @hide
+     */
+    @NonNull
+    public Map<String, List<String>> getProjectionTypePropertyPaths() {
+        Bundle typePropertyPathsBundle = mBundle.getBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD);
+        Set<String> schemaTypes = typePropertyPathsBundle.keySet();
+        Map<String, List<String>> typePropertyPathsMap = new ArrayMap<>(schemaTypes.size());
+        for (String schemaType : schemaTypes) {
+            typePropertyPathsMap.put(
+                    schemaType, typePropertyPathsBundle.getStringArrayList(schemaType));
+        }
+        return typePropertyPathsMap;
+    }
+
     /** Builder for {@link SearchSpec objects}. */
     public static final class Builder {
 
         private final Bundle mBundle;
         private final ArrayList<String> mSchemaTypes = new ArrayList<>();
         private final ArrayList<String> mNamespaces = new ArrayList<>();
+        private final Bundle mProjectionTypePropertyMasks = new Bundle();
         private boolean mBuilt = false;
 
         /** Creates a new {@link SearchSpec.Builder}. */
@@ -386,6 +422,109 @@
         }
 
         /**
+         * Adds property paths for the specified type to be used for projection. If property paths
+         * are added for a type, then only the properties referred to will be retrieved for results
+         * of that type. If a property path that is specified isn't present in a result, it will be
+         * ignored for that result. Property paths cannot be null.
+         *
+         * <p>If no property paths are added for a particular type, then all properties of results
+         * of that type will be retrieved.
+         *
+         * <p>If property path is added for the {@link SearchSpec#PROJECTION_SCHEMA_TYPE_WILDCARD},
+         * then those property paths will apply to all results, excepting any types that have their
+         * own, specific property paths set.
+         *
+         * <p>Suppose the following document is in the index.
+         *
+         * <pre>{@code
+         * Email: Document {
+         *   sender: Document {
+         *     name: "Mr. Person"
+         *     email: "mrperson123@google.com"
+         *   }
+         *   recipients: [
+         *     Document {
+         *       name: "John Doe"
+         *       email: "johndoe123@google.com"
+         *     }
+         *     Document {
+         *       name: "Jane Doe"
+         *       email: "janedoe123@google.com"
+         *     }
+         *   ]
+         *   subject: "IMPORTANT"
+         *   body: "Limited time offer!"
+         * }
+         * }</pre>
+         *
+         * <p>Then, suppose that a query for "important" is issued with the following projection
+         * type property paths:
+         *
+         * <pre>{@code
+         * {schemaType: "Email", ["subject", "sender.name", "recipients.name"]}
+         * }</pre>
+         *
+         * <p>The above document will be returned as:
+         *
+         * <pre>{@code
+         * Email: Document {
+         *   sender: Document {
+         *     name: "Mr. Body"
+         *   }
+         *   recipients: [
+         *     Document {
+         *       name: "John Doe"
+         *     }
+         *     Document {
+         *       name: "Jane Doe"
+         *     }
+         *   ]
+         *   subject: "IMPORTANT"
+         * }
+         * }</pre>
+         *
+         * @hide
+         */
+        @NonNull
+        public SearchSpec.Builder addProjectionTypePropertyPaths(
+                @NonNull String schemaType, @NonNull String... propertyPaths) {
+            Preconditions.checkNotNull(propertyPaths);
+            return addProjectionTypePropertyPaths(schemaType, Arrays.asList(propertyPaths));
+        }
+
+        /**
+         * Adds property paths for the specified type to be used for projection. If property paths
+         * are added for a type, then only the properties referred to will be retrieved for results
+         * of that type. If a property path that is specified isn't present in a result, it will be
+         * ignored for that result. Property paths cannot be null.
+         *
+         * <p>If no property paths are added for a particular type, then all properties of results
+         * of that type will be retrieved.
+         *
+         * <p>If property path is added for the {@link SearchSpec#PROJECTION_SCHEMA_TYPE_WILDCARD},
+         * then those property paths will apply to all results, excepting any types that have their
+         * own, specific property paths set.
+         *
+         * <p>{@see SearchSpec.Builder#addProjectionTypePropertyPath(String, String...)}
+         *
+         * @hide
+         */
+        @NonNull
+        public SearchSpec.Builder addProjectionTypePropertyPaths(
+                @NonNull String schemaType, @NonNull Collection<String> propertyPaths) {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            Preconditions.checkNotNull(schemaType);
+            Preconditions.checkNotNull(propertyPaths);
+            ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
+            for (String propertyPath : propertyPaths) {
+                Preconditions.checkNotNull(propertyPath);
+                propertyPathsArrayList.add(propertyPath);
+            }
+            mProjectionTypePropertyMasks.putStringArrayList(schemaType, propertyPathsArrayList);
+            return this;
+        }
+
+        /**
          * Constructs a new {@link SearchSpec} from the contents of this builder.
          *
          * <p>After calling this method, the builder must no longer be used.
@@ -398,6 +537,7 @@
             }
             mBundle.putStringArrayList(NAMESPACE_FIELD, mNamespaces);
             mBundle.putStringArrayList(SCHEMA_TYPE_FIELD, mSchemaTypes);
+            mBundle.putBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD, mProjectionTypePropertyMasks);
             mBuilt = true;
             return new SearchSpec(mBundle);
         }
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index f9a0bed..5b818c7 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-I0577839bfddf95a555399df441d317b00c7c7c48
+Idd770a064edfeb6dc648571fc6706c087b8e605a
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
index 67af6b1..9e22bf6 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
@@ -20,6 +20,7 @@
 import android.app.appsearch.AppSearchManager;
 import android.app.appsearch.AppSearchResult;
 import android.app.appsearch.GlobalSearchSession;
+import android.app.appsearch.GlobalSearchSessionShim;
 import android.app.appsearch.SearchResults;
 import android.app.appsearch.SearchResultsShim;
 import android.app.appsearch.SearchSpec;
@@ -40,7 +41,7 @@
  * a consistent interface.
  * @hide
  */
-public class GlobalSearchSessionShimImpl {
+public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim {
     private final GlobalSearchSession mGlobalSearchSession;
     private final ExecutorService mExecutor;
 
@@ -64,6 +65,7 @@
     }
 
     @NonNull
+    @Override
     public SearchResultsShim query(
             @NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
         SearchResults searchResults =
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
index 9653def..459fd151 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
@@ -17,6 +17,7 @@
 package com.android.server.appsearch.testing;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.app.appsearch.AppSearchBatchResult;
 import android.app.appsearch.AppSearchSessionShim;
@@ -25,8 +26,6 @@
 import android.app.appsearch.SearchResult;
 import android.app.appsearch.SearchResultsShim;
 
-import junit.framework.AssertionFailedError;
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Future;
@@ -36,9 +35,9 @@
     public static <K, V> AppSearchBatchResult<K, V> checkIsBatchResultSuccess(
             Future<AppSearchBatchResult<K, V>> future) throws Exception {
         AppSearchBatchResult<K, V> result = future.get();
-        if (!result.isSuccess()) {
-            throw new AssertionFailedError("AppSearchBatchResult not successful: " + result);
-        }
+        assertWithMessage("AppSearchBatchResult not successful: " + result)
+                .that(result.isSuccess())
+                .isTrue();
         return result;
     }
 
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java
index 9fd480d..b3caecc 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java
@@ -22,6 +22,9 @@
 
 import org.junit.Test;
 
+import java.util.List;
+import java.util.Map;
+
 public class SearchSpecTest {
     @Test
     public void testGetBundle() {
@@ -53,4 +56,21 @@
         assertThat(bundle.getInt(SearchSpec.RANKING_STRATEGY_FIELD))
                 .isEqualTo(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE);
     }
+
+    @Test
+    public void testGetProjectionTypePropertyMasks() {
+        SearchSpec searchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                        .addProjectionTypePropertyPaths("TypeA", "field1", "field2.subfield2")
+                        .addProjectionTypePropertyPaths("TypeB", "field7")
+                        .addProjectionTypePropertyPaths("TypeC")
+                        .build();
+
+        Map<String, List<String>> typePropertyPathMap = searchSpec.getProjectionTypePropertyPaths();
+        assertThat(typePropertyPathMap.keySet()).containsExactly("TypeA", "TypeB", "TypeC");
+        assertThat(typePropertyPathMap.get("TypeA")).containsExactly("field1", "field2.subfield2");
+        assertThat(typePropertyPathMap.get("TypeB")).containsExactly("field7");
+        assertThat(typePropertyPathMap.get("TypeC")).isEmpty();
+    }
 }