Loading apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java +48 −3 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.os.RemoteException; import com.android.internal.infra.AndroidFuture; import com.google.android.icing.proto.DocumentProto; import com.google.android.icing.proto.SchemaProto; import com.google.android.icing.proto.SearchResultProto; import com.google.android.icing.proto.SearchSpecProto; Loading Loading @@ -185,6 +186,53 @@ public class AppSearchManager { return getFutureOrThrow(future); } /** * Retrieves {@link AppSearchDocument}s by URI. * * <p>You should not call this method directly; instead, use the * {@code AppSearch#getDocuments()} API provided by JetPack. * * @param uris URIs of the documents to look up. * @param executor Executor on which to invoke the callback. * @param callback Callback to receive the documents or error. */ public void getDocuments( @NonNull List<String> uris, @NonNull @CallbackExecutor Executor executor, @NonNull BiConsumer<List<AppSearchDocument>, ? super Throwable> callback) { AndroidFuture<List<byte[]>> future = new AndroidFuture<>(); future.whenCompleteAsync((documentProtos, err) -> { if (err != null) { callback.accept(null, err); return; } if (documentProtos != null) { List<AppSearchDocument> results = new ArrayList<>(documentProtos.size()); for (int i = 0; i < documentProtos.size(); i++) { DocumentProto documentProto; try { documentProto = DocumentProto.parseFrom(documentProtos.get(i)); } catch (InvalidProtocolBufferException e) { callback.accept(null, e); return; } results.add(new AppSearchDocument(documentProto)); } callback.accept(results, null); return; } // Nothing was supplied in the future at all callback.accept(null, new IllegalStateException( "Unknown failure occurred while retrieving documents")); }, executor); // TODO(b/146386470) stream uris? try { mService.getDocuments(uris.toArray(new String[uris.size()]), future); } catch (RemoteException e) { future.completeExceptionally(e); } } /** * This method searches for documents based on a given query string. It also accepts * specifications regarding how to search and format the results. Loading Loading @@ -242,7 +290,6 @@ public class AppSearchManager { callback.accept(null, err); return; } if (searchResultBytes != null) { SearchResultProto searchResultProto; try { Loading @@ -262,12 +309,10 @@ public class AppSearchManager { callback.accept(searchResults, null); return; } // Nothing was supplied in the future at all callback.accept( null, new IllegalStateException("Unknown failure occurred while querying")); }, executor); try { SearchSpecProto searchSpecProto = searchSpec.getSearchSpecProto(); searchSpecProto = searchSpecProto.toBuilder().setQuery(queryExpression).build(); Loading apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java +4 −16 Original line number Diff line number Diff line Loading @@ -104,10 +104,7 @@ public final class AppSearchSchema { * a property. */ public static final class PropertyConfig { /** * Physical data-types of the contents of the property. * @hide */ /** Physical data-types of the contents of the property. */ // NOTE: The integer values of these constants must match the proto enum constants in // com.google.android.icing.proto.PropertyConfigProto.DataType.Code. @IntDef(prefix = {"DATA_TYPE_"}, value = { Loading Loading @@ -136,10 +133,7 @@ public final class AppSearchSchema { */ public static final int DATA_TYPE_DOCUMENT = 6; /** * The cardinality of the property (whether it is required, optional or repeated). * @hide */ /** The cardinality of the property (whether it is required, optional or repeated). */ // NOTE: The integer values of these constants must match the proto enum constants in // com.google.android.icing.proto.PropertyConfigProto.Cardinality.Code. @IntDef(prefix = {"CARDINALITY_"}, value = { Loading @@ -159,10 +153,7 @@ public final class AppSearchSchema { /** Exactly one value [1]. */ public static final int CARDINALITY_REQUIRED = 3; /** * Encapsulates the configurations on how AppSearch should query/index these terms. * @hide */ /** Encapsulates the configurations on how AppSearch should query/index these terms. */ @IntDef(prefix = {"INDEXING_TYPE_"}, value = { INDEXING_TYPE_NONE, INDEXING_TYPE_EXACT_TERMS, Loading Loading @@ -197,10 +188,7 @@ public final class AppSearchSchema { */ public static final int INDEXING_TYPE_PREFIXES = 2; /** * Configures how tokens should be extracted from this property. * @hide */ /** Configures how tokens should be extracted from this property. */ // NOTE: The integer values of these constants must match the proto enum constants in // com.google.android.icing.proto.IndexingConfig.TokenizerType.Code. @IntDef(prefix = {"TOKENIZER_TYPE_"}, value = { Loading apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +10 −0 Original line number Diff line number Diff line Loading @@ -47,6 +47,16 @@ interface IAppSearchManager { */ void putDocuments(in List documentsBytes, in AndroidFuture<AppSearchBatchResult> callback); /** * Retrieves documents from the index. * * @param uris The URIs of the documents to retrieve * @param callback {@link AndroidFuture}<{@link List}<byte[]>>. Will be completed * with a {@link List} containing serialized DocumentProtos, or completed exceptionally if * get fails. */ void getDocuments(in String[] uris, in AndroidFuture callback); /** * Searches a document based on a given specifications. * Loading apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +24 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import com.google.android.icing.proto.SearchSpecProto; import com.google.android.icing.protobuf.InvalidProtocolBufferException; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** Loading Loading @@ -110,6 +111,29 @@ public class AppSearchManagerService extends SystemService { } } @Override public void getDocuments(String[] uris, AndroidFuture callback) { Preconditions.checkNotNull(uris); Preconditions.checkNotNull(callback); int callingUid = Binder.getCallingUidOrThrow(); int callingUserId = UserHandle.getUserId(callingUid); long callingIdentity = Binder.clearCallingIdentity(); try { AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); // Contains serialized DocumentProto. byte[][] is not transmissible via Binder. List<byte[]> results = new ArrayList<>(uris.length); for (String uri : uris) { DocumentProto result = impl.getDocument(callingUid, uri); results.add(result.toByteArray()); } callback.complete(results); } catch (Throwable t) { callback.completeExceptionally(t); } finally { Binder.restoreCallingIdentity(callingIdentity); } } // TODO(sidchhabra):Init FakeIcing properly. // TODO(sidchhabra): Do this in a threadpool. @Override Loading apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java +54 −9 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.appsearch.impl; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; Loading Loading @@ -106,30 +107,65 @@ public final class AppSearchImpl { // Rewrite the type names to include the app's prefix String typePrefix = getTypePrefix(callingUid); DocumentProto.Builder documentBuilder = origDocument.toBuilder(); rewriteDocumentTypes(typePrefix, documentBuilder); rewriteDocumentTypes(typePrefix, documentBuilder, /*add=*/ true); mFakeIcing.put(documentBuilder.build()); } /** * Rewrites all types mentioned anywhere in {@code documentBuilder} to prepend * Retrieves a document from the AppSearch index by URI. * * @param callingUid The uid of the app calling AppSearch. * @param uri The URI of the document to get. * @return The Document contents, or {@code null} if no such URI exists in the system. */ @Nullable public DocumentProto getDocument(int callingUid, @NonNull String uri) { String typePrefix = getTypePrefix(callingUid); DocumentProto document = mFakeIcing.get(uri); // Rewrite the type names to remove the app's prefix DocumentProto.Builder documentBuilder = document.toBuilder(); rewriteDocumentTypes(typePrefix, documentBuilder, /*add=*/ false); return documentBuilder.build(); } /** * Rewrites all types mentioned anywhere in {@code documentBuilder} to prepend or remove * {@code typePrefix}. * * @param typePrefix The prefix to add * @param typePrefix The prefix to add or remove * @param documentBuilder The document to mutate * @param add Whether to add typePrefix to the types. If {@code false}, typePrefix will be * removed from the types. * @throws IllegalArgumentException If {@code add=false} and the document has a type that * doesn't start with {@code typePrefix}. */ @VisibleForTesting void rewriteDocumentTypes( @NonNull String typePrefix, @NonNull DocumentProto.Builder documentBuilder) { // Rewrite the type name to include the app's prefix String newSchema = typePrefix + documentBuilder.getSchema(); @NonNull DocumentProto.Builder documentBuilder, boolean add) { // Rewrite the type name to include/remove the app's prefix String newSchema; if (add) { newSchema = typePrefix + documentBuilder.getSchema(); } else { newSchema = removePrefix(typePrefix, documentBuilder.getSchema()); } documentBuilder.setSchema(newSchema); // Add namespace. If we ever allow users to set their own namespaces, this will have // Add/remove namespace. If we ever allow users to set their own namespaces, this will have // to change to prepend the prefix instead of setting the whole namespace. We will also have // to store the namespaces in a map similar to the type map so we can rewrite queries with // empty namespaces. if (add) { documentBuilder.setNamespace(typePrefix); } else if (!documentBuilder.getNamespace().equals(typePrefix)) { throw new IllegalStateException( "Unexpected namespace \"" + documentBuilder.getNamespace() + "\" (expected \"" + typePrefix + "\")"); } else { documentBuilder.clearNamespace(); } // Recurse into derived documents for (int propertyIdx = 0; Loading @@ -142,7 +178,7 @@ public final class AppSearchImpl { for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) { DocumentProto.Builder derivedDocumentBuilder = propertyBuilder.getDocumentValues(documentIdx).toBuilder(); rewriteDocumentTypes(typePrefix, derivedDocumentBuilder); rewriteDocumentTypes(typePrefix, derivedDocumentBuilder, add); propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder); } documentBuilder.setProperties(propertyIdx, propertyBuilder); Loading @@ -165,4 +201,13 @@ public final class AppSearchImpl { } return callingUidName + "@" + mUserId + "/"; } @NonNull private static String removePrefix(@NonNull String prefix, @NonNull String input) { if (!input.startsWith(prefix)) { throw new IllegalArgumentException( "Input \"" + input + "\" does not start with \"" + prefix + "\""); } return input.substring(prefix.length()); } } Loading
apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java +48 −3 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.os.RemoteException; import com.android.internal.infra.AndroidFuture; import com.google.android.icing.proto.DocumentProto; import com.google.android.icing.proto.SchemaProto; import com.google.android.icing.proto.SearchResultProto; import com.google.android.icing.proto.SearchSpecProto; Loading Loading @@ -185,6 +186,53 @@ public class AppSearchManager { return getFutureOrThrow(future); } /** * Retrieves {@link AppSearchDocument}s by URI. * * <p>You should not call this method directly; instead, use the * {@code AppSearch#getDocuments()} API provided by JetPack. * * @param uris URIs of the documents to look up. * @param executor Executor on which to invoke the callback. * @param callback Callback to receive the documents or error. */ public void getDocuments( @NonNull List<String> uris, @NonNull @CallbackExecutor Executor executor, @NonNull BiConsumer<List<AppSearchDocument>, ? super Throwable> callback) { AndroidFuture<List<byte[]>> future = new AndroidFuture<>(); future.whenCompleteAsync((documentProtos, err) -> { if (err != null) { callback.accept(null, err); return; } if (documentProtos != null) { List<AppSearchDocument> results = new ArrayList<>(documentProtos.size()); for (int i = 0; i < documentProtos.size(); i++) { DocumentProto documentProto; try { documentProto = DocumentProto.parseFrom(documentProtos.get(i)); } catch (InvalidProtocolBufferException e) { callback.accept(null, e); return; } results.add(new AppSearchDocument(documentProto)); } callback.accept(results, null); return; } // Nothing was supplied in the future at all callback.accept(null, new IllegalStateException( "Unknown failure occurred while retrieving documents")); }, executor); // TODO(b/146386470) stream uris? try { mService.getDocuments(uris.toArray(new String[uris.size()]), future); } catch (RemoteException e) { future.completeExceptionally(e); } } /** * This method searches for documents based on a given query string. It also accepts * specifications regarding how to search and format the results. Loading Loading @@ -242,7 +290,6 @@ public class AppSearchManager { callback.accept(null, err); return; } if (searchResultBytes != null) { SearchResultProto searchResultProto; try { Loading @@ -262,12 +309,10 @@ public class AppSearchManager { callback.accept(searchResults, null); return; } // Nothing was supplied in the future at all callback.accept( null, new IllegalStateException("Unknown failure occurred while querying")); }, executor); try { SearchSpecProto searchSpecProto = searchSpec.getSearchSpecProto(); searchSpecProto = searchSpecProto.toBuilder().setQuery(queryExpression).build(); Loading
apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java +4 −16 Original line number Diff line number Diff line Loading @@ -104,10 +104,7 @@ public final class AppSearchSchema { * a property. */ public static final class PropertyConfig { /** * Physical data-types of the contents of the property. * @hide */ /** Physical data-types of the contents of the property. */ // NOTE: The integer values of these constants must match the proto enum constants in // com.google.android.icing.proto.PropertyConfigProto.DataType.Code. @IntDef(prefix = {"DATA_TYPE_"}, value = { Loading Loading @@ -136,10 +133,7 @@ public final class AppSearchSchema { */ public static final int DATA_TYPE_DOCUMENT = 6; /** * The cardinality of the property (whether it is required, optional or repeated). * @hide */ /** The cardinality of the property (whether it is required, optional or repeated). */ // NOTE: The integer values of these constants must match the proto enum constants in // com.google.android.icing.proto.PropertyConfigProto.Cardinality.Code. @IntDef(prefix = {"CARDINALITY_"}, value = { Loading @@ -159,10 +153,7 @@ public final class AppSearchSchema { /** Exactly one value [1]. */ public static final int CARDINALITY_REQUIRED = 3; /** * Encapsulates the configurations on how AppSearch should query/index these terms. * @hide */ /** Encapsulates the configurations on how AppSearch should query/index these terms. */ @IntDef(prefix = {"INDEXING_TYPE_"}, value = { INDEXING_TYPE_NONE, INDEXING_TYPE_EXACT_TERMS, Loading Loading @@ -197,10 +188,7 @@ public final class AppSearchSchema { */ public static final int INDEXING_TYPE_PREFIXES = 2; /** * Configures how tokens should be extracted from this property. * @hide */ /** Configures how tokens should be extracted from this property. */ // NOTE: The integer values of these constants must match the proto enum constants in // com.google.android.icing.proto.IndexingConfig.TokenizerType.Code. @IntDef(prefix = {"TOKENIZER_TYPE_"}, value = { Loading
apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +10 −0 Original line number Diff line number Diff line Loading @@ -47,6 +47,16 @@ interface IAppSearchManager { */ void putDocuments(in List documentsBytes, in AndroidFuture<AppSearchBatchResult> callback); /** * Retrieves documents from the index. * * @param uris The URIs of the documents to retrieve * @param callback {@link AndroidFuture}<{@link List}<byte[]>>. Will be completed * with a {@link List} containing serialized DocumentProtos, or completed exceptionally if * get fails. */ void getDocuments(in String[] uris, in AndroidFuture callback); /** * Searches a document based on a given specifications. * Loading
apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +24 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import com.google.android.icing.proto.SearchSpecProto; import com.google.android.icing.protobuf.InvalidProtocolBufferException; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** Loading Loading @@ -110,6 +111,29 @@ public class AppSearchManagerService extends SystemService { } } @Override public void getDocuments(String[] uris, AndroidFuture callback) { Preconditions.checkNotNull(uris); Preconditions.checkNotNull(callback); int callingUid = Binder.getCallingUidOrThrow(); int callingUserId = UserHandle.getUserId(callingUid); long callingIdentity = Binder.clearCallingIdentity(); try { AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); // Contains serialized DocumentProto. byte[][] is not transmissible via Binder. List<byte[]> results = new ArrayList<>(uris.length); for (String uri : uris) { DocumentProto result = impl.getDocument(callingUid, uri); results.add(result.toByteArray()); } callback.complete(results); } catch (Throwable t) { callback.completeExceptionally(t); } finally { Binder.restoreCallingIdentity(callingIdentity); } } // TODO(sidchhabra):Init FakeIcing properly. // TODO(sidchhabra): Do this in a threadpool. @Override Loading
apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java +54 −9 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.appsearch.impl; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; Loading Loading @@ -106,30 +107,65 @@ public final class AppSearchImpl { // Rewrite the type names to include the app's prefix String typePrefix = getTypePrefix(callingUid); DocumentProto.Builder documentBuilder = origDocument.toBuilder(); rewriteDocumentTypes(typePrefix, documentBuilder); rewriteDocumentTypes(typePrefix, documentBuilder, /*add=*/ true); mFakeIcing.put(documentBuilder.build()); } /** * Rewrites all types mentioned anywhere in {@code documentBuilder} to prepend * Retrieves a document from the AppSearch index by URI. * * @param callingUid The uid of the app calling AppSearch. * @param uri The URI of the document to get. * @return The Document contents, or {@code null} if no such URI exists in the system. */ @Nullable public DocumentProto getDocument(int callingUid, @NonNull String uri) { String typePrefix = getTypePrefix(callingUid); DocumentProto document = mFakeIcing.get(uri); // Rewrite the type names to remove the app's prefix DocumentProto.Builder documentBuilder = document.toBuilder(); rewriteDocumentTypes(typePrefix, documentBuilder, /*add=*/ false); return documentBuilder.build(); } /** * Rewrites all types mentioned anywhere in {@code documentBuilder} to prepend or remove * {@code typePrefix}. * * @param typePrefix The prefix to add * @param typePrefix The prefix to add or remove * @param documentBuilder The document to mutate * @param add Whether to add typePrefix to the types. If {@code false}, typePrefix will be * removed from the types. * @throws IllegalArgumentException If {@code add=false} and the document has a type that * doesn't start with {@code typePrefix}. */ @VisibleForTesting void rewriteDocumentTypes( @NonNull String typePrefix, @NonNull DocumentProto.Builder documentBuilder) { // Rewrite the type name to include the app's prefix String newSchema = typePrefix + documentBuilder.getSchema(); @NonNull DocumentProto.Builder documentBuilder, boolean add) { // Rewrite the type name to include/remove the app's prefix String newSchema; if (add) { newSchema = typePrefix + documentBuilder.getSchema(); } else { newSchema = removePrefix(typePrefix, documentBuilder.getSchema()); } documentBuilder.setSchema(newSchema); // Add namespace. If we ever allow users to set their own namespaces, this will have // Add/remove namespace. If we ever allow users to set their own namespaces, this will have // to change to prepend the prefix instead of setting the whole namespace. We will also have // to store the namespaces in a map similar to the type map so we can rewrite queries with // empty namespaces. if (add) { documentBuilder.setNamespace(typePrefix); } else if (!documentBuilder.getNamespace().equals(typePrefix)) { throw new IllegalStateException( "Unexpected namespace \"" + documentBuilder.getNamespace() + "\" (expected \"" + typePrefix + "\")"); } else { documentBuilder.clearNamespace(); } // Recurse into derived documents for (int propertyIdx = 0; Loading @@ -142,7 +178,7 @@ public final class AppSearchImpl { for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) { DocumentProto.Builder derivedDocumentBuilder = propertyBuilder.getDocumentValues(documentIdx).toBuilder(); rewriteDocumentTypes(typePrefix, derivedDocumentBuilder); rewriteDocumentTypes(typePrefix, derivedDocumentBuilder, add); propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder); } documentBuilder.setProperties(propertyIdx, propertyBuilder); Loading @@ -165,4 +201,13 @@ public final class AppSearchImpl { } return callingUidName + "@" + mUserId + "/"; } @NonNull private static String removePrefix(@NonNull String prefix, @NonNull String input) { if (!input.startsWith(prefix)) { throw new IllegalArgumentException( "Input \"" + input + "\" does not start with \"" + prefix + "\""); } return input.substring(prefix.length()); } }