union: java backend

union is a parcelable which can hold only a single field with a tag.

Example:
  union Union {
    int n;
    String s;
  }

In Java, you can instantiate it with
- default constructor: init with first field
- value constructor: Union.n(42) or Union.s("abc")

You can query "tag" before getting the contents from it.
It also supports getter/setter.

Example:
  void foo(Union u) {
    if (u.getTag() == Union.n) {  // query
      int n = u.getN();           // getter
      ...
    }
    u.setS("abc");                // setter
  }

Bug: 150948558
Test: atest aidl_integration_test
Change-Id: I5c2d87e09462c0d3c6617d73fdf0e49c281d551e
diff --git a/aidl.cpp b/aidl.cpp
index 9250ba1..2f00a12 100644
--- a/aidl.cpp
+++ b/aidl.cpp
@@ -424,11 +424,11 @@
   if (main_parser == nullptr) {
     return AidlError::PARSE_ERROR;
   }
-  int num_interfaces_or_structured_parcelables = 0;
+  int num_top_level_decls = 0;
   for (const auto& type : main_parser->ParsedDocument().DefinedTypes()) {
-    if (type->AsInterface() != nullptr || type->AsStructuredParcelable() != nullptr) {
-      num_interfaces_or_structured_parcelables++;
-      if (num_interfaces_or_structured_parcelables > 1) {
+    if (type->AsUnstructuredParcelable() == nullptr) {
+      num_top_level_decls++;
+      if (num_top_level_decls > 1) {
         AIDL_ERROR(*type) << "You must declare only one type per file.";
         return AidlError::BAD_TYPE;
       }
@@ -578,8 +578,10 @@
     AidlStructuredParcelable* parcelable = defined_type->AsStructuredParcelable();
     AidlParcelable* unstructured_parcelable = defined_type->AsUnstructuredParcelable();
     AidlEnumDeclaration* enum_decl = defined_type->AsEnumDeclaration();
-    AIDL_FATAL_IF(!!interface + !!parcelable + !!unstructured_parcelable + !!enum_decl != 1,
-                  defined_type);
+    AidlUnionDecl* union_decl = defined_type->AsUnionDeclaration();
+    AIDL_FATAL_IF(
+        !!interface + !!parcelable + !!unstructured_parcelable + !!enum_decl + !!union_decl != 1,
+        defined_type);
 
     // Ensure that foo.bar.IFoo is defined in <some_path>/foo/bar/IFoo.aidl
     if (num_defined_types == 1 && !check_filename(input_file_name, *defined_type)) {
diff --git a/aidl_checkapi.cpp b/aidl_checkapi.cpp
index 0f2f4b0..f20b5ca 100644
--- a/aidl_checkapi.cpp
+++ b/aidl_checkapi.cpp
@@ -182,8 +182,8 @@
   return specifier.IsNullable();
 }
 
-static bool are_compatible_parcelables(const AidlStructuredParcelable& older,
-                                       const AidlStructuredParcelable& newer) {
+template <typename ParcelableType>
+static bool are_compatible_parcelables(const ParcelableType& older, const ParcelableType& newer) {
   const auto& old_fields = older.GetFields();
   const auto& new_fields = newer.GetFields();
   if (old_fields.size() > new_fields.size()) {
@@ -370,6 +370,16 @@
       }
       compatible &= are_compatible_parcelables(*(old_type->AsStructuredParcelable()),
                                                *(new_type->AsStructuredParcelable()));
+    } else if (old_type->AsUnionDeclaration() != nullptr) {
+      if (new_type->AsUnionDeclaration() == nullptr) {
+        AIDL_ERROR(new_type) << "Type mismatch: " << old_type->GetCanonicalName()
+                             << " is changed from " << old_type->GetPreprocessDeclarationName()
+                             << " to " << new_type->GetPreprocessDeclarationName();
+        compatible = false;
+        continue;
+      }
+      compatible &= are_compatible_parcelables(*(old_type->AsUnionDeclaration()),
+                                               *(new_type->AsUnionDeclaration()));
     } else if (old_type->AsEnumDeclaration() != nullptr) {
       if (new_type->AsEnumDeclaration() == nullptr) {
         AIDL_ERROR(new_type) << "Type mismatch: " << old_type->GetCanonicalName()
diff --git a/aidl_language.cpp b/aidl_language.cpp
index b9985e5..04a4493 100644
--- a/aidl_language.cpp
+++ b/aidl_language.cpp
@@ -1110,6 +1110,32 @@
   writer->Write("}\n");
 }
 
+AidlUnionDecl::AidlUnionDecl(const AidlLocation& location, const std::string& name,
+                             const std::string& package, const std::string& comments,
+                             std::vector<std::unique_ptr<AidlVariableDeclaration>>* variables,
+                             std::vector<std::string>* type_params)
+    : AidlParcelable(location, name, package, comments, "" /*cpp_header*/, type_params),
+      variables_(std::move(*variables)) {}
+
+std::set<AidlAnnotation::Type> AidlUnionDecl::GetSupportedAnnotations() const {
+  return {AidlAnnotation::Type::VINTF_STABILITY, AidlAnnotation::Type::HIDE,
+          AidlAnnotation::Type::JAVA_PASSTHROUGH};
+}
+
+void AidlUnionDecl::Dump(CodeWriter* writer) const {
+  DumpHeader(writer);
+  writer->Write("union %s {\n", GetName().c_str());
+  writer->Indent();
+  for (const auto& field : GetFields()) {
+    if (field->GetType().IsHidden()) {
+      AddHideComment(writer);
+    }
+    writer->Write("%s;\n", field->ToString().c_str());
+  }
+  writer->Dedent();
+  writer->Write("}\n");
+}
+
 // TODO: we should treat every backend all the same in future.
 bool AidlInterface::LanguageSpecificCheckValid(const AidlTypenames& typenames,
                                                Options::Language lang) const {
diff --git a/aidl_language.h b/aidl_language.h
index a648232..6a5932d 100644
--- a/aidl_language.h
+++ b/aidl_language.h
@@ -646,6 +646,8 @@
 class AidlInterface;
 class AidlParcelable;
 class AidlStructuredParcelable;
+
+class AidlUnionDecl;
 // AidlDefinedType represents either an interface, parcelable, or enum that is
 // defined in the source file.
 class AidlDefinedType : public AidlAnnotatable {
@@ -676,6 +678,7 @@
   virtual const AidlStructuredParcelable* AsStructuredParcelable() const { return nullptr; }
   virtual const AidlParcelable* AsParcelable() const { return nullptr; }
   virtual const AidlEnumDeclaration* AsEnumDeclaration() const { return nullptr; }
+  virtual const AidlUnionDecl* AsUnionDeclaration() const { return nullptr; }
   virtual const AidlInterface* AsInterface() const { return nullptr; }
   virtual const AidlParameterizable<std::string>* AsParameterizable() const { return nullptr; }
   bool CheckValid(const AidlTypenames& typenames) const override;
@@ -692,6 +695,10 @@
     return const_cast<AidlEnumDeclaration*>(
         const_cast<const AidlDefinedType*>(this)->AsEnumDeclaration());
   }
+  AidlUnionDecl* AsUnionDeclaration() {
+    return const_cast<AidlUnionDecl*>(
+        const_cast<const AidlDefinedType*>(this)->AsUnionDeclaration());
+  }
   AidlInterface* AsInterface() {
     return const_cast<AidlInterface*>(const_cast<const AidlDefinedType*>(this)->AsInterface());
   }
@@ -703,6 +710,7 @@
 
   const AidlParcelable* AsUnstructuredParcelable() const {
     if (this->AsStructuredParcelable() != nullptr) return nullptr;
+    if (this->AsUnionDeclaration() != nullptr) return nullptr;
     return this->AsParcelable();
   }
   AidlParcelable* AsUnstructuredParcelable() {
@@ -846,6 +854,40 @@
   std::unique_ptr<const AidlTypeSpecifier> backing_type_;
 };
 
+class AidlUnionDecl : public AidlParcelable {
+ public:
+  AidlUnionDecl(const AidlLocation& location, const std::string& name, const std::string& package,
+                const std::string& comments,
+                std::vector<std::unique_ptr<AidlVariableDeclaration>>* variables,
+                std::vector<std::string>* type_params);
+  virtual ~AidlUnionDecl() = default;
+
+  // non-copyable, non-movable
+  AidlUnionDecl(const AidlUnionDecl&) = delete;
+  AidlUnionDecl(AidlUnionDecl&&) = delete;
+  AidlUnionDecl& operator=(const AidlUnionDecl&) = delete;
+  AidlUnionDecl& operator=(AidlUnionDecl&&) = delete;
+
+  std::set<AidlAnnotation::Type> GetSupportedAnnotations() const override;
+
+  const AidlNode& AsAidlNode() const override { return *this; }
+
+  const std::vector<std::unique_ptr<AidlVariableDeclaration>>& GetFields() const {
+    return variables_;
+  }
+  bool LanguageSpecificCheckValid(const AidlTypenames& /*typenames*/,
+                                  Options::Language) const override {
+    return true;
+  }
+  std::string GetPreprocessDeclarationName() const override { return "union"; }
+
+  void Dump(CodeWriter* writer) const override;
+  const AidlUnionDecl* AsUnionDeclaration() const override { return this; }
+
+ private:
+  const std::vector<std::unique_ptr<AidlVariableDeclaration>> variables_;
+};
+
 class AidlInterface final : public AidlDefinedType {
  public:
   AidlInterface(const AidlLocation& location, const std::string& name, const std::string& comments,
diff --git a/aidl_language_l.ll b/aidl_language_l.ll
index 7646f3f..a41b96a 100644
--- a/aidl_language_l.ll
+++ b/aidl_language_l.ll
@@ -124,6 +124,9 @@
 enum                  { yylval->token = new AidlToken("enum", extra_text);
                         return yy::parser::token::ENUM;
                       }
+union                 { yylval->token = new AidlToken("union", extra_text);
+                        return yy::parser::token::UNION;
+                      }
 
     /* scalars */
 {identifier}          { yylval->token = new AidlToken(yytext, extra_text);
diff --git a/aidl_language_y.yy b/aidl_language_y.yy
index 8179596..7ce8859 100644
--- a/aidl_language_y.yy
+++ b/aidl_language_y.yy
@@ -81,7 +81,6 @@
     AidlConstantValue* const_expr;
     AidlEnumerator* enumerator;
     std::vector<std::unique_ptr<AidlEnumerator>>* enumerators;
-    AidlEnumDeclaration* enum_decl;
     std::vector<std::unique_ptr<AidlConstantValue>>* constant_value_list;
     std::vector<std::unique_ptr<AidlArgument>>* arg_list;
     AidlVariableDeclaration* variable;
@@ -89,8 +88,6 @@
     AidlMethod* method;
     AidlMember* constant;
     std::vector<std::unique_ptr<AidlMember>>* interface_members;
-    AidlInterface* interface;
-    AidlParcelable* parcelable;
     AidlDefinedType* declaration;
     std::vector<std::unique_ptr<AidlTypeSpecifier>>* type_args;
     std::vector<std::string>* type_params;
@@ -110,6 +107,7 @@
 %token<token> PARCELABLE "parcelable"
 %token<token> ONEWAY "oneway"
 %token<token> ENUM "enum"
+%token<token> UNION "union"
 %token<token> CONST "const"
 
 %token<character> CHARVALUE "char literal"
@@ -151,18 +149,19 @@
 %right UNARY_PLUS UNARY_MINUS  '!' '~'
 
 %type<declaration> decl
+%type<declaration> unannotated_decl
+%type<declaration> interface_decl
+%type<declaration> parcelable_decl
+%type<declaration> enum_decl
+%type<declaration> union_decl
 %type<variable_list> variable_decls
 %type<variable> variable_decl
 %type<type_params> optional_type_params
 %type<interface_members> interface_members
-%type<declaration> unannotated_decl
-%type<interface> interface_decl
-%type<parcelable> parcelable_decl
 %type<method> method_decl
 %type<constant> constant_decl
 %type<enumerator> enumerator
 %type<enumerators> enumerators enum_decl_body
-%type<enum_decl> enum_decl
 %type<param> parameter
 %type<param_list> parameter_list
 %type<param_list> parameter_non_empty_list
@@ -275,11 +274,9 @@
 
 unannotated_decl
  : parcelable_decl
-  { $$ = $1; }
  | interface_decl
-  { $$ = $1; }
  | enum_decl
-  { $$ = $1; }
+ | union_decl
  ;
 
 type_params
@@ -563,6 +560,15 @@
    }
  ;
 
+union_decl
+ : UNION qualified_name optional_type_params '{' variable_decls '}' {
+    $$ = new AidlUnionDecl(loc(@2), $2->GetText(), ps->Package(), $1->GetComments(), $5, $3);
+    delete $1;
+    delete $2;
+    delete $5;
+  }
+ ;
+
 method_decl
  : type identifier '(' arg_list ')' ';' {
     $$ = new AidlMethod(loc(@2), false, $1, $2->GetText(), $4, $1->GetComments());
diff --git a/aidl_to_java.cpp b/aidl_to_java.cpp
index 658348b..c5d3f97 100644
--- a/aidl_to_java.cpp
+++ b/aidl_to_java.cpp
@@ -762,7 +762,7 @@
   } else {
     const AidlDefinedType* t = c.typenames.TryGetDefinedType(c.type.GetName());
     AIDL_FATAL_IF(t == nullptr, c.type) << "Unknown type: " << c.type.GetName();
-    if (t->AsParcelable() != nullptr) {
+    if (t->AsParcelable() != nullptr || t->AsUnionDeclaration() != nullptr) {
       if (c.type.IsArray()) {
         c.writer << c.parcel << ".readTypedArray(" << c.var << ", " << c.type.GetName()
                  << ".CREATOR);\n";
diff --git a/generate_java.cpp b/generate_java.cpp
index eff0bd9..17f6283 100644
--- a/generate_java.cpp
+++ b/generate_java.cpp
@@ -31,16 +31,25 @@
 #include "code_writer.h"
 #include "logging.h"
 
-using std::unique_ptr;
-using ::android::aidl::java::Variable;
+using ::android::base::EndsWith;
+using ::android::base::Join;
+using ::android::base::StartsWith;
 using std::string;
+using std::unique_ptr;
 
 namespace {
-inline string get_setter_name(const AidlNode& context, const string& variablename) {
-  AIDL_FATAL_IF(variablename.size() <= 0, context) << "A field name cannot be empty.";
-  std::ostringstream out;
-  out << "set" << static_cast<char>(toupper(variablename[0])) << variablename.substr(1);
-  return out.str();
+// join two non-empty strings according to `camelCase` naming.
+inline string camelcase_join(const string& a, const string& b, const AidlNode& context) {
+  AIDL_FATAL_IF(b.size() <= 0 || a.size() <= 0, context) << "Name cannot be empty.";
+  std::string name = a + b;
+  name[a.size()] = static_cast<char>(toupper(name[a.size()]));
+  return name;
+}
+inline string getter_name(const AidlVariableDeclaration& variable) {
+  return camelcase_join("get", variable.GetName(), variable);
+}
+inline string setter_name(const AidlVariableDeclaration& variable) {
+  return camelcase_join("set", variable.GetName(), variable);
 }
 }  // namespace
 
@@ -83,6 +92,14 @@
   return true;
 }
 
+bool generate_java_union_declaration(const std::string& filename, const AidlUnionDecl* decl,
+                                     const AidlTypenames& typenames,
+                                     const IoDelegate& io_delegate) {
+  CodeWriterPtr code_writer = io_delegate.GetCodeWriter(filename);
+  generate_union(*code_writer, decl, typenames);
+  return true;
+}
+
 bool generate_java(const std::string& filename, const AidlDefinedType* defined_type,
                    const AidlTypenames& typenames, const IoDelegate& io_delegate,
                    const Options& options) {
@@ -100,6 +117,10 @@
     return generate_java_interface(filename, interface, typenames, io_delegate, options);
   }
 
+  if (const AidlUnionDecl* union_decl = defined_type->AsUnionDeclaration(); union_decl != nullptr) {
+    return generate_java_union_declaration(filename, union_decl, typenames, io_delegate);
+  }
+
   AIDL_FATAL(defined_type) << "Unrecognized type sent for Java generation.";
   return false;
 }
@@ -162,7 +183,7 @@
         out << " = " << variable->ValueString(ConstantValueDecorator);
       }
       out << ";\n";
-      out << "public Builder " << get_setter_name(*variable, variable->GetName()) << "("
+      out << "public Builder " << setter_name(*variable) << "("
           << JavaSignatureOf(variable->GetType(), typenames) << " " << variable->GetName()
           << ") {\n"
           << "  "
@@ -338,8 +359,7 @@
     }
     CreateFromParcelFor(context);
     if (parcel->IsJavaOnlyImmutable()) {
-      context.writer.Write("%s.%s(%s);\n", builder_variable.c_str(),
-                           get_setter_name(*field, field->GetName()).c_str(),
+      context.writer.Write("%s.%s(%s);\n", builder_variable.c_str(), setter_name(*field).c_str(),
                            field_variable_name.c_str());
     }
     writer->Close();
@@ -427,6 +447,209 @@
   code_writer->Write("}\n");
 }
 
+void generate_union(CodeWriter& out, const AidlUnionDecl* decl, const AidlTypenames& typenames) {
+  const string tag_type = "int";
+  const AidlTypeSpecifier tag_type_specifier(AIDL_LOCATION_HERE, tag_type, false /* isArray */,
+                                             nullptr /* type_params */, "");
+  const string tag_name = "_tag";
+  const string value_name = "_value";
+  const string clazz = decl->GetName();
+
+  out << "/*\n";
+  out << " * This file is auto-generated.  DO NOT MODIFY.\n";
+  out << " */\n";
+
+  out << "package " + decl->GetPackage() + ";\n";
+  out << "\n";
+  out << decl->GetComments() << "\n";
+  for (const auto& annotation : generate_java_annotations(*decl)) {
+    out << annotation << "\n";
+  }
+
+  out << "public final class " + clazz + " implements android.os.Parcelable {\n";
+  out.Indent();
+
+  size_t tag_index = 0;
+  out << "// tags union fields\n";
+  for (const auto& variable : decl->GetFields()) {
+    auto raw_type = variable->GetType().ToString();
+    out << "public final static " + tag_type + " " + variable->GetName() + " = " +
+               std::to_string(tag_index++) + ";  // " + raw_type + "\n";
+  }
+  out << "\n";
+
+  out << "private " + tag_type + " " + tag_name + ";\n";
+  out << "private Object " + value_name + ";\n";
+  out << "\n";
+
+  const auto& first_field = decl->GetFields()[0];
+  // ctor()
+  out << "public " + clazz + "() {\n";
+  out << "  this(" + first_field->GetName() + ", " +
+             first_field->ValueString(ConstantValueDecorator) + ");\n";
+  out << "}\n";
+  // ctor(Parcel)
+  out << "private " + clazz + "(android.os.Parcel _aidl_parcel) {\n";
+  out << "  readFromParcel(_aidl_parcel);\n";
+  out << "}\n";
+  // ctor(tag, value)
+  out << "private " + clazz + "(" + tag_type + " tag, Object value) {\n";
+  out << "  _set(tag, value);\n";
+  out << "}\n";
+  out << "\n";
+
+  // getTag()
+  out << "public " + tag_type + " " + "getTag() {\n";
+  out << "  return " + tag_name + ";\n";
+  out << "}\n";
+  out << "\n";
+
+  // value ctor, getter, setter for each field
+  for (const auto& variable : decl->GetFields()) {
+    auto var_name = variable->GetName();
+    auto var_type = JavaSignatureOf(variable->GetType(), typenames);
+    auto raw_type = variable->GetType().ToString();
+
+    out << "// " + raw_type + " " + var_name + "\n";
+    // value ctor
+    out << variable->GetType().GetComments() + "\n";
+    out << "public static " + clazz + " " + var_name + "(" + var_type + " " + value_name + ") {\n";
+    out << "  return new " + clazz + "(" + var_name + ", " + value_name + ");\n";
+    out << "}\n";
+    // getter
+    if (variable->GetType().IsGeneric()) {
+      out << "@SuppressWarnings(\"unchecked\")\n";
+    }
+    out << "public " + var_type + " " + getter_name(*variable) + "() {\n";
+    out << "  _assertTag(" + var_name + ");\n";
+    out << "  return (" + var_type + ") " + value_name + ";\n";
+    out << "}\n";
+    // setter
+    out << "public void " + setter_name(*variable) + "(" + var_type + " " + value_name + ") {\n";
+    out << "  _set(" + var_name + ", " + value_name + ");\n";
+    out << "}\n";
+    out << "\n";
+  }
+
+  if (decl->IsVintfStability()) {
+    out << "@Override\n";
+    out << "public final int getStability() {\n";
+    out << "  return android.os.Parcelable.PARCELABLE_STABILITY_VINTF;\n";
+    out << "}\n";
+    out << "\n";
+  }
+
+  out << "public static final android.os.Parcelable.Creator<" << clazz << "> CREATOR = "
+      << "new android.os.Parcelable.Creator<" << clazz << ">() {\n";
+  out << "  @Override\n";
+  out << "  public " << clazz << " createFromParcel(android.os.Parcel _aidl_source) {\n";
+  out << "    return new Union(_aidl_source);\n";  // to avoid unnecessary allocation of "default"
+  out << "  }\n";
+  out << "  @Override\n";
+  out << "  public " << clazz << "[] newArray(int _aidl_size) {\n";
+  out << "    return new " << clazz << "[_aidl_size];\n";
+  out << "  }\n";
+  out << "};\n";
+
+  auto write_to_parcel = [&](const AidlTypeSpecifier& type, std::string name, std::string parcel) {
+    string code;
+    CodeWriterPtr writer = CodeWriter::ForString(&code);
+    CodeGeneratorContext context{
+        .writer = *(writer.get()),
+        .typenames = typenames,
+        .type = type,
+        .parcel = parcel,
+        .var = name,
+        .is_return_value = false,
+    };
+    WriteToParcelFor(context);
+    writer->Close();
+    return code;
+  };
+
+  out << "@Override\n";
+  out << "public final void writeToParcel(android.os.Parcel _aidl_parcel, int _aidl_flag) {\n";
+  out << "  " + write_to_parcel(tag_type_specifier, tag_name, "_aidl_parcel");
+  out << "  switch (" + tag_name + ") {\n";
+  for (const auto& variable : decl->GetFields()) {
+    out << "  case " + variable->GetName() + ":\n";
+    out << "    " +
+               write_to_parcel(variable->GetType(), getter_name(*variable) + "()", "_aidl_parcel");
+    out << "    break;\n";
+  }
+  out << "  }\n";
+  out << "}\n";
+
+  // keep this across different fields in order to create the classloader
+  // at most once.
+  bool is_classloader_created = false;
+  auto read_from_parcel = [&](const AidlTypeSpecifier& type, std::string name, std::string parcel) {
+    string code;
+    CodeWriterPtr writer = CodeWriter::ForString(&code);
+    CodeGeneratorContext context{
+        .writer = *(writer.get()),
+        .typenames = typenames,
+        .type = type,
+        .parcel = parcel,
+        .var = name,
+        .is_classloader_created = &is_classloader_created,
+    };
+    CreateFromParcelFor(context);
+    writer->Close();
+    return code;
+  };
+
+  // Not override, but as a user-defined parcelable, this method should be public
+  out << "public void readFromParcel(android.os.Parcel _aidl_parcel) {\n";
+  out << "  " + tag_type + " _aidl_tag;\n";
+  out << "  " + read_from_parcel(tag_type_specifier, "_aidl_tag", "_aidl_parcel");
+  out << "  switch (_aidl_tag) {\n";
+  for (const auto& variable : decl->GetFields()) {
+    auto var_name = variable->GetName();
+    auto var_type = JavaSignatureOf(variable->GetType(), typenames);
+    out << "  case " + var_name + ": {\n";
+    out << "    " + var_type + " _aidl_value;\n";
+    out << "    " + read_from_parcel(variable->GetType(), "_aidl_value", "_aidl_parcel");
+    out << "    _set(_aidl_tag, _aidl_value);\n";
+    out << "    return; }\n";
+  }
+  out << "  }\n";
+  out << "  throw new RuntimeException(\"union: out of range: \" + _aidl_tag);\n";
+  out << "}\n";
+
+  out << "@Override\n";
+  out << "public int describeContents() {\n";
+  out << "  return 0;\n";
+  out << "}\n";
+  out << "\n";
+
+  // helper: _assertTag
+  out << "private void _assertTag(" + tag_type + " tag) {\n";
+  out << "  if (getTag() != tag) {\n";
+  out << "    throw new IllegalStateException(\"bad access: \" + _tagString(tag) + \", \" + "
+         "_tagString(tag) + \" is available.\");\n";
+  out << "  }\n";
+  out << "}\n";
+  // helper: _tagString
+  out << "private String _tagString(" + tag_type + " " + tag_name + ") {\n";
+  out << "  switch (" + tag_name + ") {\n";
+  for (const auto& variable : decl->GetFields()) {
+    auto var_name = variable->GetName();
+    out << "  case " + var_name + ": return \"" + var_name + "\";\n";
+  }
+  out << "  }\n";
+  out << "  throw new IllegalStateException(\"unknown field: \" + " + tag_name + ");\n";
+  out << "}\n";
+  // helper: _set
+  out << "private void _set(" + tag_type + " tag, Object value) {\n";
+  out << "  this." + tag_name + " = tag;\n";
+  out << "  this." + value_name + " = value;\n";
+  out << "}\n";
+
+  out.Dedent();
+  out << "}\n";
+}
+
 std::string dump_location(const AidlNode& method) {
   return method.PrintLocation();
 }
@@ -440,7 +663,7 @@
     parameters_decl.push_back(param_name + " = " + param_value);
   }
   parameters_decl.push_back("overrideSourcePosition=\"" + dump_location(a) + "\"");
-  return "(" + base::Join(parameters_decl, ", ") + ")";
+  return "(" + Join(parameters_decl, ", ") + ")";
 }
 
 std::vector<std::string> generate_java_annotations(const AidlAnnotatable& a) {
@@ -455,9 +678,9 @@
                         generate_java_unsupportedappusage_parameters(*unsupported_app_usage));
   }
 
-  auto strip_double_quote = [](const AidlTypeSpecifier& type, const std::string& raw_value) -> std::string {
-    if (!android::base::StartsWith(raw_value, "\"") ||
-        !android::base::EndsWith(raw_value, "\"")) {
+  auto strip_double_quote = [](const AidlTypeSpecifier& type,
+                               const std::string& raw_value) -> std::string {
+    if (!StartsWith(raw_value, "\"") || !EndsWith(raw_value, "\"")) {
       AIDL_FATAL(type) << "Java passthrough annotation " << raw_value << " is not properly quoted";
       return "";
     }
diff --git a/generate_java.h b/generate_java.h
index d2c8a6d..3ed2546 100644
--- a/generate_java.h
+++ b/generate_java.h
@@ -40,6 +40,8 @@
 void generate_enum(const CodeWriterPtr& code_writer, const AidlEnumDeclaration* enum_decl,
                    const AidlTypenames& typenames);
 
+void generate_union(CodeWriter& out, const AidlUnionDecl* decl, const AidlTypenames& typenames);
+
 std::vector<std::string> generate_java_annotations(const AidlAnnotatable& a);
 
 }  // namespace java
diff --git a/tests/android/aidl/tests/Union.aidl b/tests/android/aidl/tests/Union.aidl
new file mode 100644
index 0000000..c1b6e52
--- /dev/null
+++ b/tests/android/aidl/tests/Union.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.aidl.tests;
+
+union Union {
+    int n;
+    int m;
+    int[] ns;
+    String s;
+    @utf8InCpp String s2;
+    @nullable IBinder ibinder;
+    List<String> ss;
+}
+
diff --git a/tests/java/src/android/aidl/tests/AidlJavaTests.java b/tests/java/src/android/aidl/tests/AidlJavaTests.java
index 0762cb2..b53c96f 100644
--- a/tests/java/src/android/aidl/tests/AidlJavaTests.java
+++ b/tests/java/src/android/aidl/tests/AidlJavaTests.java
@@ -9,15 +9,9 @@
     public static void main(String[] args) {
         JUnitCore junit = new JUnitCore();
         junit.addListener(new TextListener(System.out));
-        Result result = junit.run(
-                ExtensionTests.class,
-                GenericTests.class,
-                JavaOnlyImmutableAnnotationTests.class,
-                MapTests.class,
-                NullableTests.class,
-                TestServiceClient.class,
-                TestVersionedInterface.class
-            );
+        Result result = junit.run(ExtensionTests.class, GenericTests.class,
+            JavaOnlyImmutableAnnotationTests.class, MapTests.class, NullableTests.class,
+            TestServiceClient.class, TestVersionedInterface.class, UnionTests.class);
 
         System.out.println(result.wasSuccessful() ? "TEST SUCCESS" : "TEST FAILURE");
     }
diff --git a/tests/java/src/android/aidl/tests/UnionTests.java b/tests/java/src/android/aidl/tests/UnionTests.java
new file mode 100644
index 0000000..6b903b1
--- /dev/null
+++ b/tests/java/src/android/aidl/tests/UnionTests.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.aidl.tests;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+import android.aidl.tests.Union;
+import android.os.Parcel;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class UnionTests {
+  @Test
+  public void defaultConstructorInitsWithFirstField() {
+    Union u = new Union();
+    assertThat(u.getTag(), is(Union.n));
+    assertThat(u.getN(), is(0));
+  }
+
+  @Test
+  public void updatesUnionWithSetter() {
+    Union u = new Union();
+    u.setNs(new int[] {1, 2, 3});
+    assertThat(u.getTag(), is(Union.ns));
+    assertThat(u.getNs(), is(new int[] {1, 2, 3}));
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void gettingWrongFieldThrowsException() {
+    Union u = new Union();
+    u.getSs();
+  }
+
+  @Test
+  public void readWriteViaParcel() {
+    List<String> ss = Arrays.asList("hello", "world");
+
+    Union u = Union.ss(ss);
+    Parcel parcel = Parcel.obtain();
+    u.writeToParcel(parcel, 0);
+    parcel.setDataPosition(0);
+
+    Union v = Union.CREATOR.createFromParcel(parcel);
+
+    assertThat(v.getTag(), is(Union.ss));
+    assertThat(v.getSs(), is(ss));
+
+    parcel.recycle();
+  }
+}