Merge "Pull upstream changes from AndroidX."
This commit is contained in:
@@ -267,14 +267,14 @@ public class AppSearchManager {
|
||||
searchResultsFuture.completeExceptionally(e);
|
||||
}
|
||||
|
||||
// Translate the list of Bundle into a list of SearchResult
|
||||
AppSearchResult<SearchResults> searchResultsResult = getFutureOrThrow(searchResultsFuture);
|
||||
if (!searchResultsResult.isSuccess()) {
|
||||
return AppSearchResult.newFailedResult(
|
||||
searchResultsResult.getResultCode(), searchResultsResult.getErrorMessage());
|
||||
// Translate the Bundle into a searchResultPage.
|
||||
AppSearchResult<Bundle> bundleResult = getFutureOrThrow(searchResultsFuture);
|
||||
if (!bundleResult.isSuccess()) {
|
||||
return AppSearchResult.newFailedResult(bundleResult.getResultCode(),
|
||||
bundleResult.getErrorMessage());
|
||||
}
|
||||
SearchResults searchResults = searchResultsResult.getResultValue();
|
||||
return AppSearchResult.newSuccessfulResult(searchResults.mResults);
|
||||
SearchResultPage searchResultPage = new SearchResultPage(bundleResult.getResultValue());
|
||||
return AppSearchResult.newSuccessfulResult(searchResultPage.getResults());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,39 +294,7 @@ public class AppSearchManager {
|
||||
List<String> uris = new ArrayList<>(request.getUris());
|
||||
AndroidFuture<AppSearchBatchResult> future = new AndroidFuture<>();
|
||||
try {
|
||||
mService.delete(DEFAULT_DATABASE, request.getNamespace(), uris, future);
|
||||
} catch (RemoteException e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
return getFutureOrThrow(future);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes {@link android.app.appsearch.GenericDocument}s by schema type.
|
||||
*
|
||||
* <p>You should not call this method directly; instead, use the
|
||||
* {@code AppSearch#deleteByType()} API provided by JetPack.
|
||||
*
|
||||
* @param schemaTypes Schema types whose documents to delete.
|
||||
* @return An {@link AppSearchBatchResult} mapping each schema type to a {@code null} success if
|
||||
* deletion was successful, to a {@code null} failure if the type did not exist, or to a
|
||||
* {@code throwable} failure if deletion failed for another reason.
|
||||
*/
|
||||
public AppSearchBatchResult<String, Void> deleteByTypes(@NonNull List<String> schemaTypes) {
|
||||
AndroidFuture<AppSearchBatchResult> future = new AndroidFuture<>();
|
||||
try {
|
||||
mService.deleteByTypes(DEFAULT_DATABASE, schemaTypes, future);
|
||||
} catch (RemoteException e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
return getFutureOrThrow(future);
|
||||
}
|
||||
|
||||
/** Deletes all documents owned by the calling app. */
|
||||
public AppSearchResult<Void> deleteAll() {
|
||||
AndroidFuture<AppSearchResult> future = new AndroidFuture<>();
|
||||
try {
|
||||
mService.deleteAll(DEFAULT_DATABASE, future);
|
||||
mService.removeByUri(DEFAULT_DATABASE, request.getNamespace(), uris, future);
|
||||
} catch (RemoteException e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ interface IAppSearchManager {
|
||||
in AndroidFuture<AppSearchResult> callback);
|
||||
|
||||
/**
|
||||
* Deletes documents by URI.
|
||||
* Removes documents by URI.
|
||||
*
|
||||
* @param databaseName The databaseName the document is in.
|
||||
* @param namespace Namespace of the document to remove.
|
||||
@@ -105,36 +105,23 @@ interface IAppSearchManager {
|
||||
* where the keys are document URIs. If a document doesn't exist, it will be reported as a
|
||||
* failure where the {@code throwable} is {@code null}.
|
||||
*/
|
||||
void delete(
|
||||
void removeByUri(
|
||||
in String databaseName,
|
||||
in String namespace,
|
||||
in List<String> uris,
|
||||
in AndroidFuture<AppSearchBatchResult> callback);
|
||||
|
||||
/**
|
||||
* Deletes documents by schema type.
|
||||
* Removes documents by given query.
|
||||
*
|
||||
* @param databaseName The databaseName the document is in.
|
||||
* @param schemaTypes The schema types of the documents to delete
|
||||
* @param callback
|
||||
* {@link AndroidFuture}<{@link AppSearchBatchResult}<{@link String}, {@link Void}>>.
|
||||
* If the call fails to start, {@code callback} will be completed exceptionally. Otherwise,
|
||||
* {@code callback} will be completed with an
|
||||
* {@link AppSearchBatchResult}<{@link String}, {@link Void}>
|
||||
* where the keys are schema types. If a schema type doesn't exist, it will be reported as a
|
||||
* failure where the {@code throwable} is {@code null}.
|
||||
* @param databaseName The databaseName this query for.
|
||||
* @param queryExpression String to search for
|
||||
* @param searchSpecBundle SearchSpec bundle
|
||||
* @param callback {@link AndroidFuture}<{@link AppSearchResult}<{@link SearchResults}>>
|
||||
*/
|
||||
void deleteByTypes(
|
||||
void removeByQuery(
|
||||
in String databaseName,
|
||||
in List<String> schemaTypes,
|
||||
in AndroidFuture<AppSearchBatchResult> callback);
|
||||
|
||||
/**
|
||||
* Deletes all documents belonging to the calling app.
|
||||
*
|
||||
* @param databaseName The databaseName to remove all documents from.
|
||||
* @param callback {@link AndroidFuture}<{@link AppSearchResult}<{@link Void}>>.
|
||||
* Will be completed with the result of the call.
|
||||
*/
|
||||
void deleteAll(in String databaseName, in AndroidFuture<AppSearchResult> callback);
|
||||
in String queryExpression,
|
||||
in Bundle searchSpecBundle,
|
||||
in AndroidFuture<AppSearchResult> callback);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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 android.os.Bundle;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class represents a page of {@link SearchResult}s
|
||||
* @hide
|
||||
*/
|
||||
|
||||
public class SearchResultPage {
|
||||
public static final String RESULTS_FIELD = "results";
|
||||
public static final String NEXT_PAGE_TOKEN_FIELD = "nextPageToken";
|
||||
private final long mNextPageToken;
|
||||
|
||||
@Nullable
|
||||
private List<SearchResult> mResults;
|
||||
|
||||
@NonNull
|
||||
private final Bundle mBundle;
|
||||
|
||||
public SearchResultPage(@NonNull Bundle bundle) {
|
||||
mBundle = Preconditions.checkNotNull(bundle);
|
||||
mNextPageToken = mBundle.getLong(NEXT_PAGE_TOKEN_FIELD);
|
||||
}
|
||||
|
||||
/** Returns the {@link Bundle} of this class. */
|
||||
@NonNull
|
||||
public Bundle getBundle() {
|
||||
return mBundle;
|
||||
}
|
||||
|
||||
/** Returns the Token to get next {@link SearchResultPage}. */
|
||||
public long getNextPageToken() {
|
||||
return mNextPageToken;
|
||||
}
|
||||
|
||||
/** Returns all {@link android.app.appsearch.SearchResult}s of this page */
|
||||
@NonNull
|
||||
public List<SearchResult> getResults() {
|
||||
if (mResults == null) {
|
||||
ArrayList<Bundle> resultBundles = mBundle.getParcelableArrayList(RESULTS_FIELD);
|
||||
if (resultBundles == null) {
|
||||
mResults = Collections.emptyList();
|
||||
} else {
|
||||
mResults = new ArrayList<>(resultBundles.size());
|
||||
for (int i = 0; i < resultBundles.size(); i++) {
|
||||
mResults.add(new SearchResult(resultBundles.get(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return mResults;
|
||||
}
|
||||
}
|
||||
@@ -21,33 +21,23 @@ import android.app.appsearch.AppSearchResult;
|
||||
import android.app.appsearch.AppSearchSchema;
|
||||
import android.app.appsearch.GenericDocument;
|
||||
import android.app.appsearch.IAppSearchManager;
|
||||
import android.app.appsearch.SearchResult;
|
||||
import android.app.appsearch.SearchResults;
|
||||
import android.app.appsearch.SearchResultPage;
|
||||
import android.app.appsearch.SearchSpec;
|
||||
import android.app.appsearch.exceptions.AppSearchException;
|
||||
import android.content.Context;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import com.android.internal.infra.AndroidFuture;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
|
||||
import com.android.server.appsearch.external.localstorage.converter.GenericDocumentToProtoConverter;
|
||||
import com.android.server.appsearch.external.localstorage.converter.SchemaToProtoConverter;
|
||||
import com.android.server.appsearch.external.localstorage.converter.SearchResultToProtoConverter;
|
||||
import com.android.server.appsearch.external.localstorage.converter.SearchSpecToProtoConverter;
|
||||
|
||||
import com.google.android.icing.proto.DocumentProto;
|
||||
import com.google.android.icing.proto.SchemaProto;
|
||||
import com.google.android.icing.proto.SchemaTypeConfigProto;
|
||||
import com.google.android.icing.proto.SearchResultProto;
|
||||
import com.google.android.icing.proto.SearchSpecProto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* TODO(b/142567528): add comments when implement this class
|
||||
@@ -72,21 +62,20 @@ public class AppSearchManagerService extends SystemService {
|
||||
@NonNull List<Bundle> schemaBundles,
|
||||
boolean forceOverride,
|
||||
@NonNull AndroidFuture<AppSearchResult> callback) {
|
||||
Preconditions.checkNotNull(databaseName);
|
||||
Preconditions.checkNotNull(schemaBundles);
|
||||
Preconditions.checkNotNull(callback);
|
||||
int callingUid = Binder.getCallingUidOrThrow();
|
||||
int callingUserId = UserHandle.getUserId(callingUid);
|
||||
final long callingIdentity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
SchemaProto.Builder schemaProtoBuilder = SchemaProto.newBuilder();
|
||||
Set<AppSearchSchema> schemas = new ArraySet<>(schemaBundles.size());
|
||||
for (int i = 0; i < schemaBundles.size(); i++) {
|
||||
AppSearchSchema schema = new AppSearchSchema(schemaBundles.get(i));
|
||||
SchemaTypeConfigProto schemaTypeProto = SchemaToProtoConverter.convert(schema);
|
||||
schemaProtoBuilder.addTypes(schemaTypeProto);
|
||||
schemas.add(new AppSearchSchema(schemaBundles.get(i)));
|
||||
}
|
||||
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
|
||||
databaseName = rewriteDatabaseNameWithUid(databaseName, callingUid);
|
||||
impl.setSchema(databaseName, schemaProtoBuilder.build(), forceOverride);
|
||||
impl.setSchema(databaseName, schemas, forceOverride);
|
||||
callback.complete(AppSearchResult.newSuccessfulResult(/*result=*/ null));
|
||||
} catch (Throwable t) {
|
||||
callback.complete(throwableToFailedResult(t));
|
||||
@@ -100,6 +89,7 @@ public class AppSearchManagerService extends SystemService {
|
||||
@NonNull String databaseName,
|
||||
@NonNull List<Bundle> documentBundles,
|
||||
@NonNull AndroidFuture<AppSearchBatchResult> callback) {
|
||||
Preconditions.checkNotNull(databaseName);
|
||||
Preconditions.checkNotNull(documentBundles);
|
||||
Preconditions.checkNotNull(callback);
|
||||
int callingUid = Binder.getCallingUidOrThrow();
|
||||
@@ -112,9 +102,8 @@ public class AppSearchManagerService extends SystemService {
|
||||
new AppSearchBatchResult.Builder<>();
|
||||
for (int i = 0; i < documentBundles.size(); i++) {
|
||||
GenericDocument document = new GenericDocument(documentBundles.get(i));
|
||||
DocumentProto documentProto = GenericDocumentToProtoConverter.convert(document);
|
||||
try {
|
||||
impl.putDocument(databaseName, documentProto);
|
||||
impl.putDocument(databaseName, document);
|
||||
resultBuilder.setSuccess(document.getUri(), /*result=*/ null);
|
||||
} catch (Throwable t) {
|
||||
resultBuilder.setResult(document.getUri(), throwableToFailedResult(t));
|
||||
@@ -131,6 +120,8 @@ public class AppSearchManagerService extends SystemService {
|
||||
@Override
|
||||
public void getDocuments(@NonNull String databaseName, @NonNull String namespace,
|
||||
@NonNull List<String> uris, @NonNull AndroidFuture<AppSearchBatchResult> callback) {
|
||||
Preconditions.checkNotNull(databaseName);
|
||||
Preconditions.checkNotNull(namespace);
|
||||
Preconditions.checkNotNull(uris);
|
||||
Preconditions.checkNotNull(callback);
|
||||
int callingUid = Binder.getCallingUidOrThrow();
|
||||
@@ -144,16 +135,8 @@ public class AppSearchManagerService extends SystemService {
|
||||
for (int i = 0; i < uris.size(); i++) {
|
||||
String uri = uris.get(i);
|
||||
try {
|
||||
DocumentProto documentProto = impl.getDocument(
|
||||
databaseName, namespace, uri);
|
||||
if (documentProto == null) {
|
||||
resultBuilder.setFailure(
|
||||
uri, AppSearchResult.RESULT_NOT_FOUND, /*errorMessage=*/ null);
|
||||
} else {
|
||||
GenericDocument genericDocument =
|
||||
GenericDocumentToProtoConverter.convert(documentProto);
|
||||
resultBuilder.setSuccess(uri, genericDocument.getBundle());
|
||||
}
|
||||
GenericDocument document = impl.getDocument(databaseName, namespace, uri);
|
||||
resultBuilder.setSuccess(uri, document.getBundle());
|
||||
} catch (Throwable t) {
|
||||
resultBuilder.setResult(uri, throwableToFailedResult(t));
|
||||
}
|
||||
@@ -167,12 +150,14 @@ public class AppSearchManagerService extends SystemService {
|
||||
}
|
||||
|
||||
// TODO(sidchhabra): Do this in a threadpool.
|
||||
// TODO(b/162450968) handle pagination after getNextPage and SearchResults is ready.
|
||||
@Override
|
||||
public void query(
|
||||
@NonNull String databaseName,
|
||||
@NonNull String queryExpression,
|
||||
@NonNull Bundle searchSpecBundle,
|
||||
@NonNull AndroidFuture<AppSearchResult> callback) {
|
||||
Preconditions.checkNotNull(databaseName);
|
||||
Preconditions.checkNotNull(queryExpression);
|
||||
Preconditions.checkNotNull(searchSpecBundle);
|
||||
Preconditions.checkNotNull(callback);
|
||||
@@ -180,29 +165,14 @@ public class AppSearchManagerService extends SystemService {
|
||||
int callingUserId = UserHandle.getUserId(callingUid);
|
||||
final long callingIdentity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
SearchSpec searchSpec = new SearchSpec(searchSpecBundle);
|
||||
SearchSpecProto searchSpecProto =
|
||||
SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
|
||||
searchSpecProto = searchSpecProto.toBuilder()
|
||||
.setQuery(queryExpression).build();
|
||||
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
|
||||
databaseName = rewriteDatabaseNameWithUid(databaseName, callingUid);
|
||||
// TODO(adorokhine): handle pagination
|
||||
SearchResultProto searchResultProto = impl.query(
|
||||
SearchResultPage searchResultPage = impl.query(
|
||||
databaseName,
|
||||
searchSpecProto,
|
||||
SearchSpecToProtoConverter.toResultSpecProto(searchSpec),
|
||||
SearchSpecToProtoConverter.toScoringSpecProto(searchSpec));
|
||||
List<SearchResult> searchResultList =
|
||||
new ArrayList<>(searchResultProto.getResultsCount());
|
||||
for (int i = 0; i < searchResultProto.getResultsCount(); i++) {
|
||||
SearchResult result = SearchResultToProtoConverter.convertSearchResult(
|
||||
searchResultProto.getResults(i));
|
||||
searchResultList.add(result);
|
||||
}
|
||||
SearchResults searchResults =
|
||||
new SearchResults(searchResultList, searchResultProto.getNextPageToken());
|
||||
callback.complete(AppSearchResult.newSuccessfulResult(searchResults));
|
||||
queryExpression,
|
||||
new SearchSpec(searchSpecBundle));
|
||||
callback.complete(
|
||||
AppSearchResult.newSuccessfulResult(searchResultPage.getBundle()));
|
||||
} catch (Throwable t) {
|
||||
callback.complete(throwableToFailedResult(t));
|
||||
} finally {
|
||||
@@ -211,8 +181,10 @@ public class AppSearchManagerService extends SystemService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(@NonNull String databaseName, @NonNull String namespace,
|
||||
public void removeByUri(@NonNull String databaseName, @NonNull String namespace,
|
||||
List<String> uris, AndroidFuture<AppSearchBatchResult> callback) {
|
||||
Preconditions.checkNotNull(databaseName);
|
||||
Preconditions.checkNotNull(namespace);
|
||||
Preconditions.checkNotNull(uris);
|
||||
Preconditions.checkNotNull(callback);
|
||||
int callingUid = Binder.getCallingUidOrThrow();
|
||||
@@ -241,38 +213,14 @@ public class AppSearchManagerService extends SystemService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteByTypes(@NonNull String databaseName,
|
||||
List<String> schemaTypes, AndroidFuture<AppSearchBatchResult> callback) {
|
||||
Preconditions.checkNotNull(schemaTypes);
|
||||
Preconditions.checkNotNull(callback);
|
||||
int callingUid = Binder.getCallingUidOrThrow();
|
||||
int callingUserId = UserHandle.getUserId(callingUid);
|
||||
final long callingIdentity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
|
||||
databaseName = rewriteDatabaseNameWithUid(databaseName, callingUid);
|
||||
AppSearchBatchResult.Builder<String, Void> resultBuilder =
|
||||
new AppSearchBatchResult.Builder<>();
|
||||
for (int i = 0; i < schemaTypes.size(); i++) {
|
||||
String schemaType = schemaTypes.get(i);
|
||||
try {
|
||||
impl.removeByType(databaseName, schemaType);
|
||||
resultBuilder.setSuccess(schemaType, /*result=*/ null);
|
||||
} catch (Throwable t) {
|
||||
resultBuilder.setResult(schemaType, throwableToFailedResult(t));
|
||||
}
|
||||
}
|
||||
callback.complete(resultBuilder.build());
|
||||
} catch (Throwable t) {
|
||||
callback.completeExceptionally(t);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(callingIdentity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAll(@NonNull String databaseName,
|
||||
public void removeByQuery(
|
||||
@NonNull String databaseName,
|
||||
@NonNull String queryExpression,
|
||||
@NonNull Bundle searchSpecBundle,
|
||||
@NonNull AndroidFuture<AppSearchResult> callback) {
|
||||
Preconditions.checkNotNull(databaseName);
|
||||
Preconditions.checkNotNull(queryExpression);
|
||||
Preconditions.checkNotNull(searchSpecBundle);
|
||||
Preconditions.checkNotNull(callback);
|
||||
int callingUid = Binder.getCallingUidOrThrow();
|
||||
int callingUserId = UserHandle.getUserId(callingUid);
|
||||
@@ -280,8 +228,8 @@ public class AppSearchManagerService extends SystemService {
|
||||
try {
|
||||
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
|
||||
databaseName = rewriteDatabaseNameWithUid(databaseName, callingUid);
|
||||
impl.removeAll(databaseName);
|
||||
callback.complete(AppSearchResult.newSuccessfulResult(null));
|
||||
impl.removeByQuery(databaseName, queryExpression, new SearchSpec(searchSpecBundle));
|
||||
callback.complete(AppSearchResult.newSuccessfulResult(/*result= */null));
|
||||
} catch (Throwable t) {
|
||||
callback.complete(throwableToFailedResult(t));
|
||||
} finally {
|
||||
|
||||
@@ -16,21 +16,27 @@
|
||||
|
||||
package com.android.server.appsearch.external.localstorage;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import android.annotation.WorkerThread;
|
||||
import android.app.appsearch.AppSearchResult;
|
||||
import android.app.appsearch.AppSearchSchema;
|
||||
import android.app.appsearch.GenericDocument;
|
||||
import android.app.appsearch.SearchResultPage;
|
||||
import android.app.appsearch.SearchSpec;
|
||||
import android.app.appsearch.exceptions.AppSearchException;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.server.appsearch.external.localstorage.converter.GenericDocumentToProtoConverter;
|
||||
import com.android.server.appsearch.external.localstorage.converter.SchemaToProtoConverter;
|
||||
import com.android.server.appsearch.external.localstorage.converter.SearchResultToProtoConverter;
|
||||
import com.android.server.appsearch.external.localstorage.converter.SearchSpecToProtoConverter;
|
||||
|
||||
import com.google.android.icing.IcingSearchEngine;
|
||||
import com.google.android.icing.proto.DeleteByNamespaceResultProto;
|
||||
import com.google.android.icing.proto.DeleteBySchemaTypeResultProto;
|
||||
import com.google.android.icing.proto.DeleteResultProto;
|
||||
import com.google.android.icing.proto.DocumentProto;
|
||||
import com.google.android.icing.proto.GetAllNamespacesResultProto;
|
||||
@@ -54,8 +60,10 @@ import com.google.android.icing.proto.SetSchemaResultProto;
|
||||
import com.google.android.icing.proto.StatusProto;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
@@ -89,12 +97,14 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
* </ul>
|
||||
*
|
||||
* <p>This class is thread safe.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
|
||||
@WorkerThread
|
||||
public final class AppSearchImpl {
|
||||
private static final String TAG = "AppSearchImpl";
|
||||
private static final char DATABASE_DELIMITER = '/';
|
||||
|
||||
@VisibleForTesting
|
||||
static final int OPTIMIZE_THRESHOLD_DOC_COUNT = 1000;
|
||||
@@ -173,20 +183,27 @@ public final class AppSearchImpl {
|
||||
* <p>This method belongs to mutate group.
|
||||
*
|
||||
* @param databaseName The name of the database where this schema lives.
|
||||
* @param origSchema The schema to set for this app.
|
||||
* @param schemas Schemas to set for this app.
|
||||
* @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 SchemaProto origSchema,
|
||||
public void setSchema(@NonNull String databaseName, @NonNull Set<AppSearchSchema> schemas,
|
||||
boolean forceOverride) throws AppSearchException {
|
||||
SchemaProto schemaProto = getSchemaProto();
|
||||
|
||||
SchemaProto.Builder existingSchemaBuilder = schemaProto.toBuilder();
|
||||
|
||||
SchemaProto.Builder newSchemaBuilder = SchemaProto.newBuilder();
|
||||
for (AppSearchSchema schema : schemas) {
|
||||
SchemaTypeConfigProto schemaTypeProto = SchemaToProtoConverter.convert(schema);
|
||||
newSchemaBuilder.addTypes(schemaTypeProto);
|
||||
}
|
||||
|
||||
// Combine the existing schema (which may have types from other databases) with this
|
||||
// database's new schema. Modifies the existingSchemaBuilder.
|
||||
Set<String> newTypeNames = rewriteSchema(databaseName, existingSchemaBuilder, origSchema);
|
||||
Set<String> newTypeNames = rewriteSchema(databaseName, existingSchemaBuilder,
|
||||
newSchemaBuilder.build());
|
||||
|
||||
SetSchemaResultProto setSchemaResultProto;
|
||||
mReadWriteLock.writeLock().lock();
|
||||
@@ -220,7 +237,7 @@ public final class AppSearchImpl {
|
||||
if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0
|
||||
|| (setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0
|
||||
&& forceOverride)) {
|
||||
// Any existing schemas which is not in origSchema will be deleted, and all
|
||||
// Any existing schemas which is not in 'schemas' will be deleted, and all
|
||||
// documents of these types were also deleted. And so well if we force override
|
||||
// incompatible schemas.
|
||||
checkForOptimize(/* force= */true);
|
||||
@@ -239,10 +256,11 @@ public final class AppSearchImpl {
|
||||
* @param document The document to index.
|
||||
* @throws AppSearchException on IcingSearchEngine error.
|
||||
*/
|
||||
public void putDocument(@NonNull String databaseName, @NonNull DocumentProto document)
|
||||
public void putDocument(@NonNull String databaseName, @NonNull GenericDocument document)
|
||||
throws AppSearchException {
|
||||
DocumentProto.Builder documentBuilder = document.toBuilder();
|
||||
rewriteDocumentTypes(getDatabasePrefix(databaseName), documentBuilder, /*add=*/ true);
|
||||
DocumentProto.Builder documentBuilder = GenericDocumentToProtoConverter.convert(
|
||||
document).toBuilder();
|
||||
addPrefixToDocument(documentBuilder, getDatabasePrefix(databaseName));
|
||||
|
||||
PutResultProto putResultProto;
|
||||
mReadWriteLock.writeLock().lock();
|
||||
@@ -266,11 +284,11 @@ public final class AppSearchImpl {
|
||||
* @param databaseName The databaseName this document resides in.
|
||||
* @param namespace The namespace this document resides in.
|
||||
* @param uri The URI of the document to get.
|
||||
* @return The Document contents, or {@code null} if no such URI exists in the system.
|
||||
* @return The Document contents
|
||||
* @throws AppSearchException on IcingSearchEngine error.
|
||||
*/
|
||||
@Nullable
|
||||
public DocumentProto getDocument(@NonNull String databaseName, @NonNull String namespace,
|
||||
@NonNull
|
||||
public GenericDocument getDocument(@NonNull String databaseName, @NonNull String namespace,
|
||||
@NonNull String uri) throws AppSearchException {
|
||||
GetResultProto getResultProto;
|
||||
mReadWriteLock.readLock().lock();
|
||||
@@ -283,8 +301,8 @@ public final class AppSearchImpl {
|
||||
checkSuccess(getResultProto.getStatus());
|
||||
|
||||
DocumentProto.Builder documentBuilder = getResultProto.getDocument().toBuilder();
|
||||
rewriteDocumentTypes(getDatabasePrefix(databaseName), documentBuilder, /*add=*/ false);
|
||||
return documentBuilder.build();
|
||||
removeDatabasesFromDocument(documentBuilder);
|
||||
return GenericDocumentToProtoConverter.convert(documentBuilder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,33 +310,60 @@ public final class AppSearchImpl {
|
||||
*
|
||||
* <p>This method belongs to query group.
|
||||
*
|
||||
* @param databaseName The databaseName this query for.
|
||||
* @param searchSpec Defines what and how to search
|
||||
* @param resultSpec Defines what results to show
|
||||
* @param scoringSpec Defines how to order results
|
||||
* @return The results of performing this search The proto might have no {@code results} if no
|
||||
* documents matched the query.
|
||||
* @param databaseName The databaseName this query for.
|
||||
* @param queryExpression Query String to search.
|
||||
* @param searchSpec Spec for setting filters, raw query etc.
|
||||
* @return The results of performing this search. It may contain an empty list of results if
|
||||
* no documents matched the query.
|
||||
* @throws AppSearchException on IcingSearchEngine error.
|
||||
*/
|
||||
@NonNull
|
||||
public SearchResultProto query(
|
||||
public SearchResultPage query(
|
||||
@NonNull String databaseName,
|
||||
@NonNull SearchSpecProto searchSpec,
|
||||
@NonNull ResultSpecProto resultSpec,
|
||||
@NonNull ScoringSpecProto scoringSpec) throws AppSearchException {
|
||||
SearchSpecProto.Builder searchSpecBuilder = searchSpec.toBuilder();
|
||||
@NonNull String queryExpression,
|
||||
@NonNull SearchSpec searchSpec) throws AppSearchException {
|
||||
return doQuery(Collections.singleton(databaseName), queryExpression, searchSpec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a global query, i.e. over all permitted databases, against the AppSearch index and
|
||||
* returns results.
|
||||
*
|
||||
* <p>This method belongs to query group.
|
||||
*
|
||||
* @param queryExpression Query String to search.
|
||||
* @param searchSpec Spec for setting filters, raw query etc.
|
||||
* @return The results of performing this search. It may contain an empty list of results if
|
||||
* no documents matched the query.
|
||||
* @throws AppSearchException on IcingSearchEngine error.
|
||||
*/
|
||||
@NonNull
|
||||
public SearchResultPage globalQuery(
|
||||
@NonNull String queryExpression,
|
||||
@NonNull SearchSpec searchSpec) throws AppSearchException {
|
||||
return doQuery(mNamespaceMap.keySet(), queryExpression, searchSpec);
|
||||
}
|
||||
|
||||
private SearchResultPage doQuery(
|
||||
@NonNull Set<String> databases, @NonNull String queryExpression,
|
||||
@NonNull SearchSpec searchSpec)
|
||||
throws AppSearchException {
|
||||
SearchSpecProto searchSpecProto =
|
||||
SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
|
||||
SearchSpecProto.Builder searchSpecBuilder = searchSpecProto.toBuilder()
|
||||
.setQuery(queryExpression);
|
||||
|
||||
ResultSpecProto resultSpec = SearchSpecToProtoConverter.toResultSpecProto(searchSpec);
|
||||
ScoringSpecProto scoringSpec = SearchSpecToProtoConverter.toScoringSpecProto(searchSpec);
|
||||
SearchResultProto searchResultProto;
|
||||
mReadWriteLock.readLock().lock();
|
||||
try {
|
||||
// Only rewrite SearchSpec for non empty database.
|
||||
// rewriteSearchSpecForNonEmptyDatabase will return false for empty database, we
|
||||
// should just return an empty SearchResult and skip sending request to Icing.
|
||||
if (!rewriteSearchSpecForNonEmptyDatabase(databaseName, searchSpecBuilder)) {
|
||||
return SearchResultProto.newBuilder()
|
||||
.setStatus(StatusProto.newBuilder()
|
||||
.setCode(StatusProto.Code.OK)
|
||||
.build())
|
||||
.build();
|
||||
// rewriteSearchSpecForDatabases will return false if none of the databases have
|
||||
// documents, so we can return an empty SearchResult and skip sending request to Icing.
|
||||
// We use the mNamespaceMap.keySet here because it's the smaller set of valid databases
|
||||
// that could exist.
|
||||
if (!rewriteSearchSpecForDatabases(searchSpecBuilder, databases)) {
|
||||
return new SearchResultPage(Bundle.EMPTY);
|
||||
}
|
||||
searchResultProto = mIcingSearchEngine.search(
|
||||
searchSpecBuilder.build(), scoringSpec, resultSpec);
|
||||
@@ -326,34 +371,32 @@ public final class AppSearchImpl {
|
||||
mReadWriteLock.readLock().unlock();
|
||||
}
|
||||
checkSuccess(searchResultProto.getStatus());
|
||||
if (searchResultProto.getResultsCount() == 0) {
|
||||
return searchResultProto;
|
||||
}
|
||||
return rewriteSearchResultProto(databaseName, searchResultProto);
|
||||
return rewriteSearchResultProto(searchResultProto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the next page of results of a previously executed query. Results can be empty if
|
||||
* next-page token is invalid or all pages have been returned.
|
||||
*
|
||||
* @param databaseName The databaseName of the previously executed query.
|
||||
* <p>This method belongs to query group.
|
||||
*
|
||||
* @param nextPageToken The token of pre-loaded results of previously executed query.
|
||||
* @return The next page of results of previously executed query.
|
||||
* @throws AppSearchException on IcingSearchEngine error.
|
||||
*/
|
||||
@NonNull
|
||||
public SearchResultProto getNextPage(@NonNull String databaseName, long nextPageToken)
|
||||
public SearchResultPage getNextPage(long nextPageToken)
|
||||
throws AppSearchException {
|
||||
SearchResultProto searchResultProto = mIcingSearchEngine.getNextPage(nextPageToken);
|
||||
checkSuccess(searchResultProto.getStatus());
|
||||
if (searchResultProto.getResultsCount() == 0) {
|
||||
return searchResultProto;
|
||||
}
|
||||
return rewriteSearchResultProto(databaseName, searchResultProto);
|
||||
return rewriteSearchResultProto(searchResultProto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the next-page token so that no more results of the related query can be returned.
|
||||
*
|
||||
* <p>This method belongs to query group.
|
||||
*
|
||||
* @param nextPageToken The token of pre-loaded results of previously executed query to be
|
||||
* Invalidated.
|
||||
*/
|
||||
@@ -386,93 +429,43 @@ public final class AppSearchImpl {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all documents having the given {@code schemaType} in given database.
|
||||
* Removes documents by given query.
|
||||
*
|
||||
* <p>This method belongs to mutate group.
|
||||
*
|
||||
* @param databaseName The databaseName that contains documents of schemaType.
|
||||
* @param schemaType The schemaType of documents to remove.
|
||||
* @param databaseName The databaseName the document is in.
|
||||
* @param queryExpression Query String to search.
|
||||
* @param searchSpec Defines what and how to remove
|
||||
* @throws AppSearchException on IcingSearchEngine error.
|
||||
*/
|
||||
public void removeByType(@NonNull String databaseName, @NonNull String schemaType)
|
||||
public void removeByQuery(@NonNull String databaseName, @NonNull String queryExpression,
|
||||
@NonNull SearchSpec searchSpec)
|
||||
throws AppSearchException {
|
||||
String qualifiedType = getDatabasePrefix(databaseName) + schemaType;
|
||||
DeleteBySchemaTypeResultProto deleteBySchemaTypeResultProto;
|
||||
mReadWriteLock.writeLock().lock();
|
||||
try {
|
||||
Set<String> existingSchemaTypes = mSchemaMap.get(databaseName);
|
||||
if (existingSchemaTypes == null || !existingSchemaTypes.contains(qualifiedType)) {
|
||||
return;
|
||||
}
|
||||
deleteBySchemaTypeResultProto = mIcingSearchEngine.deleteBySchemaType(qualifiedType);
|
||||
checkForOptimize(/* force= */true);
|
||||
} finally {
|
||||
mReadWriteLock.writeLock().unlock();
|
||||
}
|
||||
checkSuccess(deleteBySchemaTypeResultProto.getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all documents having the given {@code namespace} in given database.
|
||||
*
|
||||
* <p>This method belongs to mutate group.
|
||||
*
|
||||
* @param databaseName The databaseName that contains documents of namespace.
|
||||
* @param namespace The namespace of documents to remove.
|
||||
* @throws AppSearchException on IcingSearchEngine error.
|
||||
*/
|
||||
public void removeByNamespace(@NonNull String databaseName, @NonNull String namespace)
|
||||
throws AppSearchException {
|
||||
String qualifiedNamespace = getDatabasePrefix(databaseName) + namespace;
|
||||
DeleteByNamespaceResultProto deleteByNamespaceResultProto;
|
||||
SearchSpecProto searchSpecProto =
|
||||
SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
|
||||
SearchSpecProto.Builder searchSpecBuilder = searchSpecProto.toBuilder()
|
||||
.setQuery(queryExpression);
|
||||
DeleteResultProto deleteResultProto;
|
||||
mReadWriteLock.writeLock().lock();
|
||||
try {
|
||||
Set<String> existingNamespaces = mNamespaceMap.get(databaseName);
|
||||
if (existingNamespaces == null || !existingNamespaces.contains(qualifiedNamespace)) {
|
||||
// Only rewrite SearchSpec for non empty database.
|
||||
// rewriteSearchSpecForNonEmptyDatabase will return false for empty database, we
|
||||
// should skip sending request to Icing and return in here.
|
||||
if (!rewriteSearchSpecForDatabases(searchSpecBuilder,
|
||||
Collections.singleton(databaseName))) {
|
||||
return;
|
||||
}
|
||||
deleteByNamespaceResultProto = mIcingSearchEngine.deleteByNamespace(qualifiedNamespace);
|
||||
checkForOptimize(/* force= */true);
|
||||
} finally {
|
||||
mReadWriteLock.writeLock().unlock();
|
||||
}
|
||||
checkSuccess(deleteByNamespaceResultProto.getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the given database by removing all documents and types.
|
||||
*
|
||||
* <p>The schemas will remain. To clear everything including schemas, please call
|
||||
* {@link #setSchema} with an empty schema and {@code forceOverride} set to true.
|
||||
*
|
||||
* <p>This method belongs to mutate group.
|
||||
*
|
||||
* @param databaseName The databaseName to remove all documents from.
|
||||
* @throws AppSearchException on IcingSearchEngine error.
|
||||
*/
|
||||
public void removeAll(@NonNull String databaseName)
|
||||
throws AppSearchException {
|
||||
mReadWriteLock.writeLock().lock();
|
||||
try {
|
||||
Set<String> existingNamespaces = mNamespaceMap.get(databaseName);
|
||||
if (existingNamespaces == null) {
|
||||
return;
|
||||
}
|
||||
for (String namespace : existingNamespaces) {
|
||||
DeleteByNamespaceResultProto deleteByNamespaceResultProto =
|
||||
mIcingSearchEngine.deleteByNamespace(namespace);
|
||||
// There's no way for AppSearch to know that all documents in a particular
|
||||
// namespace have been deleted, but if you try to delete an empty namespace, Icing
|
||||
// returns NOT_FOUND. Just ignore that code.
|
||||
checkCodeOneOf(
|
||||
deleteByNamespaceResultProto.getStatus(),
|
||||
StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
|
||||
}
|
||||
mNamespaceMap.remove(databaseName);
|
||||
deleteResultProto = mIcingSearchEngine.deleteByQuery(
|
||||
searchSpecBuilder.build());
|
||||
checkForOptimize(/* force= */true);
|
||||
} finally {
|
||||
mReadWriteLock.writeLock().unlock();
|
||||
}
|
||||
// It seems that the caller wants to get success if the data matching the query is not in
|
||||
// the DB because it was not there or was successfully deleted.
|
||||
checkCodeOneOf(deleteResultProto.getStatus(),
|
||||
StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -565,36 +558,63 @@ public final class AppSearchImpl {
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites all types and namespaces mentioned anywhere in {@code documentBuilder} to prepend
|
||||
* or remove {@code prefix}.
|
||||
* Prepends {@code prefix} to all types and namespaces mentioned anywhere in
|
||||
* {@code documentBuilder}.
|
||||
*
|
||||
* @param prefix The prefix to add or remove
|
||||
* @param documentBuilder The document to mutate
|
||||
* @param add Whether to add prefix to the types and namespaces. If {@code false},
|
||||
* prefix will be removed.
|
||||
* @throws IllegalStateException If {@code add=false} and the document has a type or namespace
|
||||
* that doesn't start with {@code prefix}.
|
||||
* @param prefix The prefix to add
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void rewriteDocumentTypes(
|
||||
@NonNull String prefix,
|
||||
void addPrefixToDocument(
|
||||
@NonNull DocumentProto.Builder documentBuilder,
|
||||
boolean add) {
|
||||
@NonNull String prefix) {
|
||||
// Rewrite the type name to include/remove the prefix.
|
||||
String newSchema;
|
||||
if (add) {
|
||||
newSchema = prefix + documentBuilder.getSchema();
|
||||
} else {
|
||||
newSchema = removePrefix(prefix, "schemaType", documentBuilder.getSchema());
|
||||
}
|
||||
String newSchema = prefix + documentBuilder.getSchema();
|
||||
documentBuilder.setSchema(newSchema);
|
||||
|
||||
// Rewrite the namespace to include/remove the prefix.
|
||||
if (add) {
|
||||
documentBuilder.setNamespace(prefix + documentBuilder.getNamespace());
|
||||
} else {
|
||||
documentBuilder.setNamespace(
|
||||
removePrefix(prefix, "namespace", documentBuilder.getNamespace()));
|
||||
documentBuilder.setNamespace(prefix + documentBuilder.getNamespace());
|
||||
|
||||
// Recurse into derived documents
|
||||
for (int propertyIdx = 0;
|
||||
propertyIdx < documentBuilder.getPropertiesCount();
|
||||
propertyIdx++) {
|
||||
int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount();
|
||||
if (documentCount > 0) {
|
||||
PropertyProto.Builder propertyBuilder =
|
||||
documentBuilder.getProperties(propertyIdx).toBuilder();
|
||||
for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
|
||||
DocumentProto.Builder derivedDocumentBuilder =
|
||||
propertyBuilder.getDocumentValues(documentIdx).toBuilder();
|
||||
addPrefixToDocument(derivedDocumentBuilder, prefix);
|
||||
propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
|
||||
}
|
||||
documentBuilder.setProperties(propertyIdx, propertyBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any database names from types and namespaces mentioned anywhere in
|
||||
* {@code documentBuilder}.
|
||||
*
|
||||
* @param documentBuilder The document to mutate
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void removeDatabasesFromDocument(@NonNull DocumentProto.Builder documentBuilder) {
|
||||
int delimiterIndex;
|
||||
if ((delimiterIndex = documentBuilder.getSchema().indexOf(DATABASE_DELIMITER)) != -1) {
|
||||
// Rewrite the type name to remove the prefix.
|
||||
// Add 1 to include the char size of the DATABASE_DELIMITER
|
||||
String newSchema = documentBuilder.getSchema().substring(delimiterIndex + 1);
|
||||
documentBuilder.setSchema(newSchema);
|
||||
}
|
||||
|
||||
if ((delimiterIndex = documentBuilder.getNamespace().indexOf(DATABASE_DELIMITER)) != -1) {
|
||||
// Rewrite the namespace to remove the prefix.
|
||||
// Add 1 to include the char size of the DATABASE_DELIMITER
|
||||
String newNamespace = documentBuilder.getNamespace().substring(delimiterIndex + 1);
|
||||
documentBuilder.setNamespace(newNamespace);
|
||||
}
|
||||
|
||||
// Recurse into derived documents
|
||||
@@ -608,7 +628,7 @@ public final class AppSearchImpl {
|
||||
for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
|
||||
DocumentProto.Builder derivedDocumentBuilder =
|
||||
propertyBuilder.getDocumentValues(documentIdx).toBuilder();
|
||||
rewriteDocumentTypes(prefix, derivedDocumentBuilder, add);
|
||||
removeDatabasesFromDocument(derivedDocumentBuilder);
|
||||
propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
|
||||
}
|
||||
documentBuilder.setProperties(propertyIdx, propertyBuilder);
|
||||
@@ -617,51 +637,68 @@ public final class AppSearchImpl {
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites searchSpec by adding schemaTypeFilter and namespacesFilter
|
||||
* Rewrites the schemaTypeFilters and namespacesFilters that exist in {@code databaseNames}.
|
||||
*
|
||||
* <p>If user input empty filter lists, will look up {@link #mSchemaMap} and
|
||||
* {@link #mNamespaceMap} and put all values belong to current database to narrow down Icing
|
||||
* search area.
|
||||
* <p>If the searchSpec has empty filter lists, all existing databases from
|
||||
* {@code databaseNames} will be added.
|
||||
* <p>This method should be only called in query methods and get the READ lock to keep thread
|
||||
* safety.
|
||||
* @return false if the current database is brand new and contains nothing. We should just
|
||||
* return an empty query result to user.
|
||||
*
|
||||
* @return false if none of the requested databases exist.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@GuardedBy("mReadWriteLock")
|
||||
boolean rewriteSearchSpecForNonEmptyDatabase(@NonNull String databaseName,
|
||||
@NonNull SearchSpecProto.Builder searchSpecBuilder) {
|
||||
Set<String> existingSchemaTypes = mSchemaMap.get(databaseName);
|
||||
Set<String> existingNamespaces = mNamespaceMap.get(databaseName);
|
||||
if (existingSchemaTypes == null || existingSchemaTypes.isEmpty()
|
||||
|| existingNamespaces == null || existingNamespaces.isEmpty()) {
|
||||
boolean rewriteSearchSpecForDatabases(
|
||||
@NonNull SearchSpecProto.Builder searchSpecBuilder,
|
||||
@NonNull Set<String> databaseNames) {
|
||||
// Create a copy since retainAll() modifies the original set.
|
||||
Set<String> existingDatabases = new HashSet<>(mNamespaceMap.keySet());
|
||||
existingDatabases.retainAll(databaseNames);
|
||||
|
||||
if (existingDatabases.isEmpty()) {
|
||||
// None of the databases exist, empty query.
|
||||
return false;
|
||||
}
|
||||
// Rewrite any existing schema types specified in the searchSpec, or add schema types to
|
||||
// limit the search to this database instance.
|
||||
if (searchSpecBuilder.getSchemaTypeFiltersCount() > 0) {
|
||||
for (int i = 0; i < searchSpecBuilder.getSchemaTypeFiltersCount(); i++) {
|
||||
String qualifiedType = getDatabasePrefix(databaseName)
|
||||
+ searchSpecBuilder.getSchemaTypeFilters(i);
|
||||
if (existingSchemaTypes.contains(qualifiedType)) {
|
||||
searchSpecBuilder.setSchemaTypeFilters(i, qualifiedType);
|
||||
|
||||
// Cache the schema type filters and namespaces before clearing everything.
|
||||
List<String> schemaTypeFilters = searchSpecBuilder.getSchemaTypeFiltersList();
|
||||
searchSpecBuilder.clearSchemaTypeFilters();
|
||||
|
||||
List<String> namespaceFilters = searchSpecBuilder.getNamespaceFiltersList();
|
||||
searchSpecBuilder.clearNamespaceFilters();
|
||||
|
||||
// Rewrite filters to include a database prefix.
|
||||
for (String databaseName : existingDatabases) {
|
||||
Set<String> existingSchemaTypes = mSchemaMap.get(databaseName);
|
||||
if (schemaTypeFilters.isEmpty()) {
|
||||
// Include all schema types
|
||||
searchSpecBuilder.addAllSchemaTypeFilters(existingSchemaTypes);
|
||||
} else {
|
||||
// Qualify the given schema types
|
||||
for (String schemaType : schemaTypeFilters) {
|
||||
String qualifiedType = getDatabasePrefix(databaseName) + schemaType;
|
||||
if (existingSchemaTypes.contains(qualifiedType)) {
|
||||
searchSpecBuilder.addSchemaTypeFilters(qualifiedType);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> existingNamespaces = mNamespaceMap.get(databaseName);
|
||||
if (namespaceFilters.isEmpty()) {
|
||||
// Include all namespaces
|
||||
searchSpecBuilder.addAllNamespaceFilters(existingNamespaces);
|
||||
} else {
|
||||
// Qualify the given namespaces.
|
||||
for (String namespace : namespaceFilters) {
|
||||
String qualifiedNamespace = getDatabasePrefix(databaseName) + namespace;
|
||||
if (existingNamespaces.contains(qualifiedNamespace)) {
|
||||
searchSpecBuilder.addNamespaceFilters(qualifiedNamespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
searchSpecBuilder.addAllSchemaTypeFilters(existingSchemaTypes);
|
||||
}
|
||||
|
||||
// Rewrite any existing namespaces specified in the searchSpec, or add namespaces to
|
||||
// limit the search to this database instance.
|
||||
if (searchSpecBuilder.getNamespaceFiltersCount() > 0) {
|
||||
for (int i = 0; i < searchSpecBuilder.getNamespaceFiltersCount(); i++) {
|
||||
String qualifiedNamespace = getDatabasePrefix(databaseName)
|
||||
+ searchSpecBuilder.getNamespaceFilters(i);
|
||||
searchSpecBuilder.setNamespaceFilters(i, qualifiedNamespace);
|
||||
}
|
||||
} else {
|
||||
searchSpecBuilder.addAllNamespaceFilters(existingNamespaces);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -676,12 +713,13 @@ public final class AppSearchImpl {
|
||||
|
||||
@NonNull
|
||||
private String getDatabasePrefix(@NonNull String databaseName) {
|
||||
return databaseName + "/";
|
||||
// TODO(b/170370381): Reconsider the way we separate database names for security reasons.
|
||||
return databaseName + DATABASE_DELIMITER;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String getDatabaseName(@NonNull String prefixedValue) throws AppSearchException {
|
||||
int delimiterIndex = prefixedValue.indexOf('/');
|
||||
int delimiterIndex = prefixedValue.indexOf(DATABASE_DELIMITER);
|
||||
if (delimiterIndex == -1) {
|
||||
throw new AppSearchException(AppSearchResult.RESULT_UNKNOWN_ERROR,
|
||||
"The databaseName prefixed value doesn't contains a valid database name.");
|
||||
@@ -689,17 +727,6 @@ public final class AppSearchImpl {
|
||||
return prefixedValue.substring(0, delimiterIndex);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String removePrefix(@NonNull String prefix, @NonNull String inputType,
|
||||
@NonNull String input) {
|
||||
if (!input.startsWith(prefix)) {
|
||||
throw new IllegalStateException(
|
||||
"Unexpected " + inputType + " \"" + input
|
||||
+ "\" does not start with \"" + prefix + "\"");
|
||||
}
|
||||
return input.substring(prefix.length());
|
||||
}
|
||||
|
||||
@GuardedBy("mReadWriteLock")
|
||||
private void addToMap(Map<String, Set<String>> map, String databaseName, String prefixedValue) {
|
||||
Set<String> values = map.get(databaseName);
|
||||
@@ -733,7 +760,7 @@ public final class AppSearchImpl {
|
||||
}
|
||||
|
||||
if (statusProto.getCode() == StatusProto.Code.WARNING_DATA_LOSS) {
|
||||
// TODO: May want to propagate WARNING_DATA_LOSS up to AppSearchManager so they can
|
||||
// TODO: May want to propagate WARNING_DATA_LOSS up to AppSearchSession so they can
|
||||
// choose to log the error or potentially pass it on to clients.
|
||||
Log.w(TAG, "Encountered WARNING_DATA_LOSS: " + statusProto.getMessage());
|
||||
return;
|
||||
@@ -776,22 +803,21 @@ public final class AppSearchImpl {
|
||||
}
|
||||
}
|
||||
|
||||
/** Remove the rewritten schema types from any result documents.*/
|
||||
private SearchResultProto rewriteSearchResultProto(@NonNull String databaseName,
|
||||
/** Remove the rewritten schema types from any result documents. */
|
||||
private SearchResultPage rewriteSearchResultProto(
|
||||
@NonNull SearchResultProto searchResultProto) {
|
||||
SearchResultProto.Builder searchResultsBuilder = searchResultProto.toBuilder();
|
||||
for (int i = 0; i < searchResultsBuilder.getResultsCount(); i++) {
|
||||
SearchResultProto.Builder resultsBuilder = searchResultProto.toBuilder();
|
||||
for (int i = 0; i < searchResultProto.getResultsCount(); i++) {
|
||||
if (searchResultProto.getResults(i).hasDocument()) {
|
||||
SearchResultProto.ResultProto.Builder resultBuilder =
|
||||
searchResultsBuilder.getResults(i).toBuilder();
|
||||
searchResultProto.getResults(i).toBuilder();
|
||||
DocumentProto.Builder documentBuilder = resultBuilder.getDocument().toBuilder();
|
||||
rewriteDocumentTypes(
|
||||
getDatabasePrefix(databaseName), documentBuilder, /*add=*/false);
|
||||
removeDatabasesFromDocument(documentBuilder);
|
||||
resultBuilder.setDocument(documentBuilder);
|
||||
searchResultsBuilder.setResults(i, resultBuilder);
|
||||
resultsBuilder.setResults(i, resultBuilder);
|
||||
}
|
||||
}
|
||||
return searchResultsBuilder.build();
|
||||
return SearchResultToProtoConverter.convertToSearchResultPage(resultsBuilder);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
||||
@@ -22,8 +22,10 @@ import android.annotation.NonNull;
|
||||
|
||||
import android.app.appsearch.GenericDocument;
|
||||
import android.app.appsearch.SearchResult;
|
||||
import android.app.appsearch.SearchResultPage;
|
||||
|
||||
import com.google.android.icing.proto.SearchResultProto;
|
||||
import com.google.android.icing.proto.SearchResultProtoOrBuilder;
|
||||
import com.google.android.icing.proto.SnippetMatchProto;
|
||||
import com.google.android.icing.proto.SnippetProto;
|
||||
|
||||
@@ -38,9 +40,24 @@ import java.util.ArrayList;
|
||||
public class SearchResultToProtoConverter {
|
||||
private SearchResultToProtoConverter() {}
|
||||
|
||||
|
||||
/** Translate a {@link SearchResultProto} into {@link SearchResultPage}. */
|
||||
@NonNull
|
||||
public static SearchResultPage convertToSearchResultPage(
|
||||
@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)));
|
||||
}
|
||||
bundle.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, resultBundles);
|
||||
return new SearchResultPage(bundle);
|
||||
}
|
||||
|
||||
/** Translate a {@link SearchResultProto.ResultProto} into {@link SearchResult}. */
|
||||
@NonNull
|
||||
public static SearchResult convertSearchResult(
|
||||
private static Bundle convertToSearchResultBundle(
|
||||
@NonNull SearchResultProto.ResultProtoOrBuilder proto) {
|
||||
Bundle bundle = new Bundle();
|
||||
GenericDocument document = GenericDocumentToProtoConverter.convert(proto.getDocument());
|
||||
@@ -59,7 +76,7 @@ public class SearchResultToProtoConverter {
|
||||
}
|
||||
bundle.putParcelableArrayList(SearchResult.MATCHES_FIELD, matchList);
|
||||
|
||||
return new SearchResult(bundle);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
private static Bundle convertToMatchInfoBundle(
|
||||
|
||||
@@ -20,6 +20,10 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.testng.Assert.expectThrows;
|
||||
|
||||
import android.app.appsearch.AppSearchSchema;
|
||||
import android.app.appsearch.GenericDocument;
|
||||
import android.app.appsearch.SearchResultPage;
|
||||
import android.app.appsearch.SearchSpec;
|
||||
import android.app.appsearch.exceptions.AppSearchException;
|
||||
|
||||
import com.android.server.appsearch.proto.DocumentProto;
|
||||
@@ -27,20 +31,19 @@ import com.android.server.appsearch.proto.GetOptimizeInfoResultProto;
|
||||
import com.android.server.appsearch.proto.IndexingConfig;
|
||||
import com.android.server.appsearch.proto.PropertyConfigProto;
|
||||
import com.android.server.appsearch.proto.PropertyProto;
|
||||
import com.android.server.appsearch.proto.ResultSpecProto;
|
||||
import com.android.server.appsearch.proto.SchemaProto;
|
||||
import com.android.server.appsearch.proto.SchemaTypeConfigProto;
|
||||
import com.android.server.appsearch.proto.ScoringSpecProto;
|
||||
import com.android.server.appsearch.proto.SearchResultProto;
|
||||
import com.android.server.appsearch.proto.SearchSpecProto;
|
||||
import com.android.server.appsearch.proto.StatusProto;
|
||||
import com.android.server.appsearch.proto.TermMatchType;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class AppSearchImplTest {
|
||||
@@ -93,7 +96,7 @@ public class AppSearchImplTest {
|
||||
|
||||
SchemaProto expectedSchema = SchemaProto.newBuilder()
|
||||
.addTypes(SchemaTypeConfigProto.newBuilder()
|
||||
.setSchemaType("databaseName/Foo").build())
|
||||
.setSchemaType("databaseName/Foo").build())
|
||||
.addTypes(SchemaTypeConfigProto.newBuilder()
|
||||
.setSchemaType("databaseName/TestType")
|
||||
.addProperties(PropertyConfigProto.newBuilder()
|
||||
@@ -120,7 +123,7 @@ public class AppSearchImplTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRewriteDocumentProto() {
|
||||
public void testAddDocumentTypePrefix() {
|
||||
DocumentProto insideDocument = DocumentProto.newBuilder()
|
||||
.setUri("inside-uri")
|
||||
.setSchema("type")
|
||||
@@ -146,30 +149,56 @@ public class AppSearchImplTest {
|
||||
.build();
|
||||
|
||||
DocumentProto.Builder actualDocument = documentProto.toBuilder();
|
||||
mAppSearchImpl.rewriteDocumentTypes("databaseName/", actualDocument, /*add=*/true);
|
||||
mAppSearchImpl.addPrefixToDocument(actualDocument, "databaseName/");
|
||||
assertThat(actualDocument.build()).isEqualTo(expectedDocumentProto);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveDocumentTypePrefixes() {
|
||||
DocumentProto insideDocument = DocumentProto.newBuilder()
|
||||
.setUri("inside-uri")
|
||||
.setSchema("databaseName1/type")
|
||||
.setNamespace("databaseName2/namespace")
|
||||
.build();
|
||||
DocumentProto documentProto = DocumentProto.newBuilder()
|
||||
.setUri("uri")
|
||||
.setSchema("databaseName2/type")
|
||||
.setNamespace("databaseName3/namespace")
|
||||
.addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument))
|
||||
.build();
|
||||
|
||||
DocumentProto expectedInsideDocument = DocumentProto.newBuilder()
|
||||
.setUri("inside-uri")
|
||||
.setSchema("type")
|
||||
.setNamespace("namespace")
|
||||
.build();
|
||||
// Since we don't pass in "databaseName3/" as a prefix to remove, it stays on the Document.
|
||||
DocumentProto expectedDocumentProto = DocumentProto.newBuilder()
|
||||
.setUri("uri")
|
||||
.setSchema("type")
|
||||
.setNamespace("namespace")
|
||||
.addProperties(PropertyProto.newBuilder().addDocumentValues(expectedInsideDocument))
|
||||
.build();
|
||||
|
||||
DocumentProto.Builder actualDocument = documentProto.toBuilder();
|
||||
mAppSearchImpl.removeDatabasesFromDocument(actualDocument);
|
||||
assertThat(actualDocument.build()).isEqualTo(expectedDocumentProto);
|
||||
mAppSearchImpl.rewriteDocumentTypes("databaseName/", actualDocument, /*add=*/false);
|
||||
assertThat(actualDocument.build()).isEqualTo(documentProto);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptimize() throws Exception {
|
||||
// Insert schema
|
||||
SchemaProto schema = SchemaProto.newBuilder()
|
||||
.addTypes(SchemaTypeConfigProto.newBuilder()
|
||||
.setSchemaType("type").build())
|
||||
.build();
|
||||
mAppSearchImpl.setSchema("database", schema, /*forceOverride=*/false);
|
||||
Set<AppSearchSchema> schemas =
|
||||
Collections.singleton(new AppSearchSchema.Builder("type").build());
|
||||
mAppSearchImpl.setSchema("database", schemas, /*forceOverride=*/false);
|
||||
|
||||
// Insert enough documents.
|
||||
for (int i = 0; i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
|
||||
+ AppSearchImpl.CHECK_OPTIMIZE_INTERVAL; i++) {
|
||||
DocumentProto insideDocument = DocumentProto.newBuilder()
|
||||
.setUri("inside-uri" + i)
|
||||
.setSchema("type")
|
||||
.setNamespace("namespace")
|
||||
.build();
|
||||
mAppSearchImpl.putDocument("database", insideDocument);
|
||||
GenericDocument document =
|
||||
new GenericDocument.Builder("uri" + i, "type").setNamespace(
|
||||
"namespace").build();
|
||||
mAppSearchImpl.putDocument("database", document);
|
||||
}
|
||||
|
||||
// Check optimize() will release 0 docs since there is no deletion.
|
||||
@@ -179,7 +208,7 @@ public class AppSearchImplTest {
|
||||
// delete 999 documents , we will reach the threshold to trigger optimize() in next
|
||||
// deletion.
|
||||
for (int i = 0; i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1; i++) {
|
||||
mAppSearchImpl.remove("database", "namespace", "inside-uri" + i);
|
||||
mAppSearchImpl.remove("database", "namespace", "uri" + i);
|
||||
}
|
||||
|
||||
// optimize() still not be triggered since we are in the interval to call getOptimizeInfo()
|
||||
@@ -191,65 +220,111 @@ public class AppSearchImplTest {
|
||||
for (int i = AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT;
|
||||
i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
|
||||
+ AppSearchImpl.CHECK_OPTIMIZE_INTERVAL; i++) {
|
||||
mAppSearchImpl.remove("database", "namespace", "inside-uri" + i);
|
||||
mAppSearchImpl.remove("database", "namespace", "uri" + i);
|
||||
}
|
||||
|
||||
// Verify optimize() is triggered
|
||||
optimizeInfo = mAppSearchImpl.getOptimizeInfoResult();
|
||||
assertThat(optimizeInfo.getOptimizableDocs())
|
||||
.isLessThan((long) AppSearchImpl.CHECK_OPTIMIZE_INTERVAL);
|
||||
.isLessThan(AppSearchImpl.CHECK_OPTIMIZE_INTERVAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRewriteSearchSpec() throws Exception {
|
||||
public void testRewriteSearchSpec_OneInstance() throws Exception {
|
||||
SearchSpecProto.Builder searchSpecProto =
|
||||
SearchSpecProto.newBuilder().setQuery("");
|
||||
|
||||
// Insert schema
|
||||
SchemaProto schema = SchemaProto.newBuilder()
|
||||
.addTypes(SchemaTypeConfigProto.newBuilder()
|
||||
.setSchemaType("type").build())
|
||||
.build();
|
||||
mAppSearchImpl.setSchema("database", schema, /*forceOverride=*/false);
|
||||
Set<AppSearchSchema> schemas =
|
||||
Collections.singleton(new AppSearchSchema.Builder("type").build());
|
||||
mAppSearchImpl.setSchema("database", schemas, /*forceOverride=*/false);
|
||||
|
||||
// Insert document
|
||||
DocumentProto insideDocument = DocumentProto.newBuilder()
|
||||
.setUri("inside-uri")
|
||||
.setSchema("type")
|
||||
.setNamespace("namespace")
|
||||
.build();
|
||||
mAppSearchImpl.putDocument("database", insideDocument);
|
||||
GenericDocument document = new GenericDocument.Builder("uri", "type").setNamespace(
|
||||
"namespace").build();
|
||||
mAppSearchImpl.putDocument("database", document);
|
||||
|
||||
// Rewrite SearchSpec
|
||||
mAppSearchImpl.rewriteSearchSpecForNonEmptyDatabase(
|
||||
"database", searchSpecProto);
|
||||
mAppSearchImpl.rewriteSearchSpecForDatabases(searchSpecProto, Collections.singleton(
|
||||
"database"));
|
||||
assertThat(searchSpecProto.getSchemaTypeFiltersList()).containsExactly("database/type");
|
||||
assertThat(searchSpecProto.getNamespaceFiltersList()).containsExactly("database/namespace");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRewriteSearchSpec_TwoInstances() throws Exception {
|
||||
SearchSpecProto.Builder searchSpecProto =
|
||||
SearchSpecProto.newBuilder().setQuery("");
|
||||
|
||||
// Insert schema
|
||||
Set<AppSearchSchema> schemas = Set.of(
|
||||
new AppSearchSchema.Builder("typeA").build(),
|
||||
new AppSearchSchema.Builder("typeB").build());
|
||||
mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/false);
|
||||
mAppSearchImpl.setSchema("database2", schemas, /*forceOverride=*/false);
|
||||
|
||||
// Insert documents
|
||||
GenericDocument document1 = new GenericDocument.Builder("uri", "typeA").setNamespace(
|
||||
"namespace").build();
|
||||
mAppSearchImpl.putDocument("database1", document1);
|
||||
|
||||
GenericDocument document2 = new GenericDocument.Builder("uri", "typeB").setNamespace(
|
||||
"namespace").build();
|
||||
mAppSearchImpl.putDocument("database2", document2);
|
||||
|
||||
// Rewrite SearchSpec
|
||||
mAppSearchImpl.rewriteSearchSpecForDatabases(searchSpecProto,
|
||||
ImmutableSet.of("database1", "database2"));
|
||||
assertThat(searchSpecProto.getSchemaTypeFiltersList()).containsExactly(
|
||||
"database1/typeA", "database1/typeB", "database2/typeA", "database2/typeB");
|
||||
assertThat(searchSpecProto.getNamespaceFiltersList()).containsExactly(
|
||||
"database1/namespace", "database2/namespace");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryEmptyDatabase() throws Exception {
|
||||
SearchResultProto searchResultProto = mAppSearchImpl.query("EmptyDatabase",
|
||||
SearchSpecProto.getDefaultInstance(),
|
||||
ResultSpecProto.getDefaultInstance(), ScoringSpecProto.getDefaultInstance());
|
||||
assertThat(searchResultProto.getResultsCount()).isEqualTo(0);
|
||||
assertThat(searchResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.OK);
|
||||
SearchSpec searchSpec =
|
||||
new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
|
||||
SearchResultPage searchResultPage = mAppSearchImpl.query(
|
||||
"EmptyDatabase",
|
||||
"", searchSpec);
|
||||
assertThat(searchResultPage.getResults()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGlobalQueryEmptyDatabase() throws Exception {
|
||||
SearchSpec searchSpec =
|
||||
new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
|
||||
SearchResultPage searchResultPage = mAppSearchImpl.query(
|
||||
"EmptyDatabase",
|
||||
"", searchSpec);
|
||||
assertThat(searchResultPage.getResults()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveEmptyDatabase_NoExceptionThrown() throws Exception {
|
||||
mAppSearchImpl.removeByType("EmptyDatabase", "FakeType");
|
||||
mAppSearchImpl.removeByNamespace("EmptyDatabase", "FakeNamespace");
|
||||
mAppSearchImpl.removeAll("EmptyDatabase");
|
||||
SearchSpec searchSpec =
|
||||
new SearchSpec.Builder().addSchema("FakeType").setTermMatch(
|
||||
TermMatchType.Code.PREFIX_VALUE).build();
|
||||
mAppSearchImpl.removeByQuery("EmptyDatabase",
|
||||
"", searchSpec);
|
||||
|
||||
searchSpec =
|
||||
new SearchSpec.Builder().addNamespace("FakeNamespace").setTermMatch(
|
||||
TermMatchType.Code.PREFIX_VALUE).build();
|
||||
mAppSearchImpl.removeByQuery("EmptyDatabase",
|
||||
"", searchSpec);
|
||||
|
||||
searchSpec = new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
|
||||
mAppSearchImpl.removeByQuery("EmptyDatabase", "", searchSpec);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSchema() throws Exception {
|
||||
// Create schemas
|
||||
SchemaProto schemaProto = SchemaProto.newBuilder()
|
||||
.addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email")).build();
|
||||
|
||||
Set<AppSearchSchema> schemas =
|
||||
Collections.singleton(new AppSearchSchema.Builder("Email").build());
|
||||
// Set schema Email to AppSearch database1
|
||||
mAppSearchImpl.setSchema("database1", schemaProto, /*forceOverride=*/false);
|
||||
mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/false);
|
||||
|
||||
// Create excepted schemaType proto.
|
||||
SchemaProto exceptedProto = SchemaProto.newBuilder()
|
||||
@@ -261,13 +336,11 @@ public class AppSearchImplTest {
|
||||
|
||||
@Test
|
||||
public void testRemoveSchema() throws Exception {
|
||||
// Create schemas
|
||||
SchemaProto schemaProto = SchemaProto.newBuilder()
|
||||
.addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email"))
|
||||
.addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Document")).build();
|
||||
|
||||
Set<AppSearchSchema> schemas = new HashSet<>();
|
||||
schemas.add(new AppSearchSchema.Builder("Email").build());
|
||||
schemas.add(new AppSearchSchema.Builder("Document").build());
|
||||
// Set schema Email and Document to AppSearch database1
|
||||
mAppSearchImpl.setSchema("database1", schemaProto, /*forceOverride=*/false);
|
||||
mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/false);
|
||||
|
||||
// Create excepted schemaType proto.
|
||||
SchemaProto exceptedProto = SchemaProto.newBuilder()
|
||||
@@ -279,19 +352,16 @@ public class AppSearchImplTest {
|
||||
assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
|
||||
.containsExactlyElementsIn(exceptedProto.getTypesList());
|
||||
|
||||
// Save only Email this time.
|
||||
schemaProto = SchemaProto.newBuilder()
|
||||
.addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email")).build();
|
||||
|
||||
final Set<AppSearchSchema> finalSchemas = Collections.singleton(new AppSearchSchema.Builder(
|
||||
"Email").build());
|
||||
// Check the incompatible error has been thrown.
|
||||
SchemaProto finalSchemaProto = schemaProto;
|
||||
AppSearchException e = expectThrows(AppSearchException.class, () ->
|
||||
mAppSearchImpl.setSchema("database1", finalSchemaProto, /*forceOverride=*/false));
|
||||
mAppSearchImpl.setSchema("database1", finalSchemas, /*forceOverride=*/false));
|
||||
assertThat(e).hasMessageThat().contains("Schema is incompatible");
|
||||
assertThat(e).hasMessageThat().contains("Deleted types: [database1/Document]");
|
||||
|
||||
// ForceOverride to delete.
|
||||
mAppSearchImpl.setSchema("database1", finalSchemaProto, /*forceOverride=*/true);
|
||||
mAppSearchImpl.setSchema("database1", finalSchemas, /*forceOverride=*/true);
|
||||
|
||||
// Check Document schema is removed.
|
||||
exceptedProto = SchemaProto.newBuilder()
|
||||
@@ -304,13 +374,13 @@ public class AppSearchImplTest {
|
||||
@Test
|
||||
public void testRemoveSchema_differentDataBase() throws Exception {
|
||||
// Create schemas
|
||||
SchemaProto emailAndDocSchemaProto = SchemaProto.newBuilder()
|
||||
.addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email"))
|
||||
.addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Document")).build();
|
||||
Set<AppSearchSchema> schemas = new HashSet<>();
|
||||
schemas.add(new AppSearchSchema.Builder("Email").build());
|
||||
schemas.add(new AppSearchSchema.Builder("Document").build());
|
||||
|
||||
// Set schema Email and Document to AppSearch database1 and 2
|
||||
mAppSearchImpl.setSchema("database1", emailAndDocSchemaProto, /*forceOverride=*/false);
|
||||
mAppSearchImpl.setSchema("database2", emailAndDocSchemaProto, /*forceOverride=*/false);
|
||||
mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/false);
|
||||
mAppSearchImpl.setSchema("database2", schemas, /*forceOverride=*/false);
|
||||
|
||||
// Create excepted schemaType proto.
|
||||
SchemaProto exceptedProto = SchemaProto.newBuilder()
|
||||
@@ -325,10 +395,8 @@ public class AppSearchImplTest {
|
||||
.containsExactlyElementsIn(exceptedProto.getTypesList());
|
||||
|
||||
// Save only Email to database1 this time.
|
||||
SchemaProto emailSchemaProto = SchemaProto.newBuilder()
|
||||
.addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email"))
|
||||
.build();
|
||||
mAppSearchImpl.setSchema("database1", emailSchemaProto, /*forceOverride=*/true);
|
||||
schemas = Collections.singleton(new AppSearchSchema.Builder("Email").build());
|
||||
mAppSearchImpl.setSchema("database1", schemas, /*forceOverride=*/true);
|
||||
|
||||
// Create excepted schemaType list, database 1 should only contain Email but database 2
|
||||
// remains in same.
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.server.appsearch.external.localstorage.converter;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.app.appsearch.SearchResult;
|
||||
import android.app.appsearch.SearchResultPage;
|
||||
|
||||
import com.android.server.appsearch.proto.DocumentProto;
|
||||
import com.android.server.appsearch.proto.PropertyProto;
|
||||
@@ -75,8 +76,9 @@ public class SnippetTest {
|
||||
.build();
|
||||
|
||||
// Making ResultReader and getting Snippet values.
|
||||
for (SearchResultProto.ResultProto proto : searchResultProto.getResultsList()) {
|
||||
SearchResult result = SearchResultToProtoConverter.convertSearchResult(proto);
|
||||
SearchResultPage searchResultPage =
|
||||
SearchResultToProtoConverter.convertToSearchResultPage(searchResultProto);
|
||||
for (SearchResult result : searchResultPage.getResults()) {
|
||||
SearchResult.MatchInfo match = result.getMatches().get(0);
|
||||
assertThat(match.getPropertyPath()).isEqualTo(propertyKeyString);
|
||||
assertThat(match.getFullText()).isEqualTo(propertyValueString);
|
||||
@@ -121,8 +123,9 @@ public class SnippetTest {
|
||||
.addResults(resultProto)
|
||||
.build();
|
||||
|
||||
for (SearchResultProto.ResultProto proto : searchResultProto.getResultsList()) {
|
||||
SearchResult result = SearchResultToProtoConverter.convertSearchResult(proto);
|
||||
SearchResultPage searchResultPage =
|
||||
SearchResultToProtoConverter.convertToSearchResultPage(searchResultProto);
|
||||
for (SearchResult result : searchResultPage.getResults()) {
|
||||
assertThat(result.getMatches()).isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -182,8 +185,9 @@ public class SnippetTest {
|
||||
.build();
|
||||
|
||||
// Making ResultReader and getting Snippet values.
|
||||
for (SearchResultProto.ResultProto proto : searchResultProto.getResultsList()) {
|
||||
SearchResult result = SearchResultToProtoConverter.convertSearchResult(proto);
|
||||
SearchResultPage searchResultPage =
|
||||
SearchResultToProtoConverter.convertToSearchResultPage(searchResultProto);
|
||||
for (SearchResult result : searchResultPage.getResults()) {
|
||||
|
||||
SearchResult.MatchInfo match1 = result.getMatches().get(0);
|
||||
assertThat(match1.getPropertyPath()).isEqualTo("sender.name");
|
||||
|
||||
Reference in New Issue
Block a user