AAPT2: Change proto format to reduce usage of StringPool

The StringPool class is a binary blob and makes it difficult to
modify the proto files from external tools (like bundle-tool).

Size increase of full build is negligible.

Test: make aapt2_tests
Change-Id: I984755170c315730ab751b51133f8fb2f614f6af
diff --git a/tools/aapt2/Format.proto b/tools/aapt2/Format.proto
index 8381fa00..6be7f02 100644
--- a/tools/aapt2/Format.proto
+++ b/tools/aapt2/Format.proto
@@ -67,21 +67,12 @@
 
 // Top level message representing a resource table.
 message ResourceTable {
-  // The string pool containing string values (strings that actually end up in the binary ARSC
-  // file) referenced throughout the resource table.
-  optional StringPool string_pool = 1;
-
   // The string pool containing source paths referenced throughout the resource table. This does
   // not end up in the final binary ARSC file.
-  optional StringPool source_pool = 2;
-
-  // The string pool containing the names of unresolved symbols. This does not end up in the final
-  // binary ARSC file. Unresolved symbols are just resource names that haven't had a resource ID
-  // assigned to them, therefore can't be referenced by resource ID.
-  optional StringPool symbol_pool = 3;
+  optional StringPool source_pool = 1;
 
   // Resource definitions corresponding to an Android package.
-  repeated Package packages = 4;
+  repeated Package packages = 2;
 }
 
 // Defines resources for an Android package.
@@ -202,9 +193,10 @@
   optional Reference ref = 1;
   optional String str = 2;
   optional RawString raw_str = 3;
-  optional FileReference file = 4;
-  optional Id id = 5;
-  optional Primitive prim = 6;
+  optional StyledString styled_str = 4;
+  optional FileReference file = 5;
+  optional Id id = 6;
+  optional Primitive prim = 7;
 }
 
 // A CompoundValue is an abstract type. It represents a value that is a made of other values.
@@ -233,10 +225,8 @@
   // The resource ID (0xPPTTEEEE) of the resource being referred.
   optional uint32 id = 2;
 
-  // If the resource ID is not resolved, the index into the symbol string pool where the name of
-  // the reference is stored. The symbol string pool is located at the top level ResourceTable
-  // message.
-  optional uint32 symbol_idx = 3;
+  // The optional resource name.
+  optional string name = 3;
 
   // Whether this reference is referencing a private resource (@*package:type/entry).
   optional bool private = 4;
@@ -249,23 +239,41 @@
 
 // A value that is a string.
 message String {
-  // The index into the values string pool, located at the top level ResourceTable message.
-  optional uint32 idx = 1;
+  optional string value = 1;
 }
 
 // A value that is a raw string, which is unescaped/uninterpreted. This is typically used to
 // represent the value of a style attribute before the attribute is compiled and the set of
 // allowed values is known.
 message RawString {
-  // The index into the values string pool, located at the top level ResourceTable message.
-  optional uint32 idx = 1;
+  optional string value = 1;
+}
+
+// A string with styling information, like html tags that specify boldness, italics, etc.
+message StyledString {
+  // The raw text of the string.
+  optional string value = 1;
+
+  // A Span marks a region of the string text that is styled.
+  message Span {
+    // The name of the tag, and its attributes, encoded as follows:
+    // tag_name;attr1=value1;attr2=value2;[...]
+    optional string tag = 1;
+
+    // The first character position this span applies to, in UTF-16 offset.
+    optional uint32 first_char = 2;
+
+    // The last character position this span applies to, in UTF-16 offset.
+    optional uint32 last_char = 3;
+  }
+
+  repeated Span span = 2;
 }
 
 // A value that is a reference to an external entity, like an XML file or a PNG.
 message FileReference {
-  // The index into the values string pool, located at the top level ResourceTable message. This
-  // represents the path to the file within an APK (typically res/type-config/entry.ext).
-  optional uint32 path_idx = 1;
+  // Path to a file within the APK (typically res/type-config/entry.ext).
+  optional string path = 1;
 }
 
 // A value that represents a primitive data type (float, int, boolean, etc.).
diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp
index 37d5ed0..e891954 100644
--- a/tools/aapt2/proto/TableProtoDeserializer.cpp
+++ b/tools/aapt2/proto/TableProtoDeserializer.cpp
@@ -55,15 +55,10 @@
 
 class PackagePbDeserializer {
  public:
-  PackagePbDeserializer(const android::ResStringPool* valuePool,
-                        const android::ResStringPool* sourcePool,
-                        const android::ResStringPool* symbolPool,
-                        const Source& source, IDiagnostics* diag)
-      : value_pool_(valuePool),
-        source_pool_(sourcePool),
-        symbol_pool_(symbolPool),
-        source_(source),
-        diag_(diag) {}
+  PackagePbDeserializer(const android::ResStringPool* sourcePool, const Source& source,
+                        IDiagnostics* diag)
+      : source_pool_(sourcePool), source_(source), diag_(diag) {
+  }
 
  public:
   bool DeserializeFromPb(const pb::Package& pbPackage, ResourceTable* table) {
@@ -87,8 +82,7 @@
       for (const pb::Entry& pbEntry : pbType.entries()) {
         ResourceEntry* entry = type->FindOrCreateEntry(pbEntry.name());
 
-        // Deserialize the symbol status (public/private with source and
-        // comments).
+        // Deserialize the symbol status (public/private with source and comments).
         if (pbEntry.has_symbol_status()) {
           const pb::SymbolStatus& pbStatus = pbEntry.symbol_status();
           if (pbStatus.has_source()) {
@@ -161,8 +155,7 @@
 
  private:
   std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
-                                              const ConfigDescription& config,
-                                              StringPool* pool) {
+                                              const ConfigDescription& config, StringPool* pool) {
     if (pb_item.has_ref()) {
       const pb::Reference& pb_ref = pb_item.ref();
       std::unique_ptr<Reference> ref = util::make_unique<Reference>();
@@ -173,45 +166,32 @@
 
     } else if (pb_item.has_prim()) {
       const pb::Primitive& pb_prim = pb_item.prim();
-      android::Res_value prim = {};
-      prim.dataType = static_cast<uint8_t>(pb_prim.type());
-      prim.data = pb_prim.data();
-      return util::make_unique<BinaryPrimitive>(prim);
+      return util::make_unique<BinaryPrimitive>(static_cast<uint8_t>(pb_prim.type()),
+                                                pb_prim.data());
 
     } else if (pb_item.has_id()) {
       return util::make_unique<Id>();
 
     } else if (pb_item.has_str()) {
-      const uint32_t idx = pb_item.str().idx();
-      const std::string str = util::GetString(*value_pool_, idx);
-
-      const android::ResStringPool_span* spans = value_pool_->styleAt(idx);
-      if (spans && spans->name.index != android::ResStringPool_span::END) {
-        StyleString style_str = {str};
-        while (spans->name.index != android::ResStringPool_span::END) {
-          style_str.spans.push_back(
-              Span{util::GetString(*value_pool_, spans->name.index),
-                   spans->firstChar, spans->lastChar});
-          spans++;
-        }
-        return util::make_unique<StyledString>(pool->MakeRef(
-            style_str, StringPool::Context(StringPool::Context::kNormalPriority, config)));
-      }
       return util::make_unique<String>(
-          pool->MakeRef(str, StringPool::Context(config)));
+          pool->MakeRef(pb_item.str().value(), StringPool::Context(config)));
 
     } else if (pb_item.has_raw_str()) {
-      const uint32_t idx = pb_item.raw_str().idx();
-      const std::string str = util::GetString(*value_pool_, idx);
       return util::make_unique<RawString>(
-          pool->MakeRef(str, StringPool::Context(config)));
+          pool->MakeRef(pb_item.raw_str().value(), StringPool::Context(config)));
+
+    } else if (pb_item.has_styled_str()) {
+      const pb::StyledString& pb_str = pb_item.styled_str();
+      StyleString style_str{pb_str.value()};
+      for (const pb::StyledString::Span& pb_span : pb_str.span()) {
+        style_str.spans.push_back(Span{pb_span.tag(), pb_span.first_char(), pb_span.last_char()});
+      }
+      return util::make_unique<StyledString>(pool->MakeRef(
+          style_str, StringPool::Context(StringPool::Context::kNormalPriority, config)));
 
     } else if (pb_item.has_file()) {
-      const uint32_t idx = pb_item.file().path_idx();
-      const std::string str = util::GetString(*value_pool_, idx);
       return util::make_unique<FileReference>(pool->MakeRef(
-          str,
-          StringPool::Context(StringPool::Context::kHighPriority, config)));
+          pb_item.file().path(), StringPool::Context(StringPool::Context::kHighPriority, config)));
 
     } else {
       diag_->Error(DiagMessage(source_) << "unknown item");
@@ -255,15 +235,13 @@
         std::unique_ptr<Style> style = util::make_unique<Style>();
         if (pb_style.has_parent()) {
           style->parent = Reference();
-          if (!DeserializeReferenceFromPb(pb_style.parent(),
-                                          &style->parent.value())) {
+          if (!DeserializeReferenceFromPb(pb_style.parent(), &style->parent.value())) {
             return {};
           }
 
           if (pb_style.has_parent_source()) {
             Source parent_source;
-            DeserializeSourceFromPb(pb_style.parent_source(), *source_pool_,
-                                    &parent_source);
+            DeserializeSourceFromPb(pb_style.parent_source(), *source_pool_, &parent_source);
             style->parent.value().SetSource(std::move(parent_source));
           }
         }
@@ -300,8 +278,7 @@
         const pb::Array& pb_array = pb_compound_value.array();
         std::unique_ptr<Array> array = util::make_unique<Array>();
         for (const pb::Array_Entry& pb_entry : pb_array.entries()) {
-          std::unique_ptr<Item> item =
-              DeserializeItemFromPb(pb_entry.item(), config, pool);
+          std::unique_ptr<Item> item = DeserializeItemFromPb(pb_entry.item(), config, pool);
           if (!item) {
             return {};
           }
@@ -316,8 +293,7 @@
         std::unique_ptr<Plural> plural = util::make_unique<Plural>();
         for (const pb::Plural_Entry& pb_entry : pb_plural.entries()) {
           size_t pluralIdx = DeserializePluralEnumFromPb(pb_entry.arity());
-          plural->values[pluralIdx] =
-              DeserializeItemFromPb(pb_entry.item(), config, pool);
+          plural->values[pluralIdx] = DeserializeItemFromPb(pb_entry.item(), config, pool);
           if (!plural->values[pluralIdx]) {
             return {};
           }
@@ -350,11 +326,10 @@
       out_ref->id = ResourceId(pb_ref.id());
     }
 
-    if (pb_ref.has_symbol_idx()) {
-      const std::string str_symbol = util::GetString(*symbol_pool_, pb_ref.symbol_idx());
+    if (pb_ref.has_name()) {
       ResourceNameRef name_ref;
-      if (!ResourceUtils::ParseResourceName(str_symbol, &name_ref, nullptr)) {
-        diag_->Error(DiagMessage(source_) << "invalid reference name '" << str_symbol << "'");
+      if (!ResourceUtils::ParseResourceName(pb_ref.name(), &name_ref, nullptr)) {
+        diag_->Error(DiagMessage(source_) << "invalid reference name '" << pb_ref.name() << "'");
         return false;
       }
 
@@ -377,60 +352,32 @@
   }
 
  private:
-  const android::ResStringPool* value_pool_;
   const android::ResStringPool* source_pool_;
-  const android::ResStringPool* symbol_pool_;
   const Source source_;
   IDiagnostics* diag_;
 };
 
 }  // namespace
 
-std::unique_ptr<ResourceTable> DeserializeTableFromPb(
-    const pb::ResourceTable& pb_table, const Source& source,
-    IDiagnostics* diag) {
-  // We import the android namespace because on Windows NO_ERROR is a macro, not
-  // an enum, which
+std::unique_ptr<ResourceTable> DeserializeTableFromPb(const pb::ResourceTable& pb_table,
+                                                      const Source& source, IDiagnostics* diag) {
+  // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which
   // causes errors when qualifying it with android::
   using namespace android;
 
   std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
 
-  if (!pb_table.has_string_pool()) {
-    diag->Error(DiagMessage(source) << "no string pool found");
-    return {};
-  }
-
-  ResStringPool value_pool;
-  status_t result = value_pool.setTo(pb_table.string_pool().data().data(),
-                                     pb_table.string_pool().data().size());
-  if (result != NO_ERROR) {
-    diag->Error(DiagMessage(source) << "invalid string pool");
-    return {};
-  }
-
   ResStringPool source_pool;
   if (pb_table.has_source_pool()) {
-    result = source_pool.setTo(pb_table.source_pool().data().data(),
-                               pb_table.source_pool().data().size());
+    status_t result = source_pool.setTo(pb_table.source_pool().data().data(),
+                                        pb_table.source_pool().data().size());
     if (result != NO_ERROR) {
       diag->Error(DiagMessage(source) << "invalid source pool");
       return {};
     }
   }
 
-  ResStringPool symbol_pool;
-  if (pb_table.has_symbol_pool()) {
-    result = symbol_pool.setTo(pb_table.symbol_pool().data().data(),
-                               pb_table.symbol_pool().data().size());
-    if (result != NO_ERROR) {
-      diag->Error(DiagMessage(source) << "invalid symbol pool");
-      return {};
-    }
-  }
-
-  PackagePbDeserializer package_pb_deserializer(&value_pool, &source_pool,
-                                                &symbol_pool, source, diag);
+  PackagePbDeserializer package_pb_deserializer(&source_pool, source, diag);
   for (const pb::Package& pb_package : pb_table.packages()) {
     if (!package_pb_deserializer.DeserializeFromPb(pb_package, table.get())) {
       return {};
diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp
index 730442c..3f48655 100644
--- a/tools/aapt2/proto/TableProtoSerializer.cpp
+++ b/tools/aapt2/proto/TableProtoSerializer.cpp
@@ -24,9 +24,9 @@
 
 #include "android-base/logging.h"
 
-using google::protobuf::io::CodedOutputStream;
-using google::protobuf::io::CodedInputStream;
-using google::protobuf::io::ZeroCopyOutputStream;
+using ::google::protobuf::io::CodedInputStream;
+using ::google::protobuf::io::CodedOutputStream;
+using ::google::protobuf::io::ZeroCopyOutputStream;
 
 namespace aapt {
 
@@ -36,46 +36,46 @@
  public:
   using RawValueVisitor::Visit;
 
-  /**
-   * Constructor to use when expecting to serialize any value.
-   */
-  PbSerializerVisitor(StringPool* source_pool, StringPool* symbol_pool,
-                      pb::Value* out_pb_value)
-      : source_pool_(source_pool),
-        symbol_pool_(symbol_pool),
-        out_pb_value_(out_pb_value),
-        out_pb_item_(nullptr) {}
+  // Constructor to use when expecting to serialize any value.
+  PbSerializerVisitor(StringPool* source_pool, pb::Value* out_pb_value)
+      : source_pool_(source_pool), out_pb_value_(out_pb_value), out_pb_item_(nullptr) {
+  }
 
-  /**
-   * Constructor to use when expecting to serialize an Item.
-   */
-  PbSerializerVisitor(StringPool* sourcePool, StringPool* symbolPool,
-                      pb::Item* outPbItem)
-      : source_pool_(sourcePool),
-        symbol_pool_(symbolPool),
-        out_pb_value_(nullptr),
-        out_pb_item_(outPbItem) {}
+  // Constructor to use when expecting to serialize an Item.
+  PbSerializerVisitor(StringPool* sourcePool, pb::Item* outPbItem)
+      : source_pool_(sourcePool), out_pb_value_(nullptr), out_pb_item_(outPbItem) {
+  }
 
   void Visit(Reference* ref) override {
     SerializeReferenceToPb(*ref, pb_item()->mutable_ref());
   }
 
   void Visit(String* str) override {
-    pb_item()->mutable_str()->set_idx(str->value.index());
+    pb_item()->mutable_str()->set_value(*str->value);
+  }
+
+  void Visit(RawString* str) override {
+    pb_item()->mutable_raw_str()->set_value(*str->value);
   }
 
   void Visit(StyledString* str) override {
-    pb_item()->mutable_str()->set_idx(str->value.index());
+    pb::StyledString* pb_str = pb_item()->mutable_styled_str();
+    pb_str->set_value(str->value->value);
+
+    for (const StringPool::Span& span : str->value->spans) {
+      pb::StyledString::Span* pb_span = pb_str->add_span();
+      pb_span->set_tag(*span.name);
+      pb_span->set_first_char(span.first_char);
+      pb_span->set_last_char(span.last_char);
+    }
   }
 
   void Visit(FileReference* file) override {
-    pb_item()->mutable_file()->set_path_idx(file->path.index());
+    pb_item()->mutable_file()->set_path(*file->path);
   }
 
-  void Visit(Id* id) override { pb_item()->mutable_id(); }
-
-  void Visit(RawString* raw_str) override {
-    pb_item()->mutable_raw_str()->set_idx(raw_str->value.index());
+  void Visit(Id* /*id*/) override {
+    pb_item()->mutable_id();
   }
 
   void Visit(BinaryPrimitive* prim) override {
@@ -119,7 +119,7 @@
 
       pb::Item* pb_item = pb_entry->mutable_item();
       SerializeItemCommonToPb(entry.key, pb_entry);
-      PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, pb_item);
+      PbSerializerVisitor sub_visitor(source_pool_, pb_item);
       entry.value->Accept(&sub_visitor);
     }
   }
@@ -138,8 +138,7 @@
     for (auto& value : array->items) {
       pb::Array_Entry* pb_entry = pb_array->add_entries();
       SerializeItemCommonToPb(*value, pb_entry);
-      PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_,
-                                      pb_entry->mutable_item());
+      PbSerializerVisitor sub_visitor(source_pool_, pb_entry->mutable_item());
       value->Accept(&sub_visitor);
     }
   }
@@ -157,7 +156,7 @@
       pb_entry->set_arity(SerializePluralEnumToPb(i));
       pb::Item* pb_element = pb_entry->mutable_item();
       SerializeItemCommonToPb(*plural->values[i], pb_entry);
-      PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, pb_element);
+      PbSerializerVisitor sub_visitor(source_pool_, pb_element);
       plural->values[i]->Accept(&sub_visitor);
     }
   }
@@ -179,8 +178,7 @@
 
   template <typename T>
   void SerializeItemCommonToPb(const Item& item, T* pb_item) {
-    SerializeSourceToPb(item.GetSource(), source_pool_,
-                        pb_item->mutable_source());
+    SerializeSourceToPb(item.GetSource(), source_pool_, pb_item->mutable_source());
     if (!item.GetComment().empty()) {
       pb_item->set_comment(item.GetComment());
     }
@@ -192,8 +190,7 @@
     }
 
     if (ref.name) {
-      StringPool::Ref symbol_ref = symbol_pool_->MakeRef(ref.name.value().ToString());
-      pb_ref->set_symbol_idx(static_cast<uint32_t>(symbol_ref.index()));
+      pb_ref->set_name(ref.name.value().ToString());
     }
 
     pb_ref->set_private_(ref.private_reference);
@@ -201,7 +198,6 @@
   }
 
   StringPool* source_pool_;
-  StringPool* symbol_pool_;
   pb::Value* out_pb_value_;
   pb::Item* out_pb_item_;
 };
@@ -220,9 +216,7 @@
   });
 
   auto pb_table = util::make_unique<pb::ResourceTable>();
