Merge "Update AppSearch code from Jetpack."
This commit is contained in:
committed by
Android (Google) Code Review
commit
2b8c1ecae4
@@ -224,7 +224,11 @@ public class AppSearchManager {
|
||||
}
|
||||
AndroidFuture<AppSearchResult> future = new AndroidFuture<>();
|
||||
try {
|
||||
mService.setSchema(DEFAULT_DATABASE_NAME, schemaBundles, request.isForceOverride(),
|
||||
mService.setSchema(
|
||||
DEFAULT_DATABASE_NAME,
|
||||
schemaBundles,
|
||||
new ArrayList<>(request.getSchemasNotPlatformSurfaceable()),
|
||||
request.isForceOverride(),
|
||||
new IAppSearchResultCallback.Stub() {
|
||||
public void onResult(AppSearchResult result) {
|
||||
future.complete(result);
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.appsearch.exceptions.IllegalSchemaException;
|
||||
import android.app.appsearch.util.BundleUtil;
|
||||
import android.os.Bundle;
|
||||
import android.util.ArraySet;
|
||||
|
||||
@@ -31,6 +32,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -40,7 +42,7 @@ import java.util.Set;
|
||||
*
|
||||
* <p>The schema consists of type information, properties, and config (like tokenization type).
|
||||
*
|
||||
* @see AppSearchManager#setSchema
|
||||
* @see AppSearchSession#setSchema
|
||||
*/
|
||||
public final class AppSearchSchema {
|
||||
private static final String SCHEMA_TYPE_FIELD = "schemaType";
|
||||
@@ -94,17 +96,37 @@ public final class AppSearchSchema {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof AppSearchSchema)) {
|
||||
return false;
|
||||
}
|
||||
AppSearchSchema otherSchema = (AppSearchSchema) other;
|
||||
if (!getSchemaType().equals(otherSchema.getSchemaType())) {
|
||||
return false;
|
||||
}
|
||||
return getProperties().equals(otherSchema.getProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getSchemaType(), getProperties());
|
||||
}
|
||||
|
||||
/** Builder for {@link AppSearchSchema objects}. */
|
||||
public static final class Builder {
|
||||
private final String mTypeName;
|
||||
private final String mSchemaType;
|
||||
private final ArrayList<Bundle> mPropertyBundles = new ArrayList<>();
|
||||
private final Set<String> mPropertyNames = new ArraySet<>();
|
||||
private boolean mBuilt = false;
|
||||
|
||||
/** Creates a new {@link AppSearchSchema.Builder}. */
|
||||
public Builder(@NonNull String typeName) {
|
||||
Preconditions.checkNotNull(typeName);
|
||||
mTypeName = typeName;
|
||||
public Builder(@NonNull String schemaType) {
|
||||
Preconditions.checkNotNull(schemaType);
|
||||
mSchemaType = schemaType;
|
||||
}
|
||||
|
||||
/** Adds a property to the given type. */
|
||||
@@ -133,7 +155,7 @@ public final class AppSearchSchema {
|
||||
public AppSearchSchema build() {
|
||||
Preconditions.checkState(!mBuilt, "Builder has already been used");
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(AppSearchSchema.SCHEMA_TYPE_FIELD, mTypeName);
|
||||
bundle.putString(AppSearchSchema.SCHEMA_TYPE_FIELD, mSchemaType);
|
||||
bundle.putParcelableArrayList(AppSearchSchema.PROPERTIES_FIELD, mPropertyBundles);
|
||||
mBuilt = true;
|
||||
return new AppSearchSchema(bundle);
|
||||
@@ -279,6 +301,8 @@ public final class AppSearchSchema {
|
||||
|
||||
final Bundle mBundle;
|
||||
|
||||
@Nullable private Integer mHashCode;
|
||||
|
||||
PropertyConfig(@NonNull Bundle bundle) {
|
||||
mBundle = Preconditions.checkNotNull(bundle);
|
||||
}
|
||||
@@ -327,6 +351,26 @@ public final class AppSearchSchema {
|
||||
return mBundle.getInt(TOKENIZER_TYPE_FIELD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof PropertyConfig)) {
|
||||
return false;
|
||||
}
|
||||
PropertyConfig otherProperty = (PropertyConfig) other;
|
||||
return BundleUtil.deepEquals(this.mBundle, otherProperty.mBundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (mHashCode == null) {
|
||||
mHashCode = BundleUtil.deepHashCode(mBundle);
|
||||
}
|
||||
return mHashCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link PropertyConfig}.
|
||||
*
|
||||
|
||||
@@ -140,7 +140,11 @@ public final class AppSearchSession {
|
||||
schemaBundles.add(schema.getBundle());
|
||||
}
|
||||
try {
|
||||
mService.setSchema(mDatabaseName, schemaBundles, request.isForceOverride(),
|
||||
mService.setSchema(
|
||||
mDatabaseName,
|
||||
schemaBundles,
|
||||
new ArrayList<>(request.getSchemasNotPlatformSurfaceable()),
|
||||
request.isForceOverride(),
|
||||
new IAppSearchResultCallback.Stub() {
|
||||
public void onResult(AppSearchResult result) {
|
||||
executor.execute(() -> callback.accept(result));
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.appsearch.exceptions.AppSearchException;
|
||||
import android.app.appsearch.util.BundleUtil;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -37,12 +38,12 @@ import java.util.Set;
|
||||
*
|
||||
* <p>Documents are constructed via {@link GenericDocument.Builder}.
|
||||
*
|
||||
* @see AppSearchManager#putDocuments
|
||||
* @see AppSearchManager#getByUri
|
||||
* @see AppSearchManager#query
|
||||
* @see AppSearchSession#putDocuments
|
||||
* @see AppSearchSession#getByUri
|
||||
* @see AppSearchSession#query
|
||||
*/
|
||||
public class GenericDocument {
|
||||
private static final String TAG = "GenericDocument";
|
||||
private static final String TAG = "AppSearchGenericDocumen";
|
||||
|
||||
/** The default empty namespace. */
|
||||
public static final String DEFAULT_NAMESPACE = "";
|
||||
@@ -462,144 +463,17 @@ public class GenericDocument {
|
||||
return false;
|
||||
}
|
||||
GenericDocument otherDocument = (GenericDocument) other;
|
||||
return bundleEquals(this.mBundle, otherDocument.mBundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deeply checks whether two bundles are equal.
|
||||
*
|
||||
* <p>Two bundles will be considered equal if they contain the same content.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static boolean bundleEquals(Bundle one, Bundle two) {
|
||||
if (one.size() != two.size()) {
|
||||
return false;
|
||||
}
|
||||
Set<String> keySetOne = one.keySet();
|
||||
Object valueOne;
|
||||
Object valueTwo;
|
||||
// Bundle inherit its equals() from Object.java, which only compare their memory address.
|
||||
// We should iterate all keys and check their presents and values in both bundle.
|
||||
for (String key : keySetOne) {
|
||||
valueOne = one.get(key);
|
||||
valueTwo = two.get(key);
|
||||
if (valueOne instanceof Bundle
|
||||
&& valueTwo instanceof Bundle
|
||||
&& !bundleEquals((Bundle) valueOne, (Bundle) valueTwo)) {
|
||||
return false;
|
||||
} else if (valueOne == null && (valueTwo != null || !two.containsKey(key))) {
|
||||
// If we call bundle.get(key) when the 'key' doesn't actually exist in the
|
||||
// bundle, we'll get back a null. So make sure that both values are null and
|
||||
// both keys exist in the bundle.
|
||||
return false;
|
||||
} else if (valueOne instanceof boolean[]) {
|
||||
if (!(valueTwo instanceof boolean[])
|
||||
|| !Arrays.equals((boolean[]) valueOne, (boolean[]) valueTwo)) {
|
||||
return false;
|
||||
}
|
||||
} else if (valueOne instanceof long[]) {
|
||||
if (!(valueTwo instanceof long[])
|
||||
|| !Arrays.equals((long[]) valueOne, (long[]) valueTwo)) {
|
||||
return false;
|
||||
}
|
||||
} else if (valueOne instanceof double[]) {
|
||||
if (!(valueTwo instanceof double[])
|
||||
|| !Arrays.equals((double[]) valueOne, (double[]) valueTwo)) {
|
||||
return false;
|
||||
}
|
||||
} else if (valueOne instanceof Bundle[]) {
|
||||
if (!(valueTwo instanceof Bundle[])) {
|
||||
return false;
|
||||
}
|
||||
Bundle[] bundlesOne = (Bundle[]) valueOne;
|
||||
Bundle[] bundlesTwo = (Bundle[]) valueTwo;
|
||||
if (bundlesOne.length != bundlesTwo.length) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < bundlesOne.length; i++) {
|
||||
if (!bundleEquals(bundlesOne[i], bundlesTwo[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (valueOne instanceof ArrayList) {
|
||||
if (!(valueTwo instanceof ArrayList)) {
|
||||
return false;
|
||||
}
|
||||
ArrayList<Bundle> bundlesOne = (ArrayList<Bundle>) valueOne;
|
||||
ArrayList<Bundle> bundlesTwo = (ArrayList<Bundle>) valueTwo;
|
||||
if (bundlesOne.size() != bundlesTwo.size()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < bundlesOne.size(); i++) {
|
||||
if (!bundleEquals(bundlesOne.get(i), bundlesTwo.get(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (valueOne instanceof Object[]) {
|
||||
if (!(valueTwo instanceof Object[])
|
||||
|| !Arrays.equals((Object[]) valueOne, (Object[]) valueTwo)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return BundleUtil.deepEquals(this.mBundle, otherDocument.mBundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (mHashCode == null) {
|
||||
mHashCode = bundleHashCode(mBundle);
|
||||
mHashCode = BundleUtil.deepHashCode(mBundle);
|
||||
}
|
||||
return mHashCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the hash code for a bundle.
|
||||
*
|
||||
* <p>The hash code is only effected by the contents in the bundle. Bundles will get consistent
|
||||
* hash code if they have same contents.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static int bundleHashCode(Bundle bundle) {
|
||||
int[] hashCodes = new int[bundle.size()];
|
||||
int i = 0;
|
||||
// Bundle inherit its hashCode() from Object.java, which only relative to their memory
|
||||
// address. Bundle doesn't have an order, so we should iterate all keys and combine
|
||||
// their value's hashcode into an array. And use the hashcode of the array to be
|
||||
// the hashcode of the bundle.
|
||||
for (String key : bundle.keySet()) {
|
||||
Object value = bundle.get(key);
|
||||
if (value instanceof boolean[]) {
|
||||
hashCodes[i++] = Arrays.hashCode((boolean[]) value);
|
||||
} else if (value instanceof long[]) {
|
||||
hashCodes[i++] = Arrays.hashCode((long[]) value);
|
||||
} else if (value instanceof double[]) {
|
||||
hashCodes[i++] = Arrays.hashCode((double[]) value);
|
||||
} else if (value instanceof String[]) {
|
||||
hashCodes[i++] = Arrays.hashCode((Object[]) value);
|
||||
} else if (value instanceof Bundle) {
|
||||
hashCodes[i++] = bundleHashCode((Bundle) value);
|
||||
} else if (value instanceof Bundle[]) {
|
||||
Bundle[] bundles = (Bundle[]) value;
|
||||
int[] innerHashCodes = new int[bundles.length];
|
||||
for (int j = 0; j < innerHashCodes.length; j++) {
|
||||
innerHashCodes[j] = bundleHashCode(bundles[j]);
|
||||
}
|
||||
hashCodes[i++] = Arrays.hashCode(innerHashCodes);
|
||||
} else if (value instanceof ArrayList) {
|
||||
ArrayList<Bundle> bundles = (ArrayList<Bundle>) value;
|
||||
int[] innerHashCodes = new int[bundles.size()];
|
||||
for (int j = 0; j < innerHashCodes.length; j++) {
|
||||
innerHashCodes[j] = bundleHashCode(bundles.get(j));
|
||||
}
|
||||
hashCodes[i++] = Arrays.hashCode(innerHashCodes);
|
||||
} else {
|
||||
hashCodes[i++] = value.hashCode();
|
||||
}
|
||||
}
|
||||
return Arrays.hashCode(hashCodes);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String toString() {
|
||||
@@ -683,10 +557,10 @@ public class GenericDocument {
|
||||
*
|
||||
* @param uri The uri of {@link GenericDocument}.
|
||||
* @param schemaType The schema type of the {@link GenericDocument}. The passed-in {@code
|
||||
* schemaType} must be defined using {@link AppSearchManager#setSchema} prior to
|
||||
* schemaType} must be defined using {@link AppSearchSession#setSchema} prior to
|
||||
* inserting a document of this {@code schemaType} into the AppSearch index using {@link
|
||||
* AppSearchManager#putDocuments}. Otherwise, the document will be rejected by {@link
|
||||
* AppSearchManager#putDocuments}.
|
||||
* AppSearchSession#putDocuments}. Otherwise, the document will be rejected by {@link
|
||||
* AppSearchSession#putDocuments}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Builder(@NonNull String uri, @NonNull String schemaType) {
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.util.Set;
|
||||
/**
|
||||
* Encapsulates a request to retrieve documents by namespace and URI.
|
||||
*
|
||||
* @see AppSearchManager#getByUri
|
||||
* @see AppSearchSession#getByUri
|
||||
*/
|
||||
public final class GetByUriRequest {
|
||||
private final String mNamespace;
|
||||
|
||||
@@ -32,6 +32,8 @@ interface IAppSearchManager {
|
||||
*
|
||||
* @param databaseName The databaseName this document resides in.
|
||||
* @param schemaBundles List of AppSearchSchema bundles.
|
||||
* @param schemasNotPlatformSurfaceable Schema types that should not be surfaced on platform
|
||||
* surfaces.
|
||||
* @param forceOverride Whether to apply the new schema even if it is incompatible. All
|
||||
* incompatible documents will be deleted.
|
||||
* @param callback {@link IAppSearchResultCallback#onResult} will be called with an
|
||||
@@ -40,6 +42,7 @@ interface IAppSearchManager {
|
||||
void setSchema(
|
||||
in String databaseName,
|
||||
in List<Bundle> schemaBundles,
|
||||
in List<String> schemasNotPlatformSurfaceable,
|
||||
boolean forceOverride,
|
||||
in IAppSearchResultCallback callback);
|
||||
|
||||
|
||||
@@ -29,9 +29,9 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Encapsulates a request to index a document into an {@link AppSearchManager} database.
|
||||
* Encapsulates a request to index a document into an {@link AppSearchSession} database.
|
||||
*
|
||||
* @see AppSearchManager#putDocuments
|
||||
* @see AppSearchSession#putDocuments
|
||||
*/
|
||||
public final class PutDocumentsRequest {
|
||||
private final List<GenericDocument> mDocuments;
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.util.Set;
|
||||
/**
|
||||
* Encapsulates a request to remove documents by namespace and URI.
|
||||
*
|
||||
* @see AppSearchManager#removeByUri
|
||||
* @see AppSearchSession#removeByUri
|
||||
*/
|
||||
public final class RemoveByUriRequest {
|
||||
private final String mNamespace;
|
||||
|
||||
@@ -26,27 +26,43 @@ import com.android.internal.util.Preconditions;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Encapsulates a request to update the schema of an {@link AppSearchManager} database.
|
||||
* Encapsulates a request to update the schema of an {@link AppSearchSession} database.
|
||||
*
|
||||
* @see AppSearchManager#setSchema
|
||||
* @see AppSearchSession#setSchema
|
||||
*/
|
||||
public final class SetSchemaRequest {
|
||||
private final Set<AppSearchSchema> mSchemas;
|
||||
private final Set<String> mSchemasNotPlatformSurfaceable;
|
||||
private final boolean mForceOverride;
|
||||
|
||||
SetSchemaRequest(Set<AppSearchSchema> schemas, boolean forceOverride) {
|
||||
mSchemas = schemas;
|
||||
SetSchemaRequest(
|
||||
@NonNull Set<AppSearchSchema> schemas,
|
||||
@NonNull Set<String> schemasNotPlatformSurfaceable,
|
||||
boolean forceOverride) {
|
||||
mSchemas = Preconditions.checkNotNull(schemas);
|
||||
mSchemasNotPlatformSurfaceable = Preconditions.checkNotNull(schemasNotPlatformSurfaceable);
|
||||
mForceOverride = forceOverride;
|
||||
}
|
||||
|
||||
/** Returns the schemas that are part of this request. */
|
||||
@NonNull
|
||||
public Set<AppSearchSchema> getSchemas() {
|
||||
return mSchemas;
|
||||
return Collections.unmodifiableSet(mSchemas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of schema types that have opted out of being visible on system UI surfaces.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public Set<String> getSchemasNotPlatformSurfaceable() {
|
||||
return Collections.unmodifiableSet(mSchemasNotPlatformSurfaceable);
|
||||
}
|
||||
|
||||
/** Returns whether this request will force the schema to be overridden. */
|
||||
@@ -57,17 +73,26 @@ public final class SetSchemaRequest {
|
||||
/** Builder for {@link SetSchemaRequest} objects. */
|
||||
public static final class Builder {
|
||||
private final Set<AppSearchSchema> mSchemas = new ArraySet<>();
|
||||
private final Set<String> mSchemasNotPlatformSurfaceable = new ArraySet<>();
|
||||
private boolean mForceOverride = false;
|
||||
private boolean mBuilt = false;
|
||||
|
||||
/** Adds one or more types to the schema. */
|
||||
/**
|
||||
* Adds one or more types to the schema.
|
||||
*
|
||||
* <p>Any documents of these types will be visible on system UI surfaces by default.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder addSchema(@NonNull AppSearchSchema... schemas) {
|
||||
Preconditions.checkNotNull(schemas);
|
||||
return addSchema(Arrays.asList(schemas));
|
||||
}
|
||||
|
||||
/** Adds one or more types to the schema. */
|
||||
/**
|
||||
* Adds one or more types to the schema.
|
||||
*
|
||||
* <p>Any documents of these types will be visible on system UI surfaces by default.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder addSchema(@NonNull Collection<AppSearchSchema> schemas) {
|
||||
Preconditions.checkState(!mBuilt, "Builder has already been used");
|
||||
@@ -76,14 +101,44 @@ public final class SetSchemaRequest {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets visibility on system UI surfaces for schema types.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setSchemaTypeVisibilityForSystemUi(
|
||||
boolean visible, @NonNull String... schemaTypes) {
|
||||
Preconditions.checkNotNull(schemaTypes);
|
||||
return this.setSchemaTypeVisibilityForSystemUi(visible, Arrays.asList(schemaTypes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets visibility on system UI surfaces for schema types.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setSchemaTypeVisibilityForSystemUi(
|
||||
boolean visible, @NonNull Collection<String> schemaTypes) {
|
||||
Preconditions.checkState(!mBuilt, "Builder has already been used");
|
||||
Preconditions.checkNotNull(schemaTypes);
|
||||
if (visible) {
|
||||
mSchemasNotPlatformSurfaceable.removeAll(schemaTypes);
|
||||
} else {
|
||||
mSchemasNotPlatformSurfaceable.addAll(schemaTypes);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link SetSchemaRequest} to delete any existing documents that don't
|
||||
* follow the new schema.
|
||||
*
|
||||
* <p>By default, this is {@code false} and schema incompatibility causes the {@link
|
||||
* AppSearchManager#setSchema} call to fail.
|
||||
* AppSearchSession#setSchema} call to fail.
|
||||
*
|
||||
* @see AppSearchManager#setSchema
|
||||
* @see AppSearchSession#setSchema
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setForceOverride(boolean forceOverride) {
|
||||
@@ -91,12 +146,34 @@ public final class SetSchemaRequest {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Builds a new {@link SetSchemaRequest}. */
|
||||
/**
|
||||
* Builds a new {@link SetSchemaRequest}.
|
||||
*
|
||||
* @throws IllegalArgumentException If schema types were referenced, but the corresponding
|
||||
* {@link AppSearchSchema} was never added.
|
||||
*/
|
||||
@NonNull
|
||||
public SetSchemaRequest build() {
|
||||
Preconditions.checkState(!mBuilt, "Builder has already been used");
|
||||
mBuilt = true;
|
||||
return new SetSchemaRequest(mSchemas, mForceOverride);
|
||||
|
||||
// Verify that any schema types with visibility settings refer to a real schema.
|
||||
// Create a copy because we're going to remove from the set for verification purposes.
|
||||
Set<String> schemasNotPlatformSurfaceableCopy =
|
||||
new ArraySet<>(mSchemasNotPlatformSurfaceable);
|
||||
for (AppSearchSchema schema : mSchemas) {
|
||||
schemasNotPlatformSurfaceableCopy.remove(schema.getSchemaType());
|
||||
}
|
||||
if (!schemasNotPlatformSurfaceableCopy.isEmpty()) {
|
||||
// We still have schema types that weren't seen in our mSchemas set. This means
|
||||
// there wasn't a corresponding AppSearchSchema.
|
||||
throw new IllegalArgumentException(
|
||||
"Schema types "
|
||||
+ schemasNotPlatformSurfaceableCopy
|
||||
+ " referenced, but were not added.");
|
||||
}
|
||||
|
||||
return new SetSchemaRequest(mSchemas, mSchemasNotPlatformSurfaceable, mForceOverride);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.app.appsearch.util;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Bundle;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Utilities for working with {@link android.os.Bundle}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class BundleUtil {
|
||||
private BundleUtil() {}
|
||||
|
||||
/**
|
||||
* Deeply checks two bundles are equal or not.
|
||||
*
|
||||
* <p>Two bundles will be considered equal if they contain the same keys, and each value is also
|
||||
* equal. Bundle values are compared using deepEquals.
|
||||
*/
|
||||
public static boolean deepEquals(@Nullable Bundle one, @Nullable Bundle two) {
|
||||
if (one == null && two == null) {
|
||||
return true;
|
||||
}
|
||||
if (one == null || two == null) {
|
||||
return false;
|
||||
}
|
||||
if (one.size() != two.size()) {
|
||||
return false;
|
||||
}
|
||||
if (!one.keySet().equals(two.keySet())) {
|
||||
return false;
|
||||
}
|
||||
// Bundle inherit its equals() from Object.java, which only compare their memory address.
|
||||
// We should iterate all keys and check their presents and values in both bundle.
|
||||
for (String key : one.keySet()) {
|
||||
if (!bundleValueEquals(one.get(key), two.get(key))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deeply checks whether two values in a Bundle are equal or not.
|
||||
*
|
||||
* <p>Values of type Bundle are compared using {@link #deepEquals}.
|
||||
*/
|
||||
private static boolean bundleValueEquals(@Nullable Object one, @Nullable Object two) {
|
||||
if (one == null && two == null) {
|
||||
return true;
|
||||
}
|
||||
if (one == null || two == null) {
|
||||
return false;
|
||||
}
|
||||
if (one.equals(two)) {
|
||||
return true;
|
||||
}
|
||||
if (one instanceof Bundle && two instanceof Bundle) {
|
||||
return deepEquals((Bundle) one, (Bundle) two);
|
||||
} else if (one instanceof int[] && two instanceof int[]) {
|
||||
return Arrays.equals((int[]) one, (int[]) two);
|
||||
} else if (one instanceof byte[] && two instanceof byte[]) {
|
||||
return Arrays.equals((byte[]) one, (byte[]) two);
|
||||
} else if (one instanceof char[] && two instanceof char[]) {
|
||||
return Arrays.equals((char[]) one, (char[]) two);
|
||||
} else if (one instanceof long[] && two instanceof long[]) {
|
||||
return Arrays.equals((long[]) one, (long[]) two);
|
||||
} else if (one instanceof float[] && two instanceof float[]) {
|
||||
return Arrays.equals((float[]) one, (float[]) two);
|
||||
} else if (one instanceof short[] && two instanceof short[]) {
|
||||
return Arrays.equals((short[]) one, (short[]) two);
|
||||
} else if (one instanceof double[] && two instanceof double[]) {
|
||||
return Arrays.equals((double[]) one, (double[]) two);
|
||||
} else if (one instanceof boolean[] && two instanceof boolean[]) {
|
||||
return Arrays.equals((boolean[]) one, (boolean[]) two);
|
||||
} else if (one instanceof Object[] && two instanceof Object[]) {
|
||||
Object[] arrayOne = (Object[]) one;
|
||||
Object[] arrayTwo = (Object[]) two;
|
||||
if (arrayOne.length != arrayTwo.length) {
|
||||
return false;
|
||||
}
|
||||
if (Arrays.equals(arrayOne, arrayTwo)) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < arrayOne.length; i++) {
|
||||
if (!bundleValueEquals(arrayOne[i], arrayTwo[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (one instanceof ArrayList && two instanceof ArrayList) {
|
||||
ArrayList<?> listOne = (ArrayList<?>) one;
|
||||
ArrayList<?> listTwo = (ArrayList<?>) two;
|
||||
if (listOne.size() != listTwo.size()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < listOne.size(); i++) {
|
||||
if (!bundleValueEquals(listOne.get(i), listTwo.get(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (one instanceof SparseArray && two instanceof SparseArray) {
|
||||
SparseArray<?> arrayOne = (SparseArray<?>) one;
|
||||
SparseArray<?> arrayTwo = (SparseArray<?>) two;
|
||||
if (arrayOne.size() != arrayTwo.size()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < arrayOne.size(); i++) {
|
||||
if (arrayOne.keyAt(i) != arrayTwo.keyAt(i)
|
||||
|| !bundleValueEquals(arrayOne.valueAt(i), arrayTwo.valueAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the hash code for a bundle.
|
||||
*
|
||||
* <p>The hash code is only effected by the contents in the bundle. Bundles will get consistent
|
||||
* hash code if they have same contents.
|
||||
*/
|
||||
public static int deepHashCode(@Nullable Bundle bundle) {
|
||||
if (bundle == null) {
|
||||
return 0;
|
||||
}
|
||||
int[] hashCodes = new int[bundle.size()];
|
||||
int i = 0;
|
||||
// Bundle inherit its hashCode() from Object.java, which only relative to their memory
|
||||
// address. Bundle doesn't have an order, so we should iterate all keys and combine
|
||||
// their value's hashcode into an array. And use the hashcode of the array to be
|
||||
// the hashcode of the bundle.
|
||||
for (String key : bundle.keySet()) {
|
||||
Object value = bundle.get(key);
|
||||
if (value instanceof Bundle) {
|
||||
hashCodes[i++] = deepHashCode((Bundle) value);
|
||||
} else if (value instanceof int[]) {
|
||||
hashCodes[i++] = Arrays.hashCode((int[]) value);
|
||||
} else if (value instanceof byte[]) {
|
||||
hashCodes[i++] = Arrays.hashCode((byte[]) value);
|
||||
} else if (value instanceof char[]) {
|
||||
hashCodes[i++] = Arrays.hashCode((char[]) value);
|
||||
} else if (value instanceof long[]) {
|
||||
hashCodes[i++] = Arrays.hashCode((long[]) value);
|
||||
} else if (value instanceof float[]) {
|
||||
hashCodes[i++] = Arrays.hashCode((float[]) value);
|
||||
} else if (value instanceof short[]) {
|
||||
hashCodes[i++] = Arrays.hashCode((short[]) value);
|
||||
} else if (value instanceof double[]) {
|
||||
hashCodes[i++] = Arrays.hashCode((double[]) value);
|
||||
} else if (value instanceof boolean[]) {
|
||||
hashCodes[i++] = Arrays.hashCode((boolean[]) value);
|
||||
} else if (value instanceof String[]) {
|
||||
// Optimization to avoid Object[] handler creating an inner array for common cases
|
||||
hashCodes[i++] = Arrays.hashCode((String[]) value);
|
||||
} else if (value instanceof Object[]) {
|
||||
Object[] array = (Object[]) value;
|
||||
int[] innerHashCodes = new int[array.length];
|
||||
for (int j = 0; j < array.length; j++) {
|
||||
if (array[j] instanceof Bundle) {
|
||||
innerHashCodes[j] = deepHashCode((Bundle) array[j]);
|
||||
} else if (array[j] != null) {
|
||||
innerHashCodes[j] = array[j].hashCode();
|
||||
}
|
||||
}
|
||||
hashCodes[i++] = Arrays.hashCode(innerHashCodes);
|
||||
} else if (value instanceof ArrayList) {
|
||||
ArrayList<?> list = (ArrayList<?>) value;
|
||||
int[] innerHashCodes = new int[list.size()];
|
||||
for (int j = 0; j < innerHashCodes.length; j++) {
|
||||
Object item = list.get(j);
|
||||
if (item instanceof Bundle) {
|
||||
innerHashCodes[j] = deepHashCode((Bundle) item);
|
||||
} else if (item != null) {
|
||||
innerHashCodes[j] = item.hashCode();
|
||||
}
|
||||
}
|
||||
hashCodes[i++] = Arrays.hashCode(innerHashCodes);
|
||||
} else if (value instanceof SparseArray) {
|
||||
SparseArray<?> array = (SparseArray<?>) value;
|
||||
int[] innerHashCodes = new int[array.size() * 2];
|
||||
for (int j = 0; j < array.size(); j++) {
|
||||
innerHashCodes[j * 2] = array.keyAt(j);
|
||||
Object item = array.valueAt(j);
|
||||
if (item instanceof Bundle) {
|
||||
innerHashCodes[j * 2 + 1] = deepHashCode((Bundle) item);
|
||||
} else if (item != null) {
|
||||
innerHashCodes[j * 2 + 1] = item.hashCode();
|
||||
}
|
||||
}
|
||||
hashCodes[i++] = Arrays.hashCode(innerHashCodes);
|
||||
} else {
|
||||
hashCodes[i++] = value.hashCode();
|
||||
}
|
||||
}
|
||||
return Arrays.hashCode(hashCodes);
|
||||
}
|
||||
}
|
||||
@@ -33,15 +33,14 @@ import android.os.Bundle;
|
||||
import android.os.ParcelableException;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* TODO(b/142567528): add comments when implement this class
|
||||
@@ -64,6 +63,7 @@ public class AppSearchManagerService extends SystemService {
|
||||
public void setSchema(
|
||||
@NonNull String databaseName,
|
||||
@NonNull List<Bundle> schemaBundles,
|
||||
@NonNull List<String> schemasNotPlatformSurfaceable,
|
||||
boolean forceOverride,
|
||||
@NonNull IAppSearchResultCallback callback) {
|
||||
Preconditions.checkNotNull(databaseName);
|
||||
@@ -73,13 +73,13 @@ public class AppSearchManagerService extends SystemService {
|
||||
int callingUserId = UserHandle.getUserId(callingUid);
|
||||
final long callingIdentity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
Set<AppSearchSchema> schemas = new ArraySet<>(schemaBundles.size());
|
||||
List<AppSearchSchema> schemas = new ArrayList<>(schemaBundles.size());
|
||||
for (int i = 0; i < schemaBundles.size(); i++) {
|
||||
schemas.add(new AppSearchSchema(schemaBundles.get(i)));
|
||||
}
|
||||
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
|
||||
databaseName = rewriteDatabaseNameWithUid(databaseName, callingUid);
|
||||
impl.setSchema(databaseName, schemas, forceOverride);
|
||||
impl.setSchema(databaseName, schemas, schemasNotPlatformSurfaceable, forceOverride);
|
||||
invokeCallbackOnResult(callback,
|
||||
AppSearchResult.newSuccessfulResult(/*result=*/ null));
|
||||
} catch (Throwable t) {
|
||||
|
||||
@@ -60,6 +60,7 @@ import com.google.android.icing.proto.SetSchemaResultProto;
|
||||
import com.google.android.icing.proto.StatusProto;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -226,13 +227,16 @@ public final class AppSearchImpl {
|
||||
*
|
||||
* @param databaseName The name of the database where this schema lives.
|
||||
* @param schemas Schemas to set for this app.
|
||||
* @param schemasNotPlatformSurfaceable Schema types that should not be surfaced on platform
|
||||
* surfaces.
|
||||
* @param forceOverride Whether to force-apply the schema even if it is incompatible. Documents
|
||||
* which do not comply with the new schema will be deleted.
|
||||
* @throws AppSearchException on IcingSearchEngine error.
|
||||
*/
|
||||
public void setSchema(
|
||||
@NonNull String databaseName,
|
||||
@NonNull Set<AppSearchSchema> schemas,
|
||||
@NonNull List<AppSearchSchema> schemas,
|
||||
@NonNull List<String> schemasNotPlatformSurfaceable,
|
||||
boolean forceOverride)
|
||||
throws AppSearchException {
|
||||
mReadWriteLock.writeLock().lock();
|
||||
@@ -240,8 +244,9 @@ public final class AppSearchImpl {
|
||||
SchemaProto.Builder existingSchemaBuilder = getSchemaProtoLocked().toBuilder();
|
||||
|
||||
SchemaProto.Builder newSchemaBuilder = SchemaProto.newBuilder();
|
||||
for (AppSearchSchema schema : schemas) {
|
||||
SchemaTypeConfigProto schemaTypeProto = SchemaToProtoConverter.convert(schema);
|
||||
for (int i = 0; i < schemas.size(); i++) {
|
||||
SchemaTypeConfigProto schemaTypeProto =
|
||||
SchemaToProtoConverter.toSchemaTypeConfigProto(schemas.get(i));
|
||||
newSchemaBuilder.addTypes(schemaTypeProto);
|
||||
}
|
||||
|
||||
@@ -276,8 +281,16 @@ public final class AppSearchImpl {
|
||||
|
||||
// Update derived data structures.
|
||||
mSchemaMapLocked.put(databaseName, rewrittenSchemaResults.mRewrittenQualifiedTypes);
|
||||
mVisibilityStoreLocked.updateSchemas(
|
||||
databaseName, rewrittenSchemaResults.mDeletedQualifiedTypes);
|
||||
|
||||
String databasePrefix = getDatabasePrefix(databaseName);
|
||||
Set<String> qualifiedSchemasNotPlatformSurfaceable =
|
||||
new ArraySet<>(schemasNotPlatformSurfaceable.size());
|
||||
for (int i = 0; i < schemasNotPlatformSurfaceable.size(); i++) {
|
||||
qualifiedSchemasNotPlatformSurfaceable.add(
|
||||
databasePrefix + schemasNotPlatformSurfaceable.get(i));
|
||||
}
|
||||
mVisibilityStoreLocked.setVisibility(
|
||||
databaseName, qualifiedSchemasNotPlatformSurfaceable);
|
||||
|
||||
// Determine whether to schedule an immediate optimize.
|
||||
if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0
|
||||
@@ -294,38 +307,55 @@ public final class AppSearchImpl {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the visibility settings for this app.
|
||||
* Retrieves the AppSearch schema for this database.
|
||||
*
|
||||
* <p>This method belongs to the mutate group
|
||||
* <p>This method belongs to query group.
|
||||
*
|
||||
* @param databaseName The name of the database where the visibility settings will apply.
|
||||
* @param schemasHiddenFromPlatformSurfaces Schemas that should be hidden from platform surfaces
|
||||
* @throws AppSearchException on IcingSearchEngine error
|
||||
* @param databaseName The name of the database where this schema lives.
|
||||
* @throws AppSearchException on IcingSearchEngine error.
|
||||
*/
|
||||
public void setVisibility(
|
||||
@NonNull String databaseName, @NonNull Set<String> schemasHiddenFromPlatformSurfaces)
|
||||
throws AppSearchException {
|
||||
mReadWriteLock.writeLock().lock();
|
||||
@NonNull
|
||||
public List<AppSearchSchema> getSchema(@NonNull String databaseName) throws AppSearchException {
|
||||
SchemaProto fullSchema;
|
||||
mReadWriteLock.readLock().lock();
|
||||
try {
|
||||
String databasePrefix = getDatabasePrefix(databaseName);
|
||||
Set<String> qualifiedSchemasHiddenFromPlatformSurface =
|
||||
new ArraySet<>(schemasHiddenFromPlatformSurfaces.size());
|
||||
for (String schema : schemasHiddenFromPlatformSurfaces) {
|
||||
Set<String> existingSchemas = mSchemaMapLocked.get(databaseName);
|
||||
if (existingSchemas == null || !existingSchemas.contains(databasePrefix + schema)) {
|
||||
throw new AppSearchException(
|
||||
AppSearchResult.RESULT_NOT_FOUND,
|
||||
"Unknown schema(s): "
|
||||
+ schemasHiddenFromPlatformSurfaces
|
||||
+ " provided during setVisibility.");
|
||||
}
|
||||
qualifiedSchemasHiddenFromPlatformSurface.add(databasePrefix + schema);
|
||||
}
|
||||
mVisibilityStoreLocked.setVisibility(
|
||||
databaseName, qualifiedSchemasHiddenFromPlatformSurface);
|
||||
fullSchema = getSchemaProtoLocked();
|
||||
} finally {
|
||||
mReadWriteLock.writeLock().lock();
|
||||
mReadWriteLock.readLock().unlock();
|
||||
}
|
||||
|
||||
List<AppSearchSchema> result = new ArrayList<>();
|
||||
for (int i = 0; i < fullSchema.getTypesCount(); i++) {
|
||||
String typeDatabase = getDatabaseName(fullSchema.getTypes(i).getSchemaType());
|
||||
if (!databaseName.equals(typeDatabase)) {
|
||||
continue;
|
||||
}
|
||||
// Rewrite SchemaProto.types.schema_type
|
||||
SchemaTypeConfigProto.Builder typeConfigBuilder = fullSchema.getTypes(i).toBuilder();
|
||||
String newSchemaType =
|
||||
typeConfigBuilder.getSchemaType().substring(databaseName.length() + 1);
|
||||
typeConfigBuilder.setSchemaType(newSchemaType);
|
||||
|
||||
// Rewrite SchemaProto.types.properties.schema_type
|
||||
for (int propertyIdx = 0;
|
||||
propertyIdx < typeConfigBuilder.getPropertiesCount();
|
||||
propertyIdx++) {
|
||||
PropertyConfigProto.Builder propertyConfigBuilder =
|
||||
typeConfigBuilder.getProperties(propertyIdx).toBuilder();
|
||||
if (!propertyConfigBuilder.getSchemaType().isEmpty()) {
|
||||
String newPropertySchemaType =
|
||||
propertyConfigBuilder
|
||||
.getSchemaType()
|
||||
.substring(databaseName.length() + 1);
|
||||
propertyConfigBuilder.setSchemaType(newPropertySchemaType);
|
||||
typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
AppSearchSchema schema = SchemaToProtoConverter.toAppSearchSchema(typeConfigBuilder);
|
||||
result.add(schema);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -340,7 +370,7 @@ public final class AppSearchImpl {
|
||||
public void putDocument(@NonNull String databaseName, @NonNull GenericDocument document)
|
||||
throws AppSearchException {
|
||||
DocumentProto.Builder documentBuilder =
|
||||
GenericDocumentToProtoConverter.convert(document).toBuilder();
|
||||
GenericDocumentToProtoConverter.toDocumentProto(document).toBuilder();
|
||||
addPrefixToDocument(documentBuilder, getDatabasePrefix(databaseName));
|
||||
|
||||
PutResultProto putResultProto;
|
||||
@@ -384,7 +414,7 @@ public final class AppSearchImpl {
|
||||
|
||||
DocumentProto.Builder documentBuilder = getResultProto.getDocument().toBuilder();
|
||||
removeDatabasesFromDocument(documentBuilder);
|
||||
return GenericDocumentToProtoConverter.convert(documentBuilder.build());
|
||||
return GenericDocumentToProtoConverter.toGenericDocument(documentBuilder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -969,7 +999,7 @@ public final class AppSearchImpl {
|
||||
resultsBuilder.setResults(i, resultBuilder);
|
||||
}
|
||||
}
|
||||
return SearchResultToProtoConverter.convertToSearchResultPage(resultsBuilder);
|
||||
return SearchResultToProtoConverter.toSearchResultPage(resultsBuilder);
|
||||
}
|
||||
|
||||
@GuardedBy("mReadWriteLock")
|
||||
|
||||
@@ -53,19 +53,23 @@ import java.util.Set;
|
||||
class VisibilityStore {
|
||||
// Schema type for documents that hold AppSearch's metadata, e.g. visibility settings
|
||||
@VisibleForTesting static final String SCHEMA_TYPE = "Visibility";
|
||||
|
||||
// Property that holds the list of platform-hidden schemas, as part of the visibility
|
||||
// settings.
|
||||
@VisibleForTesting static final String PLATFORM_HIDDEN_PROPERTY = "platformHidden";
|
||||
@VisibleForTesting
|
||||
static final String NOT_PLATFORM_SURFACEABLE_PROPERTY = "notPlatformSurfaceable";
|
||||
// Database name to prefix all visibility schemas and documents with. Special-cased to
|
||||
// minimize the chance of collision with a client-supplied database.
|
||||
|
||||
@VisibleForTesting static final String DATABASE_NAME = "$$__AppSearch__Database";
|
||||
|
||||
// Namespace of documents that contain visibility settings
|
||||
private static final String NAMESPACE = "namespace";
|
||||
private final AppSearchImpl mAppSearchImpl;
|
||||
|
||||
// The map contains schemas that are platform-hidden for each database. All schemas in the map
|
||||
// have a database name prefix.
|
||||
private final Map<String, Set<String>> mPlatformHiddenMap = new ArrayMap<>();
|
||||
private final Map<String, Set<String>> mNotPlatformSurfaceableMap = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* Creates an uninitialized VisibilityStore object. Callers must also call {@link #initialize()}
|
||||
@@ -82,8 +86,8 @@ class VisibilityStore {
|
||||
*
|
||||
* <p>This is kept separate from the constructor because this will call methods on
|
||||
* AppSearchImpl. Some may even then recursively call back into VisibilityStore (for example,
|
||||
* {@link AppSearchImpl#setSchema} will call {@link #updateSchemas}. We need to have both
|
||||
* AppSearchImpl and VisibilityStore fully initialized for this call flow to work.
|
||||
* {@link AppSearchImpl#setSchema} will call {@link #setVisibility(String, Set)}. We need to
|
||||
* have both AppSearchImpl and VisibilityStore fully initialized for this call flow to work.
|
||||
*
|
||||
* @throws AppSearchException AppSearchException on AppSearchImpl error.
|
||||
*/
|
||||
@@ -92,11 +96,11 @@ class VisibilityStore {
|
||||
// Schema type doesn't exist yet. Add it.
|
||||
mAppSearchImpl.setSchema(
|
||||
DATABASE_NAME,
|
||||
Collections.singleton(
|
||||
Collections.singletonList(
|
||||
new AppSearchSchema.Builder(SCHEMA_TYPE)
|
||||
.addProperty(
|
||||
new AppSearchSchema.PropertyConfig.Builder(
|
||||
PLATFORM_HIDDEN_PROPERTY)
|
||||
NOT_PLATFORM_SURFACEABLE_PROPERTY)
|
||||
.setDataType(
|
||||
AppSearchSchema.PropertyConfig
|
||||
.DATA_TYPE_STRING)
|
||||
@@ -105,6 +109,7 @@ class VisibilityStore {
|
||||
.CARDINALITY_REPEATED)
|
||||
.build())
|
||||
.build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*forceOverride=*/ false);
|
||||
}
|
||||
|
||||
@@ -120,8 +125,9 @@ class VisibilityStore {
|
||||
GenericDocument document =
|
||||
mAppSearchImpl.getDocument(DATABASE_NAME, NAMESPACE, /*uri=*/ database);
|
||||
|
||||
String[] schemas = document.getPropertyStringArray(PLATFORM_HIDDEN_PROPERTY);
|
||||
mPlatformHiddenMap.put(database, new ArraySet<>(Arrays.asList(schemas)));
|
||||
String[] schemas =
|
||||
document.getPropertyStringArray(NOT_PLATFORM_SURFACEABLE_PROPERTY);
|
||||
mNotPlatformSurfaceableMap.put(database, new ArraySet<>(Arrays.asList(schemas)));
|
||||
} catch (AppSearchException e) {
|
||||
if (e.getResultCode() == AppSearchResult.RESULT_NOT_FOUND) {
|
||||
// TODO(b/172068212): This indicates some desync error. We were expecting a
|
||||
@@ -135,95 +141,34 @@ class VisibilityStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update visibility settings for the {@code databaseName}.
|
||||
*
|
||||
* @param schemasToRemove Database-prefixed schemas that should be removed
|
||||
*/
|
||||
public void updateSchemas(@NonNull String databaseName, @NonNull Set<String> schemasToRemove)
|
||||
throws AppSearchException {
|
||||
Preconditions.checkNotNull(databaseName);
|
||||
Preconditions.checkNotNull(schemasToRemove);
|
||||
|
||||
GenericDocument visibilityDocument;
|
||||
try {
|
||||
visibilityDocument =
|
||||
mAppSearchImpl.getDocument(DATABASE_NAME, NAMESPACE, /*uri=*/ databaseName);
|
||||
} catch (AppSearchException e) {
|
||||
if (e.getResultCode() == AppSearchResult.RESULT_NOT_FOUND) {
|
||||
// This might be the first time we're seeing visibility changes for a database.
|
||||
// Create a new visibility document.
|
||||
mAppSearchImpl.putDocument(
|
||||
DATABASE_NAME,
|
||||
new GenericDocument.Builder(/*uri=*/ databaseName, SCHEMA_TYPE)
|
||||
.setNamespace(NAMESPACE)
|
||||
.build());
|
||||
|
||||
// Since we know there was nothing that existed before, we don't need to remove
|
||||
// anything either. Return early.
|
||||
return;
|
||||
}
|
||||
// Otherwise, this is some real error we should pass up.
|
||||
throw e;
|
||||
}
|
||||
|
||||
String[] hiddenSchemas =
|
||||
visibilityDocument.getPropertyStringArray(PLATFORM_HIDDEN_PROPERTY);
|
||||
if (hiddenSchemas == null) {
|
||||
// Nothing to remove.
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new set so we can remove from it.
|
||||
Set<String> remainingSchemas = new ArraySet<>(Arrays.asList(hiddenSchemas));
|
||||
boolean changed = remainingSchemas.removeAll(schemasToRemove);
|
||||
if (!changed) {
|
||||
// Nothing was actually removed. Can return early.
|
||||
return;
|
||||
}
|
||||
|
||||
// Update our persisted document
|
||||
// TODO(b/171882200): Switch to a .toBuilder API when it's available.
|
||||
GenericDocument.Builder newVisibilityDocument =
|
||||
new GenericDocument.Builder(/*uri=*/ databaseName, SCHEMA_TYPE)
|
||||
.setNamespace(NAMESPACE);
|
||||
if (!remainingSchemas.isEmpty()) {
|
||||
newVisibilityDocument.setPropertyString(
|
||||
PLATFORM_HIDDEN_PROPERTY, remainingSchemas.toArray(new String[0]));
|
||||
}
|
||||
mAppSearchImpl.putDocument(DATABASE_NAME, newVisibilityDocument.build());
|
||||
|
||||
// Update derived data structures
|
||||
mPlatformHiddenMap.put(databaseName, remainingSchemas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets visibility settings for {@code databaseName}. Any previous visibility settings will be
|
||||
* overwritten.
|
||||
*
|
||||
* @param databaseName Database name that owns the {@code platformHiddenSchemas}.
|
||||
* @param platformHiddenSchemas Set of database-qualified schemas that should be hidden from the
|
||||
* platform.
|
||||
* @param databaseName Database name that owns the {@code schemasNotPlatformSurfaceable}.
|
||||
* @param schemasNotPlatformSurfaceable Set of database-qualified schemas that should be hidden
|
||||
* from the platform.
|
||||
* @throws AppSearchException on AppSearchImpl error.
|
||||
*/
|
||||
public void setVisibility(
|
||||
@NonNull String databaseName, @NonNull Set<String> platformHiddenSchemas)
|
||||
@NonNull String databaseName, @NonNull Set<String> schemasNotPlatformSurfaceable)
|
||||
throws AppSearchException {
|
||||
Preconditions.checkNotNull(databaseName);
|
||||
Preconditions.checkNotNull(platformHiddenSchemas);
|
||||
Preconditions.checkNotNull(schemasNotPlatformSurfaceable);
|
||||
|
||||
// Persist the document
|
||||
GenericDocument.Builder visibilityDocument =
|
||||
new GenericDocument.Builder(/*uri=*/ databaseName, SCHEMA_TYPE)
|
||||
.setNamespace(NAMESPACE);
|
||||
if (!platformHiddenSchemas.isEmpty()) {
|
||||
if (!schemasNotPlatformSurfaceable.isEmpty()) {
|
||||
visibilityDocument.setPropertyString(
|
||||
PLATFORM_HIDDEN_PROPERTY, platformHiddenSchemas.toArray(new String[0]));
|
||||
NOT_PLATFORM_SURFACEABLE_PROPERTY,
|
||||
schemasNotPlatformSurfaceable.toArray(new String[0]));
|
||||
}
|
||||
mAppSearchImpl.putDocument(DATABASE_NAME, visibilityDocument.build());
|
||||
|
||||
// Update derived data structures.
|
||||
mPlatformHiddenMap.put(databaseName, platformHiddenSchemas);
|
||||
mNotPlatformSurfaceableMap.put(databaseName, schemasNotPlatformSurfaceable);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,13 +180,13 @@ class VisibilityStore {
|
||||
* none exist.
|
||||
*/
|
||||
@NonNull
|
||||
public Set<String> getPlatformHiddenSchemas(@NonNull String databaseName) {
|
||||
public Set<String> getSchemasNotPlatformSurfaceable(@NonNull String databaseName) {
|
||||
Preconditions.checkNotNull(databaseName);
|
||||
Set<String> platformHiddenSchemas = mPlatformHiddenMap.get(databaseName);
|
||||
if (platformHiddenSchemas == null) {
|
||||
Set<String> schemasNotPlatformSurfaceable = mNotPlatformSurfaceableMap.get(databaseName);
|
||||
if (schemasNotPlatformSurfaceable == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return platformHiddenSchemas;
|
||||
return schemasNotPlatformSurfaceable;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,7 +196,7 @@ class VisibilityStore {
|
||||
* @throws AppSearchException on AppSearchImpl error.
|
||||
*/
|
||||
public void handleReset() throws AppSearchException {
|
||||
mPlatformHiddenMap.clear();
|
||||
mNotPlatformSurfaceableMap.clear();
|
||||
initialize();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public final class GenericDocumentToProtoConverter {
|
||||
/** Converts a {@link GenericDocument} into a {@link DocumentProto}. */
|
||||
@NonNull
|
||||
@SuppressWarnings("unchecked")
|
||||
public static DocumentProto convert(@NonNull GenericDocument document) {
|
||||
public static DocumentProto toDocumentProto(@NonNull GenericDocument document) {
|
||||
Preconditions.checkNotNull(document);
|
||||
DocumentProto.Builder mProtoBuilder = DocumentProto.newBuilder();
|
||||
mProtoBuilder
|
||||
@@ -82,7 +82,7 @@ public final class GenericDocumentToProtoConverter {
|
||||
}
|
||||
} else if (documentValues != null) {
|
||||
for (int j = 0; j < documentValues.length; j++) {
|
||||
DocumentProto proto = convert(documentValues[j]);
|
||||
DocumentProto proto = toDocumentProto(documentValues[j]);
|
||||
propertyProto.addDocumentValues(proto);
|
||||
}
|
||||
} else {
|
||||
@@ -96,7 +96,7 @@ public final class GenericDocumentToProtoConverter {
|
||||
|
||||
/** Converts a {@link DocumentProto} into a {@link GenericDocument}. */
|
||||
@NonNull
|
||||
public static GenericDocument convert(@NonNull DocumentProto proto) {
|
||||
public static GenericDocument toGenericDocument(@NonNull DocumentProto proto) {
|
||||
Preconditions.checkNotNull(proto);
|
||||
GenericDocument.Builder<?> documentBuilder =
|
||||
new GenericDocument.Builder<>(proto.getUri(), proto.getSchema())
|
||||
@@ -141,7 +141,7 @@ public final class GenericDocumentToProtoConverter {
|
||||
} else if (property.getDocumentValuesCount() > 0) {
|
||||
GenericDocument[] values = new GenericDocument[property.getDocumentValuesCount()];
|
||||
for (int j = 0; j < values.length; j++) {
|
||||
values[j] = convert(property.getDocumentValues(j));
|
||||
values[j] = toGenericDocument(property.getDocumentValues(j));
|
||||
}
|
||||
documentBuilder.setPropertyDocument(name, values);
|
||||
} else {
|
||||
|
||||
@@ -18,11 +18,13 @@ package com.android.server.appsearch.external.localstorage.converter;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.appsearch.AppSearchSchema;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import com.google.android.icing.proto.PropertyConfigProto;
|
||||
import com.google.android.icing.proto.SchemaTypeConfigProto;
|
||||
import com.google.android.icing.proto.SchemaTypeConfigProtoOrBuilder;
|
||||
import com.google.android.icing.proto.StringIndexingConfig;
|
||||
import com.google.android.icing.proto.TermMatchType;
|
||||
|
||||
@@ -34,6 +36,8 @@ import java.util.List;
|
||||
* @hide
|
||||
*/
|
||||
public final class SchemaToProtoConverter {
|
||||
private static final String TAG = "AppSearchSchemaToProtoC";
|
||||
|
||||
private SchemaToProtoConverter() {}
|
||||
|
||||
/**
|
||||
@@ -41,23 +45,23 @@ public final class SchemaToProtoConverter {
|
||||
* SchemaTypeConfigProto}.
|
||||
*/
|
||||
@NonNull
|
||||
public static SchemaTypeConfigProto convert(@NonNull AppSearchSchema schema) {
|
||||
public static SchemaTypeConfigProto toSchemaTypeConfigProto(@NonNull AppSearchSchema schema) {
|
||||
Preconditions.checkNotNull(schema);
|
||||
SchemaTypeConfigProto.Builder protoBuilder =
|
||||
SchemaTypeConfigProto.newBuilder().setSchemaType(schema.getSchemaType());
|
||||
List<AppSearchSchema.PropertyConfig> properties = schema.getProperties();
|
||||
for (int i = 0; i < properties.size(); i++) {
|
||||
PropertyConfigProto propertyProto = convertProperty(properties.get(i));
|
||||
PropertyConfigProto propertyProto = toPropertyConfigProto(properties.get(i));
|
||||
protoBuilder.addProperties(propertyProto);
|
||||
}
|
||||
return protoBuilder.build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static PropertyConfigProto convertProperty(
|
||||
private static PropertyConfigProto toPropertyConfigProto(
|
||||
@NonNull AppSearchSchema.PropertyConfig property) {
|
||||
Preconditions.checkNotNull(property);
|
||||
PropertyConfigProto.Builder propertyConfigProto =
|
||||
PropertyConfigProto.Builder builder =
|
||||
PropertyConfigProto.newBuilder().setPropertyName(property.getName());
|
||||
StringIndexingConfig.Builder indexingConfig = StringIndexingConfig.newBuilder();
|
||||
|
||||
@@ -68,12 +72,12 @@ public final class SchemaToProtoConverter {
|
||||
if (dataTypeProto == null) {
|
||||
throw new IllegalArgumentException("Invalid dataType: " + dataType);
|
||||
}
|
||||
propertyConfigProto.setDataType(dataTypeProto);
|
||||
builder.setDataType(dataTypeProto);
|
||||
|
||||
// Set schemaType
|
||||
String schemaType = property.getSchemaType();
|
||||
if (schemaType != null) {
|
||||
propertyConfigProto.setSchemaType(schemaType);
|
||||
builder.setSchemaType(schemaType);
|
||||
}
|
||||
|
||||
// Set cardinality
|
||||
@@ -83,7 +87,7 @@ public final class SchemaToProtoConverter {
|
||||
if (cardinalityProto == null) {
|
||||
throw new IllegalArgumentException("Invalid cardinality: " + dataType);
|
||||
}
|
||||
propertyConfigProto.setCardinality(cardinalityProto);
|
||||
builder.setCardinality(cardinalityProto);
|
||||
|
||||
// Set indexingType
|
||||
@AppSearchSchema.PropertyConfig.IndexingType int indexingType = property.getIndexingType();
|
||||
@@ -114,7 +118,63 @@ public final class SchemaToProtoConverter {
|
||||
indexingConfig.setTokenizerType(tokenizerTypeProto);
|
||||
|
||||
// Build!
|
||||
propertyConfigProto.setStringIndexingConfig(indexingConfig);
|
||||
return propertyConfigProto.build();
|
||||
builder.setStringIndexingConfig(indexingConfig);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a {@link SchemaTypeConfigProto} into an {@link
|
||||
* android.app.appsearch.AppSearchSchema}.
|
||||
*/
|
||||
@NonNull
|
||||
public static AppSearchSchema toAppSearchSchema(@NonNull SchemaTypeConfigProtoOrBuilder proto) {
|
||||
Preconditions.checkNotNull(proto);
|
||||
AppSearchSchema.Builder builder = new AppSearchSchema.Builder(proto.getSchemaType());
|
||||
List<PropertyConfigProto> properties = proto.getPropertiesList();
|
||||
for (int i = 0; i < properties.size(); i++) {
|
||||
AppSearchSchema.PropertyConfig propertyConfig = toPropertyConfig(properties.get(i));
|
||||
builder.addProperty(propertyConfig);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static AppSearchSchema.PropertyConfig toPropertyConfig(
|
||||
@NonNull PropertyConfigProto proto) {
|
||||
Preconditions.checkNotNull(proto);
|
||||
AppSearchSchema.PropertyConfig.Builder builder =
|
||||
new AppSearchSchema.PropertyConfig.Builder(proto.getPropertyName())
|
||||
.setDataType(proto.getDataType().getNumber())
|
||||
.setCardinality(proto.getCardinality().getNumber())
|
||||
.setTokenizerType(
|
||||
proto.getStringIndexingConfig().getTokenizerType().getNumber());
|
||||
|
||||
// Set schema
|
||||
if (!proto.getSchemaType().isEmpty()) {
|
||||
builder.setSchemaType(proto.getSchemaType());
|
||||
}
|
||||
|
||||
// Set indexingType
|
||||
@AppSearchSchema.PropertyConfig.IndexingType int indexingType;
|
||||
TermMatchType.Code termMatchTypeProto = proto.getStringIndexingConfig().getTermMatchType();
|
||||
switch (termMatchTypeProto) {
|
||||
case UNKNOWN:
|
||||
indexingType = AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE;
|
||||
break;
|
||||
case EXACT_ONLY:
|
||||
indexingType = AppSearchSchema.PropertyConfig.INDEXING_TYPE_EXACT_TERMS;
|
||||
break;
|
||||
case PREFIX:
|
||||
indexingType = AppSearchSchema.PropertyConfig.INDEXING_TYPE_PREFIXES;
|
||||
break;
|
||||
default:
|
||||
// Avoid crashing in the 'read' path; we should try to interpret the document to the
|
||||
// extent possible.
|
||||
Log.w(TAG, "Invalid indexingType: " + termMatchTypeProto.getNumber());
|
||||
indexingType = AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE;
|
||||
}
|
||||
builder.setIndexingType(indexingType);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,13 +39,12 @@ public class SearchResultToProtoConverter {
|
||||
|
||||
/** Translate a {@link SearchResultProto} into {@link SearchResultPage}. */
|
||||
@NonNull
|
||||
public static SearchResultPage convertToSearchResultPage(
|
||||
@NonNull SearchResultProtoOrBuilder proto) {
|
||||
public static SearchResultPage toSearchResultPage(@NonNull SearchResultProtoOrBuilder proto) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, proto.getNextPageToken());
|
||||
ArrayList<Bundle> resultBundles = new ArrayList<>(proto.getResultsCount());
|
||||
for (int i = 0; i < proto.getResultsCount(); i++) {
|
||||
resultBundles.add(convertToSearchResultBundle(proto.getResults(i)));
|
||||
resultBundles.add(toSearchResultBundle(proto.getResults(i)));
|
||||
}
|
||||
bundle.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, resultBundles);
|
||||
return new SearchResultPage(bundle);
|
||||
@@ -53,10 +52,11 @@ public class SearchResultToProtoConverter {
|
||||
|
||||
/** Translate a {@link SearchResultProto.ResultProto} into {@link SearchResult}. */
|
||||
@NonNull
|
||||
private static Bundle convertToSearchResultBundle(
|
||||
private static Bundle toSearchResultBundle(
|
||||
@NonNull SearchResultProto.ResultProtoOrBuilder proto) {
|
||||
Bundle bundle = new Bundle();
|
||||
GenericDocument document = GenericDocumentToProtoConverter.convert(proto.getDocument());
|
||||
GenericDocument document =
|
||||
GenericDocumentToProtoConverter.toGenericDocument(proto.getDocument());
|
||||
bundle.putBundle(SearchResult.DOCUMENT_FIELD, document.getBundle());
|
||||
|
||||
ArrayList<Bundle> matchList = new ArrayList<>();
|
||||
|
||||
@@ -1 +1 @@
|
||||
I2decd83fab4c4d58fe38c9970f804046479c942c
|
||||
If5fd2bd705d5507d044706701a94b2e1496ef1df
|
||||
|
||||
65
core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java
vendored
Normal file
65
core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.app.appsearch;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.testng.Assert.expectThrows;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class SetSchemaRequestTest {
|
||||
|
||||
@Test
|
||||
public void testInvalidSchemaReferences() {
|
||||
IllegalArgumentException expected =
|
||||
expectThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
new SetSchemaRequest.Builder()
|
||||
.setSchemaTypeVisibilityForSystemUi(false, "InvalidSchema")
|
||||
.build());
|
||||
assertThat(expected).hasMessageThat().contains("referenced, but were not added");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSchemaTypeVisibilityForSystemUi_Visible() {
|
||||
AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
|
||||
|
||||
// By default, the schema is visible.
|
||||
SetSchemaRequest request = new SetSchemaRequest.Builder().addSchema(schema).build();
|
||||
assertThat(request.getSchemasNotPlatformSurfaceable()).isEmpty();
|
||||
|
||||
request =
|
||||
new SetSchemaRequest.Builder()
|
||||
.addSchema(schema)
|
||||
.setSchemaTypeVisibilityForSystemUi(true, "Schema")
|
||||
.build();
|
||||
assertThat(request.getSchemasNotPlatformSurfaceable()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSchemaTypeVisibilityForSystemUi_NotVisible() {
|
||||
AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
|
||||
SetSchemaRequest request =
|
||||
new SetSchemaRequest.Builder()
|
||||
.addSchema(schema)
|
||||
.setSchemaTypeVisibilityForSystemUi(false, "Schema")
|
||||
.build();
|
||||
assertThat(request.getSchemasNotPlatformSurfaceable()).containsExactly("Schema");
|
||||
}
|
||||
}
|
||||
@@ -78,4 +78,125 @@ public class AppSearchSchemaCtsTest {
|
||||
.build()));
|
||||
assertThat(e).hasMessageThat().contains("Property defined more than once: subject");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals_identical() {
|
||||
AppSearchSchema schema1 =
|
||||
new AppSearchSchema.Builder("Email")
|
||||
.addProperty(
|
||||
new PropertyConfig.Builder("subject")
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.build())
|
||||
.build();
|
||||
AppSearchSchema schema2 =
|
||||
new AppSearchSchema.Builder("Email")
|
||||
.addProperty(
|
||||
new PropertyConfig.Builder("subject")
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.build())
|
||||
.build();
|
||||
assertThat(schema1).isEqualTo(schema2);
|
||||
assertThat(schema1.hashCode()).isEqualTo(schema2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals_differentOrder() {
|
||||
AppSearchSchema schema1 =
|
||||
new AppSearchSchema.Builder("Email")
|
||||
.addProperty(
|
||||
new PropertyConfig.Builder("subject")
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.build())
|
||||
.build();
|
||||
AppSearchSchema schema2 =
|
||||
new AppSearchSchema.Builder("Email")
|
||||
.addProperty(
|
||||
new PropertyConfig.Builder("subject")
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
||||
.build())
|
||||
.build();
|
||||
assertThat(schema1).isEqualTo(schema2);
|
||||
assertThat(schema1.hashCode()).isEqualTo(schema2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals_failure() {
|
||||
AppSearchSchema schema1 =
|
||||
new AppSearchSchema.Builder("Email")
|
||||
.addProperty(
|
||||
new PropertyConfig.Builder("subject")
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.build())
|
||||
.build();
|
||||
AppSearchSchema schema2 =
|
||||
new AppSearchSchema.Builder("Email")
|
||||
.addProperty(
|
||||
new PropertyConfig.Builder("subject")
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
||||
.setIndexingType(
|
||||
PropertyConfig
|
||||
.INDEXING_TYPE_EXACT_TERMS) // Different
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.build())
|
||||
.build();
|
||||
assertThat(schema1).isNotEqualTo(schema2);
|
||||
assertThat(schema1.hashCode()).isNotEqualTo(schema2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals_failure_differentOrder() {
|
||||
AppSearchSchema schema1 =
|
||||
new AppSearchSchema.Builder("Email")
|
||||
.addProperty(
|
||||
new PropertyConfig.Builder("subject")
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.build())
|
||||
.addProperty(
|
||||
new PropertyConfig.Builder("body")
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.build())
|
||||
.build();
|
||||
// Order of 'body' and 'subject' has been switched
|
||||
AppSearchSchema schema2 =
|
||||
new AppSearchSchema.Builder("Email")
|
||||
.addProperty(
|
||||
new PropertyConfig.Builder("body")
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.build())
|
||||
.addProperty(
|
||||
new PropertyConfig.Builder("subject")
|
||||
.setDataType(PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
||||
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
||||
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
||||
.build())
|
||||
.build();
|
||||
assertThat(schema1).isNotEqualTo(schema2);
|
||||
assertThat(schema1.hashCode()).isNotEqualTo(schema2.hashCode());
|
||||
}
|
||||
}
|
||||
|
||||
254
core/tests/coretests/src/android/app/appsearch/external/util/BundleUtilTest.java
vendored
Normal file
254
core/tests/coretests/src/android/app/appsearch/external/util/BundleUtilTest.java
vendored
Normal file
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.app.appsearch.util;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelUuid;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Size;
|
||||
import android.util.SizeF;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
public class BundleUtilTest {
|
||||
@Test
|
||||
public void testDeepEquals_self() {
|
||||
Bundle one = new Bundle();
|
||||
one.putString("a", "a");
|
||||
assertThat(BundleUtil.deepEquals(one, one)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeepEquals_simple() {
|
||||
Bundle one = new Bundle();
|
||||
one.putString("a", "a");
|
||||
|
||||
Bundle two = new Bundle();
|
||||
two.putString("a", "a");
|
||||
|
||||
assertThat(one).isNotEqualTo(two);
|
||||
assertThat(BundleUtil.deepEquals(one, two)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeepEquals_keyMismatch() {
|
||||
Bundle one = new Bundle();
|
||||
one.putString("a", "a");
|
||||
|
||||
Bundle two = new Bundle();
|
||||
two.putString("a", "a");
|
||||
two.putString("b", "b");
|
||||
assertThat(BundleUtil.deepEquals(one, two)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeepEquals_thorough_equal() {
|
||||
Bundle[] inputs = new Bundle[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
inputs[i] = createThoroughBundle();
|
||||
}
|
||||
assertThat(inputs[0]).isNotEqualTo(inputs[1]);
|
||||
assertThat(BundleUtil.deepEquals(inputs[0], inputs[1])).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeepEquals_thorough_notEqual() {
|
||||
Bundle[] inputs = new Bundle[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
Bundle b = createThoroughBundle();
|
||||
// Create a difference
|
||||
assertThat(b.containsKey("doubleArray")).isTrue();
|
||||
b.putDoubleArray("doubleArray", new double[] {18., i});
|
||||
inputs[i] = b;
|
||||
}
|
||||
assertThat(inputs[0]).isNotEqualTo(inputs[1]);
|
||||
assertThat(BundleUtil.deepEquals(inputs[0], inputs[1])).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeepEquals_nestedNotEquals() {
|
||||
Bundle one = new Bundle();
|
||||
one.putString("a", "a");
|
||||
Bundle two = new Bundle();
|
||||
two.putBundle("b", one);
|
||||
Bundle twoClone = new Bundle();
|
||||
twoClone.putBundle("b", one);
|
||||
Bundle three = new Bundle();
|
||||
three.putBundle("b", two);
|
||||
|
||||
ArrayList<Bundle> listOne = new ArrayList<>(ImmutableList.of(one, two, three));
|
||||
ArrayList<Bundle> listOneClone = new ArrayList<>(ImmutableList.of(one, twoClone, three));
|
||||
ArrayList<Bundle> listTwo = new ArrayList<>(ImmutableList.of(one, three, two));
|
||||
Bundle b1 = new Bundle();
|
||||
b1.putParcelableArrayList("key", listOne);
|
||||
Bundle b1Clone = new Bundle();
|
||||
b1Clone.putParcelableArrayList("key", listOneClone);
|
||||
Bundle b2 = new Bundle();
|
||||
b2.putParcelableArrayList("key", listTwo);
|
||||
|
||||
assertThat(b1).isNotEqualTo(b1Clone);
|
||||
assertThat(BundleUtil.deepEquals(b1, b1Clone)).isTrue();
|
||||
assertThat(BundleUtil.deepEquals(b1, b2)).isFalse();
|
||||
assertThat(BundleUtil.deepEquals(b1Clone, b2)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeepEquals_sparseArray() {
|
||||
Parcelable parcelable1 = new ParcelUuid(UUID.randomUUID());
|
||||
Parcelable parcelable2 = new ParcelUuid(UUID.randomUUID());
|
||||
Parcelable parcelable3 = new ParcelUuid(UUID.randomUUID());
|
||||
|
||||
SparseArray<Parcelable> array1 = new SparseArray<>();
|
||||
array1.put(1, parcelable1);
|
||||
array1.put(10, parcelable2);
|
||||
|
||||
SparseArray<Parcelable> array1Clone = new SparseArray<>();
|
||||
array1Clone.put(1, parcelable1);
|
||||
array1Clone.put(10, parcelable2);
|
||||
|
||||
SparseArray<Parcelable> array2 = new SparseArray<>();
|
||||
array2.put(1, parcelable1);
|
||||
array2.put(10, parcelable3); // Different
|
||||
|
||||
Bundle b1 = new Bundle();
|
||||
b1.putSparseParcelableArray("array1", array1);
|
||||
Bundle b1Clone = new Bundle();
|
||||
b1Clone.putSparseParcelableArray("array1", array1Clone);
|
||||
Bundle b2 = new Bundle();
|
||||
b2.putSparseParcelableArray("array1", array2);
|
||||
|
||||
assertThat(b1).isNotEqualTo(b1Clone);
|
||||
assertThat(BundleUtil.deepEquals(b1, b1Clone)).isTrue();
|
||||
assertThat(BundleUtil.deepEquals(b1, b2)).isFalse();
|
||||
assertThat(BundleUtil.deepEquals(b1Clone, b2)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeepHashCode_same() {
|
||||
Bundle[] inputs = new Bundle[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
inputs[i] = createThoroughBundle();
|
||||
}
|
||||
assertThat(BundleUtil.deepHashCode(inputs[0]))
|
||||
.isEqualTo(BundleUtil.deepHashCode(inputs[1]));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeepHashCode_different() {
|
||||
Bundle[] inputs = new Bundle[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
Bundle b = createThoroughBundle();
|
||||
// Create a difference
|
||||
assertThat(b.containsKey("doubleArray")).isTrue();
|
||||
b.putDoubleArray("doubleArray", new double[] {18., i});
|
||||
inputs[i] = b;
|
||||
}
|
||||
assertThat(BundleUtil.deepHashCode(inputs[0]))
|
||||
.isNotEqualTo(BundleUtil.deepHashCode(inputs[1]));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHashCode_sparseArray() {
|
||||
Parcelable parcelable1 = new ParcelUuid(UUID.randomUUID());
|
||||
Parcelable parcelable2 = new ParcelUuid(UUID.randomUUID());
|
||||
Parcelable parcelable3 = new ParcelUuid(UUID.randomUUID());
|
||||
|
||||
SparseArray<Parcelable> array1 = new SparseArray<>();
|
||||
array1.put(1, parcelable1);
|
||||
array1.put(10, parcelable2);
|
||||
|
||||
SparseArray<Parcelable> array1Clone = new SparseArray<>();
|
||||
array1Clone.put(1, parcelable1);
|
||||
array1Clone.put(10, parcelable2);
|
||||
|
||||
SparseArray<Parcelable> array2 = new SparseArray<>();
|
||||
array2.put(1, parcelable1);
|
||||
array2.put(10, parcelable3); // Different
|
||||
|
||||
Bundle b1 = new Bundle();
|
||||
b1.putSparseParcelableArray("array1", array1);
|
||||
Bundle b1Clone = new Bundle();
|
||||
b1Clone.putSparseParcelableArray("array1", array1Clone);
|
||||
Bundle b2 = new Bundle();
|
||||
b2.putSparseParcelableArray("array1", array2);
|
||||
|
||||
assertThat(b1.hashCode()).isNotEqualTo(b1Clone.hashCode());
|
||||
assertThat(BundleUtil.deepHashCode(b1)).isEqualTo(BundleUtil.deepHashCode(b1Clone));
|
||||
assertThat(BundleUtil.deepHashCode(b1)).isNotEqualTo(BundleUtil.deepHashCode(b2));
|
||||
}
|
||||
|
||||
private static Bundle createThoroughBundle() {
|
||||
Bundle toy1 = new Bundle();
|
||||
toy1.putString("a", "a");
|
||||
Bundle toy2 = new Bundle();
|
||||
toy2.putInt("b", 2);
|
||||
|
||||
Bundle b = new Bundle();
|
||||
// BaseBundle stuff
|
||||
b.putBoolean("boolean", true);
|
||||
b.putByte("byte", (byte) 1);
|
||||
b.putChar("char", 'a');
|
||||
b.putShort("short", (short) 2);
|
||||
b.putInt("int", 3);
|
||||
b.putLong("long", 4L);
|
||||
b.putFloat("float", 5f);
|
||||
b.putDouble("double", 6f);
|
||||
b.putString("string", "b");
|
||||
b.putCharSequence("charSequence", "c");
|
||||
b.putIntegerArrayList("integerArrayList", new ArrayList<>(ImmutableList.of(7, 8)));
|
||||
b.putStringArrayList("stringArrayList", new ArrayList<>(ImmutableList.of("d", "e")));
|
||||
b.putCharSequenceArrayList(
|
||||
"charSequenceArrayList", new ArrayList<>(ImmutableList.of("f", "g")));
|
||||
b.putSerializable("serializable", new BigDecimal(9));
|
||||
b.putBooleanArray("booleanArray", new boolean[] {true, false, true});
|
||||
b.putByteArray("byteArray", new byte[] {(byte) 10, (byte) 11});
|
||||
b.putShortArray("shortArray", new short[] {(short) 12, (short) 13});
|
||||
b.putCharArray("charArray", new char[] {'h', 'i'});
|
||||
b.putLongArray("longArray", new long[] {14L, 15L});
|
||||
b.putFloatArray("floatArray", new float[] {16f, 17f});
|
||||
b.putDoubleArray("doubleArray", new double[] {18., 19.});
|
||||
b.putStringArray("stringArray", new String[] {"j", "k"});
|
||||
b.putCharSequenceArray("charSequenceArrayList", new CharSequence[] {"l", "m"});
|
||||
|
||||
// Bundle stuff
|
||||
b.putParcelable("parcelable", toy1);
|
||||
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
|
||||
b.putSize("size", new Size(20, 21));
|
||||
b.putSizeF("sizeF", new SizeF(22f, 23f));
|
||||
}
|
||||
b.putParcelableArray("parcelableArray", new Parcelable[] {toy1, toy2});
|
||||
b.putParcelableArrayList(
|
||||
"parcelableArrayList", new ArrayList<>(ImmutableList.of(toy1, toy2)));
|
||||
SparseArray<Parcelable> sparseArray = new SparseArray<>();
|
||||
sparseArray.put(24, toy1);
|
||||
sparseArray.put(1025, toy2);
|
||||
b.putSparseParcelableArray("sparceParcelableArray", sparseArray);
|
||||
b.putBundle("bundle", toy1);
|
||||
|
||||
return b;
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import com.android.server.appsearch.proto.SearchSpecProto;
|
||||
import com.android.server.appsearch.proto.StringIndexingConfig;
|
||||
import com.android.server.appsearch.proto.TermMatchType;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import org.junit.Before;
|
||||
@@ -46,9 +47,7 @@ import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class AppSearchImplTest {
|
||||
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
|
||||
@@ -66,14 +65,15 @@ public class AppSearchImplTest {
|
||||
+ VisibilityStore.SCHEMA_TYPE)
|
||||
.addProperty(
|
||||
new AppSearchSchema.PropertyConfig.Builder(
|
||||
VisibilityStore.PLATFORM_HIDDEN_PROPERTY)
|
||||
VisibilityStore.NOT_PLATFORM_SURFACEABLE_PROPERTY)
|
||||
.setDataType(
|
||||
AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
|
||||
.setCardinality(
|
||||
AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
|
||||
.build())
|
||||
.build();
|
||||
mVisibilitySchemaProto = SchemaToProtoConverter.convert(visibilityAppSearchSchema);
|
||||
mVisibilitySchemaProto =
|
||||
SchemaToProtoConverter.toSchemaTypeConfigProto(visibilityAppSearchSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -340,9 +340,13 @@ public class AppSearchImplTest {
|
||||
@Test
|
||||
public void testOptimize() throws Exception {
|
||||
// Insert schema
|
||||
Set<AppSearchSchema> schemas =
|
||||
Collections.singleton(new AppSearchSchema.Builder("type").build());
|
||||
mAppSearchImpl.setSchema("database", schemas, /*forceOverride=*/ false);
|
||||
List<AppSearchSchema> schemas =
|
||||
Collections.singletonList(new AppSearchSchema.Builder("type").build());
|
||||
mAppSearchImpl.setSchema(
|
||||
"database",
|
||||
schemas,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*forceOverride=*/ false);
|
||||
|
||||
// Insert enough documents.
|
||||
for (int i = 0;
|
||||
@@ -351,7 +355,7 @@ public class AppSearchImplTest {
|
||||
+ AppSearchImpl.CHECK_OPTIMIZE_INTERVAL;
|
||||
i++) {
|
||||
GenericDocument document =
|
||||
new GenericDocument.Builder("uri" + i, "type")
|
||||
new GenericDocument.Builder<>("uri" + i, "type")
|
||||
.setNamespace("namespace")
|
||||
.build();
|
||||
mAppSearchImpl.putDocument("database", document);
|
||||
@@ -392,13 +396,17 @@ public class AppSearchImplTest {
|
||||
SearchSpecProto.Builder searchSpecProto = SearchSpecProto.newBuilder().setQuery("");
|
||||
|
||||
// Insert schema
|
||||
Set<AppSearchSchema> schemas =
|
||||
Collections.singleton(new AppSearchSchema.Builder("type").build());
|
||||
mAppSearchImpl.setSchema("database", schemas, /*forceOverride=*/ false);
|
||||
List<AppSearchSchema> schemas =
|
||||
Collections.singletonList(new AppSearchSchema.Builder("type").build());
|
||||
mAppSearchImpl.setSchema(
|
||||
"database",
|
||||
schemas,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*forceOverride=*/ false);
|
||||
|
||||
// Insert document
|
||||
GenericDocument document =
|
||||
new GenericDocument.Builder("uri", "type").setNamespace("namespace").build();
|
||||
new GenericDocument.Builder<>("uri", "type").setNamespace("namespace").build();
|
||||
mAppSearchImpl.putDocument("database", document);
|
||||
|
||||
// Rewrite SearchSpec
|
||||
@@ -413,20 +421,28 @@ public class AppSearchImplTest {
|
||||
SearchSpecProto.Builder searchSpecProto = SearchSpecProto.newBuilder().setQuery("");
|
||||
|
||||
// Insert schema
|
||||
Set<AppSearchSchema> schemas =
|
||||
Set.of(
|
||||
List<AppSearchSchema> schemas =
|
||||
ImmutableList.of(
|
||||
new AppSearchSchema.Builder("typeA").build(),
|
||||
new AppSearchSchema.Builder("typeB").build());
|
||||
mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/ false);
|
||||
mAppSearchImpl.setSchema("database2", schemas, /*forceOverride=*/ false);
|
||||
mAppSearchImpl.setSchema(
|
||||
"database1",
|
||||
schemas,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*forceOverride=*/ false);
|
||||
mAppSearchImpl.setSchema(
|
||||
"database2",
|
||||
schemas,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*forceOverride=*/ false);
|
||||
|
||||
// Insert documents
|
||||
GenericDocument document1 =
|
||||
new GenericDocument.Builder("uri", "typeA").setNamespace("namespace").build();
|
||||
new GenericDocument.Builder<>("uri", "typeA").setNamespace("namespace").build();
|
||||
mAppSearchImpl.putDocument("database1", document1);
|
||||
|
||||
GenericDocument document2 =
|
||||
new GenericDocument.Builder("uri", "typeB").setNamespace("namespace").build();
|
||||
new GenericDocument.Builder<>("uri", "typeB").setNamespace("namespace").build();
|
||||
mAppSearchImpl.putDocument("database2", document2);
|
||||
|
||||
// Rewrite SearchSpec
|
||||
@@ -477,10 +493,14 @@ public class AppSearchImplTest {
|
||||
|
||||
@Test
|
||||
public void testSetSchema() throws Exception {
|
||||
Set<AppSearchSchema> schemas =
|
||||
Collections.singleton(new AppSearchSchema.Builder("Email").build());
|
||||
List<AppSearchSchema> schemas =
|
||||
Collections.singletonList(new AppSearchSchema.Builder("Email").build());
|
||||
// Set schema Email to AppSearch database1
|
||||
mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/ false);
|
||||
mAppSearchImpl.setSchema(
|
||||
"database1",
|
||||
schemas,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*forceOverride=*/ false);
|
||||
|
||||
// Create expected schemaType proto.
|
||||
SchemaProto expectedProto =
|
||||
@@ -500,35 +520,47 @@ public class AppSearchImplTest {
|
||||
public void testSetSchema_existingSchemaRetainsVisibilitySetting() throws Exception {
|
||||
mAppSearchImpl.setSchema(
|
||||
"database",
|
||||
Collections.singleton(new AppSearchSchema.Builder("schema1").build()),
|
||||
Collections.singletonList(new AppSearchSchema.Builder("schema1").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"),
|
||||
/*forceOverride=*/ false);
|
||||
mAppSearchImpl.setVisibility("database", Set.of("schema1"));
|
||||
|
||||
// "schema1" is platform hidden now
|
||||
assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.getSchemasNotPlatformSurfaceable("database"))
|
||||
.containsExactly("database/schema1");
|
||||
|
||||
// Add a new schema, and include the already-existing "schema1"
|
||||
mAppSearchImpl.setSchema(
|
||||
"database",
|
||||
Set.of(
|
||||
ImmutableList.of(
|
||||
new AppSearchSchema.Builder("schema1").build(),
|
||||
new AppSearchSchema.Builder("schema2").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"),
|
||||
/*forceOverride=*/ false);
|
||||
|
||||
// Check that "schema1" is still platform hidden, but "schema2" is the default platform
|
||||
// visible.
|
||||
assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.getSchemasNotPlatformSurfaceable("database"))
|
||||
.containsExactly("database/schema1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveSchema() throws Exception {
|
||||
Set<AppSearchSchema> schemas = new HashSet<>();
|
||||
schemas.add(new AppSearchSchema.Builder("Email").build());
|
||||
schemas.add(new AppSearchSchema.Builder("Document").build());
|
||||
List<AppSearchSchema> schemas =
|
||||
ImmutableList.of(
|
||||
new AppSearchSchema.Builder("Email").build(),
|
||||
new AppSearchSchema.Builder("Document").build());
|
||||
// Set schema Email and Document to AppSearch database1
|
||||
mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/ false);
|
||||
mAppSearchImpl.setSchema(
|
||||
"database1",
|
||||
schemas,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*forceOverride=*/ false);
|
||||
|
||||
// Create expected schemaType proto.
|
||||
SchemaProto expectedProto =
|
||||
@@ -547,20 +579,27 @@ public class AppSearchImplTest {
|
||||
assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
|
||||
.containsExactlyElementsIn(expectedTypes);
|
||||
|
||||
final Set<AppSearchSchema> finalSchemas =
|
||||
Collections.singleton(new AppSearchSchema.Builder("Email").build());
|
||||
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(
|
||||
"database1", finalSchemas, /*forceOverride=*/ false));
|
||||
"database1",
|
||||
finalSchemas,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*forceOverride=*/ false));
|
||||
assertThat(e).hasMessageThat().contains("Schema is incompatible");
|
||||
assertThat(e).hasMessageThat().contains("Deleted types: [database1/Document]");
|
||||
|
||||
// ForceOverride to delete.
|
||||
mAppSearchImpl.setSchema("database1", finalSchemas, /*forceOverride=*/ true);
|
||||
mAppSearchImpl.setSchema(
|
||||
"database1",
|
||||
finalSchemas,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*forceOverride=*/ true);
|
||||
|
||||
// Check Document schema is removed.
|
||||
expectedProto =
|
||||
@@ -579,13 +618,22 @@ public class AppSearchImplTest {
|
||||
@Test
|
||||
public void testRemoveSchema_differentDataBase() throws Exception {
|
||||
// Create schemas
|
||||
Set<AppSearchSchema> schemas = new HashSet<>();
|
||||
schemas.add(new AppSearchSchema.Builder("Email").build());
|
||||
schemas.add(new AppSearchSchema.Builder("Document").build());
|
||||
List<AppSearchSchema> schemas =
|
||||
ImmutableList.of(
|
||||
new AppSearchSchema.Builder("Email").build(),
|
||||
new AppSearchSchema.Builder("Document").build());
|
||||
|
||||
// Set schema Email and Document to AppSearch database1 and 2
|
||||
mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/ false);
|
||||
mAppSearchImpl.setSchema("database2", schemas, /*forceOverride=*/ false);
|
||||
mAppSearchImpl.setSchema(
|
||||
"database1",
|
||||
schemas,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*forceOverride=*/ false);
|
||||
mAppSearchImpl.setSchema(
|
||||
"database2",
|
||||
schemas,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*forceOverride=*/ false);
|
||||
|
||||
// Create expected schemaType proto.
|
||||
SchemaProto expectedProto =
|
||||
@@ -610,8 +658,12 @@ public class AppSearchImplTest {
|
||||
.containsExactlyElementsIn(expectedTypes);
|
||||
|
||||
// Save only Email to database1 this time.
|
||||
schemas = Collections.singleton(new AppSearchSchema.Builder("Email").build());
|
||||
mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/ true);
|
||||
schemas = Collections.singletonList(new AppSearchSchema.Builder("Email").build());
|
||||
mAppSearchImpl.setSchema(
|
||||
"database1",
|
||||
schemas,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*forceOverride=*/ true);
|
||||
|
||||
// Create expected schemaType list, database 1 should only contain Email but database 2
|
||||
// remains in same.
|
||||
@@ -638,68 +690,73 @@ public class AppSearchImplTest {
|
||||
public void testRemoveSchema_removedFromVisibilityStore() throws Exception {
|
||||
mAppSearchImpl.setSchema(
|
||||
"database",
|
||||
Collections.singleton(new AppSearchSchema.Builder("schema1").build()),
|
||||
Collections.singletonList(new AppSearchSchema.Builder("schema1").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"),
|
||||
/*forceOverride=*/ false);
|
||||
mAppSearchImpl.setVisibility("database", Set.of("schema1"));
|
||||
|
||||
// "schema1" is platform hidden now
|
||||
assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.getSchemasNotPlatformSurfaceable("database"))
|
||||
.containsExactly("database/schema1");
|
||||
|
||||
// Remove "schema1" by force overriding
|
||||
mAppSearchImpl.setSchema("database", Collections.emptySet(), /*forceOverride=*/ true);
|
||||
mAppSearchImpl.setSchema(
|
||||
"database",
|
||||
Collections.emptyList(),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*forceOverride=*/ true);
|
||||
|
||||
// Check that "schema1" is no longer considered platform hidden
|
||||
assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.getSchemasNotPlatformSurfaceable("database"))
|
||||
.isEmpty();
|
||||
|
||||
// Add "schema1" back, it gets default visibility settings which means it's not platform
|
||||
// hidden.
|
||||
mAppSearchImpl.setSchema(
|
||||
"database",
|
||||
Collections.singleton(new AppSearchSchema.Builder("schema1").build()),
|
||||
Collections.singletonList(new AppSearchSchema.Builder("schema1").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*forceOverride=*/ false);
|
||||
assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.getSchemasNotPlatformSurfaceable("database"))
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetVisibility_defaultPlatformVisible() throws Exception {
|
||||
public void testSetSchema_defaultPlatformVisible() throws Exception {
|
||||
mAppSearchImpl.setSchema(
|
||||
"database",
|
||||
Collections.singleton(new AppSearchSchema.Builder("Schema").build()),
|
||||
Collections.singletonList(new AppSearchSchema.Builder("Schema").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*forceOverride=*/ false);
|
||||
assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.getSchemasNotPlatformSurfaceable("database"))
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetVisibility_platformHidden() throws Exception {
|
||||
public void testSetSchema_platformHidden() throws Exception {
|
||||
mAppSearchImpl.setSchema(
|
||||
"database",
|
||||
Collections.singleton(new AppSearchSchema.Builder("Schema").build()),
|
||||
Collections.singletonList(new AppSearchSchema.Builder("Schema").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.singletonList("Schema"),
|
||||
/*forceOverride=*/ false);
|
||||
mAppSearchImpl.setVisibility("database", Set.of("Schema"));
|
||||
assertThat(mAppSearchImpl.getVisibilityStoreLocked().getPlatformHiddenSchemas("database"))
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.getSchemasNotPlatformSurfaceable("database"))
|
||||
.containsExactly("database/Schema");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetVisibility_unknownSchema() throws Exception {
|
||||
mAppSearchImpl.setSchema(
|
||||
"database",
|
||||
Collections.singleton(new AppSearchSchema.Builder("Schema").build()),
|
||||
/*forceOverride=*/ false);
|
||||
|
||||
// We'll throw an exception if a client tries to set visibility on a schema we don't know
|
||||
// about.
|
||||
AppSearchException e =
|
||||
expectThrows(
|
||||
AppSearchException.class,
|
||||
() -> mAppSearchImpl.setVisibility("database", Set.of("UnknownSchema")));
|
||||
assertThat(e).hasMessageThat().contains("Unknown schema(s)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasSchemaType() throws Exception {
|
||||
// Nothing exists yet
|
||||
@@ -707,7 +764,8 @@ public class AppSearchImplTest {
|
||||
|
||||
mAppSearchImpl.setSchema(
|
||||
"database",
|
||||
Collections.singleton(new AppSearchSchema.Builder("Schema").build()),
|
||||
Collections.singletonList(new AppSearchSchema.Builder("Schema").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*forceOverride=*/ false);
|
||||
assertThat(mAppSearchImpl.hasSchemaTypeLocked("database", "Schema")).isTrue();
|
||||
|
||||
@@ -723,7 +781,8 @@ public class AppSearchImplTest {
|
||||
// Has database1
|
||||
mAppSearchImpl.setSchema(
|
||||
"database1",
|
||||
Collections.singleton(new AppSearchSchema.Builder("schema").build()),
|
||||
Collections.singletonList(new AppSearchSchema.Builder("schema").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*forceOverride=*/ false);
|
||||
assertThat(mAppSearchImpl.getDatabasesLocked())
|
||||
.containsExactly(VisibilityStore.DATABASE_NAME, "database1");
|
||||
@@ -731,7 +790,8 @@ public class AppSearchImplTest {
|
||||
// Has both databases
|
||||
mAppSearchImpl.setSchema(
|
||||
"database2",
|
||||
Collections.singleton(new AppSearchSchema.Builder("schema").build()),
|
||||
Collections.singletonList(new AppSearchSchema.Builder("schema").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*forceOverride=*/ false);
|
||||
assertThat(mAppSearchImpl.getDatabasesLocked())
|
||||
.containsExactly(VisibilityStore.DATABASE_NAME, "database1", "database2");
|
||||
|
||||
@@ -41,34 +41,19 @@ public class VisibilityStoreTest {
|
||||
@Test
|
||||
public void testSetVisibility() throws Exception {
|
||||
mVisibilityStore.setVisibility(
|
||||
"database", /*platformHiddenSchemas=*/ Set.of("schema1", "schema2"));
|
||||
assertThat(mVisibilityStore.getPlatformHiddenSchemas("database"))
|
||||
"database", /*schemasNotPlatformSurfaceable=*/ Set.of("schema1", "schema2"));
|
||||
assertThat(mVisibilityStore.getSchemasNotPlatformSurfaceable("database"))
|
||||
.containsExactly("schema1", "schema2");
|
||||
|
||||
// New .setVisibility() call completely overrides previous visibility settings. So
|
||||
// "schema1" isn't preserved.
|
||||
mVisibilityStore.setVisibility(
|
||||
"database", /*platformHiddenSchemas=*/ Set.of("schema1", "schema3"));
|
||||
assertThat(mVisibilityStore.getPlatformHiddenSchemas("database"))
|
||||
"database", /*schemasNotPlatformSurfaceable=*/ Set.of("schema1", "schema3"));
|
||||
assertThat(mVisibilityStore.getSchemasNotPlatformSurfaceable("database"))
|
||||
.containsExactly("schema1", "schema3");
|
||||
|
||||
mVisibilityStore.setVisibility(
|
||||
"database", /*platformHiddenSchemas=*/ Collections.emptySet());
|
||||
assertThat(mVisibilityStore.getPlatformHiddenSchemas("database")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveSchemas() throws Exception {
|
||||
mVisibilityStore.setVisibility(
|
||||
"database", /*platformHiddenSchemas=*/ Set.of("schema1", "schema2"));
|
||||
|
||||
// Removed just schema1
|
||||
mVisibilityStore.updateSchemas("database", /*schemasToRemove=*/ Set.of("schema1"));
|
||||
assertThat(mVisibilityStore.getPlatformHiddenSchemas("database"))
|
||||
.containsExactly("schema2");
|
||||
|
||||
// Removed everything now
|
||||
mVisibilityStore.updateSchemas("database", /*schemasToRemove=*/ Set.of("schema2"));
|
||||
assertThat(mVisibilityStore.getPlatformHiddenSchemas("database")).isEmpty();
|
||||
"database", /*schemasNotPlatformSurfaceable=*/ Collections.emptySet());
|
||||
assertThat(mVisibilityStore.getSchemasNotPlatformSurfaceable("database")).isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,20 +94,24 @@ public class GenericDocumentToProtoConverterTest {
|
||||
PropertyProto.newBuilder()
|
||||
.setName("documentKey1")
|
||||
.addDocumentValues(
|
||||
GenericDocumentToProtoConverter.convert(DOCUMENT_PROPERTIES_1)));
|
||||
GenericDocumentToProtoConverter.toDocumentProto(
|
||||
DOCUMENT_PROPERTIES_1)));
|
||||
propertyProtoMap.put(
|
||||
"documentKey2",
|
||||
PropertyProto.newBuilder()
|
||||
.setName("documentKey2")
|
||||
.addDocumentValues(
|
||||
GenericDocumentToProtoConverter.convert(DOCUMENT_PROPERTIES_2)));
|
||||
GenericDocumentToProtoConverter.toDocumentProto(
|
||||
DOCUMENT_PROPERTIES_2)));
|
||||
List<String> sortedKey = new ArrayList<>(propertyProtoMap.keySet());
|
||||
Collections.sort(sortedKey);
|
||||
for (String key : sortedKey) {
|
||||
documentProtoBuilder.addProperties(propertyProtoMap.get(key));
|
||||
}
|
||||
DocumentProto documentProto = documentProtoBuilder.build();
|
||||
assertThat(GenericDocumentToProtoConverter.convert(document)).isEqualTo(documentProto);
|
||||
assertThat(document).isEqualTo(GenericDocumentToProtoConverter.convert(documentProto));
|
||||
assertThat(GenericDocumentToProtoConverter.toDocumentProto(document))
|
||||
.isEqualTo(documentProto);
|
||||
assertThat(document)
|
||||
.isEqualTo(GenericDocumentToProtoConverter.toGenericDocument(documentProto));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,10 @@ public class SchemaToProtoConverterTest {
|
||||
TermMatchType.Code.PREFIX)))
|
||||
.build();
|
||||
|
||||
assertThat(SchemaToProtoConverter.convert(emailSchema)).isEqualTo(expectedEmailProto);
|
||||
assertThat(SchemaToProtoConverter.toSchemaTypeConfigProto(emailSchema))
|
||||
.isEqualTo(expectedEmailProto);
|
||||
assertThat(SchemaToProtoConverter.toAppSearchSchema(expectedEmailProto))
|
||||
.isEqualTo(emailSchema);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -151,7 +154,9 @@ public class SchemaToProtoConverterTest {
|
||||
TermMatchType.Code.UNKNOWN)))
|
||||
.build();
|
||||
|
||||
assertThat(SchemaToProtoConverter.convert(musicRecordingSchema))
|
||||
assertThat(SchemaToProtoConverter.toSchemaTypeConfigProto(musicRecordingSchema))
|
||||
.isEqualTo(expectedMusicRecordingProto);
|
||||
assertThat(SchemaToProtoConverter.toAppSearchSchema(expectedMusicRecordingProto))
|
||||
.isEqualTo(musicRecordingSchema);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ public class SnippetTest {
|
||||
|
||||
// Making ResultReader and getting Snippet values.
|
||||
SearchResultPage searchResultPage =
|
||||
SearchResultToProtoConverter.convertToSearchResultPage(searchResultProto);
|
||||
SearchResultToProtoConverter.toSearchResultPage(searchResultProto);
|
||||
for (SearchResult result : searchResultPage.getResults()) {
|
||||
SearchResult.MatchInfo match = result.getMatches().get(0);
|
||||
assertThat(match.getPropertyPath()).isEqualTo(propertyKeyString);
|
||||
@@ -131,7 +131,7 @@ public class SnippetTest {
|
||||
SearchResultProto.newBuilder().addResults(resultProto).build();
|
||||
|
||||
SearchResultPage searchResultPage =
|
||||
SearchResultToProtoConverter.convertToSearchResultPage(searchResultProto);
|
||||
SearchResultToProtoConverter.toSearchResultPage(searchResultProto);
|
||||
for (SearchResult result : searchResultPage.getResults()) {
|
||||
assertThat(result.getMatches()).isEmpty();
|
||||
}
|
||||
@@ -196,7 +196,7 @@ public class SnippetTest {
|
||||
|
||||
// Making ResultReader and getting Snippet values.
|
||||
SearchResultPage searchResultPage =
|
||||
SearchResultToProtoConverter.convertToSearchResultPage(searchResultProto);
|
||||
SearchResultToProtoConverter.toSearchResultPage(searchResultProto);
|
||||
for (SearchResult result : searchResultPage.getResults()) {
|
||||
|
||||
SearchResult.MatchInfo match1 = result.getMatches().get(0);
|
||||
|
||||
Reference in New Issue
Block a user