fix header includes for generic types (NDK)

Generic type references can be nested like A<B<C>>. We need to visit
them all recursively.

Bug: n/a
Test: aidl_unittests
Change-Id: I16cd9db9ab543fc491db953aaa41d025d04dd7d4
diff --git a/aidl_unittest.cpp b/aidl_unittest.cpp
index 2f4c7b3..470b09d 100644
--- a/aidl_unittest.cpp
+++ b/aidl_unittest.cpp
@@ -3011,6 +3011,39 @@
   EXPECT_EQ(expected_stderr, GetCapturedStderr());
 }
 
+struct GenericAidlTest : ::testing::Test {
+  FakeIoDelegate io_delegate_;
+  void Compile(string cmd) {
+    io_delegate_.SetFileContents("Foo.aidl", "parcelable Foo { Bar<Baz<Qux> > x; }");
+    io_delegate_.SetFileContents("Bar.aidl", "parcelable Bar<T> {  }");
+    io_delegate_.SetFileContents("Baz.aidl", "parcelable Baz<T> {  }");
+    io_delegate_.SetFileContents("Qux.aidl", "parcelable Qux {  }");
+
+    Options options = Options::From(cmd);
+    CaptureStderr();
+    EXPECT_EQ(0, ::android::aidl::compile_aidl(options, io_delegate_));
+    EXPECT_EQ("", GetCapturedStderr());
+  }
+};
+
+TEST_F(GenericAidlTest, ImportGenericParameterTypesCPP) {
+  Compile("aidl Foo.aidl --lang=cpp -I . -o out -h out");
+  string code;
+  EXPECT_TRUE(io_delegate_.GetWrittenContents("out/Foo.h", &code));
+  EXPECT_THAT(code, testing::HasSubstr("#include <Bar.h>"));
+  EXPECT_THAT(code, testing::HasSubstr("#include <Baz.h>"));
+  EXPECT_THAT(code, testing::HasSubstr("#include <Qux.h>"));
+}
+
+TEST_F(GenericAidlTest, ImportGenericParameterTypesNDK) {
+  Compile("aidl Foo.aidl --lang=ndk -I . -o out -h out");
+  string code;
+  EXPECT_TRUE(io_delegate_.GetWrittenContents("out/aidl/Foo.h", &code));
+  EXPECT_THAT(code, testing::HasSubstr("#include <aidl/Bar.h>"));
+  EXPECT_THAT(code, testing::HasSubstr("#include <aidl/Baz.h>"));
+  EXPECT_THAT(code, testing::HasSubstr("#include <aidl/Qux.h>"));
+}
+
 TEST_P(AidlTest, RejectGenericStructuredParcelabelRepeatedParam) {
   io_delegate_.SetFileContents("Foo.aidl", "parcelable Foo<T,T> { int a; int A; }");
   Options options =
diff --git a/generate_ndk.cpp b/generate_ndk.cpp
index e511680..64334a9 100644
--- a/generate_ndk.cpp
+++ b/generate_ndk.cpp
@@ -211,18 +211,22 @@
 
   std::set<std::string> includes;
 
+  // visit a type and collect all reference types' headers
+  std::function<void(const AidlTypeSpecifier& type)> visit = [&](const AidlTypeSpecifier& type) {
+    includes.insert(headerFilePath(type));
+    if (type.IsGeneric()) {
+      for (const auto& param : type.GetTypeParameters()) {
+        visit(*param);
+      }
+    }
+  };
+
   const AidlInterface* interface = defined_type.AsInterface();
   if (interface != nullptr) {
     for (const auto& method : interface->GetMethods()) {
-      includes.insert(headerFilePath(method->GetType()));
+      visit(method->GetType());
       for (const auto& argument : method->GetArguments()) {
-        includes.insert(headerFilePath(argument->GetType()));
-        // Check the method arguments for generic type arguments
-        if (argument->GetType().IsGeneric()) {
-          for (const auto& type_argument : argument->GetType().GetTypeParameters()) {
-            includes.insert(headerFilePath(*type_argument));
-          }
-        }
+        visit(argument->GetType());
       }
     }
   }
@@ -230,11 +234,11 @@
   const AidlStructuredParcelable* parcelable = defined_type.AsStructuredParcelable();
   if (parcelable != nullptr) {
     for (const auto& field : parcelable->GetFields()) {
-      includes.insert(headerFilePath(field->GetType()));
+      visit(field->GetType());
       // Check the fields for generic type arguments
       if (field->GetType().IsGeneric()) {
         for (const auto& type_argument : field->GetType().GetTypeParameters()) {
-          includes.insert(headerFilePath(*type_argument));
+          visit(*type_argument);
         }
       }
     }
@@ -242,7 +246,7 @@
 
   const AidlEnumDeclaration* enum_decl = defined_type.AsEnumDeclaration();
   if (enum_decl != nullptr) {
-    includes.insert(headerFilePath(enum_decl->GetBackingType()));
+    visit(enum_decl->GetBackingType());
   }
 
   for (const auto& path : includes) {