Merge "Update framework from Jetpack." into sc-dev

This commit is contained in:
Alexander Dorokhine
2021-02-03 04:46:48 +00:00
committed by Android (Google) Code Review
25 changed files with 369 additions and 159 deletions

View File

@@ -143,11 +143,11 @@ package android.app.appsearch {
method public void close(); method public void close();
method public void getByUri(@NonNull android.app.appsearch.GetByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,android.app.appsearch.GenericDocument>); method public void getByUri(@NonNull android.app.appsearch.GetByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,android.app.appsearch.GenericDocument>);
method public void getSchema(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.util.Set<android.app.appsearch.AppSearchSchema>>>); method public void getSchema(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.util.Set<android.app.appsearch.AppSearchSchema>>>);
method public void putDocuments(@NonNull android.app.appsearch.PutDocumentsRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>); method public void put(@NonNull android.app.appsearch.PutDocumentsRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>);
method @NonNull public android.app.appsearch.SearchResults query(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor); method public void remove(@NonNull android.app.appsearch.RemoveByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>);
method public void removeByQuery(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>); method public void remove(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>);
method public void removeByUri(@NonNull android.app.appsearch.RemoveByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>);
method @NonNull public void reportUsage(@NonNull android.app.appsearch.ReportUsageRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>); method @NonNull public void reportUsage(@NonNull android.app.appsearch.ReportUsageRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>);
method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor);
method public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>); method public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>);
} }
@@ -216,7 +216,7 @@ package android.app.appsearch {
public class GlobalSearchSession implements java.io.Closeable { public class GlobalSearchSession implements java.io.Closeable {
method public void close(); method public void close();
method @NonNull public android.app.appsearch.SearchResults query(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor); method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor);
} }
public class PackageIdentifier { public class PackageIdentifier {
@@ -226,13 +226,13 @@ package android.app.appsearch {
} }
public final class PutDocumentsRequest { public final class PutDocumentsRequest {
method @NonNull public java.util.List<android.app.appsearch.GenericDocument> getDocuments(); method @NonNull public java.util.List<android.app.appsearch.GenericDocument> getGenericDocuments();
} }
public static final class PutDocumentsRequest.Builder { public static final class PutDocumentsRequest.Builder {
ctor public PutDocumentsRequest.Builder(); ctor public PutDocumentsRequest.Builder();
method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocument(@NonNull android.app.appsearch.GenericDocument...); method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocuments(@NonNull android.app.appsearch.GenericDocument...);
method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocument(@NonNull java.util.Collection<? extends android.app.appsearch.GenericDocument>); method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocuments(@NonNull java.util.Collection<? extends android.app.appsearch.GenericDocument>);
method @NonNull public android.app.appsearch.PutDocumentsRequest build(); method @NonNull public android.app.appsearch.PutDocumentsRequest build();
} }
@@ -290,14 +290,14 @@ package android.app.appsearch {
} }
public final class SearchSpec { public final class SearchSpec {
method @NonNull public java.util.List<java.lang.String> getFilterNamespaces();
method @NonNull public java.util.List<java.lang.String> getFilterPackageNames(); method @NonNull public java.util.List<java.lang.String> getFilterPackageNames();
method @NonNull public java.util.List<java.lang.String> getFilterSchemas();
method public int getMaxSnippetSize(); method public int getMaxSnippetSize();
method @NonNull public java.util.List<java.lang.String> getNamespaces();
method public int getOrder(); method public int getOrder();
method @NonNull public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getProjections(); method @NonNull public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getProjections();
method public int getRankingStrategy(); method public int getRankingStrategy();
method public int getResultCountPerPage(); method public int getResultCountPerPage();
method @NonNull public java.util.List<java.lang.String> getSchemaTypes();
method public int getSnippetCount(); method public int getSnippetCount();
method public int getSnippetCountPerProperty(); method public int getSnippetCountPerProperty();
method public int getTermMatch(); method public int getTermMatch();
@@ -316,14 +316,14 @@ package android.app.appsearch {
public static final class SearchSpec.Builder { public static final class SearchSpec.Builder {
ctor public SearchSpec.Builder(); ctor public SearchSpec.Builder();
method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterNamespaces(@NonNull java.lang.String...);
method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterNamespaces(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterPackageNames(@NonNull java.lang.String...); method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterPackageNames(@NonNull java.lang.String...);
method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterPackageNames(@NonNull java.util.Collection<java.lang.String>); method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterPackageNames(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SearchSpec.Builder addNamespace(@NonNull java.lang.String...); method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterSchemas(@NonNull java.lang.String...);
method @NonNull public android.app.appsearch.SearchSpec.Builder addNamespace(@NonNull java.util.Collection<java.lang.String>); method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterSchemas(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SearchSpec.Builder addProjection(@NonNull String, @NonNull java.lang.String...); method @NonNull public android.app.appsearch.SearchSpec.Builder addProjection(@NonNull String, @NonNull java.lang.String...);
method @NonNull public android.app.appsearch.SearchSpec.Builder addProjection(@NonNull String, @NonNull java.util.Collection<java.lang.String>); method @NonNull public android.app.appsearch.SearchSpec.Builder addProjection(@NonNull String, @NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SearchSpec.Builder addSchemaType(@NonNull java.lang.String...);
method @NonNull public android.app.appsearch.SearchSpec.Builder addSchemaType(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SearchSpec build(); method @NonNull public android.app.appsearch.SearchSpec build();
method @NonNull public android.app.appsearch.SearchSpec.Builder setMaxSnippetSize(@IntRange(from=0, to=android.app.appsearch.SearchSpec.MAX_SNIPPET_SIZE_LIMIT) int); method @NonNull public android.app.appsearch.SearchSpec.Builder setMaxSnippetSize(@IntRange(from=0, to=android.app.appsearch.SearchSpec.MAX_SNIPPET_SIZE_LIMIT) int);
method @NonNull public android.app.appsearch.SearchSpec.Builder setOrder(int); method @NonNull public android.app.appsearch.SearchSpec.Builder setOrder(int);
@@ -344,8 +344,8 @@ package android.app.appsearch {
public static final class SetSchemaRequest.Builder { public static final class SetSchemaRequest.Builder {
ctor public SetSchemaRequest.Builder(); ctor public SetSchemaRequest.Builder();
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchema(@NonNull android.app.appsearch.AppSearchSchema...); method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchemas(@NonNull android.app.appsearch.AppSearchSchema...);
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchema(@NonNull java.util.Collection<android.app.appsearch.AppSearchSchema>); method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchemas(@NonNull java.util.Collection<android.app.appsearch.AppSearchSchema>);
method @NonNull public android.app.appsearch.SetSchemaRequest build(); method @NonNull public android.app.appsearch.SetSchemaRequest build();
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setForceOverride(boolean); method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setForceOverride(boolean);
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setMigrator(@NonNull String, @NonNull android.app.appsearch.AppSearchSchema.Migrator); method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setMigrator(@NonNull String, @NonNull android.app.appsearch.AppSearchSchema.Migrator);

View File

@@ -270,12 +270,12 @@ public class AppSearchManager {
* they were successfully indexed, or a failed {@link AppSearchResult} otherwise. * they were successfully indexed, or a failed {@link AppSearchResult} otherwise.
* @throws RuntimeException If an error occurred during the execution. * @throws RuntimeException If an error occurred during the execution.
* @hide * @hide
* @deprecated use {@link AppSearchSession#putDocuments} instead. * @deprecated use {@link AppSearchSession#put} instead.
*/ */
public AppSearchBatchResult<String, Void> putDocuments(@NonNull PutDocumentsRequest request) { public AppSearchBatchResult<String, Void> putDocuments(@NonNull PutDocumentsRequest request) {
// TODO(b/146386470): Transmit these documents as a RemoteStream instead of sending them in // TODO(b/146386470): Transmit these documents as a RemoteStream instead of sending them in
// one big list. // one big list.
List<GenericDocument> documents = request.getDocuments(); List<GenericDocument> documents = request.getGenericDocuments();
List<Bundle> documentBundles = new ArrayList<>(documents.size()); List<Bundle> documentBundles = new ArrayList<>(documents.size());
for (int i = 0; i < documents.size(); i++) { for (int i = 0; i < documents.size(); i++) {
documentBundles.add(documents.get(i).getBundle()); documentBundles.add(documents.get(i).getBundle());
@@ -330,7 +330,7 @@ public class AppSearchManager {
DEFAULT_DATABASE_NAME, DEFAULT_DATABASE_NAME,
request.getNamespace(), request.getNamespace(),
uris, uris,
request.getProjectionsVisibleToPackagesInternal(), request.getProjectionsInternal(),
mContext.getUserId(), mContext.getUserId(),
new IAppSearchBatchResultCallback.Stub() { new IAppSearchBatchResultCallback.Stub() {
public void onResult(AppSearchBatchResult result) { public void onResult(AppSearchBatchResult result) {
@@ -465,7 +465,7 @@ public class AppSearchManager {
* {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}. * {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}.
* @throws RuntimeException If an error occurred during the execution. * @throws RuntimeException If an error occurred during the execution.
* @hide * @hide
* @deprecated use {@link AppSearchSession#removeByUri} instead. * @deprecated use {@link AppSearchSession#remove} instead.
*/ */
public AppSearchBatchResult<String, Void> removeByUri(@NonNull RemoveByUriRequest request) { public AppSearchBatchResult<String, Void> removeByUri(@NonNull RemoveByUriRequest request) {
List<String> uris = new ArrayList<>(request.getUris()); List<String> uris = new ArrayList<>(request.getUris());

View File

@@ -102,7 +102,7 @@ public final class AppSearchSession implements Closeable {
} }
/** /**
* Sets the schema that will be used by documents provided to the {@link #putDocuments} method. * Sets the schema that will be used by documents provided to the {@link #put} method.
* *
* <p>The schema provided here is compared to the stored copy of the schema previously supplied * <p>The schema provided here is compared to the stored copy of the schema previously supplied
* to {@link #setSchema}, if any, to determine how to treat existing documents. The following * to {@link #setSchema}, if any, to determine how to treat existing documents. The following
@@ -268,7 +268,7 @@ public final class AppSearchSession implements Closeable {
* {@link Throwable} if an unexpected internal error occurred in AppSearch * {@link Throwable} if an unexpected internal error occurred in AppSearch
* service. * service.
*/ */
public void putDocuments( public void put(
@NonNull PutDocumentsRequest request, @NonNull PutDocumentsRequest request,
@NonNull @CallbackExecutor Executor executor, @NonNull @CallbackExecutor Executor executor,
@NonNull BatchResultCallback<String, Void> callback) { @NonNull BatchResultCallback<String, Void> callback) {
@@ -276,7 +276,7 @@ public final class AppSearchSession implements Closeable {
Objects.requireNonNull(executor); Objects.requireNonNull(executor);
Objects.requireNonNull(callback); Objects.requireNonNull(callback);
Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed"); Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
List<GenericDocument> documents = request.getDocuments(); List<GenericDocument> documents = request.getGenericDocuments();
List<Bundle> documentBundles = new ArrayList<>(documents.size()); List<Bundle> documentBundles = new ArrayList<>(documents.size());
for (int i = 0; i < documents.size(); i++) { for (int i = 0; i < documents.size(); i++) {
documentBundles.add(documents.get(i).getBundle()); documentBundles.add(documents.get(i).getBundle());
@@ -327,7 +327,7 @@ public final class AppSearchSession implements Closeable {
mDatabaseName, mDatabaseName,
request.getNamespace(), request.getNamespace(),
new ArrayList<>(request.getUris()), new ArrayList<>(request.getUris()),
request.getProjectionsVisibleToPackagesInternal(), request.getProjectionsInternal(),
mUserId, mUserId,
new IAppSearchBatchResultCallback.Stub() { new IAppSearchBatchResultCallback.Stub() {
public void onResult(AppSearchBatchResult result) { public void onResult(AppSearchBatchResult result) {
@@ -423,7 +423,7 @@ public final class AppSearchSession implements Closeable {
* @return The search result of performing this operation. * @return The search result of performing this operation.
*/ */
@NonNull @NonNull
public SearchResults query( public SearchResults search(
@NonNull String queryExpression, @NonNull String queryExpression,
@NonNull SearchSpec searchSpec, @NonNull SearchSpec searchSpec,
@NonNull @CallbackExecutor Executor executor) { @NonNull @CallbackExecutor Executor executor) {
@@ -441,7 +441,7 @@ public final class AppSearchSession implements Closeable {
* <p>A usage report represents an event in which a user interacted with or viewed a document. * <p>A usage report represents an event in which a user interacted with or viewed a document.
* *
* <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency * <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency
* metrics for that particular document. These metrics are used for ordering {@link #query} * metrics for that particular document. These metrics are used for ordering {@link #search}
* results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and * results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and
* {@link SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies. * {@link SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies.
* *
@@ -494,7 +494,7 @@ public final class AppSearchSession implements Closeable {
* {@link Throwable} if an unexpected internal error occurred in AppSearch * {@link Throwable} if an unexpected internal error occurred in AppSearch
* service. * service.
*/ */
public void removeByUri( public void remove(
@NonNull RemoveByUriRequest request, @NonNull RemoveByUriRequest request,
@NonNull @CallbackExecutor Executor executor, @NonNull @CallbackExecutor Executor executor,
@NonNull BatchResultCallback<String, Void> callback) { @NonNull BatchResultCallback<String, Void> callback) {
@@ -523,7 +523,8 @@ public final class AppSearchSession implements Closeable {
/** /**
* Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they * Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they
* match the {@code queryExpression} in given namespaces and schemaTypes which is set via * match the {@code queryExpression} in given namespaces and schemaTypes which is set via
* {@link SearchSpec.Builder#addNamespace} and {@link SearchSpec.Builder#addSchemaType}. * {@link SearchSpec.Builder#addFilterNamespaces} and
* {@link SearchSpec.Builder#addFilterSchemas}.
* *
* <p> An empty {@code queryExpression} matches all documents. * <p> An empty {@code queryExpression} matches all documents.
* *
@@ -539,7 +540,8 @@ public final class AppSearchSession implements Closeable {
* the operation succeeds, the callback will be invoked with * the operation succeeds, the callback will be invoked with
* {@code null}. * {@code null}.
*/ */
public void removeByQuery(@NonNull String queryExpression, public void remove(
@NonNull String queryExpression,
@NonNull SearchSpec searchSpec, @NonNull SearchSpec searchSpec,
@NonNull @CallbackExecutor Executor executor, @NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<Void>> callback) { @NonNull Consumer<AppSearchResult<Void>> callback) {

View File

@@ -134,7 +134,7 @@ public class GlobalSearchSession implements Closeable {
* @return The search result of performing this operation. * @return The search result of performing this operation.
*/ */
@NonNull @NonNull
public SearchResults query( public SearchResults search(
@NonNull String queryExpression, @NonNull String queryExpression,
@NonNull SearchSpec searchSpec, @NonNull SearchSpec searchSpec,
@NonNull @CallbackExecutor Executor executor) { @NonNull @CallbackExecutor Executor executor) {

View File

@@ -141,10 +141,6 @@ public final class AppSearchSchema {
} }
/** Adds a property to the given type. */ /** Adds a property to the given type. */
// TODO(b/171360120): MissingGetterMatchingBuilder expects a method called getPropertys, but
// we provide the (correct) method getProperties. Once the bug referenced in this TODO is
// fixed, remove this SuppressLint.
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull @NonNull
public AppSearchSchema.Builder addProperty(@NonNull PropertyConfig propertyConfig) { public AppSearchSchema.Builder addProperty(@NonNull PropertyConfig propertyConfig) {
Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkState(!mBuilt, "Builder has already been used");

View File

@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.appsearch.util.BundleUtil; import android.app.appsearch.util.BundleUtil;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log; import android.util.Log;
import com.android.internal.util.Preconditions; import com.android.internal.util.Preconditions;
@@ -37,9 +38,9 @@ import java.util.Set;
* *
* <p>Documents are constructed via {@link GenericDocument.Builder}. * <p>Documents are constructed via {@link GenericDocument.Builder}.
* *
* @see AppSearchSession#putDocuments * @see AppSearchSession#put
* @see AppSearchSession#getByUri * @see AppSearchSession#getByUri
* @see AppSearchSession#query * @see AppSearchSession#search
*/ */
public class GenericDocument { public class GenericDocument {
private static final String TAG = "AppSearchGenericDocumen"; private static final String TAG = "AppSearchGenericDocumen";
@@ -210,7 +211,7 @@ public class GenericDocument {
Object property = mProperties.get(key); Object property = mProperties.get(key);
if (property instanceof ArrayList) { if (property instanceof ArrayList) {
return getPropertyBytesArray(key); return getPropertyBytesArray(key);
} else if (property instanceof Bundle[]) { } else if (property instanceof Parcelable[]) {
return getPropertyDocumentArray(key); return getPropertyDocumentArray(key);
} }
return property; return property;
@@ -436,7 +437,7 @@ public class GenericDocument {
@Nullable @Nullable
public GenericDocument[] getPropertyDocumentArray(@NonNull String key) { public GenericDocument[] getPropertyDocumentArray(@NonNull String key) {
Preconditions.checkNotNull(key); Preconditions.checkNotNull(key);
Bundle[] bundles = getAndCastPropertyArray(key, Bundle[].class); Parcelable[] bundles = getAndCastPropertyArray(key, Parcelable[].class);
if (bundles == null || bundles.length == 0) { if (bundles == null || bundles.length == 0) {
return null; return null;
} }
@@ -446,7 +447,18 @@ public class GenericDocument {
Log.e(TAG, "The inner bundle is null at " + i + ", for key: " + key); Log.e(TAG, "The inner bundle is null at " + i + ", for key: " + key);
continue; continue;
} }
documents[i] = new GenericDocument(bundles[i]); if (!(bundles[i] instanceof Bundle)) {
Log.e(
TAG,
"The inner element at "
+ i
+ " is a "
+ bundles[i].getClass()
+ ", not a Bundle for key: "
+ key);
continue;
}
documents[i] = new GenericDocument((Bundle) bundles[i]);
} }
return documents; return documents;
} }
@@ -574,8 +586,8 @@ public class GenericDocument {
* @param schemaType The schema type of the {@link GenericDocument}. The passed-in {@code * @param schemaType The schema type of the {@link GenericDocument}. The passed-in {@code
* schemaType} must be defined using {@link AppSearchSession#setSchema} prior to * schemaType} must be defined using {@link AppSearchSession#setSchema} prior to
* inserting a document of this {@code schemaType} into the AppSearch index using {@link * inserting a document of this {@code schemaType} into the AppSearch index using {@link
* AppSearchSession#putDocuments}. Otherwise, the document will be rejected by {@link * AppSearchSession#put}. Otherwise, the document will be rejected by {@link
* AppSearchSession#putDocuments}. * AppSearchSession#put}.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Builder(@NonNull String uri, @NonNull String schemaType) { public Builder(@NonNull String uri, @NonNull String schemaType) {
@@ -817,7 +829,7 @@ public class GenericDocument {
private void putInPropertyBundle(@NonNull String key, @NonNull GenericDocument[] values) { private void putInPropertyBundle(@NonNull String key, @NonNull GenericDocument[] values) {
validateRepeatedPropertyLength(key, values.length); validateRepeatedPropertyLength(key, values.length);
Bundle[] documentBundles = new Bundle[values.length]; Parcelable[] documentBundles = new Parcelable[values.length];
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
if (values[i] == null) { if (values[i] == null) {
throw new IllegalArgumentException("The document at " + i + " is null."); throw new IllegalArgumentException("The document at " + i + " is null.");

View File

@@ -96,7 +96,7 @@ public final class GetByUriRequest {
* @hide * @hide
*/ */
@NonNull @NonNull
public Map<String, List<String>> getProjectionsVisibleToPackagesInternal() { public Map<String, List<String>> getProjectionsInternal() {
return mTypePropertyPathsMap; return mTypePropertyPathsMap;
} }

View File

@@ -16,8 +16,8 @@
package android.app.appsearch; package android.app.appsearch;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.SuppressLint;
import com.android.internal.util.Preconditions; import com.android.internal.util.Preconditions;
@@ -41,7 +41,7 @@ public final class PutDocumentsRequest {
/** Returns the documents that are part of this request. */ /** Returns the documents that are part of this request. */
@NonNull @NonNull
public List<GenericDocument> getDocuments() { public List<GenericDocument> getGenericDocuments() {
return Collections.unmodifiableList(mDocuments); return Collections.unmodifiableList(mDocuments);
} }
@@ -55,17 +55,15 @@ public final class PutDocumentsRequest {
private boolean mBuilt = false; private boolean mBuilt = false;
/** Adds one or more {@link GenericDocument} objects to the request. */ /** Adds one or more {@link GenericDocument} objects to the request. */
@SuppressLint("MissingGetterMatchingBuilder") // Merged list available from getDocuments()
@NonNull @NonNull
public Builder addGenericDocument(@NonNull GenericDocument... documents) { public Builder addGenericDocuments(@NonNull GenericDocument... documents) {
Preconditions.checkNotNull(documents); Preconditions.checkNotNull(documents);
return addGenericDocument(Arrays.asList(documents)); return addGenericDocuments(Arrays.asList(documents));
} }
/** Adds a collection of {@link GenericDocument} objects to the request. */ /** Adds a collection of {@link GenericDocument} objects to the request. */
@SuppressLint("MissingGetterMatchingBuilder") // Merged list available from getDocuments()
@NonNull @NonNull
public Builder addGenericDocument( public Builder addGenericDocuments(
@NonNull Collection<? extends GenericDocument> documents) { @NonNull Collection<? extends GenericDocument> documents) {
Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkState(!mBuilt, "Builder has already been used");
Preconditions.checkNotNull(documents); Preconditions.checkNotNull(documents);

View File

@@ -29,7 +29,7 @@ import java.util.Set;
/** /**
* Encapsulates a request to remove documents by namespace and URI. * Encapsulates a request to remove documents by namespace and URI.
* *
* @see AppSearchSession#removeByUri * @see AppSearchSession#remove
*/ */
public final class RemoveByUriRequest { public final class RemoveByUriRequest {
private final String mNamespace; private final String mNamespace;

View File

@@ -19,7 +19,6 @@ package android.app.appsearch;
import android.annotation.IntDef; import android.annotation.IntDef;
import android.annotation.IntRange; import android.annotation.IntRange;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.app.appsearch.exceptions.IllegalSearchSpecException; import android.app.appsearch.exceptions.IllegalSearchSpecException;
import android.os.Bundle; import android.os.Bundle;
import android.util.ArrayMap; import android.util.ArrayMap;
@@ -49,7 +48,7 @@ public final class SearchSpec {
public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*"; public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
static final String TERM_MATCH_TYPE_FIELD = "termMatchType"; static final String TERM_MATCH_TYPE_FIELD = "termMatchType";
static final String SCHEMA_TYPE_FIELD = "schemaType"; static final String SCHEMA_FIELD = "schema";
static final String NAMESPACE_FIELD = "namespace"; static final String NAMESPACE_FIELD = "namespace";
static final String PACKAGE_NAME_FIELD = "packageName"; static final String PACKAGE_NAME_FIELD = "packageName";
static final String NUM_PER_PAGE_FIELD = "numPerPage"; static final String NUM_PER_PAGE_FIELD = "numPerPage";
@@ -171,12 +170,12 @@ public final class SearchSpec {
* <p>If empty, the query will search over all schema types. * <p>If empty, the query will search over all schema types.
*/ */
@NonNull @NonNull
public List<String> getSchemaTypes() { public List<String> getFilterSchemas() {
List<String> schemaTypes = mBundle.getStringArrayList(SCHEMA_TYPE_FIELD); List<String> schemas = mBundle.getStringArrayList(SCHEMA_FIELD);
if (schemaTypes == null) { if (schemas == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
return Collections.unmodifiableList(schemaTypes); return Collections.unmodifiableList(schemas);
} }
/** /**
@@ -185,7 +184,7 @@ public final class SearchSpec {
* <p>If empty, the query will search over all namespaces. * <p>If empty, the query will search over all namespaces.
*/ */
@NonNull @NonNull
public List<String> getNamespaces() { public List<String> getFilterNamespaces() {
List<String> namespaces = mBundle.getStringArrayList(NAMESPACE_FIELD); List<String> namespaces = mBundle.getStringArrayList(NAMESPACE_FIELD);
if (namespaces == null) { if (namespaces == null) {
return Collections.emptyList(); return Collections.emptyList();
@@ -209,24 +208,6 @@ public final class SearchSpec {
return Collections.unmodifiableList(packageNames); return Collections.unmodifiableList(packageNames);
} }
/**
* Returns the list of package names to search over.
*
* <p>If unset, the query will search over all packages that the caller has access to. If
* package names are specified which caller doesn't have access to, then those package names
* will be ignored.
*
* @hide
*/
@NonNull
public List<String> getPackageNames() {
List<String> packageNames = mBundle.getStringArrayList(PACKAGE_NAME_FIELD);
if (packageNames == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(packageNames);
}
/** Returns the number of results per page in the result set. */ /** Returns the number of results per page in the result set. */
public int getResultCountPerPage() { public int getResultCountPerPage() {
return mBundle.getInt(NUM_PER_PAGE_FIELD, DEFAULT_NUM_PER_PAGE); return mBundle.getInt(NUM_PER_PAGE_FIELD, DEFAULT_NUM_PER_PAGE);
@@ -270,11 +251,10 @@ public final class SearchSpec {
@NonNull @NonNull
public Map<String, List<String>> getProjections() { public Map<String, List<String>> getProjections() {
Bundle typePropertyPathsBundle = mBundle.getBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD); Bundle typePropertyPathsBundle = mBundle.getBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD);
Set<String> schemaTypes = typePropertyPathsBundle.keySet(); Set<String> schemas = typePropertyPathsBundle.keySet();
Map<String, List<String>> typePropertyPathsMap = new ArrayMap<>(schemaTypes.size()); Map<String, List<String>> typePropertyPathsMap = new ArrayMap<>(schemas.size());
for (String schemaType : schemaTypes) { for (String schema : schemas) {
typePropertyPathsMap.put( typePropertyPathsMap.put(schema, typePropertyPathsBundle.getStringArrayList(schema));
schemaType, typePropertyPathsBundle.getStringArrayList(schemaType));
} }
return typePropertyPathsMap; return typePropertyPathsMap;
} }
@@ -283,7 +263,7 @@ public final class SearchSpec {
public static final class Builder { public static final class Builder {
private final Bundle mBundle; private final Bundle mBundle;
private final ArrayList<String> mSchemaTypes = new ArrayList<>(); private final ArrayList<String> mSchemas = new ArrayList<>();
private final ArrayList<String> mNamespaces = new ArrayList<>(); private final ArrayList<String> mNamespaces = new ArrayList<>();
private final ArrayList<String> mPackageNames = new ArrayList<>(); private final ArrayList<String> mPackageNames = new ArrayList<>();
private final Bundle mProjectionTypePropertyMasks = new Bundle(); private final Bundle mProjectionTypePropertyMasks = new Bundle();
@@ -312,10 +292,10 @@ public final class SearchSpec {
* <p>If unset, the query will search over all schema types. * <p>If unset, the query will search over all schema types.
*/ */
@NonNull @NonNull
public Builder addSchemaType(@NonNull String... schemaTypes) { public Builder addFilterSchemas(@NonNull String... schemas) {
Preconditions.checkNotNull(schemaTypes); Preconditions.checkNotNull(schemas);
Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkState(!mBuilt, "Builder has already been used");
return addSchemaType(Arrays.asList(schemaTypes)); return addFilterSchemas(Arrays.asList(schemas));
} }
/** /**
@@ -325,10 +305,10 @@ public final class SearchSpec {
* <p>If unset, the query will search over all schema types. * <p>If unset, the query will search over all schema types.
*/ */
@NonNull @NonNull
public Builder addSchemaType(@NonNull Collection<String> schemaTypes) { public Builder addFilterSchemas(@NonNull Collection<String> schemas) {
Preconditions.checkNotNull(schemaTypes); Preconditions.checkNotNull(schemas);
Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkState(!mBuilt, "Builder has already been used");
mSchemaTypes.addAll(schemaTypes); mSchemas.addAll(schemas);
return this; return this;
} }
@@ -339,10 +319,10 @@ public final class SearchSpec {
* <p>If unset, the query will search over all namespaces. * <p>If unset, the query will search over all namespaces.
*/ */
@NonNull @NonNull
public Builder addNamespace(@NonNull String... namespaces) { public Builder addFilterNamespaces(@NonNull String... namespaces) {
Preconditions.checkNotNull(namespaces); Preconditions.checkNotNull(namespaces);
Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkState(!mBuilt, "Builder has already been used");
return addNamespace(Arrays.asList(namespaces)); return addFilterNamespaces(Arrays.asList(namespaces));
} }
/** /**
@@ -352,7 +332,7 @@ public final class SearchSpec {
* <p>If unset, the query will search over all namespaces. * <p>If unset, the query will search over all namespaces.
*/ */
@NonNull @NonNull
public Builder addNamespace(@NonNull Collection<String> namespaces) { public Builder addFilterNamespaces(@NonNull Collection<String> namespaces) {
Preconditions.checkNotNull(namespaces); Preconditions.checkNotNull(namespaces);
Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkState(!mBuilt, "Builder has already been used");
mNamespaces.addAll(namespaces); mNamespaces.addAll(namespaces);
@@ -367,9 +347,6 @@ public final class SearchSpec {
* package names are specified which caller doesn't have access to, then those package names * package names are specified which caller doesn't have access to, then those package names
* will be ignored. * will be ignored.
*/ */
// Getter is called "getFilterPackageNames" (as opposed to the suggested
// "getFilterPackageNameses")
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull @NonNull
public Builder addFilterPackageNames(@NonNull String... packageNames) { public Builder addFilterPackageNames(@NonNull String... packageNames) {
Preconditions.checkNotNull(packageNames); Preconditions.checkNotNull(packageNames);
@@ -385,9 +362,6 @@ public final class SearchSpec {
* package names are specified which caller doesn't have access to, then those package names * package names are specified which caller doesn't have access to, then those package names
* will be ignored. * will be ignored.
*/ */
// Getter is called "getFilterPackageNames" (as opposed to the suggested
// "getFilterPackageNameses")
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull @NonNull
public Builder addFilterPackageNames(@NonNull Collection<String> packageNames) { public Builder addFilterPackageNames(@NonNull Collection<String> packageNames) {
Preconditions.checkNotNull(packageNames); Preconditions.checkNotNull(packageNames);
@@ -537,7 +511,7 @@ public final class SearchSpec {
* type property paths: * type property paths:
* *
* <pre>{@code * <pre>{@code
* {schemaType: "Email", ["subject", "sender.name", "recipients.name"]} * {schema: "Email", ["subject", "sender.name", "recipients.name"]}
* }</pre> * }</pre>
* *
* <p>The above document will be returned as: * <p>The above document will be returned as:
@@ -561,9 +535,9 @@ public final class SearchSpec {
*/ */
@NonNull @NonNull
public SearchSpec.Builder addProjection( public SearchSpec.Builder addProjection(
@NonNull String schemaType, @NonNull String... propertyPaths) { @NonNull String schema, @NonNull String... propertyPaths) {
Preconditions.checkNotNull(propertyPaths); Preconditions.checkNotNull(propertyPaths);
return addProjection(schemaType, Arrays.asList(propertyPaths)); return addProjection(schema, Arrays.asList(propertyPaths));
} }
/** /**
@@ -583,16 +557,16 @@ public final class SearchSpec {
*/ */
@NonNull @NonNull
public SearchSpec.Builder addProjection( public SearchSpec.Builder addProjection(
@NonNull String schemaType, @NonNull Collection<String> propertyPaths) { @NonNull String schema, @NonNull Collection<String> propertyPaths) {
Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkState(!mBuilt, "Builder has already been used");
Preconditions.checkNotNull(schemaType); Preconditions.checkNotNull(schema);
Preconditions.checkNotNull(propertyPaths); Preconditions.checkNotNull(propertyPaths);
ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size()); ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
for (String propertyPath : propertyPaths) { for (String propertyPath : propertyPaths) {
Preconditions.checkNotNull(propertyPath); Preconditions.checkNotNull(propertyPath);
propertyPathsArrayList.add(propertyPath); propertyPathsArrayList.add(propertyPath);
} }
mProjectionTypePropertyMasks.putStringArrayList(schemaType, propertyPathsArrayList); mProjectionTypePropertyMasks.putStringArrayList(schema, propertyPathsArrayList);
return this; return this;
} }
@@ -608,7 +582,7 @@ public final class SearchSpec {
throw new IllegalSearchSpecException("Missing termMatchType field."); throw new IllegalSearchSpecException("Missing termMatchType field.");
} }
mBundle.putStringArrayList(NAMESPACE_FIELD, mNamespaces); mBundle.putStringArrayList(NAMESPACE_FIELD, mNamespaces);
mBundle.putStringArrayList(SCHEMA_TYPE_FIELD, mSchemaTypes); mBundle.putStringArrayList(SCHEMA_FIELD, mSchemas);
mBundle.putStringArrayList(PACKAGE_NAME_FIELD, mPackageNames); mBundle.putStringArrayList(PACKAGE_NAME_FIELD, mPackageNames);
mBundle.putBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD, mProjectionTypePropertyMasks); mBundle.putBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD, mProjectionTypePropertyMasks);
mBuilt = true; mBuilt = true;

View File

@@ -126,9 +126,9 @@ public final class SetSchemaRequest {
* <p>Any documents of these types will be visible on system UI surfaces by default. * <p>Any documents of these types will be visible on system UI surfaces by default.
*/ */
@NonNull @NonNull
public Builder addSchema(@NonNull AppSearchSchema... schemas) { public Builder addSchemas(@NonNull AppSearchSchema... schemas) {
Preconditions.checkNotNull(schemas); Preconditions.checkNotNull(schemas);
return addSchema(Arrays.asList(schemas)); return addSchemas(Arrays.asList(schemas));
} }
/** /**
@@ -137,7 +137,7 @@ public final class SetSchemaRequest {
* <p>Any documents of these types will be visible on system UI surfaces by default. * <p>Any documents of these types will be visible on system UI surfaces by default.
*/ */
@NonNull @NonNull
public Builder addSchema(@NonNull Collection<AppSearchSchema> schemas) { public Builder addSchemas(@NonNull Collection<AppSearchSchema> schemas) {
Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkState(!mBuilt, "Builder has already been used");
Preconditions.checkNotNull(schemas); Preconditions.checkNotNull(schemas);
mSchemas.addAll(schemas); mSchemas.addAll(schemas);

View File

@@ -179,9 +179,9 @@ public class SetSchemaResponse {
/** Adds migratedTypes to the list of migrated schema types. */ /** Adds migratedTypes to the list of migrated schema types. */
@NonNull @NonNull
public Builder addMigratedType(@NonNull String migratedType) { public Builder addMigratedType(@NonNull Collection<String> migratedTypes) {
Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkState(!mBuilt, "Builder has already been used");
mMigratedTypes.add(Preconditions.checkNotNull(migratedType)); mMigratedTypes.addAll(Preconditions.checkNotNull(migratedTypes));
return this; return this;
} }

View File

@@ -195,8 +195,7 @@ public final class AppSearchImpl {
mIcingSearchEngineLocked = new IcingSearchEngine(options); mIcingSearchEngineLocked = new IcingSearchEngine(options);
mVisibilityStoreLocked = mVisibilityStoreLocked =
new VisibilityStore( new VisibilityStore(this, context, userId, globalQuerierPackage);
this, context, userId, globalQuerierPackage);
InitializeResultProto initializeResultProto = mIcingSearchEngineLocked.initialize(); InitializeResultProto initializeResultProto = mIcingSearchEngineLocked.initialize();
SchemaProto schemaProto; SchemaProto schemaProto;
@@ -508,8 +507,8 @@ public final class AppSearchImpl {
@NonNull String queryExpression, @NonNull String queryExpression,
@NonNull SearchSpec searchSpec) @NonNull SearchSpec searchSpec)
throws AppSearchException { throws AppSearchException {
if (!searchSpec.getPackageNames().isEmpty() List<String> filterPackageNames = searchSpec.getFilterPackageNames();
&& !searchSpec.getPackageNames().contains(packageName)) { if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
// Client wanted to query over some packages that weren't its own. This isn't // Client wanted to query over some packages that weren't its own. This isn't
// allowed through local query so we can return early with no results. // allowed through local query so we can return early with no results.
return new SearchResultPage(Bundle.EMPTY); return new SearchResultPage(Bundle.EMPTY);
@@ -553,7 +552,7 @@ public final class AppSearchImpl {
throws AppSearchException { throws AppSearchException {
mReadWriteLock.readLock().lock(); mReadWriteLock.readLock().lock();
try { try {
Set<String> packageFilters = new ArraySet<>(searchSpec.getPackageNames()); Set<String> packageFilters = new ArraySet<>(searchSpec.getFilterPackageNames());
Set<String> prefixFilters = new ArraySet<>(); Set<String> prefixFilters = new ArraySet<>();
Set<String> allPrefixes = mNamespaceMapLocked.keySet(); Set<String> allPrefixes = mNamespaceMapLocked.keySet();
if (packageFilters.isEmpty()) { if (packageFilters.isEmpty()) {
@@ -573,7 +572,7 @@ public final class AppSearchImpl {
// Find which schemas the client is allowed to query over. // Find which schemas the client is allowed to query over.
Set<String> allowedPrefixedSchemas = new ArraySet<>(); Set<String> allowedPrefixedSchemas = new ArraySet<>();
List<String> schemaFilters = searchSpec.getSchemaTypes(); List<String> schemaFilters = searchSpec.getFilterSchemas();
for (String prefix : prefixFilters) { for (String prefix : prefixFilters) {
String packageName = getPackageName(prefix); String packageName = getPackageName(prefix);
@@ -752,8 +751,8 @@ public final class AppSearchImpl {
@NonNull String queryExpression, @NonNull String queryExpression,
@NonNull SearchSpec searchSpec) @NonNull SearchSpec searchSpec)
throws AppSearchException { throws AppSearchException {
if (!searchSpec.getPackageNames().isEmpty() List<String> filterPackageNames = searchSpec.getFilterPackageNames();
&& !searchSpec.getPackageNames().contains(packageName)) { if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
// We're only removing documents within the parameter `packageName`. If we're not // We're only removing documents within the parameter `packageName`. If we're not
// restricting our remove-query to this package name, then there's nothing for us to // restricting our remove-query to this package name, then there's nothing for us to
// remove. // remove.
@@ -1085,7 +1084,7 @@ public final class AppSearchImpl {
Set<String> allowedPrefixedSchemas = new ArraySet<>(); Set<String> allowedPrefixedSchemas = new ArraySet<>();
// Add all the schema filters the client specified. // Add all the schema filters the client specified.
List<String> schemaFilters = searchSpec.getSchemaTypes(); List<String> schemaFilters = searchSpec.getFilterSchemas();
for (int i = 0; i < schemaFilters.size(); i++) { for (int i = 0; i < schemaFilters.size(); i++) {
allowedPrefixedSchemas.add(prefix + schemaFilters.get(i)); allowedPrefixedSchemas.add(prefix + schemaFilters.get(i));
} }

View File

@@ -0,0 +1,166 @@
/*
* 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.
* 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 com.android.server.appsearch.external.localstorage;
import static android.app.appsearch.AppSearchResult.throwableToFailedResult;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchMigrationHelper;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.SearchResultPage;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaResponse;
import android.app.appsearch.exceptions.AppSearchException;
import android.os.Bundle;
import android.os.Parcel;
import com.android.internal.util.Preconditions;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
/**
* An implementation of {@link AppSearchMigrationHelper} which query document and save post-migrated
* documents to locally in the app's storage space.
*/
class AppSearchMigrationHelperImpl implements AppSearchMigrationHelper {
private final AppSearchImpl mAppSearchImpl;
private final String mPackageName;
private final String mDatabaseName;
private final File mFile;
private final Map<String, Integer> mCurrentVersionMap;
private final Map<String, Integer> mFinalVersionMap;
AppSearchMigrationHelperImpl(
@NonNull AppSearchImpl appSearchImpl,
@NonNull Map<String, Integer> currentVersionMap,
@NonNull Map<String, Integer> finalVersionMap,
@NonNull String packageName,
@NonNull String databaseName)
throws IOException {
mAppSearchImpl = Preconditions.checkNotNull(appSearchImpl);
mCurrentVersionMap = Preconditions.checkNotNull(currentVersionMap);
mFinalVersionMap = Preconditions.checkNotNull(finalVersionMap);
mPackageName = Preconditions.checkNotNull(packageName);
mDatabaseName = Preconditions.checkNotNull(databaseName);
mFile = File.createTempFile(/*prefix=*/ "appsearch", /*suffix=*/ null);
}
@Override
public void queryAndTransform(
@NonNull String schemaType, @NonNull AppSearchMigrationHelper.Transformer migrator)
throws Exception {
Preconditions.checkState(mFile.exists(), "Internal temp file does not exist.");
int currentVersion = mCurrentVersionMap.get(schemaType);
int finalVersion = mFinalVersionMap.get(schemaType);
try (FileOutputStream outputStream = new FileOutputStream(mFile)) {
// TODO(b/151178558) change the output stream so that we can use it in platform
CodedOutputStream codedOutputStream = CodedOutputStream.newInstance(outputStream);
SearchResultPage searchResultPage =
mAppSearchImpl.query(
mPackageName,
mDatabaseName,
/*queryExpression=*/ "",
new SearchSpec.Builder()
.addFilterSchemas(schemaType)
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.build());
while (!searchResultPage.getResults().isEmpty()) {
for (int i = 0; i < searchResultPage.getResults().size(); i++) {
GenericDocument newDocument =
migrator.transform(
currentVersion,
finalVersion,
searchResultPage.getResults().get(i).getDocument());
Bundle bundle = newDocument.getBundle();
Parcel parcel = Parcel.obtain();
parcel.writeBundle(bundle);
byte[] serializedMessage = parcel.marshall();
parcel.recycle();
codedOutputStream.writeByteArrayNoTag(serializedMessage);
}
codedOutputStream.flush();
searchResultPage = mAppSearchImpl.getNextPage(searchResultPage.getNextPageToken());
outputStream.flush();
}
}
}
/**
* Reads {@link GenericDocument} from the temperate file and saves them to AppSearch.
*
* <p>This method should be only called once.
*
* @return the {@link AppSearchBatchResult} for migration documents.
*/
@NonNull
public SetSchemaResponse readAndPutDocuments(SetSchemaResponse.Builder responseBuilder)
throws IOException, AppSearchException {
Preconditions.checkState(mFile.exists(), "Internal temp file does not exist.");
try (InputStream inputStream = new FileInputStream(mFile)) {
CodedInputStream codedInputStream = CodedInputStream.newInstance(inputStream);
while (!codedInputStream.isAtEnd()) {
GenericDocument document = readDocumentFromInputStream(codedInputStream);
try {
mAppSearchImpl.putDocument(mPackageName, mDatabaseName, document);
} catch (Throwable t) {
responseBuilder.setFailure(
document.getSchemaType(),
document.getNamespace(),
document.getUri(),
throwableToFailedResult(t));
}
}
mAppSearchImpl.persistToDisk();
return responseBuilder.build();
} finally {
mFile.delete();
}
}
void deleteTempFile() {
mFile.delete();
}
/**
* Reads {@link GenericDocument} from given {@link CodedInputStream}.
*
* @param codedInputStream The codedInputStream to read from
* @throws IOException on File operation error.
*/
@NonNull
private static GenericDocument readDocumentFromInputStream(
@NonNull CodedInputStream codedInputStream) throws IOException {
byte[] serializedMessage = codedInputStream.readByteArray();
Parcel parcel = Parcel.obtain();
parcel.unmarshall(serializedMessage, 0, serializedMessage.length);
parcel.setDataPosition(0);
Bundle bundle = parcel.readBundle();
parcel.recycle();
return new GenericDocument(bundle);
}
}

View File

@@ -40,8 +40,8 @@ public final class SearchSpecToProtoConverter {
Preconditions.checkNotNull(spec); Preconditions.checkNotNull(spec);
SearchSpecProto.Builder protoBuilder = SearchSpecProto.Builder protoBuilder =
SearchSpecProto.newBuilder() SearchSpecProto.newBuilder()
.addAllSchemaTypeFilters(spec.getSchemaTypes()) .addAllSchemaTypeFilters(spec.getFilterSchemas())
.addAllNamespaceFilters(spec.getNamespaces()); .addAllNamespaceFilters(spec.getFilterNamespaces());
@SearchSpec.TermMatch int termMatchCode = spec.getTermMatch(); @SearchSpec.TermMatch int termMatchCode = spec.getTermMatch();
TermMatchType.Code termMatchCodeProto = TermMatchType.Code.forNumber(termMatchCode); TermMatchType.Code termMatchCodeProto = TermMatchType.Code.forNumber(termMatchCode);

View File

@@ -1 +1 @@
I3fd4c96bf775c2539d744c416cdbf1d3c9544f03 Ibe06fb9c574c8718191f833bb042fa10c300e4e2

View File

@@ -102,10 +102,10 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim {
@Override @Override
@NonNull @NonNull
public ListenableFuture<AppSearchBatchResult<String, Void>> putDocuments( public ListenableFuture<AppSearchBatchResult<String, Void>> put(
@NonNull PutDocumentsRequest request) { @NonNull PutDocumentsRequest request) {
SettableFuture<AppSearchBatchResult<String, Void>> future = SettableFuture.create(); SettableFuture<AppSearchBatchResult<String, Void>> future = SettableFuture.create();
mAppSearchSession.putDocuments( mAppSearchSession.put(
request, mExecutor, new BatchResultCallbackAdapter<>(future)); request, mExecutor, new BatchResultCallbackAdapter<>(future));
return future; return future;
} }
@@ -122,10 +122,10 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim {
@Override @Override
@NonNull @NonNull
public SearchResultsShim query( public SearchResultsShim search(
@NonNull String queryExpression, @NonNull SearchSpec searchSpec) { @NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
SearchResults searchResults = SearchResults searchResults =
mAppSearchSession.query(queryExpression, searchSpec, mExecutor); mAppSearchSession.search(queryExpression, searchSpec, mExecutor);
return new SearchResultsShimImpl(searchResults, mExecutor); return new SearchResultsShimImpl(searchResults, mExecutor);
} }
@@ -139,19 +139,19 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim {
@Override @Override
@NonNull @NonNull
public ListenableFuture<AppSearchBatchResult<String, Void>> removeByUri( public ListenableFuture<AppSearchBatchResult<String, Void>> remove(
@NonNull RemoveByUriRequest request) { @NonNull RemoveByUriRequest request) {
SettableFuture<AppSearchBatchResult<String, Void>> future = SettableFuture.create(); SettableFuture<AppSearchBatchResult<String, Void>> future = SettableFuture.create();
mAppSearchSession.removeByUri(request, mExecutor, new BatchResultCallbackAdapter<>(future)); mAppSearchSession.remove(request, mExecutor, new BatchResultCallbackAdapter<>(future));
return future; return future;
} }
@Override @Override
@NonNull @NonNull
public ListenableFuture<Void> removeByQuery( public ListenableFuture<Void> remove(
@NonNull String queryExpression, @NonNull SearchSpec searchSpec) { @NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
SettableFuture<AppSearchResult<Void>> future = SettableFuture.create(); SettableFuture<AppSearchResult<Void>> future = SettableFuture.create();
mAppSearchSession.removeByQuery(queryExpression, searchSpec, mExecutor, future::set); mAppSearchSession.remove(queryExpression, searchSpec, mExecutor, future::set);
return Futures.transformAsync(future, this::transformResult, mExecutor); return Futures.transformAsync(future, this::transformResult, mExecutor);
} }

View File

@@ -67,10 +67,10 @@ public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim {
@NonNull @NonNull
@Override @Override
public SearchResultsShim query( public SearchResultsShim search(
@NonNull String queryExpression, @NonNull SearchSpec searchSpec) { @NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
SearchResults searchResults = SearchResults searchResults =
mGlobalSearchSession.query(queryExpression, searchSpec, mExecutor); mGlobalSearchSession.search(queryExpression, searchSpec, mExecutor);
return new SearchResultsShimImpl(searchResults, mExecutor); return new SearchResultsShimImpl(searchResults, mExecutor);
} }

View File

@@ -33,7 +33,7 @@ import java.util.Set;
public interface AppSearchSessionShim extends Closeable { public interface AppSearchSessionShim extends Closeable {
/** /**
* Sets the schema that will be used by documents provided to the {@link #putDocuments} method. * Sets the schema that will be used by documents provided to the {@link #put} method.
* *
* <p>The schema provided here is compared to the stored copy of the schema previously supplied * <p>The schema provided here is compared to the stored copy of the schema previously supplied
* to {@link #setSchema}, if any, to determine how to treat existing documents. The following * to {@link #setSchema}, if any, to determine how to treat existing documents. The following
@@ -143,8 +143,7 @@ public interface AppSearchSessionShim extends Closeable {
* they were successfully indexed, or a failed {@link AppSearchResult} otherwise. * they were successfully indexed, or a failed {@link AppSearchResult} otherwise.
*/ */
@NonNull @NonNull
ListenableFuture<AppSearchBatchResult<String, Void>> putDocuments( ListenableFuture<AppSearchBatchResult<String, Void>> put(@NonNull PutDocumentsRequest request);
@NonNull PutDocumentsRequest request);
/** /**
* Retrieves {@link GenericDocument}s by URI. * Retrieves {@link GenericDocument}s by URI.
@@ -161,7 +160,7 @@ public interface AppSearchSessionShim extends Closeable {
@NonNull GetByUriRequest request); @NonNull GetByUriRequest request);
/** /**
* Searches a document based on a given query string. * Searches for documents based on a given query string.
* *
* <p>Currently we support following features in the raw query format: * <p>Currently we support following features in the raw query format:
* *
@@ -200,7 +199,7 @@ public interface AppSearchSessionShim extends Closeable {
* @return The search result of performing this operation. * @return The search result of performing this operation.
*/ */
@NonNull @NonNull
SearchResultsShim query(@NonNull String queryExpression, @NonNull SearchSpec searchSpec); SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
/** /**
* Reports usage of a particular document by URI and namespace. * Reports usage of a particular document by URI and namespace.
@@ -208,7 +207,7 @@ public interface AppSearchSessionShim extends Closeable {
* <p>A usage report represents an event in which a user interacted with or viewed a document. * <p>A usage report represents an event in which a user interacted with or viewed a document.
* *
* <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency * <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency
* metrics for that particular document. These metrics are used for ordering {@link #query} * metrics for that particular document. These metrics are used for ordering {@link #search}
* results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and {@link * results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and {@link
* SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies. * SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies.
* *
@@ -231,13 +230,13 @@ public interface AppSearchSessionShim extends Closeable {
* {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}. * {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}.
*/ */
@NonNull @NonNull
ListenableFuture<AppSearchBatchResult<String, Void>> removeByUri( ListenableFuture<AppSearchBatchResult<String, Void>> remove(
@NonNull RemoveByUriRequest request); @NonNull RemoveByUriRequest request);
/** /**
* Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they * Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they
* match the {@code queryExpression} in given namespaces and schemaTypes which is set via {@link * match the {@code queryExpression} in given namespaces and schemaTypes which is set via {@link
* SearchSpec.Builder#addNamespace} and {@link SearchSpec.Builder#addSchemaType}. * SearchSpec.Builder#addFilterNamespaces} and {@link SearchSpec.Builder#addFilterSchemas}.
* *
* <p>An empty {@code queryExpression} matches all documents. * <p>An empty {@code queryExpression} matches all documents.
* *
@@ -251,8 +250,7 @@ public interface AppSearchSessionShim extends Closeable {
* @return The pending result of performing this operation. * @return The pending result of performing this operation.
*/ */
@NonNull @NonNull
ListenableFuture<Void> removeByQuery( ListenableFuture<Void> remove(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
/** /**
* Flush all schema and document updates, additions, and deletes to disk if possible. * Flush all schema and document updates, additions, and deletes to disk if possible.

View File

@@ -23,7 +23,7 @@ import java.io.Closeable;
/** /**
* This class provides global access to the centralized AppSearch index maintained by the system. * This class provides global access to the centralized AppSearch index maintained by the system.
* *
* <p>Apps can retrieve indexed documents through the query API. * <p>Apps can retrieve indexed documents through the {@link #search} API.
*/ */
public interface GlobalSearchSessionShim extends Closeable { public interface GlobalSearchSessionShim extends Closeable {
/** /**
@@ -66,7 +66,7 @@ public interface GlobalSearchSessionShim extends Closeable {
* @return The search result of performing this operation. * @return The search result of performing this operation.
*/ */
@NonNull @NonNull
SearchResultsShim query(@NonNull String queryExpression, @NonNull SearchSpec searchSpec); SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
/** Closes the {@link GlobalSearchSessionShim}. */ /** Closes the {@link GlobalSearchSessionShim}. */
@Override @Override

View File

@@ -0,0 +1,65 @@
/*
* 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;
import static com.google.common.truth.Truth.assertThat;
import android.os.Bundle;
import android.os.Parcel;
import org.junit.Test;
public class GenericDocumentTest {
@Test
public void testRecreateFromParcel() {
GenericDocument inDoc =
new GenericDocument.Builder<>("uri1", "schema1")
.setScore(42)
.setPropertyString("propString", "Hello")
.setPropertyBytes("propBytes", new byte[][] {{1, 2}})
.setPropertyDocument(
"propDocument",
new GenericDocument.Builder<>("uri2", "schema2")
.setPropertyString("propString", "Goodbye")
.setPropertyBytes("propBytes", new byte[][] {{3, 4}})
.build())
.build();
// Serialize the document
Parcel inParcel = Parcel.obtain();
inParcel.writeBundle(inDoc.getBundle());
byte[] data = inParcel.marshall();
inParcel.recycle();
// Deserialize the document
Parcel outParcel = Parcel.obtain();
outParcel.unmarshall(data, 0, data.length);
outParcel.setDataPosition(0);
Bundle outBundle = outParcel.readBundle();
outParcel.recycle();
// Compare results
GenericDocument outDoc = new GenericDocument(outBundle);
assertThat(inDoc).isEqualTo(outDoc);
assertThat(outDoc.getPropertyString("propString")).isEqualTo("Hello");
assertThat(outDoc.getPropertyBytesArray("propBytes")).isEqualTo(new byte[][] {{1, 2}});
assertThat(outDoc.getPropertyDocument("propDocument").getPropertyString("propString"))
.isEqualTo("Goodbye");
assertThat(outDoc.getPropertyDocument("propDocument").getPropertyBytesArray("propBytes"))
.isEqualTo(new byte[][] {{3, 4}});
}
}

View File

@@ -34,9 +34,9 @@ public class PutDocumentsRequestTest {
new AppSearchEmail.Builder("test1").build(), new AppSearchEmail.Builder("test1").build(),
new AppSearchEmail.Builder("test2").build()); new AppSearchEmail.Builder("test2").build());
PutDocumentsRequest request = PutDocumentsRequest request =
new PutDocumentsRequest.Builder().addGenericDocument(emails).build(); new PutDocumentsRequest.Builder().addGenericDocuments(emails).build();
assertThat(request.getDocuments().get(0).getUri()).isEqualTo("test1"); assertThat(request.getGenericDocuments().get(0).getUri()).isEqualTo("test1");
assertThat(request.getDocuments().get(1).getUri()).isEqualTo("test2"); assertThat(request.getGenericDocuments().get(1).getUri()).isEqualTo("test2");
} }
} }

View File

@@ -33,8 +33,8 @@ public class SearchSpecTest {
SearchSpec searchSpec = SearchSpec searchSpec =
new SearchSpec.Builder() new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX) .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.addNamespace("namespace1", "namespace2") .addFilterNamespaces("namespace1", "namespace2")
.addSchemaType("schemaTypes1", "schemaTypes2") .addFilterSchemas("schemaTypes1", "schemaTypes2")
.addFilterPackageNames("package1", "package2") .addFilterPackageNames("package1", "package2")
.setSnippetCount(5) .setSnippetCount(5)
.setSnippetCountPerProperty(10) .setSnippetCountPerProperty(10)
@@ -49,7 +49,7 @@ public class SearchSpecTest {
.isEqualTo(SearchSpec.TERM_MATCH_PREFIX); .isEqualTo(SearchSpec.TERM_MATCH_PREFIX);
assertThat(bundle.getStringArrayList(SearchSpec.NAMESPACE_FIELD)) assertThat(bundle.getStringArrayList(SearchSpec.NAMESPACE_FIELD))
.containsExactly("namespace1", "namespace2"); .containsExactly("namespace1", "namespace2");
assertThat(bundle.getStringArrayList(SearchSpec.SCHEMA_TYPE_FIELD)) assertThat(bundle.getStringArrayList(SearchSpec.SCHEMA_FIELD))
.containsExactly("schemaTypes1", "schemaTypes2"); .containsExactly("schemaTypes1", "schemaTypes2");
assertThat(bundle.getStringArrayList(SearchSpec.PACKAGE_NAME_FIELD)) assertThat(bundle.getStringArrayList(SearchSpec.PACKAGE_NAME_FIELD))
.containsExactly("package1", "package2"); .containsExactly("package1", "package2");

View File

@@ -75,12 +75,12 @@ public class SetSchemaRequestTest {
AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build(); AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
// By default, the schema is visible. // By default, the schema is visible.
SetSchemaRequest request = new SetSchemaRequest.Builder().addSchema(schema).build(); SetSchemaRequest request = new SetSchemaRequest.Builder().addSchemas(schema).build();
assertThat(request.getSchemasNotVisibleToSystemUi()).isEmpty(); assertThat(request.getSchemasNotVisibleToSystemUi()).isEmpty();
request = request =
new SetSchemaRequest.Builder() new SetSchemaRequest.Builder()
.addSchema(schema) .addSchemas(schema)
.setSchemaTypeVisibilityForSystemUi("Schema", true) .setSchemaTypeVisibilityForSystemUi("Schema", true)
.build(); .build();
assertThat(request.getSchemasNotVisibleToSystemUi()).isEmpty(); assertThat(request.getSchemasNotVisibleToSystemUi()).isEmpty();
@@ -91,7 +91,7 @@ public class SetSchemaRequestTest {
AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build(); AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
SetSchemaRequest request = SetSchemaRequest request =
new SetSchemaRequest.Builder() new SetSchemaRequest.Builder()
.addSchema(schema) .addSchemas(schema)
.setSchemaTypeVisibilityForSystemUi("Schema", false) .setSchemaTypeVisibilityForSystemUi("Schema", false)
.build(); .build();
assertThat(request.getSchemasNotVisibleToSystemUi()).containsExactly("Schema"); assertThat(request.getSchemasNotVisibleToSystemUi()).containsExactly("Schema");
@@ -102,7 +102,7 @@ public class SetSchemaRequestTest {
AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build(); AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
// By default, the schema is not visible. // By default, the schema is not visible.
SetSchemaRequest request = new SetSchemaRequest.Builder().addSchema(schema).build(); SetSchemaRequest request = new SetSchemaRequest.Builder().addSchemas(schema).build();
assertThat(request.getSchemasVisibleToPackages()).isEmpty(); assertThat(request.getSchemasVisibleToPackages()).isEmpty();
PackageIdentifier packageIdentifier = PackageIdentifier packageIdentifier =
@@ -112,7 +112,7 @@ public class SetSchemaRequestTest {
request = request =
new SetSchemaRequest.Builder() new SetSchemaRequest.Builder()
.addSchema(schema) .addSchemas(schema)
.setSchemaTypeVisibilityForPackage( .setSchemaTypeVisibilityForPackage(
"Schema", /*visible=*/ true, packageIdentifier) "Schema", /*visible=*/ true, packageIdentifier)
.build(); .build();
@@ -126,7 +126,7 @@ public class SetSchemaRequestTest {
SetSchemaRequest request = SetSchemaRequest request =
new SetSchemaRequest.Builder() new SetSchemaRequest.Builder()
.addSchema(schema) .addSchemas(schema)
.setSchemaTypeVisibilityForPackage( .setSchemaTypeVisibilityForPackage(
"Schema", "Schema",
/*visible=*/ false, /*visible=*/ false,
@@ -147,7 +147,7 @@ public class SetSchemaRequestTest {
SetSchemaRequest request = SetSchemaRequest request =
new SetSchemaRequest.Builder() new SetSchemaRequest.Builder()
.addSchema(schema) .addSchemas(schema)
// Set it visible for "Schema" // Set it visible for "Schema"
.setSchemaTypeVisibilityForPackage( .setSchemaTypeVisibilityForPackage(
"Schema", /*visible=*/ true, packageIdentifier) "Schema", /*visible=*/ true, packageIdentifier)
@@ -165,7 +165,7 @@ public class SetSchemaRequestTest {
SetSchemaRequest request = SetSchemaRequest request =
new SetSchemaRequest.Builder() new SetSchemaRequest.Builder()
.addSchema(schema) .addSchemas(schema)
// First set it as visible // First set it as visible
.setSchemaTypeVisibilityForPackage( .setSchemaTypeVisibilityForPackage(
"Schema", "Schema",

View File

@@ -700,14 +700,14 @@ public class AppSearchImplTest {
public void testRemoveEmptyDatabase_noExceptionThrown() throws Exception { public void testRemoveEmptyDatabase_noExceptionThrown() throws Exception {
SearchSpec searchSpec = SearchSpec searchSpec =
new SearchSpec.Builder() new SearchSpec.Builder()
.addSchemaType("FakeType") .addFilterSchemas("FakeType")
.setTermMatch(TermMatchType.Code.PREFIX_VALUE) .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.build(); .build();
mAppSearchImpl.removeByQuery("package", "EmptyDatabase", "", searchSpec); mAppSearchImpl.removeByQuery("package", "EmptyDatabase", "", searchSpec);
searchSpec = searchSpec =
new SearchSpec.Builder() new SearchSpec.Builder()
.addNamespace("FakeNamespace") .addFilterNamespaces("FakeNamespace")
.setTermMatch(TermMatchType.Code.PREFIX_VALUE) .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.build(); .build();
mAppSearchImpl.removeByQuery("package", "EmptyDatabase", "", searchSpec); mAppSearchImpl.removeByQuery("package", "EmptyDatabase", "", searchSpec);