Loading apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java +40 −11 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * 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. Loading @@ -22,6 +22,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; import com.android.internal.util.Preconditions; import java.util.Collections; import java.util.Map; Loading @@ -33,11 +35,11 @@ import java.util.Map; * @param <ValueType> The type of result objects associated with the keys. * @hide */ public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable { public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelable { @NonNull private final Map<KeyType, ValueType> mSuccesses; @NonNull private final Map<KeyType, AppSearchResult<ValueType>> mFailures; private AppSearchBatchResult( AppSearchBatchResult( @NonNull Map<KeyType, ValueType> successes, @NonNull Map<KeyType, AppSearchResult<ValueType>> failures) { mSuccesses = successes; Loading @@ -61,8 +63,8 @@ public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable { } /** * Returns a {@link Map} of all successful keys mapped to the successful {@link ValueType} * values they produced. * Returns a {@link Map} of all successful keys mapped to the successful * {@link AppSearchResult}s they produced. * * <p>The values of the {@link Map} will not be {@code null}. */ Loading @@ -82,6 +84,22 @@ public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable { return mFailures; } /** * Asserts that this {@link AppSearchBatchResult} has no failures. * @hide */ public void checkSuccess() { if (!isSuccess()) { throw new IllegalStateException("AppSearchBatchResult has failures: " + this); } } @Override @NonNull public String toString() { return "{\n successes: " + mSuccesses + "\n failures: " + mFailures + "\n}"; } @Override public int describeContents() { return 0; Loading Loading @@ -112,16 +130,18 @@ public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable { public static final class Builder<KeyType, ValueType> { private final Map<KeyType, ValueType> mSuccesses = new ArrayMap<>(); private final Map<KeyType, AppSearchResult<ValueType>> mFailures = new ArrayMap<>(); /** Creates a new {@link Builder} for this {@link AppSearchBatchResult}. */ public Builder() {} private boolean mBuilt = false; /** * Associates the {@code key} with the given successful return value. * * <p>Any previous mapping for a key, whether success or failure, is deleted. */ public Builder setSuccess(@NonNull KeyType key, @Nullable ValueType result) { @NonNull public Builder<KeyType, ValueType> setSuccess( @NonNull KeyType key, @Nullable ValueType result) { Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkNotNull(key); return setResult(key, AppSearchResult.newSuccessfulResult(result)); } Loading @@ -130,10 +150,13 @@ public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable { * * <p>Any previous mapping for a key, whether success or failure, is deleted. */ public Builder setFailure( @NonNull public Builder<KeyType, ValueType> setFailure( @NonNull KeyType key, @AppSearchResult.ResultCode int resultCode, @Nullable String errorMessage) { Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkNotNull(key); return setResult(key, AppSearchResult.newFailedResult(resultCode, errorMessage)); } Loading @@ -143,7 +166,11 @@ public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable { * <p>Any previous mapping for a key, whether success or failure, is deleted. */ @NonNull public Builder setResult(@NonNull KeyType key, @NonNull AppSearchResult<ValueType> result) { public Builder<KeyType, ValueType> setResult( @NonNull KeyType key, @NonNull AppSearchResult<ValueType> result) { Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkNotNull(key); Preconditions.checkNotNull(result); if (result.isSuccess()) { mSuccesses.put(key, result.getResultValue()); mFailures.remove(key); Loading @@ -157,6 +184,8 @@ public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable { /** Builds an {@link AppSearchBatchResult} from the contents of this {@link Builder}. */ @NonNull public AppSearchBatchResult<KeyType, ValueType> build() { Preconditions.checkState(!mBuilt, "Builder has already been used"); mBuilt = true; return new AppSearchBatchResult<>(mSuccesses, mFailures); } } Loading apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java +19 −13 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * 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. Loading Loading @@ -32,9 +32,12 @@ import java.util.Objects; * @param <ValueType> The type of result object for successful calls. * @hide */ public class AppSearchResult<ValueType> implements Parcelable { /** Result codes from {@link AppSearchManager} methods. */ @IntDef(prefix = {"RESULT_"}, value = { public final class AppSearchResult<ValueType> implements Parcelable { /** * Result codes from {@link AppSearchManager} methods. * @hide */ @IntDef(value = { RESULT_OK, RESULT_UNKNOWN_ERROR, RESULT_INTERNAL_ERROR, Loading Loading @@ -120,15 +123,18 @@ public class AppSearchResult<ValueType> implements Parcelable { } /** * Returns the returned value associated with this result. * Returns the result value associated with this result, if it was successful. * * <p>If {@link #isSuccess} is {@code false}, the result value is always {@code null}. The value * may be {@code null} even if {@link #isSuccess} is {@code true}. See the documentation of the * particular {@link AppSearchManager} call producing this {@link AppSearchResult} for what is * returned by {@link #getResultValue}. * <p>See the documentation of the particular {@link AppSearchManager} call producing this * {@link AppSearchResult} for what is placed in the result value by that call. * * @throws IllegalStateException if this {@link AppSearchResult} is not successful. */ @Nullable public ValueType getResultValue() { if (!isSuccess()) { throw new IllegalStateException("AppSearchResult is a failure: " + this); } return mResultValue; } Loading @@ -146,14 +152,14 @@ public class AppSearchResult<ValueType> implements Parcelable { } @Override public boolean equals(Object other) { public boolean equals(@Nullable Object other) { if (this == other) { return true; } if (!(other instanceof AppSearchResult)) { return false; } AppSearchResult<?> otherResult = (AppSearchResult) other; AppSearchResult<?> otherResult = (AppSearchResult<?>) other; return mResultCode == otherResult.mResultCode && Objects.equals(mResultValue, otherResult.mResultValue) && Objects.equals(mErrorMessage, otherResult.mErrorMessage); Loading @@ -168,9 +174,9 @@ public class AppSearchResult<ValueType> implements Parcelable { @NonNull public String toString() { if (isSuccess()) { return "AppSearchResult [SUCCESS]: " + mResultValue; return "[SUCCESS]: " + mResultValue; } return "AppSearchResult [FAILURE(" + mResultCode + ")]: " + mErrorMessage; return "[FAILURE(" + mResultCode + ")]: " + mErrorMessage; } @Override Loading apex/appsearch/framework/java/android/app/appsearch/exceptions/AppSearchException.java +4 −0 Original line number Diff line number Diff line Loading @@ -54,6 +54,10 @@ public class AppSearchException extends Exception { mResultCode = resultCode; } public @AppSearchResult.ResultCode int getResultCode() { return mResultCode; } /** * Converts this {@link java.lang.Exception} into a failed {@link AppSearchResult} */ Loading apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java +1 −3 Original line number Diff line number Diff line Loading @@ -69,9 +69,7 @@ public final class ImplInstanceManager { private static AppSearchImpl createImpl(@NonNull Context context, @UserIdInt int userId) throws AppSearchException { File appSearchDir = getAppSearchDir(context, userId); AppSearchImpl appSearchImpl = new AppSearchImpl(appSearchDir); appSearchImpl.initialize(); return appSearchImpl; return AppSearchImpl.create(appSearchDir); } private static File getAppSearchDir(@NonNull Context context, @UserIdInt int userId) { Loading apex/appsearch/service/java/com/android/server/appsearch/external/localbackend/AppSearchImpl.java +52 −83 Original line number Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.server.appsearch.external.localbackend; import android.util.Log; import android.annotation.AnyThread; import com.android.internal.annotations.GuardedBy; import android.annotation.NonNull; import android.annotation.Nullable; Loading @@ -27,6 +26,7 @@ import com.android.internal.annotations.VisibleForTesting; import android.annotation.WorkerThread; import android.app.appsearch.AppSearchResult; import android.app.appsearch.exceptions.AppSearchException; import com.android.internal.util.Preconditions; import com.google.android.icing.IcingSearchEngine; import com.google.android.icing.proto.DeleteByNamespaceResultProto; Loading Loading @@ -58,7 +58,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; Loading @@ -66,8 +65,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; * Manages interaction with the native IcingSearchEngine and other components to implement AppSearch * functionality. * * <p>Callers should call {@link #initialize} before using the AppSearchImpl instance. Never create * two instances using the same folder. * <p>Never create two instances using the same folder. * * <p>A single instance of {@link AppSearchImpl} can support all databases. Schemas and documents * are physically saved together in {@link IcingSearchEngine}, but logically isolated: Loading Loading @@ -106,9 +104,7 @@ public final class AppSearchImpl { static final int CHECK_OPTIMIZE_INTERVAL = 100; private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock(); private final CountDownLatch mInitCompleteLatch = new CountDownLatch(1); private final File mIcingDir; private IcingSearchEngine mIcingSearchEngine; private final IcingSearchEngine mIcingSearchEngine; // The map contains schemaTypes and namespaces for all database. All values in the map have // been already added database name prefix. Loading @@ -121,33 +117,24 @@ public final class AppSearchImpl { */ private int mOptimizeIntervalCount = 0; /** Creates an instance of {@link AppSearchImpl} which writes data to the given folder. */ @AnyThread public AppSearchImpl(@NonNull File icingDir) { mIcingDir = icingDir; } /** * Initializes the underlying IcingSearchEngine. * * <p>This method belongs to mutate group. * * @throws AppSearchException on IcingSearchEngine error. * Creates and initializes an instance of {@link AppSearchImpl} which writes data to the given * folder. */ public void initialize() throws AppSearchException { if (isInitialized()) { return; @NonNull public static AppSearchImpl create(@NonNull File icingDir) throws AppSearchException { Preconditions.checkNotNull(icingDir); return new AppSearchImpl(icingDir); } private AppSearchImpl(@NonNull File icingDir) throws AppSearchException { boolean isReset = false; mReadWriteLock.writeLock().lock(); try { // We synchronize here because we don't want to call IcingSearchEngine.initialize() more // than once. It's unnecessary and can be a costly operation. if (isInitialized()) { return; } IcingSearchEngineOptions options = IcingSearchEngineOptions.newBuilder() .setBaseDir(mIcingDir.getAbsolutePath()).build(); .setBaseDir(icingDir.getAbsolutePath()).build(); mIcingSearchEngine = new IcingSearchEngine(options); InitializeResultProto initializeResultProto = mIcingSearchEngine.initialize(); Loading @@ -170,7 +157,8 @@ public final class AppSearchImpl { for (String qualifiedNamespace : getAllNamespacesResultProto.getNamespacesList()) { addToMap(mNamespaceMap, getDatabaseName(qualifiedNamespace), qualifiedNamespace); } mInitCompleteLatch.countDown(); // TODO(b/155939114): It's possible to optimize after init, which would reduce the time // to when we're able to serve queries. Consider moving this optimize call out. if (!isReset) { checkForOptimize(/* force= */ true); } Loading @@ -179,12 +167,6 @@ public final class AppSearchImpl { } } /** Checks if the internal state of {@link AppSearchImpl} has been initialized. */ @AnyThread public boolean isInitialized() { return mInitCompleteLatch.getCount() == 0; } /** * Updates the AppSearch schema for this app. * Loading @@ -195,12 +177,9 @@ public final class AppSearchImpl { * @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. * @throws InterruptedException if the current thread was interrupted during execution. */ public void setSchema(@NonNull String databaseName, @NonNull SchemaProto origSchema, boolean forceOverride) throws AppSearchException, InterruptedException { awaitInitialized(); boolean forceOverride) throws AppSearchException { SchemaProto schemaProto = getSchemaProto(); SchemaProto.Builder existingSchemaBuilder = schemaProto.toBuilder(); Loading @@ -212,10 +191,32 @@ public final class AppSearchImpl { SetSchemaResultProto setSchemaResultProto; mReadWriteLock.writeLock().lock(); try { setSchemaResultProto = mIcingSearchEngine.setSchema(existingSchemaBuilder.build(), forceOverride); // Apply schema setSchemaResultProto = mIcingSearchEngine.setSchema(existingSchemaBuilder.build(), forceOverride); // Determine whether it succeeded. try { checkSuccess(setSchemaResultProto.getStatus()); } catch (AppSearchException e) { // Improve the error message by merging in information about incompatible types. if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0 || setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0) { String newMessage = e.getMessage() + "\n Deleted types: " + setSchemaResultProto.getDeletedSchemaTypesList() + "\n Incompatible types: " + setSchemaResultProto.getIncompatibleSchemaTypesList(); throw new AppSearchException(e.getResultCode(), newMessage, e.getCause()); } else { throw e; } } // Update derived data structures. mSchemaMap.put(databaseName, newTypeNames); // Determine whether to schedule an immediate optimize. if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0 || (setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0 && forceOverride)) { Loading @@ -237,12 +238,9 @@ public final class AppSearchImpl { * @param databaseName The databaseName this document resides in. * @param document The document to index. * @throws AppSearchException on IcingSearchEngine error. * @throws InterruptedException if the current thread was interrupted during execution. */ public void putDocument(@NonNull String databaseName, @NonNull DocumentProto document) throws AppSearchException, InterruptedException { awaitInitialized(); throws AppSearchException { DocumentProto.Builder documentBuilder = document.toBuilder(); rewriteDocumentTypes(getDatabasePrefix(databaseName), documentBuilder, /*add=*/ true); Loading Loading @@ -270,12 +268,10 @@ public final class AppSearchImpl { * @param uri The URI of the document to get. * @return The Document contents, or {@code null} if no such URI exists in the system. * @throws AppSearchException on IcingSearchEngine error. * @throws InterruptedException if the current thread was interrupted during execution. */ @Nullable public DocumentProto getDocument(@NonNull String databaseName, @NonNull String namespace, @NonNull String uri) throws AppSearchException, InterruptedException { awaitInitialized(); @NonNull String uri) throws AppSearchException { GetResultProto getResultProto; mReadWriteLock.readLock().lock(); try { Loading Loading @@ -303,16 +299,13 @@ public final class AppSearchImpl { * @return The results of performing this search The proto might have no {@code results} if no * documents matched the query. * @throws AppSearchException on IcingSearchEngine error. * @throws InterruptedException if the current thread was interrupted during execution. */ @NonNull public SearchResultProto query( @NonNull String databaseName, @NonNull SearchSpecProto searchSpec, @NonNull ResultSpecProto resultSpec, @NonNull ScoringSpecProto scoringSpec) throws AppSearchException, InterruptedException { awaitInitialized(); @NonNull ScoringSpecProto scoringSpec) throws AppSearchException { SearchSpecProto.Builder searchSpecBuilder = searchSpec.toBuilder(); SearchResultProto searchResultProto; mReadWriteLock.readLock().lock(); Loading Loading @@ -347,13 +340,10 @@ public final class AppSearchImpl { * @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. * @throws InterruptedException if the current thread was interrupted during execution. */ @NonNull public SearchResultProto getNextPage(@NonNull String databaseName, long nextPageToken) throws AppSearchException, InterruptedException { awaitInitialized(); throws AppSearchException { SearchResultProto searchResultProto = mIcingSearchEngine.getNextPage(nextPageToken); checkSuccess(searchResultProto.getStatus()); if (searchResultProto.getResultsCount() == 0) { Loading @@ -367,8 +357,7 @@ public final class AppSearchImpl { * @param nextPageToken The token of pre-loaded results of previously executed query to be * Invalidated. */ public void invalidateNextPageToken(long nextPageToken) throws InterruptedException { awaitInitialized(); public void invalidateNextPageToken(long nextPageToken) { mIcingSearchEngine.invalidateNextPageToken(nextPageToken); } Loading @@ -381,12 +370,9 @@ public final class AppSearchImpl { * @param namespace Namespace of the document to remove. * @param uri URI of the document to remove. * @throws AppSearchException on IcingSearchEngine error. * @throws InterruptedException if the current thread was interrupted during execution. */ public void remove(@NonNull String databaseName, @NonNull String namespace, @NonNull String uri) throws AppSearchException, InterruptedException { awaitInitialized(); @NonNull String uri) throws AppSearchException { String qualifiedNamespace = getDatabasePrefix(databaseName) + namespace; DeleteResultProto deleteResultProto; mReadWriteLock.writeLock().lock(); Loading @@ -407,12 +393,9 @@ public final class AppSearchImpl { * @param databaseName The databaseName that contains documents of schemaType. * @param schemaType The schemaType of documents to remove. * @throws AppSearchException on IcingSearchEngine error. * @throws InterruptedException if the current thread was interrupted during execution. */ public void removeByType(@NonNull String databaseName, @NonNull String schemaType) throws AppSearchException, InterruptedException { awaitInitialized(); throws AppSearchException { String qualifiedType = getDatabasePrefix(databaseName) + schemaType; DeleteBySchemaTypeResultProto deleteBySchemaTypeResultProto; mReadWriteLock.writeLock().lock(); Loading @@ -437,12 +420,9 @@ public final class AppSearchImpl { * @param databaseName The databaseName that contains documents of namespace. * @param namespace The namespace of documents to remove. * @throws AppSearchException on IcingSearchEngine error. * @throws InterruptedException if the current thread was interrupted during execution. */ public void removeByNamespace(@NonNull String databaseName, @NonNull String namespace) throws AppSearchException, InterruptedException { awaitInitialized(); throws AppSearchException { String qualifiedNamespace = getDatabasePrefix(databaseName) + namespace; DeleteByNamespaceResultProto deleteByNamespaceResultProto; mReadWriteLock.writeLock().lock(); Loading @@ -469,11 +449,9 @@ public final class AppSearchImpl { * * @param databaseName The databaseName to remove all documents from. * @throws AppSearchException on IcingSearchEngine error. * @throws InterruptedException if the current thread was interrupted during execution. */ public void removeAll(@NonNull String databaseName) throws AppSearchException, InterruptedException { awaitInitialized(); throws AppSearchException { mReadWriteLock.writeLock().lock(); try { Set<String> existingNamespaces = mNamespaceMap.get(databaseName); Loading Loading @@ -732,15 +710,6 @@ public final class AppSearchImpl { values.add(prefixedValue); } /** * Waits for the instance to become initialized. * * @throws InterruptedException if the current thread was interrupted during waiting. */ private void awaitInitialized() throws InterruptedException { mInitCompleteLatch.await(); } /** * Checks the given status code and throws an {@link AppSearchException} if code is an error. * Loading Loading
apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java +40 −11 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * 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. Loading @@ -22,6 +22,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; import com.android.internal.util.Preconditions; import java.util.Collections; import java.util.Map; Loading @@ -33,11 +35,11 @@ import java.util.Map; * @param <ValueType> The type of result objects associated with the keys. * @hide */ public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable { public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelable { @NonNull private final Map<KeyType, ValueType> mSuccesses; @NonNull private final Map<KeyType, AppSearchResult<ValueType>> mFailures; private AppSearchBatchResult( AppSearchBatchResult( @NonNull Map<KeyType, ValueType> successes, @NonNull Map<KeyType, AppSearchResult<ValueType>> failures) { mSuccesses = successes; Loading @@ -61,8 +63,8 @@ public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable { } /** * Returns a {@link Map} of all successful keys mapped to the successful {@link ValueType} * values they produced. * Returns a {@link Map} of all successful keys mapped to the successful * {@link AppSearchResult}s they produced. * * <p>The values of the {@link Map} will not be {@code null}. */ Loading @@ -82,6 +84,22 @@ public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable { return mFailures; } /** * Asserts that this {@link AppSearchBatchResult} has no failures. * @hide */ public void checkSuccess() { if (!isSuccess()) { throw new IllegalStateException("AppSearchBatchResult has failures: " + this); } } @Override @NonNull public String toString() { return "{\n successes: " + mSuccesses + "\n failures: " + mFailures + "\n}"; } @Override public int describeContents() { return 0; Loading Loading @@ -112,16 +130,18 @@ public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable { public static final class Builder<KeyType, ValueType> { private final Map<KeyType, ValueType> mSuccesses = new ArrayMap<>(); private final Map<KeyType, AppSearchResult<ValueType>> mFailures = new ArrayMap<>(); /** Creates a new {@link Builder} for this {@link AppSearchBatchResult}. */ public Builder() {} private boolean mBuilt = false; /** * Associates the {@code key} with the given successful return value. * * <p>Any previous mapping for a key, whether success or failure, is deleted. */ public Builder setSuccess(@NonNull KeyType key, @Nullable ValueType result) { @NonNull public Builder<KeyType, ValueType> setSuccess( @NonNull KeyType key, @Nullable ValueType result) { Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkNotNull(key); return setResult(key, AppSearchResult.newSuccessfulResult(result)); } Loading @@ -130,10 +150,13 @@ public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable { * * <p>Any previous mapping for a key, whether success or failure, is deleted. */ public Builder setFailure( @NonNull public Builder<KeyType, ValueType> setFailure( @NonNull KeyType key, @AppSearchResult.ResultCode int resultCode, @Nullable String errorMessage) { Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkNotNull(key); return setResult(key, AppSearchResult.newFailedResult(resultCode, errorMessage)); } Loading @@ -143,7 +166,11 @@ public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable { * <p>Any previous mapping for a key, whether success or failure, is deleted. */ @NonNull public Builder setResult(@NonNull KeyType key, @NonNull AppSearchResult<ValueType> result) { public Builder<KeyType, ValueType> setResult( @NonNull KeyType key, @NonNull AppSearchResult<ValueType> result) { Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkNotNull(key); Preconditions.checkNotNull(result); if (result.isSuccess()) { mSuccesses.put(key, result.getResultValue()); mFailures.remove(key); Loading @@ -157,6 +184,8 @@ public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable { /** Builds an {@link AppSearchBatchResult} from the contents of this {@link Builder}. */ @NonNull public AppSearchBatchResult<KeyType, ValueType> build() { Preconditions.checkState(!mBuilt, "Builder has already been used"); mBuilt = true; return new AppSearchBatchResult<>(mSuccesses, mFailures); } } Loading
apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java +19 −13 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * 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. Loading Loading @@ -32,9 +32,12 @@ import java.util.Objects; * @param <ValueType> The type of result object for successful calls. * @hide */ public class AppSearchResult<ValueType> implements Parcelable { /** Result codes from {@link AppSearchManager} methods. */ @IntDef(prefix = {"RESULT_"}, value = { public final class AppSearchResult<ValueType> implements Parcelable { /** * Result codes from {@link AppSearchManager} methods. * @hide */ @IntDef(value = { RESULT_OK, RESULT_UNKNOWN_ERROR, RESULT_INTERNAL_ERROR, Loading Loading @@ -120,15 +123,18 @@ public class AppSearchResult<ValueType> implements Parcelable { } /** * Returns the returned value associated with this result. * Returns the result value associated with this result, if it was successful. * * <p>If {@link #isSuccess} is {@code false}, the result value is always {@code null}. The value * may be {@code null} even if {@link #isSuccess} is {@code true}. See the documentation of the * particular {@link AppSearchManager} call producing this {@link AppSearchResult} for what is * returned by {@link #getResultValue}. * <p>See the documentation of the particular {@link AppSearchManager} call producing this * {@link AppSearchResult} for what is placed in the result value by that call. * * @throws IllegalStateException if this {@link AppSearchResult} is not successful. */ @Nullable public ValueType getResultValue() { if (!isSuccess()) { throw new IllegalStateException("AppSearchResult is a failure: " + this); } return mResultValue; } Loading @@ -146,14 +152,14 @@ public class AppSearchResult<ValueType> implements Parcelable { } @Override public boolean equals(Object other) { public boolean equals(@Nullable Object other) { if (this == other) { return true; } if (!(other instanceof AppSearchResult)) { return false; } AppSearchResult<?> otherResult = (AppSearchResult) other; AppSearchResult<?> otherResult = (AppSearchResult<?>) other; return mResultCode == otherResult.mResultCode && Objects.equals(mResultValue, otherResult.mResultValue) && Objects.equals(mErrorMessage, otherResult.mErrorMessage); Loading @@ -168,9 +174,9 @@ public class AppSearchResult<ValueType> implements Parcelable { @NonNull public String toString() { if (isSuccess()) { return "AppSearchResult [SUCCESS]: " + mResultValue; return "[SUCCESS]: " + mResultValue; } return "AppSearchResult [FAILURE(" + mResultCode + ")]: " + mErrorMessage; return "[FAILURE(" + mResultCode + ")]: " + mErrorMessage; } @Override Loading
apex/appsearch/framework/java/android/app/appsearch/exceptions/AppSearchException.java +4 −0 Original line number Diff line number Diff line Loading @@ -54,6 +54,10 @@ public class AppSearchException extends Exception { mResultCode = resultCode; } public @AppSearchResult.ResultCode int getResultCode() { return mResultCode; } /** * Converts this {@link java.lang.Exception} into a failed {@link AppSearchResult} */ Loading
apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java +1 −3 Original line number Diff line number Diff line Loading @@ -69,9 +69,7 @@ public final class ImplInstanceManager { private static AppSearchImpl createImpl(@NonNull Context context, @UserIdInt int userId) throws AppSearchException { File appSearchDir = getAppSearchDir(context, userId); AppSearchImpl appSearchImpl = new AppSearchImpl(appSearchDir); appSearchImpl.initialize(); return appSearchImpl; return AppSearchImpl.create(appSearchDir); } private static File getAppSearchDir(@NonNull Context context, @UserIdInt int userId) { Loading
apex/appsearch/service/java/com/android/server/appsearch/external/localbackend/AppSearchImpl.java +52 −83 Original line number Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.server.appsearch.external.localbackend; import android.util.Log; import android.annotation.AnyThread; import com.android.internal.annotations.GuardedBy; import android.annotation.NonNull; import android.annotation.Nullable; Loading @@ -27,6 +26,7 @@ import com.android.internal.annotations.VisibleForTesting; import android.annotation.WorkerThread; import android.app.appsearch.AppSearchResult; import android.app.appsearch.exceptions.AppSearchException; import com.android.internal.util.Preconditions; import com.google.android.icing.IcingSearchEngine; import com.google.android.icing.proto.DeleteByNamespaceResultProto; Loading Loading @@ -58,7 +58,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; Loading @@ -66,8 +65,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; * Manages interaction with the native IcingSearchEngine and other components to implement AppSearch * functionality. * * <p>Callers should call {@link #initialize} before using the AppSearchImpl instance. Never create * two instances using the same folder. * <p>Never create two instances using the same folder. * * <p>A single instance of {@link AppSearchImpl} can support all databases. Schemas and documents * are physically saved together in {@link IcingSearchEngine}, but logically isolated: Loading Loading @@ -106,9 +104,7 @@ public final class AppSearchImpl { static final int CHECK_OPTIMIZE_INTERVAL = 100; private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock(); private final CountDownLatch mInitCompleteLatch = new CountDownLatch(1); private final File mIcingDir; private IcingSearchEngine mIcingSearchEngine; private final IcingSearchEngine mIcingSearchEngine; // The map contains schemaTypes and namespaces for all database. All values in the map have // been already added database name prefix. Loading @@ -121,33 +117,24 @@ public final class AppSearchImpl { */ private int mOptimizeIntervalCount = 0; /** Creates an instance of {@link AppSearchImpl} which writes data to the given folder. */ @AnyThread public AppSearchImpl(@NonNull File icingDir) { mIcingDir = icingDir; } /** * Initializes the underlying IcingSearchEngine. * * <p>This method belongs to mutate group. * * @throws AppSearchException on IcingSearchEngine error. * Creates and initializes an instance of {@link AppSearchImpl} which writes data to the given * folder. */ public void initialize() throws AppSearchException { if (isInitialized()) { return; @NonNull public static AppSearchImpl create(@NonNull File icingDir) throws AppSearchException { Preconditions.checkNotNull(icingDir); return new AppSearchImpl(icingDir); } private AppSearchImpl(@NonNull File icingDir) throws AppSearchException { boolean isReset = false; mReadWriteLock.writeLock().lock(); try { // We synchronize here because we don't want to call IcingSearchEngine.initialize() more // than once. It's unnecessary and can be a costly operation. if (isInitialized()) { return; } IcingSearchEngineOptions options = IcingSearchEngineOptions.newBuilder() .setBaseDir(mIcingDir.getAbsolutePath()).build(); .setBaseDir(icingDir.getAbsolutePath()).build(); mIcingSearchEngine = new IcingSearchEngine(options); InitializeResultProto initializeResultProto = mIcingSearchEngine.initialize(); Loading @@ -170,7 +157,8 @@ public final class AppSearchImpl { for (String qualifiedNamespace : getAllNamespacesResultProto.getNamespacesList()) { addToMap(mNamespaceMap, getDatabaseName(qualifiedNamespace), qualifiedNamespace); } mInitCompleteLatch.countDown(); // TODO(b/155939114): It's possible to optimize after init, which would reduce the time // to when we're able to serve queries. Consider moving this optimize call out. if (!isReset) { checkForOptimize(/* force= */ true); } Loading @@ -179,12 +167,6 @@ public final class AppSearchImpl { } } /** Checks if the internal state of {@link AppSearchImpl} has been initialized. */ @AnyThread public boolean isInitialized() { return mInitCompleteLatch.getCount() == 0; } /** * Updates the AppSearch schema for this app. * Loading @@ -195,12 +177,9 @@ public final class AppSearchImpl { * @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. * @throws InterruptedException if the current thread was interrupted during execution. */ public void setSchema(@NonNull String databaseName, @NonNull SchemaProto origSchema, boolean forceOverride) throws AppSearchException, InterruptedException { awaitInitialized(); boolean forceOverride) throws AppSearchException { SchemaProto schemaProto = getSchemaProto(); SchemaProto.Builder existingSchemaBuilder = schemaProto.toBuilder(); Loading @@ -212,10 +191,32 @@ public final class AppSearchImpl { SetSchemaResultProto setSchemaResultProto; mReadWriteLock.writeLock().lock(); try { setSchemaResultProto = mIcingSearchEngine.setSchema(existingSchemaBuilder.build(), forceOverride); // Apply schema setSchemaResultProto = mIcingSearchEngine.setSchema(existingSchemaBuilder.build(), forceOverride); // Determine whether it succeeded. try { checkSuccess(setSchemaResultProto.getStatus()); } catch (AppSearchException e) { // Improve the error message by merging in information about incompatible types. if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0 || setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0) { String newMessage = e.getMessage() + "\n Deleted types: " + setSchemaResultProto.getDeletedSchemaTypesList() + "\n Incompatible types: " + setSchemaResultProto.getIncompatibleSchemaTypesList(); throw new AppSearchException(e.getResultCode(), newMessage, e.getCause()); } else { throw e; } } // Update derived data structures. mSchemaMap.put(databaseName, newTypeNames); // Determine whether to schedule an immediate optimize. if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0 || (setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0 && forceOverride)) { Loading @@ -237,12 +238,9 @@ public final class AppSearchImpl { * @param databaseName The databaseName this document resides in. * @param document The document to index. * @throws AppSearchException on IcingSearchEngine error. * @throws InterruptedException if the current thread was interrupted during execution. */ public void putDocument(@NonNull String databaseName, @NonNull DocumentProto document) throws AppSearchException, InterruptedException { awaitInitialized(); throws AppSearchException { DocumentProto.Builder documentBuilder = document.toBuilder(); rewriteDocumentTypes(getDatabasePrefix(databaseName), documentBuilder, /*add=*/ true); Loading Loading @@ -270,12 +268,10 @@ public final class AppSearchImpl { * @param uri The URI of the document to get. * @return The Document contents, or {@code null} if no such URI exists in the system. * @throws AppSearchException on IcingSearchEngine error. * @throws InterruptedException if the current thread was interrupted during execution. */ @Nullable public DocumentProto getDocument(@NonNull String databaseName, @NonNull String namespace, @NonNull String uri) throws AppSearchException, InterruptedException { awaitInitialized(); @NonNull String uri) throws AppSearchException { GetResultProto getResultProto; mReadWriteLock.readLock().lock(); try { Loading Loading @@ -303,16 +299,13 @@ public final class AppSearchImpl { * @return The results of performing this search The proto might have no {@code results} if no * documents matched the query. * @throws AppSearchException on IcingSearchEngine error. * @throws InterruptedException if the current thread was interrupted during execution. */ @NonNull public SearchResultProto query( @NonNull String databaseName, @NonNull SearchSpecProto searchSpec, @NonNull ResultSpecProto resultSpec, @NonNull ScoringSpecProto scoringSpec) throws AppSearchException, InterruptedException { awaitInitialized(); @NonNull ScoringSpecProto scoringSpec) throws AppSearchException { SearchSpecProto.Builder searchSpecBuilder = searchSpec.toBuilder(); SearchResultProto searchResultProto; mReadWriteLock.readLock().lock(); Loading Loading @@ -347,13 +340,10 @@ public final class AppSearchImpl { * @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. * @throws InterruptedException if the current thread was interrupted during execution. */ @NonNull public SearchResultProto getNextPage(@NonNull String databaseName, long nextPageToken) throws AppSearchException, InterruptedException { awaitInitialized(); throws AppSearchException { SearchResultProto searchResultProto = mIcingSearchEngine.getNextPage(nextPageToken); checkSuccess(searchResultProto.getStatus()); if (searchResultProto.getResultsCount() == 0) { Loading @@ -367,8 +357,7 @@ public final class AppSearchImpl { * @param nextPageToken The token of pre-loaded results of previously executed query to be * Invalidated. */ public void invalidateNextPageToken(long nextPageToken) throws InterruptedException { awaitInitialized(); public void invalidateNextPageToken(long nextPageToken) { mIcingSearchEngine.invalidateNextPageToken(nextPageToken); } Loading @@ -381,12 +370,9 @@ public final class AppSearchImpl { * @param namespace Namespace of the document to remove. * @param uri URI of the document to remove. * @throws AppSearchException on IcingSearchEngine error. * @throws InterruptedException if the current thread was interrupted during execution. */ public void remove(@NonNull String databaseName, @NonNull String namespace, @NonNull String uri) throws AppSearchException, InterruptedException { awaitInitialized(); @NonNull String uri) throws AppSearchException { String qualifiedNamespace = getDatabasePrefix(databaseName) + namespace; DeleteResultProto deleteResultProto; mReadWriteLock.writeLock().lock(); Loading @@ -407,12 +393,9 @@ public final class AppSearchImpl { * @param databaseName The databaseName that contains documents of schemaType. * @param schemaType The schemaType of documents to remove. * @throws AppSearchException on IcingSearchEngine error. * @throws InterruptedException if the current thread was interrupted during execution. */ public void removeByType(@NonNull String databaseName, @NonNull String schemaType) throws AppSearchException, InterruptedException { awaitInitialized(); throws AppSearchException { String qualifiedType = getDatabasePrefix(databaseName) + schemaType; DeleteBySchemaTypeResultProto deleteBySchemaTypeResultProto; mReadWriteLock.writeLock().lock(); Loading @@ -437,12 +420,9 @@ public final class AppSearchImpl { * @param databaseName The databaseName that contains documents of namespace. * @param namespace The namespace of documents to remove. * @throws AppSearchException on IcingSearchEngine error. * @throws InterruptedException if the current thread was interrupted during execution. */ public void removeByNamespace(@NonNull String databaseName, @NonNull String namespace) throws AppSearchException, InterruptedException { awaitInitialized(); throws AppSearchException { String qualifiedNamespace = getDatabasePrefix(databaseName) + namespace; DeleteByNamespaceResultProto deleteByNamespaceResultProto; mReadWriteLock.writeLock().lock(); Loading @@ -469,11 +449,9 @@ public final class AppSearchImpl { * * @param databaseName The databaseName to remove all documents from. * @throws AppSearchException on IcingSearchEngine error. * @throws InterruptedException if the current thread was interrupted during execution. */ public void removeAll(@NonNull String databaseName) throws AppSearchException, InterruptedException { awaitInitialized(); throws AppSearchException { mReadWriteLock.writeLock().lock(); try { Set<String> existingNamespaces = mNamespaceMap.get(databaseName); Loading Loading @@ -732,15 +710,6 @@ public final class AppSearchImpl { values.add(prefixedValue); } /** * Waits for the instance to become initialized. * * @throws InterruptedException if the current thread was interrupted during waiting. */ private void awaitInitialized() throws InterruptedException { mInitCompleteLatch.await(); } /** * Checks the given status code and throws an {@link AppSearchException} if code is an error. * Loading