Merge "Update framework from Jetpack"

This commit is contained in:
Alexander Dorokhine 2020-12-29 05:24:10 +00:00 committed by Android (Google) Code Review
commit fb182aebae
16 changed files with 496 additions and 151 deletions

View File

@ -233,7 +233,7 @@ public class AppSearchManager {
mService.setSchema(
DEFAULT_DATABASE_NAME,
schemaBundles,
new ArrayList<>(request.getSchemasNotPlatformSurfaceable()),
new ArrayList<>(request.getSchemasNotVisibleToSystemUi()),
request.isForceOverride(),
mContext.getUserId(),
new IAppSearchResultCallback.Stub() {

View File

@ -158,7 +158,7 @@ public final class AppSearchSession {
mService.setSchema(
mDatabaseName,
schemaBundles,
new ArrayList<>(request.getSchemasNotPlatformSurfaceable()),
new ArrayList<>(request.getSchemasNotVisibleToSystemUi()),
request.isForceOverride(),
mUserId,
new IAppSearchResultCallback.Stub() {

View File

@ -18,27 +18,38 @@ package android.app.appsearch;
import android.annotation.NonNull;
import com.android.internal.util.Preconditions;
import java.util.Arrays;
import java.util.Objects;
/**
* This class represents a uniquely identifiable package.
*
* @hide
*/
public class PackageIdentifier {
public final String packageName;
public final byte[] certificate;
private final String mPackageName;
private final byte[] mSha256Certificate;
/**
* Creates a unique identifier for a package.
*
* @param packageName Name of the package.
* @param certificate SHA256 certificate digest of the package.
* @param sha256Certificate SHA256 certificate digest of the package.
*/
public PackageIdentifier(@NonNull String packageName, @NonNull byte[] certificate) {
this.packageName = packageName;
this.certificate = certificate;
public PackageIdentifier(@NonNull String packageName, @NonNull byte[] sha256Certificate) {
mPackageName = Preconditions.checkNotNull(packageName);
mSha256Certificate = Preconditions.checkNotNull(sha256Certificate);
}
@NonNull
public String getPackageName() {
return mPackageName;
}
@NonNull
public byte[] getSha256Certificate() {
return mSha256Certificate;
}
@Override
@ -50,12 +61,12 @@ public class PackageIdentifier {
return false;
}
final PackageIdentifier other = (PackageIdentifier) obj;
return this.packageName.equals(other.packageName)
&& Arrays.equals(this.certificate, other.certificate);
return this.mPackageName.equals(other.mPackageName)
&& Arrays.equals(this.mSha256Certificate, other.mSha256Certificate);
}
@Override
public int hashCode() {
return Objects.hash(packageName, Arrays.hashCode(certificate));
return Objects.hash(mPackageName, Arrays.hashCode(mSha256Certificate));
}
}

View File

@ -18,17 +18,14 @@ package android.app.appsearch;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.app.appsearch.exceptions.AppSearchException;
import android.util.ArrayMap;
import android.util.ArraySet;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -39,18 +36,18 @@ import java.util.Set;
*/
public final class SetSchemaRequest {
private final Set<AppSearchSchema> mSchemas;
private final Set<String> mSchemasNotPlatformSurfaceable;
private final Map<String, Set<PackageIdentifier>> mSchemasPackageAccessible;
private final Set<String> mSchemasNotVisibleToSystemUi;
private final Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages;
private final boolean mForceOverride;
SetSchemaRequest(
@NonNull Set<AppSearchSchema> schemas,
@NonNull Set<String> schemasNotPlatformSurfaceable,
@NonNull Map<String, Set<PackageIdentifier>> schemasPackageAccessible,
@NonNull Set<String> schemasNotVisibleToSystemUi,
@NonNull Map<String, Set<PackageIdentifier>> schemasVisibleToPackages,
boolean forceOverride) {
mSchemas = Preconditions.checkNotNull(schemas);
mSchemasNotPlatformSurfaceable = Preconditions.checkNotNull(schemasNotPlatformSurfaceable);
mSchemasPackageAccessible = Preconditions.checkNotNull(schemasPackageAccessible);
mSchemasNotVisibleToSystemUi = Preconditions.checkNotNull(schemasNotVisibleToSystemUi);
mSchemasVisibleToPackages = Preconditions.checkNotNull(schemasVisibleToPackages);
mForceOverride = forceOverride;
}
@ -62,12 +59,11 @@ public final class SetSchemaRequest {
/**
* Returns the set of schema types that have opted out of being visible on system UI surfaces.
*
* @hide
*/
@NonNull
public Set<String> getSchemasNotPlatformSurfaceable() {
return Collections.unmodifiableSet(mSchemasNotPlatformSurfaceable);
public Set<String> getSchemasNotVisibleToSystemUi() {
return Collections.unmodifiableSet(mSchemasNotVisibleToSystemUi);
}
/**
@ -76,14 +72,13 @@ public final class SetSchemaRequest {
* certificate.
*
* <p>This method is inefficient to call repeatedly.
*
* @hide
*/
@NonNull
public Map<String, Set<PackageIdentifier>> getSchemasPackageAccessible() {
public Map<String, Set<PackageIdentifier>> getSchemasVisibleToPackages() {
Map<String, Set<PackageIdentifier>> copy = new ArrayMap<>();
for (String key : mSchemasPackageAccessible.keySet()) {
copy.put(key, new ArraySet<>(mSchemasPackageAccessible.get(key)));
for (String key : mSchemasVisibleToPackages.keySet()) {
copy.put(key, new ArraySet<>(mSchemasVisibleToPackages.get(key)));
}
return copy;
}
@ -93,14 +88,14 @@ public final class SetSchemaRequest {
* type. Each package is represented by a {@link PackageIdentifier}. name and byte[]
* certificate.
*
* <p>A more efficient version of {@code #getSchemasPackageAccessible}, but it returns a
* <p>A more efficient version of {@link #getSchemasVisibleToPackages}, but it returns a
* modifiable map. This is not meant to be unhidden and should only be used by internal classes.
*
* @hide
*/
@NonNull
public Map<String, Set<PackageIdentifier>> getSchemasPackageAccessibleInternal() {
return mSchemasPackageAccessible;
public Map<String, Set<PackageIdentifier>> getSchemasVisibleToPackagesInternal() {
return mSchemasVisibleToPackages;
}
/** Returns whether this request will force the schema to be overridden. */
@ -111,8 +106,8 @@ public final class SetSchemaRequest {
/** Builder for {@link SetSchemaRequest} objects. */
public static final class Builder {
private final Set<AppSearchSchema> mSchemas = new ArraySet<>();
private final Set<String> mSchemasNotPlatformSurfaceable = new ArraySet<>();
private final Map<String, Set<PackageIdentifier>> mSchemasPackageAccessible =
private final Set<String> mSchemasNotVisibleToSystemUi = new ArraySet<>();
private final Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages =
new ArrayMap<>();
private boolean mForceOverride = false;
private boolean mBuilt = false;
@ -148,6 +143,8 @@ public final class SetSchemaRequest {
* @param visible Whether the {@code schemaType} will be visible or not.
* @hide
*/
// Merged list available from getSchemasNotVisibleToSystemUi
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder setSchemaTypeVisibilityForSystemUi(
@NonNull String schemaType, boolean visible) {
@ -155,9 +152,9 @@ public final class SetSchemaRequest {
Preconditions.checkState(!mBuilt, "Builder has already been used");
if (visible) {
mSchemasNotPlatformSurfaceable.remove(schemaType);
mSchemasNotVisibleToSystemUi.remove(schemaType);
} else {
mSchemasNotPlatformSurfaceable.add(schemaType);
mSchemasNotVisibleToSystemUi.add(schemaType);
}
return this;
}
@ -170,6 +167,8 @@ public final class SetSchemaRequest {
* @param packageIdentifier Represents the package that will be granted visibility.
* @hide
*/
// Merged list available from getSchemasVisibleToPackages
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder setSchemaTypeVisibilityForPackage(
@NonNull String schemaType,
@ -179,13 +178,13 @@ public final class SetSchemaRequest {
Preconditions.checkNotNull(packageIdentifier);
Preconditions.checkState(!mBuilt, "Builder has already been used");
Set<PackageIdentifier> packageIdentifiers = mSchemasPackageAccessible.get(schemaType);
Set<PackageIdentifier> packageIdentifiers = mSchemasVisibleToPackages.get(schemaType);
if (visible) {
if (packageIdentifiers == null) {
packageIdentifiers = new ArraySet<>();
}
packageIdentifiers.add(packageIdentifier);
mSchemasPackageAccessible.put(schemaType, packageIdentifiers);
mSchemasVisibleToPackages.put(schemaType, packageIdentifiers);
} else {
if (packageIdentifiers == null) {
// Return early since there was nothing set to begin with.
@ -194,7 +193,7 @@ public final class SetSchemaRequest {
packageIdentifiers.remove(packageIdentifier);
if (packageIdentifiers.isEmpty()) {
// Remove the entire key so that we don't have empty sets as values.
mSchemasPackageAccessible.remove(schemaType);
mSchemasVisibleToPackages.remove(schemaType);
}
}
@ -229,8 +228,8 @@ public final class SetSchemaRequest {
// Verify that any schema types with visibility settings refer to a real schema.
// Create a copy because we're going to remove from the set for verification purposes.
Set<String> referencedSchemas = new ArraySet<>(mSchemasNotPlatformSurfaceable);
referencedSchemas.addAll(mSchemasPackageAccessible.keySet());
Set<String> referencedSchemas = new ArraySet<>(mSchemasNotVisibleToSystemUi);
referencedSchemas.addAll(mSchemasVisibleToPackages.keySet());
for (AppSearchSchema schema : mSchemas) {
referencedSchemas.remove(schema.getSchemaType());
@ -244,8 +243,8 @@ public final class SetSchemaRequest {
return new SetSchemaRequest(
mSchemas,
mSchemasNotPlatformSurfaceable,
mSchemasPackageAccessible,
mSchemasNotVisibleToSystemUi,
mSchemasVisibleToPackages,
mForceOverride);
}
}

View File

@ -156,7 +156,6 @@ public final class AppSearchImpl {
}
private AppSearchImpl(@NonNull File icingDir) throws AppSearchException {
boolean isReset = false;
mReadWriteLock.writeLock().lock();
try {
@ -168,9 +167,11 @@ public final class AppSearchImpl {
.build();
mIcingSearchEngineLocked = new IcingSearchEngine(options);
mVisibilityStoreLocked = new VisibilityStore(this);
InitializeResultProto initializeResultProto = mIcingSearchEngineLocked.initialize();
SchemaProto schemaProto = null;
GetAllNamespacesResultProto getAllNamespacesResultProto = null;
SchemaProto schemaProto;
GetAllNamespacesResultProto getAllNamespacesResultProto;
try {
checkSuccess(initializeResultProto.getStatus());
schemaProto = getSchemaProtoLocked();
@ -180,7 +181,7 @@ public final class AppSearchImpl {
Log.w(TAG, "Error initializing, resetting IcingSearchEngine.", e);
// Some error. Reset and see if it fixes it.
reset();
isReset = true;
return;
}
// Populate schema map
@ -196,11 +197,8 @@ public final class AppSearchImpl {
// 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) {
checkForOptimizeLocked(/* force= */ true);
}
checkForOptimizeLocked(/* force= */ true);
mVisibilityStoreLocked = new VisibilityStore(this);
} finally {
mReadWriteLock.writeLock().unlock();
}
@ -635,6 +633,9 @@ public final class AppSearchImpl {
/**
* Clears documents and schema across all packages and databaseNames.
*
* <p>This method also clear all data in {@link VisibilityStore}, an {@link
* #initializeVisibilityStore()} must be called after this.
*
* <p>This method belongs to mutate group.
*
* @throws AppSearchException on IcingSearchEngine error.

View File

@ -213,14 +213,12 @@ class VisibilityStore {
}
/**
* Handles an {@link AppSearchImpl#reset()} by clearing any cached state and resetting to a
* first-initialized state.
* Handles an {@code AppSearchImpl#reset()} by clearing any cached state.
*
* @throws AppSearchException on AppSearchImpl error.
* <p>{@link #initialize()} must be called after this.
*/
public void handleReset() throws AppSearchException {
void handleReset() {
mNotPlatformSurfaceableMap.clear();
initialize();
}
/**

View File

@ -1 +1 @@
Ia04e81bb574831fa7e8a26c725e53133b39ee3ef
I0577839bfddf95a555399df441d317b00c7c7c48

View File

@ -20,15 +20,19 @@ import android.annotation.NonNull;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.AppSearchSession;
import android.app.appsearch.AppSearchSessionShim;
import android.app.appsearch.BatchResultCallback;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.GetByUriRequest;
import android.app.appsearch.PutDocumentsRequest;
import android.app.appsearch.RemoveByUriRequest;
import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchResultsShim;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
@ -38,6 +42,7 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -46,42 +51,47 @@ import java.util.concurrent.Executors;
* a consistent interface.
* @hide
*/
public class AppSearchSessionShim {
public class AppSearchSessionShimImpl implements AppSearchSessionShim {
private final AppSearchSession mAppSearchSession;
private final ExecutorService mExecutor;
@NonNull
public static ListenableFuture<AppSearchResult<AppSearchSessionShim>> createSearchSession(
public static ListenableFuture<AppSearchSessionShim> createSearchSession(
@NonNull AppSearchManager.SearchContext searchContext) {
Context context = ApplicationProvider.getApplicationContext();
AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
SettableFuture<AppSearchResult<AppSearchSession>> future = SettableFuture.create();
ExecutorService executor = Executors.newCachedThreadPool();
appSearchManager.createSearchSession(searchContext, executor, future::set);
return Futures.transform(future, (instance) -> {
if (!instance.isSuccess()) {
return AppSearchResult.newFailedResult(
instance.getResultCode(), instance.getErrorMessage());
}
AppSearchSession searchSession = instance.getResultValue();
AppSearchSessionShim shim = new AppSearchSessionShim(searchSession, executor);
return AppSearchResult.newSuccessfulResult(shim);
}, executor);
return Futures.transform(
future,
instance -> new AppSearchSessionShimImpl(instance.getResultValue(), executor),
executor);
}
private AppSearchSessionShim(
private AppSearchSessionShimImpl(
@NonNull AppSearchSession session, @NonNull ExecutorService executor) {
mAppSearchSession = Preconditions.checkNotNull(session);
mExecutor = Preconditions.checkNotNull(executor);
}
@Override
@NonNull
public ListenableFuture<AppSearchResult<Void>> setSchema(@NonNull SetSchemaRequest request) {
public ListenableFuture<Void> setSchema(@NonNull SetSchemaRequest request) {
SettableFuture<AppSearchResult<Void>> future = SettableFuture.create();
mAppSearchSession.setSchema(request, mExecutor, future::set);
return future;
return Futures.transformAsync(future, this::transformResult, mExecutor);
}
@Override
@NonNull
public ListenableFuture<Set<AppSearchSchema>> getSchema() {
SettableFuture<AppSearchResult<Set<AppSearchSchema>>> future = SettableFuture.create();
mAppSearchSession.getSchema(mExecutor, future::set);
return Futures.transformAsync(future, this::transformResult, mExecutor);
}
@Override
@NonNull
public ListenableFuture<AppSearchBatchResult<String, Void>> putDocuments(
@NonNull PutDocumentsRequest request) {
@ -91,6 +101,7 @@ public class AppSearchSessionShim {
return future;
}
@Override
@NonNull
public ListenableFuture<AppSearchBatchResult<String, GenericDocument>> getByUri(
@NonNull GetByUriRequest request) {
@ -100,14 +111,16 @@ public class AppSearchSessionShim {
return future;
}
@Override
@NonNull
public SearchResultsShim query(
@NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
SearchResults searchResults =
mAppSearchSession.query(queryExpression, searchSpec, mExecutor);
return new SearchResultsShim(searchResults);
return new SearchResultsShimImpl(searchResults, mExecutor);
}
@Override
@NonNull
public ListenableFuture<AppSearchBatchResult<String, Void>> removeByUri(
@NonNull RemoveByUriRequest request) {
@ -116,12 +129,21 @@ public class AppSearchSessionShim {
return future;
}
@Override
@NonNull
public ListenableFuture<AppSearchResult<Void>> removeByQuery(
public ListenableFuture<Void> removeByQuery(
@NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
SettableFuture<AppSearchResult<Void>> future = SettableFuture.create();
mAppSearchSession.removeByQuery(queryExpression, searchSpec, mExecutor, future::set);
return future;
return Futures.transformAsync(future, this::transformResult, mExecutor);
}
private <T> ListenableFuture<T> transformResult(
@NonNull AppSearchResult<T> result) throws AppSearchException {
if (!result.isSuccess()) {
throw new AppSearchException(result.getResultCode(), result.getErrorMessage());
}
return Futures.immediateFuture(result.getResultValue());
}
private static final class BatchResultCallbackAdapter<K, V>

View File

@ -21,6 +21,7 @@ import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.GlobalSearchSession;
import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchResultsShim;
import android.app.appsearch.SearchSpec;
import android.content.Context;
@ -39,30 +40,24 @@ import java.util.concurrent.Executors;
* a consistent interface.
* @hide
*/
public class GlobalSearchSessionShim {
public class GlobalSearchSessionShimImpl {
private final GlobalSearchSession mGlobalSearchSession;
private final ExecutorService mExecutor;
@NonNull
public static ListenableFuture<AppSearchResult<GlobalSearchSessionShim>>
createGlobalSearchSession() {
public static ListenableFuture<GlobalSearchSessionShimImpl> createGlobalSearchSession() {
Context context = ApplicationProvider.getApplicationContext();
AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
SettableFuture<AppSearchResult<GlobalSearchSession>> future = SettableFuture.create();
ExecutorService executor = Executors.newCachedThreadPool();
appSearchManager.createGlobalSearchSession(executor, future::set);
return Futures.transform(future, (instance) -> {
if (!instance.isSuccess()) {
return AppSearchResult.newFailedResult(
instance.getResultCode(), instance.getErrorMessage());
}
GlobalSearchSession searchSession = instance.getResultValue();
GlobalSearchSessionShim shim = new GlobalSearchSessionShim(searchSession, executor);
return AppSearchResult.newSuccessfulResult(shim);
}, executor);
return Futures.transform(
future,
instance -> new GlobalSearchSessionShimImpl(instance.getResultValue(), executor),
executor);
}
private GlobalSearchSessionShim(
private GlobalSearchSessionShimImpl(
@NonNull GlobalSearchSession session, @NonNull ExecutorService executor) {
mGlobalSearchSession = Preconditions.checkNotNull(session);
mExecutor = Preconditions.checkNotNull(executor);
@ -73,6 +68,6 @@ public class GlobalSearchSessionShim {
@NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
SearchResults searchResults =
mGlobalSearchSession.query(queryExpression, searchSpec, mExecutor);
return new SearchResultsShim(searchResults);
return new SearchResultsShimImpl(searchResults, mExecutor);
}
}

View File

@ -20,31 +20,35 @@ import android.annotation.NonNull;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchResultsShim;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import java.io.Closeable;
import java.util.List;
import java.util.concurrent.Executor;
/**
* This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via
* a consistent interface.
* @hide
*/
public class SearchResultsShim implements Closeable {
public class SearchResultsShimImpl implements SearchResultsShim {
private final Executor mExecutor;
private final SearchResults mSearchResults;
SearchResultsShim(@NonNull SearchResults searchResults) {
SearchResultsShimImpl(@NonNull SearchResults searchResults, @NonNull Executor executor) {
mExecutor = Preconditions.checkNotNull(executor);
mSearchResults = Preconditions.checkNotNull(searchResults);
}
@NonNull
public ListenableFuture<AppSearchResult<List<SearchResult>>> getNextPage() {
public ListenableFuture<List<SearchResult>> getNextPage() {
SettableFuture<AppSearchResult<List<SearchResult>>> future = SettableFuture.create();
mSearchResults.getNextPage(future::set);
return future;
return Futures.transform(future, AppSearchResult::getResultValue, mExecutor);
}
@Override

View File

@ -0,0 +1,211 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Set;
/**
* Represents a connection to an AppSearch storage system where {@link GenericDocument}s can be
* placed and queried.
*
* All implementations of this interface must be thread safe.
*/
public interface AppSearchSessionShim {
/**
* Sets the schema that will be used by documents provided to the {@link #putDocuments} method.
*
* <p>The schema provided here is compared to the stored copy of the schema previously supplied
* to {@link #setSchema}, if any, to determine how to treat existing documents. The following
* types of schema modifications are always safe and are made without deleting any existing
* documents:
* <ul>
* <li>Addition of new types
* <li>Addition of new
* {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} or
* {@link AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} properties to a
* type
* <li>Changing the cardinality of a data type to be less restrictive (e.g. changing an
* {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a
* {@link AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} property.
* </ul>
*
* <p>The following types of schema changes are not backwards-compatible:
* <ul>
* <li>Removal of an existing type
* <li>Removal of a property from a type
* <li>Changing the data type ({@code boolean}, {@code long}, etc.) of an existing property
* <li>For properties of {@code Document} type, changing the schema type of
* {@code Document}s of that property
* <li>Changing the cardinality of a data type to be more restrictive (e.g. changing an
* {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a
* {@link AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property).
* <li>Adding a
* {@link AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property.
* </ul>
* <p>Supplying a schema with such changes will, by default, result in this call completing its
* future with an {@link androidx.appsearch.exceptions.AppSearchException} with a code of
* {@link AppSearchResult#RESULT_INVALID_SCHEMA} and a message describing the incompatibility.
* In this case the previously set schema will remain active.
*
* <p>If you need to make non-backwards-compatible changes as described above, you can set the
* {@link SetSchemaRequest.Builder#setForceOverride} method to {@code true}. In this case,
* instead of completing its future with an
* {@link androidx.appsearch.exceptions.AppSearchException} with the
* {@link AppSearchResult#RESULT_INVALID_SCHEMA} error code, all documents which are not
* compatible with the new schema will be deleted and the incompatible schema will be applied.
*
* <p>It is a no-op to set the same schema as has been previously set; this is handled
* efficiently.
*
* <p>By default, documents are visible on platform surfaces. To opt out, call {@code
* SetSchemaRequest.Builder#setPlatformSurfaceable} with {@code surfaceable} as false. Any
* visibility settings apply only to the schemas that are included in the {@code request}.
* Visibility settings for a schema type do not apply or persist across
* {@link SetSchemaRequest}s.
*
* @param request The schema update request.
* @return The pending result of performing this operation.
*/
// TODO(b/169883602): Change @code references to @link when setPlatformSurfaceable APIs are
// exposed.
@NonNull
ListenableFuture<Void> setSchema(@NonNull SetSchemaRequest request);
/**
* Retrieves the schema most recently successfully provided to {@link #setSchema}.
*
* @return The pending result of performing this operation.
*/
// This call hits disk; async API prevents us from treating these calls as properties.
@SuppressLint("KotlinPropertyAccess")
@NonNull
ListenableFuture<Set<AppSearchSchema>> getSchema();
/**
* Indexes documents into AppSearch.
*
* <p>Each {@link GenericDocument}'s {@code schemaType} field must be set to the name of a
* schema type previously registered via the {@link #setSchema} method.
*
* @param request {@link PutDocumentsRequest} containing documents to be indexed
* @return The pending result of performing this operation. The keys of the returned
* {@link AppSearchBatchResult} are the URIs of the input documents. The values are
* {@code null} if they were successfully indexed, or a failed {@link AppSearchResult}
* otherwise.
*/
@NonNull
ListenableFuture<AppSearchBatchResult<String, Void>> putDocuments(
@NonNull PutDocumentsRequest request);
/**
* Retrieves {@link GenericDocument}s by URI.
*
* @param request {@link GetByUriRequest} containing URIs to be retrieved.
* @return The pending result of performing this operation. The keys of the returned
* {@link AppSearchBatchResult} are the input URIs. The values are the returned
* {@link GenericDocument}s on success, or a failed {@link AppSearchResult} otherwise.
* URIs that are not found will return a failed {@link AppSearchResult} with a result code
* of {@link AppSearchResult#RESULT_NOT_FOUND}.
*/
@NonNull
ListenableFuture<AppSearchBatchResult<String, GenericDocument>> getByUri(
@NonNull GetByUriRequest request);
/**
* Searches a document based on a given query string.
*
* <p>Currently we support following features in the raw query format:
* <ul>
* <li>AND
* <p>AND joins (e.g. match documents that have both the terms dog and
* cat).
* Example: hello world matches documents that have both hello and world
* <li>OR
* <p>OR joins (e.g. match documents that have either the term dog or
* cat).
* Example: dog OR puppy
* <li>Exclusion
* <p>Exclude a term (e.g. match documents that do
* not have the term dog).
* Example: -dog excludes the term dog
* <li>Grouping terms
* <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g.
* match documents that have either dog or puppy, and either cat or kitten).
* Example: (dog puppy) (cat kitten) two one group containing two terms.
* <li>Property restricts
* <p> Specifies which properties of a document to specifically match terms in (e.g.
* match documents where the subject property contains important).
* Example: subject:important matches documents with the term important in the
* subject property
* <li>Schema type restricts
* <p>This is similar to property restricts, but allows for restricts on top-level document
* fields, such as schema_type. Clients should be able to limit their query to documents of
* a certain schema_type (e.g. match documents that are of the Email schema_type).
* Example: { schema_type_filters: Email, Video,query: dog } will match documents
* that contain the query term dog and are of either the Email schema type or the
* Video schema type.
* </ul>
*
* <p> This method is lightweight. The heavy work will be done in
* {@link SearchResults#getNextPage()}.
*
* @param queryExpression Query String to search.
* @param searchSpec Spec for setting filters, raw query etc.
* @return The search result of performing this operation.
*/
@NonNull
SearchResultsShim query(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
/**
* Removes {@link GenericDocument}s from the index by URI.
*
* @param request Request containing URIs to be removed.
* @return The pending result of performing this operation. The keys of the returned
* {@link AppSearchBatchResult} are the input URIs. The values are {@code null} on success,
* or a failed {@link AppSearchResult} otherwise. URIs that are not found will return a
* failed {@link AppSearchResult} with a result code of
* {@link AppSearchResult#RESULT_NOT_FOUND}.
*/
@NonNull
ListenableFuture<AppSearchBatchResult<String, Void>> removeByUri(
@NonNull RemoveByUriRequest request);
/**
* Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they
* match the {@code queryExpression} in given namespaces and schemaTypes which is set via
* {@link SearchSpec.Builder#addNamespace} and {@link SearchSpec.Builder#addSchemaType}.
*
* <p> An empty {@code queryExpression} matches all documents.
*
* <p> An empty set of namespaces or schemaTypes matches all namespaces or schemaTypes in
* the current database.
*
* @param queryExpression Query String to search.
* @param searchSpec Spec containing schemaTypes, namespaces and query expression
* indicates how document will be removed. All specific about how to
* scoring, ordering, snippeting and resulting will be ignored.
* @return The pending result of performing this operation.
*/
@NonNull
ListenableFuture<Void> removeByQuery(
@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
}

View File

@ -19,15 +19,11 @@ package com.android.server.appsearch.testing;
import static com.google.common.truth.Truth.assertThat;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSessionShim;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.GetByUriRequest;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SetSchemaRequest;
import android.content.Context;
import com.google.common.collect.ImmutableList;
import android.app.appsearch.SearchResultsShim;
import junit.framework.AssertionFailedError;
@ -37,32 +33,6 @@ import java.util.concurrent.Future;
public class AppSearchTestUtils {
// List of databases that may be used in tests. Keeping them in a centralized location helps
// #cleanup know which databases to clear.
public static final String DEFAULT_DATABASE = AppSearchManager.DEFAULT_DATABASE_NAME;
public static final String DB_1 = "testDb1";
public static final String DB_2 = "testDb2";
public static void cleanup(Context context) throws Exception {
List<String> databases = ImmutableList.of(DEFAULT_DATABASE, DB_1, DB_2);
for (String database : databases) {
AppSearchSessionShim session = checkIsResultSuccess(
AppSearchSessionShim.createSearchSession(
new AppSearchManager.SearchContext.Builder()
.setDatabaseName(database).build()));
checkIsResultSuccess(session.setSchema(
new SetSchemaRequest.Builder().setForceOverride(true).build()));
}
}
public static <V> V checkIsResultSuccess(Future<AppSearchResult<V>> future) throws Exception {
AppSearchResult<V> result = future.get();
if (!result.isSuccess()) {
throw new AssertionFailedError("AppSearchResult not successful: " + result);
}
return result.getResultValue();
}
public static <K, V> AppSearchBatchResult<K, V> checkIsBatchResultSuccess(
Future<AppSearchBatchResult<K, V>> future) throws Exception {
AppSearchBatchResult<K, V> result = future.get();
@ -74,10 +44,13 @@ public class AppSearchTestUtils {
public static List<GenericDocument> doGet(
AppSearchSessionShim session, String namespace, String... uris) throws Exception {
AppSearchBatchResult<String, GenericDocument> result = checkIsBatchResultSuccess(
session.getByUri(
new GetByUriRequest.Builder()
.setNamespace(namespace).addUri(uris).build()));
AppSearchBatchResult<String, GenericDocument> result =
checkIsBatchResultSuccess(
session.getByUri(
new GetByUriRequest.Builder()
.setNamespace(namespace)
.addUri(uris)
.build()));
assertThat(result.getSuccesses()).hasSize(uris.length);
assertThat(result.getFailures()).isEmpty();
List<GenericDocument> list = new ArrayList<>(uris.length);
@ -89,13 +62,13 @@ public class AppSearchTestUtils {
public static List<GenericDocument> convertSearchResultsToDocuments(
SearchResultsShim searchResults) throws Exception {
List<SearchResult> results = checkIsResultSuccess(searchResults.getNextPage());
List<SearchResult> results = searchResults.getNextPage().get();
List<GenericDocument> documents = new ArrayList<>();
while (results.size() > 0) {
for (SearchResult result : results) {
documents.add(result.getDocument());
}
results = checkIsResultSuccess(searchResults.getNextPage());
results = searchResults.getNextPage().get();
}
return documents;
}

View File

@ -0,0 +1,70 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.NonNull;
/**
* This class provides global access to the centralized AppSearch index maintained by the system.
*
* <p>Apps can retrieve indexed documents through the query API.
*/
public interface GlobalSearchSessionShim {
/**
* Searches across all documents in the storage based on a given query string.
*
* <p>Currently we support following features in the raw query format:
* <ul>
* <li>AND
* <p>AND joins (e.g. match documents that have both the terms dog and
* cat).
* Example: hello world matches documents that have both hello and world
* <li>OR
* <p>OR joins (e.g. match documents that have either the term dog or
* cat).
* Example: dog OR puppy
* <li>Exclusion
* <p>Exclude a term (e.g. match documents that do
* not have the term dog).
* Example: -dog excludes the term dog
* <li>Grouping terms
* <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g.
* match documents that have either dog or puppy, and either cat or kitten).
* Example: (dog puppy) (cat kitten) two one group containing two terms.
* <li>Property restricts
* <p> Specifies which properties of a document to specifically match terms in (e.g.
* match documents where the subject property contains important).
* Example: subject:important matches documents with the term important in the
* subject property
* <li>Schema type restricts
* <p>This is similar to property restricts, but allows for restricts on top-level document
* fields, such as schema_type. Clients should be able to limit their query to documents of
* a certain schema_type (e.g. match documents that are of the Email schema_type).
* Example: { schema_type_filters: Email, Video,query: dog } will match documents
* that contain the query term dog and are of either the Email schema type or the
* Video schema type.
* </ul>
*
* <p> This method is lightweight. The heavy work will be done in
* {@link SearchResults#getNextPage}.
*
* @param queryExpression Query String to search.
* @param searchSpec Spec for setting filters, raw query etc.
* @return The search result of performing this operation.
*/
@NonNull
SearchResultsShim query(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.NonNull;
import com.google.common.util.concurrent.ListenableFuture;
import java.io.Closeable;
import java.util.List;
/**
* SearchResults are a returned object from a query API.
*
* <p>Each {@link SearchResult} contains a document and may contain other fields like snippets
* based on request.
*
* <p>Should close this object after finish fetching results.
*
* <p>This class is not thread safe.
*/
public interface SearchResultsShim extends Closeable {
/**
* Gets a whole page of {@link SearchResult}s.
*
* <p>Re-call this method to get next page of {@link SearchResult}, until it returns an
* empty list.
*
* <p>The page size is set by
* {@link android.app.appsearch.SearchSpec.Builder#setResultCountPerPage}.
*
* @return The pending result of performing this operation.
*/
@NonNull
ListenableFuture<List<SearchResult>> getNextPage();
@Override
void close();
}

View File

@ -16,9 +16,6 @@
package android.app.appsearch;
import static android.app.appsearch.AppSearchSchema.PropertyConfig.INDEXING_TYPE_PREFIXES;
import static android.app.appsearch.AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN;
import static com.google.common.truth.Truth.assertThat;
import static org.testng.Assert.expectThrows;
@ -27,12 +24,22 @@ 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_fromSystemUiVisibility() {
IllegalArgumentException expected =
@ -57,7 +64,7 @@ public class SetSchemaRequestTest {
/*visible=*/ true,
new PackageIdentifier(
"com.foo.package",
/*certificate=*/ new byte[] {}))
/*sha256Certificate=*/ new byte[] {}))
.build());
assertThat(expected).hasMessageThat().contains("referenced, but were not added");
}
@ -68,14 +75,14 @@ public class SetSchemaRequestTest {
// By default, the schema is visible.
SetSchemaRequest request = new SetSchemaRequest.Builder().addSchema(schema).build();
assertThat(request.getSchemasNotPlatformSurfaceable()).isEmpty();
assertThat(request.getSchemasNotVisibleToSystemUi()).isEmpty();
request =
new SetSchemaRequest.Builder()
.addSchema(schema)
.setSchemaTypeVisibilityForSystemUi("Schema", true)
.build();
assertThat(request.getSchemasNotPlatformSurfaceable()).isEmpty();
assertThat(request.getSchemasNotVisibleToSystemUi()).isEmpty();
}
@Test
@ -86,7 +93,7 @@ public class SetSchemaRequestTest {
.addSchema(schema)
.setSchemaTypeVisibilityForSystemUi("Schema", false)
.build();
assertThat(request.getSchemasNotPlatformSurfaceable()).containsExactly("Schema");
assertThat(request.getSchemasNotVisibleToSystemUi()).containsExactly("Schema");
}
@Test
@ -95,12 +102,12 @@ public class SetSchemaRequestTest {
// By default, the schema is not visible.
SetSchemaRequest request = new SetSchemaRequest.Builder().addSchema(schema).build();
assertThat(request.getSchemasPackageAccessible()).isEmpty();
assertThat(request.getSchemasVisibleToPackages()).isEmpty();
PackageIdentifier packageIdentifier =
new PackageIdentifier("com.package.foo", new byte[] {100});
Map<String, Set<PackageIdentifier>> expectedPackageVisibleMap = new ArrayMap<>();
expectedPackageVisibleMap.put("Schema", Collections.singleton(packageIdentifier));
Map<String, Set<PackageIdentifier>> expectedVisibleToPackagesMap = new ArrayMap<>();
expectedVisibleToPackagesMap.put("Schema", Collections.singleton(packageIdentifier));
request =
new SetSchemaRequest.Builder()
@ -108,8 +115,8 @@ public class SetSchemaRequestTest {
.setSchemaTypeVisibilityForPackage(
"Schema", /*visible=*/ true, packageIdentifier)
.build();
assertThat(request.getSchemasPackageAccessible())
.containsExactlyEntriesIn(expectedPackageVisibleMap);
assertThat(request.getSchemasVisibleToPackages())
.containsExactlyEntriesIn(expectedVisibleToPackagesMap);
}
@Test
@ -123,9 +130,9 @@ public class SetSchemaRequestTest {
"Schema",
/*visible=*/ false,
new PackageIdentifier(
"com.package.foo", /*certificate=*/ new byte[] {}))
"com.package.foo", /*sha256Certificate=*/ new byte[] {}))
.build();
assertThat(request.getSchemasPackageAccessible()).isEmpty();
assertThat(request.getSchemasVisibleToPackages()).isEmpty();
}
@Test
@ -134,8 +141,8 @@ public class SetSchemaRequestTest {
PackageIdentifier packageIdentifier =
new PackageIdentifier("com.package.foo", new byte[] {100});
Map<String, Set<PackageIdentifier>> expectedPackageVisibleMap = new ArrayMap<>();
expectedPackageVisibleMap.put("Schema", Collections.singleton(packageIdentifier));
Map<String, Set<PackageIdentifier>> expectedVisibleToPackagesMap = new ArrayMap<>();
expectedVisibleToPackagesMap.put("Schema", Collections.singleton(packageIdentifier));
SetSchemaRequest request =
new SetSchemaRequest.Builder()
@ -147,8 +154,8 @@ public class SetSchemaRequestTest {
.setSchemaTypeVisibilityForPackage(
"Schema", /*visible=*/ true, packageIdentifier)
.build();
assertThat(request.getSchemasPackageAccessible())
.containsExactlyEntriesIn(expectedPackageVisibleMap);
assertThat(request.getSchemasVisibleToPackages())
.containsExactlyEntriesIn(expectedVisibleToPackagesMap);
}
@Test
@ -163,16 +170,16 @@ public class SetSchemaRequestTest {
"Schema",
/*visible=*/ true,
new PackageIdentifier(
"com.package.foo", /*certificate=*/ new byte[] {100}))
"com.package.foo", /*sha256Certificate=*/ new byte[] {100}))
// Then make it not visible
.setSchemaTypeVisibilityForPackage(
"Schema",
/*visible=*/ false,
new PackageIdentifier(
"com.package.foo", /*certificate=*/ new byte[] {100}))
"com.package.foo", /*sha256Certificate=*/ new byte[] {100}))
.build();
// Nothing should be visible.
assertThat(request.getSchemasPackageAccessible()).isEmpty();
assertThat(request.getSchemasVisibleToPackages()).isEmpty();
}
}

View File

@ -75,6 +75,8 @@ public class AppSearchImplTest {
SchemaToProtoConverter.toSchemaTypeConfigProto(rewrittenVisibilitySchema.build());
}
// 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