Loading apex/appsearch/service/Android.bp +3 −0 Original line number Diff line number Diff line Loading @@ -21,4 +21,7 @@ java_library { "framework", "services.core", ], static_libs: [ "icing-java-proto-lite", ] } apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java +53 −29 Original line number Diff line number Diff line Loading @@ -20,9 +20,11 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; import android.util.SparseArray; import com.google.android.icing.proto.DocumentProto; import com.google.android.icing.proto.PropertyProto; import java.util.ArrayList; import java.util.Collections; import java.util.List; Loading @@ -42,18 +44,19 @@ import java.util.concurrent.atomic.AtomicInteger; public class FakeIcing { private final AtomicInteger mNextDocId = new AtomicInteger(); private final Map<String, Integer> mUriToDocIdMap = new ArrayMap<>(); /** Array of Documents (pair of uri and content) where index into the array is the docId. */ private final SparseArray<Pair<String, String>> mDocStore = new SparseArray<>(); /** Array of Documents where index into the array is the docId. */ private final SparseArray<DocumentProto> mDocStore = new SparseArray<>(); /** Map of term to posting-list (the set of DocIds containing that term). */ private final Map<String, Set<Integer>> mIndex = new ArrayMap<>(); /** * Inserts a document into the index. * * @param uri The globally unique identifier of the document. * @param doc The contents of the document. * @param document The document to insert. */ public void put(@NonNull String uri, @NonNull String doc) { public void put(@NonNull DocumentProto document) { String uri = document.getUri(); // Update mDocIdMap Integer docId = mUriToDocIdMap.get(uri); if (docId != null) { Loading @@ -66,18 +69,10 @@ public class FakeIcing { mUriToDocIdMap.put(uri, docId); // Update mDocStore mDocStore.put(docId, Pair.create(uri, doc)); mDocStore.put(docId, document); // Update mIndex String[] words = normalizeString(doc).split("\\s+"); for (String word : words) { Set<Integer> postingList = mIndex.get(word); if (postingList == null) { postingList = new ArraySet<>(); mIndex.put(word, postingList); } postingList.add(docId); } indexDocument(docId, document); } /** Loading @@ -87,39 +82,35 @@ public class FakeIcing { * @return The body of the document, or {@code null} if no such document exists. */ @Nullable public String get(@NonNull String uri) { public DocumentProto get(@NonNull String uri) { Integer docId = mUriToDocIdMap.get(uri); if (docId == null) { return null; } Pair<String, String> record = mDocStore.get(docId); if (record == null) { return null; } return record.second; return mDocStore.get(docId); } /** * Returns documents containing the given term. * * @param term A single exact term to look up in the index. * @return The URIs of the matching documents, or an empty {@code List} if no documents match. * @return The matching documents, or an empty {@code List} if no documents match. */ @NonNull public List<String> query(@NonNull String term) { public List<DocumentProto> query(@NonNull String term) { String normTerm = normalizeString(term); Set<Integer> docIds = mIndex.get(normTerm); if (docIds == null || docIds.isEmpty()) { return Collections.emptyList(); } List<String> uris = new ArrayList<>(docIds.size()); List<DocumentProto> matches = new ArrayList<>(docIds.size()); for (int docId : docIds) { Pair<String, String> record = mDocStore.get(docId); if (record != null) { uris.add(record.first); DocumentProto document = mDocStore.get(docId); if (document != null) { matches.add(document); } } return uris; return matches; } /** Loading @@ -137,6 +128,39 @@ public class FakeIcing { } } private void indexDocument(int docId, DocumentProto document) { for (PropertyProto property : document.getPropertiesList()) { for (String stringValue : property.getStringValuesList()) { String[] words = normalizeString(stringValue).split("\\s+"); for (String word : words) { indexTerm(docId, word); } } for (Long longValue : property.getInt64ValuesList()) { indexTerm(docId, longValue.toString()); } for (Double doubleValue : property.getDoubleValuesList()) { indexTerm(docId, doubleValue.toString()); } for (Boolean booleanValue : property.getBooleanValuesList()) { indexTerm(docId, booleanValue.toString()); } // Intentionally skipping bytes values for (DocumentProto documentValue : property.getDocumentValuesList()) { indexDocument(docId, documentValue); } } } private void indexTerm(int docId, String term) { Set<Integer> postingList = mIndex.get(term); if (postingList == null) { postingList = new ArraySet<>(); mIndex.put(term, postingList); } postingList.add(docId); } /** Strips out punctuation and converts to lowercase. */ private static String normalizeString(String input) { return input.replaceAll("\\p{P}", "").toLowerCase(Locale.getDefault()); Loading services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java +60 −30 Original line number Diff line number Diff line Loading @@ -19,75 +19,105 @@ import static com.google.common.truth.Truth.assertThat; import androidx.test.runner.AndroidJUnit4; import com.google.android.icing.proto.DocumentProto; import com.google.android.icing.proto.PropertyProto; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; @RunWith(AndroidJUnit4.class) public class FakeIcingTest { @Test public void query() { FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); icing.put("uri:dog", "The dog said woof"); icing.put(createDoc("uri:cat", "The cat said meow")); icing.put(createDoc("uri:dog", "The dog said woof")); assertThat(icing.query("meow")).containsExactly("uri:cat"); assertThat(icing.query("said")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.query("fred")).isEmpty(); assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat"); assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog"); assertThat(queryGetUris(icing, "fred")).isEmpty(); } @Test public void queryNorm() { FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); icing.put("uri:dog", "The dog said woof"); icing.put(createDoc("uri:cat", "The cat said meow")); icing.put(createDoc("uri:dog", "The dog said woof")); assertThat(icing.query("the")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.query("The")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.query("tHe")).containsExactly("uri:cat", "uri:dog"); assertThat(queryGetUris(icing, "the")).containsExactly("uri:cat", "uri:dog"); assertThat(queryGetUris(icing, "The")).containsExactly("uri:cat", "uri:dog"); assertThat(queryGetUris(icing, "tHe")).containsExactly("uri:cat", "uri:dog"); } @Test public void get() { DocumentProto cat = createDoc("uri:cat", "The cat said meow"); FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); assertThat(icing.get("uri:cat")).isEqualTo("The cat said meow"); icing.put(cat); assertThat(icing.get("uri:cat")).isEqualTo(cat); } @Test public void replace() { DocumentProto cat = createDoc("uri:cat", "The cat said meow"); DocumentProto dog = createDoc("uri:dog", "The dog said woof"); FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); icing.put("uri:dog", "The dog said woof"); icing.put(cat); icing.put(dog); assertThat(icing.query("meow")).containsExactly("uri:cat"); assertThat(icing.query("said")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.get("uri:cat")).isEqualTo("The cat said meow"); assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat"); assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.get("uri:cat")).isEqualTo(cat); // Replace icing.put("uri:cat", "The cat said purr"); icing.put("uri:bird", "The cat said tweet"); assertThat(icing.query("meow")).isEmpty(); assertThat(icing.query("said")).containsExactly("uri:cat", "uri:dog", "uri:bird"); assertThat(icing.get("uri:cat")).isEqualTo("The cat said purr"); DocumentProto cat2 = createDoc("uri:cat", "The cat said purr"); DocumentProto bird = createDoc("uri:bird", "The cat said tweet"); icing.put(cat2); icing.put(bird); assertThat(queryGetUris(icing, "meow")).isEmpty(); assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog", "uri:bird"); assertThat(icing.get("uri:cat")).isEqualTo(cat2); } @Test public void delete() { DocumentProto cat = createDoc("uri:cat", "The cat said meow"); DocumentProto dog = createDoc("uri:dog", "The dog said woof"); FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); icing.put("uri:dog", "The dog said woof"); icing.put(cat); icing.put(dog); assertThat(icing.query("meow")).containsExactly("uri:cat"); assertThat(icing.query("said")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.get("uri:cat")).isEqualTo("The cat said meow"); assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat"); assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.get("uri:cat")).isEqualTo(cat); // Delete icing.delete("uri:cat"); icing.delete("uri:notreal"); assertThat(icing.query("meow")).isEmpty(); assertThat(icing.query("said")).containsExactly("uri:dog"); assertThat(queryGetUris(icing, "meow")).isEmpty(); assertThat(queryGetUris(icing, "said")).containsExactly("uri:dog"); assertThat(icing.get("uri:cat")).isNull(); } private static DocumentProto createDoc(String uri, String body) { return DocumentProto.newBuilder() .setUri(uri) .addProperties(PropertyProto.newBuilder().addStringValues(body)) .build(); } private static List<String> queryGetUris(FakeIcing icing, String term) { List<String> uris = new ArrayList<>(); for (DocumentProto result : icing.query(term)) { uris.add(result.getUri()); } return uris; } } Loading
apex/appsearch/service/Android.bp +3 −0 Original line number Diff line number Diff line Loading @@ -21,4 +21,7 @@ java_library { "framework", "services.core", ], static_libs: [ "icing-java-proto-lite", ] }
apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java +53 −29 Original line number Diff line number Diff line Loading @@ -20,9 +20,11 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; import android.util.SparseArray; import com.google.android.icing.proto.DocumentProto; import com.google.android.icing.proto.PropertyProto; import java.util.ArrayList; import java.util.Collections; import java.util.List; Loading @@ -42,18 +44,19 @@ import java.util.concurrent.atomic.AtomicInteger; public class FakeIcing { private final AtomicInteger mNextDocId = new AtomicInteger(); private final Map<String, Integer> mUriToDocIdMap = new ArrayMap<>(); /** Array of Documents (pair of uri and content) where index into the array is the docId. */ private final SparseArray<Pair<String, String>> mDocStore = new SparseArray<>(); /** Array of Documents where index into the array is the docId. */ private final SparseArray<DocumentProto> mDocStore = new SparseArray<>(); /** Map of term to posting-list (the set of DocIds containing that term). */ private final Map<String, Set<Integer>> mIndex = new ArrayMap<>(); /** * Inserts a document into the index. * * @param uri The globally unique identifier of the document. * @param doc The contents of the document. * @param document The document to insert. */ public void put(@NonNull String uri, @NonNull String doc) { public void put(@NonNull DocumentProto document) { String uri = document.getUri(); // Update mDocIdMap Integer docId = mUriToDocIdMap.get(uri); if (docId != null) { Loading @@ -66,18 +69,10 @@ public class FakeIcing { mUriToDocIdMap.put(uri, docId); // Update mDocStore mDocStore.put(docId, Pair.create(uri, doc)); mDocStore.put(docId, document); // Update mIndex String[] words = normalizeString(doc).split("\\s+"); for (String word : words) { Set<Integer> postingList = mIndex.get(word); if (postingList == null) { postingList = new ArraySet<>(); mIndex.put(word, postingList); } postingList.add(docId); } indexDocument(docId, document); } /** Loading @@ -87,39 +82,35 @@ public class FakeIcing { * @return The body of the document, or {@code null} if no such document exists. */ @Nullable public String get(@NonNull String uri) { public DocumentProto get(@NonNull String uri) { Integer docId = mUriToDocIdMap.get(uri); if (docId == null) { return null; } Pair<String, String> record = mDocStore.get(docId); if (record == null) { return null; } return record.second; return mDocStore.get(docId); } /** * Returns documents containing the given term. * * @param term A single exact term to look up in the index. * @return The URIs of the matching documents, or an empty {@code List} if no documents match. * @return The matching documents, or an empty {@code List} if no documents match. */ @NonNull public List<String> query(@NonNull String term) { public List<DocumentProto> query(@NonNull String term) { String normTerm = normalizeString(term); Set<Integer> docIds = mIndex.get(normTerm); if (docIds == null || docIds.isEmpty()) { return Collections.emptyList(); } List<String> uris = new ArrayList<>(docIds.size()); List<DocumentProto> matches = new ArrayList<>(docIds.size()); for (int docId : docIds) { Pair<String, String> record = mDocStore.get(docId); if (record != null) { uris.add(record.first); DocumentProto document = mDocStore.get(docId); if (document != null) { matches.add(document); } } return uris; return matches; } /** Loading @@ -137,6 +128,39 @@ public class FakeIcing { } } private void indexDocument(int docId, DocumentProto document) { for (PropertyProto property : document.getPropertiesList()) { for (String stringValue : property.getStringValuesList()) { String[] words = normalizeString(stringValue).split("\\s+"); for (String word : words) { indexTerm(docId, word); } } for (Long longValue : property.getInt64ValuesList()) { indexTerm(docId, longValue.toString()); } for (Double doubleValue : property.getDoubleValuesList()) { indexTerm(docId, doubleValue.toString()); } for (Boolean booleanValue : property.getBooleanValuesList()) { indexTerm(docId, booleanValue.toString()); } // Intentionally skipping bytes values for (DocumentProto documentValue : property.getDocumentValuesList()) { indexDocument(docId, documentValue); } } } private void indexTerm(int docId, String term) { Set<Integer> postingList = mIndex.get(term); if (postingList == null) { postingList = new ArraySet<>(); mIndex.put(term, postingList); } postingList.add(docId); } /** Strips out punctuation and converts to lowercase. */ private static String normalizeString(String input) { return input.replaceAll("\\p{P}", "").toLowerCase(Locale.getDefault()); Loading
services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java +60 −30 Original line number Diff line number Diff line Loading @@ -19,75 +19,105 @@ import static com.google.common.truth.Truth.assertThat; import androidx.test.runner.AndroidJUnit4; import com.google.android.icing.proto.DocumentProto; import com.google.android.icing.proto.PropertyProto; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; @RunWith(AndroidJUnit4.class) public class FakeIcingTest { @Test public void query() { FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); icing.put("uri:dog", "The dog said woof"); icing.put(createDoc("uri:cat", "The cat said meow")); icing.put(createDoc("uri:dog", "The dog said woof")); assertThat(icing.query("meow")).containsExactly("uri:cat"); assertThat(icing.query("said")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.query("fred")).isEmpty(); assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat"); assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog"); assertThat(queryGetUris(icing, "fred")).isEmpty(); } @Test public void queryNorm() { FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); icing.put("uri:dog", "The dog said woof"); icing.put(createDoc("uri:cat", "The cat said meow")); icing.put(createDoc("uri:dog", "The dog said woof")); assertThat(icing.query("the")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.query("The")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.query("tHe")).containsExactly("uri:cat", "uri:dog"); assertThat(queryGetUris(icing, "the")).containsExactly("uri:cat", "uri:dog"); assertThat(queryGetUris(icing, "The")).containsExactly("uri:cat", "uri:dog"); assertThat(queryGetUris(icing, "tHe")).containsExactly("uri:cat", "uri:dog"); } @Test public void get() { DocumentProto cat = createDoc("uri:cat", "The cat said meow"); FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); assertThat(icing.get("uri:cat")).isEqualTo("The cat said meow"); icing.put(cat); assertThat(icing.get("uri:cat")).isEqualTo(cat); } @Test public void replace() { DocumentProto cat = createDoc("uri:cat", "The cat said meow"); DocumentProto dog = createDoc("uri:dog", "The dog said woof"); FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); icing.put("uri:dog", "The dog said woof"); icing.put(cat); icing.put(dog); assertThat(icing.query("meow")).containsExactly("uri:cat"); assertThat(icing.query("said")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.get("uri:cat")).isEqualTo("The cat said meow"); assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat"); assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.get("uri:cat")).isEqualTo(cat); // Replace icing.put("uri:cat", "The cat said purr"); icing.put("uri:bird", "The cat said tweet"); assertThat(icing.query("meow")).isEmpty(); assertThat(icing.query("said")).containsExactly("uri:cat", "uri:dog", "uri:bird"); assertThat(icing.get("uri:cat")).isEqualTo("The cat said purr"); DocumentProto cat2 = createDoc("uri:cat", "The cat said purr"); DocumentProto bird = createDoc("uri:bird", "The cat said tweet"); icing.put(cat2); icing.put(bird); assertThat(queryGetUris(icing, "meow")).isEmpty(); assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog", "uri:bird"); assertThat(icing.get("uri:cat")).isEqualTo(cat2); } @Test public void delete() { DocumentProto cat = createDoc("uri:cat", "The cat said meow"); DocumentProto dog = createDoc("uri:dog", "The dog said woof"); FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); icing.put("uri:dog", "The dog said woof"); icing.put(cat); icing.put(dog); assertThat(icing.query("meow")).containsExactly("uri:cat"); assertThat(icing.query("said")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.get("uri:cat")).isEqualTo("The cat said meow"); assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat"); assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.get("uri:cat")).isEqualTo(cat); // Delete icing.delete("uri:cat"); icing.delete("uri:notreal"); assertThat(icing.query("meow")).isEmpty(); assertThat(icing.query("said")).containsExactly("uri:dog"); assertThat(queryGetUris(icing, "meow")).isEmpty(); assertThat(queryGetUris(icing, "said")).containsExactly("uri:dog"); assertThat(icing.get("uri:cat")).isNull(); } private static DocumentProto createDoc(String uri, String body) { return DocumentProto.newBuilder() .setUri(uri) .addProperties(PropertyProto.newBuilder().addStringValues(body)) .build(); } private static List<String> queryGetUris(FakeIcing icing, String term) { List<String> uris = new ArrayList<>(); for (DocumentProto result : icing.query(term)) { uris.add(result.getUri()); } return uris; } }