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();
+ }
}