Merge "Cache nextPageTokens that a package can use." into sc-dev
This commit is contained in:
@@ -795,7 +795,7 @@ public class AppSearchManagerService extends SystemService {
|
|||||||
AppSearchUserInstance instance =
|
AppSearchUserInstance instance =
|
||||||
mAppSearchUserInstanceManager.getUserInstance(callingUser);
|
mAppSearchUserInstanceManager.getUserInstance(callingUser);
|
||||||
SearchResultPage searchResultPage =
|
SearchResultPage searchResultPage =
|
||||||
instance.getAppSearchImpl().getNextPage(nextPageToken);
|
instance.getAppSearchImpl().getNextPage(packageName, nextPageToken);
|
||||||
invokeCallbackOnResult(
|
invokeCallbackOnResult(
|
||||||
callback,
|
callback,
|
||||||
AppSearchResult.newSuccessfulResult(searchResultPage.getBundle()));
|
AppSearchResult.newSuccessfulResult(searchResultPage.getBundle()));
|
||||||
@@ -821,7 +821,7 @@ public class AppSearchManagerService extends SystemService {
|
|||||||
verifyNotInstantApp(userContext, packageName);
|
verifyNotInstantApp(userContext, packageName);
|
||||||
AppSearchUserInstance instance =
|
AppSearchUserInstance instance =
|
||||||
mAppSearchUserInstanceManager.getUserInstance(callingUser);
|
mAppSearchUserInstanceManager.getUserInstance(callingUser);
|
||||||
instance.getAppSearchImpl().invalidateNextPageToken(nextPageToken);
|
instance.getAppSearchImpl().invalidateNextPageToken(packageName, nextPageToken);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
Log.e(TAG, "Unable to invalidate the query page token", t);
|
Log.e(TAG, "Unable to invalidate the query page token", t);
|
||||||
}
|
}
|
||||||
@@ -871,7 +871,7 @@ public class AppSearchManagerService extends SystemService {
|
|||||||
.getGenericDocument().getBundle());
|
.getGenericDocument().getBundle());
|
||||||
}
|
}
|
||||||
searchResultPage = instance.getAppSearchImpl().getNextPage(
|
searchResultPage = instance.getAppSearchImpl().getNextPage(
|
||||||
searchResultPage.getNextPageToken());
|
packageName, searchResultPage.getNextPageToken());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
|
invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
|
||||||
|
|||||||
@@ -173,6 +173,21 @@ public final class AppSearchImpl implements Closeable {
|
|||||||
@GuardedBy("mReadWriteLock")
|
@GuardedBy("mReadWriteLock")
|
||||||
private final Map<String, Integer> mDocumentCountMapLocked = new ArrayMap<>();
|
private final Map<String, Integer> mDocumentCountMapLocked = new ArrayMap<>();
|
||||||
|
|
||||||
|
// Maps packages to the set of valid nextPageTokens that the package can manipulate. A token
|
||||||
|
// is unique and constant per query (i.e. the same token '123' is used to iterate through
|
||||||
|
// pages of search results). The tokens themselves are generated and tracked by
|
||||||
|
// IcingSearchEngine. IcingSearchEngine considers a token valid and won't be reused
|
||||||
|
// until we call invalidateNextPageToken on the token.
|
||||||
|
//
|
||||||
|
// Note that we synchronize on itself because the nextPageToken cache is checked at
|
||||||
|
// query-time, and queries are done in parallel with a read lock. Ideally, this would be
|
||||||
|
// guarded by the normal mReadWriteLock.writeLock, but ReentrantReadWriteLocks can't upgrade
|
||||||
|
// read to write locks. This lock should be acquired at the smallest scope possible.
|
||||||
|
// mReadWriteLock is a higher-level lock, so calls shouldn't be made out
|
||||||
|
// to any functions that grab the lock.
|
||||||
|
@GuardedBy("mNextPageTokensLocked")
|
||||||
|
private final Map<String, Set<Long>> mNextPageTokensLocked = new ArrayMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The counter to check when to call {@link #checkForOptimize}. The interval is {@link
|
* The counter to check when to call {@link #checkForOptimize}. The interval is {@link
|
||||||
* #CHECK_OPTIMIZE_INTERVAL}.
|
* #CHECK_OPTIMIZE_INTERVAL}.
|
||||||
@@ -837,12 +852,15 @@ public final class AppSearchImpl implements Closeable {
|
|||||||
String prefix = createPrefix(packageName, databaseName);
|
String prefix = createPrefix(packageName, databaseName);
|
||||||
Set<String> allowedPrefixedSchemas = getAllowedPrefixSchemasLocked(prefix, searchSpec);
|
Set<String> allowedPrefixedSchemas = getAllowedPrefixSchemasLocked(prefix, searchSpec);
|
||||||
|
|
||||||
return doQueryLocked(
|
SearchResultPage searchResultPage =
|
||||||
|
doQueryLocked(
|
||||||
Collections.singleton(createPrefix(packageName, databaseName)),
|
Collections.singleton(createPrefix(packageName, databaseName)),
|
||||||
allowedPrefixedSchemas,
|
allowedPrefixedSchemas,
|
||||||
queryExpression,
|
queryExpression,
|
||||||
searchSpec,
|
searchSpec,
|
||||||
sStatsBuilder);
|
sStatsBuilder);
|
||||||
|
addNextPageToken(packageName, searchResultPage.getNextPageToken());
|
||||||
|
return searchResultPage;
|
||||||
} finally {
|
} finally {
|
||||||
mReadWriteLock.readLock().unlock();
|
mReadWriteLock.readLock().unlock();
|
||||||
if (logger != null) {
|
if (logger != null) {
|
||||||
@@ -956,12 +974,15 @@ public final class AppSearchImpl implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return doQueryLocked(
|
SearchResultPage searchResultPage =
|
||||||
|
doQueryLocked(
|
||||||
prefixFilters,
|
prefixFilters,
|
||||||
prefixedSchemaFilters,
|
prefixedSchemaFilters,
|
||||||
queryExpression,
|
queryExpression,
|
||||||
searchSpec,
|
searchSpec,
|
||||||
sStatsBuilder);
|
sStatsBuilder);
|
||||||
|
addNextPageToken(callerPackageName, searchResultPage.getNextPageToken());
|
||||||
|
return searchResultPage;
|
||||||
} finally {
|
} finally {
|
||||||
mReadWriteLock.readLock().unlock();
|
mReadWriteLock.readLock().unlock();
|
||||||
|
|
||||||
@@ -1093,17 +1114,20 @@ public final class AppSearchImpl implements Closeable {
|
|||||||
*
|
*
|
||||||
* <p>This method belongs to query group.
|
* <p>This method belongs to query group.
|
||||||
*
|
*
|
||||||
|
* @param packageName Package name of the caller.
|
||||||
* @param nextPageToken The token of pre-loaded results of previously executed query.
|
* @param nextPageToken The token of pre-loaded results of previously executed query.
|
||||||
* @return The next page of results of previously executed query.
|
* @return The next page of results of previously executed query.
|
||||||
* @throws AppSearchException on IcingSearchEngine error.
|
* @throws AppSearchException on IcingSearchEngine error or if can't advance on nextPageToken.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public SearchResultPage getNextPage(long nextPageToken) throws AppSearchException {
|
public SearchResultPage getNextPage(@NonNull String packageName, long nextPageToken)
|
||||||
|
throws AppSearchException {
|
||||||
mReadWriteLock.readLock().lock();
|
mReadWriteLock.readLock().lock();
|
||||||
try {
|
try {
|
||||||
throwIfClosedLocked();
|
throwIfClosedLocked();
|
||||||
|
|
||||||
mLogUtil.piiTrace("getNextPage, request", nextPageToken);
|
mLogUtil.piiTrace("getNextPage, request", nextPageToken);
|
||||||
|
checkNextPageToken(packageName, nextPageToken);
|
||||||
SearchResultProto searchResultProto =
|
SearchResultProto searchResultProto =
|
||||||
mIcingSearchEngineLocked.getNextPage(nextPageToken);
|
mIcingSearchEngineLocked.getNextPage(nextPageToken);
|
||||||
mLogUtil.piiTrace(
|
mLogUtil.piiTrace(
|
||||||
@@ -1122,16 +1146,26 @@ public final class AppSearchImpl implements Closeable {
|
|||||||
*
|
*
|
||||||
* <p>This method belongs to query group.
|
* <p>This method belongs to query group.
|
||||||
*
|
*
|
||||||
|
* @param packageName Package name of the caller.
|
||||||
* @param nextPageToken The token of pre-loaded results of previously executed query to be
|
* @param nextPageToken The token of pre-loaded results of previously executed query to be
|
||||||
* Invalidated.
|
* Invalidated.
|
||||||
|
* @throws AppSearchException if nextPageToken is unusable.
|
||||||
*/
|
*/
|
||||||
public void invalidateNextPageToken(long nextPageToken) {
|
public void invalidateNextPageToken(@NonNull String packageName, long nextPageToken)
|
||||||
|
throws AppSearchException {
|
||||||
mReadWriteLock.readLock().lock();
|
mReadWriteLock.readLock().lock();
|
||||||
try {
|
try {
|
||||||
throwIfClosedLocked();
|
throwIfClosedLocked();
|
||||||
|
|
||||||
mLogUtil.piiTrace("invalidateNextPageToken, request", nextPageToken);
|
mLogUtil.piiTrace("invalidateNextPageToken, request", nextPageToken);
|
||||||
|
checkNextPageToken(packageName, nextPageToken);
|
||||||
mIcingSearchEngineLocked.invalidateNextPageToken(nextPageToken);
|
mIcingSearchEngineLocked.invalidateNextPageToken(nextPageToken);
|
||||||
|
|
||||||
|
synchronized (mNextPageTokensLocked) {
|
||||||
|
// At this point, we're guaranteed that this nextPageToken exists for this package,
|
||||||
|
// otherwise checkNextPageToken would've thrown an exception.
|
||||||
|
mNextPageTokensLocked.get(packageName).remove(nextPageToken);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
mReadWriteLock.readLock().unlock();
|
mReadWriteLock.readLock().unlock();
|
||||||
}
|
}
|
||||||
@@ -1568,6 +1602,9 @@ public final class AppSearchImpl implements Closeable {
|
|||||||
Set<String> databaseNames = entry.getValue();
|
Set<String> databaseNames = entry.getValue();
|
||||||
if (!installedPackages.contains(packageName) && databaseNames != null) {
|
if (!installedPackages.contains(packageName) && databaseNames != null) {
|
||||||
mDocumentCountMapLocked.remove(packageName);
|
mDocumentCountMapLocked.remove(packageName);
|
||||||
|
synchronized (mNextPageTokensLocked) {
|
||||||
|
mNextPageTokensLocked.remove(packageName);
|
||||||
|
}
|
||||||
for (String databaseName : databaseNames) {
|
for (String databaseName : databaseNames) {
|
||||||
String removedPrefix = createPrefix(packageName, databaseName);
|
String removedPrefix = createPrefix(packageName, databaseName);
|
||||||
mSchemaMapLocked.remove(removedPrefix);
|
mSchemaMapLocked.remove(removedPrefix);
|
||||||
@@ -1601,6 +1638,9 @@ public final class AppSearchImpl implements Closeable {
|
|||||||
mSchemaMapLocked.clear();
|
mSchemaMapLocked.clear();
|
||||||
mNamespaceMapLocked.clear();
|
mNamespaceMapLocked.clear();
|
||||||
mDocumentCountMapLocked.clear();
|
mDocumentCountMapLocked.clear();
|
||||||
|
synchronized (mNextPageTokensLocked) {
|
||||||
|
mNextPageTokensLocked.clear();
|
||||||
|
}
|
||||||
if (initStatsBuilder != null) {
|
if (initStatsBuilder != null) {
|
||||||
initStatsBuilder
|
initStatsBuilder
|
||||||
.setHasReset(true)
|
.setHasReset(true)
|
||||||
@@ -2015,6 +2055,32 @@ public final class AppSearchImpl implements Closeable {
|
|||||||
return schemaProto.getSchema();
|
return schemaProto.getSchema();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addNextPageToken(String packageName, long nextPageToken) {
|
||||||
|
synchronized (mNextPageTokensLocked) {
|
||||||
|
Set<Long> tokens = mNextPageTokensLocked.get(packageName);
|
||||||
|
if (tokens == null) {
|
||||||
|
tokens = new ArraySet<>();
|
||||||
|
mNextPageTokensLocked.put(packageName, tokens);
|
||||||
|
}
|
||||||
|
tokens.add(nextPageToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkNextPageToken(String packageName, long nextPageToken)
|
||||||
|
throws AppSearchException {
|
||||||
|
synchronized (mNextPageTokensLocked) {
|
||||||
|
Set<Long> nextPageTokens = mNextPageTokensLocked.get(packageName);
|
||||||
|
if (nextPageTokens == null || !nextPageTokens.contains(nextPageToken)) {
|
||||||
|
throw new AppSearchException(
|
||||||
|
AppSearchResult.RESULT_SECURITY_ERROR,
|
||||||
|
"Package \""
|
||||||
|
+ packageName
|
||||||
|
+ "\" cannot use nextPageToken: "
|
||||||
|
+ nextPageToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void addToMap(
|
private static void addToMap(
|
||||||
Map<String, Set<String>> map, String prefix, String prefixedValue) {
|
Map<String, Set<String>> map, String prefix, String prefixedValue) {
|
||||||
Set<String> values = map.get(prefix);
|
Set<String> values = map.get(prefix);
|
||||||
|
|||||||
@@ -868,6 +868,446 @@ public class AppSearchImplTest {
|
|||||||
assertThat(searchResultPage.getResults()).isEmpty();
|
assertThat(searchResultPage.getResults()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetNextPageToken_query() throws Exception {
|
||||||
|
// Insert package1 schema
|
||||||
|
List<AppSearchSchema> schema1 =
|
||||||
|
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
|
||||||
|
mAppSearchImpl.setSchema(
|
||||||
|
"package1",
|
||||||
|
"database1",
|
||||||
|
schema1,
|
||||||
|
/*visibilityStore=*/ null,
|
||||||
|
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
|
||||||
|
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
|
||||||
|
/*forceOverride=*/ false,
|
||||||
|
/*version=*/ 0);
|
||||||
|
|
||||||
|
// Insert two package1 documents
|
||||||
|
GenericDocument document1 =
|
||||||
|
new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
|
||||||
|
GenericDocument document2 =
|
||||||
|
new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
|
||||||
|
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
|
||||||
|
mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
|
||||||
|
|
||||||
|
// Query for only 1 result per page
|
||||||
|
SearchSpec searchSpec =
|
||||||
|
new SearchSpec.Builder()
|
||||||
|
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
|
||||||
|
.setResultCountPerPage(1)
|
||||||
|
.build();
|
||||||
|
SearchResultPage searchResultPage =
|
||||||
|
mAppSearchImpl.query("package1", "database1", "", searchSpec, /*logger=*/ null);
|
||||||
|
|
||||||
|
// Document2 will come first because it was inserted last and default return order is
|
||||||
|
// most recent.
|
||||||
|
assertThat(searchResultPage.getResults()).hasSize(1);
|
||||||
|
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
|
||||||
|
|
||||||
|
long nextPageToken = searchResultPage.getNextPageToken();
|
||||||
|
searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
|
||||||
|
assertThat(searchResultPage.getResults()).hasSize(1);
|
||||||
|
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetNextPageWithDifferentPackage_query() throws Exception {
|
||||||
|
// Insert package1 schema
|
||||||
|
List<AppSearchSchema> schema1 =
|
||||||
|
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
|
||||||
|
mAppSearchImpl.setSchema(
|
||||||
|
"package1",
|
||||||
|
"database1",
|
||||||
|
schema1,
|
||||||
|
/*visibilityStore=*/ null,
|
||||||
|
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
|
||||||
|
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
|
||||||
|
/*forceOverride=*/ false,
|
||||||
|
/*version=*/ 0);
|
||||||
|
|
||||||
|
// Insert two package1 documents
|
||||||
|
GenericDocument document1 =
|
||||||
|
new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
|
||||||
|
GenericDocument document2 =
|
||||||
|
new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
|
||||||
|
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
|
||||||
|
mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
|
||||||
|
|
||||||
|
// Query for only 1 result per page
|
||||||
|
SearchSpec searchSpec =
|
||||||
|
new SearchSpec.Builder()
|
||||||
|
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
|
||||||
|
.setResultCountPerPage(1)
|
||||||
|
.build();
|
||||||
|
SearchResultPage searchResultPage =
|
||||||
|
mAppSearchImpl.query("package1", "database1", "", searchSpec, /*logger=*/ null);
|
||||||
|
|
||||||
|
// Document2 will come first because it was inserted last and default return order is
|
||||||
|
// most recent.
|
||||||
|
assertThat(searchResultPage.getResults()).hasSize(1);
|
||||||
|
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
|
||||||
|
|
||||||
|
long nextPageToken = searchResultPage.getNextPageToken();
|
||||||
|
|
||||||
|
// Try getting next page with the wrong package, package2
|
||||||
|
AppSearchException e =
|
||||||
|
assertThrows(
|
||||||
|
AppSearchException.class,
|
||||||
|
() -> mAppSearchImpl.getNextPage("package2", nextPageToken));
|
||||||
|
assertThat(e)
|
||||||
|
.hasMessageThat()
|
||||||
|
.contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken);
|
||||||
|
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
|
||||||
|
|
||||||
|
// Can continue getting next page for package1
|
||||||
|
searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
|
||||||
|
assertThat(searchResultPage.getResults()).hasSize(1);
|
||||||
|
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetNextPageToken_globalQuery() throws Exception {
|
||||||
|
// Insert package1 schema
|
||||||
|
List<AppSearchSchema> schema1 =
|
||||||
|
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
|
||||||
|
mAppSearchImpl.setSchema(
|
||||||
|
"package1",
|
||||||
|
"database1",
|
||||||
|
schema1,
|
||||||
|
/*visibilityStore=*/ null,
|
||||||
|
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
|
||||||
|
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
|
||||||
|
/*forceOverride=*/ false,
|
||||||
|
/*version=*/ 0);
|
||||||
|
|
||||||
|
// Insert two package1 documents
|
||||||
|
GenericDocument document1 =
|
||||||
|
new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
|
||||||
|
GenericDocument document2 =
|
||||||
|
new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
|
||||||
|
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
|
||||||
|
mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
|
||||||
|
|
||||||
|
// Query for only 1 result per page
|
||||||
|
SearchSpec searchSpec =
|
||||||
|
new SearchSpec.Builder()
|
||||||
|
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
|
||||||
|
.setResultCountPerPage(1)
|
||||||
|
.build();
|
||||||
|
SearchResultPage searchResultPage =
|
||||||
|
mAppSearchImpl.globalQuery(
|
||||||
|
/*queryExpression=*/ "",
|
||||||
|
searchSpec,
|
||||||
|
"package1",
|
||||||
|
/*visibilityStore=*/ null,
|
||||||
|
Process.myUid(),
|
||||||
|
/*callerHasSystemAccess=*/ false,
|
||||||
|
/*logger=*/ null);
|
||||||
|
|
||||||
|
// Document2 will come first because it was inserted last and default return order is
|
||||||
|
// most recent.
|
||||||
|
assertThat(searchResultPage.getResults()).hasSize(1);
|
||||||
|
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
|
||||||
|
|
||||||
|
long nextPageToken = searchResultPage.getNextPageToken();
|
||||||
|
searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
|
||||||
|
assertThat(searchResultPage.getResults()).hasSize(1);
|
||||||
|
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetNextPageWithDifferentPackage_globalQuery() throws Exception {
|
||||||
|
// Insert package1 schema
|
||||||
|
List<AppSearchSchema> schema1 =
|
||||||
|
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
|
||||||
|
mAppSearchImpl.setSchema(
|
||||||
|
"package1",
|
||||||
|
"database1",
|
||||||
|
schema1,
|
||||||
|
/*visibilityStore=*/ null,
|
||||||
|
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
|
||||||
|
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
|
||||||
|
/*forceOverride=*/ false,
|
||||||
|
/*version=*/ 0);
|
||||||
|
|
||||||
|
// Insert two package1 documents
|
||||||
|
GenericDocument document1 =
|
||||||
|
new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
|
||||||
|
GenericDocument document2 =
|
||||||
|
new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
|
||||||
|
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
|
||||||
|
mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
|
||||||
|
|
||||||
|
// Query for only 1 result per page
|
||||||
|
SearchSpec searchSpec =
|
||||||
|
new SearchSpec.Builder()
|
||||||
|
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
|
||||||
|
.setResultCountPerPage(1)
|
||||||
|
.build();
|
||||||
|
SearchResultPage searchResultPage =
|
||||||
|
mAppSearchImpl.globalQuery(
|
||||||
|
/*queryExpression=*/ "",
|
||||||
|
searchSpec,
|
||||||
|
"package1",
|
||||||
|
/*visibilityStore=*/ null,
|
||||||
|
Process.myUid(),
|
||||||
|
/*callerHasSystemAccess=*/ false,
|
||||||
|
/*logger=*/ null);
|
||||||
|
|
||||||
|
// Document2 will come first because it was inserted last and default return order is
|
||||||
|
// most recent.
|
||||||
|
assertThat(searchResultPage.getResults()).hasSize(1);
|
||||||
|
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
|
||||||
|
|
||||||
|
long nextPageToken = searchResultPage.getNextPageToken();
|
||||||
|
|
||||||
|
// Try getting next page with the wrong package, package2
|
||||||
|
AppSearchException e =
|
||||||
|
assertThrows(
|
||||||
|
AppSearchException.class,
|
||||||
|
() -> mAppSearchImpl.getNextPage("package2", nextPageToken));
|
||||||
|
assertThat(e)
|
||||||
|
.hasMessageThat()
|
||||||
|
.contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken);
|
||||||
|
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
|
||||||
|
|
||||||
|
// Can continue getting next page for package1
|
||||||
|
searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
|
||||||
|
assertThat(searchResultPage.getResults()).hasSize(1);
|
||||||
|
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidateNextPageToken_query() throws Exception {
|
||||||
|
// Insert package1 schema
|
||||||
|
List<AppSearchSchema> schema1 =
|
||||||
|
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
|
||||||
|
mAppSearchImpl.setSchema(
|
||||||
|
"package1",
|
||||||
|
"database1",
|
||||||
|
schema1,
|
||||||
|
/*visibilityStore=*/ null,
|
||||||
|
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
|
||||||
|
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
|
||||||
|
/*forceOverride=*/ false,
|
||||||
|
/*version=*/ 0);
|
||||||
|
|
||||||
|
// Insert two package1 documents
|
||||||
|
GenericDocument document1 =
|
||||||
|
new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
|
||||||
|
GenericDocument document2 =
|
||||||
|
new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
|
||||||
|
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
|
||||||
|
mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
|
||||||
|
|
||||||
|
// Query for only 1 result per page
|
||||||
|
SearchSpec searchSpec =
|
||||||
|
new SearchSpec.Builder()
|
||||||
|
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
|
||||||
|
.setResultCountPerPage(1)
|
||||||
|
.build();
|
||||||
|
SearchResultPage searchResultPage =
|
||||||
|
mAppSearchImpl.query("package1", "database1", "", searchSpec, /*logger=*/ null);
|
||||||
|
|
||||||
|
// Document2 will come first because it was inserted last and default return order is
|
||||||
|
// most recent.
|
||||||
|
assertThat(searchResultPage.getResults()).hasSize(1);
|
||||||
|
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
|
||||||
|
|
||||||
|
long nextPageToken = searchResultPage.getNextPageToken();
|
||||||
|
|
||||||
|
// Invalidate the token
|
||||||
|
mAppSearchImpl.invalidateNextPageToken("package1", nextPageToken);
|
||||||
|
|
||||||
|
// Can't get next page because we invalidated the token.
|
||||||
|
AppSearchException e =
|
||||||
|
assertThrows(
|
||||||
|
AppSearchException.class,
|
||||||
|
() -> mAppSearchImpl.getNextPage("package1", nextPageToken));
|
||||||
|
assertThat(e)
|
||||||
|
.hasMessageThat()
|
||||||
|
.contains("Package \"package1\" cannot use nextPageToken: " + nextPageToken);
|
||||||
|
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidateNextPageTokenWithDifferentPackage_query() throws Exception {
|
||||||
|
// Insert package1 schema
|
||||||
|
List<AppSearchSchema> schema1 =
|
||||||
|
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
|
||||||
|
mAppSearchImpl.setSchema(
|
||||||
|
"package1",
|
||||||
|
"database1",
|
||||||
|
schema1,
|
||||||
|
/*visibilityStore=*/ null,
|
||||||
|
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
|
||||||
|
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
|
||||||
|
/*forceOverride=*/ false,
|
||||||
|
/*version=*/ 0);
|
||||||
|
|
||||||
|
// Insert two package1 documents
|
||||||
|
GenericDocument document1 =
|
||||||
|
new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
|
||||||
|
GenericDocument document2 =
|
||||||
|
new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
|
||||||
|
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
|
||||||
|
mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
|
||||||
|
|
||||||
|
// Query for only 1 result per page
|
||||||
|
SearchSpec searchSpec =
|
||||||
|
new SearchSpec.Builder()
|
||||||
|
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
|
||||||
|
.setResultCountPerPage(1)
|
||||||
|
.build();
|
||||||
|
SearchResultPage searchResultPage =
|
||||||
|
mAppSearchImpl.query("package1", "database1", "", searchSpec, /*logger=*/ null);
|
||||||
|
|
||||||
|
// Document2 will come first because it was inserted last and default return order is
|
||||||
|
// most recent.
|
||||||
|
assertThat(searchResultPage.getResults()).hasSize(1);
|
||||||
|
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
|
||||||
|
|
||||||
|
long nextPageToken = searchResultPage.getNextPageToken();
|
||||||
|
|
||||||
|
// Try getting next page with the wrong package, package2
|
||||||
|
AppSearchException e =
|
||||||
|
assertThrows(
|
||||||
|
AppSearchException.class,
|
||||||
|
() -> mAppSearchImpl.invalidateNextPageToken("package2", nextPageToken));
|
||||||
|
assertThat(e)
|
||||||
|
.hasMessageThat()
|
||||||
|
.contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken);
|
||||||
|
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
|
||||||
|
|
||||||
|
// Can continue getting next page for package1
|
||||||
|
searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
|
||||||
|
assertThat(searchResultPage.getResults()).hasSize(1);
|
||||||
|
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidateNextPageToken_globalQuery() throws Exception {
|
||||||
|
// Insert package1 schema
|
||||||
|
List<AppSearchSchema> schema1 =
|
||||||
|
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
|
||||||
|
mAppSearchImpl.setSchema(
|
||||||
|
"package1",
|
||||||
|
"database1",
|
||||||
|
schema1,
|
||||||
|
/*visibilityStore=*/ null,
|
||||||
|
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
|
||||||
|
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
|
||||||
|
/*forceOverride=*/ false,
|
||||||
|
/*version=*/ 0);
|
||||||
|
|
||||||
|
// Insert two package1 documents
|
||||||
|
GenericDocument document1 =
|
||||||
|
new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
|
||||||
|
GenericDocument document2 =
|
||||||
|
new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
|
||||||
|
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
|
||||||
|
mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
|
||||||
|
|
||||||
|
// Query for only 1 result per page
|
||||||
|
SearchSpec searchSpec =
|
||||||
|
new SearchSpec.Builder()
|
||||||
|
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
|
||||||
|
.setResultCountPerPage(1)
|
||||||
|
.build();
|
||||||
|
SearchResultPage searchResultPage =
|
||||||
|
mAppSearchImpl.globalQuery(
|
||||||
|
/*queryExpression=*/ "",
|
||||||
|
searchSpec,
|
||||||
|
"package1",
|
||||||
|
/*visibilityStore=*/ null,
|
||||||
|
Process.myUid(),
|
||||||
|
/*callerHasSystemAccess=*/ false,
|
||||||
|
/*logger=*/ null);
|
||||||
|
|
||||||
|
// Document2 will come first because it was inserted last and default return order is
|
||||||
|
// most recent.
|
||||||
|
assertThat(searchResultPage.getResults()).hasSize(1);
|
||||||
|
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
|
||||||
|
|
||||||
|
long nextPageToken = searchResultPage.getNextPageToken();
|
||||||
|
|
||||||
|
// Invalidate the token
|
||||||
|
mAppSearchImpl.invalidateNextPageToken("package1", nextPageToken);
|
||||||
|
|
||||||
|
// Can't get next page because we invalidated the token.
|
||||||
|
AppSearchException e =
|
||||||
|
assertThrows(
|
||||||
|
AppSearchException.class,
|
||||||
|
() -> mAppSearchImpl.getNextPage("package1", nextPageToken));
|
||||||
|
assertThat(e)
|
||||||
|
.hasMessageThat()
|
||||||
|
.contains("Package \"package1\" cannot use nextPageToken: " + nextPageToken);
|
||||||
|
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidateNextPageTokenWithDifferentPackage_globalQuery() throws Exception {
|
||||||
|
// Insert package1 schema
|
||||||
|
List<AppSearchSchema> schema1 =
|
||||||
|
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
|
||||||
|
mAppSearchImpl.setSchema(
|
||||||
|
"package1",
|
||||||
|
"database1",
|
||||||
|
schema1,
|
||||||
|
/*visibilityStore=*/ null,
|
||||||
|
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
|
||||||
|
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
|
||||||
|
/*forceOverride=*/ false,
|
||||||
|
/*version=*/ 0);
|
||||||
|
|
||||||
|
// Insert two package1 documents
|
||||||
|
GenericDocument document1 =
|
||||||
|
new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
|
||||||
|
GenericDocument document2 =
|
||||||
|
new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
|
||||||
|
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
|
||||||
|
mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
|
||||||
|
|
||||||
|
// Query for only 1 result per page
|
||||||
|
SearchSpec searchSpec =
|
||||||
|
new SearchSpec.Builder()
|
||||||
|
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
|
||||||
|
.setResultCountPerPage(1)
|
||||||
|
.build();
|
||||||
|
SearchResultPage searchResultPage =
|
||||||
|
mAppSearchImpl.globalQuery(
|
||||||
|
/*queryExpression=*/ "",
|
||||||
|
searchSpec,
|
||||||
|
"package1",
|
||||||
|
/*visibilityStore=*/ null,
|
||||||
|
Process.myUid(),
|
||||||
|
/*callerHasSystemAccess=*/ false,
|
||||||
|
/*logger=*/ null);
|
||||||
|
|
||||||
|
// Document2 will come first because it was inserted last and default return order is
|
||||||
|
// most recent.
|
||||||
|
assertThat(searchResultPage.getResults()).hasSize(1);
|
||||||
|
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
|
||||||
|
|
||||||
|
long nextPageToken = searchResultPage.getNextPageToken();
|
||||||
|
|
||||||
|
// Try getting next page with the wrong package, package2
|
||||||
|
AppSearchException e =
|
||||||
|
assertThrows(
|
||||||
|
AppSearchException.class,
|
||||||
|
() -> mAppSearchImpl.invalidateNextPageToken("package2", nextPageToken));
|
||||||
|
assertThat(e)
|
||||||
|
.hasMessageThat()
|
||||||
|
.contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken);
|
||||||
|
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
|
||||||
|
|
||||||
|
// Can continue getting next page for package1
|
||||||
|
searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
|
||||||
|
assertThat(searchResultPage.getResults()).hasSize(1);
|
||||||
|
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRemoveEmptyDatabase_noExceptionThrown() throws Exception {
|
public void testRemoveEmptyDatabase_noExceptionThrown() throws Exception {
|
||||||
SearchSpec searchSpec =
|
SearchSpec searchSpec =
|
||||||
@@ -1777,11 +2217,11 @@ public class AppSearchImplTest {
|
|||||||
|
|
||||||
assertThrows(
|
assertThrows(
|
||||||
IllegalStateException.class,
|
IllegalStateException.class,
|
||||||
() -> appSearchImpl.getNextPage(/*nextPageToken=*/ 1L));
|
() -> appSearchImpl.getNextPage("package", /*nextPageToken=*/ 1L));
|
||||||
|
|
||||||
assertThrows(
|
assertThrows(
|
||||||
IllegalStateException.class,
|
IllegalStateException.class,
|
||||||
() -> appSearchImpl.invalidateNextPageToken(/*nextPageToken=*/ 1L));
|
() -> appSearchImpl.invalidateNextPageToken("package", /*nextPageToken=*/ 1L));
|
||||||
|
|
||||||
assertThrows(
|
assertThrows(
|
||||||
IllegalStateException.class,
|
IllegalStateException.class,
|
||||||
|
|||||||
Reference in New Issue
Block a user