Loading apex/appsearch/framework/java/android/app/appsearch/AppSearch.java +48 −0 Original line number Diff line number Diff line Loading @@ -101,6 +101,54 @@ public final class AppSearch { this(document.mProto, document.mPropertyBundle); } /** @hide */ Document(@NonNull DocumentProto documentProto) { this(documentProto, new Bundle()); for (int i = 0; i < documentProto.getPropertiesCount(); i++) { PropertyProto property = documentProto.getProperties(i); String name = property.getName(); if (property.getStringValuesCount() > 0) { String[] values = new String[property.getStringValuesCount()]; for (int j = 0; j < values.length; j++) { values[j] = property.getStringValues(j); } mPropertyBundle.putStringArray(name, values); } else if (property.getInt64ValuesCount() > 0) { long[] values = new long[property.getInt64ValuesCount()]; for (int j = 0; j < values.length; j++) { values[j] = property.getInt64Values(j); } mPropertyBundle.putLongArray(property.getName(), values); } else if (property.getDoubleValuesCount() > 0) { double[] values = new double[property.getDoubleValuesCount()]; for (int j = 0; j < values.length; j++) { values[j] = property.getDoubleValues(j); } mPropertyBundle.putDoubleArray(property.getName(), values); } else if (property.getBooleanValuesCount() > 0) { boolean[] values = new boolean[property.getBooleanValuesCount()]; for (int j = 0; j < values.length; j++) { values[j] = property.getBooleanValues(j); } mPropertyBundle.putBooleanArray(property.getName(), values); } else if (property.getBytesValuesCount() > 0) { byte[][] values = new byte[property.getBytesValuesCount()][]; for (int j = 0; j < values.length; j++) { values[j] = property.getBytesValues(j).toByteArray(); } mPropertyBundle.putObject(name, values); } else if (property.getDocumentValuesCount() > 0) { Document[] values = new Document[property.getDocumentValuesCount()]; for (int j = 0; j < values.length; j++) { values[j] = new Document(property.getDocumentValues(j)); } mPropertyBundle.putObject(name, values); } else { throw new IllegalStateException("Unknown type of value: " + name); } } } /** * Creates a new {@link Document.Builder}. * Loading apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java +12 −7 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import com.android.internal.infra.AndroidFuture; import com.google.android.icing.proto.SchemaProto; import com.google.android.icing.proto.SearchResultProto; import com.google.android.icing.proto.SearchSpecProto; import com.google.android.icing.proto.StatusProto; import com.google.android.icing.protobuf.InvalidProtocolBufferException; Loading Loading @@ -186,28 +187,28 @@ public class AppSearchManager { *<p>Currently we support following features in the raw query format: * <ul> * <li>AND * AND joins (e.g. “match documents that have both the terms ‘dog’ and * <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and * ‘cat’”). * Example: hello world matches documents that have both ‘hello’ and ‘world’ * <li>OR * OR joins (e.g. “match documents that have either the term ‘dog’ or * <p>OR joins (e.g. “match documents that have either the term ‘dog’ or * ‘cat’”). * Example: dog OR puppy * <li>Exclusion * Exclude a term (e.g. “match documents that do * <p>Exclude a term (e.g. “match documents that do * not have the term ‘dog’”). * Example: -dog excludes the term ‘dog’ * <li>Grouping terms * Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g. * <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g. * “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”). * Example: (dog puppy) (cat kitten) two one group containing two terms. * <li>Property restricts * which properties of a document to specifically match terms in (e.g. * <p> Specifies which properties of a document to specifically match terms in (e.g. * “match documents where the ‘subject’ property contains ‘important’”). * Example: subject:important matches documents with the term ‘important’ in the * ‘subject’ property * <li>Schema type restricts * This is similar to property restricts, but allows for restricts on top-level document * <p>This is similar to property restricts, but allows for restricts on top-level document * fields, such as schema_type. Clients should be able to limit their query to documents of * a certain schema_type (e.g. “match documents that are of the ‘Email’ schema_type”). * Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will match documents Loading Loading @@ -263,7 +264,11 @@ public class AppSearchManager { }, executor); try { mService.query(queryExpression, searchSpec.getProto().toByteArray(), future); SearchSpecProto searchSpecProto = searchSpec.getSearchSpecProto(); searchSpecProto = searchSpecProto.toBuilder().setQuery(queryExpression).build(); mService.query(searchSpecProto.toByteArray(), searchSpec.getResultSpecProto().toByteArray(), searchSpec.getScoringSpecProto().toByteArray(), future); } catch (RemoteException e) { future.completeExceptionally(e); } Loading apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +6 −4 Original line number Diff line number Diff line Loading @@ -47,12 +47,14 @@ interface IAppSearchManager { void putDocuments(in List documentsBytes, in AndroidFuture<AppSearchBatchResult> callback); /** * Searches a document based on a given query string. * Searches a document based on a given specifications. * * @param queryExpression Query String to search. * @param searchSpec Serialized SearchSpecProto. * @param searchSpecBytes Serialized SearchSpecProto. * @param resultSpecBytes Serialized SearchResultsProto. * @param scoringSpecBytes Serialized ScoringSpecProto. * @param callback {@link AndroidFuture}. Will be completed with a serialized * {@link SearchResultsProto}, or completed exceptionally if query fails. */ void query(in String queryExpression, in byte[] searchSpecBytes, in AndroidFuture callback); void query(in byte[] searchSpecBytes, in byte[] resultSpecBytes, in byte[] scoringSpecBytes, in AndroidFuture callback); } apex/appsearch/framework/java/android/app/appsearch/MatchInfo.java 0 → 100644 +182 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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 android.app.appsearch; import android.annotation.NonNull; import android.util.Range; import com.google.android.icing.proto.SnippetMatchProto; /** * Snippet: It refers to a substring of text from the content of document that is returned as a * part of search result. * This class represents a match objects for any Snippets that might be present in * {@link SearchResults} from query. Using this class user can get the full text, exact matches and * Snippets of document content for a given match. * * <p>Class Example 1: * A document contains following text in property subject: * <p>A commonly used fake word is foo. Another nonsense word that’s used a lot is bar. * * <p>If the queryExpression is "foo". * * <p>{@link MatchInfo#getPropertyPath()} returns "subject" * <p>{@link MatchInfo#getFullText()} returns "A commonly used fake word is foo. Another nonsense * word that’s used a lot is bar." * <p>{@link MatchInfo#getExactMatchPosition()} returns [29, 32] * <p>{@link MatchInfo#getExactMatch()} returns "foo" * <p>{@link MatchInfo#getSnippetPosition()} returns [29, 41] * <p>{@link MatchInfo#getSnippet()} returns "is foo. Another" * <p> * <p>Class Example 2: * A document contains a property name sender which contains 2 property names name and email, so * we will have 2 property paths: {@code sender.name} and {@code sender.email}. * <p> Let {@code sender.name = "Test Name Jr."} and {@code sender.email = "TestNameJr@gmail.com"} * * <p>If the queryExpression is "Test". We will have 2 matches. * * <p> Match-1 * <p>{@link MatchInfo#getPropertyPath()} returns "sender.name" * <p>{@link MatchInfo#getFullText()} returns "Test Name Jr." * <p>{@link MatchInfo#getExactMatchPosition()} returns [0, 4] * <p>{@link MatchInfo#getExactMatch()} returns "Test" * <p>{@link MatchInfo#getSnippetPosition()} returns [0, 9] * <p>{@link MatchInfo#getSnippet()} returns "Test Name Jr." * <p> Match-2 * <p>{@link MatchInfo#getPropertyPath()} returns "sender.email" * <p>{@link MatchInfo#getFullText()} returns "TestNameJr@gmail.com" * <p>{@link MatchInfo#getExactMatchPosition()} returns [0, 20] * <p>{@link MatchInfo#getExactMatch()} returns "TestNameJr@gmail.com" * <p>{@link MatchInfo#getSnippetPosition()} returns [0, 20] * <p>{@link MatchInfo#getSnippet()} returns "TestNameJr@gmail.com" * @hide */ // TODO(sidchhabra): Capture real snippet after integration with icingLib. public final class MatchInfo { private final String mPropertyPath; private final SnippetMatchProto mSnippetMatch; private final AppSearch.Document mDocument; /** * List of content with same property path in a document when there are multiple matches in * repeated sections. */ private final String[] mValues; /** @hide */ public MatchInfo(@NonNull String propertyPath, @NonNull SnippetMatchProto snippetMatch, @NonNull AppSearch.Document document) { mPropertyPath = propertyPath; mSnippetMatch = snippetMatch; mDocument = document; // In IcingLib snippeting is available for only 3 data types i.e String, double and long, // so we need to check which of these three are requested. // TODO (sidchhabra): getPropertyStringArray takes property name, handle for property path. String[] values = mDocument.getPropertyStringArray(propertyPath); if (values == null) { values = doubleToString(mDocument.getPropertyDoubleArray(propertyPath)); } if (values == null) { values = longToString(mDocument.getPropertyLongArray(propertyPath)); } if (values == null) { throw new IllegalStateException("No content found for requested property path!"); } mValues = values; } /** * Gets the property path corresponding to the given entry. * <p>Property Path: '.' - delimited sequence of property names indicating which property in * the Document these snippets correspond to. * <p>Example properties: 'body', 'sender.name', 'sender.emailaddress', etc. * For class example 1 this returns "subject" */ @NonNull public String getPropertyPath() { return mPropertyPath; } /** * Gets the full text corresponding to the given entry. * <p>For class example this returns "A commonly used fake word is foo. Another nonsense word * that’s used a lot is bar." */ @NonNull public String getFullText() { return mValues[mSnippetMatch.getValuesIndex()]; } /** * Gets the exact match range corresponding to the given entry. * <p>For class example 1 this returns [29, 32] */ @NonNull public Range getExactMatchPosition() { return new Range(mSnippetMatch.getExactMatchPosition(), mSnippetMatch.getExactMatchPosition() + mSnippetMatch.getExactMatchBytes()); } /** * Gets the exact match corresponding to the given entry. * <p>For class example 1 this returns "foo" */ @NonNull public CharSequence getExactMatch() { return getSubstring(getExactMatchPosition()); } /** * Gets the snippet range corresponding to the given entry. * <p>For class example 1 this returns [29, 41] */ @NonNull public Range getSnippetPosition() { return new Range(mSnippetMatch.getWindowPosition(), mSnippetMatch.getWindowPosition() + mSnippetMatch.getWindowBytes()); } /** * Gets the snippet corresponding to the given entry. * <p>Snippet - Provides a subset of the content to display. The * length of this content can be changed {@link SearchSpec.Builder#setMaxSnippetSize(int)}. * Windowing is centered around the middle of the matched token with content on either side * clipped to token boundaries. * <p>For class example 1 this returns "foo. Another" */ @NonNull public CharSequence getSnippet() { return getSubstring(getSnippetPosition()); } private CharSequence getSubstring(Range range) { return getFullText() .substring((int) range.getLower(), (int) range.getUpper()); } /** Utility method to convert double[] to String[] */ private String[] doubleToString(double[] values) { //TODO(sidchhabra): Implement the method. return null; } /** Utility method to convert long[] to String[] */ private String[] longToString(long[] values) { //TODO(sidchhabra): Implement the method. return null; } } apex/appsearch/framework/java/android/app/appsearch/SearchResults.java +61 −22 Original line number Diff line number Diff line Loading @@ -17,27 +17,51 @@ package android.app.appsearch; import android.annotation.NonNull; import android.annotation.Nullable; import com.google.android.icing.proto.SearchResultProto; import com.google.android.icing.proto.SnippetMatchProto; import com.google.android.icing.proto.SnippetProto; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; /** * SearchResults are a list of results that are returned from a query. Each result from this * list contains a document and may contain other fields like snippets based on request. * This iterator class is not thread safe. * @hide */ public final class SearchResults { public final class SearchResults implements Iterator<SearchResults.Result> { private final SearchResultProto mSearchResultProto; private int mNextIdx; /** @hide */ public SearchResults(SearchResultProto searchResultProto) { mSearchResultProto = searchResultProto; } @Override public boolean hasNext() { return mNextIdx < mSearchResultProto.getResultsCount(); } @NonNull @Override public Result next() { if (!hasNext()) { throw new NoSuchElementException(); } Result result = new Result(mSearchResultProto.getResults(mNextIdx)); mNextIdx++; return result; } /** * This class represents the result obtained from the query. It will contain the document which * which matched the specified query string and specifications. Loading @@ -46,6 +70,9 @@ public final class SearchResults { public static final class Result { private final SearchResultProto.ResultProto mResultProto; @Nullable private AppSearch.Document mDocument; private Result(SearchResultProto.ResultProto resultProto) { mResultProto = resultProto; } Loading @@ -55,35 +82,47 @@ public final class SearchResults { * @return Document object which matched the query. * @hide */ // TODO(sidchhabra): Switch to Document constructor that takes proto. @NonNull public AppSearch.Document getDocument() { return AppSearch.Document.newBuilder(mResultProto.getDocument().getUri(), mResultProto.getDocument().getSchema()) .setCreationTimestampMillis(mResultProto.getDocument().getCreationTimestampMs()) .setScore(mResultProto.getDocument().getScore()) .build(); } // TODO(sidchhabra): Add Getter for ResultReader for Snippet. if (mDocument == null) { mDocument = new AppSearch.Document(mResultProto.getDocument()); } @Override public String toString() { return mSearchResultProto.toString(); return mDocument; } /** * Returns a {@link Result} iterator. Returns Empty Iterator if there are no matching results. * Contains a list of Snippets that matched the request. Only populated when requested in * {@link SearchSpec.Builder#setMaxSnippetSize(int)}. * @return List of matches based on {@link SearchSpec}, if snippeting is disabled and this * method is called it will return {@code null}. Users can also restrict snippet population * using {@link SearchSpec.Builder#setNumToSnippet} and * {@link SearchSpec.Builder#setNumMatchesPerProperty}, for all results after that value * this method will return {@code null}. * @hide */ @NonNull public Iterator<Result> getResults() { List<Result> results = new ArrayList<>(); // TODO(sidchhabra): Pass results using a RemoteStream. for (SearchResultProto.ResultProto resultProto : mSearchResultProto.getResultsList()) { results.add(new Result(resultProto)); // TODO(sidchhabra): Replace Document with proper constructor. @Nullable public List<MatchInfo> getMatchInfo() { if (!mResultProto.hasSnippet()) { return null; } AppSearch.Document document = getDocument(); List<MatchInfo> matchList = new ArrayList<>(); for (Iterator entryProtoIterator = mResultProto.getSnippet() .getEntriesList().iterator(); entryProtoIterator.hasNext(); ) { SnippetProto.EntryProto entry = (SnippetProto.EntryProto) entryProtoIterator.next(); for (Iterator snippetMatchProtoIterator = entry.getSnippetMatchesList().iterator(); snippetMatchProtoIterator.hasNext(); ) { matchList.add(new MatchInfo(entry.getPropertyName(), (SnippetMatchProto) snippetMatchProtoIterator.next(), document)); } } return results.iterator(); return matchList; } } @Override public String toString() { return mSearchResultProto.toString(); } } Loading
apex/appsearch/framework/java/android/app/appsearch/AppSearch.java +48 −0 Original line number Diff line number Diff line Loading @@ -101,6 +101,54 @@ public final class AppSearch { this(document.mProto, document.mPropertyBundle); } /** @hide */ Document(@NonNull DocumentProto documentProto) { this(documentProto, new Bundle()); for (int i = 0; i < documentProto.getPropertiesCount(); i++) { PropertyProto property = documentProto.getProperties(i); String name = property.getName(); if (property.getStringValuesCount() > 0) { String[] values = new String[property.getStringValuesCount()]; for (int j = 0; j < values.length; j++) { values[j] = property.getStringValues(j); } mPropertyBundle.putStringArray(name, values); } else if (property.getInt64ValuesCount() > 0) { long[] values = new long[property.getInt64ValuesCount()]; for (int j = 0; j < values.length; j++) { values[j] = property.getInt64Values(j); } mPropertyBundle.putLongArray(property.getName(), values); } else if (property.getDoubleValuesCount() > 0) { double[] values = new double[property.getDoubleValuesCount()]; for (int j = 0; j < values.length; j++) { values[j] = property.getDoubleValues(j); } mPropertyBundle.putDoubleArray(property.getName(), values); } else if (property.getBooleanValuesCount() > 0) { boolean[] values = new boolean[property.getBooleanValuesCount()]; for (int j = 0; j < values.length; j++) { values[j] = property.getBooleanValues(j); } mPropertyBundle.putBooleanArray(property.getName(), values); } else if (property.getBytesValuesCount() > 0) { byte[][] values = new byte[property.getBytesValuesCount()][]; for (int j = 0; j < values.length; j++) { values[j] = property.getBytesValues(j).toByteArray(); } mPropertyBundle.putObject(name, values); } else if (property.getDocumentValuesCount() > 0) { Document[] values = new Document[property.getDocumentValuesCount()]; for (int j = 0; j < values.length; j++) { values[j] = new Document(property.getDocumentValues(j)); } mPropertyBundle.putObject(name, values); } else { throw new IllegalStateException("Unknown type of value: " + name); } } } /** * Creates a new {@link Document.Builder}. * Loading
apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java +12 −7 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import com.android.internal.infra.AndroidFuture; import com.google.android.icing.proto.SchemaProto; import com.google.android.icing.proto.SearchResultProto; import com.google.android.icing.proto.SearchSpecProto; import com.google.android.icing.proto.StatusProto; import com.google.android.icing.protobuf.InvalidProtocolBufferException; Loading Loading @@ -186,28 +187,28 @@ public class AppSearchManager { *<p>Currently we support following features in the raw query format: * <ul> * <li>AND * AND joins (e.g. “match documents that have both the terms ‘dog’ and * <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and * ‘cat’”). * Example: hello world matches documents that have both ‘hello’ and ‘world’ * <li>OR * OR joins (e.g. “match documents that have either the term ‘dog’ or * <p>OR joins (e.g. “match documents that have either the term ‘dog’ or * ‘cat’”). * Example: dog OR puppy * <li>Exclusion * Exclude a term (e.g. “match documents that do * <p>Exclude a term (e.g. “match documents that do * not have the term ‘dog’”). * Example: -dog excludes the term ‘dog’ * <li>Grouping terms * Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g. * <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g. * “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”). * Example: (dog puppy) (cat kitten) two one group containing two terms. * <li>Property restricts * which properties of a document to specifically match terms in (e.g. * <p> Specifies which properties of a document to specifically match terms in (e.g. * “match documents where the ‘subject’ property contains ‘important’”). * Example: subject:important matches documents with the term ‘important’ in the * ‘subject’ property * <li>Schema type restricts * This is similar to property restricts, but allows for restricts on top-level document * <p>This is similar to property restricts, but allows for restricts on top-level document * fields, such as schema_type. Clients should be able to limit their query to documents of * a certain schema_type (e.g. “match documents that are of the ‘Email’ schema_type”). * Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will match documents Loading Loading @@ -263,7 +264,11 @@ public class AppSearchManager { }, executor); try { mService.query(queryExpression, searchSpec.getProto().toByteArray(), future); SearchSpecProto searchSpecProto = searchSpec.getSearchSpecProto(); searchSpecProto = searchSpecProto.toBuilder().setQuery(queryExpression).build(); mService.query(searchSpecProto.toByteArray(), searchSpec.getResultSpecProto().toByteArray(), searchSpec.getScoringSpecProto().toByteArray(), future); } catch (RemoteException e) { future.completeExceptionally(e); } Loading
apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +6 −4 Original line number Diff line number Diff line Loading @@ -47,12 +47,14 @@ interface IAppSearchManager { void putDocuments(in List documentsBytes, in AndroidFuture<AppSearchBatchResult> callback); /** * Searches a document based on a given query string. * Searches a document based on a given specifications. * * @param queryExpression Query String to search. * @param searchSpec Serialized SearchSpecProto. * @param searchSpecBytes Serialized SearchSpecProto. * @param resultSpecBytes Serialized SearchResultsProto. * @param scoringSpecBytes Serialized ScoringSpecProto. * @param callback {@link AndroidFuture}. Will be completed with a serialized * {@link SearchResultsProto}, or completed exceptionally if query fails. */ void query(in String queryExpression, in byte[] searchSpecBytes, in AndroidFuture callback); void query(in byte[] searchSpecBytes, in byte[] resultSpecBytes, in byte[] scoringSpecBytes, in AndroidFuture callback); }
apex/appsearch/framework/java/android/app/appsearch/MatchInfo.java 0 → 100644 +182 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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 android.app.appsearch; import android.annotation.NonNull; import android.util.Range; import com.google.android.icing.proto.SnippetMatchProto; /** * Snippet: It refers to a substring of text from the content of document that is returned as a * part of search result. * This class represents a match objects for any Snippets that might be present in * {@link SearchResults} from query. Using this class user can get the full text, exact matches and * Snippets of document content for a given match. * * <p>Class Example 1: * A document contains following text in property subject: * <p>A commonly used fake word is foo. Another nonsense word that’s used a lot is bar. * * <p>If the queryExpression is "foo". * * <p>{@link MatchInfo#getPropertyPath()} returns "subject" * <p>{@link MatchInfo#getFullText()} returns "A commonly used fake word is foo. Another nonsense * word that’s used a lot is bar." * <p>{@link MatchInfo#getExactMatchPosition()} returns [29, 32] * <p>{@link MatchInfo#getExactMatch()} returns "foo" * <p>{@link MatchInfo#getSnippetPosition()} returns [29, 41] * <p>{@link MatchInfo#getSnippet()} returns "is foo. Another" * <p> * <p>Class Example 2: * A document contains a property name sender which contains 2 property names name and email, so * we will have 2 property paths: {@code sender.name} and {@code sender.email}. * <p> Let {@code sender.name = "Test Name Jr."} and {@code sender.email = "TestNameJr@gmail.com"} * * <p>If the queryExpression is "Test". We will have 2 matches. * * <p> Match-1 * <p>{@link MatchInfo#getPropertyPath()} returns "sender.name" * <p>{@link MatchInfo#getFullText()} returns "Test Name Jr." * <p>{@link MatchInfo#getExactMatchPosition()} returns [0, 4] * <p>{@link MatchInfo#getExactMatch()} returns "Test" * <p>{@link MatchInfo#getSnippetPosition()} returns [0, 9] * <p>{@link MatchInfo#getSnippet()} returns "Test Name Jr." * <p> Match-2 * <p>{@link MatchInfo#getPropertyPath()} returns "sender.email" * <p>{@link MatchInfo#getFullText()} returns "TestNameJr@gmail.com" * <p>{@link MatchInfo#getExactMatchPosition()} returns [0, 20] * <p>{@link MatchInfo#getExactMatch()} returns "TestNameJr@gmail.com" * <p>{@link MatchInfo#getSnippetPosition()} returns [0, 20] * <p>{@link MatchInfo#getSnippet()} returns "TestNameJr@gmail.com" * @hide */ // TODO(sidchhabra): Capture real snippet after integration with icingLib. public final class MatchInfo { private final String mPropertyPath; private final SnippetMatchProto mSnippetMatch; private final AppSearch.Document mDocument; /** * List of content with same property path in a document when there are multiple matches in * repeated sections. */ private final String[] mValues; /** @hide */ public MatchInfo(@NonNull String propertyPath, @NonNull SnippetMatchProto snippetMatch, @NonNull AppSearch.Document document) { mPropertyPath = propertyPath; mSnippetMatch = snippetMatch; mDocument = document; // In IcingLib snippeting is available for only 3 data types i.e String, double and long, // so we need to check which of these three are requested. // TODO (sidchhabra): getPropertyStringArray takes property name, handle for property path. String[] values = mDocument.getPropertyStringArray(propertyPath); if (values == null) { values = doubleToString(mDocument.getPropertyDoubleArray(propertyPath)); } if (values == null) { values = longToString(mDocument.getPropertyLongArray(propertyPath)); } if (values == null) { throw new IllegalStateException("No content found for requested property path!"); } mValues = values; } /** * Gets the property path corresponding to the given entry. * <p>Property Path: '.' - delimited sequence of property names indicating which property in * the Document these snippets correspond to. * <p>Example properties: 'body', 'sender.name', 'sender.emailaddress', etc. * For class example 1 this returns "subject" */ @NonNull public String getPropertyPath() { return mPropertyPath; } /** * Gets the full text corresponding to the given entry. * <p>For class example this returns "A commonly used fake word is foo. Another nonsense word * that’s used a lot is bar." */ @NonNull public String getFullText() { return mValues[mSnippetMatch.getValuesIndex()]; } /** * Gets the exact match range corresponding to the given entry. * <p>For class example 1 this returns [29, 32] */ @NonNull public Range getExactMatchPosition() { return new Range(mSnippetMatch.getExactMatchPosition(), mSnippetMatch.getExactMatchPosition() + mSnippetMatch.getExactMatchBytes()); } /** * Gets the exact match corresponding to the given entry. * <p>For class example 1 this returns "foo" */ @NonNull public CharSequence getExactMatch() { return getSubstring(getExactMatchPosition()); } /** * Gets the snippet range corresponding to the given entry. * <p>For class example 1 this returns [29, 41] */ @NonNull public Range getSnippetPosition() { return new Range(mSnippetMatch.getWindowPosition(), mSnippetMatch.getWindowPosition() + mSnippetMatch.getWindowBytes()); } /** * Gets the snippet corresponding to the given entry. * <p>Snippet - Provides a subset of the content to display. The * length of this content can be changed {@link SearchSpec.Builder#setMaxSnippetSize(int)}. * Windowing is centered around the middle of the matched token with content on either side * clipped to token boundaries. * <p>For class example 1 this returns "foo. Another" */ @NonNull public CharSequence getSnippet() { return getSubstring(getSnippetPosition()); } private CharSequence getSubstring(Range range) { return getFullText() .substring((int) range.getLower(), (int) range.getUpper()); } /** Utility method to convert double[] to String[] */ private String[] doubleToString(double[] values) { //TODO(sidchhabra): Implement the method. return null; } /** Utility method to convert long[] to String[] */ private String[] longToString(long[] values) { //TODO(sidchhabra): Implement the method. return null; } }
apex/appsearch/framework/java/android/app/appsearch/SearchResults.java +61 −22 Original line number Diff line number Diff line Loading @@ -17,27 +17,51 @@ package android.app.appsearch; import android.annotation.NonNull; import android.annotation.Nullable; import com.google.android.icing.proto.SearchResultProto; import com.google.android.icing.proto.SnippetMatchProto; import com.google.android.icing.proto.SnippetProto; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; /** * SearchResults are a list of results that are returned from a query. Each result from this * list contains a document and may contain other fields like snippets based on request. * This iterator class is not thread safe. * @hide */ public final class SearchResults { public final class SearchResults implements Iterator<SearchResults.Result> { private final SearchResultProto mSearchResultProto; private int mNextIdx; /** @hide */ public SearchResults(SearchResultProto searchResultProto) { mSearchResultProto = searchResultProto; } @Override public boolean hasNext() { return mNextIdx < mSearchResultProto.getResultsCount(); } @NonNull @Override public Result next() { if (!hasNext()) { throw new NoSuchElementException(); } Result result = new Result(mSearchResultProto.getResults(mNextIdx)); mNextIdx++; return result; } /** * This class represents the result obtained from the query. It will contain the document which * which matched the specified query string and specifications. Loading @@ -46,6 +70,9 @@ public final class SearchResults { public static final class Result { private final SearchResultProto.ResultProto mResultProto; @Nullable private AppSearch.Document mDocument; private Result(SearchResultProto.ResultProto resultProto) { mResultProto = resultProto; } Loading @@ -55,35 +82,47 @@ public final class SearchResults { * @return Document object which matched the query. * @hide */ // TODO(sidchhabra): Switch to Document constructor that takes proto. @NonNull public AppSearch.Document getDocument() { return AppSearch.Document.newBuilder(mResultProto.getDocument().getUri(), mResultProto.getDocument().getSchema()) .setCreationTimestampMillis(mResultProto.getDocument().getCreationTimestampMs()) .setScore(mResultProto.getDocument().getScore()) .build(); } // TODO(sidchhabra): Add Getter for ResultReader for Snippet. if (mDocument == null) { mDocument = new AppSearch.Document(mResultProto.getDocument()); } @Override public String toString() { return mSearchResultProto.toString(); return mDocument; } /** * Returns a {@link Result} iterator. Returns Empty Iterator if there are no matching results. * Contains a list of Snippets that matched the request. Only populated when requested in * {@link SearchSpec.Builder#setMaxSnippetSize(int)}. * @return List of matches based on {@link SearchSpec}, if snippeting is disabled and this * method is called it will return {@code null}. Users can also restrict snippet population * using {@link SearchSpec.Builder#setNumToSnippet} and * {@link SearchSpec.Builder#setNumMatchesPerProperty}, for all results after that value * this method will return {@code null}. * @hide */ @NonNull public Iterator<Result> getResults() { List<Result> results = new ArrayList<>(); // TODO(sidchhabra): Pass results using a RemoteStream. for (SearchResultProto.ResultProto resultProto : mSearchResultProto.getResultsList()) { results.add(new Result(resultProto)); // TODO(sidchhabra): Replace Document with proper constructor. @Nullable public List<MatchInfo> getMatchInfo() { if (!mResultProto.hasSnippet()) { return null; } AppSearch.Document document = getDocument(); List<MatchInfo> matchList = new ArrayList<>(); for (Iterator entryProtoIterator = mResultProto.getSnippet() .getEntriesList().iterator(); entryProtoIterator.hasNext(); ) { SnippetProto.EntryProto entry = (SnippetProto.EntryProto) entryProtoIterator.next(); for (Iterator snippetMatchProtoIterator = entry.getSnippetMatchesList().iterator(); snippetMatchProtoIterator.hasNext(); ) { matchList.add(new MatchInfo(entry.getPropertyName(), (SnippetMatchProto) snippetMatchProtoIterator.next(), document)); } } return results.iterator(); return matchList; } } @Override public String toString() { return mSearchResultProto.toString(); } }