Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2e225be9 authored by Alexander Dorokhine's avatar Alexander Dorokhine
Browse files

Implement getDocuments() support in AppSearchImpl.

Bug: 146526096
Test: AppSearchManagerTest
Change-Id: I013ac2f017ce138143bd9c0f1d6bfd92f880dd2c
parent 659a8bc0
Loading
Loading
Loading
Loading
+48 −3
Original line number Original line Diff line number Diff line
@@ -24,6 +24,7 @@ import android.os.RemoteException;


import com.android.internal.infra.AndroidFuture;
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.SchemaProto;
import com.google.android.icing.proto.SearchResultProto;
import com.google.android.icing.proto.SearchResultProto;
import com.google.android.icing.proto.SearchSpecProto;
import com.google.android.icing.proto.SearchSpecProto;
@@ -180,6 +181,53 @@ public class AppSearchManager {
        return getFutureOrThrow(future);
        return getFutureOrThrow(future);
    }
    }


    /**
     * Retrieves {@link android.app.appsearch.AppSearch.Document}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<AppSearch.Document>, ? 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<AppSearch.Document> 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 AppSearch.Document(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
     * This method searches for documents based on a given query string. It also accepts
     * specifications regarding how to search and format the results.
     * specifications regarding how to search and format the results.
@@ -237,7 +285,6 @@ public class AppSearchManager {
                callback.accept(null, err);
                callback.accept(null, err);
                return;
                return;
            }
            }

            if (searchResultBytes != null) {
            if (searchResultBytes != null) {
                SearchResultProto searchResultProto;
                SearchResultProto searchResultProto;
                try {
                try {
@@ -257,12 +304,10 @@ public class AppSearchManager {
                callback.accept(searchResults, null);
                callback.accept(searchResults, null);
                return;
                return;
            }
            }

            // Nothing was supplied in the future at all
            // Nothing was supplied in the future at all
            callback.accept(
            callback.accept(
                    null, new IllegalStateException("Unknown failure occurred while querying"));
                    null, new IllegalStateException("Unknown failure occurred while querying"));
        }, executor);
        }, executor);

        try {
        try {
            SearchSpecProto searchSpecProto = searchSpec.getSearchSpecProto();
            SearchSpecProto searchSpecProto = searchSpec.getSearchSpecProto();
            searchSpecProto = searchSpecProto.toBuilder().setQuery(queryExpression).build();
            searchSpecProto = searchSpecProto.toBuilder().setQuery(queryExpression).build();
+10 −0
Original line number Original line Diff line number Diff line
@@ -46,6 +46,16 @@ interface IAppSearchManager {
     */
     */
    void putDocuments(in List documentsBytes, in AndroidFuture<AppSearchBatchResult> callback);
    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}&lt;{@link List}&lt;byte[]&gt;&gt;. 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.
     * Searches a document based on a given specifications.
     *
     *
+25 −0
Original line number Original line Diff line number Diff line
@@ -35,6 +35,7 @@ import com.google.android.icing.proto.SearchResultProto;
import com.google.android.icing.proto.SearchSpecProto;
import com.google.android.icing.proto.SearchSpecProto;
import com.google.android.icing.protobuf.InvalidProtocolBufferException;
import com.google.android.icing.protobuf.InvalidProtocolBufferException;


import java.util.ArrayList;
import java.util.List;
import java.util.List;


/**
/**
@@ -103,6 +104,30 @@ public class AppSearchManagerService extends SystemService {
                Binder.restoreCallingIdentity(callingIdentity);
                Binder.restoreCallingIdentity(callingIdentity);
            }
            }
        }
        }

        @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):Init FakeIcing properly.
        // TODO(sidchhabra): Do this in a threadpool.
        // TODO(sidchhabra): Do this in a threadpool.
        @Override
        @Override
+54 −9
Original line number Original line Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.server.appsearch.impl;
package com.android.server.appsearch.impl;


import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.Context;


@@ -106,30 +107,65 @@ public final class AppSearchImpl {
        // Rewrite the type names to include the app's prefix
        // Rewrite the type names to include the app's prefix
        String typePrefix = getTypePrefix(callingUid);
        String typePrefix = getTypePrefix(callingUid);
        DocumentProto.Builder documentBuilder = origDocument.toBuilder();
        DocumentProto.Builder documentBuilder = origDocument.toBuilder();
        rewriteDocumentTypes(typePrefix, documentBuilder);
        rewriteDocumentTypes(typePrefix, documentBuilder, /*add=*/ true);
        mFakeIcing.put(documentBuilder.build());
        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}.
     * {@code typePrefix}.
     *
     *
     * @param typePrefix The prefix to add
     * @param typePrefix The prefix to add or remove
     * @param documentBuilder The document to mutate
     * @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
    @VisibleForTesting
    void rewriteDocumentTypes(
    void rewriteDocumentTypes(
            @NonNull String typePrefix,
            @NonNull String typePrefix,
            @NonNull DocumentProto.Builder documentBuilder) {
            @NonNull DocumentProto.Builder documentBuilder,
        // Rewrite the type name to include the app's prefix
            boolean add) {
        String newSchema = typePrefix + documentBuilder.getSchema();
        // 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);
        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 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
        // to store the namespaces in a map similar to the type map so we can rewrite queries with
        // empty namespaces.
        // empty namespaces.
        if (add) {
            documentBuilder.setNamespace(typePrefix);
            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
        // Recurse into derived documents
        for (int propertyIdx = 0;
        for (int propertyIdx = 0;
@@ -142,7 +178,7 @@ public final class AppSearchImpl {
                for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
                for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
                    DocumentProto.Builder derivedDocumentBuilder =
                    DocumentProto.Builder derivedDocumentBuilder =
                            propertyBuilder.getDocumentValues(documentIdx).toBuilder();
                            propertyBuilder.getDocumentValues(documentIdx).toBuilder();
                    rewriteDocumentTypes(typePrefix, derivedDocumentBuilder);
                    rewriteDocumentTypes(typePrefix, derivedDocumentBuilder, add);
                    propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
                    propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
                }
                }
                documentBuilder.setProperties(propertyIdx, propertyBuilder);
                documentBuilder.setProperties(propertyIdx, propertyBuilder);
@@ -165,4 +201,13 @@ public final class AppSearchImpl {
        }
        }
        return callingUidName + "@" + mUserId + "/";
        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());
    }
}
}