Merge "Add AppSearch.java, containing the Document class, base Builder, and Email."
diff --git a/framework/java/android/app/appsearch/AppSearchSchema.java b/framework/java/android/app/appsearch/AppSearchSchema.java
index b5d903a..7e5f187 100644
--- a/framework/java/android/app/appsearch/AppSearchSchema.java
+++ b/framework/java/android/app/appsearch/AppSearchSchema.java
@@ -19,6 +19,8 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import com.google.android.icing.proto.PropertyConfigProto;
 import com.google.android.icing.proto.SchemaProto;
 import com.google.android.icing.proto.SchemaTypeConfigProto;
@@ -70,7 +72,8 @@
      * @hide
      */
     @NonNull
-    SchemaProto getProto() {
+    @VisibleForTesting
+    public SchemaProto getProto() {
         return mProto;
     }
 
@@ -288,7 +291,7 @@
             @NonNull
             public PropertyConfig build() {
                 if (mProtoBuilder.getDataType() == PropertyConfigProto.DataType.Code.UNKNOWN) {
-                    throw new IllegalSchemaException("Missing dataType field");
+                    throw new IllegalSchemaException("Missing field: dataType");
                 }
                 if (mProtoBuilder.getSchemaType().isEmpty()
                         && mProtoBuilder.getDataType()
@@ -299,7 +302,7 @@
                 }
                 if (mProtoBuilder.getCardinality()
                         == PropertyConfigProto.Cardinality.Code.UNKNOWN) {
-                    throw new IllegalSchemaException("Missing cardinality field");
+                    throw new IllegalSchemaException("Missing field: cardinality");
                 }
                 return new PropertyConfig(mProtoBuilder.build());
             }
diff --git a/testing/coretests/src/android/app/appsearch/AppSearchSchemaTest.java b/testing/coretests/src/android/app/appsearch/AppSearchSchemaTest.java
new file mode 100644
index 0000000..0be52c1
--- /dev/null
+++ b/testing/coretests/src/android/app/appsearch/AppSearchSchemaTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2019 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 static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.AppSearchSchema.IndexingConfig;
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.android.icing.proto.IndexingConfig.TokenizerType;
+import com.google.android.icing.proto.PropertyConfigProto;
+import com.google.android.icing.proto.SchemaProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
+import com.google.android.icing.proto.TermMatchType;
+
+import org.junit.Test;
+
+@SmallTest
+public class AppSearchSchemaTest {
+    @Test
+    public void testSuccess() {
+        AppSearchSchema schema = AppSearchSchema.newBuilder()
+                .addType(AppSearchSchema.newSchemaTypeBuilder("Email")
+                        .addProperty(AppSearchSchema.newPropertyBuilder("subject")
+                                .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+                                        .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_PREFIX)
+                                        .build()
+                                ).build()
+                        ).addProperty(AppSearchSchema.newPropertyBuilder("body")
+                                .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+                                        .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_PREFIX)
+                                        .build()
+                                ).build()
+                        ).build()
+
+                ).addType(AppSearchSchema.newSchemaTypeBuilder("MusicRecording")
+                        .addProperty(AppSearchSchema.newPropertyBuilder("artist")
+                                .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
+                                .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+                                        .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_PREFIX)
+                                        .build()
+                                ).build()
+                        ).addProperty(AppSearchSchema.newPropertyBuilder("pubDate")
+                                .setDataType(PropertyConfig.DATA_TYPE_INT64)
+                                .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+                                        .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_NONE)
+                                        .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_UNKNOWN)
+                                        .build()
+                                ).build()
+                        ).build()
+                ).build();
+
+        SchemaProto expectedProto = SchemaProto.newBuilder()
+                .addTypes(SchemaTypeConfigProto.newBuilder()
+                        .setSchemaType("Email")
+                        .addProperties(PropertyConfigProto.newBuilder()
+                                .setPropertyName("subject")
+                                .setDataType(PropertyConfigProto.DataType.Code.STRING)
+                                .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+                                .setIndexingConfig(
+                                        com.google.android.icing.proto.IndexingConfig.newBuilder()
+                                                .setTokenizerType(TokenizerType.Code.PLAIN)
+                                                .setTermMatchType(TermMatchType.Code.PREFIX)
+                                )
+                        ).addProperties(PropertyConfigProto.newBuilder()
+                                .setPropertyName("body")
+                                .setDataType(PropertyConfigProto.DataType.Code.STRING)
+                                .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+                                .setIndexingConfig(
+                                        com.google.android.icing.proto.IndexingConfig.newBuilder()
+                                                .setTokenizerType(TokenizerType.Code.PLAIN)
+                                                .setTermMatchType(TermMatchType.Code.PREFIX)
+                                )
+                        )
+
+                ).addTypes(SchemaTypeConfigProto.newBuilder()
+                        .setSchemaType("MusicRecording")
+                        .addProperties(PropertyConfigProto.newBuilder()
+                                .setPropertyName("artist")
+                                .setDataType(PropertyConfigProto.DataType.Code.STRING)
+                                .setCardinality(PropertyConfigProto.Cardinality.Code.REPEATED)
+                                .setIndexingConfig(
+                                        com.google.android.icing.proto.IndexingConfig.newBuilder()
+                                                .setTokenizerType(TokenizerType.Code.PLAIN)
+                                                .setTermMatchType(TermMatchType.Code.PREFIX)
+                                )
+                        ).addProperties(PropertyConfigProto.newBuilder()
+                                .setPropertyName("pubDate")
+                                .setDataType(PropertyConfigProto.DataType.Code.INT64)
+                                .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+                                .setIndexingConfig(
+                                        com.google.android.icing.proto.IndexingConfig.newBuilder()
+                                                .setTokenizerType(TokenizerType.Code.NONE)
+                                                .setTermMatchType(TermMatchType.Code.UNKNOWN)
+                                )
+                        )
+                ).build();
+
+        assertThat(schema.getProto()).isEqualTo(expectedProto);
+    }
+
+    @Test
+    public void testInvalidEnums() {
+        PropertyConfig.Builder builder = AppSearchSchema.newPropertyBuilder("test");
+        assertThrows(IllegalArgumentException.class, () -> builder.setDataType(99));
+        assertThrows(IllegalArgumentException.class, () -> builder.setCardinality(99));
+    }
+
+    @Test
+    public void testMissingFields() {
+        PropertyConfig.Builder builder = AppSearchSchema.newPropertyBuilder("test");
+        Exception e = expectThrows(IllegalSchemaException.class, builder::build);
+        assertThat(e).hasMessageThat().contains("Missing field: dataType");
+
+        builder.setDataType(PropertyConfig.DATA_TYPE_DOCUMENT);
+        e = expectThrows(IllegalSchemaException.class, builder::build);
+        assertThat(e).hasMessageThat().contains("Missing field: schemaType");
+
+        builder.setSchemaType("TestType");
+        e = expectThrows(IllegalSchemaException.class, builder::build);
+        assertThat(e).hasMessageThat().contains("Missing field: cardinality");
+
+        builder.setCardinality(PropertyConfig.CARDINALITY_REPEATED);
+        builder.build();
+    }
+}