diff options
| author | 2021-01-22 10:51:50 +0000 | |
|---|---|---|
| committer | 2021-01-22 10:51:50 +0000 | |
| commit | 41260e11731788ba5ff57274d050377210cbe935 (patch) | |
| tree | e171af6ea38170c23f39675d8112cc5a1ef5ab92 | |
| parent | ab65d7cdeb83718f666d7e36b3c927517edaee94 (diff) | |
| parent | cb362c021f29815c2bb965646729df0a2ed3ab54 (diff) | |
Merge "Sync AppSearch from framework."
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( |