Adam Lesinski | 8cdca1b | 2017-09-28 15:50:03 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2016 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 | |
Adam Lesinski | 4670805 | 2017-09-29 14:49:15 -0700 | [diff] [blame^] | 17 | #include "format/proto/ProtoSerialize.h" |
Adam Lesinski | 8cdca1b | 2017-09-28 15:50:03 -0700 | [diff] [blame] | 18 | |
| 19 | #include "google/protobuf/io/zero_copy_stream_impl_lite.h" |
| 20 | |
| 21 | #include "ResourceUtils.h" |
Adam Lesinski | 4670805 | 2017-09-29 14:49:15 -0700 | [diff] [blame^] | 22 | #include "format/proto/ProtoDeserialize.h" |
Adam Lesinski | 8cdca1b | 2017-09-28 15:50:03 -0700 | [diff] [blame] | 23 | #include "test/Test.h" |
| 24 | |
| 25 | using ::android::StringPiece; |
| 26 | using ::google::protobuf::io::StringOutputStream; |
| 27 | using ::testing::Eq; |
| 28 | using ::testing::IsEmpty; |
| 29 | using ::testing::NotNull; |
| 30 | using ::testing::SizeIs; |
| 31 | using ::testing::StrEq; |
| 32 | |
| 33 | namespace aapt { |
| 34 | |
| 35 | TEST(ProtoSerializeTest, SerializeSinglePackage) { |
| 36 | std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); |
| 37 | std::unique_ptr<ResourceTable> table = |
| 38 | test::ResourceTableBuilder() |
| 39 | .SetPackageId("com.app.a", 0x7f) |
| 40 | .AddFileReference("com.app.a:layout/main", ResourceId(0x7f020000), "res/layout/main.xml") |
| 41 | .AddReference("com.app.a:layout/other", ResourceId(0x7f020001), "com.app.a:layout/main") |
| 42 | .AddString("com.app.a:string/text", {}, "hi") |
| 43 | .AddValue("com.app.a:id/foo", {}, util::make_unique<Id>()) |
| 44 | .SetSymbolState("com.app.a:bool/foo", {}, SymbolState::kUndefined, true /*allow_new*/) |
| 45 | .Build(); |
| 46 | |
| 47 | Symbol public_symbol; |
| 48 | public_symbol.state = SymbolState::kPublic; |
| 49 | ASSERT_TRUE(table->SetSymbolState(test::ParseNameOrDie("com.app.a:layout/main"), |
| 50 | ResourceId(0x7f020000), public_symbol, |
| 51 | context->GetDiagnostics())); |
| 52 | |
| 53 | Id* id = test::GetValue<Id>(table.get(), "com.app.a:id/foo"); |
| 54 | ASSERT_THAT(id, NotNull()); |
| 55 | |
| 56 | // Make a plural. |
| 57 | std::unique_ptr<Plural> plural = util::make_unique<Plural>(); |
| 58 | plural->values[Plural::One] = util::make_unique<String>(table->string_pool.MakeRef("one")); |
| 59 | ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.app.a:plurals/hey"), ConfigDescription{}, |
| 60 | {}, std::move(plural), context->GetDiagnostics())); |
| 61 | |
| 62 | // Make a styled string. |
| 63 | StyleString style_string; |
| 64 | style_string.str = "hello"; |
| 65 | style_string.spans.push_back(Span{"b", 0u, 4u}); |
| 66 | ASSERT_TRUE( |
| 67 | table->AddResource(test::ParseNameOrDie("com.app.a:string/styled"), ConfigDescription{}, {}, |
| 68 | util::make_unique<StyledString>(table->string_pool.MakeRef(style_string)), |
| 69 | context->GetDiagnostics())); |
| 70 | |
| 71 | // Make a resource with different products. |
| 72 | ASSERT_TRUE(table->AddResource( |
| 73 | test::ParseNameOrDie("com.app.a:integer/one"), test::ParseConfigOrDie("land"), {}, |
| 74 | test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 123u), context->GetDiagnostics())); |
| 75 | ASSERT_TRUE(table->AddResource( |
| 76 | test::ParseNameOrDie("com.app.a:integer/one"), test::ParseConfigOrDie("land"), "tablet", |
| 77 | test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 321u), context->GetDiagnostics())); |
| 78 | |
| 79 | // Make a reference with both resource name and resource ID. |
| 80 | // The reference should point to a resource outside of this table to test that both name and id |
| 81 | // get serialized. |
| 82 | Reference expected_ref; |
| 83 | expected_ref.name = test::ParseNameOrDie("android:layout/main"); |
| 84 | expected_ref.id = ResourceId(0x01020000); |
| 85 | ASSERT_TRUE(table->AddResource( |
| 86 | test::ParseNameOrDie("com.app.a:layout/abc"), ConfigDescription::DefaultConfig(), {}, |
| 87 | util::make_unique<Reference>(expected_ref), context->GetDiagnostics())); |
| 88 | |
| 89 | pb::ResourceTable pb_table; |
| 90 | SerializeTableToPb(*table, &pb_table); |
| 91 | |
| 92 | ResourceTable new_table; |
| 93 | std::string error; |
| 94 | ASSERT_TRUE(DeserializeTableFromPb(pb_table, &new_table, &error)); |
| 95 | EXPECT_THAT(error, IsEmpty()); |
| 96 | |
| 97 | Id* new_id = test::GetValue<Id>(&new_table, "com.app.a:id/foo"); |
| 98 | ASSERT_THAT(new_id, NotNull()); |
| 99 | EXPECT_THAT(new_id->IsWeak(), Eq(id->IsWeak())); |
| 100 | |
| 101 | Maybe<ResourceTable::SearchResult> result = |
| 102 | new_table.FindResource(test::ParseNameOrDie("com.app.a:layout/main")); |
| 103 | ASSERT_TRUE(result); |
| 104 | |
| 105 | EXPECT_THAT(result.value().type->symbol_status.state, Eq(SymbolState::kPublic)); |
| 106 | EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic)); |
| 107 | |
| 108 | result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/foo")); |
| 109 | ASSERT_TRUE(result); |
| 110 | EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kUndefined)); |
| 111 | EXPECT_TRUE(result.value().entry->symbol_status.allow_new); |
| 112 | |
| 113 | // Find the product-dependent values |
| 114 | BinaryPrimitive* prim = test::GetValueForConfigAndProduct<BinaryPrimitive>( |
| 115 | &new_table, "com.app.a:integer/one", test::ParseConfigOrDie("land"), ""); |
| 116 | ASSERT_THAT(prim, NotNull()); |
| 117 | EXPECT_THAT(prim->value.data, Eq(123u)); |
| 118 | |
| 119 | prim = test::GetValueForConfigAndProduct<BinaryPrimitive>( |
| 120 | &new_table, "com.app.a:integer/one", test::ParseConfigOrDie("land"), "tablet"); |
| 121 | ASSERT_THAT(prim, NotNull()); |
| 122 | EXPECT_THAT(prim->value.data, Eq(321u)); |
| 123 | |
| 124 | Reference* actual_ref = test::GetValue<Reference>(&new_table, "com.app.a:layout/abc"); |
| 125 | ASSERT_THAT(actual_ref, NotNull()); |
| 126 | ASSERT_TRUE(actual_ref->name); |
| 127 | ASSERT_TRUE(actual_ref->id); |
| 128 | EXPECT_THAT(*actual_ref, Eq(expected_ref)); |
| 129 | |
| 130 | StyledString* actual_styled_str = |
| 131 | test::GetValue<StyledString>(&new_table, "com.app.a:string/styled"); |
| 132 | ASSERT_THAT(actual_styled_str, NotNull()); |
| 133 | EXPECT_THAT(actual_styled_str->value->value, Eq("hello")); |
| 134 | ASSERT_THAT(actual_styled_str->value->spans, SizeIs(1u)); |
| 135 | EXPECT_THAT(*actual_styled_str->value->spans[0].name, Eq("b")); |
| 136 | EXPECT_THAT(actual_styled_str->value->spans[0].first_char, Eq(0u)); |
| 137 | EXPECT_THAT(actual_styled_str->value->spans[0].last_char, Eq(4u)); |
| 138 | } |
| 139 | |
| 140 | TEST(ProtoSerializeTest, SerializeFileHeader) { |
| 141 | std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); |
| 142 | |
| 143 | ResourceFile f; |
| 144 | f.config = test::ParseConfigOrDie("hdpi-v9"); |
| 145 | f.name = test::ParseNameOrDie("com.app.a:layout/main"); |
| 146 | f.source.path = "res/layout-hdpi-v9/main.xml"; |
| 147 | f.exported_symbols.push_back(SourcedResourceName{test::ParseNameOrDie("id/unchecked"), 23u}); |
| 148 | |
| 149 | const std::string expected_data1 = "123"; |
| 150 | const std::string expected_data2 = "1234"; |
| 151 | |
| 152 | std::string output_str; |
| 153 | { |
| 154 | pb::internal::CompiledFile pb_f1, pb_f2; |
| 155 | SerializeCompiledFileToPb(f, &pb_f1); |
| 156 | |
| 157 | f.name.entry = "__" + f.name.entry + "$0"; |
| 158 | SerializeCompiledFileToPb(f, &pb_f2); |
| 159 | |
| 160 | StringOutputStream out_stream(&output_str); |
| 161 | CompiledFileOutputStream out_file_stream(&out_stream); |
| 162 | out_file_stream.WriteLittleEndian32(2); |
| 163 | out_file_stream.WriteCompiledFile(pb_f1); |
| 164 | out_file_stream.WriteData(expected_data1.data(), expected_data1.size()); |
| 165 | out_file_stream.WriteCompiledFile(pb_f2); |
| 166 | out_file_stream.WriteData(expected_data2.data(), expected_data2.size()); |
| 167 | ASSERT_FALSE(out_file_stream.HadError()); |
| 168 | } |
| 169 | |
| 170 | CompiledFileInputStream in_file_stream(output_str.data(), output_str.size()); |
| 171 | uint32_t num_files = 0; |
| 172 | ASSERT_TRUE(in_file_stream.ReadLittleEndian32(&num_files)); |
| 173 | ASSERT_EQ(2u, num_files); |
| 174 | |
| 175 | // Read the first compiled file. |
| 176 | |
| 177 | pb::internal::CompiledFile new_pb_f1; |
| 178 | ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_f1)); |
| 179 | |
| 180 | ResourceFile new_f1; |
| 181 | std::string error; |
| 182 | ASSERT_TRUE(DeserializeCompiledFileFromPb(new_pb_f1, &new_f1, &error)); |
| 183 | EXPECT_THAT(error, IsEmpty()); |
| 184 | |
| 185 | uint64_t offset, len; |
| 186 | ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len)); |
| 187 | |
| 188 | std::string actual_data(output_str.data() + offset, len); |
| 189 | EXPECT_EQ(expected_data1, actual_data); |
| 190 | |
| 191 | // Expect the data to be aligned. |
| 192 | EXPECT_EQ(0u, offset & 0x03); |
| 193 | |
| 194 | ASSERT_EQ(1u, new_f1.exported_symbols.size()); |
| 195 | EXPECT_EQ(test::ParseNameOrDie("id/unchecked"), new_f1.exported_symbols[0].name); |
| 196 | |
| 197 | // Read the second compiled file. |
| 198 | |
| 199 | pb::internal::CompiledFile new_pb_f2; |
| 200 | ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_f2)); |
| 201 | |
| 202 | ResourceFile new_f2; |
| 203 | ASSERT_TRUE(DeserializeCompiledFileFromPb(new_pb_f2, &new_f2, &error)); |
| 204 | EXPECT_THAT(error, IsEmpty()); |
| 205 | |
| 206 | ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len)); |
| 207 | |
| 208 | actual_data = std::string(output_str.data() + offset, len); |
| 209 | EXPECT_EQ(expected_data2, actual_data); |
| 210 | |
| 211 | // Expect the data to be aligned. |
| 212 | EXPECT_EQ(0u, offset & 0x03); |
| 213 | } |
| 214 | |
| 215 | TEST(ProtoSerializeTest, DeserializeCorruptHeaderSafely) { |
| 216 | ResourceFile f; |
| 217 | pb::internal::CompiledFile pb_file; |
| 218 | SerializeCompiledFileToPb(f, &pb_file); |
| 219 | |
| 220 | const std::string expected_data = "1234"; |
| 221 | |
| 222 | std::string output_str; |
| 223 | { |
| 224 | StringOutputStream out_stream(&output_str); |
| 225 | CompiledFileOutputStream out_file_stream(&out_stream); |
| 226 | out_file_stream.WriteLittleEndian32(1); |
| 227 | out_file_stream.WriteCompiledFile(pb_file); |
| 228 | out_file_stream.WriteData(expected_data.data(), expected_data.size()); |
| 229 | ASSERT_FALSE(out_file_stream.HadError()); |
| 230 | } |
| 231 | |
| 232 | output_str[4] = 0xff; |
| 233 | |
| 234 | CompiledFileInputStream in_file_stream(output_str.data(), output_str.size()); |
| 235 | |
| 236 | uint32_t num_files = 0; |
| 237 | EXPECT_TRUE(in_file_stream.ReadLittleEndian32(&num_files)); |
| 238 | EXPECT_EQ(1u, num_files); |
| 239 | |
| 240 | pb::internal::CompiledFile new_pb_file; |
| 241 | EXPECT_FALSE(in_file_stream.ReadCompiledFile(&new_pb_file)); |
| 242 | |
| 243 | uint64_t offset, len; |
| 244 | EXPECT_FALSE(in_file_stream.ReadDataMetaData(&offset, &len)); |
| 245 | } |
| 246 | |
| 247 | TEST(ProtoSerializeTest, SerializeAndDeserializeXml) { |
| 248 | xml::Element element; |
| 249 | element.line_number = 22; |
| 250 | element.column_number = 23; |
| 251 | element.name = "element"; |
| 252 | element.namespace_uri = "uri://"; |
| 253 | |
| 254 | xml::NamespaceDecl decl; |
| 255 | decl.prefix = "android"; |
| 256 | decl.uri = xml::kSchemaAndroid; |
| 257 | decl.line_number = 21; |
| 258 | decl.column_number = 24; |
| 259 | |
| 260 | element.namespace_decls.push_back(decl); |
| 261 | |
| 262 | xml::Attribute attr; |
| 263 | attr.name = "name"; |
| 264 | attr.namespace_uri = xml::kSchemaAndroid; |
| 265 | attr.value = "23dp"; |
| 266 | attr.compiled_attribute = xml::AaptAttribute({}, ResourceId(0x01010000)); |
| 267 | attr.compiled_value = |
| 268 | ResourceUtils::TryParseItemForAttribute(attr.value, android::ResTable_map::TYPE_DIMENSION); |
| 269 | attr.compiled_value->SetSource(Source().WithLine(25)); |
| 270 | element.attributes.push_back(std::move(attr)); |
| 271 | |
| 272 | std::unique_ptr<xml::Text> text = util::make_unique<xml::Text>(); |
| 273 | text->line_number = 25; |
| 274 | text->column_number = 3; |
| 275 | text->text = "hey there"; |
| 276 | element.AppendChild(std::move(text)); |
| 277 | |
| 278 | std::unique_ptr<xml::Element> child = util::make_unique<xml::Element>(); |
| 279 | child->name = "child"; |
| 280 | |
| 281 | text = util::make_unique<xml::Text>(); |
| 282 | text->text = "woah there"; |
| 283 | child->AppendChild(std::move(text)); |
| 284 | |
| 285 | element.AppendChild(std::move(child)); |
| 286 | |
| 287 | pb::XmlNode pb_xml; |
| 288 | SerializeXmlToPb(element, &pb_xml); |
| 289 | |
| 290 | StringPool pool; |
| 291 | xml::Element actual_el; |
| 292 | std::string error; |
| 293 | ASSERT_TRUE(DeserializeXmlFromPb(pb_xml, &actual_el, &pool, &error)); |
| 294 | ASSERT_THAT(error, IsEmpty()); |
| 295 | |
| 296 | EXPECT_THAT(actual_el.name, StrEq("element")); |
| 297 | EXPECT_THAT(actual_el.namespace_uri, StrEq("uri://")); |
| 298 | EXPECT_THAT(actual_el.line_number, Eq(22u)); |
| 299 | EXPECT_THAT(actual_el.column_number, Eq(23u)); |
| 300 | |
| 301 | ASSERT_THAT(actual_el.namespace_decls, SizeIs(1u)); |
| 302 | const xml::NamespaceDecl& actual_decl = actual_el.namespace_decls[0]; |
| 303 | EXPECT_THAT(actual_decl.prefix, StrEq("android")); |
| 304 | EXPECT_THAT(actual_decl.uri, StrEq(xml::kSchemaAndroid)); |
| 305 | EXPECT_THAT(actual_decl.line_number, Eq(21u)); |
| 306 | EXPECT_THAT(actual_decl.column_number, Eq(24u)); |
| 307 | |
| 308 | ASSERT_THAT(actual_el.attributes, SizeIs(1u)); |
| 309 | const xml::Attribute& actual_attr = actual_el.attributes[0]; |
| 310 | EXPECT_THAT(actual_attr.name, StrEq("name")); |
| 311 | EXPECT_THAT(actual_attr.namespace_uri, StrEq(xml::kSchemaAndroid)); |
| 312 | EXPECT_THAT(actual_attr.value, StrEq("23dp")); |
| 313 | |
| 314 | ASSERT_THAT(actual_attr.compiled_value, NotNull()); |
| 315 | const BinaryPrimitive* prim = ValueCast<BinaryPrimitive>(actual_attr.compiled_value.get()); |
| 316 | ASSERT_THAT(prim, NotNull()); |
| 317 | EXPECT_THAT(prim->value.dataType, Eq(android::Res_value::TYPE_DIMENSION)); |
| 318 | |
| 319 | ASSERT_TRUE(actual_attr.compiled_attribute); |
| 320 | ASSERT_TRUE(actual_attr.compiled_attribute.value().id); |
| 321 | |
| 322 | ASSERT_THAT(actual_el.children, SizeIs(2u)); |
| 323 | const xml::Text* child_text = xml::NodeCast<xml::Text>(actual_el.children[0].get()); |
| 324 | ASSERT_THAT(child_text, NotNull()); |
| 325 | const xml::Element* child_el = xml::NodeCast<xml::Element>(actual_el.children[1].get()); |
| 326 | ASSERT_THAT(child_el, NotNull()); |
| 327 | |
| 328 | EXPECT_THAT(child_text->line_number, Eq(25u)); |
| 329 | EXPECT_THAT(child_text->column_number, Eq(3u)); |
| 330 | EXPECT_THAT(child_text->text, StrEq("hey there")); |
| 331 | |
| 332 | EXPECT_THAT(child_el->name, StrEq("child")); |
| 333 | ASSERT_THAT(child_el->children, SizeIs(1u)); |
| 334 | |
| 335 | child_text = xml::NodeCast<xml::Text>(child_el->children[0].get()); |
| 336 | ASSERT_THAT(child_text, NotNull()); |
| 337 | EXPECT_THAT(child_text->text, StrEq("woah there")); |
| 338 | } |
| 339 | |
| 340 | static void ExpectConfigSerializes(const StringPiece& config_str) { |
| 341 | const ConfigDescription expected_config = test::ParseConfigOrDie(config_str); |
| 342 | pb::Configuration pb_config; |
| 343 | SerializeConfig(expected_config, &pb_config); |
| 344 | |
| 345 | ConfigDescription actual_config; |
| 346 | std::string error; |
| 347 | ASSERT_TRUE(DeserializeConfigFromPb(pb_config, &actual_config, &error)); |
| 348 | ASSERT_THAT(error, IsEmpty()); |
| 349 | EXPECT_EQ(expected_config, actual_config); |
| 350 | } |
| 351 | |
| 352 | TEST(ProtoSerializeTest, SerializeDeserializeConfiguration) { |
| 353 | ExpectConfigSerializes(""); |
| 354 | |
| 355 | ExpectConfigSerializes("mcc123"); |
| 356 | |
| 357 | ExpectConfigSerializes("mnc123"); |
| 358 | |
| 359 | ExpectConfigSerializes("en"); |
| 360 | ExpectConfigSerializes("en-rGB"); |
| 361 | ExpectConfigSerializes("b+en+GB"); |
| 362 | |
| 363 | ExpectConfigSerializes("ldltr"); |
| 364 | ExpectConfigSerializes("ldrtl"); |
| 365 | |
| 366 | ExpectConfigSerializes("sw3600dp"); |
| 367 | |
| 368 | ExpectConfigSerializes("w300dp"); |
| 369 | |
| 370 | ExpectConfigSerializes("h400dp"); |
| 371 | |
| 372 | ExpectConfigSerializes("small"); |
| 373 | ExpectConfigSerializes("normal"); |
| 374 | ExpectConfigSerializes("large"); |
| 375 | ExpectConfigSerializes("xlarge"); |
| 376 | |
| 377 | ExpectConfigSerializes("long"); |
| 378 | ExpectConfigSerializes("notlong"); |
| 379 | |
| 380 | ExpectConfigSerializes("round"); |
| 381 | ExpectConfigSerializes("notround"); |
| 382 | |
| 383 | ExpectConfigSerializes("widecg"); |
| 384 | ExpectConfigSerializes("nowidecg"); |
| 385 | |
| 386 | ExpectConfigSerializes("highdr"); |
| 387 | ExpectConfigSerializes("lowdr"); |
| 388 | |
| 389 | ExpectConfigSerializes("port"); |
| 390 | ExpectConfigSerializes("land"); |
| 391 | ExpectConfigSerializes("square"); |
| 392 | |
| 393 | ExpectConfigSerializes("desk"); |
| 394 | ExpectConfigSerializes("car"); |
| 395 | ExpectConfigSerializes("television"); |
| 396 | ExpectConfigSerializes("appliance"); |
| 397 | ExpectConfigSerializes("watch"); |
| 398 | ExpectConfigSerializes("vrheadset"); |
| 399 | |
| 400 | ExpectConfigSerializes("night"); |
| 401 | ExpectConfigSerializes("notnight"); |
| 402 | |
| 403 | ExpectConfigSerializes("300dpi"); |
| 404 | ExpectConfigSerializes("hdpi"); |
| 405 | |
| 406 | ExpectConfigSerializes("notouch"); |
| 407 | ExpectConfigSerializes("stylus"); |
| 408 | ExpectConfigSerializes("finger"); |
| 409 | |
| 410 | ExpectConfigSerializes("keysexposed"); |
| 411 | ExpectConfigSerializes("keyshidden"); |
| 412 | ExpectConfigSerializes("keyssoft"); |
| 413 | |
| 414 | ExpectConfigSerializes("nokeys"); |
| 415 | ExpectConfigSerializes("qwerty"); |
| 416 | ExpectConfigSerializes("12key"); |
| 417 | |
| 418 | ExpectConfigSerializes("navhidden"); |
| 419 | ExpectConfigSerializes("navexposed"); |
| 420 | |
| 421 | ExpectConfigSerializes("nonav"); |
| 422 | ExpectConfigSerializes("dpad"); |
| 423 | ExpectConfigSerializes("trackball"); |
| 424 | ExpectConfigSerializes("wheel"); |
| 425 | |
| 426 | ExpectConfigSerializes("300x200"); |
| 427 | |
| 428 | ExpectConfigSerializes("v8"); |
| 429 | |
| 430 | ExpectConfigSerializes( |
| 431 | "mcc123-mnc456-b+en+GB-ldltr-sw300dp-w300dp-h400dp-large-long-round-widecg-highdr-land-car-" |
| 432 | "night-xhdpi-stylus-keysexposed-qwerty-navhidden-dpad-300x200-v23"); |
| 433 | } |
| 434 | |
| 435 | } // namespace aapt |