Merge "Update AppSearch code from Jetpack."

This commit is contained in:
Alexander Dorokhine
2020-12-07 06:24:53 +00:00
committed by Android (Google) Code Review
25 changed files with 1159 additions and 403 deletions

View File

@@ -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);

View File

@@ -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}.
*

View File

@@ -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));

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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")

View File

@@ -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();
}
}

View File

@@ -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 {

View File

@@ -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();
}
}

View File

@@ -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<>();

View File

@@ -1 +1 @@
I2decd83fab4c4d58fe38c9970f804046479c942c
If5fd2bd705d5507d044706701a94b2e1496ef1df

View 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");
}
}

View File

@@ -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());
}
}

View 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;
}
}

View File

@@ -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");

View File

@@ -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();
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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);