Merge "Update Framework from Jetpack."

This commit is contained in:
Alexander Dorokhine
2021-01-14 18:02:39 +00:00
committed by Android (Google) Code Review
17 changed files with 979 additions and 138 deletions

View File

@@ -162,7 +162,7 @@ package android.app.appsearch {
public static final class PutDocumentsRequest.Builder {
ctor public PutDocumentsRequest.Builder();
method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocument(@NonNull android.app.appsearch.GenericDocument...);
method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocument(@NonNull java.util.Collection<android.app.appsearch.GenericDocument>);
method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocument(@NonNull java.util.Collection<? extends android.app.appsearch.GenericDocument>);
method @NonNull public android.app.appsearch.PutDocumentsRequest build();
}
@@ -182,6 +182,7 @@ package android.app.appsearch {
public final class SearchResult {
method @NonNull public android.app.appsearch.GenericDocument getDocument();
method @NonNull public java.util.List<android.app.appsearch.SearchResult.MatchInfo> getMatches();
method @NonNull public String getPackageName();
}
public static final class SearchResult.MatchInfo {
@@ -204,9 +205,11 @@ package android.app.appsearch {
}
public final class SearchSpec {
method @NonNull public java.util.List<java.lang.String> getFilterPackageNames();
method public int getMaxSnippetSize();
method @NonNull public java.util.List<java.lang.String> getNamespaces();
method public int getOrder();
method @NonNull public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getProjections();
method public int getRankingStrategy();
method public int getResultCountPerPage();
method @NonNull public java.util.List<java.lang.String> getSchemaTypes();
@@ -215,17 +218,23 @@ package android.app.appsearch {
method public int getTermMatch();
field public static final int ORDER_ASCENDING = 1; // 0x1
field public static final int ORDER_DESCENDING = 0; // 0x0
field public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
field public static final int RANKING_STRATEGY_CREATION_TIMESTAMP = 2; // 0x2
field public static final int RANKING_STRATEGY_DOCUMENT_SCORE = 1; // 0x1
field public static final int RANKING_STRATEGY_NONE = 0; // 0x0
field public static final int RANKING_STRATEGY_RELEVANCE_SCORE = 3; // 0x3
field public static final int TERM_MATCH_EXACT_ONLY = 1; // 0x1
field public static final int TERM_MATCH_PREFIX = 2; // 0x2
}
public static final class SearchSpec.Builder {
ctor public SearchSpec.Builder();
method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterPackageNames(@NonNull java.lang.String...);
method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterPackageNames(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SearchSpec.Builder addNamespace(@NonNull java.lang.String...);
method @NonNull public android.app.appsearch.SearchSpec.Builder addNamespace(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SearchSpec.Builder addProjection(@NonNull String, @NonNull java.lang.String...);
method @NonNull public android.app.appsearch.SearchSpec.Builder addProjection(@NonNull String, @NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SearchSpec.Builder addSchemaType(@NonNull java.lang.String...);
method @NonNull public android.app.appsearch.SearchSpec.Builder addSchemaType(@NonNull java.util.Collection<java.lang.String>);
method @NonNull public android.app.appsearch.SearchSpec build();

View File

@@ -28,6 +28,7 @@ import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -234,6 +235,7 @@ public class AppSearchManager {
DEFAULT_DATABASE_NAME,
schemaBundles,
new ArrayList<>(request.getSchemasNotVisibleToSystemUi()),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
request.isForceOverride(),
mContext.getUserId(),
new IAppSearchResultCallback.Stub() {

View File

@@ -22,6 +22,7 @@ import android.annotation.UserIdInt;
import android.os.Bundle;
import android.os.ParcelableException;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -161,11 +162,22 @@ public final class AppSearchSession {
for (AppSearchSchema schema : request.getSchemas()) {
schemaBundles.add(schema.getBundle());
}
Map<String, List<Bundle>> schemasPackageAccessibleBundles =
new ArrayMap<>(request.getSchemasVisibleToPackagesInternal().size());
for (Map.Entry<String, Set<PackageIdentifier>> entry :
request.getSchemasVisibleToPackagesInternal().entrySet()) {
List<Bundle> packageIdentifierBundles = new ArrayList<>(entry.getValue().size());
for (PackageIdentifier packageIdentifier : entry.getValue()) {
packageIdentifierBundles.add(packageIdentifier.getBundle());
}
schemasPackageAccessibleBundles.put(entry.getKey(), packageIdentifierBundles);
}
try {
mService.setSchema(
mDatabaseName,
schemaBundles,
new ArrayList<>(request.getSchemasNotVisibleToSystemUi()),
schemasPackageAccessibleBundles,
request.isForceOverride(),
mUserId,
new IAppSearchResultCallback.Stub() {

View File

@@ -34,6 +34,8 @@ interface IAppSearchManager {
* @param schemaBundles List of {@link AppSearchSchema} bundles.
* @param schemasNotPlatformSurfaceable Schema types that should not be surfaced on platform
* surfaces.
* @param schemasPackageAccessibleBundles Schema types that are visible to the specified
* packages. The value List contains PackageIdentifier Bundles.
* @param forceOverride Whether to apply the new schema even if it is incompatible. All
* incompatible documents will be deleted.
* @param userId Id of the calling user
@@ -44,11 +46,11 @@ interface IAppSearchManager {
in String databaseName,
in List<Bundle> schemaBundles,
in List<String> schemasNotPlatformSurfaceable,
in Map<String, List<Bundle>> schemasPackageAccessibleBundles,
boolean forceOverride,
in int userId,
in IAppSearchResultCallback callback);
/**
* Retrieves the AppSearch schema for this database.
*

View File

@@ -17,16 +17,17 @@
package android.app.appsearch;
import android.annotation.NonNull;
import android.app.appsearch.util.BundleUtil;
import android.os.Bundle;
import com.android.internal.util.Preconditions;
import java.util.Arrays;
import java.util.Objects;
/** This class represents a uniquely identifiable package. */
public class PackageIdentifier {
private final String mPackageName;
private final byte[] mSha256Certificate;
private static final String PACKAGE_NAME_FIELD = "packageName";
private static final String SHA256_CERTIFICATE_FIELD = "sha256Certificate";
private final Bundle mBundle;
/**
* Creates a unique identifier for a package.
@@ -35,18 +36,30 @@ public class PackageIdentifier {
* @param sha256Certificate SHA256 certificate digest of the package.
*/
public PackageIdentifier(@NonNull String packageName, @NonNull byte[] sha256Certificate) {
mPackageName = Preconditions.checkNotNull(packageName);
mSha256Certificate = Preconditions.checkNotNull(sha256Certificate);
mBundle = new Bundle();
mBundle.putString(PACKAGE_NAME_FIELD, packageName);
mBundle.putByteArray(SHA256_CERTIFICATE_FIELD, sha256Certificate);
}
/** @hide */
public PackageIdentifier(@NonNull Bundle bundle) {
mBundle = Preconditions.checkNotNull(bundle);
}
/** @hide */
@NonNull
public Bundle getBundle() {
return mBundle;
}
@NonNull
public String getPackageName() {
return mPackageName;
return Preconditions.checkNotNull(mBundle.getString(PACKAGE_NAME_FIELD));
}
@NonNull
public byte[] getSha256Certificate() {
return mSha256Certificate;
return Preconditions.checkNotNull(mBundle.getByteArray(SHA256_CERTIFICATE_FIELD));
}
@Override
@@ -58,12 +71,11 @@ public class PackageIdentifier {
return false;
}
final PackageIdentifier other = (PackageIdentifier) obj;
return this.mPackageName.equals(other.mPackageName)
&& Arrays.equals(this.mSha256Certificate, other.mSha256Certificate);
return BundleUtil.deepEquals(mBundle, other.mBundle);
}
@Override
public int hashCode() {
return Objects.hash(mPackageName, Arrays.hashCode(mSha256Certificate));
return BundleUtil.deepHashCode(mBundle);
}
}

View File

@@ -30,7 +30,7 @@ import java.util.List;
/**
* Encapsulates a request to index a document into an {@link AppSearchSession} database.
*
* @see AppSearchSession#putDocuments
* <p>@see AppSearchSession#putDocuments
*/
public final class PutDocumentsRequest {
private final List<GenericDocument> mDocuments;
@@ -45,12 +45,16 @@ public final class PutDocumentsRequest {
return Collections.unmodifiableList(mDocuments);
}
/** Builder for {@link PutDocumentsRequest} objects. */
/**
* Builder for {@link PutDocumentsRequest} objects.
*
* <p>Once {@link #build} is called, the instance can no longer be used.
*/
public static final class Builder {
private final List<GenericDocument> mDocuments = new ArrayList<>();
private boolean mBuilt = false;
/** Adds one or more documents to the request. */
/** Adds one or more {@link GenericDocument} objects to the request. */
@SuppressLint("MissingGetterMatchingBuilder") // Merged list available from getDocuments()
@NonNull
public Builder addGenericDocument(@NonNull GenericDocument... documents) {
@@ -58,17 +62,18 @@ public final class PutDocumentsRequest {
return addGenericDocument(Arrays.asList(documents));
}
/** Adds one or more documents to the request. */
/** Adds a collection of {@link GenericDocument} objects to the request. */
@SuppressLint("MissingGetterMatchingBuilder") // Merged list available from getDocuments()
@NonNull
public Builder addGenericDocument(@NonNull Collection<GenericDocument> documents) {
public Builder addGenericDocument(
@NonNull Collection<? extends GenericDocument> documents) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Preconditions.checkNotNull(documents);
mDocuments.addAll(documents);
return this;
}
/** Builds a new {@link PutDocumentsRequest}. */
/** Creates a new {@link PutDocumentsRequest} object. */
@NonNull
public PutDocumentsRequest build() {
Preconditions.checkState(!mBuilt, "Builder has already been used");

View File

@@ -109,10 +109,9 @@ public final class SearchResult {
}
/**
* Contains the package name that stored the {@link GenericDocument}.
* Contains the package name of the app that stored the {@link GenericDocument}.
*
* @return Package name that stored the document
* @hide
*/
@NonNull
public String getPackageName() {

View File

@@ -19,6 +19,7 @@ package android.app.appsearch;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.app.appsearch.exceptions.IllegalSearchSpecException;
import android.os.Bundle;
import android.util.ArrayMap;
@@ -42,17 +43,15 @@ import java.util.Set;
// TODO(sidchhabra) : AddResultSpec fields for Snippets etc.
public final class SearchSpec {
/**
* Schema type to be used in {@link SearchSpec.Builder#addProjectionTypePropertyPath} to apply
* property paths to all results, excepting any types that have had their own, specific property
* paths set.
*
* @hide
* Schema type to be used in {@link SearchSpec.Builder#addProjection} to apply property paths to
* all results, excepting any types that have had their own, specific property paths set.
*/
public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
static final String TERM_MATCH_TYPE_FIELD = "termMatchType";
static final String SCHEMA_TYPE_FIELD = "schemaType";
static final String NAMESPACE_FIELD = "namespace";
static final String PACKAGE_NAME_FIELD = "packageName";
static final String NUM_PER_PAGE_FIELD = "numPerPage";
static final String RANKING_STRATEGY_FIELD = "rankingStrategy";
static final String ORDER_FIELD = "order";
@@ -106,7 +105,8 @@ public final class SearchSpec {
value = {
RANKING_STRATEGY_NONE,
RANKING_STRATEGY_DOCUMENT_SCORE,
RANKING_STRATEGY_CREATION_TIMESTAMP
RANKING_STRATEGY_CREATION_TIMESTAMP,
RANKING_STRATEGY_RELEVANCE_SCORE
})
@Retention(RetentionPolicy.SOURCE)
public @interface RankingStrategy {}
@@ -117,6 +117,8 @@ public final class SearchSpec {
public static final int RANKING_STRATEGY_DOCUMENT_SCORE = 1;
/** Ranked by document creation timestamps. */
public static final int RANKING_STRATEGY_CREATION_TIMESTAMP = 2;
/** Ranked by document relevance score. */
public static final int RANKING_STRATEGY_RELEVANCE_SCORE = 3;
/**
* Order for query result.
@@ -172,7 +174,7 @@ public final class SearchSpec {
}
/**
* Returns the list of namespaces to search for.
* Returns the list of namespaces to search over.
*
* <p>If empty, the query will search over all namespaces.
*/
@@ -185,6 +187,40 @@ public final class SearchSpec {
return Collections.unmodifiableList(namespaces);
}
/**
* Returns the list of package name filters to search over.
*
* <p>If empty, the query will search over all packages that the caller has access to. If
* package names are specified which caller doesn't have access to, then those package names
* will be ignored.
*/
@NonNull
public List<String> getFilterPackageNames() {
List<String> packageNames = mBundle.getStringArrayList(PACKAGE_NAME_FIELD);
if (packageNames == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(packageNames);
}
/**
* Returns the list of package names to search over.
*
* <p>If unset, the query will search over all packages that the caller has access to. If
* package names are specified which caller doesn't have access to, then those package names
* will be ignored.
*
* @hide
*/
@NonNull
public List<String> getPackageNames() {
List<String> packageNames = mBundle.getStringArrayList(PACKAGE_NAME_FIELD);
if (packageNames == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(packageNames);
}
/** Returns the number of results per page in the result set. */
public int getResultCountPerPage() {
return mBundle.getInt(NUM_PER_PAGE_FIELD, DEFAULT_NUM_PER_PAGE);
@@ -224,11 +260,9 @@ public final class SearchSpec {
*
* <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned by this
* function, rather than calling it multiple times.
*
* @hide
*/
@NonNull
public Map<String, List<String>> getProjectionTypePropertyPaths() {
public Map<String, List<String>> getProjections() {
Bundle typePropertyPathsBundle = mBundle.getBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD);
Set<String> schemaTypes = typePropertyPathsBundle.keySet();
Map<String, List<String>> typePropertyPathsMap = new ArrayMap<>(schemaTypes.size());
@@ -245,6 +279,7 @@ public final class SearchSpec {
private final Bundle mBundle;
private final ArrayList<String> mSchemaTypes = new ArrayList<>();
private final ArrayList<String> mNamespaces = new ArrayList<>();
private final ArrayList<String> mPackageNames = new ArrayList<>();
private final Bundle mProjectionTypePropertyMasks = new Bundle();
private boolean mBuilt = false;
@@ -318,6 +353,43 @@ public final class SearchSpec {
return this;
}
/**
* Adds a package name filter to {@link SearchSpec} Entry. Only search for documents that
* were indexed from the specified packages.
*
* <p>If unset, the query will search over all packages that the caller has access to. If
* package names are specified which caller doesn't have access to, then those package names
* will be ignored.
*/
// Getter is called "getFilterPackageNames" (as opposed to the suggested
// "getFilterPackageNameses")
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder addFilterPackageNames(@NonNull String... packageNames) {
Preconditions.checkNotNull(packageNames);
Preconditions.checkState(!mBuilt, "Builder has already been used");
return addFilterPackageNames(Arrays.asList(packageNames));
}
/**
* Adds a package name filter to {@link SearchSpec} Entry. Only search for documents that
* were indexed from the specified packages.
*
* <p>If unset, the query will search over all packages that the caller has access to. If
* package names are specified which caller doesn't have access to, then those package names
* will be ignored.
*/
// Getter is called "getFilterPackageNames" (as opposed to the suggested
// "getFilterPackageNameses")
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder addFilterPackageNames(@NonNull Collection<String> packageNames) {
Preconditions.checkNotNull(packageNames);
Preconditions.checkState(!mBuilt, "Builder has already been used");
mPackageNames.addAll(packageNames);
return this;
}
/**
* Sets the number of results per page in the returned object.
*
@@ -339,7 +411,7 @@ public final class SearchSpec {
Preconditions.checkArgumentInRange(
rankingStrategy,
RANKING_STRATEGY_NONE,
RANKING_STRATEGY_CREATION_TIMESTAMP,
RANKING_STRATEGY_RELEVANCE_SCORE,
"Result ranking strategy");
mBundle.putInt(RANKING_STRATEGY_FIELD, rankingStrategy);
return this;
@@ -480,14 +552,12 @@ public final class SearchSpec {
* subject: "IMPORTANT"
* }
* }</pre>
*
* @hide
*/
@NonNull
public SearchSpec.Builder addProjectionTypePropertyPaths(
public SearchSpec.Builder addProjection(
@NonNull String schemaType, @NonNull String... propertyPaths) {
Preconditions.checkNotNull(propertyPaths);
return addProjectionTypePropertyPaths(schemaType, Arrays.asList(propertyPaths));
return addProjection(schemaType, Arrays.asList(propertyPaths));
}
/**
@@ -503,12 +573,10 @@ public final class SearchSpec {
* then those property paths will apply to all results, excepting any types that have their
* own, specific property paths set.
*
* <p>{@see SearchSpec.Builder#addProjectionTypePropertyPath(String, String...)}
*
* @hide
* <p>{@see SearchSpec.Builder#addProjection(String, String...)}
*/
@NonNull
public SearchSpec.Builder addProjectionTypePropertyPaths(
public SearchSpec.Builder addProjection(
@NonNull String schemaType, @NonNull Collection<String> propertyPaths) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Preconditions.checkNotNull(schemaType);
@@ -535,6 +603,7 @@ public final class SearchSpec {
}
mBundle.putStringArrayList(NAMESPACE_FIELD, mNamespaces);
mBundle.putStringArrayList(SCHEMA_TYPE_FIELD, mSchemaTypes);
mBundle.putStringArrayList(PACKAGE_NAME_FIELD, mPackageNames);
mBundle.putBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD, mProjectionTypePropertyMasks);
mBuilt = true;
return new SearchSpec(mBundle);

View File

@@ -27,6 +27,7 @@ import android.app.appsearch.GenericDocument;
import android.app.appsearch.IAppSearchBatchResultCallback;
import android.app.appsearch.IAppSearchManager;
import android.app.appsearch.IAppSearchResultCallback;
import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.SearchResultPage;
import android.app.appsearch.SearchSpec;
import android.content.Context;
@@ -34,6 +35,7 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.ParcelableException;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -42,6 +44,7 @@ import com.android.server.appsearch.external.localstorage.AppSearchImpl;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* TODO(b/142567528): add comments when implement this class
@@ -64,6 +67,7 @@ public class AppSearchManagerService extends SystemService {
@NonNull String databaseName,
@NonNull List<Bundle> schemaBundles,
@NonNull List<String> schemasNotPlatformSurfaceable,
@NonNull Map<String, List<Bundle>> schemasPackageAccessibleBundles,
boolean forceOverride,
@UserIdInt int userId,
@NonNull IAppSearchResultCallback callback) {
@@ -78,9 +82,25 @@ public class AppSearchManagerService extends SystemService {
for (int i = 0; i < schemaBundles.size(); i++) {
schemas.add(new AppSearchSchema(schemaBundles.get(i)));
}
Map<String, List<PackageIdentifier>> schemasPackageAccessible =
new ArrayMap<>(schemasPackageAccessibleBundles.size());
for (Map.Entry<String, List<Bundle>> entry :
schemasPackageAccessibleBundles.entrySet()) {
List<PackageIdentifier> packageIdentifiers =
new ArrayList<>(entry.getValue().size());
for (int i = 0; i < packageIdentifiers.size(); i++) {
packageIdentifiers.add(new PackageIdentifier(entry.getValue().get(i)));
}
schemasPackageAccessible.put(entry.getKey(), packageIdentifiers);
}
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
String packageName = convertUidToPackageName(callingUid);
impl.setSchema(packageName, databaseName, schemas, schemasNotPlatformSurfaceable,
impl.setSchema(
packageName,
databaseName,
schemas,
schemasNotPlatformSurfaceable,
schemasPackageAccessible,
forceOverride);
invokeCallbackOnResult(callback,
AppSearchResult.newSuccessfulResult(/*result=*/ null));

View File

@@ -21,10 +21,12 @@ import android.annotation.WorkerThread;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.SearchResultPage;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.exceptions.AppSearchException;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -230,6 +232,7 @@ public final class AppSearchImpl {
* @param schemas Schemas to set for this app.
* @param schemasNotPlatformSurfaceable Schema types that should not be surfaced on platform
* surfaces.
* @param schemasPackageAccessible Schema types that are visible to the specified packages.
* @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.
@@ -239,6 +242,7 @@ public final class AppSearchImpl {
@NonNull String databaseName,
@NonNull List<AppSearchSchema> schemas,
@NonNull List<String> schemasNotPlatformSurfaceable,
@NonNull Map<String, List<PackageIdentifier>> schemasPackageAccessible,
boolean forceOverride)
throws AppSearchException {
mReadWriteLock.writeLock().lock();
@@ -291,7 +295,18 @@ public final class AppSearchImpl {
prefixedSchemasNotPlatformSurfaceable.add(
prefix + schemasNotPlatformSurfaceable.get(i));
}
mVisibilityStoreLocked.setVisibility(prefix, prefixedSchemasNotPlatformSurfaceable);
Map<String, List<PackageIdentifier>> prefixedSchemasPackageAccessible =
new ArrayMap<>(schemasNotPlatformSurfaceable.size());
for (Map.Entry<String, List<PackageIdentifier>> entry :
schemasPackageAccessible.entrySet()) {
prefixedSchemasPackageAccessible.put(prefix + entry.getKey(), entry.getValue());
}
mVisibilityStoreLocked.setVisibility(
prefix,
prefixedSchemasNotPlatformSurfaceable,
prefixedSchemasPackageAccessible);
// Determine whether to schedule an immediate optimize.
if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0
@@ -448,6 +463,13 @@ public final class AppSearchImpl {
@NonNull String queryExpression,
@NonNull SearchSpec searchSpec)
throws AppSearchException {
if (!searchSpec.getPackageNames().isEmpty()
&& !searchSpec.getPackageNames().contains(packageName)) {
// Client wanted to query over some packages that weren't its own. This isn't
// allowed through local query so we can return early with no results.
return new SearchResultPage(Bundle.EMPTY);
}
mReadWriteLock.readLock().lock();
try {
return doQueryLocked(
@@ -480,13 +502,25 @@ public final class AppSearchImpl {
// verified.
mReadWriteLock.readLock().lock();
try {
// We use the mNamespaceMap.keySet here because it's the smaller set of valid prefixes
// that could exist.
Set<String> prefixes = mNamespaceMapLocked.keySet();
Set<String> prefixes = new ArraySet<>();
Set<String> packageFilters = new ArraySet<>(searchSpec.getPackageNames());
// Filter out any VisibilityStore documents which are AppSearch-internal only.
prefixes.remove(
createPrefix(VisibilityStore.PACKAGE_NAME, VisibilityStore.DATABASE_NAME));
for (String prefix : mNamespaceMapLocked.keySet()) {
if (prefix.equals(VisibilityStore.VISIBILITY_STORE_PREFIX)) {
// Filter out any VisibilityStore documents which are AppSearch-internal only.
continue;
}
if (!packageFilters.isEmpty() && !packageFilters.contains(getPackageName(prefix))) {
// Client wanted to restrict search over specified packages. Since the
// specified packages don't include this prefix, don't add it to our search
// filters.
continue;
}
// Otherwise, include this prefix in our global search.
prefixes.add(prefix);
}
return doQueryLocked(prefixes, queryExpression, searchSpec);
} finally {
@@ -500,22 +534,30 @@ public final class AppSearchImpl {
@NonNull String queryExpression,
@NonNull SearchSpec searchSpec)
throws AppSearchException {
SearchSpecProto searchSpecProto = SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
SearchSpecProto.Builder searchSpecBuilder =
searchSpecProto.toBuilder().setQuery(queryExpression);
ResultSpecProto resultSpec = SearchSpecToProtoConverter.toResultSpecProto(searchSpec);
ScoringSpecProto scoringSpec = SearchSpecToProtoConverter.toScoringSpecProto(searchSpec);
SearchResultProto searchResultProto;
SearchSpecToProtoConverter.toSearchSpecProto(searchSpec).toBuilder()
.setQuery(queryExpression);
// rewriteSearchSpecForPrefixesLocked will return false if none of the prefixes that the
// client is trying to search on exist, so we can return an empty SearchResult and skip
// sending request to Icing.
if (!rewriteSearchSpecForPrefixesLocked(searchSpecBuilder, prefixes)) {
return new SearchResultPage(Bundle.EMPTY);
}
searchResultProto =
mIcingSearchEngineLocked.search(searchSpecBuilder.build(), scoringSpec, resultSpec);
ResultSpecProto.Builder resultSpecBuilder =
SearchSpecToProtoConverter.toResultSpecProto(searchSpec).toBuilder();
// rewriteResultSpecForPrefixesLocked will return false if none of the prefixes that the
// client is trying to search on exist, so we can return an empty SearchResult and skip
// sending request to Icing.
if (!rewriteResultSpecForPrefixesLocked(resultSpecBuilder, prefixes)) {
return new SearchResultPage(Bundle.EMPTY);
}
ScoringSpecProto scoringSpec = SearchSpecToProtoConverter.toScoringSpecProto(searchSpec);
SearchResultProto searchResultProto =
mIcingSearchEngineLocked.search(
searchSpecBuilder.build(), scoringSpec, resultSpecBuilder.build());
checkSuccess(searchResultProto.getStatus());
return rewriteSearchResultProto(searchResultProto);
@@ -607,6 +649,14 @@ public final class AppSearchImpl {
@NonNull String queryExpression,
@NonNull SearchSpec searchSpec)
throws AppSearchException {
if (!searchSpec.getPackageNames().isEmpty()
&& !searchSpec.getPackageNames().contains(packageName)) {
// We're only removing documents within the parameter `packageName`. If we're not
// restricting our remove-query to this package name, then there's nothing for us to
// remove.
return;
}
SearchSpecProto searchSpecProto = SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
SearchSpecProto.Builder searchSpecBuilder =
searchSpecProto.toBuilder().setQuery(queryExpression);
@@ -915,6 +965,47 @@ public final class AppSearchImpl {
return true;
}
/**
* Rewrites the typePropertyMasks that exist in {@code prefixes}.
*
* <p>This method should be only called in query methods and get the READ lock to keep thread
* safety.
*
* @return false if none of the requested prefixes exist.
*/
@VisibleForTesting
@GuardedBy("mReadWriteLock")
boolean rewriteResultSpecForPrefixesLocked(
@NonNull ResultSpecProto.Builder resultSpecBuilder, @NonNull Set<String> prefixes) {
// Create a copy since retainAll() modifies the original set.
Set<String> existingPrefixes = new ArraySet<>(mNamespaceMapLocked.keySet());
existingPrefixes.retainAll(prefixes);
if (existingPrefixes.isEmpty()) {
// None of the prefixes exist, empty query.
return false;
}
List<ResultSpecProto.TypePropertyMask> prefixedTypePropertyMasks = new ArrayList<>();
// Rewrite filters to include a database prefix.
for (String prefix : existingPrefixes) {
Set<String> existingSchemaTypes = mSchemaMapLocked.get(prefix);
// Qualify the given schema types
for (ResultSpecProto.TypePropertyMask typePropertyMask :
resultSpecBuilder.getTypePropertyMasksList()) {
String qualifiedType = prefix + typePropertyMask.getSchemaType();
if (existingSchemaTypes.contains(qualifiedType)) {
prefixedTypePropertyMasks.add(
typePropertyMask.toBuilder().setSchemaType(qualifiedType).build());
}
}
}
resultSpecBuilder
.clearTypePropertyMasks()
.addAllTypePropertyMasks(prefixedTypePropertyMasks);
return true;
}
@VisibleForTesting
@GuardedBy("mReadWriteLock")
SchemaProto getSchemaProtoLocked() throws AppSearchException {

View File

@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.exceptions.AppSearchException;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -27,19 +28,21 @@ import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Manages any visibility settings for all the databases that AppSearchImpl knows about. Persists
* the visibility settings and reloads them on initialization.
* Manages any visibility settings for all the package's databases that AppSearchImpl knows about.
* Persists the visibility settings and reloads them on initialization.
*
* <p>The VisibilityStore creates a document for each database. This document holds the visibility
* settings that apply to that database. The VisibilityStore also creates a schema for these
* documents and has its own database so that its data doesn't interfere with any clients' data. It
* persists the document and schema through AppSearchImpl.
* <p>The VisibilityStore creates a document for each package's databases. This document holds the
* visibility settings that apply to that package's database. The VisibilityStore also creates a
* schema for these documents and has its own package and database so that its data doesn't
* interfere with any clients' data. It persists the document and schema through AppSearchImpl.
*
* <p>These visibility settings are used to ensure AppSearch queries respect the clients' settings
* on who their data is visible to.
@@ -52,18 +55,31 @@ import java.util.Set;
*/
class VisibilityStore {
/** Schema type for documents that hold AppSearch's metadata, e.g. visibility settings */
@VisibleForTesting static final String SCHEMA_TYPE = "Visibility";
@VisibleForTesting static final String VISIBILITY_TYPE = "VisibilityType";
/**
* Property that holds the list of platform-hidden schemas, as part of the visibility settings.
*/
@VisibleForTesting
static final String NOT_PLATFORM_SURFACEABLE_PROPERTY = "notPlatformSurfaceable";
private static final String NOT_PLATFORM_SURFACEABLE_PROPERTY = "notPlatformSurfaceable";
/** Schema for the VisibilityStore's docuemnts. */
@VisibleForTesting
static final AppSearchSchema SCHEMA =
new AppSearchSchema.Builder(SCHEMA_TYPE)
/** Property that holds nested documents of package accessible schemas. */
private static final String PACKAGE_ACCESSIBLE_PROPERTY = "packageAccessible";
/** Schema type for nested documents that hold package accessible information. */
private static final String PACKAGE_ACCESSIBLE_TYPE = "PackageAccessibleType";
/** Property that holds the package name that can access a schema. */
private static final String PACKAGE_NAME_PROPERTY = "packageName";
/** Property that holds the SHA 256 certificate of the app that can access a schema. */
private static final String SHA_256_CERT_PROPERTY = "sha256Cert";
/** Property that holds the prefixed schema type that is accessible by some package. */
private static final String ACCESSIBLE_SCHEMA_PROPERTY = "accessibleSchema";
/** Schema for the VisibilityStore's documents. */
private static final AppSearchSchema VISIBILITY_SCHEMA =
new AppSearchSchema.Builder(VISIBILITY_TYPE)
.addProperty(
new AppSearchSchema.PropertyConfig.Builder(
NOT_PLATFORM_SURFACEABLE_PROPERTY)
@@ -71,6 +87,39 @@ class VisibilityStore {
.setCardinality(
AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
.build())
.addProperty(
new AppSearchSchema.PropertyConfig.Builder(PACKAGE_ACCESSIBLE_PROPERTY)
.setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_DOCUMENT)
.setSchemaType(PACKAGE_ACCESSIBLE_TYPE)
.setCardinality(
AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
.build())
.build();
/**
* Schema for package accessible documents, these will be nested in a top-level visibility
* document.
*/
private static final AppSearchSchema PACKAGE_ACCESSIBLE_SCHEMA =
new AppSearchSchema.Builder(PACKAGE_ACCESSIBLE_TYPE)
.addProperty(
new AppSearchSchema.PropertyConfig.Builder(PACKAGE_NAME_PROPERTY)
.setCardinality(
AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
.setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
.build())
.addProperty(
new AppSearchSchema.PropertyConfig.Builder(SHA_256_CERT_PROPERTY)
.setCardinality(
AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
.setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES)
.build())
.addProperty(
new AppSearchSchema.PropertyConfig.Builder(ACCESSIBLE_SCHEMA_PROPERTY)
.setCardinality(
AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
.setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
.build())
.build();
/**
@@ -86,7 +135,7 @@ class VisibilityStore {
* database name. Tracked here to tell when we're looking at our own prefix when looking through
* AppSearchImpl.
*/
private static final String VISIBILITY_STORE_PREFIX =
static final String VISIBILITY_STORE_PREFIX =
AppSearchImpl.createPrefix(PACKAGE_NAME, DATABASE_NAME);
/** Namespace of documents that contain visibility settings */
@@ -102,9 +151,22 @@ class VisibilityStore {
/**
* Maps prefixes to the set of schemas that are platform-hidden within that prefix. All schemas
* in the map are prefixed.
*
* <p>Although the prefix key isn't used for lookup, it's helpful in ensuring that all previous
* visibility settings for a prefix are completely overridden by new visibility settings.
*/
private final Map<String, Set<String>> mNotPlatformSurfaceableMap = new ArrayMap<>();
/**
* Maps prefixes to a an internal map. The internal map maps prefixed schemas to the set of
* PackageIdentifiers that have access to that schema.
*
* <p>Although the prefix key isn't used for lookup, it's helpful in ensuring that all previous
* visibility settings for a prefix are completely overridden by new visibility settings.
*/
private final Map<String, Map<String, Set<PackageIdentifier>>> mPackageAccessibleMap =
new ArrayMap<>();
/**
* Creates an uninitialized VisibilityStore object. Callers must also call {@link #initialize()}
* before using the object.
@@ -120,19 +182,22 @@ class VisibilityStore {
*
* <p>This is kept separate from the constructor because this will call methods on
* AppSearchImpl. Some may even then recursively call back into VisibilityStore (for example,
* {@link AppSearchImpl#setSchema} will call {@link #setVisibility(String, Set)}. We need to
* have both AppSearchImpl and VisibilityStore fully initialized for this call flow to work.
* {@link AppSearchImpl#setSchema} will call {@link #setVisibility}. We need to have both
* AppSearchImpl and VisibilityStore fully initialized for this call flow to work.
*
* @throws AppSearchException AppSearchException on AppSearchImpl error.
*/
public void initialize() throws AppSearchException {
if (!mAppSearchImpl.hasSchemaTypeLocked(PACKAGE_NAME, DATABASE_NAME, SCHEMA_TYPE)) {
if (!mAppSearchImpl.hasSchemaTypeLocked(PACKAGE_NAME, DATABASE_NAME, VISIBILITY_TYPE)
|| !mAppSearchImpl.hasSchemaTypeLocked(
PACKAGE_NAME, DATABASE_NAME, PACKAGE_ACCESSIBLE_TYPE)) {
// Schema type doesn't exist yet. Add it.
mAppSearchImpl.setSchema(
PACKAGE_NAME,
DATABASE_NAME,
Collections.singletonList(SCHEMA),
Arrays.asList(VISIBILITY_SCHEMA, PACKAGE_ACCESSIBLE_SCHEMA),
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
}
@@ -153,9 +218,41 @@ class VisibilityStore {
NAMESPACE,
/*uri=*/ addUriPrefix(prefix));
// Update platform visibility settings
String[] schemas =
document.getPropertyStringArray(NOT_PLATFORM_SURFACEABLE_PROPERTY);
mNotPlatformSurfaceableMap.put(prefix, new ArraySet<>(Arrays.asList(schemas)));
if (schemas != null) {
mNotPlatformSurfaceableMap.put(prefix, new ArraySet<>(Arrays.asList(schemas)));
}
// Update 3p package visibility settings
Map<String, Set<PackageIdentifier>> schemaToPackageIdentifierMap = new ArrayMap<>();
GenericDocument[] packageAccessibleDocuments =
document.getPropertyDocumentArray(PACKAGE_ACCESSIBLE_PROPERTY);
if (packageAccessibleDocuments != null) {
for (int i = 0; i < packageAccessibleDocuments.length; i++) {
String packageName =
packageAccessibleDocuments[i].getPropertyString(
PACKAGE_NAME_PROPERTY);
byte[] sha256Cert =
packageAccessibleDocuments[i].getPropertyBytes(
SHA_256_CERT_PROPERTY);
PackageIdentifier packageIdentifier =
new PackageIdentifier(packageName, sha256Cert);
String prefixedSchema =
packageAccessibleDocuments[i].getPropertyString(
ACCESSIBLE_SCHEMA_PROPERTY);
Set<PackageIdentifier> packageIdentifiers =
schemaToPackageIdentifierMap.get(prefixedSchema);
if (packageIdentifiers == null) {
packageIdentifiers = new ArraySet<>();
}
packageIdentifiers.add(packageIdentifier);
schemaToPackageIdentifierMap.put(prefixedSchema, packageIdentifiers);
}
}
mPackageAccessibleMap.put(prefix, schemaToPackageIdentifierMap);
} catch (AppSearchException e) {
if (e.getResultCode() == AppSearchResult.RESULT_NOT_FOUND) {
// TODO(b/172068212): This indicates some desync error. We were expecting a
@@ -176,31 +273,67 @@ class VisibilityStore {
* @param prefix Prefix that identifies who owns the {@code schemasNotPlatformSurfaceable}.
* @param schemasNotPlatformSurfaceable Set of prefixed schemas that should be hidden from the
* platform.
* @param schemasPackageAccessible Map of prefixed schemas to a list of package identifiers that
* have access to the schema.
* @throws AppSearchException on AppSearchImpl error.
*/
public void setVisibility(
@NonNull String prefix, @NonNull Set<String> schemasNotPlatformSurfaceable)
@NonNull String prefix,
@NonNull Set<String> schemasNotPlatformSurfaceable,
@NonNull Map<String, List<PackageIdentifier>> schemasPackageAccessible)
throws AppSearchException {
Preconditions.checkNotNull(prefix);
Preconditions.checkNotNull(schemasNotPlatformSurfaceable);
Preconditions.checkNotNull(schemasPackageAccessible);
// Persist the document
GenericDocument.Builder visibilityDocument =
new GenericDocument.Builder(/*uri=*/ addUriPrefix(prefix), SCHEMA_TYPE)
new GenericDocument.Builder(/*uri=*/ addUriPrefix(prefix), VISIBILITY_TYPE)
.setNamespace(NAMESPACE);
if (!schemasNotPlatformSurfaceable.isEmpty()) {
visibilityDocument.setPropertyString(
NOT_PLATFORM_SURFACEABLE_PROPERTY,
schemasNotPlatformSurfaceable.toArray(new String[0]));
}
Map<String, Set<PackageIdentifier>> schemaToPackageIdentifierMap = new ArrayMap<>();
List<GenericDocument> packageAccessibleDocuments = new ArrayList<>();
for (Map.Entry<String, List<PackageIdentifier>> entry :
schemasPackageAccessible.entrySet()) {
for (int i = 0; i < entry.getValue().size(); i++) {
// TODO(b/169883602): remove the "placeholder" uri once upstream changes to relax
// nested
// document uri rules gets synced down.
GenericDocument packageAccessibleDocument =
new GenericDocument.Builder(/*uri=*/ "placeholder", PACKAGE_ACCESSIBLE_TYPE)
.setNamespace(NAMESPACE)
.setPropertyString(
PACKAGE_NAME_PROPERTY,
entry.getValue().get(i).getPackageName())
.setPropertyBytes(
SHA_256_CERT_PROPERTY,
entry.getValue().get(i).getSha256Certificate())
.setPropertyString(ACCESSIBLE_SCHEMA_PROPERTY, entry.getKey())
.build();
packageAccessibleDocuments.add(packageAccessibleDocument);
}
schemaToPackageIdentifierMap.put(entry.getKey(), new ArraySet<>(entry.getValue()));
}
if (!packageAccessibleDocuments.isEmpty()) {
visibilityDocument.setPropertyDocument(
PACKAGE_ACCESSIBLE_PROPERTY,
packageAccessibleDocuments.toArray(new GenericDocument[0]));
}
mAppSearchImpl.putDocument(PACKAGE_NAME, DATABASE_NAME, visibilityDocument.build());
// Update derived data structures.
mNotPlatformSurfaceableMap.put(prefix, schemasNotPlatformSurfaceable);
mPackageAccessibleMap.put(prefix, schemaToPackageIdentifierMap);
}
/** Returns if the schema is surfaceable by the platform. */
@NonNull
// TODO(b/169883602): check permissions against the allowlisted global querier package name.
public boolean isSchemaPlatformSurfaceable(
@NonNull String prefix, @NonNull String prefixedSchema) {
Preconditions.checkNotNull(prefix);
@@ -212,6 +345,31 @@ class VisibilityStore {
return !notPlatformSurfaceableSchemas.contains(prefixedSchema);
}
/** Returns whether the schema is accessible by {@code accessingPackage}. */
// TODO(b/169883602): check certificate and package against the incoming querier's uid/package.
public boolean isSchemaPackageAccessible(
@NonNull String prefix,
@NonNull String prefixedSchema,
@NonNull PackageIdentifier accessingPackage) {
Preconditions.checkNotNull(prefix);
Preconditions.checkNotNull(prefixedSchema);
Preconditions.checkNotNull(accessingPackage);
Map<String, Set<PackageIdentifier>> schemaToPackageIdentifierMap =
mPackageAccessibleMap.get(prefix);
if (schemaToPackageIdentifierMap == null) {
return false;
}
Set<PackageIdentifier> packageIdentifiers =
schemaToPackageIdentifierMap.get(prefixedSchema);
if (packageIdentifiers == null) {
return false;
}
return packageIdentifiers.contains(accessingPackage);
}
/**
* Handles an {@code AppSearchImpl#reset()} by clearing any cached state.
*
@@ -219,6 +377,7 @@ class VisibilityStore {
*/
void handleReset() {
mNotPlatformSurfaceableMap.clear();
mPackageAccessibleMap.clear();
}
/**

View File

@@ -26,6 +26,9 @@ import com.google.android.icing.proto.ScoringSpecProto;
import com.google.android.icing.proto.SearchSpecProto;
import com.google.android.icing.proto.TermMatchType;
import java.util.List;
import java.util.Map;
/**
* Translates a {@link SearchSpec} into icing search protos.
*
@@ -57,14 +60,22 @@ public final class SearchSpecToProtoConverter {
@NonNull
public static ResultSpecProto toResultSpecProto(@NonNull SearchSpec spec) {
Preconditions.checkNotNull(spec);
return ResultSpecProto.newBuilder()
.setNumPerPage(spec.getResultCountPerPage())
.setSnippetSpec(
ResultSpecProto.SnippetSpecProto.newBuilder()
.setNumToSnippet(spec.getSnippetCount())
.setNumMatchesPerProperty(spec.getSnippetCountPerProperty())
.setMaxWindowBytes(spec.getMaxSnippetSize()))
.build();
ResultSpecProto.Builder builder =
ResultSpecProto.newBuilder()
.setNumPerPage(spec.getResultCountPerPage())
.setSnippetSpec(
ResultSpecProto.SnippetSpecProto.newBuilder()
.setNumToSnippet(spec.getSnippetCount())
.setNumMatchesPerProperty(spec.getSnippetCountPerProperty())
.setMaxWindowBytes(spec.getMaxSnippetSize()));
Map<String, List<String>> projectionTypePropertyPaths = spec.getProjections();
for (Map.Entry<String, List<String>> e : projectionTypePropertyPaths.entrySet()) {
builder.addTypePropertyMasks(
ResultSpecProto.TypePropertyMask.newBuilder()
.setSchemaType(e.getKey())
.addAllPaths(e.getValue()));
}
return builder.build();
}
/** Extracts {@link ScoringSpecProto} information from a {@link SearchSpec}. */
@@ -79,17 +90,28 @@ public final class SearchSpecToProtoConverter {
if (orderCodeProto == null) {
throw new IllegalArgumentException("Invalid result ranking order: " + orderCode);
}
protoBuilder.setOrderBy(orderCodeProto);
@SearchSpec.RankingStrategy int rankingStrategyCode = spec.getRankingStrategy();
ScoringSpecProto.RankingStrategy.Code rankingStrategyCodeProto =
ScoringSpecProto.RankingStrategy.Code.forNumber(rankingStrategyCode);
if (rankingStrategyCodeProto == null) {
throw new IllegalArgumentException(
"Invalid result ranking strategy: " + rankingStrategyCode);
}
protoBuilder.setRankBy(rankingStrategyCodeProto);
protoBuilder
.setOrderBy(orderCodeProto)
.setRankBy(toProtoRankingStrategy(spec.getRankingStrategy()));
return protoBuilder.build();
}
private static ScoringSpecProto.RankingStrategy.Code toProtoRankingStrategy(
@SearchSpec.RankingStrategy int rankingStrategyCode) {
switch (rankingStrategyCode) {
case SearchSpec.RANKING_STRATEGY_NONE:
return ScoringSpecProto.RankingStrategy.Code.NONE;
case SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE:
return ScoringSpecProto.RankingStrategy.Code.DOCUMENT_SCORE;
case SearchSpec.RANKING_STRATEGY_CREATION_TIMESTAMP:
return ScoringSpecProto.RankingStrategy.Code.CREATION_TIMESTAMP;
case SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE:
return ScoringSpecProto.RankingStrategy.Code
.RELEVANCE_SCORE_NONFUNCTIONAL_PLACEHOLDER;
default:
throw new IllegalArgumentException(
"Invalid result ranking strategy: " + rankingStrategyCode);
}
}
}

View File

@@ -1 +1 @@
I8b7425b3f87153547d1c8f5b560be5a54c9be97e
I6745091e5cb97d69ce2e5f85d3d15c073e7e3ef7

View File

@@ -0,0 +1,42 @@
/*
* 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 com.google.common.collect.ImmutableSet;
import org.junit.Test;
import java.util.Set;
public class PutDocumentsRequestTest {
@Test
public void addGenericDocument_byCollection() {
Set<AppSearchEmail> emails =
ImmutableSet.of(
new AppSearchEmail.Builder("test1").build(),
new AppSearchEmail.Builder("test2").build());
PutDocumentsRequest request =
new PutDocumentsRequest.Builder().addGenericDocument(emails).build();
assertThat(request.getDocuments().get(0).getUri()).isEqualTo("test1");
assertThat(request.getDocuments().get(1).getUri()).isEqualTo("test2");
}
}

View File

@@ -16,6 +16,7 @@
package android.app.appsearch;
import static com.google.common.truth.Truth.assertThat;
import android.os.Bundle;
@@ -26,6 +27,7 @@ import java.util.List;
import java.util.Map;
public class SearchSpecTest {
@Test
public void testGetBundle() {
SearchSpec searchSpec =
@@ -33,6 +35,7 @@ public class SearchSpecTest {
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.addNamespace("namespace1", "namespace2")
.addSchemaType("schemaTypes1", "schemaTypes2")
.addFilterPackageNames("package1", "package2")
.setSnippetCount(5)
.setSnippetCountPerProperty(10)
.setMaxSnippetSize(15)
@@ -48,6 +51,8 @@ public class SearchSpecTest {
.containsExactly("namespace1", "namespace2");
assertThat(bundle.getStringArrayList(SearchSpec.SCHEMA_TYPE_FIELD))
.containsExactly("schemaTypes1", "schemaTypes2");
assertThat(bundle.getStringArrayList(SearchSpec.PACKAGE_NAME_FIELD))
.containsExactly("package1", "package2");
assertThat(bundle.getInt(SearchSpec.SNIPPET_COUNT_FIELD)).isEqualTo(5);
assertThat(bundle.getInt(SearchSpec.SNIPPET_COUNT_PER_PROPERTY_FIELD)).isEqualTo(10);
assertThat(bundle.getInt(SearchSpec.MAX_SNIPPET_FIELD)).isEqualTo(15);
@@ -62,15 +67,26 @@ public class SearchSpecTest {
SearchSpec searchSpec =
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.addProjectionTypePropertyPaths("TypeA", "field1", "field2.subfield2")
.addProjectionTypePropertyPaths("TypeB", "field7")
.addProjectionTypePropertyPaths("TypeC")
.addProjection("TypeA", "field1", "field2.subfield2")
.addProjection("TypeB", "field7")
.addProjection("TypeC")
.build();
Map<String, List<String>> typePropertyPathMap = searchSpec.getProjectionTypePropertyPaths();
Map<String, List<String>> typePropertyPathMap = searchSpec.getProjections();
assertThat(typePropertyPathMap.keySet()).containsExactly("TypeA", "TypeB", "TypeC");
assertThat(typePropertyPathMap.get("TypeA")).containsExactly("field1", "field2.subfield2");
assertThat(typePropertyPathMap.get("TypeB")).containsExactly("field7");
assertThat(typePropertyPathMap.get("TypeC")).isEmpty();
}
@Test
public void testGetRankingStrategy() {
SearchSpec searchSpec =
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.setRankingStrategy(SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE)
.build();
assertThat(searchSpec.getRankingStrategy())
.isEqualTo(SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE);
}
}

View File

@@ -22,13 +22,13 @@ import static org.testng.Assert.expectThrows;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResultPage;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.exceptions.AppSearchException;
import com.android.server.appsearch.external.localstorage.converter.GenericDocumentToProtoConverter;
import com.android.server.appsearch.external.localstorage.converter.SchemaToProtoConverter;
import com.android.server.appsearch.proto.DocumentProto;
import com.android.server.appsearch.proto.GetOptimizeInfoResultProto;
import com.android.server.appsearch.proto.PropertyConfigProto;
@@ -41,6 +41,7 @@ import com.android.server.appsearch.proto.StringIndexingConfig;
import com.android.server.appsearch.proto.TermMatchType;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.junit.Before;
@@ -55,27 +56,10 @@ import java.util.List;
public class AppSearchImplTest {
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
private AppSearchImpl mAppSearchImpl;
private SchemaTypeConfigProto mVisibilitySchemaProto;
@Before
public void setUp() throws Exception {
mAppSearchImpl = AppSearchImpl.create(mTemporaryFolder.newFolder());
AppSearchSchema visibilitySchema = VisibilityStore.SCHEMA;
// We need to rewrite the schema type to follow AppSearchImpl's prefixing scheme.
AppSearchSchema.Builder rewrittenVisibilitySchema =
new AppSearchSchema.Builder(
AppSearchImpl.createPrefix(
VisibilityStore.PACKAGE_NAME, VisibilityStore.DATABASE_NAME)
+ VisibilityStore.SCHEMA_TYPE);
List<AppSearchSchema.PropertyConfig> visibilityProperties =
visibilitySchema.getProperties();
for (AppSearchSchema.PropertyConfig property : visibilityProperties) {
rewrittenVisibilitySchema.addProperty(property);
}
mVisibilitySchemaProto =
SchemaToProtoConverter.toSchemaTypeConfigProto(rewrittenVisibilitySchema.build());
}
// TODO(b/175430168) add test to verify reset is working properly.
@@ -407,6 +391,7 @@ public class AppSearchImplTest {
"database",
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
// Insert enough documents.
@@ -464,6 +449,7 @@ public class AppSearchImplTest {
"database",
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
// Insert document
@@ -495,12 +481,14 @@ public class AppSearchImplTest {
"database1",
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
mAppSearchImpl.setSchema(
"package",
"database2",
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
// Insert documents
@@ -537,6 +525,117 @@ public class AppSearchImplTest {
assertThat(searchResultPage.getResults()).isEmpty();
}
/**
* TODO(b/169883602): This should be an integration test at the cts-level. This is a short-term
* test until we have official support for multiple-apps indexing at once.
*/
@Test
public void testQueryWithMultiplePackages_noPackageFilters() throws Exception {
// Insert package1 schema
List<AppSearchSchema> schema1 =
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
mAppSearchImpl.setSchema(
"package1",
"database1",
schema1,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
// Insert package2 schema
List<AppSearchSchema> schema2 =
ImmutableList.of(new AppSearchSchema.Builder("schema2").build());
mAppSearchImpl.setSchema(
"package2",
"database2",
schema2,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
// Insert package1 document
GenericDocument document =
new GenericDocument.Builder<>("uri", "schema1").setNamespace("namespace").build();
mAppSearchImpl.putDocument("package1", "database1", document);
// No query filters specified, package2 shouldn't be able to query for package1's documents.
SearchSpec searchSpec =
new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
SearchResultPage searchResultPage =
mAppSearchImpl.query("package2", "database2", "", searchSpec);
assertThat(searchResultPage.getResults()).isEmpty();
// Insert package2 document
document =
new GenericDocument.Builder<>("uri", "schema2").setNamespace("namespace").build();
mAppSearchImpl.putDocument("package2", "database2", document);
// No query filters specified. package2 should only get its own documents back.
searchResultPage = mAppSearchImpl.query("package2", "database2", "", searchSpec);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document);
}
/**
* TODO(b/169883602): This should be an integration test at the cts-level. This is a short-term
* test until we have official support for multiple-apps indexing at once.
*/
@Test
public void testQueryWithMultiplePackages_withPackageFilters() throws Exception {
// Insert package1 schema
List<AppSearchSchema> schema1 =
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
mAppSearchImpl.setSchema(
"package1",
"database1",
schema1,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
// Insert package2 schema
List<AppSearchSchema> schema2 =
ImmutableList.of(new AppSearchSchema.Builder("schema2").build());
mAppSearchImpl.setSchema(
"package2",
"database2",
schema2,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
// Insert package1 document
GenericDocument document =
new GenericDocument.Builder<>("uri", "schema1").setNamespace("namespace").build();
mAppSearchImpl.putDocument("package1", "database1", document);
// "package1" filter specified, but package2 shouldn't be able to query for package1's
// documents.
SearchSpec searchSpec =
new SearchSpec.Builder()
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.addFilterPackageNames("package1")
.build();
SearchResultPage searchResultPage =
mAppSearchImpl.query("package2", "database2", "", searchSpec);
assertThat(searchResultPage.getResults()).isEmpty();
// Insert package2 document
document =
new GenericDocument.Builder<>("uri", "schema2").setNamespace("namespace").build();
mAppSearchImpl.putDocument("package2", "database2", document);
// "package2" filter specified, package2 should only get its own documents back.
searchSpec =
new SearchSpec.Builder()
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.addFilterPackageNames("package2")
.build();
searchResultPage = mAppSearchImpl.query("package2", "database2", "", searchSpec);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document);
}
@Test
public void testGlobalQueryEmptyDatabase() throws Exception {
SearchSpec searchSpec =
@@ -545,6 +644,115 @@ public class AppSearchImplTest {
assertThat(searchResultPage.getResults()).isEmpty();
}
/**
* TODO(b/169883602): This should be an integration test at the cts-level. This is a short-term
* test until we have official support for multiple-apps indexing at once.
*/
@Test
public void testGlobalQueryWithMultiplePackages_noPackageFilters() throws Exception {
// Insert package1 schema
List<AppSearchSchema> schema1 =
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
mAppSearchImpl.setSchema(
"package1",
"database1",
schema1,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
// Insert package2 schema
List<AppSearchSchema> schema2 =
ImmutableList.of(new AppSearchSchema.Builder("schema2").build());
mAppSearchImpl.setSchema(
"package2",
"database2",
schema2,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
// Insert package1 document
GenericDocument document1 =
new GenericDocument.Builder<>("uri", "schema1").setNamespace("namespace").build();
mAppSearchImpl.putDocument("package1", "database1", document1);
// Insert package2 document
GenericDocument document2 =
new GenericDocument.Builder<>("uri", "schema2").setNamespace("namespace").build();
mAppSearchImpl.putDocument("package2", "database2", document2);
// No query filters specified, global query can retrieve all documents.
SearchSpec searchSpec =
new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
SearchResultPage searchResultPage = mAppSearchImpl.globalQuery("", searchSpec);
assertThat(searchResultPage.getResults()).hasSize(2);
// Document2 will be first since it got indexed later and has a "better", aka more recent
// score.
assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document2);
assertThat(searchResultPage.getResults().get(1).getDocument()).isEqualTo(document1);
}
/**
* TODO(b/169883602): This should be an integration test at the cts-level. This is a short-term
* test until we have official support for multiple-apps indexing at once.
*/
@Test
public void testGlobalQueryWithMultiplePackages_withPackageFilters() throws Exception {
// Insert package1 schema
List<AppSearchSchema> schema1 =
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
mAppSearchImpl.setSchema(
"package1",
"database1",
schema1,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
// Insert package2 schema
List<AppSearchSchema> schema2 =
ImmutableList.of(new AppSearchSchema.Builder("schema2").build());
mAppSearchImpl.setSchema(
"package2",
"database2",
schema2,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
// Insert package1 document
GenericDocument document1 =
new GenericDocument.Builder<>("uri", "schema1").setNamespace("namespace").build();
mAppSearchImpl.putDocument("package1", "database1", document1);
// Insert package2 document
GenericDocument document2 =
new GenericDocument.Builder<>("uri", "schema2").setNamespace("namespace").build();
mAppSearchImpl.putDocument("package2", "database2", document2);
// "package1" filter specified
SearchSpec searchSpec =
new SearchSpec.Builder()
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.addFilterPackageNames("package1")
.build();
SearchResultPage searchResultPage = mAppSearchImpl.globalQuery("", searchSpec);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document1);
// "package2" filter specified
searchSpec =
new SearchSpec.Builder()
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.addFilterPackageNames("package2")
.build();
searchResultPage = mAppSearchImpl.globalQuery("", searchSpec);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document2);
}
@Test
public void testRemoveEmptyDatabase_noExceptionThrown() throws Exception {
SearchSpec searchSpec =
@@ -567,6 +775,9 @@ public class AppSearchImplTest {
@Test
public void testSetSchema() throws Exception {
List<SchemaTypeConfigProto> existingSchemas =
mAppSearchImpl.getSchemaProtoLocked().getTypesList();
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("Email").build());
// Set schema Email to AppSearch database1
@@ -575,6 +786,7 @@ public class AppSearchImplTest {
"database1",
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
// Create expected schemaType proto.
@@ -586,7 +798,7 @@ public class AppSearchImplTest {
.build();
List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>();
expectedTypes.add(mVisibilitySchemaProto);
expectedTypes.addAll(existingSchemas);
expectedTypes.addAll(expectedProto.getTypesList());
assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
.containsExactlyElementsIn(expectedTypes);
@@ -594,20 +806,30 @@ public class AppSearchImplTest {
@Test
public void testSetSchema_existingSchemaRetainsVisibilitySetting() throws Exception {
PackageIdentifier package1 =
new PackageIdentifier("package1", /*sha256Certificate=*/ new byte[] {100});
String prefix = AppSearchImpl.createPrefix("package", "database");
mAppSearchImpl.setSchema(
"package",
"database",
Collections.singletonList(new AppSearchSchema.Builder("schema1").build()),
/*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"),
/*schemasPackageAccessible=*/ ImmutableMap.of(
"schema1", ImmutableList.of(package1)),
/*forceOverride=*/ false);
// "schema1" is platform hidden now
// "schema1" is platform hidden now and package visible to package1
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
.isSchemaPlatformSurfaceable(prefix, prefix + "schema1"))
.isFalse();
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
.isSchemaPackageAccessible(prefix, prefix + "schema1", package1))
.isTrue();
// Add a new schema, and include the already-existing "schema1"
mAppSearchImpl.setSchema(
@@ -617,24 +839,40 @@ public class AppSearchImplTest {
new AppSearchSchema.Builder("schema1").build(),
new AppSearchSchema.Builder("schema2").build()),
/*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"),
/*schemasPackageAccessible=*/ ImmutableMap.of(
"schema1", ImmutableList.of(package1)),
/*forceOverride=*/ false);
// Check that "schema1" is still platform hidden, but "schema2" is the default platform
// visible.
// Check that "schema1" still has the same visibility settings
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
.isSchemaPlatformSurfaceable(prefix, prefix + "schema1"))
.isFalse();
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
.isSchemaPackageAccessible(prefix, prefix + "schema1", package1))
.isTrue();
// "schema2" has default visibility settings
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
.isSchemaPlatformSurfaceable(prefix, prefix + "schema2"))
.isTrue();
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
.isSchemaPackageAccessible(prefix, prefix + "schema2", package1))
.isFalse();
}
@Test
public void testRemoveSchema() throws Exception {
List<SchemaTypeConfigProto> existingSchemas =
mAppSearchImpl.getSchemaProtoLocked().getTypesList();
List<AppSearchSchema> schemas =
ImmutableList.of(
new AppSearchSchema.Builder("Email").build(),
@@ -645,6 +883,7 @@ public class AppSearchImplTest {
"database1",
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
// Create expected schemaType proto.
@@ -660,7 +899,7 @@ public class AppSearchImplTest {
// Check both schema Email and Document saved correctly.
List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>();
expectedTypes.add(mVisibilitySchemaProto);
expectedTypes.addAll(existingSchemas);
expectedTypes.addAll(expectedProto.getTypesList());
assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
.containsExactlyElementsIn(expectedTypes);
@@ -677,6 +916,7 @@ public class AppSearchImplTest {
"database1",
finalSchemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false));
assertThat(e).hasMessageThat().contains("Schema is incompatible");
assertThat(e).hasMessageThat().contains("Deleted types: [package$database1/Document]");
@@ -687,6 +927,7 @@ public class AppSearchImplTest {
"database1",
finalSchemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ true);
// Check Document schema is removed.
@@ -698,7 +939,7 @@ public class AppSearchImplTest {
.build();
expectedTypes = new ArrayList<>();
expectedTypes.add(mVisibilitySchemaProto);
expectedTypes.addAll(existingSchemas);
expectedTypes.addAll(expectedProto.getTypesList());
assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
.containsExactlyElementsIn(expectedTypes);
@@ -706,6 +947,9 @@ public class AppSearchImplTest {
@Test
public void testRemoveSchema_differentDataBase() throws Exception {
List<SchemaTypeConfigProto> existingSchemas =
mAppSearchImpl.getSchemaProtoLocked().getTypesList();
// Create schemas
List<AppSearchSchema> schemas =
ImmutableList.of(
@@ -718,12 +962,14 @@ public class AppSearchImplTest {
"database1",
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
mAppSearchImpl.setSchema(
"package",
"database2",
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
// Create expected schemaType proto.
@@ -745,7 +991,7 @@ public class AppSearchImplTest {
// Check Email and Document is saved in database 1 and 2 correctly.
List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>();
expectedTypes.add(mVisibilitySchemaProto);
expectedTypes.addAll(existingSchemas);
expectedTypes.addAll(expectedProto.getTypesList());
assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
.containsExactlyElementsIn(expectedTypes);
@@ -757,6 +1003,7 @@ public class AppSearchImplTest {
"database1",
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ true);
// Create expected schemaType list, database 1 should only contain Email but database 2
@@ -776,7 +1023,7 @@ public class AppSearchImplTest {
// Check nothing changed in database2.
expectedTypes = new ArrayList<>();
expectedTypes.add(mVisibilitySchemaProto);
expectedTypes.addAll(existingSchemas);
expectedTypes.addAll(expectedProto.getTypesList());
assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
.containsExactlyElementsIn(expectedTypes);
@@ -784,49 +1031,71 @@ public class AppSearchImplTest {
@Test
public void testRemoveSchema_removedFromVisibilityStore() throws Exception {
PackageIdentifier package1 =
new PackageIdentifier("package1", /*sha256Certificate=*/ new byte[] {100});
String prefix = AppSearchImpl.createPrefix("package", "database");
mAppSearchImpl.setSchema(
"package",
"database",
Collections.singletonList(new AppSearchSchema.Builder("schema1").build()),
/*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"),
/*schemasPackageAccessible=*/ ImmutableMap.of(
"schema1", ImmutableList.of(package1)),
/*forceOverride=*/ false);
// "schema1" is platform hidden now
// "schema1" is platform hidden now and package accessible
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
.isSchemaPlatformSurfaceable(prefix, prefix + "schema1"))
.isFalse();
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
.isSchemaPackageAccessible(prefix, prefix + "schema1", package1))
.isTrue();
// Remove "schema1" by force overriding
mAppSearchImpl.setSchema(
"package",
"database",
Collections.emptyList(),
/*schemas=*/ Collections.emptyList(),
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ true);
// Check that "schema1" is no longer considered platform hidden
// Check that "schema1" is no longer considered platform hidden or package accessible
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
.isSchemaPlatformSurfaceable(prefix, prefix + "schema1"))
.isTrue();
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
.isSchemaPackageAccessible(prefix, prefix + "schema1", package1))
.isFalse();
// Add "schema1" back, it gets default visibility settings which means it's not platform
// hidden.
// hidden and not package accessible
mAppSearchImpl.setSchema(
"package",
"database",
Collections.singletonList(new AppSearchSchema.Builder("schema1").build()),
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
.isSchemaPlatformSurfaceable(prefix, prefix + "schema1"))
.isTrue();
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
.isSchemaPackageAccessible(prefix, prefix + "schema1", package1))
.isFalse();
}
@Test
@@ -837,6 +1106,7 @@ public class AppSearchImplTest {
"database",
Collections.singletonList(new AppSearchSchema.Builder("Schema").build()),
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
assertThat(
mAppSearchImpl
@@ -853,6 +1123,7 @@ public class AppSearchImplTest {
"database",
Collections.singletonList(new AppSearchSchema.Builder("Schema").build()),
/*schemasNotPlatformSurfaceable=*/ Collections.singletonList("Schema"),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
assertThat(
mAppSearchImpl
@@ -861,6 +1132,46 @@ public class AppSearchImplTest {
.isFalse();
}
@Test
public void testSetSchema_defaultNotPackageAccessible() throws Exception {
PackageIdentifier package1 =
new PackageIdentifier("package1", /*sha256Certificate=*/ new byte[] {100});
String prefix = AppSearchImpl.createPrefix("package", "database");
mAppSearchImpl.setSchema(
"package",
"database",
Collections.singletonList(new AppSearchSchema.Builder("Schema").build()),
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
.isSchemaPackageAccessible(prefix, prefix + "Schema", package1))
.isFalse();
}
@Test
public void testSetSchema_packageAccessible() throws Exception {
PackageIdentifier package1 =
new PackageIdentifier("package1", /*sha256Certificate=*/ new byte[] {100});
String prefix = AppSearchImpl.createPrefix("package", "database");
mAppSearchImpl.setSchema(
"package",
"database",
Collections.singletonList(new AppSearchSchema.Builder("Schema").build()),
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ ImmutableMap.of("Schema", ImmutableList.of(package1)),
/*forceOverride=*/ false);
assertThat(
mAppSearchImpl
.getVisibilityStoreLocked()
.isSchemaPackageAccessible(prefix, prefix + "Schema", package1))
.isTrue();
}
@Test
public void testHasSchemaType() throws Exception {
// Nothing exists yet
@@ -871,6 +1182,7 @@ public class AppSearchImplTest {
"database",
Collections.singletonList(new AppSearchSchema.Builder("Schema").build()),
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
assertThat(mAppSearchImpl.hasSchemaTypeLocked("package", "database", "Schema")).isTrue();
@@ -892,6 +1204,7 @@ public class AppSearchImplTest {
"database1",
Collections.singletonList(new AppSearchSchema.Builder("schema").build()),
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
assertThat(mAppSearchImpl.getPrefixesLocked())
.containsExactly(
@@ -905,6 +1218,7 @@ public class AppSearchImplTest {
"database2",
Collections.singletonList(new AppSearchSchema.Builder("schema").build()),
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false);
assertThat(mAppSearchImpl.getPrefixesLocked())
.containsExactly(

View File

@@ -18,6 +18,10 @@ package com.android.server.appsearch.external.localstorage;
import static com.google.common.truth.Truth.assertThat;
import android.app.appsearch.PackageIdentifier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.junit.Before;
@@ -70,11 +74,12 @@ public class VisibilityStoreTest {
}
@Test
public void testSetVisibility() throws Exception {
public void testSetVisibility_platformSurfaceable() throws Exception {
mVisibilityStore.setVisibility(
"prefix",
/*schemasNotPlatformSurfaceable=*/ ImmutableSet.of(
"prefix/schema1", "prefix/schema2"));
"prefix/schema1", "prefix/schema2"),
/*schemasPackageAccessible=*/ Collections.emptyMap());
assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema1"))
.isFalse();
assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema2"))
@@ -85,7 +90,8 @@ public class VisibilityStoreTest {
mVisibilityStore.setVisibility(
"prefix",
/*schemasNotPlatformSurfaceable=*/ ImmutableSet.of(
"prefix/schema1", "prefix/schema3"));
"prefix/schema1", "prefix/schema3"),
/*schemasPackageAccessible=*/ Collections.emptyMap());
assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema1"))
.isFalse();
assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema2"))
@@ -94,7 +100,9 @@ public class VisibilityStoreTest {
.isFalse();
mVisibilityStore.setVisibility(
"prefix", /*schemasNotPlatformSurfaceable=*/ Collections.emptySet());
"prefix",
/*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
/*schemasPackageAccessible=*/ Collections.emptyMap());
assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema1"))
.isTrue();
assertThat(mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema2"))
@@ -103,14 +111,73 @@ public class VisibilityStoreTest {
.isTrue();
}
@Test
public void testSetVisibility_packageAccessible() throws Exception {
PackageIdentifier package1 =
new PackageIdentifier("package1", /*sha256Certificate=*/ new byte[] {100});
PackageIdentifier package2 =
new PackageIdentifier("package2", /*sha256Certificate=*/ new byte[] {100});
PackageIdentifier package3 =
new PackageIdentifier("package3", /*sha256Certificate=*/ new byte[] {100});
mVisibilityStore.setVisibility(
"prefix",
/*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
/*schemasPackageAccessible=*/ ImmutableMap.of(
"prefix/schema1", ImmutableList.of(package1),
"prefix/schema2", ImmutableList.of(package2)));
assertThat(mVisibilityStore.isSchemaPackageAccessible("prefix", "prefix/schema1", package1))
.isTrue();
assertThat(mVisibilityStore.isSchemaPackageAccessible("prefix", "prefix/schema2", package2))
.isTrue();
// New .setVisibility() call completely overrides previous visibility settings. So
// "schema2" isn't preserved.
mVisibilityStore.setVisibility(
"prefix",
/*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
/*schemasPackageAccessible=*/ ImmutableMap.of(
"prefix/schema1", ImmutableList.of(package1),
"prefix/schema3", ImmutableList.of(package3)));
assertThat(mVisibilityStore.isSchemaPackageAccessible("prefix", "prefix/schema1", package1))
.isTrue();
assertThat(mVisibilityStore.isSchemaPackageAccessible("prefix", "prefix/schema2", package2))
.isFalse();
assertThat(mVisibilityStore.isSchemaPackageAccessible("prefix", "prefix/schema3", package3))
.isTrue();
mVisibilityStore.setVisibility(
"prefix",
/*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
/*schemasPackageAccessible=*/ Collections.emptyMap());
assertThat(mVisibilityStore.isSchemaPackageAccessible("prefix", "prefix/schema1", package1))
.isFalse();
assertThat(mVisibilityStore.isSchemaPackageAccessible("prefix", "prefix/schema2", package2))
.isFalse();
assertThat(mVisibilityStore.isSchemaPackageAccessible("prefix", "prefix/schema3", package3))
.isFalse();
}
@Test
public void testEmptyPrefix() throws Exception {
PackageIdentifier package1 =
new PackageIdentifier("package1", /*sha256Certificate=*/ new byte[] {100});
PackageIdentifier package2 =
new PackageIdentifier("package2", /*sha256Certificate=*/ new byte[] {100});
mVisibilityStore.setVisibility(
/*prefix=*/ "",
/*schemasNotPlatformSurfaceable=*/ ImmutableSet.of("schema1", "schema2"));
/*schemasNotPlatformSurfaceable=*/ ImmutableSet.of("schema1", "schema2"),
/*schemasPackageAccessible=*/ ImmutableMap.of(
"schema1", ImmutableList.of(package1),
"schema2", ImmutableList.of(package2)));
assertThat(mVisibilityStore.isSchemaPlatformSurfaceable(/*prefix=*/ "", "schema1"))
.isFalse();
assertThat(mVisibilityStore.isSchemaPlatformSurfaceable(/*prefix=*/ "", "schema2"))
.isFalse();
assertThat(mVisibilityStore.isSchemaPackageAccessible(/*prefix=*/ "", "schema1", package1))
.isTrue();
assertThat(mVisibilityStore.isSchemaPackageAccessible(/*prefix=*/ "", "schema2", package2))
.isTrue();
}
}