-  SerializeStringPoolToPb(table->string_pool, pb_table->mutable_string_pool());
-
-  StringPool source_pool, symbol_pool;
+  StringPool source_pool;
 
   for (auto& package : table->packages) {
     pb::Package* pb_package = pb_table->add_packages();
@@ -270,7 +264,7 @@
             pb_value->set_weak(true);
           }
 
-          PbSerializerVisitor visitor(&source_pool, &symbol_pool, pb_value);
+          PbSerializerVisitor visitor(&source_pool, pb_value);
           config_value->value->Accept(&visitor);
         }
       }
@@ -278,7 +272,6 @@
   }
 
   SerializeStringPoolToPb(source_pool, pb_table->mutable_source_pool());
-  SerializeStringPoolToPb(symbol_pool, pb_table->mutable_symbol_pool());
   return pb_table;
 }
 
diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp
index 3ebb08e..3ba4e6b 100644
--- a/tools/aapt2/proto/TableProtoSerializer_test.cpp
+++ b/tools/aapt2/proto/TableProtoSerializer_test.cpp
@@ -20,7 +20,9 @@
 #include "test/Test.h"
 
 using ::google::protobuf::io::StringOutputStream;
+using ::testing::Eq;
 using ::testing::NotNull;
+using ::testing::SizeIs;
 
 namespace aapt {
 
@@ -38,12 +40,12 @@
 
   Symbol public_symbol;
   public_symbol.state = SymbolState::kPublic;
-  ASSERT_TRUE(table->SetSymbolState(
-      test::ParseNameOrDie("com.app.a:layout/main"), ResourceId(0x7f020000),
-      public_symbol, context->GetDiagnostics()));
+  ASSERT_TRUE(table->SetSymbolState(test::ParseNameOrDie("com.app.a:layout/main"),
+                                    ResourceId(0x7f020000), public_symbol,
+                                    context->GetDiagnostics()));
 
   Id* id = test::GetValue<Id>(table.get(), "com.app.a:id/foo");
-  ASSERT_NE(nullptr, id);
+  ASSERT_THAT(id, NotNull());
 
   // Make a plural.
   std::unique_ptr<Plural> plural = util::make_unique<Plural>();
@@ -52,6 +54,15 @@
                                  ConfigDescription{}, {}, std::move(plural),
                                  context->GetDiagnostics()));
 
