Merge "Update framework from jetpack." into sc-dev

This commit is contained in:
Alexander Dorokhine
2021-05-13 19:15:05 +00:00
committed by Android (Google) Code Review
16 changed files with 593 additions and 303 deletions

View File

@@ -19,6 +19,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.ArrayMap;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
@@ -48,9 +49,9 @@ public final class AppSearchBatchResult<KeyType, ValueType> {
@NonNull Map<KeyType, ValueType> successes,
@NonNull Map<KeyType, AppSearchResult<ValueType>> failures,
@NonNull Map<KeyType, AppSearchResult<ValueType>> all) {
mSuccesses = successes;
mFailures = failures;
mAll = all;
mSuccesses = Objects.requireNonNull(successes);
mFailures = Objects.requireNonNull(failures);
mAll = Objects.requireNonNull(all);
}
/** Returns {@code true} if this {@link AppSearchBatchResult} has no failures. */
@@ -70,7 +71,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> {
*/
@NonNull
public Map<KeyType, ValueType> getSuccesses() {
return mSuccesses;
return Collections.unmodifiableMap(mSuccesses);
}
/**
@@ -81,7 +82,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> {
*/
@NonNull
public Map<KeyType, AppSearchResult<ValueType>> getFailures() {
return mFailures;
return Collections.unmodifiableMap(mFailures);
}
/**
@@ -92,7 +93,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> {
*/
@NonNull
public Map<KeyType, AppSearchResult<ValueType>> getAll() {
return mAll;
return Collections.unmodifiableMap(mAll);
}
/**
@@ -128,20 +129,37 @@ public final class AppSearchBatchResult<KeyType, ValueType> {
* Associates the {@code key} with the provided successful return value.
*
* <p>Any previous mapping for a key, whether success or failure, is deleted.
*
* <p>This is a convenience function which is equivalent to {@code setResult(key,
* AppSearchResult.newSuccessfulResult(value))}.
*
* @param key The key to associate the result with; usually corresponds to some identifier
* from the input like an ID or name.
* @param value An optional value to associate with the successful result of the operation
* being performed.
*/
@SuppressWarnings("MissingGetterMatchingBuilder") // See getSuccesses
@NonNull
public Builder<KeyType, ValueType> setSuccess(
@NonNull KeyType key, @Nullable ValueType result) {
@NonNull KeyType key, @Nullable ValueType value) {
Objects.requireNonNull(key);
resetIfBuilt();
return setResult(key, AppSearchResult.newSuccessfulResult(result));
return setResult(key, AppSearchResult.newSuccessfulResult(value));
}
/**
* Associates the {@code key} with the provided failure code and error message.
*
* <p>Any previous mapping for a key, whether success or failure, is deleted.
*
* <p>This is a convenience function which is equivalent to {@code setResult(key,
* AppSearchResult.newFailedResult(resultCode, errorMessage))}.
*
* @param key The key to associate the result with; usually corresponds to some identifier
* from the input like an ID or name.
* @param resultCode One of the constants documented in {@link
* AppSearchResult#getResultCode}.
* @param errorMessage An optional string describing the reason or nature of the failure.
*/
@SuppressWarnings("MissingGetterMatchingBuilder") // See getFailures
@NonNull
@@ -158,6 +176,10 @@ public final class AppSearchBatchResult<KeyType, ValueType> {
* Associates the {@code key} with the provided {@code result}.
*
* <p>Any previous mapping for a key, whether success or failure, is deleted.
*
* @param key The key to associate the result with; usually corresponds to some identifier
* from the input like an ID or name.
* @param result The result to associate with the key.
*/
@SuppressWarnings("MissingGetterMatchingBuilder") // See getAll
@NonNull
@@ -183,8 +205,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> {
@NonNull
public AppSearchBatchResult<KeyType, ValueType> build() {
mBuilt = true;
return new AppSearchBatchResult<>(
new ArrayMap<>(mSuccesses), new ArrayMap<>(mFailures), new ArrayMap<>(mAll));
return new AppSearchBatchResult<>(mSuccesses, mFailures, mAll);
}
private void resetIfBuilt() {

View File

@@ -175,14 +175,24 @@ public final class AppSearchResult<ValueType> {
return "[FAILURE(" + mResultCode + ")]: " + mErrorMessage;
}
/** Creates a new successful {@link AppSearchResult}. */
/**
* Creates a new successful {@link AppSearchResult}.
*
* @param value An optional value to associate with the successful result of the operation being
* performed.
*/
@NonNull
public static <ValueType> AppSearchResult<ValueType> newSuccessfulResult(
@Nullable ValueType value) {
return new AppSearchResult<>(RESULT_OK, value, /*errorMessage=*/ null);
}
/** Creates a new failed {@link AppSearchResult}. */
/**
* Creates a new failed {@link AppSearchResult}.
*
* @param resultCode One of the constants documented in {@link AppSearchResult#getResultCode}.
* @param errorMessage An optional string describing the reason or nature of the failure.
*/
@NonNull
public static <ValueType> AppSearchResult<ValueType> newFailedResult(
@ResultCode int resultCode, @Nullable String errorMessage) {

View File

@@ -164,8 +164,7 @@ public final class GetByDocumentIdRequest {
@NonNull
public GetByDocumentIdRequest build() {
mBuilt = true;
return new GetByDocumentIdRequest(
mNamespace, new ArraySet<>(mIds), new ArrayMap<>(mProjectionTypePropertyPaths));
return new GetByDocumentIdRequest(mNamespace, mIds, mProjectionTypePropertyPaths);
}
private void resetIfBuilt() {

View File

@@ -93,14 +93,25 @@ public final class ReportSystemUsageRequest {
private final String mDocumentId;
private Long mUsageTimestampMillis;
/** Creates a {@link ReportSystemUsageRequest.Builder} instance. */
/**
* Creates a {@link ReportSystemUsageRequest.Builder} instance.
*
* @param packageName The package name of the app which owns the document that was used
* (e.g. from {@link SearchResult#getPackageName}).
* @param databaseName The database in which the document that was used resides (e.g. from
* {@link SearchResult#getDatabaseName}).
* @param namespace The namespace of the document that was used (e.g. from {@link
* GenericDocument#getNamespace}.
* @param documentId The ID of document that was used (e.g. from {@link
* GenericDocument#getId}.
*/
public Builder(
@NonNull String packageName,
@NonNull String database,
@NonNull String databaseName,
@NonNull String namespace,
@NonNull String documentId) {
mPackageName = Objects.requireNonNull(packageName);
mDatabase = Objects.requireNonNull(database);
mDatabase = Objects.requireNonNull(databaseName);
mNamespace = Objects.requireNonNull(namespace);
mDocumentId = Objects.requireNonNull(documentId);
}

View File

@@ -71,7 +71,14 @@ public final class ReportUsageRequest {
private String mDocumentId;
private Long mUsageTimestampMillis;
/** Creates a {@link ReportUsageRequest.Builder} instance. */
/**
* Creates a new {@link ReportUsageRequest.Builder} instance.
*
* @param namespace The namespace of the document that was used (e.g. from {@link
* GenericDocument#getNamespace}.
* @param documentId The ID of document that was used (e.g. from {@link
* GenericDocument#getId}.
*/
public Builder(@NonNull String namespace, @NonNull String documentId) {
mNamespace = Objects.requireNonNull(namespace);
mDocumentId = Objects.requireNonNull(documentId);

View File

@@ -375,5 +375,19 @@ public class SetSchemaResponse {
mBundle.getInt(RESULT_CODE_FIELD),
mBundle.getString(ERROR_MESSAGE_FIELD, /*defaultValue=*/ ""));
}
@NonNull
@Override
public String toString() {
return "MigrationFailure { schemaType: "
+ getSchemaType()
+ ", namespace: "
+ getNamespace()
+ ", documentId: "
+ getDocumentId()
+ ", appSearchResult: "
+ getAppSearchResult().toString()
+ "}";
}
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch.util;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Log;
import java.util.Objects;
/**
* Utilities for logging to logcat.
*
* @hide
*/
public final class LogUtil {
/**
* The {@link #piiTrace} logs are intended for sensitive data that can't be enabled in
* production, so they are build-gated by this constant.
*
* <p>
*
* <ul>
* <li>0: no tracing.
* <li>1: fast tracing (statuses/counts only)
* <li>2: full tracing (complete messages)
* </ul>
*/
private static final int PII_TRACE_LEVEL = 0;
private final String mTag;
public LogUtil(@NonNull String tag) {
mTag = Objects.requireNonNull(tag);
}
/** Returns whether piiTrace() is enabled (PII_TRACE_LEVEL > 0). */
public boolean isPiiTraceEnabled() {
return PII_TRACE_LEVEL > 0;
}
/**
* If icing lib interaction tracing is enabled via {@link #PII_TRACE_LEVEL}, logs the provided
* message to logcat.
*
* <p>If {@link #PII_TRACE_LEVEL} is 0, nothing is logged and this method returns immediately.
*/
public void piiTrace(@NonNull String message) {
piiTrace(message, /*fastTraceObj=*/ null, /*fullTraceObj=*/ null);
}
/**
* If icing lib interaction tracing is enabled via {@link #PII_TRACE_LEVEL}, logs the provided
* message and object to logcat.
*
* <p>If {@link #PII_TRACE_LEVEL} is 0, nothing is logged and this method returns immediately.
*
* <p>Otherwise, {@code traceObj} is logged if it is non-null.
*/
public void piiTrace(@NonNull String message, @Nullable Object traceObj) {
piiTrace(message, /*fastTraceObj=*/ traceObj, /*fullTraceObj=*/ null);
}
/**
* If icing lib interaction tracing is enabled via {@link #PII_TRACE_LEVEL}, logs the provided
* message and objects to logcat.
*
* <p>If {@link #PII_TRACE_LEVEL} is 0, nothing is logged and this method returns immediately.
*
* <p>If {@link #PII_TRACE_LEVEL} is 1, {@code fastTraceObj} is logged if it is non-null.
*
* <p>If {@link #PII_TRACE_LEVEL} is 2, {@code fullTraceObj} is logged if it is non-null, else
* {@code fastTraceObj} is logged if it is non-null..
*/
public void piiTrace(
@NonNull String message, @Nullable Object fastTraceObj, @Nullable Object fullTraceObj) {
if (PII_TRACE_LEVEL == 0) {
return;
}
StringBuilder builder = new StringBuilder("(trace) ").append(message);
if (PII_TRACE_LEVEL == 1 && fastTraceObj != null) {
builder.append(": ").append(fastTraceObj);
} else if (PII_TRACE_LEVEL == 2 && fullTraceObj != null) {
builder.append(": ").append(fullTraceObj);
} else if (PII_TRACE_LEVEL == 2 && fastTraceObj != null) {
builder.append(": ").append(fastTraceObj);
}
Log.i(mTag, builder.toString());
}
}

View File

@@ -28,6 +28,7 @@ import static com.android.server.appsearch.external.localstorage.util.PrefixUtil
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.GetByDocumentIdRequest;
@@ -38,6 +39,7 @@ import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaResponse;
import android.app.appsearch.StorageInfo;
import android.app.appsearch.exceptions.AppSearchException;
import android.app.appsearch.util.LogUtil;
import android.content.Context;
import android.os.Bundle;
import android.os.SystemClock;
@@ -147,8 +149,11 @@ public final class AppSearchImpl implements Closeable {
private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
private final LogUtil mLogUtil = new LogUtil(TAG);
@GuardedBy("mReadWriteLock")
private final IcingSearchEngine mIcingSearchEngineLocked;
@VisibleForTesting
final IcingSearchEngine mIcingSearchEngineLocked;
@GuardedBy("mReadWriteLock")
private final VisibilityStore mVisibilityStoreLocked;
@@ -216,7 +221,7 @@ public final class AppSearchImpl implements Closeable {
appSearchImpl.initializeVisibilityStore();
long prepareVisibilityStoreLatencyEndMillis = SystemClock.elapsedRealtime();
if (logger != null && initStatsBuilder != null) {
if (logger != null) {
initStatsBuilder
.setTotalLatencyMillis(
(int) (SystemClock.elapsedRealtime() - totalLatencyStartMillis))
@@ -247,66 +252,91 @@ public final class AppSearchImpl implements Closeable {
IcingSearchEngineOptions.newBuilder()
.setBaseDir(icingDir.getAbsolutePath())
.build();
mLogUtil.piiTrace("Constructing IcingSearchEngine, request", options);
mIcingSearchEngineLocked = new IcingSearchEngine(options);
mLogUtil.piiTrace(
"Constructing IcingSearchEngine, response",
Objects.hashCode(mIcingSearchEngineLocked));
mVisibilityStoreLocked =
new VisibilityStore(this, context, userId, globalQuerierPackage);
InitializeResultProto initializeResultProto = mIcingSearchEngineLocked.initialize();
if (initStatsBuilder != null) {
initStatsBuilder
.setStatusCode(
statusProtoToAppSearchException(initializeResultProto.getStatus())
.getResultCode())
// TODO(b/173532925) how to get DeSyncs value
.setHasDeSync(false);
AppSearchLoggerHelper.copyNativeStats(
initializeResultProto.getInitializeStats(), initStatsBuilder);
}
long prepareSchemaAndNamespacesLatencyStartMillis = SystemClock.elapsedRealtime();
SchemaProto schemaProto;
GetAllNamespacesResultProto getAllNamespacesResultProto = null;
// The core initialization procedure. If any part of this fails, we bail into
// resetLocked(), deleting all data (but hopefully allowing AppSearchImpl to come up).
try {
checkSuccess(initializeResultProto.getStatus());
schemaProto = getSchemaProtoLocked();
getAllNamespacesResultProto = mIcingSearchEngineLocked.getAllNamespaces();
checkSuccess(getAllNamespacesResultProto.getStatus());
} catch (AppSearchException e) {
Log.w(TAG, "Error initializing, resetting IcingSearchEngine.", e);
if (initStatsBuilder != null && getAllNamespacesResultProto != null) {
mLogUtil.piiTrace("icingSearchEngine.initialize, request");
InitializeResultProto initializeResultProto = mIcingSearchEngineLocked.initialize();
mLogUtil.piiTrace(
"icingSearchEngine.initialize, response",
initializeResultProto.getStatus(),
initializeResultProto);
if (initStatsBuilder != null) {
initStatsBuilder
.setStatusCode(
statusProtoToAppSearchException(
getAllNamespacesResultProto.getStatus())
.getResultCode())
statusProtoToResultCode(initializeResultProto.getStatus()))
// TODO(b/173532925) how to get DeSyncs value
.setHasDeSync(false);
AppSearchLoggerHelper.copyNativeStats(
initializeResultProto.getInitializeStats(), initStatsBuilder);
}
checkSuccess(initializeResultProto.getStatus());
long prepareSchemaAndNamespacesLatencyStartMillis = SystemClock.elapsedRealtime();
SchemaProto schemaProto = getSchemaProtoLocked();
mLogUtil.piiTrace("init:getAllNamespaces, request");
GetAllNamespacesResultProto getAllNamespacesResultProto =
mIcingSearchEngineLocked.getAllNamespaces();
mLogUtil.piiTrace(
"init:getAllNamespaces, response",
getAllNamespacesResultProto.getNamespacesCount(),
getAllNamespacesResultProto);
if (initStatsBuilder != null) {
initStatsBuilder
.setStatusCode(
statusProtoToResultCode(
getAllNamespacesResultProto.getStatus()))
.setPrepareSchemaAndNamespacesLatencyMillis(
(int)
(SystemClock.elapsedRealtime()
- prepareSchemaAndNamespacesLatencyStartMillis));
}
checkSuccess(getAllNamespacesResultProto.getStatus());
// Populate schema map
for (SchemaTypeConfigProto schema : schemaProto.getTypesList()) {
String prefixedSchemaType = schema.getSchemaType();
addToMap(mSchemaMapLocked, getPrefix(prefixedSchemaType), schema);
}
// Populate namespace map
for (String prefixedNamespace : getAllNamespacesResultProto.getNamespacesList()) {
addToMap(mNamespaceMapLocked, getPrefix(prefixedNamespace), prefixedNamespace);
}
// logging prepare_schema_and_namespaces latency
if (initStatsBuilder != null) {
initStatsBuilder.setPrepareSchemaAndNamespacesLatencyMillis(
(int)
(SystemClock.elapsedRealtime()
- prepareSchemaAndNamespacesLatencyStartMillis));
}
mLogUtil.piiTrace("Init completed successfully");
} catch (AppSearchException e) {
// Some error. Reset and see if it fixes it.
resetLocked();
return;
Log.e(TAG, "Error initializing, resetting IcingSearchEngine.", e);
if (initStatsBuilder != null) {
initStatsBuilder.setStatusCode(e.getResultCode());
}
resetLocked(initStatsBuilder);
}
// Populate schema map
for (SchemaTypeConfigProto schema : schemaProto.getTypesList()) {
String prefixedSchemaType = schema.getSchemaType();
addToMap(mSchemaMapLocked, getPrefix(prefixedSchemaType), schema);
}
// Populate namespace map
for (String prefixedNamespace : getAllNamespacesResultProto.getNamespacesList()) {
addToMap(mNamespaceMapLocked, getPrefix(prefixedNamespace), prefixedNamespace);
}
// logging prepare_schema_and_namespaces latency
if (initStatsBuilder != null) {
initStatsBuilder.setPrepareSchemaAndNamespacesLatencyMillis(
(int)
(SystemClock.elapsedRealtime()
- prepareSchemaAndNamespacesLatencyStartMillis));
}
} finally {
mReadWriteLock.writeLock().unlock();
}
@@ -321,8 +351,9 @@ public final class AppSearchImpl implements Closeable {
mReadWriteLock.writeLock().lock();
try {
throwIfClosedLocked();
mLogUtil.piiTrace("Initializing VisibilityStore, request");
mVisibilityStoreLocked.initialize();
mLogUtil.piiTrace("Initializing VisibilityStore, response");
} finally {
mReadWriteLock.writeLock().unlock();
}
@@ -348,9 +379,10 @@ public final class AppSearchImpl implements Closeable {
if (mClosedLocked) {
return;
}
persistToDisk(PersistType.Code.FULL);
mLogUtil.piiTrace("icingSearchEngine.close, request");
mIcingSearchEngineLocked.close();
mLogUtil.piiTrace("icingSearchEngine.close, response");
mClosedLocked = true;
} catch (AppSearchException e) {
Log.w(TAG, "Error when closing AppSearchImpl.", e);
@@ -410,9 +442,12 @@ public final class AppSearchImpl implements Closeable {
rewriteSchema(prefix, existingSchemaBuilder, newSchemaBuilder.build());
// Apply schema
SchemaProto finalSchema = existingSchemaBuilder.build();
mLogUtil.piiTrace("setSchema, request", finalSchema.getTypesCount(), finalSchema);
SetSchemaResultProto setSchemaResultProto =
mIcingSearchEngineLocked.setSchema(
existingSchemaBuilder.build(), forceOverride);
mIcingSearchEngineLocked.setSchema(finalSchema, forceOverride);
mLogUtil.piiTrace(
"setSchema, response", setSchemaResultProto.getStatus(), setSchemaResultProto);
// Determine whether it succeeded.
try {
@@ -544,11 +579,16 @@ public final class AppSearchImpl implements Closeable {
mReadWriteLock.readLock().lock();
try {
throwIfClosedLocked();
mLogUtil.piiTrace("getAllNamespaces, request");
// We can't just use mNamespaceMap here because we have no way to prune namespaces from
// mNamespaceMap when they have no more documents (e.g. after setting schema to empty or
// using deleteByQuery).
GetAllNamespacesResultProto getAllNamespacesResultProto =
mIcingSearchEngineLocked.getAllNamespaces();
mLogUtil.piiTrace(
"getAllNamespaces, response",
getAllNamespacesResultProto.getNamespacesCount(),
getAllNamespacesResultProto);
checkSuccess(getAllNamespacesResultProto.getStatus());
String prefix = createPrefix(packageName, databaseName);
List<String> results = new ArrayList<>();
@@ -601,17 +641,18 @@ public final class AppSearchImpl implements Closeable {
String prefix = createPrefix(packageName, databaseName);
addPrefixToDocument(documentBuilder, prefix);
long rewriteDocumentTypeEndTimeMillis = SystemClock.elapsedRealtime();
DocumentProto finalDocument = documentBuilder.build();
mLogUtil.piiTrace("putDocument, request", finalDocument.getUri(), finalDocument);
PutResultProto putResultProto = mIcingSearchEngineLocked.put(documentBuilder.build());
mLogUtil.piiTrace("putDocument, response", putResultProto.getStatus(), putResultProto);
addToMap(mNamespaceMapLocked, prefix, documentBuilder.getNamespace());
// Logging stats
if (logger != null && pStatsBuilder != null) {
if (pStatsBuilder != null) {
pStatsBuilder
.getGeneralStatsBuilder()
.setStatusCode(
statusProtoToAppSearchException(putResultProto.getStatus())
.getResultCode());
.setStatusCode(statusProtoToResultCode(putResultProto.getStatus()));
pStatsBuilder
.setGenerateDocumentProtoLatencyMillis(
(int)
@@ -629,7 +670,7 @@ public final class AppSearchImpl implements Closeable {
} finally {
mReadWriteLock.writeLock().unlock();
if (logger != null && pStatsBuilder != null) {
if (logger != null) {
long totalEndTimeMillis = SystemClock.elapsedRealtime();
pStatsBuilder
.getGeneralStatsBuilder()
@@ -685,8 +726,14 @@ public final class AppSearchImpl implements Closeable {
.addAllTypePropertyMasks(prefixedPropertyMasks)
.build();
String finalNamespace = createPrefix(packageName, databaseName) + namespace;
if (mLogUtil.isPiiTraceEnabled()) {
mLogUtil.piiTrace(
"getDocument, request", finalNamespace + ", " + id + "," + getResultSpec);
}
GetResultProto getResultProto =
mIcingSearchEngineLocked.get(prefix + namespace, id, getResultSpec);
mIcingSearchEngineLocked.get(finalNamespace, id, getResultSpec);
mLogUtil.piiTrace("getDocument, response", getResultProto.getStatus(), getResultProto);
checkSuccess(getResultProto.getStatus());
// The schema type map cannot be null at this point. It could only be null if no
@@ -754,7 +801,7 @@ public final class AppSearchImpl implements Closeable {
sStatsBuilder);
} finally {
mReadWriteLock.readLock().unlock();
if (logger != null && sStatsBuilder != null) {
if (logger != null) {
sStatsBuilder.setTotalLatencyMillis(
(int) (SystemClock.elapsedRealtime() - totalLatencyStartMillis));
logger.logStats(sStatsBuilder.build());
@@ -832,9 +879,10 @@ public final class AppSearchImpl implements Closeable {
}
} else {
// Client didn't specify certain schemas to search over, check all schemas
Set<String> prefixedSchemas = mSchemaMapLocked.get(prefix).keySet();
Map<String, SchemaTypeConfigProto> prefixedSchemas =
mSchemaMapLocked.get(prefix);
if (prefixedSchemas != null) {
for (String prefixedSchema : prefixedSchemas) {
for (String prefixedSchema : prefixedSchemas.keySet()) {
if (packageName.equals(callerPackageName)
|| mVisibilityStoreLocked.isSchemaSearchableByCaller(
prefix, prefixedSchema, callerUid)) {
@@ -854,7 +902,7 @@ public final class AppSearchImpl implements Closeable {
} finally {
mReadWriteLock.readLock().unlock();
if (logger != null && sStatsBuilder != null) {
if (logger != null) {
sStatsBuilder.setTotalLatencyMillis(
(int) (SystemClock.elapsedRealtime() - totalLatencyStartMillis));
logger.logStats(sStatsBuilder.build());
@@ -938,18 +986,25 @@ public final class AppSearchImpl implements Closeable {
rewriteResultSpecForPrefixesLocked(resultSpecBuilder, prefixes, allowedPrefixedSchemas);
ScoringSpecProto scoringSpec = SearchSpecToProtoConverter.toScoringSpecProto(searchSpec);
SearchSpecProto finalSearchSpec = searchSpecBuilder.build();
ResultSpecProto finalResultSpec = resultSpecBuilder.build();
long rewriteSearchSpecLatencyEndMillis = SystemClock.elapsedRealtime();
if (mLogUtil.isPiiTraceEnabled()) {
mLogUtil.piiTrace(
"search, request",
finalSearchSpec.getQuery(),
finalSearchSpec + ", " + scoringSpec + ", " + finalResultSpec);
}
SearchResultProto searchResultProto =
mIcingSearchEngineLocked.search(
searchSpecBuilder.build(), scoringSpec, resultSpecBuilder.build());
mIcingSearchEngineLocked.search(finalSearchSpec, scoringSpec, finalResultSpec);
mLogUtil.piiTrace(
"search, response", searchResultProto.getResultsCount(), searchResultProto);
if (sStatsBuilder != null) {
sStatsBuilder
.setStatusCode(
statusProtoToAppSearchException(searchResultProto.getStatus())
.getResultCode())
.setStatusCode(statusProtoToResultCode(searchResultProto.getStatus()))
.setRewriteSearchSpecLatencyMillis(
(int)
(rewriteSearchSpecLatencyEndMillis
@@ -985,8 +1040,13 @@ public final class AppSearchImpl implements Closeable {
try {
throwIfClosedLocked();
mLogUtil.piiTrace("getNextPage, request", nextPageToken);
SearchResultProto searchResultProto =
mIcingSearchEngineLocked.getNextPage(nextPageToken);
mLogUtil.piiTrace(
"getNextPage, response",
searchResultProto.getResultsCount(),
searchResultProto);
checkSuccess(searchResultProto.getStatus());
return rewriteSearchResultProto(searchResultProto, mSchemaMapLocked);
} finally {
@@ -1007,6 +1067,7 @@ public final class AppSearchImpl implements Closeable {
try {
throwIfClosedLocked();
mLogUtil.piiTrace("invalidateNextPageToken, request", nextPageToken);
mIcingSearchEngineLocked.invalidateNextPageToken(nextPageToken);
} finally {
mReadWriteLock.readLock().unlock();
@@ -1039,7 +1100,9 @@ public final class AppSearchImpl implements Closeable {
.setUsageType(usageType)
.build();
mLogUtil.piiTrace("reportUsage, request", report.getDocumentUri(), report);
ReportUsageResultProto result = mIcingSearchEngineLocked.reportUsage(report);
mLogUtil.piiTrace("reportUsage, response", result.getStatus(), result);
checkSuccess(result.getStatus());
} finally {
mReadWriteLock.writeLock().unlock();
@@ -1068,9 +1131,13 @@ public final class AppSearchImpl implements Closeable {
throwIfClosedLocked();
String prefixedNamespace = createPrefix(packageName, databaseName) + namespace;
if (mLogUtil.isPiiTraceEnabled()) {
mLogUtil.piiTrace("removeById, request", prefixedNamespace + ", " + id);
}
DeleteResultProto deleteResultProto =
mIcingSearchEngineLocked.delete(prefixedNamespace, id);
mLogUtil.piiTrace(
"removeById, response", deleteResultProto.getStatus(), deleteResultProto);
checkSuccess(deleteResultProto.getStatus());
} finally {
mReadWriteLock.writeLock().unlock();
@@ -1121,8 +1188,12 @@ public final class AppSearchImpl implements Closeable {
searchSpecBuilder, Collections.singleton(prefix), allowedPrefixedSchemas)) {
return;
}
SearchSpecProto finalSearchSpec = searchSpecBuilder.build();
mLogUtil.piiTrace("removeByQuery, request", finalSearchSpec);
DeleteByQueryResultProto deleteResultProto =
mIcingSearchEngineLocked.deleteByQuery(searchSpecBuilder.build());
mIcingSearchEngineLocked.deleteByQuery(finalSearchSpec);
mLogUtil.piiTrace(
"removeByQuery, response", deleteResultProto.getStatus(), deleteResultProto);
// 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.
@@ -1202,7 +1273,10 @@ public final class AppSearchImpl implements Closeable {
@NonNull
private StorageInfo getStorageInfoForNamespacesLocked(@NonNull Set<String> prefixedNamespaces)
throws AppSearchException {
mLogUtil.piiTrace("getStorageInfo, request");
StorageInfoResultProto storageInfoResult = mIcingSearchEngineLocked.getStorageInfo();
mLogUtil.piiTrace(
"getStorageInfo, response", storageInfoResult.getStatus(), storageInfoResult);
checkSuccess(storageInfoResult.getStatus());
if (!storageInfoResult.hasStorageInfo()
|| !storageInfoResult.getStorageInfo().hasDocumentStorageInfo()) {
@@ -1277,8 +1351,13 @@ public final class AppSearchImpl implements Closeable {
try {
throwIfClosedLocked();
mLogUtil.piiTrace("persistToDisk, request", persistType);
PersistToDiskResultProto persistToDiskResultProto =
mIcingSearchEngineLocked.persistToDisk(persistType);
mLogUtil.piiTrace(
"persistToDisk, response",
persistToDiskResultProto.getStatus(),
persistToDiskResultProto);
checkSuccess(persistToDiskResultProto.getStatus());
} finally {
mReadWriteLock.writeLock().unlock();
@@ -1305,12 +1384,21 @@ public final class AppSearchImpl implements Closeable {
newSchemaBuilder.addTypes(existingSchema.getTypes(i));
}
}
SchemaProto finalSchema = newSchemaBuilder.build();
// Apply schema, set force override to true to remove all schemas and documents under
// that package.
mLogUtil.piiTrace(
"clearPackageData.setSchema, request",
finalSchema.getTypesCount(),
finalSchema);
SetSchemaResultProto setSchemaResultProto =
mIcingSearchEngineLocked.setSchema(
newSchemaBuilder.build(), /*ignoreErrorsAndDeleteDocuments=*/ true);
finalSchema, /*ignoreErrorsAndDeleteDocuments=*/ true);
mLogUtil.piiTrace(
"clearPackageData.setSchema, response",
setSchemaResultProto.getStatus(),
setSchemaResultProto);
// Determine whether it succeeded.
checkSuccess(setSchemaResultProto.getStatus());
@@ -1330,12 +1418,24 @@ public final class AppSearchImpl implements Closeable {
* @throws AppSearchException on IcingSearchEngine error.
*/
@GuardedBy("mReadWriteLock")
private void resetLocked() throws AppSearchException {
private void resetLocked(@Nullable InitializeStats.Builder initStatsBuilder)
throws AppSearchException {
mLogUtil.piiTrace("icingSearchEngine.reset, request");
ResetResultProto resetResultProto = mIcingSearchEngineLocked.reset();
mLogUtil.piiTrace(
"icingSearchEngine.reset, response",
resetResultProto.getStatus(),
resetResultProto);
mOptimizeIntervalCountLocked = 0;
mSchemaMapLocked.clear();
mNamespaceMapLocked.clear();
if (initStatsBuilder != null) {
initStatsBuilder
.setHasReset(true)
.setResetStatusCode(statusProtoToResultCode(resetResultProto.getStatus()));
}
// Must be called after everything else since VisibilityStore may repopulate
// IcingSearchEngine with an initial schema.
mVisibilityStoreLocked.handleReset();
@@ -1472,15 +1572,17 @@ public final class AppSearchImpl implements Closeable {
// Empty namespaces on the search spec means to query over all namespaces.
Set<String> existingNamespaces = mNamespaceMapLocked.get(prefix);
if (namespaceFilters.isEmpty()) {
// Include all namespaces
searchSpecBuilder.addAllNamespaceFilters(existingNamespaces);
} else {
// Prefix the given namespaces.
for (int i = 0; i < namespaceFilters.size(); i++) {
String prefixedNamespace = prefix + namespaceFilters.get(i);
if (existingNamespaces.contains(prefixedNamespace)) {
searchSpecBuilder.addNamespaceFilters(prefixedNamespace);
if (existingNamespaces != null) {
if (namespaceFilters.isEmpty()) {
// Include all namespaces
searchSpecBuilder.addAllNamespaceFilters(existingNamespaces);
} else {
// Prefix the given namespaces.
for (int i = 0; i < namespaceFilters.size(); i++) {
String prefixedNamespace = prefix + namespaceFilters.get(i);
if (existingNamespaces.contains(prefixedNamespace)) {
searchSpecBuilder.addNamespaceFilters(prefixedNamespace);
}
}
}
}
@@ -1581,6 +1683,9 @@ public final class AppSearchImpl implements Closeable {
Map<String, List<String>> packageAndNamespaceToNamespaces = new ArrayMap<>();
for (String prefix : existingPrefixes) {
Set<String> prefixedNamespaces = mNamespaceMapLocked.get(prefix);
if (prefixedNamespaces == null) {
continue;
}
String packageName = getPackageName(prefix);
// Create a new prefix without the database name. This will allow us to group namespaces
// that have the same name and package but a different database name together.
@@ -1636,6 +1741,9 @@ public final class AppSearchImpl implements Closeable {
Map<String, List<String>> packageToNamespacesMap = new ArrayMap<>();
for (String prefix : existingPrefixes) {
Set<String> prefixedNamespaces = mNamespaceMapLocked.get(prefix);
if (prefixedNamespaces == null) {
continue;
}
String packageName = getPackageName(prefix);
List<String> packageNamespaceList = packageToNamespacesMap.get(packageName);
if (packageNamespaceList == null) {
@@ -1677,6 +1785,9 @@ public final class AppSearchImpl implements Closeable {
Map<String, List<String>> namespaceToPrefixedNamespaces = new ArrayMap<>();
for (String prefix : existingPrefixes) {
Set<String> prefixedNamespaces = mNamespaceMapLocked.get(prefix);
if (prefixedNamespaces == null) {
continue;
}
for (String prefixedNamespace : prefixedNamespaces) {
String namespace;
try {
@@ -1707,7 +1818,9 @@ public final class AppSearchImpl implements Closeable {
@VisibleForTesting
@GuardedBy("mReadWriteLock")
SchemaProto getSchemaProtoLocked() throws AppSearchException {
mLogUtil.piiTrace("getSchema, request");
GetSchemaResultProto schemaProto = mIcingSearchEngineLocked.getSchema();
mLogUtil.piiTrace("getSchema, response", schemaProto.getStatus(), schemaProto);
// TODO(b/161935693) check GetSchemaResultProto is success or not. Call reset() if it's not.
// TODO(b/161935693) only allow GetSchemaResultProto NOT_FOUND on first run
checkCodeOneOf(schemaProto.getStatus(), StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
@@ -1781,7 +1894,9 @@ public final class AppSearchImpl implements Closeable {
return;
}
throw statusProtoToAppSearchException(statusProto);
throw new AppSearchException(
ResultCodeToProtoConverter.toResultCode(statusProto.getCode()),
statusProto.getMessage());
}
/**
@@ -1849,7 +1964,10 @@ public final class AppSearchImpl implements Closeable {
public void optimize() throws AppSearchException {
mReadWriteLock.writeLock().lock();
try {
mLogUtil.piiTrace("optimize, request");
OptimizeResultProto optimizeResultProto = mIcingSearchEngineLocked.optimize();
mLogUtil.piiTrace(
"optimize, response", optimizeResultProto.getStatus(), optimizeResultProto);
checkSuccess(optimizeResultProto.getStatus());
} finally {
mReadWriteLock.writeLock().unlock();
@@ -1887,7 +2005,10 @@ public final class AppSearchImpl implements Closeable {
@GuardedBy("mReadWriteLock")
@VisibleForTesting
GetOptimizeInfoResultProto getOptimizeInfoResultLocked() {
return mIcingSearchEngineLocked.getOptimizeInfo();
mLogUtil.piiTrace("getOptimizeInfo, request");
GetOptimizeInfoResultProto result = mIcingSearchEngineLocked.getOptimizeInfo();
mLogUtil.piiTrace("getOptimizeInfo, response", result.getStatus(), result);
return result;
}
@GuardedBy("mReadWriteLock")
@@ -1898,16 +2019,16 @@ public final class AppSearchImpl implements Closeable {
}
/**
* Converts an erroneous status code to an AppSearchException. Callers should ensure that the
* status code is not OK or WARNING_DATA_LOSS.
* Converts an erroneous status code from the Icing status enums to the AppSearchResult enums.
*
* @param statusProto StatusProto with error code and message to translate into
* AppSearchException.
* @return AppSearchException with the parallel error code.
* <p>Callers should ensure that the status code is not OK or WARNING_DATA_LOSS.
*
* @param statusProto StatusProto with error code to translate into an {@link AppSearchResult}
* code.
* @return {@link AppSearchResult} error code
*/
private static AppSearchException statusProtoToAppSearchException(StatusProto statusProto) {
return new AppSearchException(
ResultCodeToProtoConverter.toResultCode(statusProto.getCode()),
statusProto.getMessage());
private static @AppSearchResult.ResultCode int statusProtoToResultCode(
@NonNull StatusProto statusProto) {
return ResultCodeToProtoConverter.toResultCode(statusProto.getCode());
}
}

View File

@@ -106,6 +106,10 @@ public final class InitializeStats {
private final int mNativeNumDocuments;
/** Returns number of schema types currently in the schema store. */
private final int mNativeNumSchemaTypes;
/** Whether we had to reset the index, losing all data, during initialization. */
private final boolean mHasReset;
/** If we had to reset, contains the status code of the reset operation. */
@AppSearchResult.ResultCode private final int mResetStatusCode;
/** Returns the status of the initialization. */
@AppSearchResult.ResultCode
@@ -214,6 +218,21 @@ public final class InitializeStats {
return mNativeNumSchemaTypes;
}
/** Returns whether we had to reset the index, losing all data, as part of initialization. */
public boolean hasReset() {
return mHasReset;
}
/**
* Returns the status of the reset, if one was performed according to {@link #hasReset}.
*
* <p>If no value has been set, the default value is {@link AppSearchResult#RESULT_OK}.
*/
@AppSearchResult.ResultCode
public int getResetStatusCode() {
return mResetStatusCode;
}
InitializeStats(@NonNull Builder builder) {
Objects.requireNonNull(builder);
mStatusCode = builder.mStatusCode;
@@ -232,11 +251,14 @@ public final class InitializeStats {
mNativeDocumentStoreDataStatus = builder.mNativeDocumentStoreDataStatus;
mNativeNumDocuments = builder.mNativeNumDocuments;
mNativeNumSchemaTypes = builder.mNativeNumSchemaTypes;
mHasReset = builder.mHasReset;
mResetStatusCode = builder.mResetStatusCode;
}
/** Builder for {@link InitializeStats}. */
public static class Builder {
@AppSearchResult.ResultCode int mStatusCode;
int mTotalLatencyMillis;
boolean mHasDeSync;
int mPrepareSchemaAndNamespacesLatencyMillis;
@@ -251,6 +273,8 @@ public final class InitializeStats {
@DocumentStoreDataStatus int mNativeDocumentStoreDataStatus;
int mNativeNumDocuments;
int mNativeNumSchemaTypes;
boolean mHasReset;
@AppSearchResult.ResultCode int mResetStatusCode;
/** Sets the status of the initialization. */
@NonNull
@@ -392,6 +416,20 @@ public final class InitializeStats {
return this;
}
/** Sets whether we had to reset the index, losing all data, as part of initialization. */
@NonNull
public Builder setHasReset(boolean hasReset) {
mHasReset = hasReset;
return this;
}
/** Sets the status of the reset, if one was performed according to {@link #setHasReset}. */
@NonNull
public Builder setResetStatusCode(@AppSearchResult.ResultCode int resetStatusCode) {
mResetStatusCode = resetStatusCode;
return this;
}
/**
* Constructs a new {@link InitializeStats} from the contents of this {@link
* InitializeStats.Builder}

View File

@@ -112,8 +112,11 @@ public class PrefixUtil {
return prefixedString.substring(delimiterIndex + 1);
}
throw new AppSearchException(
AppSearchResult.RESULT_UNKNOWN_ERROR,
"The prefixed value doesn't contains a valid database name.");
AppSearchResult.RESULT_INTERNAL_ERROR,
"The prefixed value \""
+ prefixedString
+ "\" doesn't contain a valid "
+ "database name");
}
/**
@@ -128,8 +131,11 @@ public class PrefixUtil {
int databaseDelimiterIndex = prefixedString.indexOf(DATABASE_DELIMITER);
if (databaseDelimiterIndex == -1) {
throw new AppSearchException(
AppSearchResult.RESULT_UNKNOWN_ERROR,
"The databaseName prefixed value doesn't contain a valid database name.");
AppSearchResult.RESULT_INTERNAL_ERROR,
"The prefixed value \""
+ prefixedString
+ "\" doesn't contain a valid "
+ "database name");
}
// Add 1 to include the char size of the DATABASE_DELIMITER

View File

@@ -1 +1 @@
I0216abecc41d020f16ed8947a9f37b710afd331e
a83c33a5a394141fea1d065ce0fab513a62d4bcf

View File

@@ -30,6 +30,9 @@ import java.util.Set;
* <p>An {@link AppSearchSessionShim} instance provides access to database operations such as
* setting a schema, adding documents, and searching.
*
* <p>Instances of this interface are usually obtained from a storage implementation, e.g. {@code
* AppSearchManager.createSearchSession()} or {@code PlatformStorage.createSearchSession()}.
*
* <p>All implementations of this interface must be thread safe.
*
* @see GlobalSearchSessionShim

View File

@@ -1,186 +0,0 @@
/*
* 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 android.util.ArrayMap;
import org.junit.Test;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class SetSchemaRequestTest {
private static Collection<String> getSchemaTypesFromSetSchemaRequest(SetSchemaRequest request) {
HashSet<String> schemaTypes = new HashSet<>();
for (AppSearchSchema schema : request.getSchemas()) {
schemaTypes.add(schema.getSchemaType());
}
return schemaTypes;
}
@Test
public void testInvalidSchemaReferences_fromDisplayedBySystem() {
IllegalArgumentException expected =
expectThrows(
IllegalArgumentException.class,
() ->
new SetSchemaRequest.Builder()
.setSchemaTypeDisplayedBySystem("InvalidSchema", false)
.build());
assertThat(expected).hasMessageThat().contains("referenced, but were not added");
}
@Test
public void testInvalidSchemaReferences_fromPackageVisibility() {
IllegalArgumentException expected =
expectThrows(
IllegalArgumentException.class,
() ->
new SetSchemaRequest.Builder()
.setSchemaTypeVisibilityForPackage(
"InvalidSchema",
/*visible=*/ true,
new PackageIdentifier(
"com.foo.package",
/*sha256Certificate=*/ new byte[] {}))
.build());
assertThat(expected).hasMessageThat().contains("referenced, but were not added");
}
@Test
public void testSetSchemaTypeDisplayedBySystem_displayed() {
AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
// By default, the schema is displayed.
SetSchemaRequest request = new SetSchemaRequest.Builder().addSchemas(schema).build();
assertThat(request.getSchemasNotDisplayedBySystem()).isEmpty();
request =
new SetSchemaRequest.Builder()
.addSchemas(schema)
.setSchemaTypeDisplayedBySystem("Schema", true)
.build();
assertThat(request.getSchemasNotDisplayedBySystem()).isEmpty();
}
@Test
public void testSetSchemaTypeDisplayedBySystem_notDisplayed() {
AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
SetSchemaRequest request =
new SetSchemaRequest.Builder()
.addSchemas(schema)
.setSchemaTypeDisplayedBySystem("Schema", false)
.build();
assertThat(request.getSchemasNotDisplayedBySystem()).containsExactly("Schema");
}
@Test
public void testSchemaTypeVisibilityForPackage_visible() {
AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
// By default, the schema is not visible.
SetSchemaRequest request = new SetSchemaRequest.Builder().addSchemas(schema).build();
assertThat(request.getSchemasVisibleToPackages()).isEmpty();
PackageIdentifier packageIdentifier =
new PackageIdentifier("com.package.foo", new byte[] {100});
Map<String, Set<PackageIdentifier>> expectedVisibleToPackagesMap = new ArrayMap<>();
expectedVisibleToPackagesMap.put("Schema", Collections.singleton(packageIdentifier));
request =
new SetSchemaRequest.Builder()
.addSchemas(schema)
.setSchemaTypeVisibilityForPackage(
"Schema", /*visible=*/ true, packageIdentifier)
.build();
assertThat(request.getSchemasVisibleToPackages())
.containsExactlyEntriesIn(expectedVisibleToPackagesMap);
}
@Test
public void testSchemaTypeVisibilityForPackage_notVisible() {
AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
SetSchemaRequest request =
new SetSchemaRequest.Builder()
.addSchemas(schema)
.setSchemaTypeVisibilityForPackage(
"Schema",
/*visible=*/ false,
new PackageIdentifier(
"com.package.foo", /*sha256Certificate=*/ new byte[] {}))
.build();
assertThat(request.getSchemasVisibleToPackages()).isEmpty();
}
@Test
public void testSchemaTypeVisibilityForPackage_deduped() throws Exception {
AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
PackageIdentifier packageIdentifier =
new PackageIdentifier("com.package.foo", new byte[] {100});
Map<String, Set<PackageIdentifier>> expectedVisibleToPackagesMap = new ArrayMap<>();
expectedVisibleToPackagesMap.put("Schema", Collections.singleton(packageIdentifier));
SetSchemaRequest request =
new SetSchemaRequest.Builder()
.addSchemas(schema)
// Set it visible for "Schema"
.setSchemaTypeVisibilityForPackage(
"Schema", /*visible=*/ true, packageIdentifier)
// Set it visible for "Schema" again, which should be a no-op
.setSchemaTypeVisibilityForPackage(
"Schema", /*visible=*/ true, packageIdentifier)
.build();
assertThat(request.getSchemasVisibleToPackages())
.containsExactlyEntriesIn(expectedVisibleToPackagesMap);
}
@Test
public void testSchemaTypeVisibilityForPackage_removed() throws Exception {
AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
SetSchemaRequest request =
new SetSchemaRequest.Builder()
.addSchemas(schema)
// First set it as visible
.setSchemaTypeVisibilityForPackage(
"Schema",
/*visible=*/ true,
new PackageIdentifier(
"com.package.foo", /*sha256Certificate=*/ new byte[] {100}))
// Then make it not visible
.setSchemaTypeVisibilityForPackage(
"Schema",
/*visible=*/ false,
new PackageIdentifier(
"com.package.foo", /*sha256Certificate=*/ new byte[] {100}))
.build();
// Nothing should be visible.
assertThat(request.getSchemasVisibleToPackages()).isEmpty();
}
}

View File

@@ -24,6 +24,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.testng.Assert.expectThrows;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.SearchResult;
@@ -39,16 +40,19 @@ import android.util.ArraySet;
import androidx.test.core.app.ApplicationProvider;
import com.android.server.appsearch.external.localstorage.converter.GenericDocumentToProtoConverter;
import com.android.server.appsearch.external.localstorage.stats.InitializeStats;
import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
import com.android.server.appsearch.proto.DocumentProto;
import com.android.server.appsearch.proto.GetOptimizeInfoResultProto;
import com.android.server.appsearch.proto.PersistType;
import com.android.server.appsearch.proto.PropertyConfigProto;
import com.android.server.appsearch.proto.PropertyProto;
import com.android.server.appsearch.proto.PutResultProto;
import com.android.server.appsearch.proto.SchemaProto;
import com.android.server.appsearch.proto.SchemaTypeConfigProto;
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.StringIndexingConfig;
import com.android.server.appsearch.proto.TermMatchType;
@@ -86,8 +90,6 @@ public class AppSearchImplTest {
/*logger=*/ null);
}
// TODO(b/175430168) add test to verify reset is working properly.
/**
* Ensure that we can rewrite an incoming schema type by adding the database as a prefix. While
* also keeping any other existing schema types that may already be part of Icing's persisted
@@ -485,6 +487,142 @@ public class AppSearchImplTest {
.isLessThan(AppSearchImpl.CHECK_OPTIMIZE_INTERVAL);
}
@Test
public void testReset() throws Exception {
// Setup the index
Context context = ApplicationProvider.getApplicationContext();
File appsearchDir = mTemporaryFolder.newFolder();
AppSearchImpl appSearchImpl =
AppSearchImpl.create(
appsearchDir,
context,
VisibilityStore.NO_OP_USER_ID,
/*globalQuerierPackage=*/ "",
/*logger=*/ null);
// Insert schema
List<AppSearchSchema> schemas =
ImmutableList.of(
new AppSearchSchema.Builder("Type1").build(),
new AppSearchSchema.Builder("Type2").build());
appSearchImpl.setSchema(
context.getPackageName(),
"database1",
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert a valid doc
GenericDocument validDoc =
new GenericDocument.Builder<>("namespace1", "id1", "Type1").build();
appSearchImpl.putDocument(
context.getPackageName(), "database1", validDoc, /*logger=*/ null);
// Query it via global query. We use the same code again later so this is to make sure we
// have our global query configured right.
SearchResultPage results =
appSearchImpl.globalQuery(
/*queryExpression=*/ "",
new SearchSpec.Builder().addFilterSchemas("Type1").build(),
context.getPackageName(),
VisibilityStore.NO_OP_USER_ID,
/*logger=*/ null);
assertThat(results.getResults()).hasSize(1);
assertThat(results.getResults().get(0).getGenericDocument()).isEqualTo(validDoc);
// Create a doc with a malformed namespace
DocumentProto invalidDoc =
DocumentProto.newBuilder()
.setNamespace("invalidNamespace")
.setUri("id2")
.setSchema(context.getPackageName() + "$database1/Type1")
.build();
AppSearchException e =
expectThrows(
AppSearchException.class,
() -> PrefixUtil.getPrefix(invalidDoc.getNamespace()));
assertThat(e)
.hasMessageThat()
.isEqualTo(
"The prefixed value \"invalidNamespace\" doesn't contain a valid database"
+ " name");
// Insert the invalid doc with an invalid namespace right into icing
PutResultProto putResultProto = appSearchImpl.mIcingSearchEngineLocked.put(invalidDoc);
assertThat(putResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.OK);
// Create a logger for capturing initialization to make sure we are logging the recovery
// process correctly.
AppSearchLoggerTest.TestLogger testLogger = new AppSearchLoggerTest.TestLogger();
// Initialize AppSearchImpl. This should cause a reset.
appSearchImpl.close();
appSearchImpl =
AppSearchImpl.create(
appsearchDir,
context,
VisibilityStore.NO_OP_USER_ID,
/*globalQuerierPackage=*/ context.getPackageName(),
testLogger);
// Check recovery state
InitializeStats initStats = testLogger.mInitializeStats;
assertThat(initStats).isNotNull();
assertThat(initStats.getStatusCode()).isEqualTo(AppSearchResult.RESULT_INTERNAL_ERROR);
assertThat(initStats.hasDeSync()).isFalse();
assertThat(initStats.getDocumentStoreRecoveryCause())
.isEqualTo(InitializeStats.RECOVERY_CAUSE_NONE);
// TODO(b/187879464): There should not be a recovery here, but icing lib reports one if the
// doc had no tokens. Once the mentioned bug is fixed, uncomment this.
// assertThat(initStats.getIndexRestorationCause())
// .isEqualTo(InitializeStats.RECOVERY_CAUSE_NONE);
assertThat(initStats.getSchemaStoreRecoveryCause())
.isEqualTo(InitializeStats.RECOVERY_CAUSE_NONE);
assertThat(initStats.getDocumentStoreDataStatus())
.isEqualTo(InitializeStats.DOCUMENT_STORE_DATA_STATUS_NO_DATA_LOSS);
assertThat(initStats.hasReset()).isTrue();
assertThat(initStats.getResetStatusCode()).isEqualTo(AppSearchResult.RESULT_OK);
// Make sure all our data is gone
assertThat(appSearchImpl.getSchema(context.getPackageName(), "database1").getSchemas())
.isEmpty();
results =
appSearchImpl.globalQuery(
/*queryExpression=*/ "",
new SearchSpec.Builder().addFilterSchemas("Type1").build(),
context.getPackageName(),
VisibilityStore.NO_OP_USER_ID,
/*logger=*/ null);
assertThat(results.getResults()).isEmpty();
// Make sure the index can now be used successfully
appSearchImpl.setSchema(
context.getPackageName(),
"database1",
Collections.singletonList(new AppSearchSchema.Builder("Type1").build()),
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert a valid doc
appSearchImpl.putDocument(
context.getPackageName(), "database1", validDoc, /*logger=*/ null);
// Query it via global query.
results =
appSearchImpl.globalQuery(
/*queryExpression=*/ "",
new SearchSpec.Builder().addFilterSchemas("Type1").build(),
context.getPackageName(),
VisibilityStore.NO_OP_USER_ID,
/*logger=*/ null);
assertThat(results.getResults()).hasSize(1);
assertThat(results.getResults().get(0).getGenericDocument()).isEqualTo(validDoc);
}
@Test
public void testRewriteSearchSpec_oneInstance() throws Exception {
SearchSpecProto.Builder searchSpecProto = SearchSpecProto.newBuilder().setQuery("");

View File

@@ -68,7 +68,7 @@ public class AppSearchLoggerTest {
}
// Test only not thread safe.
public class TestLogger implements AppSearchLogger {
public static class TestLogger implements AppSearchLogger {
@Nullable CallStats mCallStats;
@Nullable PutDocumentStats mPutDocumentStats;
@Nullable InitializeStats mInitializeStats;

View File

@@ -168,7 +168,9 @@ public class AppSearchStatsTest {
.setSchemaStoreRecoveryLatencyMillis(nativeSchemaStoreRecoveryLatencyMillis)
.setDocumentStoreDataStatus(nativeDocumentStoreDataStatus)
.setDocumentCount(nativeNumDocuments)
.setSchemaTypeCount(nativeNumSchemaTypes);
.setSchemaTypeCount(nativeNumSchemaTypes)
.setHasReset(true)
.setResetStatusCode(AppSearchResult.RESULT_INVALID_SCHEMA);
final InitializeStats iStats = iStatsBuilder.build();
assertThat(iStats.getStatusCode()).isEqualTo(TEST_STATUS_CODE);
@@ -192,6 +194,8 @@ public class AppSearchStatsTest {
assertThat(iStats.getDocumentStoreDataStatus()).isEqualTo(nativeDocumentStoreDataStatus);
assertThat(iStats.getDocumentCount()).isEqualTo(nativeNumDocuments);
assertThat(iStats.getSchemaTypeCount()).isEqualTo(nativeNumSchemaTypes);
assertThat(iStats.hasReset()).isTrue();
assertThat(iStats.getResetStatusCode()).isEqualTo(AppSearchResult.RESULT_INVALID_SCHEMA);
}
@Test