summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Terry Wang <tytytyww@google.com> 2021-01-22 10:51:50 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2021-01-22 10:51:50 +0000
commit41260e11731788ba5ff57274d050377210cbe935 (patch)
treee171af6ea38170c23f39675d8112cc5a1ef5ab92
parentab65d7cdeb83718f666d7e36b3c927517edaee94 (diff)
parentcb362c021f29815c2bb965646729df0a2ed3ab54 (diff)
Merge "Sync AppSearch from framework."
-rw-r--r--apex/appsearch/framework/api/current.txt32
-rw-r--r--apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java15
-rw-r--r--apex/appsearch/framework/java/external/android/app/appsearch/AppSearchMigrationHelper.java65
-rw-r--r--apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java50
-rw-r--r--apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java28
-rw-r--r--apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java258
-rw-r--r--apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResult.java125
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java48
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/ResultCodeToProtoConverter.java62
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResultToProtoConverter.java64
-rw-r--r--apex/appsearch/synced_jetpack_changeid.txt2
-rw-r--r--apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java15
-rw-r--r--apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java55
-rw-r--r--apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java78
15 files changed, 840 insertions, 67 deletions
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt
index ebb00707354b..b1394c1441f0 100644
--- a/apex/appsearch/framework/api/current.txt
+++ b/apex/appsearch/framework/api/current.txt
@@ -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 {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index c4c123c3df0c..670f8b9c0236 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -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;
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchMigrationHelper.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchMigrationHelper.java
new file mode 100644
index 000000000000..37943fc24ded
--- /dev/null
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchMigrationHelper.java
@@ -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;
+ }
+}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
index 7f5d2020a6f4..2e00ff2320dc 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
@@ -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 {}
+ }
}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
index e9c4cb4e9e34..1486df3a6c29 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
@@ -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;
@@ -197,6 +207,23 @@ public final class SetSchemaRequest {
}
/**
+ * 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);
}
}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
new file mode 100644
index 000000000000..90a6f60188a6
--- /dev/null
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
@@ -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;
+ }
+ }
+}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResult.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResult.java
new file mode 100644
index 000000000000..f04ace684839
--- /dev/null
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResult.java
@@ -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);
+ }
+ }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
index 651ccd9fd3b8..592b8b97db60 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
@@ -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());
}
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/ResultCodeToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/ResultCodeToProtoConverter.java
new file mode 100644
index 000000000000..e340de0a5802
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/ResultCodeToProtoConverter.java
@@ -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;
+ }
+ }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResultToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResultToProtoConverter.java
new file mode 100644
index 000000000000..e1e7d46d77ea
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResultToProtoConverter.java
@@ -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();
+ }
+}
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index 51aeb89a33fd..277418164210 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-I03df55376689c1557c651d5b9671c40da1c35955
+I3fd4c96bf775c2539d744c416cdbf1d3c9544f03
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
index c2c1d7c6c129..f8d0d8052024 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
@@ -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()) {
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
index ff91f594e567..e8ea6ef7fe87 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
@@ -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}.
@@ -227,6 +255,17 @@ public interface AppSearchSessionShim extends Closeable {
@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.
*/
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
index 13858a3252d2..20fb90986f41 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
@@ -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 =
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
index c6fb37dcad0c..8d744c40e914 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
@@ -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());
}
@@ -746,6 +749,54 @@ public class AppSearchImplTest {
}
@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 =
mAppSearchImpl.getSchemaProtoLocked().getTypesList();
@@ -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(