+  // Make a styled string.
+  StyleString style_string;
+  style_string.str = "hello";
+  style_string.spans.push_back(Span{"b", 0u, 4u});
+  ASSERT_TRUE(
+      table->AddResource(test::ParseNameOrDie("com.app.a:string/styled"), ConfigDescription{}, {},
+                         util::make_unique<StyledString>(table->string_pool.MakeRef(style_string)),
+                         context->GetDiagnostics()));
+
   // Make a resource with different products.
   ASSERT_TRUE(table->AddResource(
       test::ParseNameOrDie("com.app.a:integer/one"),
@@ -65,9 +76,8 @@
       context->GetDiagnostics()));
 
   // Make a reference with both resource name and resource ID.
-  // The reference should point to a resource outside of this table to test that
-  // both
-  // name and id get serialized.
+  // The reference should point to a resource outside of this table to test that both name and id
+  // get serialized.
   Reference expected_ref;
   expected_ref.name = test::ParseNameOrDie("android:layout/main");
   expected_ref.id = ResourceId(0x01020000);
@@ -85,36 +95,45 @@
 
   Id* new_id = test::GetValue<Id>(new_table.get(), "com.app.a:id/foo");
   ASSERT_THAT(new_id, NotNull());
-  EXPECT_EQ(id->IsWeak(), new_id->IsWeak());
+  EXPECT_THAT(new_id->IsWeak(), Eq(id->IsWeak()));
 
   Maybe<ResourceTable::SearchResult> result =
       new_table->FindResource(test::ParseNameOrDie("com.app.a:layout/main"));
   ASSERT_TRUE(result);
