blob: 42b9505acc7e6c262cb17b4dec09c9b1faafa4de [file] [log] [blame]
/*
* Copyright (C) 2022 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.contactsindexer;
import static com.google.common.truth.Truth.assertThat;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchSessionShim;
import android.app.appsearch.SetSchemaRequest;
import android.app.appsearch.testutil.AppSearchSessionShimImpl;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.test.ProviderTestCase2;
import android.util.Pair;
import androidx.test.core.app.ApplicationProvider;
import com.android.server.appsearch.contactsindexer.ContactsIndexerImpl.ContactsBatcher;
import com.android.server.appsearch.contactsindexer.appsearchtypes.Person;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
public class ContactsIndexerImplTest extends ProviderTestCase2<FakeContactsProvider> {
// TODO(b/203605504) we could just use AppSearchHelper.
private FakeAppSearchHelper mAppSearchHelper;
private ContactsUpdateStats mUpdateStats;
public ContactsIndexerImplTest() {
super(FakeContactsProvider.class, FakeContactsProvider.AUTHORITY);
}
@Override
public void setUp() throws Exception {
super.setUp();
Context context = ApplicationProvider.getApplicationContext();
mContext = new ContextWrapper(context) {
@Override
public ContentResolver getContentResolver() {
return getMockContentResolver();
}
};
mAppSearchHelper = new FakeAppSearchHelper(mContext);
mUpdateStats = new ContactsUpdateStats();
}
@Override
public void tearDown() throws Exception {
// Wipe the data in AppSearchHelper.DATABASE_NAME.
AppSearchManager.SearchContext searchContext =
new AppSearchManager.SearchContext.Builder(AppSearchHelper.DATABASE_NAME).build();
AppSearchSessionShim db = AppSearchSessionShimImpl.createSearchSessionAsync(
searchContext).get();
SetSchemaRequest setSchemaRequest = new SetSchemaRequest.Builder()
.setForceOverride(true).build();
db.setSchemaAsync(setSchemaRequest).get();
}
/**
* Helper method to run a delta update in the test.
*
* <p> Get is called on the futures to make this helper method synchronous.
*
* @param lastUpdatedTimestamp used as the "since" filter for updating the contacts.
* @param lastDeletedTimestamp used as the "since" filter for deleting the contacts.
* @return new (lastUpdatedTimestamp, lastDeletedTimestamp) pair after the update and deletion.
*/
private Pair<Long, Long> runDeltaUpdateOnContactsIndexerImpl(
@NonNull ContactsIndexerImpl indexerImpl,
long lastUpdatedTimestamp,
long lastDeletedTimestamp,
@NonNull ContactsUpdateStats updateStats)
throws ExecutionException, InterruptedException {
Objects.requireNonNull(indexerImpl);
Objects.requireNonNull(updateStats);
List<String> wantedContactIds = new ArrayList<>();
List<String> unWantedContactIds = new ArrayList<>();
lastUpdatedTimestamp = ContactsProviderUtil.getUpdatedContactIds(mContext,
lastUpdatedTimestamp, ContactsProviderUtil.UPDATE_LIMIT_NONE,
wantedContactIds, /*stats=*/ null);
lastDeletedTimestamp = ContactsProviderUtil.getDeletedContactIds(mContext,
lastDeletedTimestamp, unWantedContactIds, /*stats=*/ null);
indexerImpl.updatePersonCorpusAsync(wantedContactIds, unWantedContactIds,
updateStats).get();
return new Pair<>(lastUpdatedTimestamp, lastDeletedTimestamp);
}
public void testBatcher_noFlushBeforeReachingLimit() throws Exception {
int batchSize = 5;
ContactsBatcher batcher = new ContactsBatcher(mAppSearchHelper, batchSize);
for (int i = 0; i < batchSize - 1; ++i) {
batcher.add(new PersonBuilderHelper(/*id=*/ String.valueOf(i),
new Person.Builder("namespace", /*id=*/ String.valueOf(i), /*name=*/
String.valueOf(i))).setCreationTimestampMillis(0), mUpdateStats);
}
batcher.getCompositeFuture().get();
assertThat(mAppSearchHelper.mIndexedContacts).isEmpty();
}
public void testBatcher_autoFlush() throws Exception {
int batchSize = 5;
ContactsBatcher batcher = new ContactsBatcher(mAppSearchHelper, batchSize);
for (int i = 0; i < batchSize; ++i) {
batcher.add(
new PersonBuilderHelper(
/*id=*/ String.valueOf(i),
new Person.Builder("namespace", /*id=*/ String.valueOf(i), /*name=*/
String.valueOf(i))
).setCreationTimestampMillis(0), mUpdateStats);
}
batcher.getCompositeFuture().get();
assertThat(mAppSearchHelper.mIndexedContacts).hasSize(batchSize);
assertThat(batcher.getPendingDiffContactsCount()).isEqualTo(0);
assertThat(batcher.getPendingIndexContactsCount()).isEqualTo(0);
}
public void testBatcher_contactFingerprintSame_notIndexed() throws Exception {
int batchSize = 2;
ContactsBatcher batcher = new ContactsBatcher(mAppSearchHelper, batchSize);
PersonBuilderHelper builderHelper1 = new PersonBuilderHelper("id1",
new Person.Builder("namespace", "id1", "name1")
.setGivenName("given1")
).setCreationTimestampMillis(0);
PersonBuilderHelper builderHelper2 = new PersonBuilderHelper("id2",
new Person.Builder("namespace", "id2", "name2")
.setGivenName("given2")
).setCreationTimestampMillis(0);
mAppSearchHelper.setExistingContacts(ImmutableList.of(builderHelper1.buildPerson(),
builderHelper2.buildPerson()));
// Try to add the same contacts
batcher.add(builderHelper1, mUpdateStats);
batcher.add(builderHelper2, mUpdateStats);
batcher.getCompositeFuture().get();
assertThat(mAppSearchHelper.mIndexedContacts).isEmpty();
assertThat(batcher.getPendingDiffContactsCount()).isEqualTo(0);
assertThat(batcher.getPendingIndexContactsCount()).isEqualTo(0);
}
public void testBatcher_contactFingerprintDifferent_notIndexedButBatched() throws Exception {
int batchSize = 2;
ContactsBatcher batcher = new ContactsBatcher(mAppSearchHelper, batchSize);
PersonBuilderHelper builderHelper1 = new PersonBuilderHelper("id1",
new Person.Builder("namespace", "id1", "name1")
.setGivenName("given1")
).setCreationTimestampMillis(0);
PersonBuilderHelper builderHelper2 = new PersonBuilderHelper("id2",
new Person.Builder("namespace", "id2", "name2")
.setGivenName("given2")
).setCreationTimestampMillis(0);
mAppSearchHelper.setExistingContacts(
ImmutableList.of(builderHelper1.buildPerson(), builderHelper2.buildPerson()));
PersonBuilderHelper sameAsContact1 = builderHelper1;
// use toBuilder once it works. Now it is not found due to @hide and not sure how since
// the test does depend on framework-appsearch.impl.
PersonBuilderHelper notSameAsContact2 = new PersonBuilderHelper("id2",
new Person.Builder("namespace", "id2", "name2").setGivenName(
"given2diff")
).setCreationTimestampMillis(0);
batcher.add(sameAsContact1, mUpdateStats);
batcher.add(notSameAsContact2, mUpdateStats);
batcher.getCompositeFuture().get();
assertThat(mAppSearchHelper.mIndexedContacts).isEmpty();
assertThat(batcher.getPendingDiffContactsCount()).isEqualTo(0);
assertThat(batcher.getPendingIndexContactsCount()).isEqualTo(1);
}
public void testBatcher_contactFingerprintDifferent_Indexed() throws Exception {
int batchSize = 2;
ContactsBatcher batcher = new ContactsBatcher(mAppSearchHelper, batchSize);
PersonBuilderHelper contact1 = new PersonBuilderHelper("id1",
new Person.Builder("namespace", "id1", "name1")
.setGivenName("given1")
).setCreationTimestampMillis(0);
PersonBuilderHelper contact2 = new PersonBuilderHelper("id2",
new Person.Builder("namespace", "id2", "name2")
.setGivenName("given2")
).setCreationTimestampMillis(0);
PersonBuilderHelper contact3 = new PersonBuilderHelper("id3",
new Person.Builder("namespace", "id3", "name3")
.setGivenName("given3")
).setCreationTimestampMillis(0);
mAppSearchHelper.setExistingContacts(
ImmutableList.of(contact1.buildPerson(), contact2.buildPerson(),
contact3.buildPerson()));
PersonBuilderHelper sameAsContact1 = contact1;
// use toBuilder once it works. Now it is not found due to @hide and not sure how since
// the test does depend on framework-appsearch.impl.
PersonBuilderHelper notSameAsContact2 = new PersonBuilderHelper("id2",
new Person.Builder("namespace", "id2", "name2").setGivenName(
"given2diff")
).setCreationTimestampMillis(0);
PersonBuilderHelper notSameAsContact3 = new PersonBuilderHelper("id3",
new Person.Builder("namespace", "id3", "name3").setGivenName(
"given3diff")
).setCreationTimestampMillis(0);
batcher.add(sameAsContact1, mUpdateStats);
batcher.add(notSameAsContact2, mUpdateStats);
batcher.add(notSameAsContact3, mUpdateStats);
batcher.getCompositeFuture().get();
assertThat(mAppSearchHelper.mIndexedContacts).isEmpty();
assertThat(batcher.getPendingDiffContactsCount()).isEqualTo(1);
assertThat(batcher.getPendingIndexContactsCount()).isEqualTo(1);
batcher.flushAsync(mUpdateStats).get();
assertThat(mAppSearchHelper.mIndexedContacts).containsExactly(
notSameAsContact2.buildPerson(),
notSameAsContact3.buildPerson());
assertThat(batcher.getPendingDiffContactsCount()).isEqualTo(0);
assertThat(batcher.getPendingIndexContactsCount()).isEqualTo(0);
}
public void testBatcher_contactFingerprintDifferent_IndexedWithOriginalCreationTimestamp()
throws Exception {
int batchSize = 2;
long originalTs = System.currentTimeMillis();
ContactsBatcher batcher = new ContactsBatcher(mAppSearchHelper, batchSize);
PersonBuilderHelper contact1 = new PersonBuilderHelper("id1",
new Person.Builder("namespace", "id1", "name1")
.setGivenName("given1")
).setCreationTimestampMillis(originalTs);
PersonBuilderHelper contact2 = new PersonBuilderHelper("id2",
new Person.Builder("namespace", "id2", "name2")
.setGivenName("given2")
).setCreationTimestampMillis(originalTs);
PersonBuilderHelper contact3 = new PersonBuilderHelper("id3",
new Person.Builder("namespace", "id3", "name3")
.setGivenName("given3")
).setCreationTimestampMillis(originalTs);
mAppSearchHelper.setExistingContacts(
ImmutableList.of(contact1.buildPerson(), contact2.buildPerson(),
contact3.buildPerson()));
long updatedTs1 = originalTs + 1;
long updatedTs2 = originalTs + 2;
long updatedTs3 = originalTs + 3;
PersonBuilderHelper sameAsContact1 = new PersonBuilderHelper("id1",
new Person.Builder("namespace", "id1", "name1")
.setGivenName("given1")
).setCreationTimestampMillis(updatedTs1);
// use toBuilder once it works. Now it is not found due to @hide and not sure how since
// the test does depend on framework-appsearch.impl.
PersonBuilderHelper notSameAsContact2 = new PersonBuilderHelper("id2",
new Person.Builder("namespace", "id2", "name2").setGivenName(
"given2diff")
).setCreationTimestampMillis(updatedTs2);
PersonBuilderHelper notSameAsContact3 = new PersonBuilderHelper("id3",
new Person.Builder("namespace", "id3", "name3").setGivenName(
"given3diff")
).setCreationTimestampMillis(updatedTs3);
assertThat(sameAsContact1.buildPerson().getCreationTimestampMillis()).isEqualTo(updatedTs1);
assertThat(notSameAsContact2.buildPerson().getCreationTimestampMillis()).isEqualTo(
updatedTs2);
assertThat(notSameAsContact3.buildPerson().getCreationTimestampMillis()).isEqualTo(
updatedTs3);
batcher.add(sameAsContact1, mUpdateStats);
batcher.add(notSameAsContact2, mUpdateStats);
batcher.add(notSameAsContact3, mUpdateStats);
batcher.flushAsync(mUpdateStats).get();
assertThat(mAppSearchHelper.mIndexedContacts).hasSize(2);
assertThat(mAppSearchHelper.mExistingContacts.get(
"id1").getGivenName()).isEqualTo("given1");
assertThat(mAppSearchHelper.mExistingContacts.get(
"id2").getGivenName()).isEqualTo("given2diff");
assertThat(mAppSearchHelper.mExistingContacts.get(
"id3").getGivenName()).isEqualTo("given3diff");
// But the timestamps remain same.
assertThat(mAppSearchHelper.mExistingContacts.get(
"id1").getCreationTimestampMillis()).isEqualTo(originalTs);
assertThat(mAppSearchHelper.mExistingContacts.get(
"id2").getCreationTimestampMillis()).isEqualTo(originalTs);
assertThat(mAppSearchHelper.mExistingContacts.get(
"id3").getCreationTimestampMillis()).isEqualTo(originalTs);
}
public void testBatcher_contactNew_notIndexedButBatched() throws Exception {
int batchSize = 2;
ContactsBatcher batcher = new ContactsBatcher(mAppSearchHelper, batchSize);
PersonBuilderHelper contact1 = new PersonBuilderHelper("id1",
new Person.Builder("namespace", "id1", "name1")
.setGivenName("given1")
).setCreationTimestampMillis(0);
mAppSearchHelper.setExistingContacts(ImmutableList.of(contact1.buildPerson()));
PersonBuilderHelper sameAsContact1 = contact1;
// use toBuilder once it works. Now it is not found due to @hide and not sure how since
// the test does depend on framework-appsearch.impl.
PersonBuilderHelper newContact = new PersonBuilderHelper("id2",
new Person.Builder("namespace", "id2", "name2").setGivenName(
"given2diff")
).setCreationTimestampMillis(0);
batcher.add(sameAsContact1, mUpdateStats);
batcher.add(newContact, mUpdateStats);
batcher.getCompositeFuture().get();
assertThat(mAppSearchHelper.mIndexedContacts).isEmpty();
assertThat(batcher.getPendingDiffContactsCount()).isEqualTo(0);
assertThat(batcher.getPendingIndexContactsCount()).isEqualTo(1);
}
public void testBatcher_contactNew_indexed() throws Exception {
int batchSize = 2;
ContactsBatcher batcher = new ContactsBatcher(mAppSearchHelper, batchSize);
PersonBuilderHelper contact1 = new PersonBuilderHelper("id1",
new Person.Builder("namespace", "id1", "name1")
.setGivenName("given1")).setCreationTimestampMillis(0);
mAppSearchHelper.setExistingContacts(ImmutableList.of(contact1.buildPerson()));
PersonBuilderHelper sameAsContact1 = contact1;
// use toBuilder once it works. Now it is not found due to @hide and not sure how since
// the test does depend on framework-appsearch.impl.
PersonBuilderHelper newContact1 = new PersonBuilderHelper("id2",
new Person.Builder("namespace", "id2", "name2").setGivenName(
"given2diff")
).setCreationTimestampMillis(0);
PersonBuilderHelper newContact2 = new PersonBuilderHelper("id3",
new Person.Builder("namespace", "id3", "name3").setGivenName(
"given3diff")
).setCreationTimestampMillis(0);
batcher.add(sameAsContact1, mUpdateStats);
batcher.add(newContact1, mUpdateStats);
batcher.add(newContact2, mUpdateStats);
batcher.getCompositeFuture().get();
assertThat(mAppSearchHelper.mIndexedContacts).isEmpty();
assertThat(batcher.getPendingDiffContactsCount()).isEqualTo(1);
assertThat(batcher.getPendingIndexContactsCount()).isEqualTo(1);
batcher.flushAsync(mUpdateStats).get();
assertThat(mAppSearchHelper.mIndexedContacts).containsExactly(newContact1.buildPerson(),
newContact2.buildPerson());
assertThat(batcher.getPendingDiffContactsCount()).isEqualTo(0);
assertThat(batcher.getPendingIndexContactsCount()).isEqualTo(0);
}
public void testBatcher_batchedContactClearedAfterFlush() throws Exception {
int batchSize = 5;
ContactsBatcher batcher = new ContactsBatcher(mAppSearchHelper, batchSize);
// First batch
for (int i = 0; i < batchSize; ++i) {
batcher.add(new PersonBuilderHelper(/*id=*/ String.valueOf(i),
new Person.Builder("namespace", /*id=*/ String.valueOf(i), /*name=*/
String.valueOf(i))
).setCreationTimestampMillis(0), mUpdateStats);
}
batcher.getCompositeFuture().get();
assertThat(mAppSearchHelper.mIndexedContacts).hasSize(batchSize);
assertThat(batcher.getPendingDiffContactsCount()).isEqualTo(0);
assertThat(batcher.getPendingIndexContactsCount()).isEqualTo(0);
mAppSearchHelper.mIndexedContacts.clear();
// Second batch. Make sure the first batch has been cleared.
for (int i = 0; i < batchSize; ++i) {
batcher.add(new PersonBuilderHelper(/*id=*/ String.valueOf(i),
new Person.Builder("namespace", /*id=*/ String.valueOf(i), /*name=*/
String.valueOf(i))
// Different from previous ones to bypass the fingerprinting.
.addNote("note")
).setCreationTimestampMillis(0), mUpdateStats);
}
batcher.getCompositeFuture().get();
assertThat(mAppSearchHelper.mIndexedContacts).hasSize(batchSize);
assertThat(batcher.getPendingDiffContactsCount()).isEqualTo(0);
assertThat(batcher.getPendingIndexContactsCount()).isEqualTo(0);
}
public void testContactsIndexerImpl_batchRemoveContacts_largerThanBatchSize() throws Exception {
ContactsIndexerImpl contactsIndexerImpl = new ContactsIndexerImpl(mContext,
mAppSearchHelper);
int totalNum = ContactsIndexerImpl.NUM_DELETED_CONTACTS_PER_BATCH_FOR_APPSEARCH + 1;
List<String> removedIds = new ArrayList<>(totalNum);
for (int i = 0; i < totalNum; ++i) {
removedIds.add(String.valueOf(i));
}
contactsIndexerImpl.batchRemoveContactsAsync(removedIds, mUpdateStats).get();
assertThat(mAppSearchHelper.mRemovedIds).hasSize(removedIds.size());
assertThat(mAppSearchHelper.mRemovedIds).isEqualTo(removedIds);
}
public void testContactsIndexerImpl_batchRemoveContacts_smallerThanBatchSize()
throws Exception {
ContactsIndexerImpl contactsIndexerImpl = new ContactsIndexerImpl(mContext,
mAppSearchHelper);
int totalNum = ContactsIndexerImpl.NUM_DELETED_CONTACTS_PER_BATCH_FOR_APPSEARCH - 1;
List<String> removedIds = new ArrayList<>(totalNum);
for (int i = 0; i < totalNum; ++i) {
removedIds.add(String.valueOf(i));
}
contactsIndexerImpl.batchRemoveContactsAsync(removedIds, mUpdateStats).get();
assertThat(mAppSearchHelper.mRemovedIds).hasSize(removedIds.size());
assertThat(mAppSearchHelper.mRemovedIds).isEqualTo(removedIds);
}
}