Add ability to enforce limits on docs per package and size of doc.

This is needed to prevent abuse of our service and to share icing
docids in framework, which are the resource we are most likely to
run out of.

Without this change, an app could use the platform backend to index
a very high number of documents and exhaust the docid limit, thereby
preventing any other app from using AppSearch.

Bug: 170371356
Test: New testcases added to AppSearchImplTest
Change-Id: I03ade35072bc69b84f8fcefed72b3c3bc2b8ee68
This commit is contained in:
Alexander Dorokhine
2021-07-01 13:22:08 -07:00
parent 598c5fd593
commit 5e4a11cd10
12 changed files with 1327 additions and 61 deletions

View File

@@ -60,6 +60,11 @@ public final class AppSearchConfig implements AutoCloseable {
@VisibleForTesting
static final int DEFAULT_SAMPLING_INTERVAL = 10;
@VisibleForTesting
static final int DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES = 512 * 1024; // 512KiB
@VisibleForTesting
static final int DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT = 20_000;
/*
* Keys for ALL the flags stored in DeviceConfig.
*/
@@ -70,13 +75,19 @@ public final class AppSearchConfig implements AutoCloseable {
"sampling_interval_for_batch_call_stats";
public static final String KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS =
"sampling_interval_for_put_document_stats";
public static final String KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES =
"limit_config_max_document_size_bytes";
public static final String KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT =
"limit_config_max_document_docunt";
// Array contains all the corresponding keys for the cached values.
private static final String[] KEYS_TO_ALL_CACHED_VALUES = {
KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
KEY_SAMPLING_INTERVAL_DEFAULT,
KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS
KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES,
KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT,
};
// Lock needed for all the operations in this class.
@@ -222,6 +233,24 @@ public final class AppSearchConfig implements AutoCloseable {
}
}
/** Returns the maximum serialized size an indexed document can be, in bytes. */
public int getCachedLimitConfigMaxDocumentSizeBytes() {
synchronized (mLock) {
throwIfClosedLocked();
return mBundleLocked.getInt(KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES,
DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES);
}
}
/** Returns the maximum number of active docs allowed per package. */
public int getCachedLimitConfigMaxDocumentCount() {
synchronized (mLock) {
throwIfClosedLocked();
return mBundleLocked.getInt(KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT,
DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT);
}
}
@GuardedBy("mLock")
private void throwIfClosedLocked() {
if (mIsClosedLocked) {
@@ -264,6 +293,20 @@ public final class AppSearchConfig implements AutoCloseable {
mBundleLocked.putInt(key, properties.getInt(key, DEFAULT_SAMPLING_INTERVAL));
}
break;
case KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES:
synchronized (mLock) {
mBundleLocked.putInt(
key,
properties.getInt(key, DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES));
}
break;
case KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT:
synchronized (mLock) {
mBundleLocked.putInt(
key,
properties.getInt(key, DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT));
}
break;
default:
break;
}

View File

@@ -173,8 +173,11 @@ public final class AppSearchUserInstanceManager {
File appSearchDir = getAppSearchDir(userHandle);
File icingDir = new File(appSearchDir, "icing");
Log.i(TAG, "Creating new AppSearch instance at: " + icingDir);
AppSearchImpl appSearchImpl =
AppSearchImpl.create(icingDir, initStatsBuilder, new FrameworkOptimizeStrategy());
AppSearchImpl appSearchImpl = AppSearchImpl.create(
icingDir,
new FrameworkLimitConfig(config),
initStatsBuilder,
new FrameworkOptimizeStrategy());
long prepareVisibilityStoreLatencyStartMillis = SystemClock.elapsedRealtime();
VisibilityStoreImpl visibilityStore =

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 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.
* 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 com.android.server.appsearch;
import android.annotation.NonNull;
import com.android.server.appsearch.external.localstorage.LimitConfig;
import java.util.Objects;
class FrameworkLimitConfig implements LimitConfig {
private final AppSearchConfig mAppSearchConfig;
FrameworkLimitConfig(@NonNull AppSearchConfig appSearchConfig) {
mAppSearchConfig = Objects.requireNonNull(appSearchConfig);
}
@Override
public int getMaxDocumentSizeBytes() {
return mAppSearchConfig.getCachedLimitConfigMaxDocumentSizeBytes();
}
@Override
public int getMaxDocumentCount() {
return mAppSearchConfig.getCachedLimitConfigMaxDocumentCount();
}
}

View File

@@ -88,6 +88,7 @@ import com.google.android.icing.proto.SearchResultProto;
import com.google.android.icing.proto.SearchSpecProto;
import com.google.android.icing.proto.SetSchemaResultProto;
import com.google.android.icing.proto.StatusProto;
import com.google.android.icing.proto.StorageInfoProto;
import com.google.android.icing.proto.StorageInfoResultProto;
import com.google.android.icing.proto.TypePropertyMask;
import com.google.android.icing.proto.UsageReport;
@@ -147,10 +148,9 @@ public final class AppSearchImpl implements Closeable {
@VisibleForTesting static final int CHECK_OPTIMIZE_INTERVAL = 100;
private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
private final LogUtil mLogUtil = new LogUtil(TAG);
private final OptimizeStrategy mOptimizeStrategy;
private final LimitConfig mLimitConfig;
@GuardedBy("mReadWriteLock")
@VisibleForTesting
@@ -169,6 +169,10 @@ public final class AppSearchImpl implements Closeable {
@GuardedBy("mReadWriteLock")
private final Map<String, Set<String>> mNamespaceMapLocked = new HashMap<>();
/** Maps package name to active document count. */
@GuardedBy("mReadWriteLock")
private final Map<String, Integer> mDocumentCountMapLocked = new ArrayMap<>();
/**
* The counter to check when to call {@link #checkForOptimize}. The interval is {@link
* #CHECK_OPTIMIZE_INTERVAL}.
@@ -196,19 +200,22 @@ public final class AppSearchImpl implements Closeable {
@NonNull
public static AppSearchImpl create(
@NonNull File icingDir,
@NonNull LimitConfig limitConfig,
@Nullable InitializeStats.Builder initStatsBuilder,
@NonNull OptimizeStrategy optimizeStrategy)
throws AppSearchException {
return new AppSearchImpl(icingDir, initStatsBuilder, optimizeStrategy);
return new AppSearchImpl(icingDir, limitConfig, initStatsBuilder, optimizeStrategy);
}
/** @param initStatsBuilder collects stats for initialization if provided. */
private AppSearchImpl(
@NonNull File icingDir,
@NonNull LimitConfig limitConfig,
@Nullable InitializeStats.Builder initStatsBuilder,
@NonNull OptimizeStrategy optimizeStrategy)
throws AppSearchException {
Objects.requireNonNull(icingDir);
mLimitConfig = Objects.requireNonNull(limitConfig);
mOptimizeStrategy = Objects.requireNonNull(optimizeStrategy);
mReadWriteLock.writeLock().lock();
@@ -244,9 +251,9 @@ public final class AppSearchImpl implements Closeable {
AppSearchLoggerHelper.copyNativeStats(
initializeResultProto.getInitializeStats(), initStatsBuilder);
}
checkSuccess(initializeResultProto.getStatus());
// Read all protos we need to construct AppSearchImpl's cache maps
long prepareSchemaAndNamespacesLatencyStartMillis = SystemClock.elapsedRealtime();
SchemaProto schemaProto = getSchemaProtoLocked();
@@ -258,6 +265,9 @@ public final class AppSearchImpl implements Closeable {
getAllNamespacesResultProto.getNamespacesCount(),
getAllNamespacesResultProto);
StorageInfoProto storageInfoProto = getRawStorageInfoProto();
// Log the time it took to read the data that goes into the cache maps
if (initStatsBuilder != null) {
initStatsBuilder
.setStatusCode(
@@ -268,20 +278,27 @@ public final class AppSearchImpl implements Closeable {
(SystemClock.elapsedRealtime()
- prepareSchemaAndNamespacesLatencyStartMillis));
}
checkSuccess(getAllNamespacesResultProto.getStatus());
// Populate schema map
for (SchemaTypeConfigProto schema : schemaProto.getTypesList()) {
List<SchemaTypeConfigProto> schemaProtoTypesList = schemaProto.getTypesList();
for (int i = 0; i < schemaProtoTypesList.size(); i++) {
SchemaTypeConfigProto schema = schemaProtoTypesList.get(i);
String prefixedSchemaType = schema.getSchemaType();
addToMap(mSchemaMapLocked, getPrefix(prefixedSchemaType), schema);
}
// Populate namespace map
for (String prefixedNamespace : getAllNamespacesResultProto.getNamespacesList()) {
List<String> prefixedNamespaceList =
getAllNamespacesResultProto.getNamespacesList();
for (int i = 0; i < prefixedNamespaceList.size(); i++) {
String prefixedNamespace = prefixedNamespaceList.get(i);
addToMap(mNamespaceMapLocked, getPrefix(prefixedNamespace), prefixedNamespace);
}
// Populate document count map
rebuildDocumentCountMapLocked(storageInfoProto);
// logging prepare_schema_and_namespaces latency
if (initStatsBuilder != null) {
initStatsBuilder.setPrepareSchemaAndNamespacesLatencyMillis(
@@ -596,10 +613,19 @@ public final class AppSearchImpl implements Closeable {
long rewriteDocumentTypeEndTimeMillis = SystemClock.elapsedRealtime();
DocumentProto finalDocument = documentBuilder.build();
// Check limits
int newDocumentCount =
enforceLimitConfigLocked(
packageName, finalDocument.getUri(), finalDocument.getSerializedSize());
// Insert document
mLogUtil.piiTrace("putDocument, request", finalDocument.getUri(), finalDocument);
PutResultProto putResultProto = mIcingSearchEngineLocked.put(documentBuilder.build());
PutResultProto putResultProto = mIcingSearchEngineLocked.put(finalDocument);
mLogUtil.piiTrace("putDocument, response", putResultProto.getStatus(), putResultProto);
addToMap(mNamespaceMapLocked, prefix, documentBuilder.getNamespace());
// Update caches
addToMap(mNamespaceMapLocked, prefix, finalDocument.getNamespace());
mDocumentCountMapLocked.put(packageName, newDocumentCount);
// Logging stats
if (pStatsBuilder != null) {
@@ -630,6 +656,71 @@ public final class AppSearchImpl implements Closeable {
}
}
/**
* Checks that a new document can be added to the given packageName with the given serialized
* size without violating our {@link LimitConfig}.
*
* @return the new count of documents for the given package, including the new document.
* @throws AppSearchException with a code of {@link AppSearchResult#RESULT_OUT_OF_SPACE} if the
* limits are violated by the new document.
*/
@GuardedBy("mReadWriteLock")
private int enforceLimitConfigLocked(String packageName, String newDocUri, int newDocSize)
throws AppSearchException {
// Limits check: size of document
if (newDocSize > mLimitConfig.getMaxDocumentSizeBytes()) {
throw new AppSearchException(
AppSearchResult.RESULT_OUT_OF_SPACE,
"Document \""
+ newDocUri
+ "\" for package \""
+ packageName
+ "\" serialized to "
+ newDocSize
+ " bytes, which exceeds "
+ "limit of "
+ mLimitConfig.getMaxDocumentSizeBytes()
+ " bytes");
}
// Limits check: number of documents
Integer oldDocumentCount = mDocumentCountMapLocked.get(packageName);
int newDocumentCount;
if (oldDocumentCount == null) {
newDocumentCount = 1;
} else {
newDocumentCount = oldDocumentCount + 1;
}
if (newDocumentCount > mLimitConfig.getMaxDocumentCount()) {
// Our management of mDocumentCountMapLocked doesn't account for document
// replacements, so our counter might have overcounted if the app has replaced docs.
// Rebuild the counter from StorageInfo in case this is so.
// TODO(b/170371356): If Icing lib exposes something in the result which says
// whether the document was a replacement, we could subtract 1 again after the put
// to keep the count accurate. That would allow us to remove this code.
rebuildDocumentCountMapLocked(getRawStorageInfoProto());
oldDocumentCount = mDocumentCountMapLocked.get(packageName);
if (oldDocumentCount == null) {
newDocumentCount = 1;
} else {
newDocumentCount = oldDocumentCount + 1;
}
}
if (newDocumentCount > mLimitConfig.getMaxDocumentCount()) {
// Now we really can't fit it in, even accounting for replacements.
throw new AppSearchException(
AppSearchResult.RESULT_OUT_OF_SPACE,
"Package \""
+ packageName
+ "\" exceeded limit of "
+ mLimitConfig.getMaxDocumentCount()
+ " documents. Some documents "
+ "must be removed to index additional ones.");
}
return newDocumentCount;
}
/**
* Retrieves a document from the AppSearch index by namespace and document ID.
*
@@ -1121,6 +1212,9 @@ public final class AppSearchImpl implements Closeable {
deleteResultProto.getDeleteStats(), removeStatsBuilder);
}
checkSuccess(deleteResultProto.getStatus());
// Update derived maps
updateDocumentCountAfterRemovalLocked(packageName, /*numDocumentsDeleted=*/ 1);
} finally {
mReadWriteLock.writeLock().unlock();
if (removeStatsBuilder != null) {
@@ -1196,6 +1290,11 @@ public final class AppSearchImpl implements Closeable {
// not in the DB because it was not there or was successfully deleted.
checkCodeOneOf(
deleteResultProto.getStatus(), StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
// Update derived maps
int numDocumentsDeleted =
deleteResultProto.getDeleteStats().getNumDocumentsDeleted();
updateDocumentCountAfterRemovalLocked(packageName, numDocumentsDeleted);
} finally {
mReadWriteLock.writeLock().unlock();
if (removeStatsBuilder != null) {
@@ -1205,6 +1304,22 @@ public final class AppSearchImpl implements Closeable {
}
}
@GuardedBy("mReadWriteLock")
private void updateDocumentCountAfterRemovalLocked(
@NonNull String packageName, int numDocumentsDeleted) {
if (numDocumentsDeleted > 0) {
Integer oldDocumentCount = mDocumentCountMapLocked.get(packageName);
// This should always be true: how can we delete documents for a package without
// having seen that package during init? This is just a safeguard.
if (oldDocumentCount != null) {
// This should always be >0; how can we remove more documents than we've indexed?
// This is just a safeguard.
int newDocumentCount = Math.max(oldDocumentCount - numDocumentsDeleted, 0);
mDocumentCountMapLocked.put(packageName, newDocumentCount);
}
}
}
/** Estimates the storage usage info for a specific package. */
@NonNull
public StorageInfo getStorageInfoForPackage(@NonNull String packageName)
@@ -1233,7 +1348,7 @@ public final class AppSearchImpl implements Closeable {
return new StorageInfo.Builder().build();
}
return getStorageInfoForNamespacesLocked(wantedPrefixedNamespaces);
return getStorageInfoForNamespaces(getRawStorageInfoProto(), wantedPrefixedNamespaces);
} finally {
mReadWriteLock.readLock().unlock();
}
@@ -1264,29 +1379,45 @@ public final class AppSearchImpl implements Closeable {
return new StorageInfo.Builder().build();
}
return getStorageInfoForNamespacesLocked(wantedPrefixedNamespaces);
return getStorageInfoForNamespaces(getRawStorageInfoProto(), wantedPrefixedNamespaces);
} finally {
mReadWriteLock.readLock().unlock();
}
}
@GuardedBy("mReadWriteLock")
/**
* Returns the native storage info capsuled in {@link StorageInfoResultProto} directly from
* IcingSearchEngine.
*/
@NonNull
private StorageInfo getStorageInfoForNamespacesLocked(@NonNull Set<String> prefixedNamespaces)
throws AppSearchException {
mLogUtil.piiTrace("getStorageInfo, request");
StorageInfoResultProto storageInfoResult = mIcingSearchEngineLocked.getStorageInfo();
mLogUtil.piiTrace(
"getStorageInfo, response", storageInfoResult.getStatus(), storageInfoResult);
checkSuccess(storageInfoResult.getStatus());
if (!storageInfoResult.hasStorageInfo()
|| !storageInfoResult.getStorageInfo().hasDocumentStorageInfo()) {
public StorageInfoProto getRawStorageInfoProto() throws AppSearchException {
mReadWriteLock.readLock().lock();
try {
throwIfClosedLocked();
mLogUtil.piiTrace("getStorageInfo, request");
StorageInfoResultProto storageInfoResult = mIcingSearchEngineLocked.getStorageInfo();
mLogUtil.piiTrace(
"getStorageInfo, response", storageInfoResult.getStatus(), storageInfoResult);
checkSuccess(storageInfoResult.getStatus());
return storageInfoResult.getStorageInfo();
} finally {
mReadWriteLock.readLock().unlock();
}
}
/**
* Extracts and returns {@link StorageInfo} from {@link StorageInfoProto} based on prefixed
* namespaces.
*/
@NonNull
private static StorageInfo getStorageInfoForNamespaces(
@NonNull StorageInfoProto storageInfoProto, @NonNull Set<String> prefixedNamespaces) {
if (!storageInfoProto.hasDocumentStorageInfo()) {
return new StorageInfo.Builder().build();
}
long totalStorageSize = storageInfoResult.getStorageInfo().getTotalStorageSize();
DocumentStorageInfoProto documentStorageInfo =
storageInfoResult.getStorageInfo().getDocumentStorageInfo();
long totalStorageSize = storageInfoProto.getTotalStorageSize();
DocumentStorageInfoProto documentStorageInfo = storageInfoProto.getDocumentStorageInfo();
int totalDocuments =
documentStorageInfo.getNumAliveDocuments()
+ documentStorageInfo.getNumExpiredDocuments();
@@ -1436,6 +1567,7 @@ public final class AppSearchImpl implements Closeable {
String packageName = entry.getKey();
Set<String> databaseNames = entry.getValue();
if (!installedPackages.contains(packageName) && databaseNames != null) {
mDocumentCountMapLocked.remove(packageName);
for (String databaseName : databaseNames) {
String removedPrefix = createPrefix(packageName, databaseName);
mSchemaMapLocked.remove(removedPrefix);
@@ -1468,6 +1600,7 @@ public final class AppSearchImpl implements Closeable {
mOptimizeIntervalCountLocked = 0;
mSchemaMapLocked.clear();
mNamespaceMapLocked.clear();
mDocumentCountMapLocked.clear();
if (initStatsBuilder != null) {
initStatsBuilder
.setHasReset(true)
@@ -1477,6 +1610,26 @@ public final class AppSearchImpl implements Closeable {
checkSuccess(resetResultProto.getStatus());
}
@GuardedBy("mReadWriteLock")
private void rebuildDocumentCountMapLocked(@NonNull StorageInfoProto storageInfoProto) {
mDocumentCountMapLocked.clear();
List<NamespaceStorageInfoProto> namespaceStorageInfoProtoList =
storageInfoProto.getDocumentStorageInfo().getNamespaceStorageInfoList();
for (int i = 0; i < namespaceStorageInfoProtoList.size(); i++) {
NamespaceStorageInfoProto namespaceStorageInfoProto =
namespaceStorageInfoProtoList.get(i);
String packageName = getPackageName(namespaceStorageInfoProto.getNamespace());
Integer oldCount = mDocumentCountMapLocked.get(packageName);
int newCount;
if (oldCount == null) {
newCount = namespaceStorageInfoProto.getNumAliveDocuments();
} else {
newCount = oldCount + namespaceStorageInfoProto.getNumAliveDocuments();
}
mDocumentCountMapLocked.put(packageName, newCount);
}
}
/** Wrapper around schema changes */
@VisibleForTesting
static class RewrittenSchemaResults {

View File

@@ -0,0 +1,57 @@
/*
* 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.
* 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 com.android.server.appsearch.external.localstorage;
/**
* Defines limits placed on users of AppSearch and enforced by {@link AppSearchImpl}.
*
* @hide
*/
public interface LimitConfig {
/**
* The maximum number of bytes a single document is allowed to be.
*
* <p>Enforced at the time of serializing the document into a proto.
*
* <p>This limit has two purposes:
*
* <ol>
* <li>Prevent the system service from using too much memory during indexing or querying by
* capping the size of the data structures it needs to buffer
* <li>Prevent apps from using a very large amount of data by storing exceptionally large
* documents.
* </ol>
*/
int getMaxDocumentSizeBytes();
/**
* The maximum number of documents a single app is allowed to index.
*
* <p>Enforced at indexing time.
*
* <p>This limit has two purposes:
*
* <ol>
* <li>Protect icing lib's docid space from being overwhelmed by a single app. The overall
* docid limit is currently 2^20 (~1 million)
* <li>Prevent apps from using a very large amount of data on the system by storing too many
* documents.
* </ol>
*/
int getMaxDocumentCount();
}

View File

@@ -0,0 +1,37 @@
/*
* 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.
* 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 com.android.server.appsearch.external.localstorage;
/**
* In Jetpack, AppSearch doesn't enforce artificial limits on number of documents or size of
* documents, since the app is the only user of the Icing instance. Icing still enforces a docid
* limit of 1M docs.
*
* @hide
*/
public class UnlimitedLimitConfig implements LimitConfig {
@Override
public int getMaxDocumentSizeBytes() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxDocumentCount() {
return Integer.MAX_VALUE;
}
}

View File

@@ -50,6 +50,10 @@ public class AppSearchConfigTest {
AppSearchConfig.DEFAULT_SAMPLING_INTERVAL);
assertThat(appSearchConfig.getCachedSamplingIntervalForPutDocumentStats()).isEqualTo(
AppSearchConfig.DEFAULT_SAMPLING_INTERVAL);
assertThat(appSearchConfig.getCachedLimitConfigMaxDocumentSizeBytes()).isEqualTo(
AppSearchConfig.DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES);
assertThat(appSearchConfig.getCachedLimitConfigMaxDocumentCount()).isEqualTo(
AppSearchConfig.DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT);
}
@Test
@@ -264,6 +268,22 @@ public class AppSearchConfigTest {
samplingIntervalBatchCallStats);
}
@Test
public void testCustomizedValue() {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
AppSearchConfig.KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES,
Integer.toString(2001),
/*makeDefault=*/ false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
AppSearchConfig.KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT,
Integer.toString(2002),
/*makeDefault=*/ false);
AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
assertThat(appSearchConfig.getCachedLimitConfigMaxDocumentSizeBytes()).isEqualTo(2001);
assertThat(appSearchConfig.getCachedLimitConfigMaxDocumentCount()).isEqualTo(2002);
}
@Test
public void testNotUsable_afterClose() {
AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 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.
* 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 com.android.server.appsearch;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.google.common.truth.Truth.assertThat;
import android.provider.DeviceConfig;
import com.android.server.testables.TestableDeviceConfig;
import org.junit.Rule;
import org.junit.Test;
/**
* Tests for {@link FrameworkLimitConfig}.
*
* <p>Build/Install/Run: atest FrameworksMockingServicesTests:AppSearchConfigTest
*/
public class FrameworkLimitConfigTest {
@Rule
public final TestableDeviceConfig.TestableDeviceConfigRule
mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
@Test
public void testDefaultValues() {
AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
FrameworkLimitConfig config = new FrameworkLimitConfig(appSearchConfig);
assertThat(config.getMaxDocumentSizeBytes()).isEqualTo(
AppSearchConfig.DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES);
assertThat(appSearchConfig.getCachedLimitConfigMaxDocumentCount()).isEqualTo(
AppSearchConfig.DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT);
}
@Test
public void testCustomizedValues() {
AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
FrameworkLimitConfig config = new FrameworkLimitConfig(appSearchConfig);
DeviceConfig.setProperty(
DeviceConfig.NAMESPACE_APPSEARCH,
AppSearchConfig.KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES,
"2001",
/*makeDefault=*/ false);
DeviceConfig.setProperty(
DeviceConfig.NAMESPACE_APPSEARCH,
AppSearchConfig.KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT,
"2002",
/*makeDefault=*/ false);
assertThat(config.getMaxDocumentSizeBytes()).isEqualTo(2001);
assertThat(appSearchConfig.getCachedLimitConfigMaxDocumentCount()).isEqualTo(2002);
}
}

View File

@@ -92,7 +92,10 @@ public class AppSearchImplPlatformTest {
// Give ourselves global query permissions
mAppSearchImpl = AppSearchImpl.create(
mTemporaryFolder.newFolder(), /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
mTemporaryFolder.newFolder(),
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
mVisibilityStore = VisibilityStoreImpl.create(mAppSearchImpl, mContext);
mGlobalQuerierUid =
mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0);

View File

@@ -54,6 +54,7 @@ import com.android.server.appsearch.icing.proto.SchemaTypeConfigProto;
import com.android.server.appsearch.icing.proto.SearchResultProto;
import com.android.server.appsearch.icing.proto.SearchSpecProto;
import com.android.server.appsearch.icing.proto.StatusProto;
import com.android.server.appsearch.icing.proto.StorageInfoProto;
import com.android.server.appsearch.icing.proto.StringIndexingConfig;
import com.android.server.appsearch.icing.proto.TermMatchType;
@@ -85,7 +86,10 @@ public class AppSearchImplTest {
public void setUp() throws Exception {
mAppSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(), /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
mTemporaryFolder.newFolder(),
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
}
/**
@@ -468,7 +472,11 @@ public class AppSearchImplTest {
Context context = ApplicationProvider.getApplicationContext();
File appsearchDir = mTemporaryFolder.newFolder();
AppSearchImpl appSearchImpl =
AppSearchImpl.create(appsearchDir, /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
AppSearchImpl.create(
appsearchDir,
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Insert schema
List<AppSearchSchema> schemas =
@@ -529,7 +537,12 @@ public class AppSearchImplTest {
// Initialize AppSearchImpl. This should cause a reset.
InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
appSearchImpl.close();
appSearchImpl = AppSearchImpl.create(appsearchDir, initStatsBuilder, ALWAYS_OPTIMIZE);
appSearchImpl =
AppSearchImpl.create(
appsearchDir,
new UnlimitedLimitConfig(),
initStatsBuilder,
ALWAYS_OPTIMIZE);
// Check recovery state
InitializeStats initStats = initStatsBuilder.build();
@@ -1688,7 +1701,10 @@ public class AppSearchImplTest {
public void testThrowsExceptionIfClosed() throws Exception {
AppSearchImpl appSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(), /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
mTemporaryFolder.newFolder(),
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Initial check that we could do something at first.
List<AppSearchSchema> schemas =
@@ -1816,7 +1832,11 @@ public class AppSearchImplTest {
// Setup the index
File appsearchDir = mTemporaryFolder.newFolder();
AppSearchImpl appSearchImpl =
AppSearchImpl.create(appsearchDir, /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
AppSearchImpl.create(
appsearchDir,
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
@@ -1843,7 +1863,11 @@ public class AppSearchImplTest {
// That document should be visible even from another instance.
AppSearchImpl appSearchImpl2 =
AppSearchImpl.create(appsearchDir, /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
AppSearchImpl.create(
appsearchDir,
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
getResult =
appSearchImpl2.getDocument(
"package", "database", "namespace1", "id1", Collections.emptyMap());
@@ -1855,7 +1879,11 @@ public class AppSearchImplTest {
// Setup the index
File appsearchDir = mTemporaryFolder.newFolder();
AppSearchImpl appSearchImpl =
AppSearchImpl.create(appsearchDir, /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
AppSearchImpl.create(
appsearchDir,
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
@@ -1906,7 +1934,11 @@ public class AppSearchImplTest {
// Only the second document should be retrievable from another instance.
AppSearchImpl appSearchImpl2 =
AppSearchImpl.create(appsearchDir, /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
AppSearchImpl.create(
appsearchDir,
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
assertThrows(
AppSearchException.class,
() ->
@@ -1927,7 +1959,11 @@ public class AppSearchImplTest {
// Setup the index
File appsearchDir = mTemporaryFolder.newFolder();
AppSearchImpl appSearchImpl =
AppSearchImpl.create(appsearchDir, /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
AppSearchImpl.create(
appsearchDir,
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
@@ -1986,7 +2022,11 @@ public class AppSearchImplTest {
// Only the second document should be retrievable from another instance.
AppSearchImpl appSearchImpl2 =
AppSearchImpl.create(appsearchDir, /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
AppSearchImpl.create(
appsearchDir,
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
assertThrows(
AppSearchException.class,
() ->
@@ -2001,4 +2041,784 @@ public class AppSearchImplTest {
"package", "database", "namespace2", "id2", Collections.emptyMap());
assertThat(getResult).isEqualTo(document2);
}
@Test
public void testGetIcingSearchEngineStorageInfo() throws Exception {
// Setup the index
File appsearchDir = mTemporaryFolder.newFolder();
AppSearchImpl appSearchImpl =
AppSearchImpl.create(
appsearchDir,
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
appSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Add two documents
GenericDocument document1 =
new GenericDocument.Builder<>("namespace1", "id1", "type").build();
appSearchImpl.putDocument("package", "database", document1, /*logger=*/ null);
GenericDocument document2 =
new GenericDocument.Builder<>("namespace1", "id2", "type").build();
appSearchImpl.putDocument("package", "database", document2, /*logger=*/ null);
StorageInfoProto storageInfo = appSearchImpl.getRawStorageInfoProto();
// Simple checks to verify if we can get correct StorageInfoProto from IcingSearchEngine
// No need to cover all the fields
assertThat(storageInfo.getTotalStorageSize()).isGreaterThan(0);
assertThat(storageInfo.getDocumentStorageInfo().getNumAliveDocuments()).isEqualTo(2);
assertThat(storageInfo.getSchemaStoreStorageInfo().getNumSchemaTypes()).isEqualTo(1);
}
@Test
public void testLimitConfig_DocumentSize() throws Exception {
// Create a new mAppSearchImpl with a lower limit
mAppSearchImpl.close();
mAppSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return 80;
}
@Override
public int getMaxDocumentCount() {
return 1;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Insert schema
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
mAppSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert a document which is too large
GenericDocument document =
new GenericDocument.Builder<>(
"this_namespace_is_long_to_make_the_doc_big", "id", "type")
.build();
AppSearchException e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains(
"Document \"id\" for package \"package\" serialized to 99 bytes, which"
+ " exceeds limit of 80 bytes");
// Make sure this failure didn't increase our document count. We should still be able to
// index 1 document.
GenericDocument document2 =
new GenericDocument.Builder<>("namespace", "id2", "type").build();
mAppSearchImpl.putDocument("package", "database", document2, /*logger=*/ null);
// Now we should get a failure
GenericDocument document3 =
new GenericDocument.Builder<>("namespace", "id3", "type").build();
e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document3, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 1 documents");
}
@Test
public void testLimitConfig_Init() throws Exception {
// Create a new mAppSearchImpl with a lower limit
mAppSearchImpl.close();
File tempFolder = mTemporaryFolder.newFolder();
mAppSearchImpl =
AppSearchImpl.create(
tempFolder,
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return 80;
}
@Override
public int getMaxDocumentCount() {
return 1;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Insert schema
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
mAppSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Index a document
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id1", "type").build(),
/*logger=*/ null);
// Now we should get a failure
GenericDocument document2 =
new GenericDocument.Builder<>("namespace", "id2", "type").build();
AppSearchException e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document2, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 1 documents");
// Close and reinitialize AppSearchImpl
mAppSearchImpl.close();
mAppSearchImpl =
AppSearchImpl.create(
tempFolder,
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return 80;
}
@Override
public int getMaxDocumentCount() {
return 1;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Make sure the limit is maintained
e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document2, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 1 documents");
}
@Test
public void testLimitConfig_Remove() throws Exception {
// Create a new mAppSearchImpl with a lower limit
mAppSearchImpl.close();
mAppSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxDocumentCount() {
return 3;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Insert schema
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
mAppSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Index 3 documents
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id1", "type").build(),
/*logger=*/ null);
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id2", "type").build(),
/*logger=*/ null);
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id3", "type").build(),
/*logger=*/ null);
// Now we should get a failure
GenericDocument document4 =
new GenericDocument.Builder<>("namespace", "id4", "type").build();
AppSearchException e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document4, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 3 documents");
// Remove a document that doesn't exist
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.remove(
"package",
"database",
"namespace",
"id4",
/*removeStatsBuilder=*/ null));
// Should still fail
e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document4, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 3 documents");
// Remove a document that does exist
mAppSearchImpl.remove(
"package", "database", "namespace", "id2", /*removeStatsBuilder=*/ null);
// Now doc4 should work
mAppSearchImpl.putDocument("package", "database", document4, /*logger=*/ null);
// The next one should fail again
e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id5", "type")
.build(),
/*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 3 documents");
}
@Test
public void testLimitConfig_DifferentPackages() throws Exception {
// Create a new mAppSearchImpl with a lower limit
mAppSearchImpl.close();
File tempFolder = mTemporaryFolder.newFolder();
mAppSearchImpl =
AppSearchImpl.create(
tempFolder,
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxDocumentCount() {
return 2;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Insert schema
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
mAppSearchImpl.setSchema(
"package1",
"database1",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
mAppSearchImpl.setSchema(
"package1",
"database2",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
mAppSearchImpl.setSchema(
"package2",
"database1",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
mAppSearchImpl.setSchema(
"package2",
"database2",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Index documents in package1/database1
mAppSearchImpl.putDocument(
"package1",
"database1",
new GenericDocument.Builder<>("namespace", "id1", "type").build(),
/*logger=*/ null);
mAppSearchImpl.putDocument(
"package1",
"database2",
new GenericDocument.Builder<>("namespace", "id2", "type").build(),
/*logger=*/ null);
// Indexing a third doc into package1 should fail (here we use database3)
AppSearchException e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package1",
"database3",
new GenericDocument.Builder<>("namespace", "id3", "type")
.build(),
/*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package1\" exceeded limit of 2 documents");
// Indexing a doc into package2 should succeed
mAppSearchImpl.putDocument(
"package2",
"database1",
new GenericDocument.Builder<>("namespace", "id1", "type").build(),
/*logger=*/ null);
// Reinitialize to make sure packages are parsed correctly on init
mAppSearchImpl.close();
mAppSearchImpl =
AppSearchImpl.create(
tempFolder,
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxDocumentCount() {
return 2;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// package1 should still be out of space
e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package1",
"database4",
new GenericDocument.Builder<>("namespace", "id4", "type")
.build(),
/*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package1\" exceeded limit of 2 documents");
// package2 has room for one more
mAppSearchImpl.putDocument(
"package2",
"database2",
new GenericDocument.Builder<>("namespace", "id2", "type").build(),
/*logger=*/ null);
// now package2 really is out of space
e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package2",
"database3",
new GenericDocument.Builder<>("namespace", "id3", "type")
.build(),
/*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package2\" exceeded limit of 2 documents");
}
@Test
public void testLimitConfig_RemoveByQyery() throws Exception {
// Create a new mAppSearchImpl with a lower limit
mAppSearchImpl.close();
mAppSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxDocumentCount() {
return 3;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Insert schema
List<AppSearchSchema> schemas =
Collections.singletonList(
new AppSearchSchema.Builder("type")
.addProperty(
new AppSearchSchema.StringPropertyConfig.Builder("body")
.setIndexingType(
AppSearchSchema.StringPropertyConfig
.INDEXING_TYPE_PREFIXES)
.setTokenizerType(
AppSearchSchema.StringPropertyConfig
.TOKENIZER_TYPE_PLAIN)
.build())
.build());
mAppSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Index 3 documents
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id1", "type")
.setPropertyString("body", "tablet")
.build(),
/*logger=*/ null);
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id2", "type")
.setPropertyString("body", "tabby")
.build(),
/*logger=*/ null);
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id3", "type")
.setPropertyString("body", "grabby")
.build(),
/*logger=*/ null);
// Now we should get a failure
GenericDocument document4 =
new GenericDocument.Builder<>("namespace", "id4", "type").build();
AppSearchException e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document4, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 3 documents");
// Run removebyquery, deleting nothing
mAppSearchImpl.removeByQuery(
"package",
"database",
"nothing",
new SearchSpec.Builder().build(),
/*removeStatsBuilder=*/ null);
// Should still fail
e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document4, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 3 documents");
// Remove "tab*"
mAppSearchImpl.removeByQuery(
"package",
"database",
"tab",
new SearchSpec.Builder().build(),
/*removeStatsBuilder=*/ null);
// Now doc4 and doc5 should work
mAppSearchImpl.putDocument("package", "database", document4, /*logger=*/ null);
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id5", "type").build(),
/*logger=*/ null);
// We only deleted 2 docs so the next one should fail again
e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id6", "type")
.build(),
/*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 3 documents");
}
@Test
public void testLimitConfig_Replace() throws Exception {
// Create a new mAppSearchImpl with a lower limit
mAppSearchImpl.close();
mAppSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxDocumentCount() {
return 2;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Insert schema
List<AppSearchSchema> schemas =
Collections.singletonList(
new AppSearchSchema.Builder("type")
.addProperty(
new AppSearchSchema.StringPropertyConfig.Builder("body")
.build())
.build());
mAppSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Index a document
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id1", "type")
.setPropertyString("body", "id1.orig")
.build(),
/*logger=*/ null);
// Replace it with another doc
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id1", "type")
.setPropertyString("body", "id1.new")
.build(),
/*logger=*/ null);
// Index id2. This should pass but only because we check for replacements.
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id2", "type").build(),
/*logger=*/ null);
// Now we should get a failure on id3
GenericDocument document3 =
new GenericDocument.Builder<>("namespace", "id3", "type").build();
AppSearchException e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document3, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 2 documents");
}
@Test
public void testLimitConfig_ReplaceReinit() throws Exception {
// Create a new mAppSearchImpl with a lower limit
mAppSearchImpl.close();
File tempFolder = mTemporaryFolder.newFolder();
mAppSearchImpl =
AppSearchImpl.create(
tempFolder,
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxDocumentCount() {
return 2;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Insert schema
List<AppSearchSchema> schemas =
Collections.singletonList(
new AppSearchSchema.Builder("type")
.addProperty(
new AppSearchSchema.StringPropertyConfig.Builder("body")
.build())
.build());
mAppSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Index a document
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id1", "type")
.setPropertyString("body", "id1.orig")
.build(),
/*logger=*/ null);
// Replace it with another doc
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id1", "type")
.setPropertyString("body", "id1.new")
.build(),
/*logger=*/ null);
// Reinitialize to make sure replacements are correctly accounted for by init
mAppSearchImpl.close();
mAppSearchImpl =
AppSearchImpl.create(
tempFolder,
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxDocumentCount() {
return 2;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Index id2. This should pass but only because we check for replacements.
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id2", "type").build(),
/*logger=*/ null);
// Now we should get a failure on id3
GenericDocument document3 =
new GenericDocument.Builder<>("namespace", "id3", "type").build();
AppSearchException e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document3, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 2 documents");
}
}

View File

@@ -67,7 +67,10 @@ public class AppSearchLoggerTest {
public void setUp() throws Exception {
mAppSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(), /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
mTemporaryFolder.newFolder(),
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
mLogger = new TestLogger();
}
@@ -290,7 +293,11 @@ public class AppSearchLoggerTest {
public void testLoggingStats_initializeWithoutDocuments_success() throws Exception {
// Create an unused AppSearchImpl to generated an InitializeStats.
InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
AppSearchImpl.create(mTemporaryFolder.newFolder(), initStatsBuilder, ALWAYS_OPTIMIZE);
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
new UnlimitedLimitConfig(),
initStatsBuilder,
ALWAYS_OPTIMIZE);
InitializeStats iStats = initStatsBuilder.build();
assertThat(iStats).isNotNull();
@@ -314,7 +321,11 @@ public class AppSearchLoggerTest {
final File folder = mTemporaryFolder.newFolder();
AppSearchImpl appSearchImpl =
AppSearchImpl.create(folder, /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
AppSearchImpl.create(
folder,
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
List<AppSearchSchema> schemas =
ImmutableList.of(
new AppSearchSchema.Builder("Type1").build(),
@@ -336,7 +347,7 @@ public class AppSearchLoggerTest {
// Create another appsearchImpl on the same folder
InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
AppSearchImpl.create(folder, initStatsBuilder, ALWAYS_OPTIMIZE);
AppSearchImpl.create(folder, new UnlimitedLimitConfig(), initStatsBuilder, ALWAYS_OPTIMIZE);
InitializeStats iStats = initStatsBuilder.build();
assertThat(iStats).isNotNull();
@@ -360,7 +371,11 @@ public class AppSearchLoggerTest {
final File folder = mTemporaryFolder.newFolder();
AppSearchImpl appSearchImpl =
AppSearchImpl.create(folder, /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
AppSearchImpl.create(
folder,
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
List<AppSearchSchema> schemas =
ImmutableList.of(
@@ -393,7 +408,7 @@ public class AppSearchLoggerTest {
// Create another appsearchImpl on the same folder
InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
AppSearchImpl.create(folder, initStatsBuilder, ALWAYS_OPTIMIZE);
AppSearchImpl.create(folder, new UnlimitedLimitConfig(), initStatsBuilder, ALWAYS_OPTIMIZE);
InitializeStats iStats = initStatsBuilder.build();
// Some of other fields are already covered by AppSearchImplTest#testReset()
@@ -484,11 +499,13 @@ public class AppSearchLoggerTest {
.setPropertyString("nonExist", "testPut example1")
.build();
// We mainly want to check the status code in stats. So we don't need to inspect the
// exception here.
Assert.assertThrows(
AppSearchException.class,
() -> mAppSearchImpl.putDocument(testPackageName, testDatabase, document, mLogger));
AppSearchException exception =
Assert.assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
testPackageName, testDatabase, document, mLogger));
assertThat(exception.getResultCode()).isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
PutDocumentStats pStats = mLogger.mPutDocumentStats;
assertThat(pStats).isNotNull();
@@ -676,17 +693,17 @@ public class AppSearchLoggerTest {
RemoveStats.Builder rStatsBuilder = new RemoveStats.Builder(testPackageName, testDatabase);
// We mainly want to check the status code in stats. So we don't need to inspect the
// exception here.
Assert.assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.remove(
testPackageName,
testDatabase,
testNamespace,
"invalidId",
rStatsBuilder));
AppSearchException exception =
Assert.assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.remove(
testPackageName,
testDatabase,
testNamespace,
"invalidId",
rStatsBuilder));
assertThat(exception.getResultCode()).isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
RemoveStats rStats = rStatsBuilder.build();
assertThat(rStats.getPackageName()).isEqualTo(testPackageName);

View File

@@ -38,6 +38,7 @@ import androidx.test.core.app.ApplicationProvider;
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
import com.android.server.appsearch.external.localstorage.OptimizeStrategy;
import com.android.server.appsearch.external.localstorage.UnlimitedLimitConfig;
import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore;
@@ -88,7 +89,10 @@ public class VisibilityStoreImplTest {
// Give ourselves global query permissions
AppSearchImpl appSearchImpl = AppSearchImpl.create(
mTemporaryFolder.newFolder(), /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
mTemporaryFolder.newFolder(),
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
mVisibilityStore = VisibilityStoreImpl.create(appSearchImpl, mContext);
mUid = mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0);
}