Update Framework from Jetpack.

Included changes:
* 4c1b251: Sync AppSearch with google3 update ag/14342999 (8f8b43)
* cdf1913: Add a GenericDocument#toBuilder API.
* 64f80f3: Improve snippeting and matching per API feedback.
* 23dd35c: Move AppSearchEmail out of the prod tree into the test utils.

Bug: 171882200
Bug: 186520196
Bug: 186717514
Test: Presubmit
Change-Id: I9d8e1fb87e6e74c57a38f3a27cdd04a1a3ccf90b
This commit is contained in:
Alexander Dorokhine
2021-04-29 18:00:32 -07:00
parent 267965a8fc
commit 45bb21c98d
18 changed files with 212 additions and 46 deletions

View File

@@ -289,14 +289,16 @@ package android.app.appsearch {
public final class SearchResult {
method @NonNull public String getDatabaseName();
method @NonNull public android.app.appsearch.GenericDocument getGenericDocument();
method @NonNull public java.util.List<android.app.appsearch.SearchResult.MatchInfo> getMatches();
method @NonNull public java.util.List<android.app.appsearch.SearchResult.MatchInfo> getMatchInfos();
method @Deprecated @NonNull public java.util.List<android.app.appsearch.SearchResult.MatchInfo> getMatches();
method @NonNull public String getPackageName();
method public double getRankingSignal();
}
public static final class SearchResult.Builder {
ctor public SearchResult.Builder(@NonNull String, @NonNull String);
method @NonNull public android.app.appsearch.SearchResult.Builder addMatch(@NonNull android.app.appsearch.SearchResult.MatchInfo);
method @Deprecated @NonNull public android.app.appsearch.SearchResult.Builder addMatch(@NonNull android.app.appsearch.SearchResult.MatchInfo);
method @NonNull public android.app.appsearch.SearchResult.Builder addMatchInfo(@NonNull android.app.appsearch.SearchResult.MatchInfo);
method @NonNull public android.app.appsearch.SearchResult build();
method @NonNull public android.app.appsearch.SearchResult.Builder setGenericDocument(@NonNull android.app.appsearch.GenericDocument);
method @NonNull public android.app.appsearch.SearchResult.Builder setRankingSignal(double);

View File

@@ -843,6 +843,20 @@ public class GenericDocument {
}
}
/**
* Copies the contents of this {@link GenericDocument} into a new {@link
* GenericDocument.Builder}.
*
* <p>The returned builder is a deep copy whose data is separate from this document.
*
* @hide
*/
@NonNull
public GenericDocument.Builder<GenericDocument.Builder<?>> toBuilder() {
Bundle clonedBundle = BundleUtil.deepCopy(mBundle);
return new GenericDocument.Builder<>(clonedBundle);
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
@@ -936,8 +950,8 @@ public class GenericDocument {
@SuppressLint("StaticFinalBuilder")
public static class Builder<BuilderType extends Builder> {
private final Bundle mProperties = new Bundle();
private final Bundle mBundle = new Bundle();
private final Bundle mBundle;
private final Bundle mProperties;
private final BuilderType mBuilderTypeInstance;
private boolean mBuilt = false;
@@ -964,6 +978,8 @@ public class GenericDocument {
Objects.requireNonNull(namespace);
Objects.requireNonNull(id);
Objects.requireNonNull(schemaType);
mBundle = new Bundle();
mBuilderTypeInstance = (BuilderType) this;
mBundle.putString(GenericDocument.NAMESPACE_FIELD, namespace);
mBundle.putString(GenericDocument.ID_FIELD, id);
@@ -973,9 +989,72 @@ public class GenericDocument {
GenericDocument.CREATION_TIMESTAMP_MILLIS_FIELD, System.currentTimeMillis());
mBundle.putLong(GenericDocument.TTL_MILLIS_FIELD, DEFAULT_TTL_MILLIS);
mBundle.putInt(GenericDocument.SCORE_FIELD, DEFAULT_SCORE);
mProperties = new Bundle();
mBundle.putBundle(PROPERTIES_FIELD, mProperties);
}
/** Creates a new {@link GenericDocument.Builder} from the given Bundle. */
@SuppressWarnings("unchecked")
Builder(@NonNull Bundle bundle) {
mBundle = Objects.requireNonNull(bundle);
mProperties = mBundle.getBundle(PROPERTIES_FIELD);
mBuilderTypeInstance = (BuilderType) this;
}
/**
* Sets the app-defined namespace this document resides in, changing the value provided in
* the constructor. No special values are reserved or understood by the infrastructure.
*
* <p>Document IDs are unique within a namespace.
*
* <p>The number of namespaces per app should be kept small for efficiency reasons.
*
* @throws IllegalStateException if the builder has already been used.
* @hide
*/
@NonNull
public BuilderType setNamespace(@NonNull String namespace) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Objects.requireNonNull(namespace);
mBundle.putString(GenericDocument.NAMESPACE_FIELD, namespace);
return mBuilderTypeInstance;
}
/**
* Sets the ID of this document, changing the value provided in the constructor. No special
* values are reserved or understood by the infrastructure.
*
* <p>Document IDs are unique within a namespace.
*
* @throws IllegalStateException if the builder has already been used.
* @hide
*/
@NonNull
public BuilderType setId(@NonNull String id) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Objects.requireNonNull(id);
mBundle.putString(GenericDocument.ID_FIELD, id);
return mBuilderTypeInstance;
}
/**
* Sets the schema type of this document, changing the value provided in the constructor.
*
* <p>To successfully index a document, the schema type must match the name of an {@link
* AppSearchSchema} object previously provided to {@link AppSearchSession#setSchema}.
*
* @throws IllegalStateException if the builder has already been used.
* @hide
*/
@NonNull
public BuilderType setSchemaType(@NonNull String schemaType) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Objects.requireNonNull(schemaType);
mBundle.putString(GenericDocument.SCHEMA_TYPE_FIELD, schemaType);
return mBuilderTypeInstance;
}
/**
* Sets the score of the {@link GenericDocument}.
*
@@ -1156,6 +1235,22 @@ public class GenericDocument {
return mBuilderTypeInstance;
}
/**
* Clears the value for the property with the given name.
*
* <p>Note that this method does not support property paths.
*
* @param name The name of the property to clear.
* @hide
*/
@NonNull
public BuilderType clearProperty(@NonNull String name) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Objects.requireNonNull(name);
mProperties.remove(name);
return mBuilderTypeInstance;
}
private void putInPropertyBundle(@NonNull String name, @NonNull String[] values)
throws IllegalArgumentException {
validateRepeatedPropertyLength(name, values.length);

View File

@@ -34,7 +34,7 @@ import java.util.Objects;
* <ul>
* <li>The document which matched, using {@link #getGenericDocument}
* <li>Information about which properties in the document matched, and "snippet" information
* containing textual summaries of the document's matches, using {@link #getMatches}
* containing textual summaries of the document's matches, using {@link #getMatchInfos}
* </ul>
*
* <p>"Snippet" refers to a substring of text from the content of document that is returned as a
@@ -44,7 +44,7 @@ import java.util.Objects;
*/
public final class SearchResult {
static final String DOCUMENT_FIELD = "document";
static final String MATCHES_FIELD = "matches";
static final String MATCH_INFOS_FIELD = "matchInfos";
static final String PACKAGE_NAME_FIELD = "packageName";
static final String DATABASE_NAME_FIELD = "databaseName";
static final String RANKING_SIGNAL_FIELD = "rankingSignal";
@@ -55,7 +55,7 @@ public final class SearchResult {
@Nullable private GenericDocument mDocument;
/** Cache of the inflated matches. Comes from inflating mMatchBundles at first use. */
@Nullable private List<MatchInfo> mMatches;
@Nullable private List<MatchInfo> mMatchInfos;
/** @hide */
public SearchResult(@NonNull Bundle bundle) {
@@ -82,8 +82,16 @@ public final class SearchResult {
return mDocument;
}
/** @deprecated This method exists only for dogfooder transition and must be removed. */
@Deprecated
@NonNull
public List<MatchInfo> getMatches() {
return getMatchInfos();
}
/**
* Contains a list of Snippets that matched the request.
* Returns a list of {@link MatchInfo}s providing information about how the document in {@link
* #getGenericDocument} matched the query.
*
* @return List of matches based on {@link SearchSpec}. If snippeting is disabled using {@link
* SearchSpec.Builder#setSnippetCount} or {@link
@@ -91,17 +99,17 @@ public final class SearchResult {
* method returns an empty list.
*/
@NonNull
public List<MatchInfo> getMatches() {
if (mMatches == null) {
public List<MatchInfo> getMatchInfos() {
if (mMatchInfos == null) {
List<Bundle> matchBundles =
Objects.requireNonNull(mBundle.getParcelableArrayList(MATCHES_FIELD));
mMatches = new ArrayList<>(matchBundles.size());
Objects.requireNonNull(mBundle.getParcelableArrayList(MATCH_INFOS_FIELD));
mMatchInfos = new ArrayList<>(matchBundles.size());
for (int i = 0; i < matchBundles.size(); i++) {
MatchInfo matchInfo = new MatchInfo(matchBundles.get(i), getGenericDocument());
mMatches.add(matchInfo);
mMatchInfos.add(matchInfo);
}
}
return mMatches;
return mMatchInfos;
}
/**
@@ -184,9 +192,16 @@ public final class SearchResult {
return this;
}
/** Adds another match to this SearchResult. */
/** @deprecated this method exists only for dogfooder transition and must be removed */
@Deprecated
@NonNull
public Builder addMatch(@NonNull MatchInfo matchInfo) {
return addMatchInfo(matchInfo);
}
/** Adds another match to this SearchResult. */
@NonNull
public Builder addMatchInfo(@NonNull MatchInfo matchInfo) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Preconditions.checkState(
matchInfo.mDocument == null,
@@ -212,7 +227,7 @@ public final class SearchResult {
@NonNull
public SearchResult build() {
Preconditions.checkState(!mBuilt, "Builder has already been used");
mBundle.putParcelableArrayList(MATCHES_FIELD, mMatchInfos);
mBundle.putParcelableArrayList(MATCH_INFOS_FIELD, mMatchInfos);
mBuilt = true;
return new SearchResult(mBundle);
}

View File

@@ -323,6 +323,7 @@ public final class SearchSpec {
mBundle = new Bundle();
mBundle.putInt(NUM_PER_PAGE_FIELD, DEFAULT_NUM_PER_PAGE);
mBundle.putInt(TERM_MATCH_TYPE_FIELD, TERM_MATCH_PREFIX);
mBundle.putInt(SNIPPET_COUNT_PER_PROPERTY_FIELD, MAX_SNIPPET_PER_PROPERTY_COUNT);
}
/**
@@ -471,8 +472,11 @@ public final class SearchSpec {
* Only the first {@code snippetCount} documents based on the ranking strategy will have
* snippet information provided.
*
* <p>If set to 0 (default), snippeting is disabled and {@link SearchResult#getMatches} will
* return {@code null} for that result.
* <p>The list returned from {@link SearchResult#getMatchInfos} will contain at most this
* many entries.
*
* <p>If set to 0 (default), snippeting is disabled and the list returned from {@link
* SearchResult#getMatchInfos} will be empty.
*/
@NonNull
public SearchSpec.Builder setSnippetCount(
@@ -485,10 +489,14 @@ public final class SearchSpec {
/**
* Sets {@code snippetCountPerProperty}. Only the first {@code snippetCountPerProperty}
* snippets for each property of {@link GenericDocument} will contain snippet information.
* snippets for each property of each {@link GenericDocument} will contain snippet
* information.
*
* <p>If set to 0, snippeting is disabled and {@link SearchResult#getMatches} will return
* {@code null} for that result.
* <p>If set to 0, snippeting is disabled and the list returned from {@link
* SearchResult#getMatchInfos} will be empty.
*
* <p>The default behavior is to snippet all matches a property contains, up to the maximum
* value of 10,000.
*/
@NonNull
public SearchSpec.Builder setSnippetCountPerProperty(

View File

@@ -16,8 +16,10 @@
package android.app.appsearch.util;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Parcel;
import android.util.SparseArray;
import java.util.ArrayList;
@@ -224,4 +226,26 @@ public final class BundleUtil {
}
return Arrays.hashCode(hashCodes);
}
/**
* Deeply clones a Bundle.
*
* <p>Values which are Bundles, Lists or Arrays are deeply copied themselves.
*/
@NonNull
public static Bundle deepCopy(@NonNull Bundle bundle) {
// Write bundle to bytes
Parcel parcel = Parcel.obtain();
try {
parcel.writeBundle(bundle);
byte[] serializedMessage = parcel.marshall();
// Read bundle from bytes
parcel.unmarshall(serializedMessage, 0, serializedMessage.length);
parcel.setDataPosition(0);
return parcel.readBundle();
} finally {
parcel.recycle();
}
}
}

View File

@@ -116,7 +116,7 @@ public class SearchResultToProtoConverter {
for (int j = 0; j < entry.getSnippetMatchesCount(); j++) {
SearchResult.MatchInfo matchInfo =
toMatchInfo(entry.getSnippetMatches(j), entry.getPropertyName());
builder.addMatch(matchInfo);
builder.addMatchInfo(matchInfo);
}
}
}

View File

@@ -1 +1 @@
I19dac52031c47099f621eced9f148931f1021f25
Ic6be29e84e7c6f31cdae37973850bb3395920326

View File

@@ -31,6 +31,7 @@ java_library {
"truth-prebuilt",
],
visibility: [
"//frameworks/base/core/tests/coretests",
"//cts/hostsidetests/appsearch",
"//cts/tests/appsearch",
"//vendor:__subpackages__",

View File

@@ -179,7 +179,7 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim {
@Override
@NonNull
public ListenableFuture<Void> maybeFlush() {
public ListenableFuture<Void> requestFlush() {
SettableFuture<AppSearchResult<Void>> future = SettableFuture.create();
// The data in platform will be flushed by scheduled task. AppSearchSession won't do
// anything extra flush.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 The Android Open Source Project
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,19 +14,19 @@
* limitations under the License.
*/
package android.app.appsearch;
package com.android.server.appsearch.testing;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.AppSearchSchema.PropertyConfig;
import android.app.appsearch.AppSearchSchema.StringPropertyConfig;
import android.app.appsearch.GenericDocument;
/**
* Encapsulates a {@link GenericDocument} that represent an email.
*
* <p>This class is a higher level implement of {@link GenericDocument}.
*
* @hide
*/
public class AppSearchEmail extends GenericDocument {
/** The name of the schema type for {@link AppSearchEmail} documents. */
@@ -151,9 +151,9 @@ public class AppSearchEmail extends GenericDocument {
}
/** The builder class for {@link AppSearchEmail}. */
public static class Builder extends GenericDocument.Builder<AppSearchEmail.Builder> {
public static class Builder extends GenericDocument.Builder<Builder> {
/**
* Creates a new {@link AppSearchEmail.Builder}
* Creates a new {@link Builder}
*
* @param namespace The namespace of the Email.
* @param id The ID of the Email.
@@ -164,42 +164,42 @@ public class AppSearchEmail extends GenericDocument {
/** Sets the from address of {@link AppSearchEmail} */
@NonNull
public AppSearchEmail.Builder setFrom(@NonNull String from) {
public Builder setFrom(@NonNull String from) {
setPropertyString(KEY_FROM, from);
return this;
}
/** Sets the destination address of {@link AppSearchEmail} */
@NonNull
public AppSearchEmail.Builder setTo(@NonNull String... to) {
public Builder setTo(@NonNull String... to) {
setPropertyString(KEY_TO, to);
return this;
}
/** Sets the CC list of {@link AppSearchEmail} */
@NonNull
public AppSearchEmail.Builder setCc(@NonNull String... cc) {
public Builder setCc(@NonNull String... cc) {
setPropertyString(KEY_CC, cc);
return this;
}
/** Sets the BCC list of {@link AppSearchEmail} */
@NonNull
public AppSearchEmail.Builder setBcc(@NonNull String... bcc) {
public Builder setBcc(@NonNull String... bcc) {
setPropertyString(KEY_BCC, bcc);
return this;
}
/** Sets the subject of {@link AppSearchEmail} */
@NonNull
public AppSearchEmail.Builder setSubject(@NonNull String subject) {
public Builder setSubject(@NonNull String subject) {
setPropertyString(KEY_SUBJECT, subject);
return this;
}
/** Sets the body of {@link AppSearchEmail} */
@NonNull
public AppSearchEmail.Builder setBody(@NonNull String body) {
public Builder setBody(@NonNull String body) {
setPropertyString(KEY_BODY, body);
return this;
}

View File

@@ -240,13 +240,16 @@ public interface AppSearchSessionShim extends Closeable {
/**
* Flush all schema and document updates, additions, and deletes to disk if possible.
*
* <p>The request is not guaranteed to be handled and may be ignored by some implementations of
* AppSearchSessionShim.
*
* @return The pending result of performing this operation. {@link
* android.app.appsearch.exceptions.AppSearchException} with {@link
* AppSearchResult#RESULT_INTERNAL_ERROR} will be set to the future if we hit error when
* save to disk.
*/
@NonNull
ListenableFuture<Void> maybeFlush();
ListenableFuture<Void> requestFlush();
/**
* Closes the {@link AppSearchSessionShim} to persist all schema and document updates,

View File

@@ -52,7 +52,8 @@ android_test {
"truth-prebuilt",
"print-test-util-lib",
"testng",
"servicestests-utils"
"servicestests-utils",
"AppSearchTestUtils",
],
libs: [
@@ -66,7 +67,7 @@ android_test {
"framework-res",
],
jni_libs: [
"libpowermanagertest_jni",
"libpowermanagertest_jni",
],
platform_apis: true,

View File

@@ -25,6 +25,8 @@ import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import com.android.server.appsearch.testing.AppSearchEmail;
import org.junit.Before;
import org.junit.Test;

View File

@@ -18,6 +18,8 @@ package android.app.appsearch;
import static com.google.common.truth.Truth.assertThat;
import com.android.server.appsearch.testing.AppSearchEmail;
import org.junit.Test;
public class AppSearchEmailTest {

View File

@@ -19,6 +19,8 @@ package android.app.appsearch;
import static com.google.common.truth.Truth.assertThat;
import com.android.server.appsearch.testing.AppSearchEmail;
import com.google.common.collect.ImmutableSet;
import org.junit.Test;

View File

@@ -213,6 +213,17 @@ public class BundleUtilTest {
.isNotEqualTo(BundleUtil.deepHashCode(inputs[1]));
}
@Test
public void testDeepCopy() {
Bundle input = createThoroughBundle();
Bundle output = BundleUtil.deepCopy(input);
assertThat(input).isNotSameInstanceAs(output);
assertThat(BundleUtil.deepEquals(input, output)).isTrue();
output.getIntegerArrayList("integerArrayList").add(5);
assertThat(BundleUtil.deepEquals(input, output)).isFalse();
}
private static Bundle createThoroughBundle() {
Bundle toy1 = new Bundle();
toy1.putString("a", "a");

View File

@@ -102,7 +102,7 @@ public class AppSearchLoggerTest {
int nativeIndexRestorationCause =
InitializeStatsProto.RecoveryCause.INCONSISTENT_WITH_GROUND_TRUTH_VALUE;
int nativeSchemaStoreRecoveryCause =
InitializeStatsProto.RecoveryCause.TOTAL_CHECKSUM_MISMATCH_VALUE;
InitializeStatsProto.RecoveryCause.SCHEMA_CHANGES_OUT_OF_SYNC_VALUE;
int nativeDocumentStoreRecoveryLatencyMillis = 7;
int nativeIndexRestorationLatencyMillis = 8;
int nativeSchemaStoreRecoveryLatencyMillis = 9;

View File

@@ -96,7 +96,7 @@ public class SnippetTest {
Collections.singletonList(DATABASE_NAME),
SCHEMA_MAP);
assertThat(searchResultPage.getResults()).hasSize(1);
SearchResult.MatchInfo match = searchResultPage.getResults().get(0).getMatches().get(0);
SearchResult.MatchInfo match = searchResultPage.getResults().get(0).getMatchInfos().get(0);
assertThat(match.getPropertyPath()).isEqualTo(propertyKeyString);
assertThat(match.getFullText()).isEqualTo(propertyValueString);
assertThat(match.getExactMatch()).isEqualTo(exactMatch);
@@ -142,7 +142,7 @@ public class SnippetTest {
Collections.singletonList(DATABASE_NAME),
SCHEMA_MAP);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getMatches()).isEmpty();
assertThat(searchResultPage.getResults().get(0).getMatchInfos()).isEmpty();
}
@Test
@@ -198,7 +198,7 @@ public class SnippetTest {
Collections.singletonList(DATABASE_NAME),
SCHEMA_MAP);
assertThat(searchResultPage.getResults()).hasSize(1);
SearchResult.MatchInfo match1 = searchResultPage.getResults().get(0).getMatches().get(0);
SearchResult.MatchInfo match1 = searchResultPage.getResults().get(0).getMatchInfos().get(0);
assertThat(match1.getPropertyPath()).isEqualTo("senderName");
assertThat(match1.getFullText()).isEqualTo("Test Name Jr.");
assertThat(match1.getExactMatchRange())
@@ -208,7 +208,7 @@ public class SnippetTest {
.isEqualTo(new SearchResult.MatchRange(/*lower=*/ 0, /*upper=*/ 9));
assertThat(match1.getSnippet()).isEqualTo("Test Name");
SearchResult.MatchInfo match2 = searchResultPage.getResults().get(0).getMatches().get(1);
SearchResult.MatchInfo match2 = searchResultPage.getResults().get(0).getMatchInfos().get(1);
assertThat(match2.getPropertyPath()).isEqualTo("senderEmail");
assertThat(match2.getFullText()).isEqualTo("TestNameJr@gmail.com");
assertThat(match2.getExactMatchRange())
@@ -281,7 +281,7 @@ public class SnippetTest {
Collections.singletonList(DATABASE_NAME),
SCHEMA_MAP);
assertThat(searchResultPage.getResults()).hasSize(1);
SearchResult.MatchInfo match1 = searchResultPage.getResults().get(0).getMatches().get(0);
SearchResult.MatchInfo match1 = searchResultPage.getResults().get(0).getMatchInfos().get(0);
assertThat(match1.getPropertyPath()).isEqualTo("sender.name");
assertThat(match1.getFullText()).isEqualTo("Test Name Jr.");
assertThat(match1.getExactMatchRange())
@@ -291,7 +291,7 @@ public class SnippetTest {
.isEqualTo(new SearchResult.MatchRange(/*lower=*/ 0, /*upper=*/ 9));
assertThat(match1.getSnippet()).isEqualTo("Test Name");
SearchResult.MatchInfo match2 = searchResultPage.getResults().get(0).getMatches().get(1);
SearchResult.MatchInfo match2 = searchResultPage.getResults().get(0).getMatchInfos().get(1);
assertThat(match2.getPropertyPath()).isEqualTo("sender.email[1]");
assertThat(match2.getFullText()).isEqualTo("TestNameJr2@gmail.com");
assertThat(match2.getExactMatchRange())