-  EXPECT_EQ(SymbolState::kPublic, result.value().type->symbol_status.state);
-  EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbol_status.state);
+
+  EXPECT_THAT(result.value().type->symbol_status.state, Eq(SymbolState::kPublic));
+  EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic));
 
   result = new_table->FindResource(test::ParseNameOrDie("com.app.a:bool/foo"));
   ASSERT_TRUE(result);
-  EXPECT_EQ(SymbolState::kUndefined, result.value().entry->symbol_status.state);
+  EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kUndefined));
   EXPECT_TRUE(result.value().entry->symbol_status.allow_new);
 
   // Find the product-dependent values
   BinaryPrimitive* prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(
       new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), "");
   ASSERT_THAT(prim, NotNull());
-  EXPECT_EQ(123u, prim->value.data);
+  EXPECT_THAT(prim->value.data, Eq(123u));
 
   prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(
       new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), "tablet");
   ASSERT_THAT(prim, NotNull());
-  EXPECT_EQ(321u, prim->value.data);
+  EXPECT_THAT(prim->value.data, Eq(321u));
 
   Reference* actual_ref = test::GetValue<Reference>(new_table.get(), "com.app.a:layout/abc");
   ASSERT_THAT(actual_ref, NotNull());
   ASSERT_TRUE(actual_ref->name);
   ASSERT_TRUE(actual_ref->id);
-  EXPECT_EQ(expected_ref.name.value(), actual_ref->name.value());
-  EXPECT_EQ(expected_ref.id.value(), actual_ref->id.value());
+  EXPECT_THAT(*actual_ref, Eq(expected_ref));
+
+  StyledString* actual_styled_str =
+      test::GetValue<StyledString>(new_table.get(), "com.app.a:string/styled");
+  ASSERT_THAT(actual_styled_str, NotNull());
+  EXPECT_THAT(actual_styled_str->value->value, Eq("hello"));
+  ASSERT_THAT(actual_styled_str->value->spans, SizeIs(1u));
+  EXPECT_THAT(*actual_styled_str->value->spans[0].name, Eq("b"));
+  EXPECT_THAT(actual_styled_str->value->spans[0].first_char, Eq(0u));
+  EXPECT_THAT(actual_styled_str->value->spans[0].last_char, Eq(4u));
 }
 
 TEST(TableProtoSerializer, SerializeFileHeader) {