Sync AppSearch from framework.

Included changes:
* 9d1cf52: Supports schema migration in AppSearch
* 1c55f12: Change AppSearch setSchema() to return a SetSchemaRsponse.
* 9d94171: Unhide AppSearchMigrationHelper, Transformer and
           SetSchemaResponse.
* f07132f: add maybeFlush to AppSearchSession.

Bug: 169883602
Bug: 173742929
Test: Presubmit

Change-Id: I9e70ed04014ae3d134defb93c5cb02a5def0c71e
This commit is contained in:
Terry Wang
2021-01-20 15:57:31 -08:00
parent a8786df7e1
commit cb362c021f
15 changed files with 840 additions and 67 deletions

View File

@@ -22,6 +22,14 @@ package android.app.appsearch {
method @NonNull public android.app.appsearch.AppSearchManager.SearchContext.Builder setDatabaseName(@NonNull String);
}
public interface AppSearchMigrationHelper {
method public void queryAndTransform(@NonNull String, @NonNull android.app.appsearch.AppSearchMigrationHelper.Transformer) throws java.lang.Exception;
}
public static interface AppSearchMigrationHelper.Transformer {
method @NonNull public android.app.appsearch.GenericDocument transform(int, int, @NonNull android.app.appsearch.GenericDocument) throws java.lang.Exception;
}
public final class AppSearchResult<ValueType> {
method @Nullable public String getErrorMessage();
method public int getResultCode();
@@ -99,6 +107,11 @@ package android.app.appsearch {
method @NonNull public android.app.appsearch.AppSearchSchema.Int64PropertyConfig.Builder setCardinality(int);
}
public static interface AppSearchSchema.Migrator {
method public default void onDowngrade(int, int, @NonNull android.app.appsearch.AppSearchMigrationHelper) throws java.lang.Exception;
method public default void onUpgrade(int, int, @NonNull android.app.appsearch.AppSearchMigrationHelper) throws java.lang.Exception;
}
public abstract static class AppSearchSchema.PropertyConfig {
method public int getCardinality();
method public int getDataType();
@@ -135,7 +148,7 @@ package android.app.appsearch {
method public void removeByQuery(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>);
method public void removeByUri(@NonNull android.app.appsearch.RemoveByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>);
method @NonNull public void reportUsage(@NonNull android.app.appsearch.ReportUsageRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>);
method public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>);
method public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>);
}
public interface BatchResultCallback<KeyType, ValueType> {
@@ -322,6 +335,7 @@ package android.app.appsearch {
}
public final class SetSchemaRequest {
method @NonNull public java.util.Map<java.lang.String,android.app.appsearch.AppSearchSchema.Migrator> getMigrators();
method @NonNull public java.util.Set<android.app.appsearch.AppSearchSchema> getSchemas();
method @NonNull public java.util.Set<java.lang.String> getSchemasNotVisibleToSystemUi();
method @NonNull public java.util.Map<java.lang.String,java.util.Set<android.app.appsearch.PackageIdentifier>> getSchemasVisibleToPackages();
@@ -334,10 +348,26 @@ package android.app.appsearch {
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchema(@NonNull java.util.Collection<android.app.appsearch.AppSearchSchema>);
method @NonNull public android.app.appsearch.SetSchemaRequest build();
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setForceOverride(boolean);
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setMigrator(@NonNull String, @NonNull android.app.appsearch.AppSearchSchema.Migrator);
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeVisibilityForPackage(@NonNull String, boolean, @NonNull android.app.appsearch.PackageIdentifier);
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeVisibilityForSystemUi(@NonNull String, boolean);
}
public class SetSchemaResponse {
method @NonNull public java.util.Set<java.lang.String> getDeletedTypes();
method @NonNull public java.util.Set<java.lang.String> getIncompatibleTypes();
method @NonNull public java.util.Set<java.lang.String> getMigratedTypes();
method @NonNull public java.util.List<android.app.appsearch.SetSchemaResponse.MigrationFailure> getMigrationFailures();
method public boolean isSuccess();
}
public static class SetSchemaResponse.MigrationFailure {
method @NonNull public android.app.appsearch.AppSearchResult<java.lang.Void> getAppSearchResult();
method @NonNull public String getNamespace();
method @NonNull public String getSchemaType();
method @NonNull public String getUri();
}
}
package android.app.appsearch.exceptions {

View File

@@ -162,7 +162,7 @@ public final class AppSearchSession implements Closeable {
public void setSchema(
@NonNull SetSchemaRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<Void>> callback) {
@NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) {
Objects.requireNonNull(request);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
@@ -192,7 +192,18 @@ public final class AppSearchSession implements Closeable {
mUserId,
new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
executor.execute(() -> callback.accept(result));
executor.execute(() -> {
if (result.isSuccess()) {
callback.accept(
// TODO(b/151178558) implement Migration in platform.
AppSearchResult.newSuccessfulResult(
new SetSchemaResponse.Builder().setResultCode(
result.getResultCode())
.build()));
} else {
callback.accept(result);
}
});
}
});
mIsMutated = true;

View File

@@ -0,0 +1,65 @@
/*
* 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.annotation.SuppressLint;
/**
* The helper class for {@link AppSearchSchema} migration.
*
* <p>It will query and migrate {@link GenericDocument} in given type to a new version.
*/
public interface AppSearchMigrationHelper {
/**
* Queries all documents that need to be migrated to the different version, and transform
* documents to that version by passing them to the provided {@link Transformer}.
*
* @param schemaType The schema that need be updated and migrated {@link GenericDocument} under
* this type.
* @param transformer The {@link Transformer} that will upgrade or downgrade a {@link
* GenericDocument} to new version.
* @see Transformer#transform
*/
// Rethrow the Generic Exception thrown from the Transformer.
@SuppressLint("GenericException")
void queryAndTransform(@NonNull String schemaType, @NonNull Transformer transformer)
throws Exception;
/** The class to migrate {@link GenericDocument} between different version. */
interface Transformer {
/**
* Translates a {@link GenericDocument} from a version to a different version.
*
* <p>If the uri, schema type or namespace is changed via the transform, it will apply to
* the new {@link GenericDocument}.
*
* @param currentVersion The current version of the document's schema.
* @param finalVersion The final version that documents need to be migrated to.
* @param document The {@link GenericDocument} need to be translated to new version.
* @return A {@link GenericDocument} in new version.
*/
@NonNull
// This method will be overridden by users, allow them to throw any customer Exceptions.
@SuppressLint("GenericException")
GenericDocument transform(
int currentVersion, int finalVersion, @NonNull GenericDocument document)
throws Exception;
}
}

View File

@@ -164,10 +164,10 @@ public final class AppSearchSchema {
* a {@link AppSearchSchema} type at a time.
*
* <p>Setting a version number that is different from the version number of the schema
* currently stored in AppSearch will result in AppSearch calling the Migrator provided to
* {@link AppSearchSession#setSchema} to migrate the documents already in AppSearch from the
* previous version to the one set in this request. The version number can be updated
* without any other changes to the schema.
* currently stored in AppSearch will result in AppSearch calling the {@link Migrator}
* provided to {@link AppSearchSession#setSchema} to migrate the documents already in
* AppSearch from the previous version to the one set in this request. The version number
* can be updated without any other changes to the schema.
*
* <p>The version number can stay the same, increase, or decrease relative to the current
* version number of the {@link AppSearchSchema} type that is already stored in the {@link
@@ -182,8 +182,9 @@ public final class AppSearchSchema {
* @throws IllegalStateException if the version is negative or the builder has already been
* used.
* @see AppSearchSession#setSchema
* @see AppSearchSchema.Migrator
* @see SetSchemaRequest.Builder#setMigrator
*/
// TODO(b/177266929) link to Migrator in once it's ready.
@NonNull
public AppSearchSchema.Builder setVersion(@IntRange(from = 0) int version) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
@@ -859,4 +860,43 @@ public final class AppSearchSchema {
}
}
}
/**
* A migrator class to translate {@link GenericDocument} from different version of {@link
* AppSearchSchema}
*/
public interface Migrator {
/**
* Migrates {@link GenericDocument} to a newer version of {@link AppSearchSchema}.
*
* <p>This methods will be invoked only if the {@link SetSchemaRequest} is setting a higher
* version number than the current {@link AppSearchSchema} saved in AppSearch.
*
* @param currentVersion The current version of the document's schema.
* @param targetVersion The final version that documents need to be migrated to.
* @param helper The helper class could help to query all documents need to be migrated.
*/
// This method will be overridden by users, allow them to throw any customer Exceptions.
@SuppressLint("GenericException")
default void onUpgrade(
int currentVersion, int targetVersion, @NonNull AppSearchMigrationHelper helper)
throws Exception {}
/**
* Migrates {@link GenericDocument} to an older version of {@link AppSearchSchema}.
*
* <p>The methods will be invoked only if the {@link SetSchemaRequest} is setting a higher
* version number than the current {@link AppSearchSchema} saved in AppSearch.
*
* @param currentVersion The current version of the document's schema.
* @param targetVersion The final version that documents need to be migrated to.
* @param helper The helper class could help to query all documents need to be migrated.
*/
// This method will be overridden by users, allow them to throw any customer Exceptions.
@SuppressLint("GenericException")
default void onDowngrade(
int currentVersion, int targetVersion, @NonNull AppSearchMigrationHelper helper)
throws Exception {}
}
}

View File

@@ -38,16 +38,19 @@ public final class SetSchemaRequest {
private final Set<AppSearchSchema> mSchemas;
private final Set<String> mSchemasNotVisibleToSystemUi;
private final Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages;
private final Map<String, AppSearchSchema.Migrator> mMigrators;
private final boolean mForceOverride;
SetSchemaRequest(
@NonNull Set<AppSearchSchema> schemas,
@NonNull Set<String> schemasNotVisibleToSystemUi,
@NonNull Map<String, Set<PackageIdentifier>> schemasVisibleToPackages,
@NonNull Map<String, AppSearchSchema.Migrator> migrators,
boolean forceOverride) {
mSchemas = Preconditions.checkNotNull(schemas);
mSchemasNotVisibleToSystemUi = Preconditions.checkNotNull(schemasNotVisibleToSystemUi);
mSchemasVisibleToPackages = Preconditions.checkNotNull(schemasVisibleToPackages);
mMigrators = Preconditions.checkNotNull(migrators);
mForceOverride = forceOverride;
}
@@ -81,6 +84,12 @@ public final class SetSchemaRequest {
return copy;
}
/** Returns the map of {@link android.app.appsearch.AppSearchSchema.Migrator}. */
@NonNull
public Map<String, AppSearchSchema.Migrator> getMigrators() {
return Collections.unmodifiableMap(mMigrators);
}
/**
* Returns a mapping of schema types to the set of packages that have access to that schema
* type. Each package is represented by a {@link PackageIdentifier}. name and byte[]
@@ -107,6 +116,7 @@ public final class SetSchemaRequest {
private final Set<String> mSchemasNotVisibleToSystemUi = new ArraySet<>();
private final Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages =
new ArrayMap<>();
private final Map<String, AppSearchSchema.Migrator> mMigrators = new ArrayMap<>();
private boolean mForceOverride = false;
private boolean mBuilt = false;
@@ -196,6 +206,23 @@ public final class SetSchemaRequest {
return this;
}
/**
* Sets the {@link android.app.appsearch.AppSearchSchema.Migrator}.
*
* @param schemaType The schema type to set migrator on.
* @param migrator The migrator translate a document from it's old version to a new
* incompatible version.
*/
@NonNull
@SuppressLint("MissingGetterMatchingBuilder") // Getter return plural objects.
public Builder setMigrator(
@NonNull String schemaType, @NonNull AppSearchSchema.Migrator migrator) {
Preconditions.checkNotNull(schemaType);
Preconditions.checkNotNull(migrator);
mMigrators.put(schemaType, migrator);
return this;
}
/**
* Configures the {@link SetSchemaRequest} to delete any existing documents that don't
* follow the new schema.
@@ -241,6 +268,7 @@ public final class SetSchemaRequest {
mSchemas,
mSchemasNotVisibleToSystemUi,
mSchemasVisibleToPackages,
mMigrators,
mForceOverride);
}
}

View File

@@ -0,0 +1,258 @@
/*
* 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 static android.app.appsearch.AppSearchResult.RESULT_OK;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.ArraySet;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
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) {
mMigrationFailures = Preconditions.checkNotNull(migrationFailures);
mDeletedTypes = Preconditions.checkNotNull(deletedTypes);
mMigratedTypes = Preconditions.checkNotNull(migratedTypes);
mIncompatibleTypes = Preconditions.checkNotNull(incompatibleTypes);
mResultCode = resultCode;
}
/**
* Returns a {@link List} of all failed {@link MigrationFailure}.
*
* <p>A {@link MigrationFailure} will be generated if the system trying to save a post-migrated
* {@link GenericDocument} but fail.
*
* <p>{@link MigrationFailure} contains the uri, namespace and schemaType of the post-migrated
* {@link GenericDocument} and the error reason. Mostly it will be mismatch the schema it
* migrated to.
*/
@NonNull
public List<MigrationFailure> getMigrationFailures() {
return Collections.unmodifiableList(mMigrationFailures);
}
/**
* Returns a {@link Set} of schema type that were deleted by the {@link
* AppSearchSession#setSchema} call.
*/
@NonNull
public Set<String> getDeletedTypes() {
return Collections.unmodifiableSet(mDeletedTypes);
}
/**
* Returns a {@link Set} of schema type that were migrated by the {@link
* AppSearchSession#setSchema} call.
*/
@NonNull
public Set<String> getMigratedTypes() {
return Collections.unmodifiableSet(mMigratedTypes);
}
/**
* Returns a {@link Set} of schema type whose new definitions set in the {@link
* AppSearchSession#setSchema} call were incompatible with the pre-existing schema.
*
* <p>If a {@link android.app.appsearch.AppSearchSchema.Migrator} is provided for this type and
* the migration is success triggered. The type will also appear in {@link #getMigratedTypes()}.
*
* @see AppSearchSession#setSchema
* @see SetSchemaRequest.Builder#setForceOverride
*/
@NonNull
public Set<String> getIncompatibleTypes() {
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
@NonNull
public String toString() {
return "{\n Does setSchema success? : "
+ isSuccess()
+ "\n failures: "
+ mMigrationFailures
+ "\n}";
}
/**
* Builder for {@link SetSchemaResponse} objects.
*
* @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 boolean mBuilt = false;
/** Adds a {@link MigrationFailure}. */
@NonNull
public Builder setFailure(
@NonNull String schemaType,
@NonNull String namespace,
@NonNull String uri,
@NonNull AppSearchResult<Void> failureResult) {
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));
return this;
}
/** Adds a {@link MigrationFailure}. */
@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)));
return this;
}
/** Adds deletedTypes to the list of deleted schema types. */
@NonNull
public Builder addDeletedType(@NonNull Collection<String> deletedTypes) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mDeletedTypes.addAll(Preconditions.checkNotNull(deletedTypes));
return this;
}
/** Adds incompatibleTypes to the list of incompatible schema types. */
@NonNull
public Builder addIncompatibleType(@NonNull Collection<String> incompatibleTypes) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mIncompatibleTypes.addAll(Preconditions.checkNotNull(incompatibleTypes));
return this;
}
/** Adds migratedTypes to the list of migrated schema types. */
@NonNull
public Builder addMigratedType(@NonNull String migratedType) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mMigratedTypes.add(Preconditions.checkNotNull(migratedType));
return this;
}
/** Sets the {@link AppSearchResult.ResultCode} of the response. */
@NonNull
public Builder setResultCode(@AppSearchResult.ResultCode int resultCode) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mResultCode = resultCode;
return this;
}
/** Builds a {@link SetSchemaResponse} object. */
@NonNull
public SetSchemaResponse build() {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mBuilt = true;
return new SetSchemaResponse(
mMigrationFailures,
mDeletedTypes,
mMigratedTypes,
mIncompatibleTypes,
mResultCode);
}
}
/**
* The class represents a post-migrated {@link GenericDocument} that failed to be saved by
* {@link AppSearchSession#setSchema}.
*/
public static class MigrationFailure {
private final String mSchemaType;
private final String mNamespace;
private final String mUri;
AppSearchResult<Void> mFailureResult;
MigrationFailure(
@NonNull String schemaType,
@NonNull String namespace,
@NonNull String uri,
@NonNull AppSearchResult<Void> result) {
mSchemaType = schemaType;
mNamespace = namespace;
mUri = uri;
mFailureResult = result;
}
/** Returns the schema type of the {@link GenericDocument} that fails to be migrated. */
@NonNull
public String getSchemaType() {
return mSchemaType;
}
/** Returns the namespace of the {@link GenericDocument} that fails to be migrated. */
@NonNull
public String getNamespace() {
return mNamespace;
}
/** Returns the uri of the {@link GenericDocument} that fails to be migrated. */
@NonNull
public String getUri() {
return mUri;
}
/**
* Returns the {@link AppSearchResult} that indicates why the post-migrated {@link
* GenericDocument} fails to be saved.
*/
@NonNull
public AppSearchResult<Void> getAppSearchResult() {
return mFailureResult;
}
}
}

