AAPT2: Fix JavaDoc first sentence extraction.

The old algorithm for detecting the first sentence of a JavaDoc comment
looked for the first occurence of '.'. This does not work when code or a
{@link android.R.styleable} link is encountered in the first sentence.

Switch to checking for whitespace characters after the '.' character.

Bug: 62900335
Test: make aapt2_tests
Change-Id: I8238f6a6304c9c2f92e2e576ca8962a59c2b20ea
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
index a0ef00b..1f83fa0 100644
--- a/tools/aapt2/java/AnnotationProcessor.cpp
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -18,12 +18,29 @@
 
 #include <algorithm>
 
+#include "text/Unicode.h"
+#include "text/Utf8Iterator.h"
 #include "util/Util.h"
 
-using android::StringPiece;
+using ::aapt::text::Utf8Iterator;
+using ::android::StringPiece;
 
 namespace aapt {
 
+StringPiece AnnotationProcessor::ExtractFirstSentence(const StringPiece& comment) {
+  Utf8Iterator iter(comment);
+  while (iter.HasNext()) {
+    const char32_t codepoint = iter.Next();
+    if (codepoint == U'.') {
+      const size_t current_position = iter.Position();
+      if (!iter.HasNext() || text::IsWhitespace(iter.Next())) {
+        return comment.substr(0, current_position);
+      }
+    }
+  }
+  return comment;
+}
+
 void AnnotationProcessor::AppendCommentLine(std::string& comment) {
   static const std::string sDeprecated = "@deprecated";
   static const std::string sSystemApi = "@SystemApi";
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
index 99cd44f..a06eda0 100644
--- a/tools/aapt2/java/AnnotationProcessor.h
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -53,6 +53,8 @@
  */
 class AnnotationProcessor {
  public:
+  static android::StringPiece ExtractFirstSentence(const android::StringPiece& comment);
+
   /**
    * Adds more comments. Since resources can have various values with different
    * configurations,
diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp
index 3e43c42..9ccac88 100644
--- a/tools/aapt2/java/AnnotationProcessor_test.cpp
+++ b/tools/aapt2/java/AnnotationProcessor_test.cpp
@@ -18,6 +18,10 @@
 
 #include "test/Test.h"
 
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::Not;
+
 namespace aapt {
 
 TEST(AnnotationProcessorTest, EmitsDeprecated) {
@@ -33,7 +37,7 @@
   processor.WriteToStream(&result, "");
   std::string annotations = result.str();
 
-  EXPECT_NE(std::string::npos, annotations.find("@Deprecated"));
+  EXPECT_THAT(annotations, HasSubstr("@Deprecated"));
 }
 
 TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationAndRemovesFromComment) {
@@ -44,10 +48,20 @@
   processor.WriteToStream(&result, "");
   std::string annotations = result.str();
 
-  EXPECT_NE(std::string::npos,
-            annotations.find("@android.annotation.SystemApi"));
-  EXPECT_EQ(std::string::npos, annotations.find("@SystemApi"));
-  EXPECT_NE(std::string::npos, annotations.find("This is a system API"));
+  EXPECT_THAT(annotations, HasSubstr("@android.annotation.SystemApi"));
+  EXPECT_THAT(annotations, Not(HasSubstr("@SystemApi")));
+  EXPECT_THAT(annotations, HasSubstr("This is a system API"));
+}
+
+TEST(AnnotationProcessor, ExtractsFirstSentence) {
+  EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence("This is the only sentence"),
+              Eq("This is the only sentence"));
+  EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence(
+                  "This is the\n  first sentence.  This is the rest of the paragraph."),
+              Eq("This is the\n  first sentence."));
+  EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence(
+                  "This is the first sentence with a {@link android.R.styleable.Theme}."),
+              Eq("This is the first sentence with a {@link android.R.styleable.Theme}."));
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 2a23aa9..44fa0f1 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -299,24 +299,16 @@
       }
 
       const ResourceName& attr_name = entry.attr_ref->name.value();
-      styleable_comment << "<tr><td>";
-      styleable_comment << "<code>{@link #" << entry.field_name << " "
-                        << (!attr_name.package.empty()
-                                ? attr_name.package
-                                : context_->GetCompilationPackage())
-                        << ":" << attr_name.entry << "}</code>";
-      styleable_comment << "</td>";
-
-      styleable_comment << "<td>";
+      styleable_comment << "<tr><td><code>{@link #" << entry.field_name << " "
+                        << (!attr_name.package.empty() ? attr_name.package
+                                                       : context_->GetCompilationPackage())
+                        << ":" << attr_name.entry << "}</code></td>";
 
       // Only use the comment up until the first '.'. This is to stay compatible with
       // the way old AAPT did it (presumably to keep it short and to avoid including
       // annotations like @hide which would affect this Styleable).
-      auto iter = std::find(attr_comment_line.begin(), attr_comment_line.end(), '.');
-      if (iter != attr_comment_line.end()) {
-        attr_comment_line = attr_comment_line.substr(0, (iter - attr_comment_line.begin()) + 1);
-      }
-      styleable_comment << attr_comment_line << "</td></tr>\n";
+      styleable_comment << "<td>" << AnnotationProcessor::ExtractFirstSentence(attr_comment_line)
+                        << "</td></tr>\n";
     }
     styleable_comment << "</table>\n";