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