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

Commit 68b8332e authored by Terry Wang's avatar Terry Wang Committed by Android (Google) Code Review
Browse files

Merge "Add AppSearch.java, containing the Document class, base Builder, and Email."

parents 6df25f76 0731f571
Loading
Loading
Loading
Loading
+762 −0

File added.

Preview size limit exceeded, changes collapsed.

+32 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.app.appsearch;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.SystemService;
import android.app.appsearch.AppSearch.Document;
import android.content.Context;
import android.os.RemoteException;

@@ -25,6 +26,7 @@ import com.android.internal.infra.AndroidFuture;

import com.google.android.icing.proto.SchemaProto;

import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.Consumer;

@@ -95,4 +97,34 @@ public class AppSearchManager {
        }
        future.whenCompleteAsync((noop, err) -> callback.accept(err), executor);
    }

    /**
     * Index {@link Document} to AppSearch
     *
     * <p>You should not call this method directly; instead, use the {@code AppSearch#put()} API
     * provided by JetPack.
     *
     * <p>The schema should be set via {@link #setSchema} method.
     *
     * @param documents {@link Document Documents} that need to be indexed.
     * @param executor Executor on which to invoke the callback.
     * @param callback Callback to receive errors resulting from setting the schema. If the
     *                 operation succeeds, the callback will be invoked with {@code null}.
     */
    public void put(@NonNull List<Document> documents,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull Consumer<? super Throwable> callback) {
        AndroidFuture<Void> future = new AndroidFuture<>();
        for (Document document : documents) {
            // TODO(b/146386470) batching Document protos
            try {
                mService.put(document.getProto().toByteArray(), future);
            } catch (RemoteException e) {
                future.completeExceptionally(e);
                break;
            }
        }
        // TODO(b/147614371) Fix error report for multiple documents.
        future.whenCompleteAsync((noop, err) -> callback.accept(err), executor);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -28,4 +28,5 @@ interface IAppSearchManager {
     *     if setSchema fails.
     */
    void setSchema(in byte[] schemaProto, in AndroidFuture callback);
    void put(in byte[] documentBytes, in AndroidFuture callback);
}
+10 −0
Original line number Diff line number Diff line
@@ -43,6 +43,16 @@ public class AppSearchManagerService extends SystemService {
            try {
                SchemaProto schema = SchemaProto.parseFrom(schemaBytes);
                throw new UnsupportedOperationException("setSchema not yet implemented: " + schema);

            } catch (Throwable t) {
                callback.completeExceptionally(t);
            }
        }

        @Override
        public void put(byte[] documentBytes, AndroidFuture callback) {
            try {
                throw new UnsupportedOperationException("Put document not yet implemented");
            } catch (Throwable t) {
                callback.completeExceptionally(t);
            }
+211 −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 static com.google.common.truth.Truth.assertThat;

import static org.testng.Assert.assertThrows;

import android.app.appsearch.AppSearch.Document;

import androidx.test.filters.SmallTest;

import com.google.android.icing.proto.DocumentProto;
import com.google.android.icing.proto.PropertyProto;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

@SmallTest
public class AppSearchDocumentTest {

    @Test
    public void testDocumentEquals_Identical() {
        Document document1 = Document.newBuilder("uri1", "schemaType1")
                .setCreationTimestampSecs(0L)
                .setProperty("longKey1", 1L, 2L, 3L)
                .setProperty("doubleKey1", 1.0, 2.0, 3.0)
                .setProperty("booleanKey1", true, false, true)
                .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
                .build();
        Document document2 = Document.newBuilder("uri1", "schemaType1")
                .setCreationTimestampSecs(0L)
                .setProperty("longKey1", 1L, 2L, 3L)
                .setProperty("doubleKey1", 1.0, 2.0, 3.0)
                .setProperty("booleanKey1", true, false, true)
                .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
                .build();
        assertThat(document1).isEqualTo(document2);
        assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
    }

    @Test
    public void testDocumentEquals_DifferentOrder() {
        Document document1 = Document.newBuilder("uri1", "schemaType1")
                .setCreationTimestampSecs(0L)
                .setProperty("longKey1", 1L, 2L, 3L)
                .setProperty("doubleKey1", 1.0, 2.0, 3.0)
                .setProperty("booleanKey1", true, false, true)
                .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
                .build();

        // Create second document with same parameter but different order.
        Document document2 = Document.newBuilder("uri1", "schemaType1")
                .setCreationTimestampSecs(0L)
                .setProperty("booleanKey1", true, false, true)
                .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
                .setProperty("doubleKey1", 1.0, 2.0, 3.0)
                .setProperty("longKey1", 1L, 2L, 3L)
                .build();
        assertThat(document1).isEqualTo(document2);
        assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
    }

    @Test
    public void testDocumentEquals_Failure() {
        Document document1 = Document.newBuilder("uri1", "schemaType1")
                .setProperty("longKey1", 1L, 2L, 3L)
                .build();

        // Create second document with same order but different value.
        Document document2 = Document.newBuilder("uri1", "schemaType1")
                .setProperty("longKey1", 1L, 2L, 4L) // Different
                .build();
        assertThat(document1).isNotEqualTo(document2);
        assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
    }

    @Test
    public void testDocumentEquals_Failure_RepeatedFieldOrder() {
        Document document1 = Document.newBuilder("uri1", "schemaType1")
                .setProperty("booleanKey1", true, false, true)
                .build();

        // Create second document with same order but different value.
        Document document2 = Document.newBuilder("uri1", "schemaType1")
                .setProperty("booleanKey1", true, true, false) // Different
                .build();
        assertThat(document1).isNotEqualTo(document2);
        assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
    }

    @Test
    public void testDocumentGetSingleValue() {
        Document document = Document.newBuilder("uri1", "schemaType1")
                .setProperty("longKey1", 1L)
                .setProperty("doubleKey1", 1.0)
                .setProperty("booleanKey1", true)
                .setProperty("stringKey1", "test-value1").build();
        assertThat(document.getUri()).isEqualTo("uri1");
        assertThat(document.getSchemaType()).isEqualTo("schemaType1");
        assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
        assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(1.0);
        assertThat(document.getPropertyBoolean("booleanKey1")).isTrue();
        assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
    }

    @Test
    public void testDocumentGetArrayValues() {
        Document document = Document.newBuilder("uri1", "schemaType1")
                .setScore(1)
                .setProperty("longKey1", 1L, 2L, 3L)
                .setProperty("doubleKey1", 1.0, 2.0, 3.0)
                .setProperty("booleanKey1", true, false, true)
                .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
                .build();

        assertThat(document.getUri()).isEqualTo("uri1");
        assertThat(document.getSchemaType()).isEqualTo("schemaType1");
        assertThat(document.getScore()).isEqualTo(1);
        assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L, 2L, 3L);
        assertThat(document.getPropertyDoubleArray("doubleKey1")).usingExactEquality()
                .containsExactly(1.0, 2.0, 3.0);
        assertThat(document.getPropertyBooleanArray("booleanKey1")).asList()
                .containsExactly(true, false, true);
        assertThat(document.getPropertyStringArray("stringKey1")).asList()
                .containsExactly("test-value1", "test-value2", "test-value3");
    }

    @Test
    public void testDocumentGetValues_DifferentTypes() {
        Document document = Document.newBuilder("uri1", "schemaType1")
                .setScore(1)
                .setProperty("longKey1", 1L)
                .setProperty("booleanKey1", true, false, true)
                .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
                .build();

        // Get a value for a key that doesn't exist
        assertThat(document.getPropertyDouble("doubleKey1")).isNull();
        assertThat(document.getPropertyDoubleArray("doubleKey1")).isNull();

        // Get a value with a single element as an array and as a single value
        assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
        assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L);

        // Get a value with multiple elements as an array and as a single value
        assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
        assertThat(document.getPropertyStringArray("stringKey1")).asList()
                .containsExactly("test-value1", "test-value2", "test-value3");

        // Get a value of the wrong type
        assertThat(document.getPropertyDouble("longKey1")).isNull();
        assertThat(document.getPropertyDoubleArray("longKey1")).isNull();
    }

    @Test
    public void testDocumentInvalid() {
        Document.Builder builder = Document.newBuilder("uri1", "schemaType1");
        assertThrows(
                IllegalArgumentException.class, () -> builder.setProperty("test", new boolean[]{}));
    }

    @Test
    public void testDocumentProtoPopulation() {
        Document document = Document.newBuilder("uri1", "schemaType1")
                .setScore(1)
                .setCreationTimestampSecs(0)
                .setProperty("longKey1", 1L)
                .setProperty("doubleKey1", 1.0)
                .setProperty("booleanKey1", true)
                .setProperty("stringKey1", "test-value1")
                .build();

        // Create the Document proto. Need to sort the property order by key.
        DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder()
                .setUri("uri1").setSchema("schemaType1").setScore(1).setCreationTimestampSecs(0);
        HashMap<String, PropertyProto.Builder> propertyProtoMap = new HashMap<>();
        propertyProtoMap.put("longKey1",
                PropertyProto.newBuilder().setName("longKey1").addInt64Values(1L));
        propertyProtoMap.put("doubleKey1",
                PropertyProto.newBuilder().setName("doubleKey1").addDoubleValues(1.0));
        propertyProtoMap.put("booleanKey1",
                PropertyProto.newBuilder().setName("booleanKey1").addBooleanValues(true));
        propertyProtoMap.put("stringKey1",
                PropertyProto.newBuilder().setName("stringKey1").addStringValues("test-value1"));
        List<String> sortedKey = new ArrayList<>(propertyProtoMap.keySet());
        Collections.sort(sortedKey);
        for (String key : sortedKey) {
            documentProtoBuilder.addProperties(propertyProtoMap.get(key));
        }
        assertThat(document.getProto()).isEqualTo(documentProtoBuilder.build());
    }
}
Loading