• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <jni.h>
18 
19 #include <memory>
20 #include <vector>
21 
22 #include "utils/flatbuffers/mutable.h"
23 #include "utils/intents/intent-generator.h"
24 #include "utils/intents/remote-action-template.h"
25 #include "utils/java/jni-helper.h"
26 #include "utils/jvm-test-utils.h"
27 #include "utils/resources_generated.h"
28 #include "utils/testing/logging_event_listener.h"
29 #include "utils/variant.h"
30 #include "gmock/gmock.h"
31 #include "gtest/gtest.h"
32 #include "flatbuffers/reflection.h"
33 
34 namespace libtextclassifier3 {
35 namespace {
36 
37 using ::testing::ElementsAre;
38 using ::testing::IsEmpty;
39 using ::testing::SizeIs;
40 
BuildTestIntentFactoryModel(const std::string & entity_type,const std::string & generator_code)41 flatbuffers::DetachedBuffer BuildTestIntentFactoryModel(
42     const std::string& entity_type, const std::string& generator_code) {
43   // Test intent generation options.
44   IntentFactoryModelT options;
45   options.generator.emplace_back(new IntentFactoryModel_::IntentGeneratorT());
46   options.generator.back()->type = entity_type;
47   options.generator.back()->lua_template_generator = std::vector<unsigned char>(
48       generator_code.data(), generator_code.data() + generator_code.size());
49   flatbuffers::FlatBufferBuilder builder;
50   builder.Finish(IntentFactoryModel::Pack(builder, &options));
51   return builder.Release();
52 }
53 
BuildTestResources()54 flatbuffers::DetachedBuffer BuildTestResources() {
55   // Custom string resources.
56   ResourcePoolT test_resources;
57   test_resources.locale.emplace_back(new LanguageTagT);
58   test_resources.locale.back()->language = "en";
59   test_resources.locale.emplace_back(new LanguageTagT);
60   test_resources.locale.back()->language = "de";
61 
62   // Add `add_calendar_event`
63   test_resources.resource_entry.emplace_back(new ResourceEntryT);
64   test_resources.resource_entry.back()->name = "add_calendar_event";
65 
66   // en
67   test_resources.resource_entry.back()->resource.emplace_back(new ResourceT);
68   test_resources.resource_entry.back()->resource.back()->content = "Schedule";
69   test_resources.resource_entry.back()->resource.back()->locale.push_back(0);
70 
71   // Add `add_calendar_event_desc`
72   test_resources.resource_entry.emplace_back(new ResourceEntryT);
73   test_resources.resource_entry.back()->name = "add_calendar_event_desc";
74 
75   // en
76   test_resources.resource_entry.back()->resource.emplace_back(new ResourceT);
77   test_resources.resource_entry.back()->resource.back()->content =
78       "Schedule event for selected time";
79   test_resources.resource_entry.back()->resource.back()->locale.push_back(0);
80 
81   // Add `map`.
82   test_resources.resource_entry.emplace_back(new ResourceEntryT);
83   test_resources.resource_entry.back()->name = "map";
84 
85   // en
86   test_resources.resource_entry.back()->resource.emplace_back(new ResourceT);
87   test_resources.resource_entry.back()->resource.back()->content = "Map";
88   test_resources.resource_entry.back()->resource.back()->locale.push_back(0);
89 
90   // de
91   test_resources.resource_entry.back()->resource.emplace_back(new ResourceT);
92   test_resources.resource_entry.back()->resource.back()->content = "Karte";
93   test_resources.resource_entry.back()->resource.back()->locale.push_back(1);
94 
95   // Add `map_desc`.
96   test_resources.resource_entry.emplace_back(new ResourceEntryT);
97   test_resources.resource_entry.back()->name = "map_desc";
98 
99   // en
100   test_resources.resource_entry.back()->resource.emplace_back(new ResourceT);
101   test_resources.resource_entry.back()->resource.back()->content =
102       "Locate selected address";
103   test_resources.resource_entry.back()->resource.back()->locale.push_back(0);
104 
105   // de
106   test_resources.resource_entry.back()->resource.emplace_back(new ResourceT);
107   test_resources.resource_entry.back()->resource.back()->content =
108       "Ausgewählte Adresse finden";
109   test_resources.resource_entry.back()->resource.back()->locale.push_back(1);
110 
111   flatbuffers::FlatBufferBuilder builder;
112   builder.Finish(ResourcePool::Pack(builder, &test_resources));
113   return builder.Release();
114 }
115 
116 // Common methods for intent generator tests.
117 class IntentGeneratorTest : public testing::Test {
118  protected:
IntentGeneratorTest()119   explicit IntentGeneratorTest()
120       : jni_cache_(JniCache::Create(GetJenv())),
121         resource_buffer_(BuildTestResources()),
122         resources_(
123             flatbuffers::GetRoot<ResourcePool>(resource_buffer_.data())) {}
124 
125   const std::shared_ptr<JniCache> jni_cache_;
126   const flatbuffers::DetachedBuffer resource_buffer_;
127   const ResourcePool* resources_;
128 };
129 
TEST_F(IntentGeneratorTest,HandlesDefaultClassification)130 TEST_F(IntentGeneratorTest, HandlesDefaultClassification) {
131   flatbuffers::DetachedBuffer intent_factory_model =
132       BuildTestIntentFactoryModel("unused", "");
133   std::unique_ptr<IntentGenerator> generator = IntentGenerator::Create(
134       /*options=*/flatbuffers::GetRoot<IntentFactoryModel>(
135           intent_factory_model.data()),
136       /*resources=*/resources_,
137       /*jni_cache=*/jni_cache_);
138   ClassificationResult classification;
139   std::vector<RemoteActionTemplate> intents;
140   EXPECT_TRUE(generator->GenerateIntents(
141       /*device_locales=*/nullptr, classification, /*reference_time_ms_utc=*/0,
142       /*text=*/"", /*selection_indices=*/{kInvalidIndex, kInvalidIndex},
143       /*context=*/nullptr,
144       /*annotations_entity_data_schema=*/nullptr, &intents));
145   EXPECT_THAT(intents, IsEmpty());
146 }
147 
TEST_F(IntentGeneratorTest,FailsGracefully)148 TEST_F(IntentGeneratorTest, FailsGracefully) {
149   flatbuffers::DetachedBuffer intent_factory_model =
150       BuildTestIntentFactoryModel("test", R"lua(
151 return {
152   {
153     -- Should fail, as no app GetAndroidContext() is provided.
154     data = external.android.package_name,
155   }
156 })lua");
157   std::unique_ptr<IntentGenerator> generator = IntentGenerator::Create(
158       flatbuffers::GetRoot<IntentFactoryModel>(intent_factory_model.data()),
159       /*resources=*/resources_, jni_cache_);
160   ClassificationResult classification = {"test", 1.0};
161   std::vector<RemoteActionTemplate> intents;
162   EXPECT_FALSE(generator->GenerateIntents(
163       JniHelper::NewStringUTF(GetJenv(), "en-US").ValueOrDie().get(),
164       classification,
165       /*reference_time_ms_utc=*/0, "test", {0, 4}, /*context=*/nullptr,
166       /*annotations_entity_data_schema=*/nullptr, &intents));
167   EXPECT_THAT(intents, IsEmpty());
168 }
169 
TEST_F(IntentGeneratorTest,HandlesEntityIntentGeneration)170 TEST_F(IntentGeneratorTest, HandlesEntityIntentGeneration) {
171   flatbuffers::DetachedBuffer intent_factory_model =
172       BuildTestIntentFactoryModel("address", R"lua(
173 return {
174   {
175     title_without_entity = external.android.R.map,
176     title_with_entity = external.entity.text,
177     description = external.android.R.map_desc,
178     action = "android.intent.action.VIEW",
179     data = "geo:0,0?q=" ..
180     external.android.urlencode(external.entity.text),
181   }
182 })lua");
183   std::unique_ptr<IntentGenerator> generator = IntentGenerator::Create(
184       flatbuffers::GetRoot<IntentFactoryModel>(intent_factory_model.data()),
185       /*resources=*/resources_, jni_cache_);
186   ClassificationResult classification = {"address", 1.0};
187   std::vector<RemoteActionTemplate> intents;
188   EXPECT_TRUE(generator->GenerateIntents(
189       JniHelper::NewStringUTF(GetJenv(), "en-US").ValueOrDie().get(),
190       classification,
191       /*reference_time_ms_utc=*/0, "333 E Wonderview Ave", {0, 20},
192       GetAndroidContext(),
193       /*annotations_entity_data_schema=*/nullptr, &intents));
194   EXPECT_THAT(intents, SizeIs(1));
195   EXPECT_EQ(intents[0].title_without_entity.value(), "Map");
196   EXPECT_EQ(intents[0].title_with_entity.value(), "333 E Wonderview Ave");
197   EXPECT_EQ(intents[0].description.value(), "Locate selected address");
198   EXPECT_EQ(intents[0].action.value(), "android.intent.action.VIEW");
199   EXPECT_EQ(intents[0].data.value(), "geo:0,0?q=333%20E%20Wonderview%20Ave");
200 }
201 
TEST_F(IntentGeneratorTest,HandlesCallbacks)202 TEST_F(IntentGeneratorTest, HandlesCallbacks) {
203   flatbuffers::DetachedBuffer intent_factory_model =
204       BuildTestIntentFactoryModel("test", R"lua(
205 local test = external.entity["text"]
206 return {
207   {
208     data = "encoded=" .. external.android.urlencode(test),
209     category = { "test_category" },
210     extra = {
211       { name = "package", string_value = external.android.package_name},
212       { name = "scheme",
213         string_value = external.android.url_schema("https://google.com")},
214       { name = "host",
215         string_value = external.android.url_host("https://google.com/search")},
216       { name = "permission",
217         bool_value = external.android.user_restrictions["no_sms"] },
218       { name = "language",
219         string_value = external.android.device_locales[1].language },
220       { name = "description",
221         string_value = external.format("$1 $0", "hello", "world") },
222     },
223     request_code = external.hash(test)
224   }
225 })lua");
226   std::unique_ptr<IntentGenerator> generator = IntentGenerator::Create(
227       flatbuffers::GetRoot<IntentFactoryModel>(intent_factory_model.data()),
228       /*resources=*/resources_, jni_cache_);
229   ClassificationResult classification = {"test", 1.0};
230   std::vector<RemoteActionTemplate> intents;
231   EXPECT_TRUE(generator->GenerateIntents(
232       JniHelper::NewStringUTF(GetJenv(), "en-US").ValueOrDie().get(),
233       classification,
234       /*reference_time_ms_utc=*/0, "this is a test", {0, 14},
235       GetAndroidContext(),
236       /*annotations_entity_data_schema=*/nullptr, &intents));
237   EXPECT_THAT(intents, SizeIs(1));
238   EXPECT_EQ(intents[0].data.value(), "encoded=this%20is%20a%20test");
239   EXPECT_THAT(intents[0].category, ElementsAre("test_category"));
240   EXPECT_THAT(intents[0].extra, SizeIs(6));
241   EXPECT_EQ(intents[0].extra["package"].ConstRefValue<std::string>(),
242             "com.google.android.textclassifier.tests"
243   );
244   EXPECT_EQ(intents[0].extra["scheme"].ConstRefValue<std::string>(), "https");
245   EXPECT_EQ(intents[0].extra["host"].ConstRefValue<std::string>(),
246             "google.com");
247   EXPECT_FALSE(intents[0].extra["permission"].Value<bool>());
248   EXPECT_EQ(intents[0].extra["language"].ConstRefValue<std::string>(), "en");
249   EXPECT_TRUE(intents[0].request_code.has_value());
250   EXPECT_EQ(intents[0].extra["description"].ConstRefValue<std::string>(),
251             "world hello");
252 }
253 
TEST_F(IntentGeneratorTest,HandlesActionIntentGeneration)254 TEST_F(IntentGeneratorTest, HandlesActionIntentGeneration) {
255   flatbuffers::DetachedBuffer intent_factory_model =
256       BuildTestIntentFactoryModel("view_map", R"lua(
257 return {
258   {
259     title_without_entity = external.android.R.map,
260     description = external.android.R.map_desc,
261     description_with_app_name = external.android.R.map,
262     action = "android.intent.action.VIEW",
263     data = "geo:0,0?q=" ..
264     external.android.urlencode(external.entity.annotation["location"].text),
265   }
266 })lua");
267   std::unique_ptr<IntentGenerator> generator = IntentGenerator::Create(
268       flatbuffers::GetRoot<IntentFactoryModel>(intent_factory_model.data()),
269       /*resources=*/resources_, jni_cache_);
270   Conversation conversation = {{{/*user_id=*/1, "hello there"}}};
271   ActionSuggestionAnnotation annotation;
272   annotation.entity = {"address", 1.0};
273   annotation.span = {/*message_index=*/0,
274                      /*span=*/{6, 11},
275                      /*text=*/"there"};
276   annotation.name = "location";
277   ActionSuggestion suggestion = {/*response_text=""*/ "",
278                                  /*type=*/"view_map",
279                                  /*score=*/1.0,
280                                  /*priority_score=*/0.0,
281                                  /*annotations=*/
282                                  {annotation}};
283   std::vector<RemoteActionTemplate> intents;
284   EXPECT_TRUE(generator->GenerateIntents(
285       JniHelper::NewStringUTF(GetJenv(), "en-US").ValueOrDie().get(),
286       suggestion, conversation, GetAndroidContext(),
287       /*annotations_entity_data_schema=*/nullptr,
288       /*actions_entity_data_schema=*/nullptr, &intents));
289   EXPECT_THAT(intents, SizeIs(1));
290   EXPECT_EQ(intents[0].title_without_entity.value(), "Map");
291   EXPECT_EQ(intents[0].description.value(), "Locate selected address");
292   EXPECT_EQ(intents[0].description_with_app_name.value(), "Map");
293   EXPECT_EQ(intents[0].action.value(), "android.intent.action.VIEW");
294   EXPECT_EQ(intents[0].data.value(), "geo:0,0?q=there");
295 }
296 
TEST_F(IntentGeneratorTest,HandlesTimezoneAndReferenceTime)297 TEST_F(IntentGeneratorTest, HandlesTimezoneAndReferenceTime) {
298   flatbuffers::DetachedBuffer intent_factory_model =
299       BuildTestIntentFactoryModel("test", R"lua(
300 local conversation = external.conversation
301 return {
302   {
303     extra = {
304       { name = "timezone", string_value = conversation[#conversation].timezone },
305       { name = "num_messages", int_value = #conversation },
306       { name = "reference_time", long_value = conversation[#conversation].time_ms_utc }
307     },
308   }
309 })lua");
310   std::unique_ptr<IntentGenerator> generator = IntentGenerator::Create(
311       flatbuffers::GetRoot<IntentFactoryModel>(intent_factory_model.data()),
312       /*resources=*/resources_, jni_cache_);
313   Conversation conversation = {
314       {{/*user_id=*/0, "hello there", /*reference_time_ms_utc=*/0,
315         /*reference_timezone=*/"Testing/Test"},
316        {/*user_id=*/1, "general retesti", /*reference_time_ms_utc=*/1000,
317         /*reference_timezone=*/"Europe/Zurich"}}};
318   ActionSuggestion suggestion = {/*response_text=""*/ "",
319                                  /*type=*/"test",
320                                  /*score=*/1.0,
321                                  /*priority_score=*/0.0,
322                                  /*annotations=*/
323                                  {}};
324   std::vector<RemoteActionTemplate> intents;
325   EXPECT_TRUE(generator->GenerateIntents(
326       JniHelper::NewStringUTF(GetJenv(), "en-US").ValueOrDie().get(),
327       suggestion, conversation, GetAndroidContext(),
328       /*annotations_entity_data_schema=*/nullptr,
329       /*actions_entity_data_schema=*/nullptr, &intents));
330   EXPECT_THAT(intents, SizeIs(1));
331   EXPECT_EQ(intents[0].extra["timezone"].ConstRefValue<std::string>(),
332             "Europe/Zurich");
333   EXPECT_EQ(intents[0].extra["num_messages"].Value<int>(), 2);
334   EXPECT_EQ(intents[0].extra["reference_time"].Value<int64>(), 1000);
335 }
336 
TEST_F(IntentGeneratorTest,HandlesActionIntentGenerationMultipleAnnotations)337 TEST_F(IntentGeneratorTest, HandlesActionIntentGenerationMultipleAnnotations) {
338   flatbuffers::DetachedBuffer intent_factory_model =
339       BuildTestIntentFactoryModel("create_event", R"lua(
340 return {
341   {
342     title_without_entity = external.android.R.add_calendar_event,
343     description = external.android.R.add_calendar_event_desc,
344     extra = {
345       {name = "time", string_value =
346        external.entity.annotation["time"].text},
347       {name = "location",
348        string_value = external.entity.annotation["location"].text},
349     }
350   }
351 })lua");
352   std::unique_ptr<IntentGenerator> generator = IntentGenerator::Create(
353       flatbuffers::GetRoot<IntentFactoryModel>(intent_factory_model.data()),
354       /*resources=*/resources_, jni_cache_);
355   Conversation conversation = {{{/*user_id=*/1, "hello there at 1pm"}}};
356   ActionSuggestionAnnotation location_annotation, time_annotation;
357   location_annotation.entity = {"address", 1.0};
358   location_annotation.span = {/*message_index=*/0,
359                               /*span=*/{6, 11},
360                               /*text=*/"there"};
361   location_annotation.name = "location";
362   time_annotation.entity = {"datetime", 1.0};
363   time_annotation.span = {/*message_index=*/0,
364                           /*span=*/{15, 18},
365                           /*text=*/"1pm"};
366   time_annotation.name = "time";
367   ActionSuggestion suggestion = {/*response_text=""*/ "",
368                                  /*type=*/"create_event",
369                                  /*score=*/1.0,
370                                  /*priority_score=*/0.0,
371                                  /*annotations=*/
372                                  {location_annotation, time_annotation}};
373   std::vector<RemoteActionTemplate> intents;
374   EXPECT_TRUE(generator->GenerateIntents(
375       JniHelper::NewStringUTF(GetJenv(), "en-US").ValueOrDie().get(),
376       suggestion, conversation, GetAndroidContext(),
377       /*annotations_entity_data_schema=*/nullptr,
378       /*actions_entity_data_schema=*/nullptr, &intents));
379   EXPECT_THAT(intents, SizeIs(1));
380   EXPECT_EQ(intents[0].title_without_entity.value(), "Schedule");
381   EXPECT_THAT(intents[0].extra, SizeIs(2));
382   EXPECT_EQ(intents[0].extra["location"].ConstRefValue<std::string>(), "there");
383   EXPECT_EQ(intents[0].extra["time"].ConstRefValue<std::string>(), "1pm");
384 }
385 
TEST_F(IntentGeneratorTest,HandlesActionIntentGenerationMultipleAnnotationsWithIndices)386 TEST_F(IntentGeneratorTest,
387        HandlesActionIntentGenerationMultipleAnnotationsWithIndices) {
388   flatbuffers::DetachedBuffer intent_factory_model =
389       BuildTestIntentFactoryModel("time_range", R"lua(
390 return {
391   {
392     title_without_entity = "test",
393     description = "test",
394     extra = {
395       {name = "from", string_value = external.entity.annotation[1].text},
396       {name = "to", string_value = external.entity.annotation[2].text},
397     }
398   }
399 })lua");
400   std::unique_ptr<IntentGenerator> generator = IntentGenerator::Create(
401       flatbuffers::GetRoot<IntentFactoryModel>(intent_factory_model.data()),
402       /*resources=*/resources_, jni_cache_);
403   Conversation conversation = {{{/*user_id=*/1, "from 1pm to 2pm"}}};
404   ActionSuggestionAnnotation from_annotation, to_annotation;
405   from_annotation.entity = {"datetime", 1.0};
406   from_annotation.span = {/*message_index=*/0,
407                           /*span=*/{5, 8},
408                           /*text=*/"1pm"};
409   to_annotation.entity = {"datetime", 1.0};
410   to_annotation.span = {/*message_index=*/0,
411                         /*span=*/{12, 15},
412                         /*text=*/"2pm"};
413   ActionSuggestion suggestion = {/*response_text=""*/ "",
414                                  /*type=*/"time_range",
415                                  /*score=*/1.0,
416                                  /*priority_score=*/0.0,
417                                  /*annotations=*/
418                                  {from_annotation, to_annotation}};
419   std::vector<RemoteActionTemplate> intents;
420   EXPECT_TRUE(generator->GenerateIntents(
421       JniHelper::NewStringUTF(GetJenv(), "en-US").ValueOrDie().get(),
422       suggestion, conversation, GetAndroidContext(),
423       /*annotations_entity_data_schema=*/nullptr,
424       /*actions_entity_data_schema=*/nullptr, &intents));
425   EXPECT_THAT(intents, SizeIs(1));
426   EXPECT_THAT(intents[0].extra, SizeIs(2));
427   EXPECT_EQ(intents[0].extra["from"].ConstRefValue<std::string>(), "1pm");
428   EXPECT_EQ(intents[0].extra["to"].ConstRefValue<std::string>(), "2pm");
429 }
430 
TEST_F(IntentGeneratorTest,HandlesResources)431 TEST_F(IntentGeneratorTest, HandlesResources) {
432   flatbuffers::DetachedBuffer intent_factory_model =
433       BuildTestIntentFactoryModel("address", R"lua(
434 return {
435   {
436     title_without_entity = external.android.R.map,
437     description = external.android.R.map_desc,
438     action = "android.intent.action.VIEW",
439     data = "geo:0,0?q=" ..
440     external.android.urlencode(external.entity.text),
441   }
442 })lua");
443   std::unique_ptr<IntentGenerator> generator = IntentGenerator::Create(
444       flatbuffers::GetRoot<IntentFactoryModel>(intent_factory_model.data()),
445       resources_, jni_cache_);
446   ClassificationResult classification = {"address", 1.0};
447   std::vector<RemoteActionTemplate> intents;
448   EXPECT_TRUE(generator->GenerateIntents(
449       JniHelper::NewStringUTF(GetJenv(), "de-DE").ValueOrDie().get(),
450       classification,
451       /*reference_time_ms_utc=*/0, "333 E Wonderview Ave", {0, 20},
452       GetAndroidContext(),
453       /*annotations_entity_data_schema=*/nullptr, &intents));
454   EXPECT_THAT(intents, SizeIs(1));
455   EXPECT_EQ(intents[0].title_without_entity.value(), "Karte");
456   EXPECT_EQ(intents[0].description.value(), "Ausgewählte Adresse finden");
457   EXPECT_EQ(intents[0].action.value(), "android.intent.action.VIEW");
458   EXPECT_EQ(intents[0].data.value(), "geo:0,0?q=333%20E%20Wonderview%20Ave");
459 }
460 
TEST_F(IntentGeneratorTest,HandlesIteration)461 TEST_F(IntentGeneratorTest, HandlesIteration) {
462   flatbuffers::DetachedBuffer intent_factory_model =
463       BuildTestIntentFactoryModel("iteration_test", R"lua(
464 local extra = {{ name = "length", int_value = #external.entity.annotation }}
465 for annotation_id, annotation in pairs(external.entity.annotation) do
466   table.insert(extra,
467     { name = annotation.name,
468       string_value = annotation.text })
469 end
470 return {{ extra = extra }})lua");
471   std::unique_ptr<IntentGenerator> generator = IntentGenerator::Create(
472       flatbuffers::GetRoot<IntentFactoryModel>(intent_factory_model.data()),
473       /*resources=*/resources_, jni_cache_);
474   Conversation conversation = {{{/*user_id=*/1, "hello there"}}};
475   ActionSuggestionAnnotation location_annotation;
476   location_annotation.entity = {"address", 1.0};
477   location_annotation.span = {/*message_index=*/0,
478                               /*span=*/{6, 11},
479                               /*text=*/"there"};
480   location_annotation.name = "location";
481   ActionSuggestionAnnotation greeting_annotation;
482   greeting_annotation.entity = {"greeting", 1.0};
483   greeting_annotation.span = {/*message_index=*/0,
484                               /*span=*/{0, 5},
485                               /*text=*/"hello"};
486   greeting_annotation.name = "greeting";
487   ActionSuggestion suggestion = {/*response_text=""*/ "",
488                                  /*type=*/"iteration_test",
489                                  /*score=*/1.0,
490                                  /*priority_score=*/0.0,
491                                  /*annotations=*/
492                                  {location_annotation, greeting_annotation}};
493   std::vector<RemoteActionTemplate> intents;
494   EXPECT_TRUE(generator->GenerateIntents(
495       JniHelper::NewStringUTF(GetJenv(), "en-US").ValueOrDie().get(),
496       suggestion, conversation, GetAndroidContext(),
497       /*annotations_entity_data_schema=*/nullptr,
498       /*actions_entity_data_schema=*/nullptr, &intents));
499   EXPECT_THAT(intents, SizeIs(1));
500   EXPECT_EQ(intents[0].extra["length"].Value<int>(), 2);
501   EXPECT_EQ(intents[0].extra["location"].ConstRefValue<std::string>(), "there");
502   EXPECT_EQ(intents[0].extra["greeting"].ConstRefValue<std::string>(), "hello");
503 }
504 
TEST_F(IntentGeneratorTest,HandlesEntityDataLookups)505 TEST_F(IntentGeneratorTest, HandlesEntityDataLookups) {
506   flatbuffers::DetachedBuffer intent_factory_model =
507       BuildTestIntentFactoryModel("fake", R"lua(
508 local person = external.entity.person
509 return {
510   {
511     title_without_entity = "Add to contacts",
512     extra = {
513       {name = "name", string_value = string.lower(person.name)},
514       {name = "encoded_phone", string_value = external.android.urlencode(person.phone)},
515       {name = "age", int_value = person.age_years},
516     }
517   }
518 })lua");
519 
520   // Create fake entity data schema meta data.
521   // Cannot use object oriented API here as that is not available for the
522   // reflection schema.
523   flatbuffers::FlatBufferBuilder schema_builder;
524   std::vector<flatbuffers::Offset<reflection::Field>> person_fields = {
525       reflection::CreateField(
526           schema_builder,
527           /*name=*/schema_builder.CreateString("name"),
528           /*type=*/
529           reflection::CreateType(schema_builder,
530                                  /*base_type=*/reflection::String),
531           /*id=*/0,
532           /*offset=*/4),
533       reflection::CreateField(
534           schema_builder,
535           /*name=*/schema_builder.CreateString("phone"),
536           /*type=*/
537           reflection::CreateType(schema_builder,
538                                  /*base_type=*/reflection::String),
539           /*id=*/1,
540           /*offset=*/6),
541       reflection::CreateField(
542           schema_builder,
543           /*name=*/schema_builder.CreateString("age_years"),
544           /*type=*/
545           reflection::CreateType(schema_builder,
546                                  /*base_type=*/reflection::Int),
547           /*id=*/2,
548           /*offset=*/8),
549   };
550   std::vector<flatbuffers::Offset<reflection::Field>> entity_data_fields = {
551       reflection::CreateField(
552           schema_builder,
553           /*name=*/schema_builder.CreateString("person"),
554           /*type=*/
555           reflection::CreateType(schema_builder,
556                                  /*base_type=*/reflection::Obj,
557                                  /*element=*/reflection::None,
558                                  /*index=*/1),
559           /*id=*/0,
560           /*offset=*/4)};
561   std::vector<flatbuffers::Offset<reflection::Enum>> enums;
562   std::vector<flatbuffers::Offset<reflection::Object>> objects = {
563       reflection::CreateObject(
564           schema_builder,
565           /*name=*/schema_builder.CreateString("EntityData"),
566           /*fields=*/
567           schema_builder.CreateVectorOfSortedTables(&entity_data_fields)),
568       reflection::CreateObject(
569           schema_builder,
570           /*name=*/schema_builder.CreateString("person"),
571           /*fields=*/
572           schema_builder.CreateVectorOfSortedTables(&person_fields))};
573   schema_builder.Finish(reflection::CreateSchema(
574       schema_builder, schema_builder.CreateVectorOfSortedTables(&objects),
575       schema_builder.CreateVectorOfSortedTables(&enums),
576       /*(unused) file_ident=*/0,
577       /*(unused) file_ext=*/0,
578       /*root_table*/ objects[0]));
579   const reflection::Schema* entity_data_schema =
580       flatbuffers::GetRoot<reflection::Schema>(
581           schema_builder.GetBufferPointer());
582 
583   std::unique_ptr<IntentGenerator> generator = IntentGenerator::Create(
584       flatbuffers::GetRoot<IntentFactoryModel>(intent_factory_model.data()),
585       /*resources=*/resources_, jni_cache_);
586 
587   ClassificationResult classification = {"fake", 1.0};
588 
589   // Build test entity data.
590   MutableFlatbufferBuilder entity_data_builder(entity_data_schema);
591   std::unique_ptr<MutableFlatbuffer> entity_data_buffer =
592       entity_data_builder.NewRoot();
593   MutableFlatbuffer* person = entity_data_buffer->Mutable("person");
594   person->Set("name", "Kenobi");
595   person->Set("phone", "1 800 HIGHGROUND");
596   person->Set("age_years", 38);
597   classification.serialized_entity_data = entity_data_buffer->Serialize();
598 
599   std::vector<RemoteActionTemplate> intents;
600   EXPECT_TRUE(generator->GenerateIntents(
601       JniHelper::NewStringUTF(GetJenv(), "en-US").ValueOrDie().get(),
602       classification,
603       /*reference_time_ms_utc=*/0, "highground", {0, 10}, GetAndroidContext(),
604       /*annotations_entity_data_schema=*/entity_data_schema, &intents));
605   EXPECT_THAT(intents, SizeIs(1));
606   EXPECT_THAT(intents[0].extra, SizeIs(3));
607   EXPECT_EQ(intents[0].extra["name"].ConstRefValue<std::string>(), "kenobi");
608   EXPECT_EQ(intents[0].extra["encoded_phone"].ConstRefValue<std::string>(),
609             "1%20800%20HIGHGROUND");
610   EXPECT_EQ(intents[0].extra["age"].Value<int>(), 38);
611 }
612 
TEST_F(IntentGeneratorTest,ReadExtras)613 TEST_F(IntentGeneratorTest, ReadExtras) {
614   flatbuffers::DetachedBuffer intent_factory_model =
615       BuildTestIntentFactoryModel("test", R"lua(
616         return {
617           {
618             extra = {
619               { name = "languages", string_array_value = {"en", "zh"}},
620               { name = "scores", float_array_value = {0.6, 0.4}},
621               { name = "ints", int_array_value = {7, 2, 1}},
622               { name = "bundle",
623               named_variant_array_value =
624               {
625                 { name = "inner_string", string_value = "a" },
626                 { name = "inner_int", int_value = 42 }
627               }
628             }
629           }
630         }}
631   )lua");
632   std::unique_ptr<IntentGenerator> generator = IntentGenerator::Create(
633       flatbuffers::GetRoot<IntentFactoryModel>(intent_factory_model.data()),
634       /*resources=*/resources_, jni_cache_);
635   const ClassificationResult classification = {"test", 1.0};
636   std::vector<RemoteActionTemplate> intents;
637 
638   EXPECT_TRUE(generator->GenerateIntents(
639       JniHelper::NewStringUTF(GetJenv(), "en-US").ValueOrDie().get(),
640       classification,
641       /*reference_time_ms_utc=*/0, "test", {0, 4}, GetAndroidContext(),
642       /*annotations_entity_data_schema=*/nullptr, &intents));
643 
644   EXPECT_THAT(intents, SizeIs(1));
645   RemoteActionTemplate intent = intents[0];
646   EXPECT_THAT(intent.extra, SizeIs(4));
647   EXPECT_THAT(
648       intent.extra["languages"].ConstRefValue<std::vector<std::string>>(),
649       ElementsAre("en", "zh"));
650   EXPECT_THAT(intent.extra["scores"].ConstRefValue<std::vector<float>>(),
651               ElementsAre(0.6, 0.4));
652   EXPECT_THAT(intent.extra["ints"].ConstRefValue<std::vector<int>>(),
653               ElementsAre(7, 2, 1));
654   const std::map<std::string, Variant>& map =
655       intent.extra["bundle"].ConstRefValue<std::map<std::string, Variant>>();
656   EXPECT_THAT(map, SizeIs(2));
657   EXPECT_EQ(map.at("inner_string").ConstRefValue<std::string>(), "a");
658   EXPECT_EQ(map.at("inner_int").Value<int>(), 42);
659 }
660 
661 }  // namespace
662 }  // namespace libtextclassifier3
663