View File

@@ -0,0 +1,125 @@
/*
* 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,6 +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.exceptions.AppSearchException;
import android.content.Context;
import android.os.Bundle;
@@ -36,9 +37,11 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.server.appsearch.external.localstorage.converter.GenericDocumentToProtoConverter;
import com.android.server.appsearch.external.localstorage.converter.ResultCodeToProtoConverter;
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.TypePropertyPathToProtoConverter;
import com.google.android.icing.IcingSearchEngine;
@@ -259,7 +262,8 @@ public final class AppSearchImpl {
* which do not comply with the new schema will be deleted.
* @throws AppSearchException on IcingSearchEngine error.
*/
public void setSchema(
@NonNull
public SetSchemaResult setSchema(
@NonNull String packageName,
@NonNull String databaseName,
@NonNull List<AppSearchSchema> schemas,
@@ -273,8 +277,9 @@ public final class AppSearchImpl {
SchemaProto.Builder newSchemaBuilder = SchemaProto.newBuilder();
for (int i = 0; i < schemas.size(); i++) {
AppSearchSchema schema = schemas.get(i);
SchemaTypeConfigProto schemaTypeProto =
SchemaToProtoConverter.toSchemaTypeConfigProto(schemas.get(i));
SchemaToProtoConverter.toSchemaTypeConfigProto(schema);
newSchemaBuilder.addTypes(schemaTypeProto);
}
@@ -293,16 +298,10 @@ public final class AppSearchImpl {
try {
checkSuccess(setSchemaResultProto.getStatus());
} catch (AppSearchException e) {
// Improve the error message by merging in information about incompatible types.
if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0
|| setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0) {
String newMessage =
e.getMessage()
+ "\n Deleted types: "
+ setSchemaResultProto.getDeletedSchemaTypesList()
+ "\n Incompatible types: "
+ setSchemaResultProto.getIncompatibleSchemaTypesList();
throw new AppSearchException(e.getResultCode(), newMessage, e.getCause());
return SetSchemaResultToProtoConverter.toSetSchemaResult(
setSchemaResultProto, prefix);
} else {
throw e;
}
@@ -339,6 +338,7 @@ public final class AppSearchImpl {
// incompatible schemas.
checkForOptimizeLocked(/* force= */ true);
}
return SetSchemaResultToProtoConverter.toSetSchemaResult(setSchemaResultProto, prefix);
} finally {
mReadWriteLock.writeLock().unlock();
}
@@ -796,6 +796,8 @@ public final class AppSearchImpl {
* <p>If the app crashes before a call to PersistToDisk(), Icing would trigger a costly recovery
* process in next initialization. After that, Icing would still be able to recover all written
* data.
*
* @throws AppSearchException on any error that AppSearch persist data to disk.
*/
public void persistToDisk() throws AppSearchException {
PersistToDiskResultProto persistToDiskResultProto =
@@ -1374,28 +1376,8 @@ public final class AppSearchImpl {
* @return AppSearchException with the parallel error code.
*/
private static AppSearchException statusProtoToAppSearchException(StatusProto statusProto) {
switch (statusProto.getCode()) {
case INVALID_ARGUMENT:
return new AppSearchException(
AppSearchResult.RESULT_INVALID_ARGUMENT, statusProto.getMessage());
case NOT_FOUND:
return new AppSearchException(
AppSearchResult.RESULT_NOT_FOUND, statusProto.getMessage());
case FAILED_PRECONDITION:
// Fallthrough
case ABORTED:
// Fallthrough
case INTERNAL:
return new AppSearchException(
AppSearchResult.RESULT_INTERNAL_ERROR, statusProto.getMessage());
case OUT_OF_SPACE:
return new AppSearchException(
AppSearchResult.RESULT_OUT_OF_SPACE, statusProto.getMessage());
default:
// Some unknown/unsupported error
return new AppSearchException(
AppSearchResult.RESULT_UNKNOWN_ERROR,
"Unknown IcingSearchEngine status code: " + statusProto.getCode());
}
return new AppSearchException(
ResultCodeToProtoConverter.toResultCode(statusProto.getCode()),
statusProto.getMessage());
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.appsearch.external.localstorage.converter;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchResult;
import android.util.Log;
import com.google.android.icing.proto.StatusProto;
/**
* Translates an {@link StatusProto.Code} into a {@link AppSearchResult.ResultCode}
*
* @hide
*/
public final class ResultCodeToProtoConverter {
private static final String TAG = "AppSearchResultCodeToPr";
private ResultCodeToProtoConverter() {}
/** Converts an {@link StatusProto.Code} into a {@link AppSearchResult.ResultCode}. */
public static @AppSearchResult.ResultCode int toResultCode(
@NonNull StatusProto.Code statusCode) {
switch (statusCode) {
case OK:
return AppSearchResult.RESULT_OK;
case OUT_OF_SPACE:
return AppSearchResult.RESULT_OUT_OF_SPACE;
case INTERNAL:
return AppSearchResult.RESULT_INTERNAL_ERROR;
case UNKNOWN:
return AppSearchResult.RESULT_UNKNOWN_ERROR;
case NOT_FOUND:
return AppSearchResult.RESULT_NOT_FOUND;
case INVALID_ARGUMENT:
return AppSearchResult.RESULT_INVALID_ARGUMENT;
default:
// Some unknown/unsupported error
Log.e(
TAG,
"Cannot convert IcingSearchEngine status code: "
+ statusCode
+ " to AppSearchResultCode.");
return AppSearchResult.RESULT_INTERNAL_ERROR;
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.appsearch.external.localstorage.converter;
import android.annotation.NonNull;
import android.app.appsearch.SetSchemaResult;
import com.android.internal.util.Preconditions;
import com.google.android.icing.proto.SetSchemaResultProto;
/**
* Translates a {@link SetSchemaResultProto} into {@link SetSchemaResult}.
*
* @hide
*/
public class SetSchemaResultToProtoConverter {
private SetSchemaResultToProtoConverter() {}
/**
* Translate a {@link SetSchemaResultProto} into {@link SetSchemaResult}.
*
* @param proto The {@link SetSchemaResultProto} containing results.
* @param prefix The prefix need to removed from schemaTypes
* @return {@link SetSchemaResult} of results.
*/
@NonNull
public static SetSchemaResult toSetSchemaResult(
@NonNull SetSchemaResultProto proto, @NonNull String prefix) {
Preconditions.checkNotNull(proto);
Preconditions.checkNotNull(prefix);
SetSchemaResult.Builder builder =
new SetSchemaResult.Builder()
.setResultCode(
ResultCodeToProtoConverter.toResultCode(
proto.getStatus().getCode()));
for (int i = 0; i < proto.getDeletedSchemaTypesCount(); i++) {
builder.addDeletedSchemaType(proto.getDeletedSchemaTypes(i).substring(prefix.length()));
}
for (int i = 0; i < proto.getIncompatibleSchemaTypesCount(); i++) {
builder.addIncompatibleSchemaType(
proto.getIncompatibleSchemaTypes(i).substring(prefix.length()));
}
return builder.build();
}
}

View File

@@ -1 +1 @@
I03df55376689c1557c651d5b9671c40da1c35955
I3fd4c96bf775c2539d744c416cdbf1d3c9544f03

View File

@@ -33,6 +33,7 @@ import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchResultsShim;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
import android.app.appsearch.SetSchemaResponse;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
@@ -85,8 +86,8 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim {
@Override
@NonNull
public ListenableFuture<Void> setSchema(@NonNull SetSchemaRequest request) {
SettableFuture<AppSearchResult<Void>> future = SettableFuture.create();
public ListenableFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest request) {
SettableFuture<AppSearchResult<SetSchemaResponse>> future = SettableFuture.create();
mAppSearchSession.setSchema(request, mExecutor, future::set);
return Futures.transformAsync(future, this::transformResult, mExecutor);
}
@@ -159,6 +160,16 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim {
mAppSearchSession.close();
}
@Override
@NonNull
public ListenableFuture<Void> maybeFlush() {
SettableFuture<AppSearchResult<Void>> future = SettableFuture.create();
// The data in platform will be flushed by scheduled task. AppSearchSession won't do
// anything extra flush.
future.set(AppSearchResult.newSuccessfulResult(null));
return Futures.transformAsync(future, this::transformResult, mExecutor);
}
private <T> ListenableFuture<T> transformResult(
@NonNull AppSearchResult<T> result) throws AppSearchException {
if (!result.isSuccess()) {

View File

@@ -69,12 +69,19 @@ public interface AppSearchSessionShim extends Closeable {
* {@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 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.
* <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.
* <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.
* </ul>
*
* <p>It is a no-op to set the same schema as has been previously set; this is handled
* efficiently.
@@ -85,13 +92,34 @@ public interface AppSearchSessionShim extends Closeable {
* Visibility settings for a schema type do not apply or persist across {@link
* SetSchemaRequest}s.
*
* <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.
* @return The pending result of performing this operation.
* @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.
* @see android.app.appsearch.AppSearchSchema.Migrator
* @see android.app.appsearch.AppSearchMigrationHelper.Transformer
*/
// TODO(b/169883602): Change @code references to @link when setPlatformSurfaceable APIs are
// exposed.
@NonNull
ListenableFuture<Void> setSchema(@NonNull SetSchemaRequest request);
ListenableFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest request);
/**
* Retrieves the schema most recently successfully provided to {@link #setSchema}.
@@ -226,6 +254,17 @@ public interface AppSearchSessionShim extends Closeable {
ListenableFuture<Void> removeByQuery(
@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
/**
* Flush all schema and document updates, additions, and deletes to disk if possible.
*
* @return The pending result of performing this operation. {@link
* android.app.appsearch.exceptions.AppSearchException} with {@link
* AppSearchResult#RESULT_INTERNAL_ERROR} will be set to the future if we hit error when
* save to disk.
*/
@NonNull
ListenableFuture<Void> maybeFlush();
/**
* Closes the {@link AppSearchSessionShim} to persist all schema and document updates,
* additions, and deletes to disk.

View File

@@ -25,6 +25,7 @@ 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;
@@ -42,6 +43,15 @@ 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

@@ -16,6 +16,8 @@
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;
@@ -25,6 +27,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.exceptions.AppSearchException;
import android.content.Context;
import android.util.ArraySet;
@@ -68,7 +71,7 @@ public class AppSearchImplTest {
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
context,
/*userId=*/-1,
VisibilityStore.NO_OP_USER_ID,
/*globalQuerierPackage
=*/ context.getPackageName());
}
@@ -745,6 +748,54 @@ public class AppSearchImplTest {
.containsExactlyElementsIn(expectedTypes);
}
@Test
public void testSetSchema_incompatible() throws Exception {
List<SchemaTypeConfigProto> existingSchemas =
mAppSearchImpl.getSchemaProtoLocked().getTypesList();
List<AppSearchSchema> oldSchemas = new ArrayList<>();
oldSchemas.add(
new AppSearchSchema.Builder("Email")
.addProperty(
new AppSearchSchema.StringPropertyConfig.Builder("foo")
.setCardinality(
AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
.setTokenizerType(
AppSearchSchema.StringPropertyConfig
.TOKENIZER_TYPE_PLAIN)
.setIndexingType(
AppSearchSchema.StringPropertyConfig
.INDEXING_TYPE_PREFIXES)
.build())
.build());
oldSchemas.add(new AppSearchSchema.Builder("Text").build());
// Set schema Email to AppSearch database1
mAppSearchImpl.setSchema(
"package",
"database1",
oldSchemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
// Create incompatible schema
List<AppSearchSchema> newSchemas =
Collections.singletonList(new AppSearchSchema.Builder("Email").build());
// set email incompatible and delete text
SetSchemaResult setSchemaResult =
mAppSearchImpl.setSchema(
"package",
"database1",
newSchemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ true);
assertThat(setSchemaResult.getDeletedSchemaTypes()).containsExactly("Text");
assertThat(setSchemaResult.getIncompatibleSchemaTypes()).containsExactly("Email");
assertThat(setSchemaResult.getResultCode()).isEqualTo(RESULT_OK);
}
@Test
public void testRemoveSchema() throws Exception {
List<SchemaTypeConfigProto> existingSchemas =
@@ -785,20 +836,17 @@ public class AppSearchImplTest {
final List<AppSearchSchema> finalSchemas =
Collections.singletonList(new AppSearchSchema.Builder("Email").build());
// Check the incompatible error has been thrown.
AppSearchException e =
expectThrows(
AppSearchException.class,
() ->
mAppSearchImpl.setSchema(
"package",
"database1",
finalSchemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false));
assertThat(e).hasMessageThat().contains("Schema is incompatible");
assertThat(e).hasMessageThat().contains("Deleted types: [package$database1/Document]");
SetSchemaResult setSchemaResult =
mAppSearchImpl.setSchema(
"package",
"database1",
finalSchemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
// Check the Document type has been deleted.
assertThat(setSchemaResult.getDeletedSchemaTypes()).containsExactly("Document");
// ForceOverride to delete.
mAppSearchImpl.setSchema(