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

This commit is contained in:
Alexander Dorokhine
2021-02-16 20:34:48 +00:00
committed by Android (Google) Code Review
19 changed files with 1097 additions and 726 deletions

View File

@@ -37,24 +37,36 @@ import java.util.Map;
public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelable {
@NonNull private final Map<KeyType, ValueType> mSuccesses;
@NonNull private final Map<KeyType, AppSearchResult<ValueType>> mFailures;
@NonNull private final Map<KeyType, AppSearchResult<ValueType>> mAll;
AppSearchBatchResult(
@NonNull Map<KeyType, ValueType> successes,
@NonNull Map<KeyType, AppSearchResult<ValueType>> failures) {
@NonNull Map<KeyType, AppSearchResult<ValueType>> failures,
@NonNull Map<KeyType, AppSearchResult<ValueType>> all) {
mSuccesses = successes;
mFailures = failures;
mAll = all;
}
private AppSearchBatchResult(@NonNull Parcel in) {
mSuccesses = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
mFailures = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
mAll = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
Map<KeyType, ValueType> successes = new ArrayMap<>();
Map<KeyType, AppSearchResult<ValueType>> failures = new ArrayMap<>();
for (Map.Entry<KeyType, AppSearchResult<ValueType>> entry : mAll.entrySet()) {
if (entry.getValue().isSuccess()) {
successes.put(entry.getKey(), entry.getValue().getResultValue());
} else {
failures.put(entry.getKey(), entry.getValue());
}
}
mSuccesses = Collections.unmodifiableMap(successes);
mFailures = Collections.unmodifiableMap(failures);
}
/** @hide */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeMap(mSuccesses);
dest.writeMap(mFailures);
dest.writeMap(mAll);
}
/** Returns {@code true} if this {@link AppSearchBatchResult} has no failures. */
@@ -63,8 +75,8 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl
}
/**
* Returns a {@link Map} of all successful keys mapped to the successful
* {@link AppSearchResult}s they produced.
* Returns a {@link Map} of all successful keys mapped to the successful {@link
* AppSearchResult}s they produced.
*
* <p>The values of the {@link Map} will not be {@code null}.
*/
@@ -84,8 +96,20 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl
return mFailures;
}
/**
* Returns a {@link Map} of all keys mapped to the {@link AppSearchResult}s they produced.
*
* <p>The values of the {@link Map} will not be {@code null}.
* @hide
*/
@NonNull
public Map<KeyType, AppSearchResult<ValueType>> getAll() {
return mAll;
}
/**
* Asserts that this {@link AppSearchBatchResult} has no failures.
*
* @hide
*/
public void checkSuccess() {
@@ -133,6 +157,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl
public static final class Builder<KeyType, ValueType> {
private final Map<KeyType, ValueType> mSuccesses = new ArrayMap<>();
private final Map<KeyType, AppSearchResult<ValueType>> mFailures = new ArrayMap<>();
private final Map<KeyType, AppSearchResult<ValueType>> mAll = new ArrayMap<>();
private boolean mBuilt = false;
/**
@@ -181,6 +206,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl
mFailures.put(key, result);
mSuccesses.remove(key);
}
mAll.put(key, result);
return this;
}
@@ -189,7 +215,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl
public AppSearchBatchResult<KeyType, ValueType> build() {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mBuilt = true;
return new AppSearchBatchResult<>(mSuccesses, mFailures);
return new AppSearchBatchResult<>(mSuccesses, mFailures, mAll);
}
}
}

View File

@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.app.appsearch.exceptions.AppSearchException;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -35,19 +36,21 @@ import java.util.Objects;
*/
public final class AppSearchResult<ValueType> implements Parcelable {
/**
* Result codes from {@link AppSearchManager} methods.
* Result codes from {@link AppSearchSession} methods.
*
* @hide
*/
@IntDef(value = {
RESULT_OK,
RESULT_UNKNOWN_ERROR,
RESULT_INTERNAL_ERROR,
RESULT_INVALID_ARGUMENT,
RESULT_IO_ERROR,
RESULT_OUT_OF_SPACE,
RESULT_NOT_FOUND,
RESULT_INVALID_SCHEMA,
})
@IntDef(
value = {
RESULT_OK,
RESULT_UNKNOWN_ERROR,
RESULT_INTERNAL_ERROR,
RESULT_INVALID_ARGUMENT,
RESULT_IO_ERROR,
RESULT_OUT_OF_SPACE,
RESULT_NOT_FOUND,
RESULT_INVALID_SCHEMA,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ResultCode {}
@@ -60,21 +63,21 @@ public final class AppSearchResult<ValueType> implements Parcelable {
/**
* An internal error occurred within AppSearch, which the caller cannot address.
*
* This error may be considered similar to {@link IllegalStateException}
* <p>This error may be considered similar to {@link IllegalStateException}
*/
public static final int RESULT_INTERNAL_ERROR = 2;
/**
* The caller supplied invalid arguments to the call.
*
* This error may be considered similar to {@link IllegalArgumentException}.
* <p>This error may be considered similar to {@link IllegalArgumentException}.
*/
public static final int RESULT_INVALID_ARGUMENT = 3;
/**
* An issue occurred reading or writing to storage. The call might succeed if repeated.
*
* This error may be considered similar to {@link java.io.IOException}.
* <p>This error may be considered similar to {@link java.io.IOException}.
*/
public static final int RESULT_IO_ERROR = 4;
@@ -127,7 +130,7 @@ public final class AppSearchResult<ValueType> implements Parcelable {
/**
* Returns the result value associated with this result, if it was successful.
*
* <p>See the documentation of the particular {@link AppSearchManager} call producing this
* <p>See the documentation of the particular {@link AppSearchSession} call producing this
* {@link AppSearchResult} for what is placed in the result value by that call.
*
* @throws IllegalStateException if this {@link AppSearchResult} is not successful.
@@ -145,8 +148,8 @@ public final class AppSearchResult<ValueType> implements Parcelable {
*
* <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. The error
* message may be {@code null} even if {@link #isSuccess} is {@code false}. See the
* documentation of the particular {@link AppSearchManager} call producing this
* {@link AppSearchResult} for what is returned by {@link #getErrorMessage}.
* documentation of the particular {@link AppSearchSession} call producing this {@link
* AppSearchResult} for what is returned by {@link #getErrorMessage}.
*/
@Nullable
public String getErrorMessage() {
@@ -205,6 +208,7 @@ public final class AppSearchResult<ValueType> implements Parcelable {
/**
* Creates a new successful {@link AppSearchResult}.
*
* @hide
*/
@NonNull
@@ -215,6 +219,7 @@ public final class AppSearchResult<ValueType> implements Parcelable {
/**
* Creates a new failed {@link AppSearchResult}.
*
* @hide
*/
@NonNull
@@ -227,6 +232,8 @@ public final class AppSearchResult<ValueType> implements Parcelable {
@NonNull
public static <ValueType> AppSearchResult<ValueType> throwableToFailedResult(
@NonNull Throwable t) {
Log.d("AppSearchResult", "Converting throwable to failed result.", t);
if (t instanceof AppSearchException) {
return ((AppSearchException) t).toAppSearchResult();
}
@@ -241,6 +248,6 @@ public final class AppSearchResult<ValueType> implements Parcelable {
} else {
resultCode = AppSearchResult.RESULT_UNKNOWN_ERROR;
}
return AppSearchResult.newFailedResult(resultCode, t.toString());
return AppSearchResult.newFailedResult(resultCode, t.getMessage());
}
}

View File

@@ -108,54 +108,85 @@ public final class AppSearchSession implements Closeable {
* to {@link #setSchema}, if any, to determine how to treat existing documents. The following
* types of schema modifications are always safe and are made without deleting any existing
* documents:
*
* <ul>
* <li>Addition of new types
* <li>Addition of new
* {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} or
* {@link AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} properties to a
* type
* <li>Changing the cardinality of a data type to be less restrictive (e.g. changing an
* {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a
* {@link AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} property.
* <li>Addition of new types
* <li>Addition of new {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} or
* {@link AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} properties to a
* type
* <li>Changing the cardinality of a data type to be less restrictive (e.g. changing an {@link
* AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a {@link
* AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} property.
* </ul>
*
* <p>The following types of schema changes are not backwards-compatible:
* <ul>
* <li>Removal of an existing type
* <li>Removal of a property from a type
* <li>Changing the data type ({@code boolean}, {@code long}, etc.) of an existing property
* <li>For properties of {@code Document} type, changing the schema type of
* {@code Document}s of that property
* <li>Changing the cardinality of a data type to be more restrictive (e.g. changing an
* {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a
* {@link AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property).
* <li>Adding a
* {@link AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property.
* </ul>
* <p>Supplying a schema with such changes will, by default, result in this call returning an
* {@link AppSearchResult} with a code of {@link AppSearchResult#RESULT_INVALID_SCHEMA} and an
* error message describing the incompatibility. In this case the previously set schema will
* remain active.
*
* <p>If you need to make non-backwards-compatible changes as described above, you can set the
* {@link SetSchemaRequest.Builder#setForceOverride} method to {@code true}. In this case,
* instead of returning an {@link AppSearchResult} with the
* {@link AppSearchResult#RESULT_INVALID_SCHEMA} error code, all documents which are not
* compatible with the new schema will be deleted and the incompatible schema will be applied.
* <ul>
* <li>Removal of an existing type
* <li>Removal of a property from a type
* <li>Changing the data type ({@code boolean}, {@code long}, etc.) of an existing property
* <li>For properties of {@code Document} type, changing the schema type of {@code Document}s
* of that property
* <li>Changing the cardinality of a data type to be more restrictive (e.g. changing an {@link
* AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a {@link
* AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property).
* <li>Adding a {@link AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property.
* </ul>
*
* <p>Supplying a schema with such changes will, by default, result in this call completing its
* future with an {@link android.app.appsearch.exceptions.AppSearchException} with a code of
* {@link AppSearchResult#RESULT_INVALID_SCHEMA} and a message describing the incompatibility.
* In this case the previously set schema will remain active.
*
* <p>If you need to make non-backwards-compatible changes as described above, you can either:
*
* <ul>
* <li>Set the {@link SetSchemaRequest.Builder#setForceOverride} method to {@code true}. In
* this case, instead of completing its future with an {@link
* android.app.appsearch.exceptions.AppSearchException} with the {@link
* AppSearchResult#RESULT_INVALID_SCHEMA} error code, all documents which are not
* compatible with the new schema will be deleted and the incompatible schema will be
* applied. Incompatible types and deleted types will be set into {@link
* SetSchemaResponse#getIncompatibleTypes()} and {@link
* SetSchemaResponse#getDeletedTypes()}, respectively.
* <li>Add a {@link android.app.appsearch.AppSearchSchema.Migrator} for each incompatible type
* and make no deletion. The migrator will migrate documents from it's old schema version
* to the new version. Migrated types will be set into both {@link
* SetSchemaResponse#getIncompatibleTypes()} and {@link
* SetSchemaResponse#getMigratedTypes()}. See the migration section below.
* </ul>
*
* <p>It is a no-op to set the same schema as has been previously set; this is handled
* efficiently.
*
* <p>By default, documents are visible on platform surfaces. To opt out, call
* {@link SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi} with {@code visible} as
* false. Any visibility settings apply only to the schemas that are included in the
* {@code request}. Visibility settings for a schema type do not persist across
* {@link #setSchema} calls.
* <p>By default, documents are visible on platform surfaces. To opt out, call {@code
* SetSchemaRequest.Builder#setPlatformSurfaceable} with {@code surfaceable} as false. Any
* visibility settings apply only to the schemas that are included in the {@code request}.
* Visibility settings for a schema type do not apply or persist across {@link
* SetSchemaRequest}s.
*
* @param request The schema update request.
* <p>Migration: make non-backwards-compatible changes will delete all stored documents in old
* schema. You can save your documents by setting {@link
* android.app.appsearch.AppSearchSchema.Migrator} via the {@link
* SetSchemaRequest.Builder#setMigrator} for each type you want to save.
*
* <p>{@link android.app.appsearch.AppSearchSchema.Migrator#onDowngrade} or {@link
* android.app.appsearch.AppSearchSchema.Migrator#onUpgrade} will be triggered if the version
* number of the schema stored in AppSearch is different with the version in the request.
*
* <p>If any error or Exception occurred in the {@link
* android.app.appsearch.AppSearchSchema.Migrator#onDowngrade}, {@link
* android.app.appsearch.AppSearchSchema.Migrator#onUpgrade} or {@link
* android.app.appsearch.AppSearchMigrationHelper.Transformer#transform}, the migration will be
* terminated, the setSchema request will be rejected unless the schema changes are
* backwards-compatible, and stored documents won't have any observable changes.
*
* @param request The schema update request.
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive errors resulting from setting the schema. If the
* operation succeeds, the callback will be invoked with {@code null}.
* @see android.app.appsearch.AppSearchSchema.Migrator
* @see android.app.appsearch.AppSearchMigrationHelper.Transformer
*/
public void setSchema(
@NonNull SetSchemaRequest request,
@@ -193,11 +224,9 @@ public final class AppSearchSession implements Closeable {
executor.execute(() -> {
if (result.isSuccess()) {
callback.accept(
// TODO(b/151178558) implement Migration in platform.
// TODO(b/177266929) implement Migration in platform.
AppSearchResult.newSuccessfulResult(
new SetSchemaResponse.Builder().setResultCode(
result.getResultCode())
.build()));
new SetSchemaResponse.Builder().build()));
} else {
callback.accept(result);
}
@@ -256,7 +285,7 @@ public final class AppSearchSession implements Closeable {
* <p>Each {@link GenericDocument}'s {@code schemaType} field must be set to the name of a
* schema type previously registered via the {@link #setSchema} method.
*
* @param request {@link PutDocumentsRequest} containing documents to be indexed
* @param request {@link PutDocumentsRequest} containing documents to be indexed
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive pending result of performing this operation. The keys
* of the returned {@link AppSearchBatchResult} are the URIs of the input
@@ -297,9 +326,10 @@ public final class AppSearchSession implements Closeable {
}
/**
* Retrieves {@link GenericDocument}s by URI.
* Gets {@link GenericDocument} objects by URIs and namespace from the {@link AppSearchSession}
* database.
*
* @param request {@link GetByUriRequest} containing URIs to be retrieved.
* @param request a request containing URIs and namespace to get documents for.
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive the pending result of performing this operation. The keys
* of the returned {@link AppSearchBatchResult} are the input URIs. The values
@@ -377,48 +407,65 @@ public final class AppSearchSession implements Closeable {
}
/**
* Searches a document based on a given query string.
* Retrieves documents from the open {@link AppSearchSession} that match a given query string
* and type of search provided.
*
* <p>Query strings can be empty, contain one term with no operators, or contain multiple terms
* and operators.
*
* <p>For query strings that are empty, all documents that match the {@link SearchSpec} will be
* returned.
*
* <p>For query strings with a single term and no operators, documents that match the provided
* query string and {@link SearchSpec} will be returned.
*
* <p>The following operators are supported:
*
* <p>Currently we support following features in the raw query format:
* <ul>
* <li>AND
* <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and
* ‘cat’”).
* Example: hello world matches documents that have both ‘hello’ and ‘world’
* <li>OR
* <p>OR joins (e.g. “match documents that have either the term ‘dog’ or
* ‘cat’”).
* Example: dog OR puppy
* <li>Exclusion
* <p>Exclude a term (e.g. “match documents that do
* not have the term ‘dog’”).
* Example: -dog excludes the term ‘dog’
* <li>Grouping terms
* <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g.
* “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”).
* Example: (dog puppy) (cat kitten) two one group containing two terms.
* <li>Property restricts
* <p> Specifies which properties of a document to specifically match terms in (e.g.
* “match documents where the ‘subject’ property contains ‘important’”).
* Example: subject:important matches documents with the term ‘important’ in the
* ‘subject’ property
* <li>Schema type restricts
* <p>This is similar to property restricts, but allows for restricts on top-level document
* fields, such as schema_type. Clients should be able to limit their query to documents of
* a certain schema_type (e.g. “match documents that are of the ‘Email’ schema_type”).
* Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will match documents
* that contain the query term ‘dog’ and are of either the ‘Email’ schema type or the
* ‘Video’ schema type.
* <li>AND (implicit)
* <p>AND is an operator that matches documents that contain <i>all</i> provided terms.
* <p><b>NOTE:</b> A space between terms is treated as an "AND" operator. Explicitly
* including "AND" in a query string will treat "AND" as a term, returning documents that
* also contain "AND".
* <p>Example: "apple AND banana" matches documents that contain the terms "apple", "and",
* "banana".
* <p>Example: "apple banana" matches documents that contain both "apple" and "banana".
* <p>Example: "apple banana cherry" matches documents that contain "apple", "banana", and
* "cherry".
* <li>OR
* <p>OR is an operator that matches documents that contain <i>any</i> provided term.
* <p>Example: "apple OR banana" matches documents that contain either "apple" or
* "banana".
* <p>Example: "apple OR banana OR cherry" matches documents that contain any of "apple",
* "banana", or "cherry".
* <li>Exclusion (-)
* <p>Exclusion (-) is an operator that matches documents that <i>do not</i> contain the
* provided term.
* <p>Example: "-apple" matches documents that do not contain "apple".
* <li>Grouped Terms
* <p>For queries that require multiple operators and terms, terms can be grouped into
* subqueries. Subqueries are contained within an open "(" and close ")" parenthesis.
* <p>Example: "(donut OR bagel) (coffee OR tea)" matches documents that contain either
* "donut" or "bagel" and either "coffee" or "tea".
* <li>Property Restricts
* <p>For queries that require a term to match a specific {@link AppSearchSchema} property
* of a document, a ":" must be included between the property name and the term.
* <p>Example: "subject:important" matches documents that contain the term "important" in
* the "subject" property.
* </ul>
*
* <p> This method is lightweight. The heavy work will be done in
* {@link SearchResults#getNextPage}.
* <p>Additional search specifications, such as filtering by {@link AppSearchSchema} type or
* adding projection, can be set by calling the corresponding {@link SearchSpec.Builder} setter.
*
* @param queryExpression Query String to search.
* @param searchSpec Spec for setting filters, raw query etc.
* <p>This method is lightweight. The heavy work will be done in {@link
* SearchResults#getNextPage}.
*
* @param queryExpression query string to search.
* @param searchSpec spec for setting document filters, adding projection, setting term match
* type, etc.
* @param executor Executor on which to invoke the callback of the following request
* {@link SearchResults#getNextPage}.
* @return The search result of performing this operation.
* @return a {@link SearchResults} object for retrieved matched documents.
*/
@NonNull
public SearchResults search(
@@ -440,8 +487,8 @@ public final class AppSearchSession implements Closeable {
*
* <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 #search}
* results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and
* {@link SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies.
* results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and {@link
* SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies.
*
* <p>Reporting usage of a document is optional.
*
@@ -479,9 +526,17 @@ public final class AppSearchSession implements Closeable {
}
/**
* Removes {@link GenericDocument}s from the index by URI.
* Removes {@link GenericDocument} objects by URIs and namespace from the {@link
* AppSearchSession} database.
*
* @param request Request containing URIs to be removed.
* <p>Removed documents will no longer be surfaced by {@link #search} or {@link #getByUri}
* calls.
*
* <p><b>NOTE:</b>By default, documents are removed via a soft delete operation. Once the
* document crosses the count threshold or byte usage threshold, the documents will be removed
* from disk.
*
* @param request {@link RemoveByUriRequest} with URIs and namespace to remove from the index.
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive the pending result of performing this operation. The keys
* of the returned {@link AppSearchBatchResult} are the input URIs. The values
@@ -520,19 +575,18 @@ public final class AppSearchSession implements Closeable {
/**
* 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 SearchSpec.Builder#addFilterNamespaces} and
* {@link SearchSpec.Builder#addFilterSchemas}.
* match the {@code queryExpression} in given namespaces and schemaTypes which is set via {@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.
*
* <p> An empty set of namespaces or schemaTypes matches all namespaces or schemaTypes in
* the current database.
* <p>An empty set of namespaces or schemaTypes matches all namespaces or schemaTypes in the
* current database.
*
* @param queryExpression Query String to search.
* @param searchSpec Spec containing schemaTypes, namespaces and query expression indicates
* how document will be removed. All specific about how to scoring,
* ordering, snippeting and resulting will be ignored.
* @param searchSpec Spec containing schemaTypes, namespaces and query expression indicates how
* document will be removed. All specific about how to scoring, ordering, snippeting and
* resulting will be ignored.
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive errors resulting from removing the documents. If
* the operation succeeds, the callback will be invoked with

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 The Android Open Source Project
* 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.
@@ -32,7 +32,7 @@ import java.util.function.Consumer;
/**
* 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 class GlobalSearchSession implements Closeable {
@@ -90,48 +90,26 @@ public class GlobalSearchSession implements Closeable {
}
/**
* Searches across all documents in the storage based on a given query string.
* Retrieves documents from all AppSearch databases that the querying application has access to.
*
* <p>Currently we support following features in the raw query format:
* <ul>
* <li>AND
* <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and
* ‘cat’”).
* Example: hello world matches documents that have both ‘hello’ and ‘world’
* <li>OR
* <p>OR joins (e.g. “match documents that have either the term ‘dog’ or
* ‘cat’”).
* Example: dog OR puppy
* <li>Exclusion
* <p>Exclude a term (e.g. “match documents that do
* not have the term ‘dog’”).
* Example: -dog excludes the term ‘dog’
* <li>Grouping terms
* <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g.
* “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”).
* Example: (dog puppy) (cat kitten) two one group containing two terms.
* <li>Property restricts
* <p> Specifies which properties of a document to specifically match terms in (e.g.
* “match documents where the ‘subject’ property contains ‘important’”).
* Example: subject:important matches documents with the term ‘important’ in the
* ‘subject’ property
* <li>Schema type restricts
* <p>This is similar to property restricts, but allows for restricts on top-level document
* fields, such as schema_type. Clients should be able to limit their query to documents of
* a certain schema_type (e.g. “match documents that are of the ‘Email’ schema_type”).
* Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will match documents
* that contain the query term ‘dog’ and are of either the ‘Email’ schema type or the
* ‘Video’ schema type.
* </ul>
* <p>Applications can be granted access to documents by specifying {@link
* SetSchemaRequest.Builder#setSchemaTypeVisibilityForPackage} when building a schema.
*
* <p> This method is lightweight. The heavy work will be done in
* {@link SearchResults#getNextPage}.
* <p>Document access can also be granted to system UIs by specifying {@link
* SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi} when building a schema.
*
* @param queryExpression Query String to search.
* @param searchSpec Spec for setting filters, raw query etc.
* <p>See {@link AppSearchSession#search} for a detailed explanation on
* forming a query string.
*
* <p>This method is lightweight. The heavy work will be done in {@link
* SearchResults#getNextPage}.
*
* @param queryExpression query string to search.
* @param searchSpec spec for setting document filters, adding projection, setting term match
* type, etc.
* @param executor Executor on which to invoke the callback of the following request
* {@link SearchResults#getNextPage}.
* @return The search result of performing this operation.
* @return a {@link SearchResults} object for retrieved matched documents.
*/
@NonNull
public SearchResults search(

View File

@@ -35,8 +35,8 @@ import java.util.function.Consumer;
/**
* SearchResults are a returned object from a query API.
*
* <p>Each {@link SearchResult} contains a document and may contain other fields like snippets
* based on request.
* <p>Each {@link SearchResult} contains a document and may contain other fields like snippets based
* on request.
*
* <p>Should close this object after finish fetching results.
*
@@ -89,8 +89,8 @@ public class SearchResults implements Closeable {
/**
* Gets a whole page of {@link SearchResult}s.
*
* <p>Re-call this method to get next page of {@link SearchResult}, until it returns an
* empty list.
* <p>Re-call this method to get next page of {@link SearchResult}, until it returns an empty
* list.
*
* <p>The page size is set by {@link SearchSpec.Builder#setResultCountPerPage}.
*

View File

@@ -90,6 +90,7 @@ public final class AppSearchSchema {
* <p>This method creates a new list when called.
*/
@NonNull
@SuppressWarnings("MixedMutabilityReturnType")
public List<PropertyConfig> getProperties() {
ArrayList<Bundle> propertyBundles =
mBundle.getParcelableArrayList(AppSearchSchema.PROPERTIES_FIELD);

View File

@@ -36,7 +36,8 @@ import java.util.Set;
/**
* Represents a document unit.
*
* <p>Documents are constructed via {@link GenericDocument.Builder}.
* <p>Documents contain structured data conforming to their {@link AppSearchSchema} type. Each
* document is uniquely identified by a URI and namespace.
*
* @see AppSearchSession#put
* @see AppSearchSession#getByUri
@@ -48,16 +49,10 @@ public class GenericDocument {
/** The default empty namespace. */
public static final String DEFAULT_NAMESPACE = "";
/**
* The maximum number of elements in a repeatable field. Will reject the request if exceed this
* limit.
*/
/** The maximum number of elements in a repeatable field. */
private static final int MAX_REPEATED_PROPERTY_LENGTH = 100;
/**
* The maximum {@link String#length} of a {@link String} field. Will reject the request if
* {@link String}s longer than this.
*/
/** The maximum {@link String#length} of a {@link String} field. */
private static final int MAX_STRING_LENGTH = 20_000;
/** The maximum number of indexed properties a document can have. */
@@ -149,7 +144,7 @@ public class GenericDocument {
return mBundle.getString(NAMESPACE_FIELD, DEFAULT_NAMESPACE);
}
/** Returns the schema type of the {@link GenericDocument}. */
/** Returns the {@link AppSearchSchema} type of the {@link GenericDocument}. */
@NonNull
public String getSchemaType() {
return mSchemaType;
@@ -165,14 +160,14 @@ public class GenericDocument {
}
/**
* Returns the TTL (Time To Live) of the {@link GenericDocument}, in milliseconds.
* Returns the TTL (time-to-live) of the {@link GenericDocument}, in milliseconds.
*
* <p>The TTL is measured against {@link #getCreationTimestampMillis}. At the timestamp of
* {@code creationTimestampMillis + ttlMillis}, measured in the {@link System#currentTimeMillis}
* time base, the document will be auto-deleted.
*
* <p>The default value is 0, which means the document is permanent and won't be auto-deleted
* until the app is uninstalled.
* until the app is uninstalled or {@link AppSearchSession#remove} is called.
*/
public long getTtlMillis() {
return mBundle.getLong(TTL_MILLIS_FIELD, DEFAULT_TTL_MILLIS);
@@ -182,12 +177,12 @@ public class GenericDocument {
* Returns the score of the {@link GenericDocument}.
*
* <p>The score is a query-independent measure of the document's quality, relative to other
* {@link GenericDocument}s of the same type.
* {@link GenericDocument} objects of the same {@link AppSearchSchema} type.
*
* <p>Results may be sorted by score using {@link SearchSpec.Builder#setRankingStrategy}.
* Documents with higher scores are considered better than documents with lower scores.
*
* <p>Any nonnegative integer can be used a score.
* <p>Any non-negative integer can be used a score.
*/
public int getScore() {
return mBundle.getInt(SCORE_FIELD, DEFAULT_SCORE);
@@ -355,7 +350,7 @@ public class GenericDocument {
}
/**
* Retrieves a repeated {@link String} property by key.
* Retrieves a repeated {@code long[]} property by key.
*
* @param key The key to look for.
* @return The {@code long[]} associated with the given key, or {@code null} if no value is set
@@ -580,14 +575,17 @@ public class GenericDocument {
private boolean mBuilt = false;
/**
* Create a new {@link GenericDocument.Builder}.
* Creates a new {@link GenericDocument.Builder}.
*
* @param uri The uri of {@link GenericDocument}.
* @param schemaType The schema type of the {@link GenericDocument}. The passed-in {@code
* schemaType} must be defined using {@link AppSearchSession#setSchema} prior to
* inserting a document of this {@code schemaType} into the AppSearch index using {@link
* AppSearchSession#put}. Otherwise, the document will be rejected by {@link
* AppSearchSession#put}.
* <p>Once {@link #build} is called, the instance can no longer be used.
*
* @param uri the URI to set for the {@link GenericDocument}.
* @param schemaType the {@link AppSearchSchema} type of the {@link GenericDocument}. The
* provided {@code schemaType} must be defined using {@link AppSearchSession#setSchema}
* prior to inserting a document of this {@code schemaType} into the AppSearch index
* using {@link AppSearchSession#put}. Otherwise, the document will
* be rejected by {@link AppSearchSession#put} with result code
* {@link AppSearchResult#RESULT_NOT_FOUND}.
*/
@SuppressWarnings("unchecked")
public Builder(@NonNull String uri, @NonNull String schemaType) {
@@ -606,15 +604,18 @@ public class GenericDocument {
}
/**
* Sets the app-defined namespace this Document resides in. No special values are reserved
* Sets the app-defined namespace this document resides in. No special values are reserved
* or understood by the infrastructure.
*
* <p>URIs are unique within a namespace.
*
* <p>The number of namespaces per app should be kept small for efficiency reasons.
*
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setNamespace(@NonNull String namespace) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mBundle.putString(GenericDocument.NAMESPACE_FIELD, namespace);
return mBuilderTypeInstance;
}
@@ -623,14 +624,15 @@ public class GenericDocument {
* Sets the score of the {@link GenericDocument}.
*
* <p>The score is a query-independent measure of the document's quality, relative to other
* {@link GenericDocument}s of the same type.
* {@link GenericDocument} objects of the same {@link AppSearchSchema} type.
*
* <p>Results may be sorted by score using {@link SearchSpec.Builder#setRankingStrategy}.
* Documents with higher scores are considered better than documents with lower scores.
*
* <p>Any nonnegative integer can be used a score.
* <p>Any non-negative integer can be used a score. By default, scores are set to 0.
*
* @throws IllegalArgumentException If the provided value is negative.
* @param score any non-negative {@code int} representing the document's score.
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setScore(@IntRange(from = 0, to = Integer.MAX_VALUE) int score) {
@@ -645,8 +647,11 @@ public class GenericDocument {
/**
* Sets the creation timestamp of the {@link GenericDocument}, in milliseconds.
*
* <p>Should be set using a value obtained from the {@link System#currentTimeMillis} time
* base.
* <p>This should be set using a value obtained from the {@link System#currentTimeMillis}
* time base.
*
* @param creationTimestampMillis a creation timestamp in milliseconds.
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setCreationTimestampMillis(long creationTimestampMillis) {
@@ -657,17 +662,17 @@ public class GenericDocument {
}
/**
* Sets the TTL (Time To Live) of the {@link GenericDocument}, in milliseconds.
* Sets the TTL (time-to-live) of the {@link GenericDocument}, in milliseconds.
*
* <p>The TTL is measured against {@link #getCreationTimestampMillis}. At the timestamp of
* {@code creationTimestampMillis + ttlMillis}, measured in the {@link
* System#currentTimeMillis} time base, the document will be auto-deleted.
*
* <p>The default value is 0, which means the document is permanent and won't be
* auto-deleted until the app is uninstalled.
* auto-deleted until the app is uninstalled or {@link AppSearchSession#remove} is called.
*
* @param ttlMillis A non-negative duration in milliseconds.
* @throws IllegalArgumentException If the provided value is negative.
* @param ttlMillis a non-negative duration in milliseconds.
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setTtlMillis(long ttlMillis) {
@@ -682,8 +687,11 @@ public class GenericDocument {
/**
* Sets one or multiple {@code String} values for a property, replacing its previous values.
*
* @param key The key associated with the {@code values}.
* @param values The {@code String} values of the property.
* @param key the key associated with the {@code values}.
* @param values the {@code String} values of the property.
* @throws IllegalArgumentException if no values are provided, if provided values exceed
* maximum repeated property length, or if a passed in {@code String} is {@code null}.
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setPropertyString(@NonNull String key, @NonNull String... values) {
@@ -698,8 +706,11 @@ public class GenericDocument {
* Sets one or multiple {@code boolean} values for a property, replacing its previous
* values.
*
* @param key The key associated with the {@code values}.
* @param values The {@code boolean} values of the property.
* @param key the key associated with the {@code values}.
* @param values the {@code boolean} values of the property.
* @throws IllegalArgumentException if no values are provided or if values exceed maximum
* repeated property length.
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setPropertyBoolean(@NonNull String key, @NonNull boolean... values) {
@@ -713,8 +724,11 @@ public class GenericDocument {
/**
* Sets one or multiple {@code long} values for a property, replacing its previous values.
*
* @param key The key associated with the {@code values}.
* @param values The {@code long} values of the property.
* @param key the key associated with the {@code values}.
* @param values the {@code long} values of the property.
* @throws IllegalArgumentException if no values are provided or if values exceed maximum
* repeated property length.
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setPropertyLong(@NonNull String key, @NonNull long... values) {
@@ -728,8 +742,11 @@ public class GenericDocument {
/**
* Sets one or multiple {@code double} values for a property, replacing its previous values.
*
* @param key The key associated with the {@code values}.
* @param values The {@code double} values of the property.
* @param key the key associated with the {@code values}.
* @param values the {@code double} values of the property.
* @throws IllegalArgumentException if no values are provided or if values exceed maximum
* repeated property length.
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setPropertyDouble(@NonNull String key, @NonNull double... values) {
@@ -743,8 +760,11 @@ public class GenericDocument {
/**
* Sets one or multiple {@code byte[]} for a property, replacing its previous values.
*
* @param key The key associated with the {@code values}.
* @param values The {@code byte[]} of the property.
* @param key the key associated with the {@code values}.
* @param values the {@code byte[]} of the property.
* @throws IllegalArgumentException if no values are provided, if provided values exceed
* maximum repeated property length, or if a passed in {@code byte[]} is {@code null}.
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setPropertyBytes(@NonNull String key, @NonNull byte[]... values) {
@@ -759,8 +779,12 @@ public class GenericDocument {
* Sets one or multiple {@link GenericDocument} values for a property, replacing its
* previous values.
*
* @param key The key associated with the {@code values}.
* @param values The {@link GenericDocument} values of the property.
* @param key the key associated with the {@code values}.
* @param values the {@link GenericDocument} values of the property.
* @throws IllegalArgumentException if no values are provided, if provided values exceed if
* provided values exceed maximum repeated property length, or if a passed in {@link
* GenericDocument} is {@code null}.
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setPropertyDocument(
@@ -853,7 +877,11 @@ public class GenericDocument {
}
}
/** Builds the {@link GenericDocument} object. */
/**
* Builds the {@link GenericDocument} object.
*
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public GenericDocument build() {
Preconditions.checkState(!mBuilt, "Builder has already been used");

View File

@@ -31,7 +31,8 @@ import java.util.Map;
import java.util.Set;
/**
* Encapsulates a request to retrieve documents by namespace and URI.
* Encapsulates a request to retrieve documents by namespace and URIs from the {@link
* AppSearchSession} database.
*
* @see AppSearchSession#getByUri
*/
@@ -56,13 +57,13 @@ public final class GetByUriRequest {
mTypePropertyPathsMap = Preconditions.checkNotNull(typePropertyPathsMap);
}
/** Returns the namespace to get documents from. */
/** Returns the namespace attached to the request. */
@NonNull
public String getNamespace() {
return mNamespace;
}
/** Returns the URIs to get from the namespace. */
/** Returns the set of URIs attached to the request. */
@NonNull
public Set<String> getUris() {
return Collections.unmodifiableSet(mUris);
@@ -100,7 +101,11 @@ public final class GetByUriRequest {
return mTypePropertyPathsMap;
}
/** Builder for {@link GetByUriRequest} objects. */
/**
* Builder for {@link GetByUriRequest} objects.
*
* <p>Once {@link #build} is called, the instance can no longer be used.
*/
public static final class Builder {
private String mNamespace = GenericDocument.DEFAULT_NAMESPACE;
private final Set<String> mUris = new ArraySet<>();
@@ -108,9 +113,12 @@ public final class GetByUriRequest {
private boolean mBuilt = false;
/**
* Sets which namespace these documents will be retrieved from.
* Sets the namespace to retrieve documents for.
*
* <p>If this is not set, it defaults to {@link GenericDocument#DEFAULT_NAMESPACE}.
* <p>If this is not called, the namespace defaults to {@link
* GenericDocument#DEFAULT_NAMESPACE}.
*
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public Builder setNamespace(@NonNull String namespace) {
@@ -120,14 +128,22 @@ public final class GetByUriRequest {
return this;
}
/** Adds one or more URIs to the request. */
/**
* Adds one or more URIs to the request.
*
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public Builder addUris(@NonNull String... uris) {
Preconditions.checkNotNull(uris);
return addUris(Arrays.asList(uris));
}
/** Adds one or more URIs to the request. */
/**
* Adds a collection of URIs to the request.
*
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public Builder addUris(@NonNull Collection<String> uris) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
@@ -149,7 +165,8 @@ public final class GetByUriRequest {
* GetByUriRequest#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#addProjection(String, String...)}
* @throws IllegalStateException if the builder has already been used.
* <p>{@see SearchSpec.Builder#addProjection(String, String...)}
*/
@NonNull
public Builder addProjection(@NonNull String schemaType, @NonNull String... propertyPaths) {
@@ -170,7 +187,8 @@ public final class GetByUriRequest {
* GetByUriRequest#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#addProjection(String, String...)}
* @throws IllegalStateException if the builder has already been used.
* <p>{@see SearchSpec.Builder#addProjection(String, String...)}
*/
@NonNull
public Builder addProjection(
@@ -187,7 +205,11 @@ public final class GetByUriRequest {
return this;
}
/** Builds a new {@link GetByUriRequest}. */
/**
* Builds a new {@link GetByUriRequest}.
*
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public GetByUriRequest build() {
Preconditions.checkState(!mBuilt, "Builder has already been used");

View File

@@ -27,7 +27,8 @@ import java.util.Collections;
import java.util.Set;
/**
* Encapsulates a request to remove documents by namespace and URI.
* Encapsulates a request to remove documents by namespace and URIs from the {@link
* AppSearchSession} database.
*
* @see AppSearchSession#remove
*/
@@ -46,22 +47,28 @@ public final class RemoveByUriRequest {
return mNamespace;
}
/** Returns the URIs of documents to remove from the namespace. */
/** Returns the set of URIs attached to the request. */
@NonNull
public Set<String> getUris() {
return Collections.unmodifiableSet(mUris);
}
/** Builder for {@link RemoveByUriRequest} objects. */
/**
* Builder for {@link RemoveByUriRequest} objects.
*
* <p>Once {@link #build} is called, the instance can no longer be used.
*/
public static final class Builder {
private String mNamespace = GenericDocument.DEFAULT_NAMESPACE;
private final Set<String> mUris = new ArraySet<>();
private boolean mBuilt = false;
/**
* Sets which namespace these documents will be removed from.
* Sets the namespace to remove documents for.
*
* <p>If this is not set, it defaults to {@link GenericDocument#DEFAULT_NAMESPACE}.
*
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public Builder setNamespace(@NonNull String namespace) {
@@ -71,14 +78,22 @@ public final class RemoveByUriRequest {
return this;
}
/** Adds one or more URIs to the request. */
/**
* Adds one or more URIs to the request.
*
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public Builder addUris(@NonNull String... uris) {
Preconditions.checkNotNull(uris);
return addUris(Arrays.asList(uris));
}
/** Adds one or more URIs to the request. */
/**
* Adds a collection of URIs to the request.
*
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public Builder addUris(@NonNull Collection<String> uris) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
@@ -87,7 +102,11 @@ public final class RemoveByUriRequest {
return this;
}
/** Builds a new {@link RemoveByUriRequest}. */
/**
* Builds a new {@link RemoveByUriRequest}.
*
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public RemoveByUriRequest build() {
Preconditions.checkState(!mBuilt, "Builder has already been used");

View File

@@ -16,10 +16,9 @@
package android.app.appsearch;
import static android.app.appsearch.AppSearchResult.RESULT_OK;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.util.ArraySet;
import com.android.internal.util.Preconditions;
@@ -32,23 +31,58 @@ import java.util.Set;
/** The response class of {@link AppSearchSession#setSchema} */
public class SetSchemaResponse {
private final List<MigrationFailure> mMigrationFailures;
private final Set<String> mDeletedTypes;
private final Set<String> mMigratedTypes;
private final Set<String> mIncompatibleTypes;
private final @AppSearchResult.ResultCode int mResultCode;
SetSchemaResponse(
@NonNull List<MigrationFailure> migrationFailures,
@NonNull Set<String> deletedTypes,
@NonNull Set<String> migratedTypes,
@NonNull Set<String> incompatibleTypes,
@AppSearchResult.ResultCode int resultCode) {
private static final String DELETED_TYPES_FIELD = "deletedTypes";
private static final String INCOMPATIBLE_TYPES_FIELD = "incompatibleTypes";
private static final String MIGRATED_TYPES_FIELD = "migratedTypes";
private final Bundle mBundle;
/**
* The migrationFailures won't be saved in the bundle. Since:
*
* <ul>
* <li>{@link MigrationFailure} is generated in {@link AppSearchSession} which will be the SDK
* side in platform. We don't need to pass it from service side via binder.
* <li>Translate multiple {@link MigrationFailure}s to bundles in {@link Builder} and then
* back in constructor will be a huge waste.
* </ul>
*/
private final List<MigrationFailure> mMigrationFailures;
/** Cache of the inflated deleted schema types. Comes from inflating mBundles at first use. */
@Nullable private Set<String> mDeletedTypes;
/** Cache of the inflated migrated schema types. Comes from inflating mBundles at first use. */
@Nullable private Set<String> mMigratedTypes;
/**
* Cache of the inflated incompatible schema types. Comes from inflating mBundles at first use.
*/
@Nullable private Set<String> mIncompatibleTypes;
SetSchemaResponse(@NonNull Bundle bundle, @NonNull List<MigrationFailure> migrationFailures) {
mBundle = Preconditions.checkNotNull(bundle);
mMigrationFailures = Preconditions.checkNotNull(migrationFailures);
mDeletedTypes = Preconditions.checkNotNull(deletedTypes);
mMigratedTypes = Preconditions.checkNotNull(migratedTypes);
mIncompatibleTypes = Preconditions.checkNotNull(incompatibleTypes);
mResultCode = resultCode;
}
SetSchemaResponse(@NonNull Bundle bundle) {
this(bundle, /*migrationFailures=*/ Collections.emptyList());
}
/**
* Returns the {@link Bundle} populated by this builder.
*
* @hide
*/
@NonNull
public Bundle getBundle() {
return mBundle;
}
/** TODO(b/177266929): Remove this deprecated method */
//@Deprecated
public boolean isSuccess() {
return true;
}
/**
@@ -72,6 +106,12 @@ public class SetSchemaResponse {
*/
@NonNull
public Set<String> getDeletedTypes() {
if (mDeletedTypes == null) {
mDeletedTypes =
new ArraySet<>(
Preconditions.checkNotNull(
mBundle.getStringArrayList(DELETED_TYPES_FIELD)));
}
return Collections.unmodifiableSet(mDeletedTypes);
}
@@ -81,6 +121,12 @@ public class SetSchemaResponse {
*/
@NonNull
public Set<String> getMigratedTypes() {
if (mMigratedTypes == null) {
mMigratedTypes =
new ArraySet<>(
Preconditions.checkNotNull(
mBundle.getStringArrayList(MIGRATED_TYPES_FIELD)));
}
return Collections.unmodifiableSet(mMigratedTypes);
}
@@ -96,22 +142,28 @@ public class SetSchemaResponse {
*/
@NonNull
public Set<String> getIncompatibleTypes() {
if (mIncompatibleTypes == null) {
mIncompatibleTypes =
new ArraySet<>(
Preconditions.checkNotNull(
mBundle.getStringArrayList(INCOMPATIBLE_TYPES_FIELD)));
}
return Collections.unmodifiableSet(mIncompatibleTypes);
}
/** Returns {@code true} if all {@link AppSearchSchema}s are successful set to the system. */
public boolean isSuccess() {
return mResultCode == RESULT_OK;
}
@Override
/**
* Translates the {@link SetSchemaResponse}'s bundle to {@link Builder}.
*
* @hide
*/
@NonNull
public String toString() {
return "{\n Does setSchema success? : "
+ isSuccess()
+ "\n failures: "
+ mMigrationFailures
+ "\n}";
// TODO(b/179302942) change to Builder(mBundle) powered by mBundle.deepCopy
public Builder toBuilder() {
return new Builder()
.addDeletedTypes(getDeletedTypes())
.addIncompatibleTypes(getIncompatibleTypes())
.addMigratedTypes(getMigratedTypes())
.addMigrationFailures(mMigrationFailures);
}
/**
@@ -120,44 +172,26 @@ public class SetSchemaResponse {
* @hide
*/
public static class Builder {
private final List<MigrationFailure> mMigrationFailures = new ArrayList<>();
private final Set<String> mDeletedTypes = new ArraySet<>();
private final Set<String> mMigratedTypes = new ArraySet<>();
private final Set<String> mIncompatibleTypes = new ArraySet<>();
private @AppSearchResult.ResultCode int mResultCode = RESULT_OK;
private final ArrayList<MigrationFailure> mMigrationFailures = new ArrayList<>();
private final ArrayList<String> mDeletedTypes = new ArrayList<>();
private final ArrayList<String> mMigratedTypes = new ArrayList<>();
private final ArrayList<String> mIncompatibleTypes = new ArrayList<>();
private boolean mBuilt = false;
/** Adds a {@link MigrationFailure}. */
/** Adds {@link MigrationFailure}s to the list of migration failures. */
@NonNull
public Builder setFailure(
@NonNull String schemaType,
@NonNull String namespace,
@NonNull String uri,
@NonNull AppSearchResult<Void> failureResult) {
public Builder addMigrationFailures(
@NonNull Collection<MigrationFailure> migrationFailures) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Preconditions.checkNotNull(schemaType);
Preconditions.checkNotNull(namespace);
Preconditions.checkNotNull(uri);
Preconditions.checkNotNull(failureResult);
Preconditions.checkState(!failureResult.isSuccess());
mMigrationFailures.add(new MigrationFailure(schemaType, namespace, uri, failureResult));
mMigrationFailures.addAll(Preconditions.checkNotNull(migrationFailures));
return this;
}
/** Adds a {@link MigrationFailure}. */
/** Adds a {@link MigrationFailure} to the list of migration failures. */
@NonNull
public Builder setFailure(
@NonNull String schemaType,
@NonNull String namespace,
@NonNull String uri,
@AppSearchResult.ResultCode int resultCode,
@Nullable String errorMessage) {
mMigrationFailures.add(
new MigrationFailure(
schemaType,
namespace,
uri,
AppSearchResult.newFailedResult(resultCode, errorMessage)));
public Builder addMigrationFailure(@NonNull MigrationFailure migrationFailure) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mMigrationFailures.add(Preconditions.checkNotNull(migrationFailure));
return this;
}
@@ -169,6 +203,14 @@ public class SetSchemaResponse {
return this;
}
/** Adds one deletedType to the list of deleted schema types. */
@NonNull
public Builder addDeletedType(@NonNull String deletedType) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mDeletedTypes.add(Preconditions.checkNotNull(deletedType));
return this;
}
/** Adds incompatibleTypes to the list of incompatible schema types. */
@NonNull
public Builder addIncompatibleTypes(@NonNull Collection<String> incompatibleTypes) {
@@ -177,6 +219,14 @@ public class SetSchemaResponse {
return this;
}
/** Adds one incompatibleType to the list of incompatible schema types. */
@NonNull
public Builder addIncompatibleType(@NonNull String incompatibleType) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mIncompatibleTypes.add(Preconditions.checkNotNull(incompatibleType));
return this;
}
/** Adds migratedTypes to the list of migrated schema types. */
@NonNull
public Builder addMigratedTypes(@NonNull Collection<String> migratedTypes) {
@@ -185,11 +235,11 @@ public class SetSchemaResponse {
return this;
}
/** Sets the {@link AppSearchResult.ResultCode} of the response. */
/** Adds one migratedType to the list of migrated schema types. */
@NonNull
public Builder setResultCode(@AppSearchResult.ResultCode int resultCode) {
public Builder addMigratedType(@NonNull String migratedType) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mResultCode = resultCode;
mMigratedTypes.add(Preconditions.checkNotNull(migratedType));
return this;
}
@@ -197,13 +247,15 @@ public class SetSchemaResponse {
@NonNull
public SetSchemaResponse build() {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Bundle bundle = new Bundle();
bundle.putStringArrayList(INCOMPATIBLE_TYPES_FIELD, mIncompatibleTypes);
bundle.putStringArrayList(DELETED_TYPES_FIELD, mDeletedTypes);
bundle.putStringArrayList(MIGRATED_TYPES_FIELD, mMigratedTypes);
mBuilt = true;
return new SetSchemaResponse(
mMigrationFailures,
mDeletedTypes,
mMigratedTypes,
mIncompatibleTypes,
mResultCode);
// Avoid converting the potential thousands of MigrationFailures to Pracelable and
// back just for put in bundle. In platform, we should set MigrationFailures in
// AppSearchSession after we pass SetSchemaResponse via binder.
return new SetSchemaResponse(bundle, mMigrationFailures);
}
}
@@ -212,38 +264,44 @@ public class SetSchemaResponse {
* {@link AppSearchSession#setSchema}.
*/
public static class MigrationFailure {
private final String mSchemaType;
private final String mNamespace;
private final String mUri;
AppSearchResult<Void> mFailureResult;
private static final String SCHEMA_TYPE_FIELD = "schemaType";
private static final String NAMESPACE_FIELD = "namespace";
private static final String URI_FIELD = "uri";
private static final String ERROR_MESSAGE_FIELD = "errorMessage";
private static final String RESULT_CODE_FIELD = "resultCode";
MigrationFailure(
@NonNull String schemaType,
@NonNull String namespace,
@NonNull String uri,
@NonNull AppSearchResult<Void> result) {
mSchemaType = schemaType;
mNamespace = namespace;
mUri = uri;
mFailureResult = result;
private final Bundle mBundle;
MigrationFailure(@NonNull Bundle bundle) {
mBundle = bundle;
}
/**
* Returns the Bundle of the {@link MigrationFailure}.
*
* @hide
*/
@NonNull
public Bundle getBundle() {
return mBundle;
}
/** Returns the schema type of the {@link GenericDocument} that fails to be migrated. */
@NonNull
public String getSchemaType() {
return mSchemaType;
return mBundle.getString(SCHEMA_TYPE_FIELD, /*defaultValue=*/ "");
}
/** Returns the namespace of the {@link GenericDocument} that fails to be migrated. */
@NonNull
public String getNamespace() {
return mNamespace;
return mBundle.getString(NAMESPACE_FIELD, /*defaultValue=*/ "");
}
/** Returns the uri of the {@link GenericDocument} that fails to be migrated. */
@NonNull
public String getUri() {
return mUri;
return mBundle.getString(URI_FIELD, /*defaultValue=*/ "");
}
/**
@@ -252,7 +310,69 @@ public class SetSchemaResponse {
*/
@NonNull
public AppSearchResult<Void> getAppSearchResult() {
return mFailureResult;
return AppSearchResult.newFailedResult(
mBundle.getInt(RESULT_CODE_FIELD),
mBundle.getString(ERROR_MESSAGE_FIELD, /*defaultValue=*/ ""));
}
/**
* Builder for {@link MigrationFailure} objects.
*
* @hide
*/
public static class Builder {
private String mSchemaType;
private String mNamespace;
private String mUri;
private final Bundle mBundle = new Bundle();
private AppSearchResult<Void> mFailureResult;
private boolean mBuilt = false;
/** Sets the schema type for the {@link MigrationFailure}. */
@NonNull
public Builder setSchemaType(@NonNull String schemaType) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mSchemaType = Preconditions.checkNotNull(schemaType);
return this;
}
/** Sets the namespace for the {@link MigrationFailure}. */
@NonNull
public Builder setNamespace(@NonNull String namespace) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mNamespace = Preconditions.checkNotNull(namespace);
return this;
}
/** Sets the uri for the {@link MigrationFailure}. */
@NonNull
public Builder setUri(@NonNull String uri) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mUri = Preconditions.checkNotNull(uri);
return this;
}
/** Sets the failure {@link AppSearchResult} for the {@link MigrationFailure}. */
@NonNull
public Builder setAppSearchResult(@NonNull AppSearchResult<Void> appSearchResult) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Preconditions.checkState(!appSearchResult.isSuccess(), "Input a success result");
mFailureResult = Preconditions.checkNotNull(appSearchResult);
return this;
}
/** Builds a {@link MigrationFailure} object. */
@NonNull
public MigrationFailure build() {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mBundle.putString(SCHEMA_TYPE_FIELD, mSchemaType);
mBundle.putString(NAMESPACE_FIELD, mNamespace);
mBundle.putString(URI_FIELD, mUri);
mBundle.putString(ERROR_MESSAGE_FIELD, mFailureResult.getErrorMessage());
mBundle.putInt(RESULT_CODE_FIELD, mFailureResult.getResultCode());
mBuilt = true;
return new MigrationFailure(mBundle);
}
}
}
}

View File

@@ -1,125 +0,0 @@
/*
* 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 android.app.appsearch;
import android.annotation.NonNull;
import android.os.Bundle;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* This class represents the results of setSchema().
*
* @hide
*/
public class SetSchemaResult {
public static final String DELETED_SCHEMA_TYPES_FIELD = "deletedSchemaTypes";
public static final String INCOMPATIBLE_SCHEMA_TYPES_FIELD = "incompatibleSchemaTypes";
public static final String RESULT_CODE_FIELD = "resultCode";
private final List<String> mDeletedSchemaTypes;
private final List<String> mIncompatibleSchemaTypes;
private final Bundle mBundle;
SetSchemaResult(@NonNull Bundle bundle) {
mBundle = Preconditions.checkNotNull(bundle);
mDeletedSchemaTypes =
Preconditions.checkNotNull(mBundle.getStringArrayList(DELETED_SCHEMA_TYPES_FIELD));
mIncompatibleSchemaTypes =
Preconditions.checkNotNull(
mBundle.getStringArrayList(INCOMPATIBLE_SCHEMA_TYPES_FIELD));
}
/** Returns the {@link Bundle} of this class. */
@NonNull
public Bundle getBundle() {
return mBundle;
}
/** returns all deleted schema types in this setSchema call. */
@NonNull
public List<String> getDeletedSchemaTypes() {
return Collections.unmodifiableList(mDeletedSchemaTypes);
}
/** returns all incompatible schema types in this setSchema call. */
@NonNull
public List<String> getIncompatibleSchemaTypes() {
return Collections.unmodifiableList(mIncompatibleSchemaTypes);
}
/**
* returns the {@link android.app.appsearch.AppSearchResult.ResultCode} of the {@link
* AppSearchSession#setSchema} call.
*/
public int getResultCode() {
return mBundle.getInt(RESULT_CODE_FIELD);
}
/** Builder for {@link SetSchemaResult} objects. */
public static final class Builder {
private final ArrayList<String> mDeletedSchemaTypes = new ArrayList<>();
private final ArrayList<String> mIncompatibleSchemaTypes = new ArrayList<>();
@AppSearchResult.ResultCode private int mResultCode;
private boolean mBuilt = false;
/** Adds a deletedSchemaTypes to the {@link SetSchemaResult}. */
@NonNull
public Builder addDeletedSchemaType(@NonNull String deletedSchemaType) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mDeletedSchemaTypes.add(Preconditions.checkNotNull(deletedSchemaType));
return this;
}
/** Adds a incompatible SchemaTypes to the {@link SetSchemaResult}. */
@NonNull
public Builder addIncompatibleSchemaType(@NonNull String incompatibleSchemaTypes) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mIncompatibleSchemaTypes.add(Preconditions.checkNotNull(incompatibleSchemaTypes));
return this;
}
/**
* Sets the {@link android.app.appsearch.AppSearchResult.ResultCode} of the {@link
* AppSearchSession#setSchema} call to the {@link SetSchemaResult}
*/
@NonNull
public Builder setResultCode(@AppSearchResult.ResultCode int resultCode) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mResultCode = resultCode;
return this;
}
/** Builds a {@link SetSchemaResult}. */
@NonNull
public SetSchemaResult build() {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Bundle bundle = new Bundle();
bundle.putStringArrayList(
SetSchemaResult.DELETED_SCHEMA_TYPES_FIELD, mDeletedSchemaTypes);
bundle.putStringArrayList(
SetSchemaResult.INCOMPATIBLE_SCHEMA_TYPES_FIELD, mIncompatibleSchemaTypes);
bundle.putInt(RESULT_CODE_FIELD, mResultCode);
mBuilt = true;
return new SetSchemaResult(bundle);
}
}
}

View File

@@ -25,7 +25,7 @@ import android.app.appsearch.GetByUriRequest;
import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.SearchResultPage;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaResult;
import android.app.appsearch.SetSchemaResponse;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import android.os.Bundle;
@@ -41,7 +41,7 @@ import com.android.server.appsearch.external.localstorage.converter.ResultCodeTo
import com.android.server.appsearch.external.localstorage.converter.SchemaToProtoConverter;
import com.android.server.appsearch.external.localstorage.converter.SearchResultToProtoConverter;
import com.android.server.appsearch.external.localstorage.converter.SearchSpecToProtoConverter;
import com.android.server.appsearch.external.localstorage.converter.SetSchemaResultToProtoConverter;
import com.android.server.appsearch.external.localstorage.converter.SetSchemaResponseToProtoConverter;
import com.android.server.appsearch.external.localstorage.converter.TypePropertyPathToProtoConverter;
import com.google.android.icing.IcingSearchEngine;
@@ -73,6 +73,7 @@ import com.google.android.icing.proto.StatusProto;
import com.google.android.icing.proto.TypePropertyMask;
import com.google.android.icing.proto.UsageReport;
import java.io.Closeable;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
@@ -119,7 +120,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
* @hide
*/
@WorkerThread
public final class AppSearchImpl {
public final class AppSearchImpl implements Closeable {
private static final String TAG = "AppSearchImpl";
@VisibleForTesting static final char DATABASE_DELIMITER = '/';
@@ -151,12 +152,16 @@ public final class AppSearchImpl {
private final Map<String, Set<String>> mNamespaceMapLocked = new HashMap<>();
/**
* The counter to check when to call {@link #checkForOptimizeLocked(boolean)}. The interval is
* {@link #CHECK_OPTIMIZE_INTERVAL}.
* The counter to check when to call {@link #checkForOptimize}. The interval is {@link
* #CHECK_OPTIMIZE_INTERVAL}.
*/
@GuardedBy("mReadWriteLock")
private int mOptimizeIntervalCountLocked = 0;
/** Whether this instance has been closed, and therefore unusable. */
@GuardedBy("mReadWriteLock")
private boolean mClosedLocked = false;
/**
* Creates and initializes an instance of {@link AppSearchImpl} which writes data to the given
* folder.
@@ -208,7 +213,7 @@ public final class AppSearchImpl {
} catch (AppSearchException e) {
Log.w(TAG, "Error initializing, resetting IcingSearchEngine.", e);
// Some error. Reset and see if it fixes it.
reset();
resetLocked();
return;
}
@@ -222,11 +227,6 @@ public final class AppSearchImpl {
for (String prefixedNamespace : getAllNamespacesResultProto.getNamespacesList()) {
addToMap(mNamespaceMapLocked, getPrefix(prefixedNamespace), prefixedNamespace);
}
// TODO(b/155939114): It's possible to optimize after init, which would reduce the time
// to when we're able to serve queries. Consider moving this optimize call out.
checkForOptimizeLocked(/* force= */ true);
} finally {
mReadWriteLock.writeLock().unlock();
}
@@ -240,12 +240,45 @@ public final class AppSearchImpl {
void initializeVisibilityStore() throws AppSearchException {
mReadWriteLock.writeLock().lock();
try {
throwIfClosedLocked();
mVisibilityStoreLocked.initialize();
} finally {
mReadWriteLock.writeLock().unlock();
}
}
@GuardedBy("mReadWriteLock")
private void throwIfClosedLocked() {
if (mClosedLocked) {
throw new IllegalStateException("Trying to use a closed AppSearchImpl instance.");
}
}
/**
* Persists data to disk and closes the instance.
*
* <p>This instance is no longer usable after it's been closed. Call {@link #create} to create a
* new, usable instance.
*/
@Override
public void close() {
mReadWriteLock.writeLock().lock();
try {
if (mClosedLocked) {
return;
}
persistToDisk();
mIcingSearchEngineLocked.close();
mClosedLocked = true;
} catch (AppSearchException e) {
Log.w(TAG, "Error when closing AppSearchImpl.", e);
} finally {
mReadWriteLock.writeLock().unlock();
}
}
/**
* Updates the AppSearch schema for this app.
*
@@ -259,10 +292,14 @@ public final class AppSearchImpl {
* @param schemasPackageAccessible Schema types that are visible to the specified packages.
* @param forceOverride Whether to force-apply the schema even if it is incompatible. Documents
* which do not comply with the new schema will be deleted.
* @throws AppSearchException on IcingSearchEngine error.
* @throws AppSearchException On IcingSearchEngine error. If the status code is
* FAILED_PRECONDITION for the incompatible change, the exception will be converted to the
* SetSchemaResponse.
* @return The response contains deleted schema types and incompatible schema types of this
* call.
*/
@NonNull
public SetSchemaResult setSchema(
public SetSchemaResponse setSchema(
@NonNull String packageName,
@NonNull String databaseName,
@NonNull List<AppSearchSchema> schemas,
@@ -272,6 +309,8 @@ public final class AppSearchImpl {
throws AppSearchException {
mReadWriteLock.writeLock().lock();
try {
throwIfClosedLocked();
SchemaProto.Builder existingSchemaBuilder = getSchemaProtoLocked().toBuilder();
SchemaProto.Builder newSchemaBuilder = SchemaProto.newBuilder();
@@ -297,9 +336,16 @@ public final class AppSearchImpl {
try {
checkSuccess(setSchemaResultProto.getStatus());
} catch (AppSearchException e) {
if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0
|| setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0) {
return SetSchemaResultToProtoConverter.toSetSchemaResult(
// Swallow the exception for the incompatible change case. We will propagate
// those deleted schemas and incompatible types to the SetSchemaResponse.
boolean isFailedPrecondition =
setSchemaResultProto.getStatus().getCode()
== StatusProto.Code.FAILED_PRECONDITION;
boolean isIncompatible =
setSchemaResultProto.getDeletedSchemaTypesCount() > 0
|| setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0;
if (isFailedPrecondition && isIncompatible) {
return SetSchemaResponseToProtoConverter.toSetSchemaResponse(
setSchemaResultProto, prefix);
} else {
throw e;
@@ -328,16 +374,8 @@ public final class AppSearchImpl {
prefixedSchemasNotPlatformSurfaceable,
prefixedSchemasPackageAccessible);
// Determine whether to schedule an immediate optimize.
if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0
|| (setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0
&& forceOverride)) {
// Any existing schemas which is not in 'schemas' will be deleted, and all
// documents of these types were also deleted. And so well if we force override
// incompatible schemas.
checkForOptimizeLocked(/* force= */ true);
}
return SetSchemaResultToProtoConverter.toSetSchemaResult(setSchemaResultProto, prefix);
return SetSchemaResponseToProtoConverter.toSetSchemaResponse(
setSchemaResultProto, prefix);
} finally {
mReadWriteLock.writeLock().unlock();
}
@@ -355,44 +393,47 @@ public final class AppSearchImpl {
@NonNull
public List<AppSearchSchema> getSchema(
@NonNull String packageName, @NonNull String databaseName) throws AppSearchException {
SchemaProto fullSchema;
mReadWriteLock.readLock().lock();
try {
fullSchema = getSchemaProtoLocked();
throwIfClosedLocked();
SchemaProto fullSchema = getSchemaProtoLocked();
String prefix = createPrefix(packageName, databaseName);
List<AppSearchSchema> result = new ArrayList<>();
for (int i = 0; i < fullSchema.getTypesCount(); i++) {
String typePrefix = getPrefix(fullSchema.getTypes(i).getSchemaType());
if (!prefix.equals(typePrefix)) {
continue;
}
// Rewrite SchemaProto.types.schema_type
SchemaTypeConfigProto.Builder typeConfigBuilder =
fullSchema.getTypes(i).toBuilder();
String newSchemaType = typeConfigBuilder.getSchemaType().substring(prefix.length());
typeConfigBuilder.setSchemaType(newSchemaType);
// Rewrite SchemaProto.types.properties.schema_type
for (int propertyIdx = 0;
propertyIdx < typeConfigBuilder.getPropertiesCount();
propertyIdx++) {
PropertyConfigProto.Builder propertyConfigBuilder =
typeConfigBuilder.getProperties(propertyIdx).toBuilder();
if (!propertyConfigBuilder.getSchemaType().isEmpty()) {
String newPropertySchemaType =
propertyConfigBuilder.getSchemaType().substring(prefix.length());
propertyConfigBuilder.setSchemaType(newPropertySchemaType);
typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder);
}
}
AppSearchSchema schema =
SchemaToProtoConverter.toAppSearchSchema(typeConfigBuilder);
result.add(schema);
}
return result;
} finally {
mReadWriteLock.readLock().unlock();
}
String prefix = createPrefix(packageName, databaseName);
List<AppSearchSchema> result = new ArrayList<>();
for (int i = 0; i < fullSchema.getTypesCount(); i++) {
String typePrefix = getPrefix(fullSchema.getTypes(i).getSchemaType());
if (!prefix.equals(typePrefix)) {
continue;
}
// Rewrite SchemaProto.types.schema_type
SchemaTypeConfigProto.Builder typeConfigBuilder = fullSchema.getTypes(i).toBuilder();
String newSchemaType = typeConfigBuilder.getSchemaType().substring(prefix.length());
typeConfigBuilder.setSchemaType(newSchemaType);
// Rewrite SchemaProto.types.properties.schema_type
for (int propertyIdx = 0;
propertyIdx < typeConfigBuilder.getPropertiesCount();
propertyIdx++) {
PropertyConfigProto.Builder propertyConfigBuilder =
typeConfigBuilder.getProperties(propertyIdx).toBuilder();
if (!propertyConfigBuilder.getSchemaType().isEmpty()) {
String newPropertySchemaType =
propertyConfigBuilder.getSchemaType().substring(prefix.length());
propertyConfigBuilder.setSchemaType(newPropertySchemaType);
typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder);
}
}
AppSearchSchema schema = SchemaToProtoConverter.toAppSearchSchema(typeConfigBuilder);
result.add(schema);
}
return result;
}
/**
@@ -410,23 +451,22 @@ public final class AppSearchImpl {
@NonNull String databaseName,
@NonNull GenericDocument document)
throws AppSearchException {
DocumentProto.Builder documentBuilder =
GenericDocumentToProtoConverter.toDocumentProto(document).toBuilder();
String prefix = createPrefix(packageName, databaseName);
addPrefixToDocument(documentBuilder, prefix);
PutResultProto putResultProto;
mReadWriteLock.writeLock().lock();
try {
putResultProto = mIcingSearchEngineLocked.put(documentBuilder.build());
throwIfClosedLocked();
DocumentProto.Builder documentBuilder =
GenericDocumentToProtoConverter.toDocumentProto(document).toBuilder();
String prefix = createPrefix(packageName, databaseName);
addPrefixToDocument(documentBuilder, prefix);
PutResultProto putResultProto = mIcingSearchEngineLocked.put(documentBuilder.build());
addToMap(mNamespaceMapLocked, prefix, documentBuilder.getNamespace());
// The existing documents with same URI will be deleted, so there maybe some resources
// could be released after optimize().
checkForOptimizeLocked(/* force= */ false);
checkSuccess(putResultProto.getStatus());
} finally {
mReadWriteLock.writeLock().unlock();
}
checkSuccess(putResultProto.getStatus());
}
/**
@@ -451,40 +491,42 @@ public final class AppSearchImpl {
@NonNull String uri,
@NonNull Map<String, List<String>> typePropertyPaths)
throws AppSearchException {
GetResultProto getResultProto;
List<TypePropertyMask> nonPrefixedPropertyMasks =
TypePropertyPathToProtoConverter.toTypePropertyMaskList(typePropertyPaths);
List<TypePropertyMask> prefixedPropertyMasks =
new ArrayList<>(nonPrefixedPropertyMasks.size());
for (int i = 0; i < nonPrefixedPropertyMasks.size(); ++i) {
TypePropertyMask typePropertyMask = nonPrefixedPropertyMasks.get(i);
String nonPrefixedType = typePropertyMask.getSchemaType();
String prefixedType =
nonPrefixedType.equals(GetByUriRequest.PROJECTION_SCHEMA_TYPE_WILDCARD)
? nonPrefixedType
: createPrefix(packageName, databaseName) + nonPrefixedType;
prefixedPropertyMasks.add(
typePropertyMask.toBuilder().setSchemaType(prefixedType).build());
}
GetResultSpecProto getResultSpec =
GetResultSpecProto.newBuilder()
.addAllTypePropertyMasks(prefixedPropertyMasks)
.build();
mReadWriteLock.readLock().lock();
try {
getResultProto =
throwIfClosedLocked();
List<TypePropertyMask> nonPrefixedPropertyMasks =
TypePropertyPathToProtoConverter.toTypePropertyMaskList(typePropertyPaths);
List<TypePropertyMask> prefixedPropertyMasks =
new ArrayList<>(nonPrefixedPropertyMasks.size());
for (int i = 0; i < nonPrefixedPropertyMasks.size(); ++i) {
TypePropertyMask typePropertyMask = nonPrefixedPropertyMasks.get(i);
String nonPrefixedType = typePropertyMask.getSchemaType();
String prefixedType =
nonPrefixedType.equals(GetByUriRequest.PROJECTION_SCHEMA_TYPE_WILDCARD)
? nonPrefixedType
: createPrefix(packageName, databaseName) + nonPrefixedType;
prefixedPropertyMasks.add(
typePropertyMask.toBuilder().setSchemaType(prefixedType).build());
}
GetResultSpecProto getResultSpec =
GetResultSpecProto.newBuilder()
.addAllTypePropertyMasks(prefixedPropertyMasks)
.build();
GetResultProto getResultProto =
mIcingSearchEngineLocked.get(
createPrefix(packageName, databaseName) + namespace,
uri,
getResultSpec);
checkSuccess(getResultProto.getStatus());
DocumentProto.Builder documentBuilder = getResultProto.getDocument().toBuilder();
removePrefixesFromDocument(documentBuilder);
return GenericDocumentToProtoConverter.toGenericDocument(documentBuilder.build());
} finally {
mReadWriteLock.readLock().unlock();
}
checkSuccess(getResultProto.getStatus());
DocumentProto.Builder documentBuilder = getResultProto.getDocument().toBuilder();
removePrefixesFromDocument(documentBuilder);
return GenericDocumentToProtoConverter.toGenericDocument(documentBuilder.build());
}
/**
@@ -507,17 +549,19 @@ public final class AppSearchImpl {
@NonNull String queryExpression,
@NonNull SearchSpec searchSpec)
throws AppSearchException {
List<String> filterPackageNames = searchSpec.getFilterPackageNames();
if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
// 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.
return new SearchResultPage(Bundle.EMPTY);
}
mReadWriteLock.readLock().lock();
try {
throwIfClosedLocked();
List<String> filterPackageNames = searchSpec.getFilterPackageNames();
if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
// 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.
return new SearchResultPage(Bundle.EMPTY);
}
String prefix = createPrefix(packageName, databaseName);
Set<String> allowedPrefixedSchemas = getAllowedPrefixSchemas(prefix, searchSpec);
Set<String> allowedPrefixedSchemas = getAllowedPrefixSchemasLocked(prefix, searchSpec);
return doQueryLocked(
Collections.singleton(createPrefix(packageName, databaseName)),
@@ -552,6 +596,8 @@ public final class AppSearchImpl {
throws AppSearchException {
mReadWriteLock.readLock().lock();
try {
throwIfClosedLocked();
Set<String> packageFilters = new ArraySet<>(searchSpec.getFilterPackageNames());
Set<String> prefixFilters = new ArraySet<>();
Set<String> allPrefixes = mNamespaceMapLocked.keySet();
@@ -654,6 +700,8 @@ public final class AppSearchImpl {
public SearchResultPage getNextPage(long nextPageToken) throws AppSearchException {
mReadWriteLock.readLock().lock();
try {
throwIfClosedLocked();
SearchResultProto searchResultProto =
mIcingSearchEngineLocked.getNextPage(nextPageToken);
checkSuccess(searchResultProto.getStatus());
@@ -674,6 +722,8 @@ public final class AppSearchImpl {
public void invalidateNextPageToken(long nextPageToken) {
mReadWriteLock.readLock().lock();
try {
throwIfClosedLocked();
mIcingSearchEngineLocked.invalidateNextPageToken(nextPageToken);
} finally {
mReadWriteLock.readLock().unlock();
@@ -688,16 +738,19 @@ public final class AppSearchImpl {
@NonNull String uri,
long usageTimestampMillis)
throws AppSearchException {
String prefixedNamespace = createPrefix(packageName, databaseName) + namespace;
UsageReport report =
UsageReport.newBuilder()
.setDocumentNamespace(prefixedNamespace)
.setDocumentUri(uri)
.setUsageTimestampMs(usageTimestampMillis)
.setUsageType(UsageReport.UsageType.USAGE_TYPE1)
.build();
mReadWriteLock.writeLock().lock();
try {
throwIfClosedLocked();
String prefixedNamespace = createPrefix(packageName, databaseName) + namespace;
UsageReport report =
UsageReport.newBuilder()
.setDocumentNamespace(prefixedNamespace)
.setDocumentUri(uri)
.setUsageTimestampMs(usageTimestampMillis)
.setUsageType(UsageReport.UsageType.USAGE_TYPE1)
.build();
ReportUsageResultProto result = mIcingSearchEngineLocked.reportUsage(report);
checkSuccess(result.getStatus());
} finally {
@@ -722,16 +775,18 @@ public final class AppSearchImpl {
@NonNull String namespace,
@NonNull String uri)
throws AppSearchException {
String prefixedNamespace = createPrefix(packageName, databaseName) + namespace;
DeleteResultProto deleteResultProto;
mReadWriteLock.writeLock().lock();
try {
deleteResultProto = mIcingSearchEngineLocked.delete(prefixedNamespace, uri);
checkForOptimizeLocked(/* force= */ false);
throwIfClosedLocked();
String prefixedNamespace = createPrefix(packageName, databaseName) + namespace;
DeleteResultProto deleteResultProto =
mIcingSearchEngineLocked.delete(prefixedNamespace, uri);
checkSuccess(deleteResultProto.getStatus());
} finally {
mReadWriteLock.writeLock().unlock();
}
checkSuccess(deleteResultProto.getStatus());
}
/**
@@ -751,22 +806,25 @@ public final class AppSearchImpl {
@NonNull String queryExpression,
@NonNull SearchSpec searchSpec)
throws AppSearchException {
List<String> filterPackageNames = searchSpec.getFilterPackageNames();
if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
// 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
// remove.
return;
}
SearchSpecProto searchSpecProto = SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
SearchSpecProto.Builder searchSpecBuilder =
searchSpecProto.toBuilder().setQuery(queryExpression);
DeleteByQueryResultProto deleteResultProto;
mReadWriteLock.writeLock().lock();
try {
throwIfClosedLocked();
List<String> filterPackageNames = searchSpec.getFilterPackageNames();
if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
// 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
// remove.
return;
}
SearchSpecProto searchSpecProto =
SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
SearchSpecProto.Builder searchSpecBuilder =
searchSpecProto.toBuilder().setQuery(queryExpression);
String prefix = createPrefix(packageName, databaseName);
Set<String> allowedPrefixedSchemas = getAllowedPrefixSchemas(prefix, searchSpec);
Set<String> allowedPrefixedSchemas = getAllowedPrefixSchemasLocked(prefix, searchSpec);
// rewriteSearchSpecForPrefixesLocked will return false if there is nothing to search
// over given their search filters, so we can return early and skip sending request
@@ -775,15 +833,16 @@ public final class AppSearchImpl {
searchSpecBuilder, Collections.singleton(prefix), allowedPrefixedSchemas)) {
return;
}
deleteResultProto = mIcingSearchEngineLocked.deleteByQuery(searchSpecBuilder.build());
checkForOptimizeLocked(/* force= */ true);
DeleteByQueryResultProto deleteResultProto =
mIcingSearchEngineLocked.deleteByQuery(searchSpecBuilder.build());
// It seems that the caller wants to get success if the data matching the query is
// not in the DB because it was not there or was successfully deleted.
checkCodeOneOf(
deleteResultProto.getStatus(), StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
} finally {
mReadWriteLock.writeLock().unlock();
}
// It seems that the caller wants to get success if the data matching the query is not in
// the DB because it was not there or was successfully deleted.
checkCodeOneOf(
deleteResultProto.getStatus(), StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
}
/**
@@ -799,9 +858,16 @@ public final class AppSearchImpl {
* @throws AppSearchException on any error that AppSearch persist data to disk.
*/
public void persistToDisk() throws AppSearchException {
PersistToDiskResultProto persistToDiskResultProto =
mIcingSearchEngineLocked.persistToDisk();
checkSuccess(persistToDiskResultProto.getStatus());
mReadWriteLock.writeLock().lock();
try {
throwIfClosedLocked();
PersistToDiskResultProto persistToDiskResultProto =
mIcingSearchEngineLocked.persistToDisk();
checkSuccess(persistToDiskResultProto.getStatus());
} finally {
mReadWriteLock.writeLock().unlock();
}
}
/**
@@ -814,21 +880,16 @@ public final class AppSearchImpl {
*
* @throws AppSearchException on IcingSearchEngine error.
*/
private void reset() throws AppSearchException {
ResetResultProto resetResultProto;
mReadWriteLock.writeLock().lock();
try {
resetResultProto = mIcingSearchEngineLocked.reset();
mOptimizeIntervalCountLocked = 0;
mSchemaMapLocked.clear();
mNamespaceMapLocked.clear();
@GuardedBy("mReadWriteLock")
private void resetLocked() throws AppSearchException {
ResetResultProto resetResultProto = mIcingSearchEngineLocked.reset();
mOptimizeIntervalCountLocked = 0;
mSchemaMapLocked.clear();
mNamespaceMapLocked.clear();
// Must be called after everything else since VisibilityStore may repopulate
// IcingSearchEngine with an initial schema.
mVisibilityStoreLocked.handleReset();
} finally {
mReadWriteLock.writeLock().unlock();
}
// Must be called after everything else since VisibilityStore may repopulate
// IcingSearchEngine with an initial schema.
mVisibilityStoreLocked.handleReset();
checkSuccess(resetResultProto.getStatus());
}
@@ -1079,7 +1140,8 @@ public final class AppSearchImpl {
* <p>This only checks intersection of schema filters on the search spec with those that the
* prefix owns itself. This does not check global query permissions.
*/
private Set<String> getAllowedPrefixSchemas(
@GuardedBy("mReadWriteLock")
private Set<String> getAllowedPrefixSchemasLocked(
@NonNull String prefix, @NonNull SearchSpec searchSpec) {
Set<String> allowedPrefixedSchemas = new ArraySet<>();
@@ -1295,35 +1357,72 @@ public final class AppSearchImpl {
/**
* Checks whether {@link IcingSearchEngine#optimize()} should be called to release resources.
*
* <p>This method should be only called in mutate methods and get the WRITE lock to keep thread
* safety.
* <p>This method should be only called after a mutation to local storage backend which deletes
* a mass of data and could release lots resources after {@link IcingSearchEngine#optimize()}.
*
* <p>This method will trigger {@link IcingSearchEngine#getOptimizeInfo()} to check resources
* that could be released for every {@link #CHECK_OPTIMIZE_INTERVAL} mutations.
*
* <p>{@link IcingSearchEngine#optimize()} should be called only if {@link
* GetOptimizeInfoResultProto} shows there is enough resources could be released.
*
* <p>{@link IcingSearchEngine#getOptimizeInfo()} should be called once per {@link
* #CHECK_OPTIMIZE_INTERVAL} of remove executions.
*
* @param force whether we should directly call {@link IcingSearchEngine#getOptimizeInfo()}.
* @param mutationSize The number of how many mutations have been executed for current request.
* An inside counter will accumulates it. Once the counter reaches {@link
* #CHECK_OPTIMIZE_INTERVAL}, {@link IcingSearchEngine#getOptimizeInfo()} will be triggered
* and the counter will be reset.
*/
@GuardedBy("mReadWriteLock")
private void checkForOptimizeLocked(boolean force) throws AppSearchException {
++mOptimizeIntervalCountLocked;
if (force || mOptimizeIntervalCountLocked >= CHECK_OPTIMIZE_INTERVAL) {
mOptimizeIntervalCountLocked = 0;
public void checkForOptimize(int mutationSize) throws AppSearchException {
mReadWriteLock.writeLock().lock();
try {
mOptimizeIntervalCountLocked += mutationSize;
if (mOptimizeIntervalCountLocked >= CHECK_OPTIMIZE_INTERVAL) {
checkForOptimize();
}
} finally {
mReadWriteLock.writeLock().unlock();
}
}
/**
* Checks whether {@link IcingSearchEngine#optimize()} should be called to release resources.
*
* <p>This method will directly trigger {@link IcingSearchEngine#getOptimizeInfo()} to check
* resources that could be released.
*
* <p>{@link IcingSearchEngine#optimize()} should be called only if {@link
* GetOptimizeInfoResultProto} shows there is enough resources could be released.
*/
public void checkForOptimize() throws AppSearchException {
mReadWriteLock.writeLock().lock();
try {
GetOptimizeInfoResultProto optimizeInfo = getOptimizeInfoResultLocked();
checkSuccess(optimizeInfo.getStatus());
mOptimizeIntervalCountLocked = 0;
// Second threshold, decide when to call optimize().
if (optimizeInfo.getOptimizableDocs() >= OPTIMIZE_THRESHOLD_DOC_COUNT
|| optimizeInfo.getEstimatedOptimizableBytes() >= OPTIMIZE_THRESHOLD_BYTES) {
// TODO(b/155939114): call optimize in the same thread will slow down api calls
// significantly. Move this call to background.
OptimizeResultProto optimizeResultProto = mIcingSearchEngineLocked.optimize();
checkSuccess(optimizeResultProto.getStatus());
optimize();
}
// TODO(b/147699081): Return OptimizeResultProto & log lost data detail once we add
// a field to indicate lost_schema and lost_documents in OptimizeResultProto.
// go/icing-library-apis.
} finally {
mReadWriteLock.writeLock().unlock();
}
// TODO(b/147699081): Return OptimizeResultProto & log lost data detail once we add
// a field to indicate lost_schema and lost_documents in OptimizeResultProto.
// go/icing-library-apis.
}
/**
* Triggers {@link IcingSearchEngine#optimize()} directly.
*
* <p>This method should be only called as a scheduled task in AppSearch Platform backend.
*/
public void optimize() throws AppSearchException {
mReadWriteLock.writeLock().lock();
try {
OptimizeResultProto optimizeResultProto = mIcingSearchEngineLocked.optimize();
checkSuccess(optimizeResultProto.getStatus());
} finally {
mReadWriteLock.writeLock().unlock();
}
}

View File

@@ -76,7 +76,7 @@ class AppSearchMigrationHelperImpl implements AppSearchMigrationHelper {
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
// TODO(b/177266929) change the output stream so that we can use it in platform
CodedOutputStream codedOutputStream = CodedOutputStream.newInstance(outputStream);
SearchResultPage searchResultPage =
mAppSearchImpl.query(
@@ -126,11 +126,13 @@ class AppSearchMigrationHelperImpl implements AppSearchMigrationHelper {
try {
mAppSearchImpl.putDocument(mPackageName, mDatabaseName, document);
} catch (Throwable t) {
responseBuilder.setFailure(
document.getSchemaType(),
document.getNamespace(),
document.getUri(),
throwableToFailedResult(t));
responseBuilder.addMigrationFailure(
new SetSchemaResponse.MigrationFailure.Builder()
.setNamespace(document.getNamespace())
.setSchemaType(document.getSchemaType())
.setUri(document.getUri())
.setAppSearchResult(throwableToFailedResult(t))
.build());
}
}
mAppSearchImpl.persistToDisk();

View File

@@ -17,45 +17,41 @@
package com.android.server.appsearch.external.localstorage.converter;
import android.annotation.NonNull;
import android.app.appsearch.SetSchemaResult;
import android.app.appsearch.SetSchemaResponse;
import com.android.internal.util.Preconditions;
import com.google.android.icing.proto.SetSchemaResultProto;
/**
* Translates a {@link SetSchemaResultProto} into {@link SetSchemaResult}.
* Translates a {@link SetSchemaResultProto} into {@link SetSchemaResponse}.
*
* @hide
*/
public class SetSchemaResultToProtoConverter {
public class SetSchemaResponseToProtoConverter {
private SetSchemaResultToProtoConverter() {}
private SetSchemaResponseToProtoConverter() {}
/**
* Translate a {@link SetSchemaResultProto} into {@link SetSchemaResult}.
* Translate a {@link SetSchemaResultProto} into {@link SetSchemaResponse}.
*
* @param proto The {@link SetSchemaResultProto} containing results.
* @param prefix The prefix need to removed from schemaTypes
* @return {@link SetSchemaResult} of results.
* @return The {@link SetSchemaResponse} object.
*/
@NonNull
public static SetSchemaResult toSetSchemaResult(
public static SetSchemaResponse toSetSchemaResponse(
@NonNull SetSchemaResultProto proto, @NonNull String prefix) {
Preconditions.checkNotNull(proto);
Preconditions.checkNotNull(prefix);
SetSchemaResult.Builder builder =
new SetSchemaResult.Builder()
.setResultCode(
ResultCodeToProtoConverter.toResultCode(
proto.getStatus().getCode()));
SetSchemaResponse.Builder builder = new SetSchemaResponse.Builder();
for (int i = 0; i < proto.getDeletedSchemaTypesCount(); i++) {
builder.addDeletedSchemaType(proto.getDeletedSchemaTypes(i).substring(prefix.length()));
builder.addDeletedType(proto.getDeletedSchemaTypes(i).substring(prefix.length()));
}
for (int i = 0; i < proto.getIncompatibleSchemaTypesCount(); i++) {
builder.addIncompatibleSchemaType(
builder.addIncompatibleType(
proto.getIncompatibleSchemaTypes(i).substring(prefix.length()));
}

View File

@@ -1 +1 @@
I2bf8bd9db1b71b7da4ab50dd7480e4529678413a
Ia9a8daef1a6d7d9432f7808d440abd64f4797701

View File

@@ -77,10 +77,14 @@ public interface AppSearchSessionShim extends Closeable {
* android.app.appsearch.exceptions.AppSearchException} with the {@link
* AppSearchResult#RESULT_INVALID_SCHEMA} error code, all documents which are not
* compatible with the new schema will be deleted and the incompatible schema will be
* applied.
* applied. Incompatible types and deleted types will be set into {@link
* SetSchemaResponse#getIncompatibleTypes()} and {@link
* SetSchemaResponse#getDeletedTypes()}, respectively.
* <li>Add a {@link android.app.appsearch.AppSearchSchema.Migrator} for each incompatible type
* and make no deletion. The migrator will migrate documents from it's old schema version
* to the new version. See the migration section below.
* to the new version. Migrated types will be set into both {@link
* SetSchemaResponse#getIncompatibleTypes()} and {@link
* SetSchemaResponse#getMigratedTypes()}. See the migration section below.
* </ul>
*
* <p>It is a no-op to set the same schema as has been previously set; this is handled
@@ -109,10 +113,8 @@ public interface AppSearchSessionShim extends Closeable {
* backwards-compatible, and stored documents won't have any observable changes.
*
* @param request The schema update request.
* @return The pending {@link SetSchemaResponse} of performing this operation. Success if the
* the schema has been set and any migrations has been done. Otherwise, the failure {@link
* android.app.appsearch.SetSchemaResponse.MigrationFailure} indicates which document is
* fail to be migrated.
* @return A {@link ListenableFuture} with exception if we hit any error. Or the pending {@link
* SetSchemaResponse} of performing this operation, if the schema has been successfully set.
* @see android.app.appsearch.AppSearchSchema.Migrator
* @see android.app.appsearch.AppSearchMigrationHelper.Transformer
*/
@@ -144,57 +146,79 @@ public interface AppSearchSessionShim extends Closeable {
ListenableFuture<AppSearchBatchResult<String, Void>> put(@NonNull PutDocumentsRequest request);
/**
* Retrieves {@link GenericDocument}s by URI.
* Gets {@link GenericDocument} objects by URIs and namespace from the {@link
* AppSearchSessionShim} database.
*
* @param request {@link GetByUriRequest} containing URIs to be retrieved.
* @return The pending result of performing this operation. The keys of the returned {@link
* AppSearchBatchResult} are the input URIs. The values are the returned {@link
* GenericDocument}s on success, or a failed {@link AppSearchResult} otherwise. URIs that
* are not found will return a failed {@link AppSearchResult} with a result code of {@link
* AppSearchResult#RESULT_NOT_FOUND}.
* @param request a request containing URIs and namespace to get documents for.
* @return A {@link ListenableFuture} which resolves to an {@link AppSearchBatchResult}. The
* keys of the {@link AppSearchBatchResult} represent the input URIs from the {@link
* GetByUriRequest} object. The values are either the corresponding {@link GenericDocument}
* object for the URI on success, or an {@link AppSearchResult} object on failure. For
* example, if a URI is not found, the value for that URI will be set to an {@link
* AppSearchResult} object with result code: {@link AppSearchResult#RESULT_NOT_FOUND}.
*/
@NonNull
ListenableFuture<AppSearchBatchResult<String, GenericDocument>> getByUri(
@NonNull GetByUriRequest request);
/**
* Searches for documents based on a given query string.
* Retrieves documents from the open {@link AppSearchSessionShim} that match a given query
* string and type of search provided.
*
* <p>Currently we support following features in the raw query format:
* <p>Query strings can be empty, contain one term with no operators, or contain multiple terms
* and operators.
*
* <p>For query strings that are empty, all documents that match the {@link SearchSpec} will be
* returned.
*
* <p>For query strings with a single term and no operators, documents that match the provided
* query string and {@link SearchSpec} will be returned.
*
* <p>The following operators are supported:
*
* <ul>
* <li>AND
* <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and ‘cat’”).
* Example: hello world matches documents that have both ‘hello’ and ‘world’
* <li>AND (implicit)
* <p>AND is an operator that matches documents that contain <i>all</i> provided terms.
* <p><b>NOTE:</b> A space between terms is treated as an "AND" operator. Explicitly
* including "AND" in a query string will treat "AND" as a term, returning documents that
* also contain "AND".
* <p>Example: "apple AND banana" matches documents that contain the terms "apple", "and",
* "banana".
* <p>Example: "apple banana" matches documents that contain both "apple" and "banana".
* <p>Example: "apple banana cherry" matches documents that contain "apple", "banana", and
* "cherry".
* <li>OR
* <p>OR joins (e.g. “match documents that have either the term ‘dog’ or ‘cat’”). Example:
* dog OR puppy
* <li>Exclusion
* <p>Exclude a term (e.g. “match documents that do not have the term ‘dog’”). Example:
* -dog excludes the term ‘dog’
* <li>Grouping terms
* <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g.
* “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”).
* Example: (dog puppy) (cat kitten) two one group containing two terms.
* <li>Property restricts
* <p>Specifies which properties of a document to specifically match terms in (e.g. “match
* documents where the ‘subject’ property contains ‘important’”). Example:
* subject:important matches documents with the term ‘important’ in the ‘subject’ property
* <li>Schema type restricts
* <p>This is similar to property restricts, but allows for restricts on top-level
* document fields, such as schema_type. Clients should be able to limit their query to
* documents of a certain schema_type (e.g. “match documents that are of the ‘Email’
* schema_type”). Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will
* match documents that contain the query term ‘dog’ and are of either the ‘Email’ schema
* type or the ‘Video’ schema type.
* <p>OR is an operator that matches documents that contain <i>any</i> provided term.
* <p>Example: "apple OR banana" matches documents that contain either "apple" or
* "banana".
* <p>Example: "apple OR banana OR cherry" matches documents that contain any of "apple",
* "banana", or "cherry".
* <li>Exclusion (-)
* <p>Exclusion (-) is an operator that matches documents that <i>do not</i> contain the
* provided term.
* <p>Example: "-apple" matches documents that do not contain "apple".
* <li>Grouped Terms
* <p>For queries that require multiple operators and terms, terms can be grouped into
* subqueries. Subqueries are contained within an open "(" and close ")" parenthesis.
* <p>Example: "(donut OR bagel) (coffee OR tea)" matches documents that contain either
* "donut" or "bagel" and either "coffee" or "tea".
* <li>Property Restricts
* <p>For queries that require a term to match a specific {@link AppSearchSchema} property
* of a document, a ":" must be included between the property name and the term.
* <p>Example: "subject:important" matches documents that contain the term "important" in
* the "subject" property.
* </ul>
*
* <p>Additional search specifications, such as filtering by {@link AppSearchSchema} type or
* adding projection, can be set by calling the corresponding {@link SearchSpec.Builder} setter.
*
* <p>This method is lightweight. The heavy work will be done in {@link
* SearchResultsShim#getNextPage()}.
*
* @param queryExpression Query String to search.
* @param searchSpec Spec for setting filters, raw query etc.
* @return The search result of performing this operation.
* @param queryExpression query string to search.
* @param searchSpec spec for setting document filters, adding projection, setting term match
* type, etc.
* @return a {@link SearchResultsShim} object for retrieved matched documents.
*/
@NonNull
SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
@@ -219,13 +243,22 @@ public interface AppSearchSessionShim extends Closeable {
ListenableFuture<Void> reportUsage(@NonNull ReportUsageRequest request);
/**
* Removes {@link GenericDocument}s from the index by URI.
* Removes {@link GenericDocument} objects by URIs and namespace from the {@link
* AppSearchSessionShim} database.
*
* @param request Request containing URIs to be removed.
* @return The pending result of performing this operation. The keys of the returned {@link
* AppSearchBatchResult} are the input URIs. The values are {@code null} on success, or a
* failed {@link AppSearchResult} otherwise. URIs that are not found will return a failed
* {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}.
* <p>Removed documents will no longer be surfaced by {@link #search} or {@link #getByUri}
* calls.
*
* <p><b>NOTE:</b>By default, documents are removed via a soft delete operation. Once the
* document crosses the count threshold or byte usage threshold, the documents will be removed
* from disk.
*
* @param request {@link RemoveByUriRequest} with URIs and namespace to remove from the index.
* @return a {@link ListenableFuture} which resolves to an {@link AppSearchBatchResult}. The
* keys of the {@link AppSearchBatchResult} represent the input URIs from the {@link
* RemoveByUriRequest} object. The values are either {@code null} on success, or a failed
* {@link AppSearchResult} otherwise. URIs that are not found will return a failed {@link
* AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}.
*/
@NonNull
ListenableFuture<AppSearchBatchResult<String, Void>> remove(

View File

@@ -25,7 +25,6 @@ import android.app.appsearch.GenericDocument;
import android.app.appsearch.GetByUriRequest;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResultsShim;
import android.app.appsearch.SetSchemaResponse;
import java.util.ArrayList;
import java.util.List;
@@ -43,15 +42,6 @@ public class AppSearchTestUtils {
return result;
}
// TODO(b/151178558) check setSchemaResponse.xxxtypes for the test need to verify.
public static void checkIsSetSchemaResponseSuccess(Future<SetSchemaResponse> future)
throws Exception {
SetSchemaResponse setSchemaResponse = future.get();
assertWithMessage("SetSchemaResponse not successful.")
.that(setSchemaResponse.isSuccess())
.isTrue();
}
public static List<GenericDocument> doGet(
AppSearchSessionShim session, String namespace, String... uris) throws Exception {
AppSearchBatchResult<String, GenericDocument> result =

View File

@@ -27,43 +27,26 @@ import java.io.Closeable;
*/
public interface GlobalSearchSessionShim extends Closeable {
/**
* Searches across all documents in the storage based on a given query string.
* Retrieves documents from all AppSearch databases that the querying application has access to.
*
* <p>Currently we support following features in the raw query format:
* <p>Applications can be granted access to documents by specifying {@link
* SetSchemaRequest.Builder#setSchemaTypeVisibilityForPackage}, or {@link
* SetSchemaRequest.Builder#setDocumentClassVisibilityForPackage} when building a schema.
*
* <ul>
* <li>AND
* <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and ‘cat’”).
* Example: hello world matches documents that have both ‘hello’ and ‘world’
* <li>OR
* <p>OR joins (e.g. “match documents that have either the term ‘dog’ or ‘cat’”). Example:
* dog OR puppy
* <li>Exclusion
* <p>Exclude a term (e.g. “match documents that do not have the term ‘dog’”). Example:
* -dog excludes the term ‘dog’
* <li>Grouping terms
* <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g.
* “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”).
* Example: (dog puppy) (cat kitten) two one group containing two terms.
* <li>Property restricts
* <p>Specifies which properties of a document to specifically match terms in (e.g. “match
* documents where the ‘subject’ property contains ‘important’”). Example:
* subject:important matches documents with the term ‘important’ in the ‘subject’ property
* <li>Schema type restricts
* <p>This is similar to property restricts, but allows for restricts on top-level
* document fields, such as schema_type. Clients should be able to limit their query to
* documents of a certain schema_type (e.g. “match documents that are of the ‘Email’
* schema_type”). Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will
* match documents that contain the query term ‘dog’ and are of either the ‘Email’ schema
* type or the ‘Video’ schema type.
* </ul>
* <p>Document access can also be granted to system UIs by specifying {@link
* SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi}, or {@link
* SetSchemaRequest.Builder#setDocumentClassVisibilityForSystemUi} when building a schema.
*
* <p>See {@link AppSearchSessionShim#search(String, SearchSpec)} for a detailed explanation on
* forming a query string.
*
* <p>This method is lightweight. The heavy work will be done in {@link
* SearchResultsShim#getNextPage()}.
*
* @param queryExpression Query String to search.
* @param searchSpec Spec for setting filters, raw query etc.
* @return The search result of performing this operation.
* @param queryExpression query string to search.
* @param searchSpec spec for setting document filters, adding projection, setting term match
* type, etc.
* @return a {@link SearchResultsShim} object for retrieved matched documents.
*/
@NonNull
SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);

View File

@@ -16,8 +16,6 @@
package com.android.server.appsearch.external.localstorage;
import static android.app.appsearch.AppSearchResult.RESULT_OK;
import static com.google.common.truth.Truth.assertThat;
import static org.testng.Assert.expectThrows;
@@ -27,7 +25,7 @@ import android.app.appsearch.GenericDocument;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResultPage;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaResult;
import android.app.appsearch.SetSchemaResponse;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import android.util.ArraySet;
@@ -66,14 +64,14 @@ public class AppSearchImplTest {
@Before
public void setUp() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
// Give ourselves global query permissions
mAppSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
context,
VisibilityStore.NO_OP_USER_ID,
/*globalQuerierPackage
=*/ context.getPackageName());
/*globalQuerierPackage=*/ context.getPackageName());
}
// TODO(b/175430168) add test to verify reset is working properly.
@@ -425,18 +423,24 @@ public class AppSearchImplTest {
GetOptimizeInfoResultProto optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(0);
// delete 999 documents , we will reach the threshold to trigger optimize() in next
// delete 999 documents, we will reach the threshold to trigger optimize() in next
// deletion.
for (int i = 0; i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1; i++) {
mAppSearchImpl.remove("package", "database", "namespace", "uri" + i);
}
// optimize() still not be triggered since we are in the interval to call getOptimizeInfo()
// Updates the check for optimize counter, checkForOptimize() will be triggered since
// CHECK_OPTIMIZE_INTERVAL is reached but optimize() won't since
// OPTIMIZE_THRESHOLD_DOC_COUNT is not.
mAppSearchImpl.checkForOptimize(
/*mutateBatchSize=*/ AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1);
// Verify optimize() still not be triggered.
optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
assertThat(optimizeInfo.getOptimizableDocs())
.isEqualTo(AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1);
// Keep delete docs, will reach the interval this time and trigger optimize().
// Keep delete docs
for (int i = AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT;
i
< AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
@@ -444,6 +448,9 @@ public class AppSearchImplTest {
i++) {
mAppSearchImpl.remove("package", "database", "namespace", "uri" + i);
}
// updates the check for optimize counter, will reach both CHECK_OPTIMIZE_INTERVAL and
// OPTIMIZE_THRESHOLD_DOC_COUNT this time and trigger a optimize().
mAppSearchImpl.checkForOptimize(/*mutateBatchSize*/ AppSearchImpl.CHECK_OPTIMIZE_INTERVAL);
// Verify optimize() is triggered
optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
@@ -783,7 +790,7 @@ public class AppSearchImplTest {
Collections.singletonList(new AppSearchSchema.Builder("Email").build());
// set email incompatible and delete text
SetSchemaResult setSchemaResult =
SetSchemaResponse setSchemaResponse =
mAppSearchImpl.setSchema(
"package",
"database1",
@@ -791,9 +798,8 @@ public class AppSearchImplTest {
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ true);
assertThat(setSchemaResult.getDeletedSchemaTypes()).containsExactly("Text");
assertThat(setSchemaResult.getIncompatibleSchemaTypes()).containsExactly("Email");
assertThat(setSchemaResult.getResultCode()).isEqualTo(RESULT_OK);
assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Text");
assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("Email");
}
@Test
@@ -836,7 +842,7 @@ public class AppSearchImplTest {
final List<AppSearchSchema> finalSchemas =
Collections.singletonList(new AppSearchSchema.Builder("Email").build());
SetSchemaResult setSchemaResult =
SetSchemaResponse setSchemaResponse =
mAppSearchImpl.setSchema(
"package",
"database1",
@@ -846,7 +852,7 @@ public class AppSearchImplTest {
/*forceOverride=*/ false);
// Check the Document type has been deleted.
assertThat(setSchemaResult.getDeletedSchemaTypes()).containsExactly("Document");
assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Document");
// ForceOverride to delete.
mAppSearchImpl.setSchema(
@@ -1046,4 +1052,136 @@ public class AppSearchImplTest {
strippedDocumentProto.build()));
}
}
@Test
public void testThrowsExceptionIfClosed() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
AppSearchImpl appSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
context,
VisibilityStore.NO_OP_USER_ID,
/*globalQuerierPackage=*/ "");
// Initial check that we could do something at first.
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
appSearchImpl.setSchema(
"package",
"database",
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
appSearchImpl.close();
// Check all our public APIs
expectThrows(
IllegalStateException.class,
() -> {
appSearchImpl.setSchema(
"package",
"database",
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
});
expectThrows(
IllegalStateException.class,
() -> {
appSearchImpl.getSchema("package", "database");
});
expectThrows(
IllegalStateException.class,
() -> {
appSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("uri", "type")
.setNamespace("namespace")
.build());
});
expectThrows(
IllegalStateException.class,
() -> {
appSearchImpl.getDocument(
"package", "database", "namespace", "uri", Collections.emptyMap());
});
expectThrows(
IllegalStateException.class,
() -> {
appSearchImpl.query(
"package",
"database",
"query",
new SearchSpec.Builder()
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.build());
});
expectThrows(
IllegalStateException.class,
() -> {
appSearchImpl.globalQuery(
"query",
new SearchSpec.Builder()
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.build(),
"package",
/*callerUid=*/ 1);
});
expectThrows(
IllegalStateException.class,
() -> {
appSearchImpl.getNextPage(/*nextPageToken=*/ 1L);
});
expectThrows(
IllegalStateException.class,
() -> {
appSearchImpl.invalidateNextPageToken(/*nextPageToken=*/ 1L);
});
expectThrows(
IllegalStateException.class,
() -> {
appSearchImpl.reportUsage(
"package",
"database",
"namespace",
"uri",
/*usageTimestampMillis=*/ 1000L);
});
expectThrows(
IllegalStateException.class,
() -> {
appSearchImpl.remove("package", "database", "namespace", "uri");
});
expectThrows(
IllegalStateException.class,
() -> {
appSearchImpl.removeByQuery(
"package",
"database",
"query",
new SearchSpec.Builder()
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.build());
});
expectThrows(
IllegalStateException.class,
() -> {
appSearchImpl.persistToDisk();
});
}
}