[automerger skipped] Merge "Zero hidl-generated structs" into oc-dr1-dev am: c62f78a630 -s ours am: 205dd6ed92 -s ours
am: f1dc3d156d -s ours
am skip reason: change_id I8fb10532b6fdf9afa419f19fc634981e7cbd3c10 with SHA1 da1d449882 is in history

Change-Id: Ic4462fbb9d106520fe1b29b47f2062c40237392f
diff --git a/.clang-format b/.clang-format
deleted file mode 100644
index 9b3f9d9..0000000
--- a/.clang-format
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Copyright (C) 2017 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.
-#
-
-BasedOnStyle: Google
-CommentPragmas: NOLINT:.*
-DerivePointerAlignment: false
-AllowShortFunctionsOnASingleLine: Inline
-ColumnLimit: 100
-TabWidth: 4
-UseTab: Never
-IndentWidth: 4
diff --git a/.clang-format b/.clang-format
new file mode 120000
index 0000000..973b2fa
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1 @@
+../../../build/soong/scripts/system-clang-format
\ No newline at end of file
diff --git a/AST.cpp b/AST.cpp
index cb260e2..834dc5a 100644
--- a/AST.cpp
+++ b/AST.cpp
@@ -83,16 +83,21 @@
     return mRootScope.getInterface() != nullptr;
 }
 
-bool AST::containsInterfaces() const {
-    return mRootScope.containsInterfaces();
+bool AST::definesInterfaces() const {
+    return mRootScope.definesInterfaces();
 }
 
 status_t AST::postParse() {
     status_t err;
 
-    // lookupTypes is the first pass.
+    // lookupTypes is the first pass for references to be resolved.
     err = lookupTypes();
     if (err != OK) return err;
+
+    // Indicate that all types are now in "postParse" stage.
+    err = setParseStage(Type::ParseStage::PARSE, Type::ParseStage::POST_PARSE);
+    if (err != OK) return err;
+
     // validateDefinedTypesUniqueNames is the first call
     // after lookup, as other errors could appear because
     // user meant different type than we assumed.
@@ -104,13 +109,15 @@
     if (err != OK) return err;
     err = resolveInheritance();
     if (err != OK) return err;
-    err = lookupLocalIdentifiers();
+    err = lookupConstantExpressions();
     if (err != OK) return err;
     // checkAcyclicConstantExpressions is after resolveInheritance,
     // as resolveInheritance autofills enum values.
     err = checkAcyclicConstantExpressions();
     if (err != OK) return err;
-    err = evaluate();
+    err = validateConstantExpressions();
+    if (err != OK) return err;
+    err = evaluateConstantExpressions();
     if (err != OK) return err;
     err = validate();
     if (err != OK) return err;
@@ -127,13 +134,9 @@
             return OK;
         },
         true /* processBeforeDependencies */);
-    std::unordered_set<const Type*> visited;
-    mRootScope.recursivePass(
-        [](Type* type) {
-            type->setPostParseCompleted();
-            return OK;
-        },
-        &visited);
+
+    err = setParseStage(Type::ParseStage::POST_PARSE, Type::ParseStage::COMPLETED);
+    if (err != OK) return err;
 
     return OK;
 }
@@ -142,20 +145,50 @@
     const std::function<status_t(ConstantExpression*)>& func, bool processBeforeDependencies) {
     std::unordered_set<const Type*> visitedTypes;
     std::unordered_set<const ConstantExpression*> visitedCE;
-    return mRootScope.recursivePass(
-        [&](Type* type) -> status_t {
-            for (auto* ce : type->getConstantExpressions()) {
-                status_t err = ce->recursivePass(func, &visitedCE, processBeforeDependencies);
-                if (err != OK) return err;
-            }
-            return OK;
-        },
-        &visitedTypes);
+    return mRootScope.recursivePass(Type::ParseStage::POST_PARSE,
+                                    [&](Type* type) -> status_t {
+                                        for (auto* ce : type->getConstantExpressions()) {
+                                            status_t err = ce->recursivePass(
+                                                func, &visitedCE, processBeforeDependencies);
+                                            if (err != OK) return err;
+                                        }
+                                        return OK;
+                                    },
+                                    &visitedTypes);
+}
+
+status_t AST::constantExpressionRecursivePass(
+    const std::function<status_t(const ConstantExpression*)>& func,
+    bool processBeforeDependencies) const {
+    std::unordered_set<const Type*> visitedTypes;
+    std::unordered_set<const ConstantExpression*> visitedCE;
+    return mRootScope.recursivePass(Type::ParseStage::POST_PARSE,
+                                    [&](const Type* type) -> status_t {
+                                        for (auto* ce : type->getConstantExpressions()) {
+                                            status_t err = ce->recursivePass(
+                                                func, &visitedCE, processBeforeDependencies);
+                                            if (err != OK) return err;
+                                        }
+                                        return OK;
+                                    },
+                                    &visitedTypes);
+}
+
+status_t AST::setParseStage(Type::ParseStage oldStage, Type::ParseStage newStage) {
+    std::unordered_set<const Type*> visited;
+    return mRootScope.recursivePass(oldStage,
+                                    [oldStage, newStage](Type* type) {
+                                        CHECK(type->getParseStage() == oldStage);
+                                        type->setParseStage(newStage);
+                                        return OK;
+                                    },
+                                    &visited);
 }
 
 status_t AST::lookupTypes() {
     std::unordered_set<const Type*> visited;
     return mRootScope.recursivePass(
+        Type::ParseStage::PARSE,
         [&](Type* type) -> status_t {
             Scope* scope = type->isScope() ? static_cast<Scope*>(type) : type->parent();
 
@@ -182,6 +215,7 @@
 status_t AST::gatherReferencedTypes() {
     std::unordered_set<const Type*> visited;
     return mRootScope.recursivePass(
+        Type::ParseStage::POST_PARSE,
         [&](Type* type) -> status_t {
             for (auto* nextRef : type->getReferences()) {
                 const Type *targetType = nextRef->get();
@@ -196,11 +230,12 @@
         &visited);
 }
 
-status_t AST::lookupLocalIdentifiers() {
+status_t AST::lookupConstantExpressions() {
     std::unordered_set<const Type*> visitedTypes;
     std::unordered_set<const ConstantExpression*> visitedCE;
 
     return mRootScope.recursivePass(
+        Type::ParseStage::POST_PARSE,
         [&](Type* type) -> status_t {
             Scope* scope = type->isScope() ? static_cast<Scope*>(type) : type->parent();
 
@@ -214,6 +249,18 @@
                             if (iden == nullptr) return UNKNOWN_ERROR;
                             nextRef->set(iden);
                         }
+                        for (auto* nextRef : ce->getTypeReferences()) {
+                            if (nextRef->isResolved()) continue;
+
+                            Type* nextType = lookupType(nextRef->getLookupFqName(), scope);
+                            if (nextType == nullptr) {
+                                std::cerr << "ERROR: Failed to lookup type '"
+                                          << nextRef->getLookupFqName().string() << "' at "
+                                          << nextRef->location() << "\n";
+                                return UNKNOWN_ERROR;
+                            }
+                            nextRef->set(nextType);
+                        }
                         return OK;
                     },
                     &visitedCE, true /* processBeforeDependencies */);
@@ -228,6 +275,7 @@
 status_t AST::validateDefinedTypesUniqueNames() const {
     std::unordered_set<const Type*> visited;
     return mRootScope.recursivePass(
+        Type::ParseStage::POST_PARSE,
         [&](const Type* type) -> status_t {
             // We only want to validate type definition names in this place.
             if (type->isScope()) {
@@ -240,10 +288,17 @@
 
 status_t AST::resolveInheritance() {
     std::unordered_set<const Type*> visited;
-    return mRootScope.recursivePass(&Type::resolveInheritance, &visited);
+    return mRootScope.recursivePass(Type::ParseStage::POST_PARSE, &Type::resolveInheritance,
+                                    &visited);
 }
 
-status_t AST::evaluate() {
+status_t AST::validateConstantExpressions() const {
+    return constantExpressionRecursivePass(
+        [](const ConstantExpression* ce) { return ce->validate(); },
+        true /* processBeforeDependencies */);
+}
+
+status_t AST::evaluateConstantExpressions() {
     return constantExpressionRecursivePass(
         [](ConstantExpression* ce) {
             ce->evaluate();
@@ -254,7 +309,7 @@
 
 status_t AST::validate() const {
     std::unordered_set<const Type*> visited;
-    return mRootScope.recursivePass(&Type::validate, &visited);
+    return mRootScope.recursivePass(Type::ParseStage::POST_PARSE, &Type::validate, &visited);
 }
 
 status_t AST::topologicalReorder() {
@@ -264,14 +319,14 @@
     if (err != OK) return err;
 
     std::unordered_set<const Type*> visited;
-    mRootScope.recursivePass(
-        [&](Type* type) {
-            if (type->isScope()) {
-                static_cast<Scope*>(type)->topologicalReorder(reversedOrder);
-            }
-            return OK;
-        },
-        &visited);
+    mRootScope.recursivePass(Type::ParseStage::POST_PARSE,
+                             [&](Type* type) {
+                                 if (type->isScope()) {
+                                     static_cast<Scope*>(type)->topologicalReorder(reversedOrder);
+                                 }
+                                 return OK;
+                             },
+                             &visited);
     return OK;
 }
 
@@ -279,29 +334,31 @@
     std::unordered_set<const Type*> visitedTypes;
     std::unordered_set<const ConstantExpression*> visitedCE;
     std::unordered_set<const ConstantExpression*> stack;
-    return mRootScope.recursivePass(
-        [&](const Type* type) -> status_t {
-            for (auto* ce : type->getConstantExpressions()) {
-                status_t err = ce->checkAcyclic(&visitedCE, &stack).status;
-                CHECK(err != OK || stack.empty());
-                if (err != OK) return err;
-            }
-            return OK;
-        },
-        &visitedTypes);
+    return mRootScope.recursivePass(Type::ParseStage::POST_PARSE,
+                                    [&](const Type* type) -> status_t {
+                                        for (auto* ce : type->getConstantExpressions()) {
+                                            status_t err =
+                                                ce->checkAcyclic(&visitedCE, &stack).status;
+                                            CHECK(err != OK || stack.empty());
+                                            if (err != OK) return err;
+                                        }
+                                        return OK;
+                                    },
+                                    &visitedTypes);
 }
 
 status_t AST::checkForwardReferenceRestrictions() const {
     std::unordered_set<const Type*> visited;
-    return mRootScope.recursivePass(
-        [](const Type* type) -> status_t {
-            for (const Reference<Type>* ref : type->getReferences()) {
-                status_t err = type->checkForwardReferenceRestrictions(*ref);
-                if (err != OK) return err;
-            }
-            return OK;
-        },
-        &visited);
+    return mRootScope.recursivePass(Type::ParseStage::POST_PARSE,
+                                    [](const Type* type) -> status_t {
+                                        for (const Reference<Type>* ref : type->getReferences()) {
+                                            status_t err =
+                                                type->checkForwardReferenceRestrictions(*ref);
+                                            if (err != OK) return err;
+                                        }
+                                        return OK;
+                                    },
+                                    &visited);
 }
 
 bool AST::addImport(const char *import) {
@@ -448,7 +505,6 @@
     FQName enumTypeName = fqName.typeName();
     std::string enumValueName = fqName.valueName();
 
-    CHECK(enumTypeName.isValid());
     CHECK(!enumValueName.empty());
 
     Type* type = lookupType(enumTypeName, scope);
@@ -475,8 +531,6 @@
 }
 
 Type* AST::lookupType(const FQName& fqName, Scope* scope) {
-    CHECK(fqName.isValid());
-
     if (fqName.name().empty()) {
         // Given a package and version???
         return nullptr;
diff --git a/AST.h b/AST.h
index 4de98bd..a55cd8c 100644
--- a/AST.h
+++ b/AST.h
@@ -53,7 +53,7 @@
     // package and version really.
     FQName package() const;
     bool isInterface() const;
-    bool containsInterfaces() const;
+    bool definesInterfaces() const;
 
     // Adds package, version and scope stack to local name
     FQName makeFullName(const char* localName, Scope* scope) const;
@@ -84,12 +84,19 @@
     // Recursive pass on constant expression tree
     status_t constantExpressionRecursivePass(
         const std::function<status_t(ConstantExpression*)>& func, bool processBeforeDependencies);
+    status_t constantExpressionRecursivePass(
+        const std::function<status_t(const ConstantExpression*)>& func,
+        bool processBeforeDependencies) const;
+
+    // Recursive tree pass that sets ParseStage of all types to newStage.
+    status_t setParseStage(Type::ParseStage oldStage, Type::ParseStage newStage);
 
     // Recursive tree pass that looks up all referenced types
     status_t lookupTypes();
 
     // Recursive tree pass that looks up all referenced local identifiers
-    status_t lookupLocalIdentifiers();
+    // and types referenced by constant expressions
+    status_t lookupConstantExpressions();
 
     // Recursive tree pass that validates that all defined types
     // have unique names in their scopes.
@@ -99,8 +106,11 @@
     // that depend on super types
     status_t resolveInheritance();
 
+    // Recursive tree pass that validates constant expressions
+    status_t validateConstantExpressions() const;
+
     // Recursive tree pass that evaluates constant expressions
-    status_t evaluate();
+    status_t evaluateConstantExpressions();
 
     // Recursive tree pass that validates all type-related
     // syntax restrictions
@@ -138,6 +148,8 @@
 
     void generateVts(Formatter& out) const;
 
+    void generateDependencies(Formatter& out) const;
+
     void getImportedPackages(std::set<FQName> *importSet) const;
 
     // Run getImportedPackages on this, then run getImportedPackages on
@@ -260,9 +272,9 @@
                          bool includeParents = true) const;
     void generateStubImplMethod(Formatter& out, const std::string& className,
                                 const Method* method) const;
-    void generatePassthroughMethod(Formatter& out, const Method* method) const;
+    void generatePassthroughMethod(Formatter& out, const Method* method, const Interface* superInterface) const;
     void generateStaticProxyMethodSource(Formatter& out, const std::string& className,
-                                         const Method* method) const;
+                                         const Method* method, const Interface* superInterface) const;
     void generateProxyMethodSource(Formatter& out, const std::string& className,
                                    const Method* method, const Interface* superInterface) const;
     void generateAdapterMethod(Formatter& out, const Method* method) const;
@@ -276,7 +288,7 @@
     void generateStubSourceForMethod(Formatter& out, const Method* method,
                                      const Interface* superInterface) const;
     void generateStaticStubMethodSource(Formatter& out, const FQName& fqName,
-                                        const Method* method) const;
+                                        const Method* method, const Interface* superInterface) const;
 
     void generatePassthroughSource(Formatter& out) const;
 
@@ -303,7 +315,8 @@
     void generateCppInstrumentationCall(
             Formatter &out,
             InstrumentationEvent event,
-            const Method *method) const;
+            const Method *method,
+            const Interface* superInterface) const;
 
     void declareCppReaderLocals(Formatter& out, const std::vector<NamedReference<Type>*>& arg,
                                 bool forResults) const;
@@ -320,8 +333,6 @@
                               const NamedReference<Type>* arg, bool isReader,
                               bool addPrefixToName) const;
 
-    void emitTypeDeclarations(Formatter& out) const;
-    void emitJavaTypeDeclarations(Formatter& out) const;
     void emitVtsTypeDeclarations(Formatter& out) const;
 
     DISALLOW_COPY_AND_ASSIGN(AST);
diff --git a/Android.bp b/Android.bp
index dd51705..8cf4f5d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -14,13 +14,15 @@
 
 cc_defaults {
     name: "hidl-gen-defaults",
-    cpp_std: "experimental",
     cflags: [
-        "-O0",
-        "-g",
         "-Wall",
         "-Werror",
     ],
+    target: {
+        host: {
+            cflags: ["-O0", "-g"],
+        }
+    }
 }
 
 // This configuration is inherited by all hidl-gen-generated modules.
@@ -31,6 +33,10 @@
         "-Werror",
         "-Wextra-semi",
     ],
+    tidy_checks: [
+        // _hidl_cb and addOnewayTask are stuck because of the legacy ABI
+        "-performance-unnecessary-value-param",
+    ],
     product_variables: {
         debuggable: {
             cflags: ["-D__ANDROID_DEBUGGABLE__"]
@@ -49,26 +55,6 @@
     },
 }
 
-//
-// libhidl-gen-hash
-//
-cc_library {
-    name: "libhidl-gen-hash",
-    host_supported: true,
-    defaults: ["hidl-gen-defaults"],
-    srcs: ["Hash.cpp"],
-    local_include_dirs: ["include_hash/hidl-hash"],
-    export_include_dirs: ["include_hash"],
-    shared_libs: [
-        "libbase",
-        "libcrypto",
-        "libssl",
-    ],
-}
-
-//
-// libhidl-gen
-//
 cc_library_host_shared {
     name: "libhidl-gen",
     defaults: ["hidl-gen-defaults"],
@@ -101,19 +87,17 @@
         "libbase",
         "liblog",
         "libhidl-gen-hash",
+        "libhidl-gen-host-utils",
         "libhidl-gen-utils",
     ],
     export_shared_lib_headers: [
         "libbase",
+        "libhidl-gen-host-utils",
         "libhidl-gen-utils",
     ],
     export_include_dirs: ["."], // for tests
 }
 
-//
-// libhidl-gen-ast
-//
-
 cc_library_host_shared {
     name: "libhidl-gen-ast",
     defaults: ["hidl-gen-defaults"],
@@ -122,6 +106,7 @@
         "generateCpp.cpp",
         "generateCppAdapter.cpp",
         "generateCppImpl.cpp",
+        "generateDependencies.cpp",
         "generateJava.cpp",
         "generateVts.cpp",
         "hidl-gen_y.yy",
@@ -133,6 +118,7 @@
         "liblog",
         "libhidl-gen",
         "libhidl-gen-hash",
+        "libhidl-gen-host-utils",
         "libhidl-gen-utils",
     ],
     export_shared_lib_headers: [
@@ -142,9 +128,6 @@
     export_include_dirs: ["."], // for tests
 }
 
-//
-// hidl-gen
-//
 cc_binary_host {
     name: "hidl-gen",
     defaults: ["hidl-gen-defaults"],
@@ -155,6 +138,7 @@
         "libhidl-gen",
         "libhidl-gen-ast",
         "libhidl-gen-hash",
+        "libhidl-gen-host-utils",
         "libhidl-gen-utils",
     ],
 }
diff --git a/Annotation.cpp b/Annotation.cpp
index e5a66b0..438cff6 100644
--- a/Annotation.cpp
+++ b/Annotation.cpp
@@ -84,24 +84,17 @@
     const std::string& name, std::vector<ConstantExpression*>* values)
     : AnnotationParam(name), mValues(values) {}
 
-std::string convertToString(const ConstantExpression* value) {
-    if (value->descriptionIsTrivial()) {
-        return value->value();
-    }
-    return value->value() + " /* " + value->description() + " */";
-}
-
 std::vector<std::string> ConstantExpressionAnnotationParam::getValues() const {
     std::vector<std::string> ret;
     for (const auto* value : *mValues) {
-        ret.push_back(convertToString(value));
+        ret.push_back(value->value());
     };
     return ret;
 }
 
 std::string ConstantExpressionAnnotationParam::getSingleValue() const {
     CHECK_EQ(mValues->size(), 1u) << mName << " requires one value but has multiple";
-    return convertToString(mValues->at(0));
+    return mValues->at(0)->value();
 }
 
 std::vector<const ConstantExpression*> ConstantExpressionAnnotationParam::getConstantExpressions()
diff --git a/ArrayType.cpp b/ArrayType.cpp
index 5155a27..ec849bc 100644
--- a/ArrayType.cpp
+++ b/ArrayType.cpp
@@ -80,7 +80,7 @@
 status_t ArrayType::validate() const {
     CHECK(!mElementType->isArray());
 
-    if (mElementType->isBinder()) {
+    if (mElementType->isInterface()) {
         std::cerr << "ERROR: Arrays of interface types are not supported"
                   << " at " << mElementType.location() << "\n";
 
@@ -97,14 +97,7 @@
     std::string arrayType = space + "hidl_array<" + base;
 
     for (size_t i = 0; i < mSizes.size(); ++i) {
-        arrayType += ", ";
-        arrayType += mSizes[i]->cppValue();
-
-        if (!mSizes[i]->descriptionIsTrivial()) {
-            arrayType += " /* ";
-            arrayType += mSizes[i]->description();
-            arrayType += " */";
-        }
+        arrayType += ", " + mSizes[i]->cppValue();
     }
 
     arrayType += ">";
@@ -142,12 +135,8 @@
 
         if (forInitializer) {
             base += mSizes[i]->javaValue();
-        }
-
-        if (!forInitializer || !mSizes[i]->descriptionIsTrivial()) {
-            if (forInitializer)
-                base += " ";
-            base += "/* " + mSizes[i]->description() + " */";
+        } else {
+            base += "/* " + mSizes[i]->expression() + " */";
         }
 
         base += "]";
@@ -156,10 +145,6 @@
     return base;
 }
 
-std::string ArrayType::getJavaWrapperType() const {
-    return mElementType->getJavaWrapperType();
-}
-
 std::string ArrayType::getVtsType() const {
     return "TYPE_ARRAY";
 }
@@ -366,7 +351,8 @@
     out << streamName << ".append(java.util.Arrays."
         << (countDimensions() > 1 ? "deepToString" : "toString")
         << "("
-        << name << "));\n";
+        << name
+        << "));\n";
 }
 
 
@@ -434,15 +420,17 @@
 
 void ArrayType::emitJavaFieldInitializer(
         Formatter &out, const std::string &fieldName) const {
-    std::string typeName = getJavaType(false /* forInitializer */);
-    std::string initName = getJavaType(true /* forInitializer */);
+    const std::string typeName = getJavaType(false /* forInitializer */);
+    const std::string fieldDeclaration = typeName + " " + fieldName;
 
-    out << "final "
-        << typeName
-        << " "
-        << fieldName
+    emitJavaFieldDefaultInitialValue(out, fieldDeclaration);
+}
+
+void ArrayType::emitJavaFieldDefaultInitialValue(
+        Formatter &out, const std::string &declaredFieldName) const {
+    out << declaredFieldName
         << " = new "
-        << initName
+        << getJavaType(true /* forInitializer */)
         << ";\n";
 }
 
@@ -488,15 +476,13 @@
         indexString += "[" + iteratorName + "]";
     }
 
-    if (isReader && mElementType->isCompoundType()) {
-        std::string typeName =
-            mElementType->getJavaType(false /* forInitializer */);
+    const bool isIndexed = (loopDimensions > 0);
+    const std::string fieldNameWithCast = isIndexed
+            ? "(" + getJavaTypeCast(fieldName) + ")" + indexString
+            : getJavaTypeCast(fieldName);
 
-        out << fieldName
-            << indexString
-            << " = new "
-            << typeName
-            << "();\n";
+    if (isReader && mElementType->isCompoundType()) {
+        mElementType->emitJavaFieldDefaultInitialValue(out, fieldNameWithCast);
     }
 
     if (!isPrimitiveArray) {
@@ -505,7 +491,7 @@
                 depth + 1,
                 parcelName,
                 blobName,
-                fieldName + indexString,
+                fieldNameWithCast,
                 offsetName,
                 isReader);
 
@@ -521,20 +507,43 @@
                 << "Array("
                 << offsetName
                 << ", "
-                << fieldName
-                << indexString
+                << fieldNameWithCast
                 << ", "
                 << mSizes.back()->javaValue()
                 << " /* size */);\n";
         } else {
+            std::string elemName = "_hidl_array_item_" + std::to_string(depth);
+
+            out << mElementType->getJavaType(false /* forInitializer */)
+                << "[] "
+                << elemName
+                << " = "
+                << fieldNameWithCast
+                << ";\n\n";
+
+            out << "if ("
+                << elemName
+                << " == null || "
+                << elemName
+                << ".length != "
+                << mSizes.back()->javaValue()
+                << ") {\n";
+
+            out.indent();
+
+            out << "throw new IllegalArgumentException("
+                << "\"Array element is not of the expected length\");\n";
+
+            out.unindent();
+            out << "}\n\n";
+
             out << blobName
                 << ".put"
                 << mElementType->getJavaSuffix()
                 << "Array("
                 << offsetName
                 << ", "
-                << fieldName
-                << indexString
+                << elemName
                 << ");\n";
         }
 
@@ -560,7 +569,7 @@
 
 void ArrayType::emitVtsTypeDeclarations(Formatter& out) const {
     out << "type: " << getVtsType() << "\n";
-    out << "vector_size: " << mSizes[0]->value() << "\n";
+    out << "vector_size: " << mSizes[0]->rawValue() << "\n";
     out << "vector_value: {\n";
     out.indent();
     // Simple array case.
@@ -569,7 +578,7 @@
     } else {  // Multi-dimension array case.
         for (size_t index = 1; index < mSizes.size(); index++) {
             out << "type: " << getVtsType() << "\n";
-            out << "vector_size: " << mSizes[index]->value() << "\n";
+            out << "vector_size: " << mSizes[index]->rawValue() << "\n";
             out << "vector_value: {\n";
             out.indent();
             if (index == mSizes.size() - 1) {
diff --git a/ArrayType.h b/ArrayType.h
index 2c13a33..2ced071 100644
--- a/ArrayType.h
+++ b/ArrayType.h
@@ -56,8 +56,6 @@
 
     std::string getJavaType(bool forInitializer) const override;
 
-    std::string getJavaWrapperType() const override;
-
     std::string getVtsType() const override;
 
     void emitReaderWriter(
@@ -121,6 +119,9 @@
     void emitJavaFieldInitializer(
             Formatter &out, const std::string &fieldName) const override;
 
+    void emitJavaFieldDefaultInitialValue(
+            Formatter &out, const std::string &declaredFieldName) const override;
+
     void emitJavaFieldReaderWriter(
             Formatter &out,
             size_t depth,
diff --git a/CompoundType.cpp b/CompoundType.cpp
index 87f0f14..788469f 100644
--- a/CompoundType.cpp
+++ b/CompoundType.cpp
@@ -17,6 +17,7 @@
 #include "CompoundType.h"
 
 #include "ArrayType.h"
+#include "ScalarType.h"
 #include "VectorType.h"
 
 #include <android-base/logging.h>
@@ -28,7 +29,7 @@
 
 CompoundType::CompoundType(Style style, const char* localName, const FQName& fullName,
                            const Location& location, Scope* parent)
-    : Scope(localName, fullName, location, parent), mStyle(style), mFields(NULL) {}
+    : Scope(localName, fullName, location, parent), mStyle(style), mFields(nullptr) {}
 
 CompoundType::Style CompoundType::style() const {
     return mStyle;
@@ -49,12 +50,18 @@
         const Type& type = field->type();
 
         if ((type.isVector() && static_cast<const VectorType*>(&type)->isVectorOfBinders())) {
-            std::cerr << "ERROR: Struct/Union must not contain references to interfaces at "
+            std::cerr << "ERROR: Struct/union cannot contain vectors of interfaces at "
                       << field->location() << "\n";
             return UNKNOWN_ERROR;
         }
 
         if (mStyle == STYLE_UNION) {
+            if (type.isInterface()) {
+                std::cerr << "ERROR: Union cannot contain interfaces at " << field->location()
+                          << "\n";
+                return UNKNOWN_ERROR;
+            }
+
             if (type.needsEmbeddedReadWrite()) {
                 std::cerr << "ERROR: Union must not contain any types that need fixup at "
                           << field->location() << "\n";
@@ -63,9 +70,18 @@
         }
     }
 
+    if (mStyle == STYLE_SAFE_UNION && mFields->size() < 2) {
+        std::cerr << "ERROR: Safe union must contain at least two types to be useful at "
+                  << location() << "\n";
+        return UNKNOWN_ERROR;
+    }
+
     status_t err = validateUniqueNames();
     if (err != OK) return err;
 
+    err = validateSubTypeNames();
+    if (err != OK) return err;
+
     return Scope::validate();
 }
 
@@ -84,6 +100,29 @@
     return OK;
 }
 
+void CompoundType::emitInvalidSubTypeNamesError(const std::string& subTypeName,
+                                                const Location& location) const {
+    std::cerr << "ERROR: Type name '" << subTypeName << "' defined at "
+              << location << " conflicts with a member function of "
+              << "safe_union " << localName() << ". Consider renaming or "
+              << "moving its definition outside the safe_union scope.\n";
+}
+
+status_t CompoundType::validateSubTypeNames() const {
+    if (mStyle != STYLE_SAFE_UNION) { return OK; }
+    const auto& subTypes = Scope::getSubTypes();
+
+    for (const auto& subType : subTypes) {
+        if (subType->localName() == "getDiscriminator") {
+            emitInvalidSubTypeNamesError(subType->localName(),
+                                         subType->location());
+            return UNKNOWN_ERROR;
+        }
+    }
+
+    return OK;
+}
+
 bool CompoundType::isCompoundType() const {
     return true;
 }
@@ -108,6 +147,9 @@
         case STYLE_UNION: {
             return "union " + localName();
         }
+        case STYLE_SAFE_UNION: {
+            return "safe_union " + localName();
+        }
     }
     CHECK(!"Should not be here");
 }
@@ -127,6 +169,7 @@
         case StorageMode_Result:
             return base + (containsInterface()?"":"*");
     }
+    CHECK(!"Should not be here");
 }
 
 std::string CompoundType::getJavaType(bool /* forInitializer */) const {
@@ -143,6 +186,10 @@
         {
             return "TYPE_UNION";
         }
+        case STYLE_SAFE_UNION:
+        {
+            return "TYPE_SAFE_UNION";
+        }
     }
     CHECK(!"Should not be here");
 }
@@ -163,6 +210,108 @@
     return false;
 }
 
+void CompoundType::emitSafeUnionUnknownDiscriminatorError(Formatter& out, const std::string& value,
+                                                          bool fatal) const {
+    if (fatal) {
+        out << "::android::hardware::details::logAlwaysFatal(";
+    } else {
+        out << "ALOGE(\"%s\", ";
+    }
+    out << "(\n";
+    out.indent(2, [&] {
+        out << "\"Unknown union discriminator (value: \" +\n"
+            << "std::to_string(" << getUnionDiscriminatorType()->getCppTypeCast(value)
+            << ") + \").\").c_str());\n";
+    });
+}
+
+void CompoundType::emitSafeUnionReaderWriterForInterfaces(
+        Formatter &out,
+        const std::string &name,
+        const std::string &parcelObj,
+        bool parcelObjIsPointer,
+        bool isReader,
+        ErrorMode mode) const {
+
+    CHECK(mStyle == STYLE_SAFE_UNION);
+
+    out.block([&] {
+        const auto discriminatorType = getUnionDiscriminatorType();
+        if (isReader) {
+            out << discriminatorType->getCppStackType()
+                << " _hidl_d_primitive;\n";
+        } else {
+            out << "const "
+                << discriminatorType->getCppStackType()
+                << " _hidl_d_primitive = "
+                << discriminatorType->getCppTypeCast(name + ".getDiscriminator()")
+                << ";\n";
+        }
+
+        getUnionDiscriminatorType()->emitReaderWriter(out, "_hidl_d_primitive", parcelObj,
+                                                    parcelObjIsPointer, isReader, mode);
+        out << "switch (("
+            << fullName()
+            << "::hidl_discriminator) _hidl_d_primitive) ";
+
+        out.block([&] {
+            for (const auto& field : *mFields) {
+                out << "case "
+                    << fullName()
+                    << "::hidl_discriminator::"
+                    << field->name()
+                    << ": ";
+
+                const std::string tempFieldName = "_hidl_temp_" + field->name();
+                out.block([&] {
+                    if (isReader) {
+                        out << field->type().getCppResultType()
+                            << " "
+                            << tempFieldName
+                            << ";\n";
+
+                        field->type().emitReaderWriter(out, tempFieldName, parcelObj,
+                                                       parcelObjIsPointer, isReader, mode);
+
+                        const std::string derefOperator = field->type().resultNeedsDeref()
+                                                          ? "*" : "";
+                        out << name
+                            << "."
+                            << field->name()
+                            << "(std::move("
+                            << derefOperator
+                            << tempFieldName
+                            << "));\n";
+                    } else {
+                        const std::string fieldValue = name + "." + field->name() + "()";
+                        out << field->type().getCppArgumentType()
+                            << " "
+                            << tempFieldName
+                            << " = "
+                            << fieldValue
+                            << ";\n";
+
+                        field->type().emitReaderWriter(out, tempFieldName, parcelObj,
+                                                       parcelObjIsPointer, isReader, mode);
+                    }
+                    out << "break;\n";
+                }).endl();
+            }
+
+            out << "default: ";
+            out.block([&] {
+                   emitSafeUnionUnknownDiscriminatorError(out, "_hidl_d_primitive",
+                                                          !isReader /*fatal*/);
+                   if (isReader) {
+                       out << "_hidl_err = BAD_VALUE;\n";
+                       handleError(out, mode);
+                   }
+               })
+                .endl();
+        }).endl();
+    }).endl();
+}
+
 void CompoundType::emitReaderWriter(
         Formatter &out,
         const std::string &name,
@@ -175,9 +324,44 @@
         parcelObj + (parcelObjIsPointer ? "->" : ".");
 
     if(containsInterface()){
+        if (mStyle == STYLE_SAFE_UNION) {
+            emitSafeUnionReaderWriterForInterfaces(out, name, parcelObj,
+                                                   parcelObjIsPointer,
+                                                   isReader, mode);
+            return;
+        }
+
         for (const auto& field : *mFields) {
-            field->type().emitReaderWriter(out, name + "." + field->name(),
-                                               parcelObj, parcelObjIsPointer, isReader, mode);
+            const std::string tempFieldName = "_hidl_temp_" + field->name();
+            const std::string fieldValue = name + "." + field->name();
+
+            out.block([&] {
+                if (isReader) {
+                    out << field->type().getCppResultType()
+                        << " "
+                        << tempFieldName
+                        << ";\n";
+                } else {
+                    out << field->type().getCppArgumentType()
+                        << " "
+                        << tempFieldName
+                        << " = "
+                        << fieldValue
+                        << ";\n";
+                }
+
+                field->type().emitReaderWriter(out, tempFieldName, parcelObj,
+                                               parcelObjIsPointer, isReader, mode);
+                if (isReader) {
+                    const std::string derefOperator = field->type().resultNeedsDeref()
+                                                      ? "*" : "";
+                    out << fieldValue
+                        << " = std::move("
+                        << derefOperator
+                        << tempFieldName
+                        << ");\n";
+                }
+            }).endl();
         }
     } else {
         const std::string parentName = "_hidl_" + name + "_parent";
@@ -202,10 +386,11 @@
                 << ");\n";
             handleError(out, mode);
         }
-        if (mStyle != STYLE_STRUCT) {
-            return;
-        }
-        if (needsEmbeddedReadWrite()) {
+
+        bool needEmbeddedReadWrite = needsEmbeddedReadWrite();
+        CHECK(mStyle != STYLE_UNION || !needEmbeddedReadWrite);
+
+        if (needEmbeddedReadWrite) {
             emitReaderWriterEmbedded(out, 0 /* depth */, name, name, /* sanitizedName */
                                      isReader /* nameIsPointer */, parcelObj, parcelObjIsPointer,
                                      isReader, mode, parentName, "0 /* parentOffset */");
@@ -249,20 +434,19 @@
         out << "new " << fullJavaName() << "();\n";
     }
 
-    out << argName
-        << "."
-        << (isReader ? "readFromParcel" : "writeToParcel")
-        << "("
-        << parcelObj
-        << ");\n";
+    out << "(" << getJavaTypeCast(argName) << ")."
+        << (isReader ? "readFromParcel" : "writeToParcel") << "(" << parcelObj << ");\n";
 }
 
 void CompoundType::emitJavaFieldInitializer(
         Formatter &out, const std::string &fieldName) const {
-    out << "final "
-        << fullJavaName()
-        << " "
-        << fieldName
+    const std::string fieldDeclaration = fullJavaName() + " " + fieldName;
+    emitJavaFieldDefaultInitialValue(out, fieldDeclaration);
+}
+
+void CompoundType::emitJavaFieldDefaultInitialValue(
+        Formatter &out, const std::string &declaredFieldName) const {
+    out << declaredFieldName
         << " = new "
         << fullJavaName()
         << "();\n";
@@ -277,8 +461,9 @@
         const std::string &offset,
         bool isReader) const {
     if (isReader) {
-        out << fieldName
-            << ".readEmbeddedFromParcel("
+        out << "("
+            << getJavaTypeCast(fieldName)
+            << ").readEmbeddedFromParcel("
             << parcelName
             << ", "
             << blobName
@@ -374,7 +559,164 @@
     handleError(out, mode);
 }
 
+void CompoundType::emitLayoutAsserts(Formatter& out, const Layout& layout,
+                                     const std::string& layoutName) const {
+    out << "static_assert(sizeof("
+        << fullName()
+        << layoutName
+        << ") == "
+        << layout.size
+        << ", \"wrong size\");\n";
+
+    out << "static_assert(__alignof("
+        << fullName()
+        << layoutName
+        << ") == "
+        << layout.align
+        << ", \"wrong alignment\");\n";
+}
+
+void CompoundType::emitSafeUnionTypeDeclarations(Formatter& out) const {
+    out << "struct "
+        << localName()
+        << " final {\n";
+
+    out.indent();
+
+    Scope::emitTypeDeclarations(out);
+
+    bool hasPointer = containsPointer();
+    CompoundLayout layout = hasPointer
+                            ? CompoundLayout()
+                            : getCompoundAlignmentAndSize();
+
+    out << "enum class hidl_discriminator : "
+        << getUnionDiscriminatorType()->getCppType(StorageMode_Stack, false)
+        << " ";
+
+    out.block([&] {
+        const auto elements = getSafeUnionEnumElements(true /* useCppTypeName */);
+        for (size_t i = 0; i < elements.size(); i++) {
+            out << elements[i].fieldName
+                << " = "
+                << i
+                << ",";
+
+            if (!elements[i].fieldTypeName.empty()) {
+                out << "  // "
+                    << elements[i].fieldTypeName;
+            }
+            out << "\n";
+        }
+    });
+    out << ";\n\n";
+
+    out << localName() << "();\n"  // Constructor
+        << "~" << localName() << "();\n"  // Destructor
+        << localName() << "(" << localName() << "&&);\n"  // Move constructor
+        << localName() << "(const " << localName() << "&);\n"  // Copy constructor
+        << localName() << "& operator=(" << localName() << "&&);\n"  // Move assignment
+        << localName() << "& operator=(const " << localName() << "&);\n\n";  // Copy assignment
+
+    for (const auto& field : *mFields) {
+        // Setter (copy)
+        out << "void "
+            << field->name()
+            << "("
+            << field->type().getCppArgumentType()
+            << ");\n";
+
+        if (field->type().resolveToScalarType() == nullptr) {
+            // Setter (move)
+            out << "void "
+                << field->name()
+                << "("
+                << field->type().getCppStackType()
+                << "&&);\n";
+        }
+
+        // Getter (mutable)
+        out << field->type().getCppStackType()
+            << "& "
+            << field->name()
+            << "();\n";
+
+        // Getter (immutable)
+        out << field->type().getCppArgumentType()
+            << " "
+            << field->name()
+            << "() const;\n\n";
+    }
+
+    out << "// Utility methods\n";
+    out << "hidl_discriminator getDiscriminator() const;\n\n";
+
+    out << "constexpr size_t hidl_getUnionOffset() const ";
+    out.block([&] {
+        out << "return offsetof(" << fullName() << ", hidl_u);\n";
+    }).endl().endl();
+
+    out.unindent();
+    out << "private:\n";
+    out.indent();
+
+    out << "void hidl_destructUnion();\n\n";
+
+    out << "hidl_discriminator hidl_d";
+    if (!hasPointer) {
+        out << " __attribute__ ((aligned("
+            << layout.discriminator.align << "))) ";
+    }
+    out << ";\n";
+    out << "union hidl_union final {\n";
+    out.indent();
+
+    for (const auto& field : *mFields) {
+
+        size_t fieldAlign, fieldSize;
+        field->type().getAlignmentAndSize(&fieldAlign, &fieldSize);
+
+        out << field->type().getCppStackType()
+            << " "
+            << field->name();
+
+        if (!hasPointer) {
+            out << " __attribute__ ((aligned("
+                << fieldAlign
+                << ")))";
+        }
+        out << ";\n";
+    }
+
+    out << "\n"
+        << "hidl_union();\n"
+        << "~hidl_union();\n";
+
+    out.unindent();
+    out << "} hidl_u;\n";
+
+    if (!hasPointer) {
+        out << "\n";
+
+        emitLayoutAsserts(out, layout.innerStruct, "::hidl_union");
+        emitLayoutAsserts(out, layout.discriminator, "::hidl_discriminator");
+    }
+
+    out.unindent();
+    out << "};\n\n";
+
+    if (!hasPointer) {
+        emitLayoutAsserts(out, layout.overall, "");
+        out << "\n";
+    }
+}
+
 void CompoundType::emitTypeDeclarations(Formatter& out) const {
+    if (mStyle == STYLE_SAFE_UNION) {
+        emitSafeUnionTypeDeclarations(out);
+        return;
+    }
+
     out << ((mStyle == STYLE_STRUCT) ? "struct" : "union")
         << " "
         << localName()
@@ -405,10 +747,7 @@
             size_t fieldAlign, fieldSize;
             field->type().getAlignmentAndSize(&fieldAlign, &fieldSize);
 
-            size_t pad = offset % fieldAlign;
-            if (pad > 0) {
-                offset += fieldAlign - pad;
-            }
+            offset += Layout::getPad(offset, fieldAlign);
 
             if (pass == 0) {
                 out << field->type().getCppStackType()
@@ -438,24 +777,27 @@
         }
     }
 
-    size_t structAlign, structSize;
-    getAlignmentAndSize(&structAlign, &structSize);
-
-    out << "static_assert(sizeof("
-        << fullName()
-        << ") == "
-        << structSize
-        << ", \"wrong size\");\n";
-
-    out << "static_assert(__alignof("
-        << fullName()
-        << ") == "
-        << structAlign
-        << ", \"wrong alignment\");\n\n";
+    CompoundLayout layout = getCompoundAlignmentAndSize();
+    emitLayoutAsserts(out, layout.overall, "");
+    out << "\n";
 }
 
 void CompoundType::emitTypeForwardDeclaration(Formatter& out) const {
-    out << ((mStyle == STYLE_STRUCT) ? "struct" : "union") << " " << localName() << ";\n";
+    switch (mStyle) {
+        case STYLE_UNION: {
+            out << "union";
+            break;
+        }
+        case STYLE_STRUCT:
+        case STYLE_SAFE_UNION: {
+            out << "struct";
+            break;
+        }
+        default: {
+            CHECK(!"Should not be here");
+        }
+    }
+    out << " " << localName() << ";\n";
 }
 
 void CompoundType::emitPackageTypeDeclarations(Formatter& out) const {
@@ -464,6 +806,27 @@
     out << "static inline std::string toString("
         << getCppArgumentType()
         << (mFields->empty() ? "" : " o")
+        << ");\n";
+
+    if (canCheckEquality()) {
+        out << "static inline bool operator==("
+            << getCppArgumentType() << " lhs, " << getCppArgumentType() << " rhs);\n";
+
+        out << "static inline bool operator!=("
+            << getCppArgumentType() << " lhs, " << getCppArgumentType() << " rhs);\n";
+    } else {
+        out << "// operator== and operator!= are not generated for " << localName() << "\n";
+    }
+
+    out.endl();
+}
+
+void CompoundType::emitPackageTypeHeaderDefinitions(Formatter& out) const {
+    Scope::emitPackageTypeHeaderDefinitions(out);
+
+    out << "static inline std::string toString("
+        << getCppArgumentType()
+        << (mFields->empty() ? "" : " o")
         << ") ";
 
     out.block([&] {
@@ -472,15 +835,49 @@
             << "std::string os;\n";
         out << "os += \"{\";\n";
 
-        for (const NamedReference<Type>* field : *mFields) {
-            out << "os += \"";
-            if (field != *(mFields->begin())) {
-                out << ", ";
-            }
-            out << "." << field->name() << " = \";\n";
-            field->type().emitDump(out, "os", "o." + field->name());
+        if (mStyle == STYLE_SAFE_UNION) {
+            out << "\nswitch (o.getDiscriminator()) {\n";
+            out.indent();
         }
 
+        for (const NamedReference<Type>* field : *mFields) {
+            if (mStyle == STYLE_SAFE_UNION) {
+                out << "case "
+                    << fullName()
+                    << "::hidl_discriminator::"
+                    << field->name()
+                    << ": ";
+
+                out.block([&] {
+                    out << "os += \"."
+                    << field->name()
+                    << " = \";\n"
+                    << "os += toString(o."
+                    << field->name()
+                    << "());\n"
+                    << "break;\n";
+                }).endl();
+            } else {
+                out << "os += \"";
+                if (field != *(mFields->begin())) {
+                    out << ", ";
+                }
+                out << "." << field->name() << " = \";\n";
+                field->type().emitDump(out, "os", "o." + field->name());
+            }
+        }
+
+        if (mStyle == STYLE_SAFE_UNION) {
+            out << "default: ";
+            out.block([&] {
+                   emitSafeUnionUnknownDiscriminatorError(out, "o.getDiscriminator()",
+                                                          true /*fatal*/);
+               })
+                .endl();
+
+            out.unindent();
+            out << "}\n";
+        }
         out << "os += \"}\"; return os;\n";
     }).endl().endl();
 
@@ -489,16 +886,53 @@
             << getCppArgumentType() << " " << (mFields->empty() ? "/* lhs */" : "lhs") << ", "
             << getCppArgumentType() << " " << (mFields->empty() ? "/* rhs */" : "rhs") << ") ";
         out.block([&] {
-            for (const auto &field : *mFields) {
-                out.sIf("lhs." + field->name() + " != rhs." + field->name(), [&] {
+            if (mStyle == STYLE_SAFE_UNION) {
+                out.sIf("lhs.getDiscriminator() != rhs.getDiscriminator()", [&] {
                     out << "return false;\n";
                 }).endl();
+
+                out << "switch (lhs.getDiscriminator()) {\n";
+                out.indent();
+            }
+
+            for (const auto& field : *mFields) {
+                if (mStyle == STYLE_SAFE_UNION) {
+                    out << "case "
+                        << fullName()
+                        << "::hidl_discriminator::"
+                        << field->name()
+                        << ": ";
+
+                    out.block([&] {
+                        out << "return (lhs."
+                        << field->name()
+                        << "() == rhs."
+                        << field->name()
+                        << "());\n";
+                    }).endl();
+                } else {
+                    out.sIf("lhs." + field->name() + " != rhs." + field->name(), [&] {
+                        out << "return false;\n";
+                    }).endl();
+                }
+            }
+
+            if (mStyle == STYLE_SAFE_UNION) {
+                out << "default: ";
+                out.block([&] {
+                       emitSafeUnionUnknownDiscriminatorError(out, "lhs.getDiscriminator()",
+                                                              true /*fatal*/);
+                   })
+                    .endl();
+
+                out.unindent();
+                out << "}\n";
             }
             out << "return true;\n";
         }).endl().endl();
 
         out << "static inline bool operator!=("
-            << getCppArgumentType() << " lhs," << getCppArgumentType() << " rhs)";
+            << getCppArgumentType() << " lhs, " << getCppArgumentType() << " rhs)";
         out.block([&] {
             out << "return !(lhs == rhs);\n";
         }).endl().endl();
@@ -548,6 +982,332 @@
     }
 }
 
+static void emitSafeUnionFieldConstructor(Formatter& out,
+                                          const NamedReference<Type>* field,
+                                          const std::string& initializerObjectName) {
+    out << "new (&hidl_u."
+        << field->name()
+        << ") "
+        << field->type().getCppStackType()
+        << "("
+        << initializerObjectName
+        << ");\n";
+}
+
+static void emitSafeUnionSetterDefinition(Formatter& out,
+                                          const NamedReference<Type>* field,
+                                          const std::string& parameterName,
+                                          bool usesMoveSemantics) {
+    out.block([&] {
+        out << "if (hidl_d != hidl_discriminator::"
+            << field->name()
+            << ") ";
+
+        const std::string argumentName = usesMoveSemantics
+                                         ? ("std::move(" + parameterName + ")")
+                                         : parameterName;
+        out.block([&] {
+            out << "hidl_destructUnion();\n"
+                << "::std::memset(&hidl_u, 0, sizeof(hidl_u));\n\n";
+
+            emitSafeUnionFieldConstructor(out, field, argumentName);
+            out << "hidl_d = hidl_discriminator::"
+                << field->name()
+                << ";\n";
+        }).endl();
+
+        out << "else if (&(hidl_u."
+            << field->name()
+            << ") != &"
+            << parameterName
+            << ") ";
+
+        out.block([&] {
+            out << "hidl_u."
+                << field->name()
+                << " = "
+                << argumentName
+                << ";\n";
+        }).endl();
+    }).endl().endl();
+}
+
+static void emitSafeUnionGetterDefinition(Formatter& out, const std::string& fieldName) {
+    out.block([&] {
+        out << "if (CC_UNLIKELY(hidl_d != hidl_discriminator::"
+            << fieldName
+            << ")) ";
+
+        out.block([&] {
+            out << "LOG_ALWAYS_FATAL(\"Bad safe_union access: safe_union has discriminator %\" "
+                << "PRIu64 \" but discriminator %\" PRIu64 \" was accessed.\",\n";
+            out.indent(2, [&] {
+                out << "static_cast<uint64_t>(hidl_d), "
+                    << "static_cast<uint64_t>(hidl_discriminator::" << fieldName << "));";
+            }).endl();
+        }).endl().endl();
+
+        out << "return hidl_u."
+            << fieldName
+            << ";\n";
+    }).endl().endl();
+}
+
+std::vector<CompoundType::SafeUnionEnumElement> CompoundType::getSafeUnionEnumElements(
+    bool useCppTypeName) const {
+    std::vector<SafeUnionEnumElement> elements;
+
+    for (const auto& field : *mFields) {
+        const std::string fieldTypeName = useCppTypeName
+            ? field->type().getCppStackType(true /* specifyNamespaces */)
+            : field->type().getJavaType(true /* forInitializer */);
+
+        elements.push_back({field->name(), fieldTypeName});
+    }
+
+    return elements;
+}
+
+void CompoundType::emitSafeUnionCopyAndAssignDefinition(Formatter& out,
+                                                        const std::string& parameterName,
+                                                        bool isCopyConstructor,
+                                                        bool usesMoveSemantics) const {
+    out.block([&] {
+        if (!isCopyConstructor) {
+            out << "if (this == &"
+            << parameterName
+            << ") { return *this; }\n\n";
+        }
+
+        out << "switch ("
+            << parameterName
+            << ".hidl_d) ";
+
+        out.block([&] {
+            for (const auto& field : *mFields) {
+                const std::string parameterFieldName = (parameterName + ".hidl_u." +
+                                                        field->name());
+
+                const std::string argumentName = usesMoveSemantics
+                                                 ? ("std::move(" + parameterFieldName + ")")
+                                                 : parameterFieldName;
+
+                out << "case hidl_discriminator::"
+                    << field->name()
+                    << ": ";
+
+                if (isCopyConstructor) {
+                    out.block([&] {
+                        emitSafeUnionFieldConstructor(out, field, argumentName);
+                        out << "break;\n";
+                    }).endl();
+                } else {
+                    out.block([&] {
+                        out << field->name()
+                            << "("
+                            << argumentName
+                            << ");\n"
+                            << "break;\n";
+                    }).endl();
+                }
+            }
+
+            out << "default: ";
+            out.block([&] {
+                   emitSafeUnionUnknownDiscriminatorError(out, parameterName + ".hidl_d",
+                                                          true /*fatal*/);
+               })
+                .endl();
+        }).endl();
+
+        if (isCopyConstructor) {
+            out << "\nhidl_d = "
+                << parameterName
+                << ".hidl_d;\n";
+        } else {
+            out << "return *this;\n";
+        }
+    }).endl().endl();
+}
+
+void CompoundType::emitSafeUnionTypeConstructors(Formatter& out) const {
+
+    // Default constructor
+    out << fullName()
+        << "::"
+        << localName()
+        << "() ";
+
+    out.block([&] {
+        out << "static_assert(offsetof("
+            << fullName()
+            << ", hidl_d) == 0, \"wrong offset\");\n";
+
+        const CompoundLayout layout = getCompoundAlignmentAndSize();
+
+        if (!containsPointer()) {
+            out << "static_assert(offsetof(" << fullName()
+                << ", hidl_u) == " << layout.innerStruct.offset << ", \"wrong offset\");\n";
+        }
+
+        out.endl();
+
+        out << "::std::memset(&hidl_u, 0, sizeof(hidl_u));\n";
+
+        // union itself is zero'd when set
+        // padding after descriminator
+        size_t dpad = layout.innerStruct.offset - layout.discriminator.size;
+        emitPaddingZero(out, layout.discriminator.size /*offset*/, dpad /*size*/);
+
+        size_t innerStructEnd = layout.innerStruct.offset + layout.innerStruct.size;
+        // final padding of the struct
+        size_t fpad = layout.overall.size - innerStructEnd;
+        emitPaddingZero(out, innerStructEnd /*offset*/, fpad /*size*/);
+
+        out.endl();
+
+        CHECK(!mFields->empty());
+        out << "hidl_d = hidl_discriminator::" << mFields->at(0)->name() << ";\n";
+        emitSafeUnionFieldConstructor(out, mFields->at(0), "");
+    }).endl().endl();
+
+    // Destructor
+    out << fullName()
+        << "::~"
+        << localName()
+        << "() ";
+
+    out.block([&] {
+        out << "hidl_destructUnion();\n";
+    }).endl().endl();
+
+    // Move constructor
+    out << fullName() << "::" << localName() << "(" << localName() << "&& other) : " << fullName()
+        << "() ";
+
+    emitSafeUnionCopyAndAssignDefinition(
+            out, "other", true /* isCopyConstructor */, true /* usesMoveSemantics */);
+
+    // Copy constructor
+    out << fullName() << "::" << localName() << "(const " << localName()
+        << "& other) : " << fullName() << "() ";
+
+    emitSafeUnionCopyAndAssignDefinition(
+        out, "other", true /* isCopyConstructor */, false /* usesMoveSemantics */);
+
+    // Move assignment operator
+    out << fullName()
+        << "& ("
+        << fullName()
+        << "::operator=)("
+        << localName()
+        << "&& other) ";
+
+    emitSafeUnionCopyAndAssignDefinition(
+            out, "other", false /* isCopyConstructor */, true /* usesMoveSemantics */);
+
+    // Copy assignment operator
+    out << fullName()
+        << "& ("
+        << fullName()
+        << "::operator=)(const "
+        << localName()
+        << "& other) ";
+
+    emitSafeUnionCopyAndAssignDefinition(
+            out, "other", false /* isCopyConstructor */, false /* usesMoveSemantics */);
+}
+
+void CompoundType::emitSafeUnionTypeDefinitions(Formatter& out) const {
+    emitSafeUnionTypeConstructors(out);
+
+    out << "void "
+        << fullName()
+        << "::hidl_destructUnion() ";
+
+    out.block([&] {
+        out << "switch (hidl_d) ";
+        out.block([&] {
+
+            for (const auto& field : *mFields) {
+                out << "case hidl_discriminator::"
+                    << field->name()
+                    << ": ";
+
+                out.block([&] {
+                    out << "::android::hardware::details::destructElement(&(hidl_u."
+                        << field->name()
+                        << "));\n"
+                        << "break;\n";
+                }).endl();
+            }
+
+            out << "default: ";
+            out.block(
+                   [&] { emitSafeUnionUnknownDiscriminatorError(out, "hidl_d", true /*fatal*/); })
+                .endl();
+        }).endl().endl();
+    }).endl().endl();
+
+    for (const NamedReference<Type>* field : *mFields) {
+        // Setter (copy)
+        out << "void "
+            << fullName()
+            << "::"
+            << field->name()
+            << "("
+            << field->type().getCppArgumentType()
+            << " o) ";
+
+        emitSafeUnionSetterDefinition(out, field, "o", false /* usesMoveSemantics */);
+
+        if (field->type().resolveToScalarType() == nullptr) {
+            // Setter (move)
+            out << "void "
+                << fullName()
+                << "::"
+                << field->name()
+                << "("
+                << field->type().getCppStackType()
+                << "&& o) ";
+
+            emitSafeUnionSetterDefinition(out, field, "o", true /* usesMoveSemantics */);
+        }
+
+        // Getter (mutable)
+        out << field->type().getCppStackType()
+            << "& ("
+            << fullName()
+            << "::"
+            << field->name()
+            << ")() ";
+
+        emitSafeUnionGetterDefinition(out, field->name());
+
+        // Getter (immutable)
+        out << field->type().getCppArgumentType()
+            << " ("
+            << fullName()
+            << "::"
+            << field->name()
+            << ")() const ";
+
+        emitSafeUnionGetterDefinition(out, field->name());
+    }
+
+    // Trivial constructor/destructor for internal union
+    out << fullName() << "::hidl_union::hidl_union() {}\n\n"
+        << fullName() << "::hidl_union::~hidl_union() {}\n\n";
+
+    // Utility method
+    out << fullName() << "::hidl_discriminator ("
+        << fullName() << "::getDiscriminator)() const ";
+
+    out.block([&] {
+        out << "return hidl_d;\n";
+    }).endl().endl();
+}
+
 void CompoundType::emitTypeDefinitions(Formatter& out, const std::string& prefix) const {
     std::string space = prefix.empty() ? "" : (prefix + "::");
     Scope::emitTypeDefinitions(out, space + localName());
@@ -561,6 +1321,22 @@
         emitResolveReferenceDef(out, prefix, true /* isReader */);
         emitResolveReferenceDef(out, prefix, false /* isReader */);
     }
+
+    if (mStyle == STYLE_SAFE_UNION) {
+        emitSafeUnionTypeDefinitions(out);
+    }
+}
+
+static void emitJavaSafeUnionUnknownDiscriminatorError(Formatter& out, bool fatal) {
+    out << "throw new ";
+
+    if (fatal) {
+        out << "Error";
+    } else {
+        out << "IllegalStateException";
+    }
+
+    out << "(\"Unknown union discriminator (value: \" + hidl_d + \").\");\n";
 }
 
 void CompoundType::emitJavaTypeDeclarations(Formatter& out, bool atTopLevel) const {
@@ -578,15 +1354,132 @@
 
     Scope::emitJavaTypeDeclarations(out, false /* atTopLevel */);
 
-    for (const auto& field : *mFields) {
-        field->emitDocComment(out);
+    if (mStyle == STYLE_SAFE_UNION) {
+        out << "public " << localName() << "() ";
+        out.block([&] {
+            CHECK(!mFields->empty());
+            mFields->at(0)->type().emitJavaFieldDefaultInitialValue(out, "hidl_o");
+        }).endl().endl();
 
-        out << "public ";
+        const std::string discriminatorStorageType = (
+                getUnionDiscriminatorType()->getJavaType(false));
 
-        field->type().emitJavaFieldInitializer(out, field->name());
-    }
+        out << "public static final class hidl_discriminator ";
+        out.block([&] {
+            const auto elements = getSafeUnionEnumElements(false /* useCppTypeName */);
+            for (size_t idx = 0; idx < elements.size(); idx++) {
+                out << "public static final "
+                    << discriminatorStorageType
+                    << " "
+                    << elements[idx].fieldName
+                    << " = "
+                    << idx
+                    << ";";
 
-    if (!mFields->empty()) {
+                if (!elements[idx].fieldTypeName.empty()) {
+                    out << "  // "
+                        << elements[idx].fieldTypeName;
+                }
+                out << "\n";
+            }
+
+            out << "\n"
+                << "public static final String getName("
+                << discriminatorStorageType
+                << " value) ";
+
+            out.block([&] {
+                out << "switch (value) ";
+                out.block([&] {
+                    for (size_t idx = 0; idx < elements.size(); idx++) {
+                        out << "case "
+                            << idx
+                            << ": { return \""
+                            << elements[idx].fieldName
+                            << "\"; }\n";
+                    }
+                    out << "default: { return \"Unknown\"; }\n";
+                }).endl();
+            }).endl().endl();
+
+            out << "private hidl_discriminator() {}\n";
+        }).endl().endl();
+
+        out << "private " << discriminatorStorageType << " hidl_d = 0;\n";
+        out << "private Object hidl_o = null;\n";
+
+        for (const auto& field : *mFields) {
+            // Setter
+            out << "public void "
+                << field->name()
+                << "("
+                << field->type().getJavaType(false)
+                << " "
+                << field->name()
+                << ") ";
+
+            out.block([&] {
+                out << "hidl_d = hidl_discriminator."
+                    << field->name()
+                    << ";\n";
+
+                out << "hidl_o = "
+                    << field->name()
+                    << ";\n";
+            }).endl().endl();
+
+            // Getter
+            out << "public "
+                << field->type().getJavaType(false)
+                << " "
+                << field->name()
+                << "() ";
+
+            out.block([&] {
+                out << "if (hidl_d != hidl_discriminator."
+                    << field->name()
+                    << ") ";
+
+                out.block([&] {
+                    out << "String className = (hidl_o != null) ? "
+                        << "hidl_o.getClass().getName() : \"null\";\n";
+
+                    out << "throw new IllegalStateException(\n";
+                    out.indent(2, [&] {
+                        out << "\"Read access to inactive union components is disallowed. \" +\n"
+                            << "\"Discriminator value is \" + hidl_d + \" (corresponding \" +\n"
+                            << "\"to \" + hidl_discriminator.getName(hidl_d) + \"), and \" +\n"
+                            << "\"hidl_o is of type \" + className + \".\");\n";
+                    });
+                }).endl();
+
+                out << "if (hidl_o != null && !"
+                    << field->type().getJavaTypeClass()
+                    << ".class.isInstance(hidl_o)) ";
+
+                out.block([&] {
+                    out << "throw new Error(\"Union is in a corrupted state.\");\n";
+                }).endl();
+
+                out << "return ("
+                    << field->type().getJavaTypeCast("hidl_o")
+                    << ");\n";
+            }).endl().endl();
+        }
+
+        out << "// Utility method\n"
+            << "public "
+            << discriminatorStorageType
+            << " getDiscriminator() { return hidl_d; }\n\n";
+
+    } else {
+        for (const auto& field : *mFields) {
+            field->emitDocComment(out);
+
+            out << "public ";
+            field->type().emitJavaFieldInitializer(out, field->name());
+        }
+
         out << "\n";
     }
 
@@ -606,14 +1499,24 @@
                 out << "return false;\n";
             }).endl();
             out << fullJavaName() << " other = (" << fullJavaName() << ")otherObject;\n";
-            for (const auto &field : *mFields) {
-                std::string condition = (field->type().isScalar() || field->type().isEnum())
-                    ? "this." + field->name() + " != other." + field->name()
-                    : ("!android.os.HidlSupport.deepEquals(this." + field->name()
-                            + ", other." + field->name() + ")");
-                out.sIf(condition, [&] {
+
+            if (mStyle == STYLE_SAFE_UNION) {
+                out.sIf("this.hidl_d != other.hidl_d", [&] {
                     out << "return false;\n";
                 }).endl();
+                out.sIf("!android.os.HidlSupport.deepEquals(this.hidl_o, other.hidl_o)", [&] {
+                    out << "return false;\n";
+                }).endl();
+            } else {
+                for (const auto &field : *mFields) {
+                    std::string condition = (field->type().isScalar() || field->type().isEnum())
+                        ? "this." + field->name() + " != other." + field->name()
+                        : ("!android.os.HidlSupport.deepEquals(this." + field->name()
+                                + ", other." + field->name() + ")");
+                    out.sIf(condition, [&] {
+                        out << "return false;\n";
+                    }).endl();
+                }
             }
             out << "return true;\n";
         }).endl().endl();
@@ -622,9 +1525,14 @@
         out.block([&] {
             out << "return java.util.Objects.hash(\n";
             out.indent(2, [&] {
-                out.join(mFields->begin(), mFields->end(), ", \n", [&] (const auto &field) {
-                    out << "android.os.HidlSupport.deepHashCode(this." << field->name() << ")";
-                });
+                if (mStyle == STYLE_SAFE_UNION) {
+                    out << "android.os.HidlSupport.deepHashCode(this.hidl_o),\n"
+                        << "java.util.Objects.hashCode(this.hidl_d)";
+                } else {
+                    out.join(mFields->begin(), mFields->end(), ", \n", [&] (const auto &field) {
+                        out << "android.os.HidlSupport.deepHashCode(this." << field->name() << ")";
+                    });
+                }
             });
             out << ");\n";
         }).endl().endl();
@@ -638,32 +1546,96 @@
     out.block([&] {
         out << "java.lang.StringBuilder builder = new java.lang.StringBuilder();\n"
             << "builder.append(\"{\");\n";
-        for (const auto &field : *mFields) {
-            out << "builder.append(\"";
-            if (field != *(mFields->begin())) {
-                out << ", ";
-            }
-            out << "." << field->name() << " = \");\n";
-            field->type().emitJavaDump(out, "builder", "this." + field->name());
+
+        if (mStyle == STYLE_SAFE_UNION) {
+            out << "switch (this.hidl_d) {\n";
+            out.indent();
         }
+
+        for (const auto &field : *mFields) {
+            if (mStyle == STYLE_SAFE_UNION) {
+                out << "case hidl_discriminator."
+                    << field->name()
+                    << ": ";
+
+                out.block([&] {
+                    out << "builder.append(\""
+                        << "."
+                        << field->name()
+                        << " = \");\n";
+
+                    field->type().emitJavaDump(out, "builder", "this." + field->name() + "()");
+                    out << "break;\n";
+                }).endl();
+            }
+            else {
+                out << "builder.append(\"";
+                if (field != *(mFields->begin())) {
+                    out << ", ";
+                }
+                out << "." << field->name() << " = \");\n";
+                field->type().emitJavaDump(out, "builder", "this." + field->name());
+            }
+        }
+
+        if (mStyle == STYLE_SAFE_UNION) {
+            out << "default: ";
+            out.block([&] { emitJavaSafeUnionUnknownDiscriminatorError(out, true /*fatal*/); })
+                .endl();
+
+            out.unindent();
+            out << "}\n";
+        }
+
         out << "builder.append(\"}\");\nreturn builder.toString();\n";
     }).endl().endl();
 
-    size_t structAlign, structSize;
-    getAlignmentAndSize(&structAlign, &structSize);
+    CompoundLayout layout = getCompoundAlignmentAndSize();
 
     ////////////////////////////////////////////////////////////////////////////
 
     out << "public final void readFromParcel(android.os.HwParcel parcel) {\n";
     out.indent();
     if (containsInterface()) {
+
+        if (mStyle == STYLE_SAFE_UNION) {
+            out << "hidl_d = ";
+            getUnionDiscriminatorType()->emitJavaReaderWriter(
+                    out, "parcel", "hidl_d", true);
+
+            out << "switch (hidl_d) {\n";
+            out.indent();
+        }
+
         for (const auto& field : *mFields) {
-            out << field->name() << " = ";
-            field->type().emitJavaReaderWriter(out, "parcel", field->name(), true);
+            if (mStyle == STYLE_SAFE_UNION) {
+                out << "case hidl_discriminator."
+                    << field->name()
+                    << ": ";
+
+                out.block([&] {
+                    out << "hidl_o = ";
+                    field->type().emitJavaReaderWriter(out, "parcel", "hidl_o", true);
+
+                    out << "break;\n";
+                }).endl();
+            } else {
+                out << field->name() << " = ";
+                field->type().emitJavaReaderWriter(out, "parcel", field->name(), true);
+            }
+        }
+
+        if (mStyle == STYLE_SAFE_UNION) {
+            out << "default: ";
+            out.block([&] { emitJavaSafeUnionUnknownDiscriminatorError(out, false /*fatal*/); })
+                .endl();
+
+            out.unindent();
+            out << "}\n";
         }
     } else {
         out << "android.os.HwBlob blob = parcel.readBuffer(";
-        out << structSize << "/* size */);\n";
+        out << layout.overall.size << " /* size */);\n";
         out << "readEmbeddedFromParcel(parcel, blob, 0 /* parentOffset */);\n";
     }
     out.unindent();
@@ -708,20 +1680,52 @@
         out.indent(2);
         out << "android.os.HwParcel parcel, android.os.HwBlob _hidl_blob, long _hidl_offset) {\n";
         out.unindent();
-        size_t offset = 0;
+
+        if (mStyle == STYLE_SAFE_UNION) {
+            getUnionDiscriminatorType()->emitJavaFieldReaderWriter(
+                out, 0 /* depth */, "parcel", "_hidl_blob", "hidl_d",
+                "_hidl_offset + " + std::to_string(layout.discriminator.offset),
+                true /* isReader */);
+
+            out << "switch (this.hidl_d) {\n";
+            out.indent();
+        }
+
+        size_t offset = layout.innerStruct.offset;
         for (const auto& field : *mFields) {
-            size_t fieldAlign, fieldSize;
-            field->type().getAlignmentAndSize(&fieldAlign, &fieldSize);
 
-            size_t pad = offset % fieldAlign;
-            if (pad > 0) {
-                offset += fieldAlign - pad;
+            if (mStyle == STYLE_SAFE_UNION) {
+                out << "case hidl_discriminator."
+                    << field->name()
+                    << ": ";
+
+                out.block([&] {
+                    field->type().emitJavaFieldDefaultInitialValue(out, "hidl_o");
+                    field->type().emitJavaFieldReaderWriter(
+                        out, 0 /* depth */, "parcel", "_hidl_blob", "hidl_o",
+                        "_hidl_offset + " + std::to_string(offset), true /* isReader */);
+
+                    out << "break;\n";
+                }).endl();
+            } else {
+                size_t fieldAlign, fieldSize;
+                field->type().getAlignmentAndSize(&fieldAlign, &fieldSize);
+
+                offset += Layout::getPad(offset, fieldAlign);
+                field->type().emitJavaFieldReaderWriter(
+                    out, 0 /* depth */, "parcel", "_hidl_blob", field->name(),
+                    "_hidl_offset + " + std::to_string(offset), true /* isReader */);
+                offset += fieldSize;
             }
+        }
 
-            field->type().emitJavaFieldReaderWriter(
-                out, 0 /* depth */, "parcel", "_hidl_blob", field->name(),
-                "_hidl_offset + " + std::to_string(offset), true /* isReader */);
-            offset += fieldSize;
+        if (mStyle == STYLE_SAFE_UNION) {
+            out << "default: ";
+            out.block([&] { emitJavaSafeUnionUnknownDiscriminatorError(out, false /*fatal*/); })
+                .endl();
+
+            out.unindent();
+            out << "}\n";
         }
         out.unindent();
         out << "}\n\n";
@@ -733,11 +1737,40 @@
     out.indent();
 
     if (containsInterface()) {
+        if (mStyle == STYLE_SAFE_UNION) {
+            getUnionDiscriminatorType()->emitJavaReaderWriter(
+                out, "parcel", "hidl_d", false);
+
+            out << "switch (this.hidl_d) {\n";
+            out.indent();
+        }
+
         for (const auto& field : *mFields) {
-            field->type().emitJavaReaderWriter(out, "parcel", field->name(), false);
+            if (mStyle == STYLE_SAFE_UNION) {
+                out << "case hidl_discriminator."
+                    << field->name()
+                    << ": ";
+
+                out.block([&] {
+                    field->type().emitJavaReaderWriter(out, "parcel", field->name() + "()", false);
+                    out << "break;\n";
+                }).endl();
+            } else {
+                field->type().emitJavaReaderWriter(out, "parcel", field->name(), false);
+            }
+        }
+
+        if (mStyle == STYLE_SAFE_UNION) {
+            out << "default: ";
+            out.block([&] { emitJavaSafeUnionUnknownDiscriminatorError(out, true /*fatal*/); })
+                .endl();
+
+            out.unindent();
+            out << "}\n";
         }
     } else {
-        out << "android.os.HwBlob _hidl_blob = new android.os.HwBlob(" << structSize
+        out << "android.os.HwBlob _hidl_blob = new android.os.HwBlob("
+            << layout.overall.size
             << " /* size */);\n";
 
         out << "writeEmbeddedToBlob(_hidl_blob, 0 /* parentOffset */);\n"
@@ -755,10 +1788,10 @@
 
     if (containsInterface()) {
         out << "parcel.writeInt32(_hidl_vec.size());\n";
-        out << "for(" << fullJavaName() << " tmp: _hidl_vec)\n";
-        out.indent();
-        emitJavaReaderWriter(out, "parcel", "tmp", false);
-        out.unindent();
+        out << "for(" << fullJavaName() << " tmp: _hidl_vec) ";
+        out.block([&] {
+            emitJavaReaderWriter(out, "parcel", "tmp", false);
+        }).endl();
     } else {
         out << "android.os.HwBlob _hidl_blob = new android.os.HwBlob(" << vecSize
             << " /* sizeof(hidl_vec<T>) */);\n";
@@ -774,26 +1807,57 @@
     ////////////////////////////////////////////////////////////////////////////
 
     if (containsInterface()) {
-        out << "// writeEmbeddedFromParcel() is not generated\n";
+        out << "// writeEmbeddedToBlob() is not generated\n";
     } else {
         out << "public final void writeEmbeddedToBlob(\n";
         out.indent(2);
         out << "android.os.HwBlob _hidl_blob, long _hidl_offset) {\n";
         out.unindent();
-        size_t offset = 0;
-        for (const auto& field : *mFields) {
-            size_t fieldAlign, fieldSize;
-            field->type().getAlignmentAndSize(&fieldAlign, &fieldSize);
-            size_t pad = offset % fieldAlign;
-            if (pad > 0) {
-                offset += fieldAlign - pad;
-            }
-            field->type().emitJavaFieldReaderWriter(
-                out, 0 /* depth */, "parcel", "_hidl_blob", field->name(),
-                "_hidl_offset + " + std::to_string(offset), false /* isReader */);
-            offset += fieldSize;
+
+        if (mStyle == STYLE_SAFE_UNION) {
+            getUnionDiscriminatorType()->emitJavaFieldReaderWriter(
+                out, 0 /* depth */, "parcel", "_hidl_blob", "hidl_d",
+                "_hidl_offset + " + std::to_string(layout.discriminator.offset),
+                false /* isReader */);
+
+            out << "switch (this.hidl_d) {\n";
+            out.indent();
         }
 
+        size_t offset = layout.innerStruct.offset;
+        for (const auto& field : *mFields) {
+            if (mStyle == STYLE_SAFE_UNION) {
+                out << "case hidl_discriminator."
+                    << field->name()
+                    << ": ";
+
+                out.block([&] {
+                    field->type().emitJavaFieldReaderWriter(
+                        out, 0 /* depth */, "parcel", "_hidl_blob", field->name() + "()",
+                        "_hidl_offset + " + std::to_string(offset), false /* isReader */);
+
+                    out << "break;\n";
+                }).endl();
+            } else {
+                size_t fieldAlign, fieldSize;
+                field->type().getAlignmentAndSize(&fieldAlign, &fieldSize);
+
+                offset += Layout::getPad(offset, fieldAlign);
+                field->type().emitJavaFieldReaderWriter(
+                    out, 0 /* depth */, "parcel", "_hidl_blob", field->name(),
+                    "_hidl_offset + " + std::to_string(offset), false /* isReader */);
+                offset += fieldSize;
+            }
+        }
+
+        if (mStyle == STYLE_SAFE_UNION) {
+            out << "default: ";
+            out.block([&] { emitJavaSafeUnionUnknownDiscriminatorError(out, true /*fatal*/); })
+                .endl();
+
+            out.unindent();
+            out << "}\n";
+        }
         out.unindent();
         out << "}\n";
     }
@@ -814,18 +1878,7 @@
 
     out.indent(2);
 
-    bool useName = false;
-    for (const auto &field : *mFields) {
-        if (field->type().useNameInEmitReaderWriterEmbedded(isReader)) {
-            useName = true;
-            break;
-        }
-    }
-    std::string name = useName ? "obj" : "/* obj */";
-    // if not useName, then obj  should not be used at all,
-    // then the #error should not be emitted.
-    std::string error = useName ? "" : "\n#error\n";
-
+    const std::string name = "obj";
     if (isReader) {
         out << "const " << space << localName() << " &" << name << ",\n";
         out << "const ::android::hardware::Parcel &parcel,\n";
@@ -844,15 +1897,35 @@
 
     out << "::android::status_t _hidl_err = ::android::OK;\n\n";
 
+    if (mStyle == STYLE_SAFE_UNION) {
+        out << "switch (" << name << ".getDiscriminator()) {\n";
+        out.indent();
+    }
+
     for (const auto &field : *mFields) {
         if (!field->type().needsEmbeddedReadWrite()) {
             continue;
         }
 
+        if (mStyle == STYLE_SAFE_UNION) {
+            out << "case " << fullName() << "::hidl_discriminator::"
+                << field->name() << ": {\n";
+            out.indent();
+        }
+
+        const std::string fieldName = (mStyle == STYLE_SAFE_UNION)
+                                        ? (name + "." + field->name() + "()")
+                                        : (name + "." + field->name());
+
+        const std::string fieldOffset = (mStyle == STYLE_SAFE_UNION)
+                                        ? (name + ".hidl_getUnionOffset() " +
+                                           "/* safe_union: union offset into struct */")
+                                        : ("offsetof(" + fullName() + ", " + field->name() + ")");
+
         field->type().emitReaderWriterEmbedded(
                 out,
                 0 /* depth */,
-                name + "." + field->name() + error,
+                fieldName,
                 field->name() /* sanitizedName */,
                 false /* nameIsPointer */,
                 "parcel",
@@ -860,11 +1933,19 @@
                 isReader,
                 ErrorMode_Return,
                 "parentHandle",
-                "parentOffset + offsetof("
-                    + fullName()
-                    + ", "
-                    + field->name()
-                    + ")");
+                "parentOffset + " + fieldOffset);
+
+        if (mStyle == STYLE_SAFE_UNION) {
+            out << "break;\n";
+            out.unindent();
+            out << "}\n";
+        }
+    }
+
+    if (mStyle == STYLE_SAFE_UNION) {
+        out << "default: { break; }\n";
+        out.unindent();
+        out << "}\n";
     }
 
     out << "return _hidl_err;\n";
@@ -918,15 +1999,35 @@
     // should not be used at all, then the #error should not be emitted.
     std::string error = useParent ? "" : "\n#error\n";
 
+    if (mStyle == STYLE_SAFE_UNION) {
+        out << "switch (" << nameDeref << "getDiscriminator()) {\n";
+        out.indent();
+    }
+
     for (const auto &field : *mFields) {
         if (!field->type().needsResolveReferences()) {
             continue;
         }
 
+        if (mStyle == STYLE_SAFE_UNION) {
+            out << "case " << fullName() << "::hidl_discriminator::"
+                << field->name() << ": {\n";
+            out.indent();
+        }
+
+        const std::string fieldName = (mStyle == STYLE_SAFE_UNION)
+                                        ? (nameDeref + field->name() + "()")
+                                        : (nameDeref + field->name());
+
+        const std::string fieldOffset = (mStyle == STYLE_SAFE_UNION)
+                                        ? (nameDeref + "hidl_getUnionOffset() " +
+                                           "/* safe_union: union offset into struct */")
+                                        : ("offsetof(" + fullName() + ", " + field->name() + ")");
+
         field->type().emitResolveReferencesEmbedded(
             out,
             0 /* depth */,
-            nameDeref + field->name(),
+            fieldName,
             field->name() /* sanitizedName */,
             false,    // nameIsPointer
             "parcel", // const std::string &parcelObj,
@@ -935,12 +2036,21 @@
             ErrorMode_Return,
             parentHandleName + error,
             parentOffsetName
-                + " + offsetof("
-                + fullName()
-                + ", "
-                + field->name()
-                + ")"
+                + " + "
+                + fieldOffset
                 + error);
+
+        if (mStyle == STYLE_SAFE_UNION) {
+            out << "break;\n";
+            out.unindent();
+            out << "}\n";
+        }
+    }
+
+    if (mStyle == STYLE_SAFE_UNION) {
+        out << "default: { _hidl_err = ::android::BAD_VALUE; break; }\n";
+        out.unindent();
+        out << "}\n";
     }
 
     out << "return _hidl_err;\n";
@@ -950,7 +2060,7 @@
 }
 
 bool CompoundType::needsEmbeddedReadWrite() const {
-    if (mStyle != STYLE_STRUCT) {
+    if (mStyle == STYLE_UNION) {
         return false;
     }
 
@@ -964,7 +2074,7 @@
 }
 
 bool CompoundType::deepNeedsResolveReferences(std::unordered_set<const Type*>* visited) const {
-    if (mStyle != STYLE_STRUCT) {
+    if (mStyle == STYLE_UNION) {
         return false;
     }
 
@@ -998,6 +2108,15 @@
                 out << "sub_union: {\n";
                 break;
             }
+            case STYLE_SAFE_UNION:
+            {
+                out << "sub_safe_union: {\n";
+                break;
+            }
+            default:
+            {
+                CHECK(!"Should not be here");
+            }
         }
         out.indent();
         type->emitVtsTypeDeclarations(out);
@@ -1018,6 +2137,15 @@
                 out << "union_value: {\n";
                 break;
             }
+            case STYLE_SAFE_UNION:
+            {
+                out << "safe_union_value: {\n";
+                break;
+            }
+            default:
+            {
+                CHECK(!"Should not be here");
+            }
         }
         out.indent();
         out << "name: \"" << field->name() << "\"\n";
@@ -1033,7 +2161,7 @@
 }
 
 bool CompoundType::deepIsJavaCompatible(std::unordered_set<const Type*>* visited) const {
-    if (mStyle != STYLE_STRUCT) {
+    if (mStyle == STYLE_UNION) {
         return false;
     }
 
@@ -1057,48 +2185,101 @@
 }
 
 void CompoundType::getAlignmentAndSize(size_t *align, size_t *size) const {
-    *align = 1;
-    *size = 0;
+    CompoundLayout layout = getCompoundAlignmentAndSize();
+    *align = layout.overall.align;
+    *size = layout.overall.size;
+}
 
-    size_t offset = 0;
+CompoundType::CompoundLayout CompoundType::getCompoundAlignmentAndSize() const {
+    CompoundLayout compoundLayout;
+
+    // Local aliases for convenience
+    Layout& overall = compoundLayout.overall;
+    Layout& innerStruct = compoundLayout.innerStruct;
+    Layout& discriminator = compoundLayout.discriminator;
+
+    if (mStyle == STYLE_SAFE_UNION) {
+        getUnionDiscriminatorType()->getAlignmentAndSize(
+            &(discriminator.align), &(discriminator.size));
+
+        innerStruct.offset = discriminator.size;
+    }
+
     for (const auto &field : *mFields) {
+
         // Each field is aligned according to its alignment requirement.
         // The surrounding structure's alignment is the maximum of its
         // fields' aligments.
-
         size_t fieldAlign, fieldSize;
         field->type().getAlignmentAndSize(&fieldAlign, &fieldSize);
+        size_t lPad = Layout::getPad(innerStruct.size, fieldAlign);
 
-        size_t pad = offset % fieldAlign;
-        if (pad > 0) {
-            offset += fieldAlign - pad;
-        }
+        innerStruct.size = (mStyle == STYLE_STRUCT)
+                            ? (innerStruct.size + lPad + fieldSize)
+                            : std::max(innerStruct.size, fieldSize);
 
-        if (mStyle == STYLE_STRUCT) {
-            offset += fieldSize;
-        } else {
-            *size = std::max(*size, fieldSize);
-        }
+        innerStruct.align = std::max(innerStruct.align, fieldAlign);
+    }
 
-        if (fieldAlign > (*align)) {
-            *align = fieldAlign;
+    // Pad the inner structure's size
+    innerStruct.size += Layout::getPad(innerStruct.size,
+                                       innerStruct.align);
+
+    // Compute its final offset
+    innerStruct.offset += Layout::getPad(innerStruct.offset,
+                                         innerStruct.align);
+
+    // An empty struct/union still occupies a byte of space in C++.
+    if (innerStruct.size == 0) {
+        innerStruct.size = 1;
+    }
+
+    overall.size = innerStruct.offset + innerStruct.size;
+
+    // Pad the overall structure's size
+    overall.align = std::max(innerStruct.align, discriminator.align);
+    overall.size += Layout::getPad(overall.size, overall.align);
+
+    if (mStyle != STYLE_SAFE_UNION) {
+        CHECK(overall.offset == innerStruct.offset) << overall.offset << " " << innerStruct.offset;
+        CHECK(overall.align == innerStruct.align) << overall.align << " " << innerStruct.align;
+        CHECK(overall.size == innerStruct.size) << overall.size << " " << innerStruct.size;
+    }
+
+    return compoundLayout;
+}
+
+void CompoundType::emitPaddingZero(Formatter& out, size_t offset, size_t size) const {
+    if (size > 0) {
+        out << "::std::memset(reinterpret_cast<uint8_t*>(this) + " << offset << ", 0, " << size
+            << ");\n";
+    } else {
+        out << "// no padding to zero starting at offset " << offset << "\n";
+    }
+}
+
+std::unique_ptr<ScalarType> CompoundType::getUnionDiscriminatorType() const {
+    static const std::vector<std::pair<int, ScalarType::Kind> > scalars {
+        {8, ScalarType::Kind::KIND_UINT8},
+        {16, ScalarType::Kind::KIND_UINT16},
+        {32, ScalarType::Kind::KIND_UINT32},
+    };
+
+    size_t numFields = mFields->size();
+    auto kind = ScalarType::Kind::KIND_UINT64;
+
+    for (const auto& scalar : scalars) {
+        if (numFields <= (1ULL << scalar.first)) {
+            kind = scalar.second; break;
         }
     }
 
-    if (mStyle == STYLE_STRUCT) {
-        *size = offset;
-    }
+    return std::unique_ptr<ScalarType>(new ScalarType(kind, nullptr));
+}
 
-    // Final padding to account for the structure's alignment.
-    size_t pad = (*size) % (*align);
-    if (pad > 0) {
-        (*size) += (*align) - pad;
-    }
-
-    if (*size == 0) {
-        // An empty struct still occupies a byte of space in C++.
-        *size = 1;
-    }
+size_t CompoundType::Layout::getPad(size_t offset, size_t align) {
+    size_t remainder = offset % align;
+    return (remainder > 0) ? (align - remainder) : 0;
 }
 
 }  // namespace android
diff --git a/CompoundType.h b/CompoundType.h
index 3ef5f02..70df3f5 100644
--- a/CompoundType.h
+++ b/CompoundType.h
@@ -29,6 +29,7 @@
     enum Style {
         STYLE_STRUCT,
         STYLE_UNION,
+        STYLE_SAFE_UNION,
     };
 
     CompoundType(Style style, const char* localName, const FQName& fullName,
@@ -48,6 +49,7 @@
 
     status_t validate() const override;
     status_t validateUniqueNames() const;
+    status_t validateSubTypeNames() const;
 
     std::string getCppType(StorageMode mode,
                            bool specifyNamespaces) const override;
@@ -108,6 +110,9 @@
     void emitJavaFieldInitializer(
             Formatter &out, const std::string &fieldName) const override;
 
+    void emitJavaFieldDefaultInitialValue(
+            Formatter &out, const std::string &declaredFieldName) const override;
+
     void emitJavaFieldReaderWriter(
             Formatter &out,
             size_t depth,
@@ -120,6 +125,7 @@
     void emitTypeDeclarations(Formatter& out) const override;
     void emitTypeForwardDeclaration(Formatter& out) const override;
     void emitPackageTypeDeclarations(Formatter& out) const override;
+    void emitPackageTypeHeaderDefinitions(Formatter& out) const override;
     void emitPackageHwDeclarations(Formatter& out) const override;
 
     void emitTypeDefinitions(Formatter& out, const std::string& prefix) const override;
@@ -136,13 +142,70 @@
     bool deepIsJavaCompatible(std::unordered_set<const Type*>* visited) const override;
     bool deepContainsPointer(std::unordered_set<const Type*>* visited) const override;
 
-    void getAlignmentAndSize(size_t *align, size_t *size) const;
+    void getAlignmentAndSize(size_t *align, size_t *size) const override;
 
     bool containsInterface() const;
 private:
+
+    struct Layout {
+        size_t offset;
+        size_t align;
+        size_t size;
+
+        Layout() : offset(0), align(1), size(0) {}
+        static size_t getPad(size_t offset, size_t align);
+    };
+
+    struct CompoundLayout {
+        // Layout of this entire object including metadata.
+        // For struct/union, this is the same as innerStruct.
+        Layout overall;
+        // Layout of user-specified data
+        Layout innerStruct;
+        // Layout of discriminator for safe union (otherwise zero)
+        Layout discriminator;
+    };
+
     Style mStyle;
     std::vector<NamedReference<Type>*>* mFields;
 
+    void emitLayoutAsserts(Formatter& out, const Layout& localLayout,
+                           const std::string& localLayoutName) const;
+
+    void emitInvalidSubTypeNamesError(const std::string& subTypeName,
+                                      const Location& location) const;
+
+    void emitSafeUnionTypeDefinitions(Formatter& out) const;
+    void emitSafeUnionTypeConstructors(Formatter& out) const;
+    void emitSafeUnionTypeDeclarations(Formatter& out) const;
+    std::unique_ptr<ScalarType> getUnionDiscriminatorType() const;
+
+    void emitSafeUnionUnknownDiscriminatorError(Formatter& out, const std::string& value,
+                                                bool fatal) const;
+
+    void emitSafeUnionCopyAndAssignDefinition(Formatter& out,
+                                              const std::string& parameterName,
+                                              bool isCopyConstructor,
+                                              bool usesMoveSemantics) const;
+
+    struct SafeUnionEnumElement {
+        std::string fieldName;
+        std::string fieldTypeName;
+    };
+
+    std::vector<SafeUnionEnumElement> getSafeUnionEnumElements(bool useCppTypeName) const;
+
+    CompoundLayout getCompoundAlignmentAndSize() const;
+    void emitPaddingZero(Formatter& out, size_t offset, size_t size) const;
+
+    void emitSafeUnionReaderWriterForInterfaces(
+            Formatter &out,
+            const std::string &name,
+            const std::string &parcelObj,
+            bool parcelObjIsPointer,
+            bool isReader,
+            ErrorMode mode) const;
+
     void emitStructReaderWriter(
             Formatter &out, const std::string &prefix, bool isReader) const;
     void emitResolveReferenceDef(Formatter& out, const std::string& prefix, bool isReader) const;
diff --git a/ConstantExpression.cpp b/ConstantExpression.cpp
index 8a2b875..002bf56 100644
--- a/ConstantExpression.cpp
+++ b/ConstantExpression.cpp
@@ -173,7 +173,7 @@
 
     CHECK(!expr.empty());
     CHECK(isSupported(kind));
-    mTrivialDescription = true;
+    mTrivialDescription = std::to_string(value) == expr;
     mExpr = expr;
     mValueKind = kind;
     mValue = value;
@@ -254,7 +254,7 @@
     CHECK(mUnary->isEvaluated());
     mIsEvaluated = true;
 
-    mExpr = std::string("(") + mOp + mUnary->description() + ")";
+    mExpr = std::string("(") + mOp + mUnary->mExpr + ")";
     mValueKind = mUnary->mValueKind;
 
 #define CASE_UNARY(__type__)                                          \
@@ -270,7 +270,7 @@
     CHECK(mRval->isEvaluated());
     mIsEvaluated = true;
 
-    mExpr = std::string("(") + mLval->description() + " " + mOp + " " + mRval->description() + ")";
+    mExpr = std::string("(") + mLval->mExpr + " " + mOp + " " + mRval->mExpr + ")";
 
     bool isArithmeticOrBitflip = OP_IS_BIN_ARITHMETIC || OP_IS_BIN_BITFLIP;
 
@@ -330,8 +330,7 @@
     CHECK(mFalseVal->isEvaluated());
     mIsEvaluated = true;
 
-    mExpr = std::string("(") + mCond->description() + "?" + mTrueVal->description() + ":" +
-            mFalseVal->description() + ")";
+    mExpr = std::string("(") + mCond->mExpr + "?" + mTrueVal->mExpr + ":" + mFalseVal->mExpr + ")";
 
     // note: for ?:, unlike arithmetic ops, integral promotion is not processed.
     mValueKind = usualArithmeticConversion(mTrueVal->mValueKind, mFalseVal->mValueKind);
@@ -356,34 +355,54 @@
     mIsEvaluated = true;
 }
 
+status_t AttributeConstantExpression::validate() const {
+    if (mTag == "len") {
+        if (!mReference->isEnum()) {
+            std::cerr << "ERROR: " << mExpr << " refers to " << mReference->typeName()
+                      << " but should refer to an enum." << std::endl;
+            return UNKNOWN_ERROR;
+        }
+    } else {
+        std::cerr << "ERROR: " << mExpr << " is not a supported tag" << std::endl;
+        return UNKNOWN_ERROR;
+    }
+
+    return OK;
+}
+
+void AttributeConstantExpression::evaluate() {
+    if (isEvaluated()) return;
+
+    CHECK(mTag == "len");
+    CHECK(mReference->isEnum());
+
+    EnumType* enumType = static_cast<EnumType*>(mReference.get());
+    mValue = enumType->numValueNames();
+
+    if (mValue <= INT32_MAX)
+        mValueKind = SK(INT32);
+    else
+        mValueKind = SK(INT64);
+
+    mIsEvaluated = true;
+}
+
 std::unique_ptr<ConstantExpression> ConstantExpression::addOne(ScalarType::Kind baseKind) {
     auto ret = std::make_unique<BinaryConstantExpression>(
         this, "+", ConstantExpression::One(baseKind).release());
     return ret;
 }
 
-const std::string& ConstantExpression::description() const {
-    CHECK(isEvaluated());
-    return mExpr;
-}
-
-bool ConstantExpression::descriptionIsTrivial() const {
-    CHECK(isEvaluated());
-    return mTrivialDescription;
-}
-
 std::string ConstantExpression::value() const {
-    CHECK(isEvaluated());
-    return rawValue(mValueKind);
+    return value(mValueKind);
 }
 
 std::string ConstantExpression::value(ScalarType::Kind castKind) const {
     CHECK(isEvaluated());
-    return rawValue(castKind);
+    return rawValue(castKind) + descriptionSuffix();
 }
 
 std::string ConstantExpression::cppValue() const {
-    CHECK(isEvaluated());
     return cppValue(mValueKind);
 }
 
@@ -399,35 +418,60 @@
     // -(uint64_t)9223372036854775808 == 9223372036854775808 could not
     // be narrowed to int64_t.
     if(castKind == SK(INT64) && (int64_t)mValue == INT64_MIN) {
-        return "static_cast<" +
-               ScalarType(SK(INT64), nullptr /* parent */).getCppStackType()  // "int64_t"
-               + ">(" + literal + "ull)";
+        literal = "static_cast<" +
+                  ScalarType(SK(INT64), nullptr /* parent */).getCppStackType()  // "int64_t"
+                  + ">(" + literal + "ull)";
+    } else {
+        // add suffix if necessary.
+        if (castKind == SK(UINT32) || castKind == SK(UINT64)) literal += "u";
+        if (castKind == SK(UINT64) || castKind == SK(INT64)) literal += "ll";
     }
 
-    // add suffix if necessary.
-    if(castKind == SK(UINT32) || castKind == SK(UINT64)) literal += "u";
-    if(castKind == SK(UINT64) || castKind == SK(INT64)) literal += "ll";
-    return literal;
+    return literal + descriptionSuffix();
 }
 
 std::string ConstantExpression::javaValue() const {
-    CHECK(isEvaluated());
     return javaValue(mValueKind);
 }
 
 std::string ConstantExpression::javaValue(ScalarType::Kind castKind) const {
     CHECK(isEvaluated());
+    std::string literal;
+
     switch(castKind) {
-        case SK(UINT64): return rawValue(SK(INT64)) + "L";
-        case SK(INT64):  return rawValue(SK(INT64)) + "L";
-        case SK(UINT32): return rawValue(SK(INT32));
-        case SK(UINT16): return rawValue(SK(INT16));
-        case SK(UINT8) : return rawValue(SK(INT8));
+        case SK(UINT64):
+            literal = rawValue(SK(INT64)) + "L";
+            break;
+        case SK(INT64):
+            literal = rawValue(SK(INT64)) + "L";
+            break;
+        case SK(UINT32):
+            literal = rawValue(SK(INT32));
+            break;
+        case SK(UINT16):
+            literal = rawValue(SK(INT16));
+            break;
+        case SK(UINT8):
+            literal = rawValue(SK(INT8));
+            break;
         case SK(BOOL)  :
-            return this->cast<bool>() ? "true" : "false";
-        default: break;
+            literal = this->cast<bool>() ? "true" : "false";
+            break;
+        default:
+            literal = rawValue(castKind);
+            break;
     }
-    return rawValue(castKind);
+
+    return literal + descriptionSuffix();
+}
+
+const std::string& ConstantExpression::expression() const {
+    CHECK(isEvaluated());
+    return mExpr;
+}
+
+std::string ConstantExpression::rawValue() const {
+    return rawValue(mValueKind);
 }
 
 std::string ConstantExpression::rawValue(ScalarType::Kind castKind) const {
@@ -435,7 +479,7 @@
 
 #define CASE_STR(__type__) return std::to_string(this->cast<__type__>());
 
-    SWITCH_KIND(castKind, CASE_STR, SHOULD_NOT_REACH(); return 0; );
+    SWITCH_KIND(castKind, CASE_STR, SHOULD_NOT_REACH(); return nullptr; );
 }
 
 template<typename T>
@@ -447,6 +491,17 @@
     SWITCH_KIND(mValueKind, CASE_CAST_T, SHOULD_NOT_REACH(); return 0; );
 }
 
+std::string ConstantExpression::descriptionSuffix() const {
+    CHECK(isEvaluated());
+
+    if (!mTrivialDescription) {
+        CHECK(!mExpr.empty());
+
+        return " /* " + mExpr + " */";
+    }
+    return "";
+}
+
 size_t ConstantExpression::castSizeT() const {
     CHECK(isEvaluated());
     return this->cast<size_t>();
@@ -456,6 +511,10 @@
     return false;
 }
 
+status_t ConstantExpression::validate() const {
+    return OK;
+}
+
 std::vector<ConstantExpression*> ConstantExpression::getConstantExpressions() {
     const auto& constRet = static_cast<const ConstantExpression*>(this)->getConstantExpressions();
     std::vector<ConstantExpression*> ret(constRet.size());
@@ -476,6 +535,18 @@
     return {};
 }
 
+std::vector<Reference<Type>*> ConstantExpression::getTypeReferences() {
+    const auto& constRet = static_cast<const ConstantExpression*>(this)->getTypeReferences();
+    std::vector<Reference<Type>*> ret(constRet.size());
+    std::transform(constRet.begin(), constRet.end(), ret.begin(),
+                   [](const auto* ce) { return const_cast<Reference<Type>*>(ce); });
+    return ret;
+}
+
+std::vector<const Reference<Type>*> ConstantExpression::getTypeReferences() const {
+    return {};
+}
+
 status_t ConstantExpression::recursivePass(const std::function<status_t(ConstantExpression*)>& func,
                                            std::unordered_set<const ConstantExpression*>* visited,
                                            bool processBeforeDependencies) {
@@ -655,6 +726,22 @@
     return {&mReference};
 }
 
+AttributeConstantExpression::AttributeConstantExpression(const Reference<Type>& value,
+                                                         const std::string& fqname,
+                                                         const std::string& tag)
+    : mReference(value), mTag(tag) {
+    mExpr = fqname + "#" + tag;
+}
+
+std::vector<const ConstantExpression*> AttributeConstantExpression::getConstantExpressions() const {
+    // Returns reference instead
+    return {};
+}
+
+std::vector<const Reference<Type>*> AttributeConstantExpression::getTypeReferences() const {
+    return {&mReference};
+}
+
 /*
 
 Evaluating expressions in HIDL language
diff --git a/ConstantExpression.h b/ConstantExpression.h
index 9784885..b770716 100644
--- a/ConstantExpression.h
+++ b/ConstantExpression.h
@@ -60,6 +60,9 @@
                            std::unordered_set<const ConstantExpression*>* visited,
                            bool processBeforeDependencies) const;
 
+    // If this object is in an invalid state.
+    virtual status_t validate() const;
+
     // Evaluates current constant expression
     // Doesn't call recursive evaluation, so must be called after dependencies
     virtual void evaluate() = 0;
@@ -70,6 +73,9 @@
     std::vector<Reference<LocalIdentifier>*> getReferences();
     virtual std::vector<const Reference<LocalIdentifier>*> getReferences() const;
 
+    std::vector<Reference<Type>*> getTypeReferences();
+    virtual std::vector<const Reference<Type>*> getTypeReferences() const;
+
     // Recursive tree pass checkAcyclic return type.
     // Stores cycle end for nice error messages.
     struct CheckAcyclicStatus {
@@ -93,11 +99,11 @@
 
     /* Returns true iff the value has already been evaluated. */
     bool isEvaluated() const;
-    /* Evaluated result in a string form. */
+    /* Evaluated result in a string form with comment if applicable. */
     std::string value() const;
-    /* Evaluated result in a string form. */
+    /* Evaluated result in a string form with comment if applicable. */
     std::string cppValue() const;
-    /* Evaluated result in a string form. */
+    /* Evaluated result in a string form with comment if applicable. */
     std::string javaValue() const;
     /* Evaluated result in a string form, with given contextual kind. */
     std::string value(ScalarType::Kind castKind) const;
@@ -105,10 +111,10 @@
     std::string cppValue(ScalarType::Kind castKind) const;
     /* Evaluated result in a string form, with given contextual kind. */
     std::string javaValue(ScalarType::Kind castKind) const;
-    /* Formatted expression with type. */
-    const std::string& description() const;
-    /* See mTrivialDescription */
-    bool descriptionIsTrivial() const;
+
+    /* The expression representing this value for use in comments when the value is not needed */
+    const std::string& expression() const;
+
     /* Return a ConstantExpression that is 1 plus the original. */
     std::unique_ptr<ConstantExpression> addOne(ScalarType::Kind baseKind);
 
@@ -118,6 +124,14 @@
     // Post parse passes must be proceeded during owner package parsin
     void setPostParseCompleted();
 
+    /*
+     * Helper function for all cpp/javaValue methods.
+     * Returns a plain string (without any prefixes or suffixes, just the
+     * digits) converted from mValue.
+     */
+    std::string rawValue() const;
+    std::string rawValue(ScalarType::Kind castKind) const;
+
    private:
     /* If the result value has been evaluated. */
     bool mIsEvaluated = false;
@@ -133,11 +147,9 @@
     bool mIsPostParseCompleted = false;
 
     /*
-     * Helper function for all cpp/javaValue methods.
-     * Returns a plain string (without any prefixes or suffixes, just the
-     * digits) converted from mValue.
+     * Helper function, gives suffix comment to add to value/cppValue/javaValue
      */
-    std::string rawValue(ScalarType::Kind castKind) const;
+    std::string descriptionSuffix() const;
 
     /*
      * Return the value casted to the given type.
@@ -152,17 +164,16 @@
     friend struct BinaryConstantExpression;
     friend struct TernaryConstantExpression;
     friend struct ReferenceConstantExpression;
+    friend struct AttributeConstantExpression;
 };
 
 struct LiteralConstantExpression : public ConstantExpression {
     LiteralConstantExpression(ScalarType::Kind kind, uint64_t value);
+    LiteralConstantExpression(ScalarType::Kind kind, uint64_t value, const std::string& expr);
     void evaluate() override;
     std::vector<const ConstantExpression*> getConstantExpressions() const override;
 
     static LiteralConstantExpression* tryParse(const std::string& value);
-
-private:
-    LiteralConstantExpression(ScalarType::Kind kind, uint64_t value, const std::string& expr);
 };
 
 struct UnaryConstantExpression : public ConstantExpression {
@@ -211,6 +222,22 @@
     Reference<LocalIdentifier> mReference;
 };
 
+// This constant expression is a compile-time calculatable expression based on another type
+struct AttributeConstantExpression : public ConstantExpression {
+    AttributeConstantExpression(const Reference<Type>& value, const std::string& fqname,
+                                const std::string& tag);
+
+    status_t validate() const override;
+    void evaluate() override;
+
+    std::vector<const ConstantExpression*> getConstantExpressions() const override;
+    std::vector<const Reference<Type>*> getTypeReferences() const override;
+
+   private:
+    Reference<Type> mReference;
+    const std::string mTag;
+};
+
 }  // namespace android
 
 #endif  // CONSTANT_EXPRESSION_H_
diff --git a/Coordinator.cpp b/Coordinator.cpp
index 96c9658..ab693f6 100644
--- a/Coordinator.cpp
+++ b/Coordinator.cpp
@@ -33,7 +33,7 @@
 
 static bool existdir(const char *name) {
     DIR *dir = opendir(name);
-    if (dir == NULL) {
+    if (dir == nullptr) {
         return false;
     }
     closedir(dir);
@@ -262,7 +262,7 @@
 
     *ast = new AST(this, &Hash::getHash(path));
 
-    if (typesAST != NULL) {
+    if (typesAST != nullptr) {
         // If types.hal for this AST's package existed, make it's defined
         // types available to the (about to be parsed) AST right away.
         (*ast)->addImportedAST(typesAST);
@@ -320,7 +320,7 @@
                     fqName.name().c_str());
 
             err = UNKNOWN_ERROR;
-        } else if ((*ast)->containsInterfaces()) {
+        } else if ((*ast)->definesInterfaces()) {
             fprintf(stderr,
                     "ERROR: types.hal file at '%s' declares at least one "
                     "interface type.\n",
@@ -442,7 +442,7 @@
 status_t Coordinator::getPackageInterfaceFiles(
         const FQName &package,
         std::vector<std::string> *fileNames) const {
-    fileNames->clear();
+    if (fileNames) fileNames->clear();
 
     std::string packagePath;
     status_t err =
@@ -450,18 +450,33 @@
     if (err != OK) return err;
 
     const std::string path = makeAbsolute(packagePath);
-    DIR* dir = opendir(path.c_str());
+    std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(path.c_str()), closedir);
 
-    if (dir == NULL) {
+    if (dir == nullptr) {
         fprintf(stderr, "ERROR: Could not open package path %s for package %s:\n%s\n",
                 packagePath.c_str(), package.string().c_str(), path.c_str());
         return -errno;
     }
 
+    if (fileNames == nullptr) {
+        return OK;
+    }
+
     struct dirent *ent;
-    while ((ent = readdir(dir)) != NULL) {
-        if (ent->d_type != DT_REG) {
-            continue;
+    while ((ent = readdir(dir.get())) != nullptr) {
+        // filesystems may not support d_type and return DT_UNKNOWN
+        if (ent->d_type == DT_UNKNOWN) {
+            struct stat sb;
+            const auto filename = packagePath + std::string(ent->d_name);
+            if (stat(filename.c_str(), &sb) == -1) {
+                fprintf(stderr, "ERROR: Could not stat %s\n", filename.c_str());
+                return -errno;
+            }
+            if ((sb.st_mode & S_IFMT) != S_IFREG) {
+                continue;
+            }
+        } else if (ent->d_type != DT_REG) {
+             continue;
         }
 
         const auto suffix = ".hal";
@@ -476,9 +491,6 @@
         fileNames->push_back(std::string(ent->d_name, d_namelen - suffix_len));
     }
 
-    closedir(dir);
-    dir = NULL;
-
     std::sort(fileNames->begin(), fileNames->end(),
               [](const std::string& lhs, const std::string& rhs) -> bool {
                   if (lhs == "types") {
@@ -847,9 +859,17 @@
 
         for (const FQName& importedName : packageInterfaces) {
             HashStatus status = checkHash(importedName);
-            if (status == HashStatus::ERROR) return UNKNOWN_ERROR;
-            if (status == HashStatus::UNFROZEN) {
-                result->insert(importedName);
+            switch (status) {
+                case HashStatus::CHANGED:
+                case HashStatus::ERROR:
+                    return UNKNOWN_ERROR;
+                case HashStatus::FROZEN:
+                    continue;
+                case HashStatus::UNFROZEN:
+                    result->insert(importedName);
+                    continue;
+                default:
+                    LOG(FATAL) << static_cast<uint64_t>(status);
             }
         }
     }
@@ -866,27 +886,30 @@
 
     for (const FQName& currentFQName : packageInterfaces) {
         HashStatus status = checkHash(currentFQName);
-
-        if (status == HashStatus::ERROR) return UNKNOWN_ERROR;
-        if (status == HashStatus::CHANGED) return UNKNOWN_ERROR;
-
-        // frozen interface can only depend on a frozen interface
-        if (status == HashStatus::FROZEN) {
-            std::set<FQName> unfrozenDependencies;
-            err = getUnfrozenDependencies(currentFQName, &unfrozenDependencies);
-            if (err != OK) return err;
-
-            if (!unfrozenDependencies.empty()) {
-                std::cerr << "ERROR: Frozen interface " << currentFQName.string()
-                          << " cannot depend on unfrozen thing(s):" << std::endl;
-                for (const FQName& name : unfrozenDependencies) {
-                    std::cerr << " (unfrozen) " << name.string() << std::endl;
-                }
+        switch (status) {
+            case HashStatus::CHANGED:
+            case HashStatus::ERROR:
                 return UNKNOWN_ERROR;
-            }
-        }
+            case HashStatus::FROZEN: {
+                std::set<FQName> unfrozenDependencies;
+                err = getUnfrozenDependencies(currentFQName, &unfrozenDependencies);
+                if (err != OK) return err;
 
-        // UNFROZEN, ignore
+                if (!unfrozenDependencies.empty()) {
+                    std::cerr << "ERROR: Frozen interface " << currentFQName.string()
+                              << " cannot depend on unfrozen thing(s):" << std::endl;
+                    for (const FQName& name : unfrozenDependencies) {
+                        std::cerr << " (unfrozen) " << name.string() << std::endl;
+                    }
+                    return UNKNOWN_ERROR;
+                }
+            }
+                continue;
+            case HashStatus::UNFROZEN:
+                continue;
+            default:
+                LOG(FATAL) << static_cast<uint64_t>(status);
+        }
     }
 
     return err;
diff --git a/DocComment.cpp b/DocComment.cpp
index 91bc676..eba3fc0 100644
--- a/DocComment.cpp
+++ b/DocComment.cpp
@@ -16,16 +16,18 @@
 
 #include "DocComment.h"
 
+#include <android-base/strings.h>
 #include <hidl-util/StringHelper.h>
 
 #include <cctype>
 #include <sstream>
 
+#include <iostream>
+
 namespace android {
 
 DocComment::DocComment(const std::string& comment) {
-    std::vector<std::string> lines;
-    StringHelper::SplitString(comment, '\n', &lines);
+    std::vector<std::string> lines = base::Split(base::Trim(comment), "\n");
 
     bool foundFirstLine = false;
 
@@ -40,17 +42,12 @@
         if (idx < line.size() && line[idx] == '*') idx++;
         if (idx < line.size() && line[idx] == ' ') idx++;
 
-        if (idx < line.size()) {
-            foundFirstLine = true;
-        }
+        bool isEmptyLine = idx == line.size();
 
+        foundFirstLine = foundFirstLine || !isEmptyLine;
         if (!foundFirstLine) continue;
 
-        is << line.substr(idx);
-
-        if (l + 1 < lines.size()) {
-            is << "\n";
-        }
+        is << line.substr(idx) << "\n";
     }
 
     mComment = is.str();
diff --git a/DocComment.h b/DocComment.h
index e0677eb..b397326 100644
--- a/DocComment.h
+++ b/DocComment.h
@@ -43,7 +43,10 @@
         }
     }
 
-   private:
+  protected:
+    const DocComment* getDocComment() const { return mDocComment; }
+
+  private:
     const DocComment* mDocComment = nullptr;
 };
 
diff --git a/EnumType.cpp b/EnumType.cpp
index ead64ba..3268264 100644
--- a/EnumType.cpp
+++ b/EnumType.cpp
@@ -49,6 +49,14 @@
     }
 }
 
+size_t EnumType::numValueNames() const {
+    size_t count = 0;
+    for (const auto it : typeChain()) {
+        count += it->values().size();
+    }
+    return count;
+}
+
 void EnumType::addValue(EnumValue* value) {
     CHECK(value != nullptr);
     mValues.push_back(value);
@@ -168,8 +176,8 @@
     return mStorageType->resolveToScalarType()->getJavaSuffix();
 }
 
-std::string EnumType::getJavaWrapperType() const {
-    return mStorageType->resolveToScalarType()->getJavaWrapperType();
+std::string EnumType::getJavaTypeClass() const {
+    return mStorageType->resolveToScalarType()->getJavaTypeClass();
 }
 
 std::string EnumType::getVtsType() const {
@@ -185,8 +193,8 @@
     return resolveToScalarType()->getJavaType(forInitializer);
 }
 
-std::string EnumType::getBitfieldJavaWrapperType() const {
-    return resolveToScalarType()->getJavaWrapperType();
+std::string EnumType::getBitfieldJavaTypeClass() const {
+    return resolveToScalarType()->getJavaTypeClass();
 }
 
 LocalIdentifier *EnumType::lookupIdentifier(const std::string &name) const {
@@ -210,7 +218,7 @@
         bool isReader,
         ErrorMode mode) const {
     const ScalarType *scalarType = mStorageType->resolveToScalarType();
-    CHECK(scalarType != NULL);
+    CHECK(scalarType != nullptr);
 
     scalarType->emitReaderWriterWithCast(
             out,
@@ -260,16 +268,7 @@
 
             std::string value = entry->cppValue(scalarType->getKind());
             CHECK(!value.empty()); // use autofilled values for c++.
-            out << " = " << value;
-
-            out << ",";
-
-            std::string comment = entry->comment();
-            if (!comment.empty()) {
-                out << " // " << comment;
-            }
-
-            out << "\n";
+            out << " = " << value << ",\n";
         }
     }
 
@@ -290,27 +289,17 @@
         elementCount += type->mValues.size();
     }
 
-    out << "template<> struct hidl_enum_iterator<" << getCppStackType() << ">\n";
+    out << "template<> constexpr std::array<" << getCppStackType() << ", " << elementCount
+        << "> hidl_enum_values<" << getCppStackType() << "> = ";
     out.block([&] {
-        out << "const " << getCppStackType() << "* begin() { return static_begin(); }\n";
-        out << "const " << getCppStackType() << "* end() { return begin() + " << elementCount
-            << "; }\n";
-        out << "private:\n";
-        out << "static const " << getCppStackType() << "* static_begin() ";
-        out.block([&] {
-            out << "static const " << getCppStackType() << " kVals[" << elementCount << "] ";
-            out.block([&] {
-                auto enumerators = typeChain();
-                std::reverse(enumerators.begin(), enumerators.end());
-                for (const auto* type : enumerators) {
-                    for (const auto* enumValue : type->mValues) {
-                        out << fullName() << "::" << enumValue->name() << ",\n";
-                    }
-                }
-            }) << ";\n";
-            out << "return &kVals[0];\n";
-        });
-    }) << ";\n\n";
+        auto enumerators = typeChain();
+        std::reverse(enumerators.begin(), enumerators.end());
+        for (const auto* type : enumerators) {
+            for (const auto* enumValue : type->mValues) {
+                out << fullName() << "::" << enumValue->name() << ",\n";
+            }
+        }
+    }) << ";\n";
 }
 
 void EnumType::emitEnumBitwiseOperator(
@@ -356,7 +345,7 @@
         out << ");\n";
     });
 
-    out << "}\n\n";
+    out << "}\n";
 }
 
 void EnumType::emitBitFieldBitwiseAssignmentOperator(
@@ -375,20 +364,27 @@
         out << "return v;\n";
     });
 
-    out << "}\n\n";
+    out << "}\n";
 }
 
 void EnumType::emitGlobalTypeDeclarations(Formatter& out) const {
     out << "namespace android {\n";
     out << "namespace hardware {\n";
+    out << "namespace details {\n";
 
     emitIteratorDeclaration(out);
 
+    out << "}  // namespace details\n";
     out << "}  // namespace hardware\n";
-    out << "}  // namespace android\n";
+    out << "}  // namespace android\n\n";
 }
 
 void EnumType::emitPackageTypeDeclarations(Formatter& out) const {
+    out << "template<typename>\n"
+        << "static inline std::string toString(" << resolveToScalarType()->getCppArgumentType()
+        << " o);\n";
+    out << "static inline std::string toString(" << getCppArgumentType() << " o);\n\n";
+
     emitEnumBitwiseOperator(out, true  /* lhsIsEnum */, true  /* rhsIsEnum */, "|");
     emitEnumBitwiseOperator(out, false /* lhsIsEnum */, true  /* rhsIsEnum */, "|");
     emitEnumBitwiseOperator(out, true  /* lhsIsEnum */, false /* rhsIsEnum */, "|");
@@ -399,12 +395,13 @@
     emitBitFieldBitwiseAssignmentOperator(out, "|");
     emitBitFieldBitwiseAssignmentOperator(out, "&");
 
-    const ScalarType *scalarType = mStorageType->resolveToScalarType();
-    CHECK(scalarType != NULL);
+    out.endl();
+}
 
-    out << "template<typename>\n"
-        << "static inline std::string toString(" << resolveToScalarType()->getCppArgumentType()
-        << " o);\n";
+void EnumType::emitPackageTypeHeaderDefinitions(Formatter& out) const {
+    const ScalarType *scalarType = mStorageType->resolveToScalarType();
+    CHECK(scalarType != nullptr);
+
     out << "template<>\n"
         << "inline std::string toString<" << getCppStackType() << ">("
         << scalarType->getCppArgumentType() << " o) ";
@@ -455,7 +452,7 @@
 
 void EnumType::emitJavaTypeDeclarations(Formatter& out, bool atTopLevel) const {
     const ScalarType *scalarType = mStorageType->resolveToScalarType();
-    CHECK(scalarType != NULL);
+    CHECK(scalarType != nullptr);
 
     out << "public "
         << (atTopLevel ? "" : "static ")
@@ -485,16 +482,7 @@
             // javaValue will make the number signed.
             std::string value = entry->javaValue(scalarType->getKind());
             CHECK(!value.empty()); // use autofilled values for java.
-            out << value;
-
-            out << ";";
-
-            std::string comment = entry->comment();
-            if (!comment.empty()) {
-                out << " // " << comment;
-            }
-
-            out << "\n";
+            out << value << ";\n";
         }
     }
 
@@ -512,7 +500,6 @@
     }).endl();
 
     auto bitfieldType = getBitfieldJavaType(false /* forInitializer */);
-    auto bitfieldWrapperType = getBitfieldJavaWrapperType();
     out << "\n"
         << "public static final String dumpBitfield("
         << bitfieldType << " o) ";
@@ -563,7 +550,7 @@
             out << "scalar_value: {\n";
             out.indent();
             // use autofilled values for vts.
-            std::string value = entry->value(scalarType->getKind());
+            std::string value = entry->rawValue(scalarType->getKind());
             CHECK(!value.empty());
             out << mStorageType->resolveToScalarType()->getVtsScalarType()
                 << ": "
@@ -704,16 +691,7 @@
                 // javaValue will make the number signed.
                 std::string value = entry->javaValue(scalarType->getKind());
                 CHECK(!value.empty()); // use autofilled values for java.
-                out << value;
-
-                out << ";";
-
-                std::string comment = entry->comment();
-                if (!comment.empty()) {
-                    out << " // " << comment;
-                }
-
-                out << "\n";
+                out << value << ";\n";
             }
         }
 
@@ -742,16 +720,7 @@
 
             std::string value = entry->cppValue(scalarType->getKind());
             CHECK(!value.empty()); // use autofilled values for c++.
-            out << " = " << value;
-
-            out << ",";
-
-            std::string comment = entry->comment();
-            if (!comment.empty()) {
-                out << " // " << comment;
-            }
-
-            out << "\n";
+            out << " = " << value << ",\n";
         }
     }
 
@@ -774,9 +743,9 @@
     return mName;
 }
 
-std::string EnumValue::value(ScalarType::Kind castKind) const {
+std::string EnumValue::rawValue(ScalarType::Kind castKind) const {
     CHECK(mValue != nullptr);
-    return mValue->value(castKind);
+    return mValue->rawValue(castKind);
 }
 
 std::string EnumValue::cppValue(ScalarType::Kind castKind) const {
@@ -788,12 +757,6 @@
     return mValue->javaValue(castKind);
 }
 
-std::string EnumValue::comment() const {
-    CHECK(mValue != nullptr);
-    if (mValue->descriptionIsTrivial()) return "";
-    return mValue->description();
-}
-
 ConstantExpression *EnumValue::constExpr() const {
     CHECK(mValue != nullptr);
     return mValue;
@@ -866,8 +829,8 @@
     return resolveToScalarType()->getJavaSuffix();
 }
 
-std::string BitFieldType::getJavaWrapperType() const {
-    return getElementEnumType()->getBitfieldJavaWrapperType();
+std::string BitFieldType::getJavaTypeClass() const {
+    return getElementEnumType()->getBitfieldJavaTypeClass();
 }
 
 std::string BitFieldType::getVtsType() const {
diff --git a/EnumType.h b/EnumType.h
index eef4eb2..8ae7c36 100644
--- a/EnumType.h
+++ b/EnumType.h
@@ -39,6 +39,9 @@
 
     void forEachValueFromRoot(const std::function<void(EnumValue*)> f) const;
 
+    // This is the number of distinct keys (even if they have colliding values)
+    size_t numValueNames() const;
+
     LocalIdentifier *lookupIdentifier(const std::string &name) const override;
 
     bool isElidableType() const override;
@@ -52,16 +55,15 @@
                            bool specifyNamespaces) const override;
 
     std::string getJavaType(bool forInitializer) const override;
+    std::string getJavaTypeClass() const override;
 
     std::string getJavaSuffix() const override;
 
-    std::string getJavaWrapperType() const override;
-
     std::string getVtsType() const override;
 
     std::string getBitfieldCppType(StorageMode mode, bool specifyNamespaces = true) const;
     std::string getBitfieldJavaType(bool forInitializer = false) const;
-    std::string getBitfieldJavaWrapperType() const;
+    std::string getBitfieldJavaTypeClass() const;
 
     // Return the type that corresponds to bitfield<T>.
     const BitFieldType* getBitfieldType() const;
@@ -94,6 +96,7 @@
     void emitTypeForwardDeclaration(Formatter& out) const override;
     void emitGlobalTypeDeclarations(Formatter& out) const override;
     void emitPackageTypeDeclarations(Formatter& out) const override;
+    void emitPackageTypeHeaderDefinitions(Formatter& out) const override;
 
     void emitJavaTypeDeclarations(Formatter& out, bool atTopLevel) const override;
 
@@ -141,10 +144,9 @@
     EnumValue(const char* name, ConstantExpression* value, const Location& location);
 
     std::string name() const;
-    std::string value(ScalarType::Kind castKind) const;
+    std::string rawValue(ScalarType::Kind castKind) const;
     std::string cppValue(ScalarType::Kind castKind) const;
     std::string javaValue(ScalarType::Kind castKind) const;
-    std::string comment() const;
     void autofill(const EnumType* prevType, EnumValue* prevValue, const ScalarType* type);
     ConstantExpression* constExpr() const override;
 
@@ -183,11 +185,10 @@
                            bool specifyNamespaces) const override;
 
     std::string getJavaType(bool forInitializer) const override;
+    std::string getJavaTypeClass() const override;
 
     std::string getJavaSuffix() const override;
 
-    std::string getJavaWrapperType() const override;
-
     std::string getVtsType() const override;
 
     const EnumType* getEnumType() const;
diff --git a/FmqType.h b/FmqType.h
index 77ca150..747b10a 100644
--- a/FmqType.h
+++ b/FmqType.h
@@ -27,7 +27,7 @@
 
     std::string fullName() const;
 
-    std::string templatedTypeName() const;
+    std::string templatedTypeName() const override;
 
     std::string getCppType(
             StorageMode mode,
diff --git a/HandleType.cpp b/HandleType.cpp
index 7b6545d..56660a6 100644
--- a/HandleType.cpp
+++ b/HandleType.cpp
@@ -51,6 +51,14 @@
     }
 }
 
+std::string HandleType::getJavaType(bool /* forInitializer */) const {
+    return "android.os.NativeHandle";
+}
+
+std::string HandleType::getJavaSuffix() const {
+    return "NativeHandle";
+}
+
 std::string HandleType::getVtsType() const {
     return "TYPE_HANDLE";
 }
@@ -89,15 +97,11 @@
     }
 }
 
-bool HandleType::useNameInEmitReaderWriterEmbedded(bool isReader) const {
-    return !isReader;
-}
-
 void HandleType::emitReaderWriterEmbedded(
         Formatter &out,
         size_t /* depth */,
         const std::string &name,
-        const std::string &sanitizedName,
+        const std::string & /* sanitizedName */,
         bool nameIsPointer,
         const std::string &parcelObj,
         bool parcelObjIsPointer,
@@ -105,52 +109,65 @@
         ErrorMode mode,
         const std::string &parentName,
         const std::string &offsetText) const {
+    emitReaderWriterEmbeddedForTypeName(
+            out,
+            name,
+            nameIsPointer,
+            parcelObj,
+            parcelObjIsPointer,
+            isReader,
+            mode,
+            parentName,
+            offsetText,
+            "::android::hardware::hidl_handle",
+            "" /* childName */,
+            "::android::hardware");
+}
+
+void HandleType::emitJavaFieldInitializer(
+        Formatter &out, const std::string &fieldName) const {
+    const std::string fieldDeclaration = getJavaType(false) + " " + fieldName;
+    emitJavaFieldDefaultInitialValue(out, fieldDeclaration);
+}
+
+void HandleType::emitJavaFieldDefaultInitialValue(
+        Formatter &out, const std::string &declaredFieldName) const {
+    out << declaredFieldName
+        << " = new "
+        << getJavaType(true)
+        << "();\n";
+}
+
+void HandleType::emitJavaFieldReaderWriter(
+        Formatter &out,
+        size_t /* depth */,
+        const std::string &parcelName,
+        const std::string &blobName,
+        const std::string &fieldName,
+        const std::string &offset,
+        bool isReader) const {
     if (isReader) {
-        const std::string ptrName = "_hidl_" + sanitizedName  + "_ptr";
+        out << fieldName
+            << " = "
+            << parcelName
+            << ".readEmbeddedNativeHandle(\n";
 
-        out << "const native_handle_t *"
-            << ptrName << ";\n"
-            << "_hidl_err = "
-            << parcelObj
-            << (parcelObjIsPointer ? "->" : ".")
-            << "readNullableEmbeddedNativeHandle(\n";
+        out.indent(2, [&] {
+            out << blobName
+                << ".handle(),\n"
+                << offset
+                << " + 0 /* offsetof(hidl_handle, mHandle) */);\n\n";
+        });
 
-        out.indent();
-        out.indent();
-
-        out << parentName
-            << ",\n"
-            << offsetText
-            << ",\n"
-            << "&" << ptrName
-            << "\n"
-            << ");\n\n";
-
-        out.unindent();
-        out.unindent();
-
-        handleError(out, mode);
-    } else {
-        out << "_hidl_err = "
-            << parcelObj
-            << (parcelObjIsPointer ? "->" : ".")
-            << "writeEmbeddedNativeHandle(\n";
-
-        out.indent();
-        out.indent();
-
-        out << (nameIsPointer ? ("*" + name) : name)
-            << ",\n"
-            << parentName
-            << ",\n"
-            << offsetText
-            << ");\n\n";
-
-        out.unindent();
-        out.unindent();
-
-        handleError(out, mode);
+        return;
     }
+
+    out << blobName
+        << ".putNativeHandle("
+        << offset
+        << ", "
+        << fieldName
+        << ");\n";
 }
 
 bool HandleType::needsEmbeddedReadWrite() const {
@@ -158,7 +175,7 @@
 }
 
 bool HandleType::deepIsJavaCompatible(std::unordered_set<const Type*>* /* visited */) const {
-    return false;
+    return true;
 }
 
 static HidlTypeAssertion assertion("hidl_handle", 16 /* size */);
diff --git a/HandleType.h b/HandleType.h
index feaddb0..8d3a984 100644
--- a/HandleType.h
+++ b/HandleType.h
@@ -33,6 +33,10 @@
             StorageMode mode,
             bool specifyNamespaces) const override;
 
+    std::string getJavaType(bool forInitializer) const override;
+
+    std::string getJavaSuffix() const override;
+
     std::string getVtsType() const override;
 
     void emitReaderWriter(
@@ -56,12 +60,25 @@
             const std::string &parentName,
             const std::string &offsetText) const override;
 
+    void emitJavaFieldInitializer(
+            Formatter &out, const std::string &fieldName) const override;
+
+    void emitJavaFieldDefaultInitialValue(
+            Formatter &out, const std::string &declaredFieldName) const override;
+
+    void emitJavaFieldReaderWriter(
+            Formatter &out,
+            size_t depth,
+            const std::string &parcelName,
+            const std::string &blobName,
+            const std::string &fieldName,
+            const std::string &offset,
+            bool isReader) const override;
+
     bool needsEmbeddedReadWrite() const override;
 
     bool deepIsJavaCompatible(std::unordered_set<const Type*>* visited) const override;
 
-    bool useNameInEmitReaderWriterEmbedded(bool isReader) const override;
-
     void getAlignmentAndSize(size_t *align, size_t *size) const override;
 
     void emitVtsTypeDeclarations(Formatter& out) const override;
diff --git a/HidlTypeAssertion.cpp b/HidlTypeAssertion.cpp
index 4522f15..6e4822a 100644
--- a/HidlTypeAssertion.cpp
+++ b/HidlTypeAssertion.cpp
@@ -46,7 +46,7 @@
                 return a.first < b.first;
             });
 
-    for (auto entry : registry()) {
+    for (const auto& entry : registry()) {
         out << "static_assert(sizeof(::android::hardware::"
             << entry.first
             << ") == "
diff --git a/Interface.cpp b/Interface.cpp
index 1c66b31..b0ffa34 100644
--- a/Interface.cpp
+++ b/Interface.cpp
@@ -68,6 +68,9 @@
     LAST_HIDL_TRANSACTION   = 0x0fffffff,
 };
 
+const std::unique_ptr<ConstantExpression> Interface::FLAG_ONE_WAY =
+    std::make_unique<LiteralConstantExpression>(ScalarType::KIND_UINT32, 0x01, "oneway");
+
 Interface::Interface(const char* localName, const FQName& fullName, const Location& location,
                      Scope* parent, const Reference<Type>& superType, const Hash* fileHash)
     : Scope(localName, fullName, location, parent), mSuperType(superType), mFileHash(fileHash) {}
@@ -141,7 +144,7 @@
             {
                 {IMPL_INTERFACE,
                     [](auto &out) {
-                        out << "return true;";
+                        out << "return true;\n";
                     }
                 },
                 {IMPL_PROXY,
@@ -171,16 +174,16 @@
                 {IMPL_PROXY,
                     [](auto &out) {
                         out << "std::unique_lock<std::mutex> lock(_hidl_mMutex);\n"
-                            << "for (auto it = _hidl_mDeathRecipients.begin();"
-                            << "it != _hidl_mDeathRecipients.end();"
+                            << "for (auto it = _hidl_mDeathRecipients.rbegin();"
+                            << "it != _hidl_mDeathRecipients.rend();"
                             << "++it) {\n";
                         out.indent([&] {
                             out.sIf("(*it)->getRecipient() == recipient", [&] {
                                 out << "::android::status_t status = remote()->unlinkToDeath(*it);\n"
-                                    << "_hidl_mDeathRecipients.erase(it);\n"
+                                    << "_hidl_mDeathRecipients.erase(it.base()-1);\n"
                                     << "return status == ::android::OK;\n";
                                 });
-                            });
+                            }).endl();
                         out << "}\n";
                         out << "return false;\n";
                     }
@@ -212,10 +215,10 @@
             HIDL_SYSPROPS_CHANGED_TRANSACTION,
             { { IMPL_INTERFACE, [](auto &out) {
                 out << "::android::report_sysprop_change();\n";
-                out << "return ::android::hardware::Void();";
+                out << "return ::android::hardware::Void();\n";
             } } }, /*cppImpl */
             { { IMPL_INTERFACE, [](auto &out) { /* javaImpl */
-                out << "android.os.HwBinder.enableInstrumentation();";
+                out << "android.os.HwBinder.enableInstrumentation();\n";
             } } } /*javaImpl */
     );
     return true;
@@ -281,7 +284,7 @@
                     out << ",\n";
                 out << chain[i]->fullJavaName() << ".kInterfaceName";
             }
-            out << "));";
+            out << "));\n";
             out.unindent(); out.unindent();
         } } } /* javaImpl */
     );
@@ -353,7 +356,7 @@
             out << "_hidl_cb("
                 << fullName()
                 << "::descriptor);\n"
-                << "return ::android::hardware::Void();";
+                << "return ::android::hardware::Void();\n";
         } } }, /* cppImpl */
         { { IMPL_INTERFACE, [this](auto &out) {
             out << "return "
@@ -409,7 +412,7 @@
                 << "info.pid = android.os.HidlSupport.getPidIfSharable();\n"
                 << "info.ptr = 0;\n"
                 << "info.arch = android.hidl.base.V1_0.DebugInfo.Architecture.UNKNOWN;\n"
-                << "return info;";
+                << "return info;\n";
         } } } /* javaImpl */
     );
 
@@ -421,20 +424,18 @@
         return false;
     }
 
-    method->fillImplementation(
-        HIDL_DEBUG_TRANSACTION,
-        {
-            {IMPL_INTERFACE,
-                [](auto &out) {
-                    out << "(void)fd;\n"
-                        << "(void)options;\n"
-                        << "return ::android::hardware::Void();";
-                }
-            },
-        }, /* cppImpl */
-        {
-            /* unused, as the debug method is hidden from Java */
-        } /* javaImpl */
+    method->fillImplementation(HIDL_DEBUG_TRANSACTION,
+                               {
+                                   {IMPL_INTERFACE,
+                                    [](auto& out) {
+                                        out << "(void)fd;\n"
+                                            << "(void)options;\n"
+                                            << "return ::android::hardware::Void();\n";
+                                    }},
+                               }, /* cppImpl */
+                               {
+                                   {IMPL_INTERFACE, [](auto& out) { out << "return;\n"; }},
+                               } /* javaImpl */
     );
 
     return true;
@@ -665,10 +666,6 @@
     return true;
 }
 
-bool Interface::isBinder() const {
-    return true;
-}
-
 const std::vector<Method *> &Interface::userDefinedMethods() const {
     return mUserMethods;
 }
@@ -820,13 +817,7 @@
         out << "} else {\n";
         out.indent();
         out << "::android::sp<::android::hardware::IBinder> _hidl_binder = "
-            << "::android::hardware::toBinder<\n";
-        out.indent(2, [&] {
-            out << fqName().cppName()
-                << ">("
-                << name
-                << ");\n";
-        });
+            << "::android::hardware::getOrCreateCachedBinder(" << name << ".get());\n";
         out << "if (_hidl_binder.get() != nullptr) {\n";
         out.indent([&] {
             out << "_hidl_err = "
@@ -848,6 +839,12 @@
 void Interface::emitPackageTypeDeclarations(Formatter& out) const {
     Scope::emitPackageTypeDeclarations(out);
 
+    out << "static inline std::string toString(" << getCppArgumentType() << " o);\n\n";
+}
+
+void Interface::emitPackageTypeHeaderDefinitions(Formatter& out) const {
+    Scope::emitPackageTypeHeaderDefinitions(out);
+
     out << "static inline std::string toString(" << getCppArgumentType() << " o) ";
 
     out.block([&] {
@@ -899,7 +896,7 @@
     }
 }
 
-void Interface::emitVtsMethodDeclaration(Formatter& out) const {
+void Interface::emitVtsMethodDeclaration(Formatter& out, bool isInherited) const {
     for (const auto &method : methods()) {
         if (method->isHidlReserved()) {
             continue;
@@ -908,10 +905,12 @@
         out << "api: {\n";
         out.indent();
         out << "name: \"" << method->name() << "\"\n";
+        out << "is_inherited: " << (isInherited ? "true" : "false") << "\n";
         // Generate declaration for each return value.
         for (const auto &result : method->results()) {
             out << "return_type_hidl: {\n";
             out.indent();
+            out << "name: \"" << result->name() << "\"\n";
             result->type().emitVtsAttributeType(out);
             out.unindent();
             out << "}\n";
@@ -920,6 +919,7 @@
         for (const auto &arg : method->args()) {
             out << "arg: {\n";
             out.indent();
+            out << "name: \"" << arg->name() << "\"\n";
             arg->type().emitVtsAttributeType(out);
             out.unindent();
             out << "}\n";
@@ -986,7 +986,7 @@
         }
     }
 
-    return Scope::isJavaCompatible(visited);
+    return Scope::deepIsJavaCompatible(visited);
 }
 
 bool Interface::isNeverStrongReference() const {
diff --git a/Interface.h b/Interface.h
index 0c94c0c..aa3d18f 100644
--- a/Interface.h
+++ b/Interface.h
@@ -22,6 +22,7 @@
 
 #include <hidl-hash/Hash.h>
 
+#include "ConstantExpression.h"
 #include "Reference.h"
 #include "Scope.h"
 
@@ -31,10 +32,7 @@
 struct InterfaceAndMethod;
 
 struct Interface : public Scope {
-    enum {
-        /////////////////// Flag(s) - DO NOT CHANGE
-        FLAG_ONEWAY = 0x00000001,
-    };
+    const static std::unique_ptr<ConstantExpression> FLAG_ONE_WAY;
 
     Interface(const char* localName, const FQName& fullName, const Location& location,
               Scope* parent, const Reference<Type>& superType, const Hash* fileHash);
@@ -46,7 +44,6 @@
 
     bool isElidableType() const override;
     bool isInterface() const override;
-    bool isBinder() const override;
     bool isIBase() const { return fqName() == gIBaseFqName; }
     std::string typeName() const override;
 
@@ -115,6 +112,7 @@
             ErrorMode mode) const override;
 
     void emitPackageTypeDeclarations(Formatter& out) const override;
+    void emitPackageTypeHeaderDefinitions(Formatter& out) const override;
     void emitTypeDefinitions(Formatter& out, const std::string& prefix) const override;
 
     void getAlignmentAndSize(size_t* align, size_t* size) const override;
@@ -127,7 +125,7 @@
     void emitVtsAttributeType(Formatter& out) const override;
 
     void emitVtsAttributeDeclaration(Formatter& out) const;
-    void emitVtsMethodDeclaration(Formatter& out) const;
+    void emitVtsMethodDeclaration(Formatter& out, bool isInherited) const;
 
     bool hasOnewayMethods() const;
 
diff --git a/Method.cpp b/Method.cpp
index 64a9a65..482bae5 100644
--- a/Method.cpp
+++ b/Method.cpp
@@ -139,10 +139,6 @@
     }
 }
 
-bool Method::isHiddenFromJava() const {
-    return isHidlReserved() && name() == "debug";
-}
-
 bool Method::overridesCppImpl(MethodImplType type) const {
     CHECK(mIsHidlReserved);
     return mCppImpl.find(type) != mCppImpl.end();
@@ -153,8 +149,10 @@
     return mJavaImpl.find(type) != mJavaImpl.end();
 }
 
-Method *Method::copySignature() const {
-    return new Method(mName.c_str(), mArgs, mResults, mOneway, mAnnotations, Location());
+Method* Method::copySignature() const {
+    Method* method = new Method(mName.c_str(), mArgs, mResults, mOneway, mAnnotations, location());
+    method->setDocComment(getDocComment());
+    return method;
 }
 
 void Method::setSerialId(size_t serial) {
@@ -257,10 +255,6 @@
 }
 
 bool Method::deepIsJavaCompatible(std::unordered_set<const Type*>* visited) const {
-    if (isHiddenFromJava()) {
-        return true;
-    }
-
     if (!std::all_of(mArgs->begin(), mArgs->end(),
                      [&](const auto* arg) { return (*arg)->isJavaCompatible(visited); })) {
         return false;
diff --git a/Method.h b/Method.h
index 711f087..22dd08f 100644
--- a/Method.h
+++ b/Method.h
@@ -65,7 +65,6 @@
     void cppImpl(MethodImplType type, Formatter &out) const;
     void javaImpl(MethodImplType type, Formatter &out) const;
     bool isHidlReserved() const { return mIsHidlReserved; }
-    bool isHiddenFromJava() const;
     const std::vector<Annotation *> &annotations() const;
 
     std::vector<Reference<Type>*> getReferences();
diff --git a/PointerType.cpp b/PointerType.cpp
index cdf4a99..2b8e5a4 100644
--- a/PointerType.cpp
+++ b/PointerType.cpp
@@ -31,8 +31,8 @@
     return true;
 }
 
-std::string PointerType::getCppType(StorageMode /* mode */,
-                                   bool /* specifyNamespaces */) const {
+std::string PointerType::getCppType(StorageMode /*mode*/,
+                                    bool /*specifyNamespaces*/) const {
     return "void*";
 }
 
@@ -47,16 +47,36 @@
 void PointerType::emitReaderWriter(
         Formatter& out,
         const std::string& name,
-        const std::string& /* parcelObj */,
-        bool /* parcelObjIsPointer */,
-        bool /* isReader */,
-        ErrorMode /* mode */) const {
+        const std::string& /*parcelObj*/,
+        bool /*parcelObjIsPointer*/,
+        bool /*isReader*/,
+        ErrorMode /*mode*/) const {
     out << "(void)" << name << ";\n";
-    out << "LOG_ALWAYS_FATAL(\"Pointer is only supported in passthrough mode\");\n";
+    out << "LOG_ALWAYS_FATAL(\"Pointer is only supported in passthrough mode\");\n\n";
+}
+
+void PointerType::emitReaderWriterEmbedded(
+        Formatter& out,
+        size_t /*depth*/,
+        const std::string& name,
+        const std::string& /*sanitizedName*/,
+        bool /*nameIsPointer*/,
+        const std::string& parcelObj,
+        bool parcelObjIsPointer,
+        bool isReader,
+        ErrorMode mode,
+        const std::string& parentName,
+        const std::string& offsetText) const {
+    out << "(void) " << parcelObj << ";\n";
+    out << "(void) " << parentName << ";\n";
+    out << "(void) (" << offsetText << ");\n";
+
+    // same exact code
+    emitReaderWriter(out, name, parcelObj, parcelObjIsPointer, isReader, mode);
 }
 
 bool PointerType::needsEmbeddedReadWrite() const {
-    return false;
+    return true;
 }
 
 bool PointerType::resultNeedsDeref() const {
diff --git a/PointerType.h b/PointerType.h
index 30c4a32..a5004c2 100644
--- a/PointerType.h
+++ b/PointerType.h
@@ -45,6 +45,19 @@
             bool isReader,
             ErrorMode mode) const override;
 
+    void emitReaderWriterEmbedded(
+            Formatter &out,
+            size_t depth,
+            const std::string &name,
+            const std::string &sanitizedName,
+            bool nameIsPointer,
+            const std::string &parcelObj,
+            bool parcelObjIsPointer,
+            bool isReader,
+            ErrorMode mode,
+            const std::string &parentName,
+            const std::string &offsetText) const override;
+
     bool needsEmbeddedReadWrite() const override;
     bool resultNeedsDeref() const override;
 
diff --git a/README.md b/README.md
index 4859cae..5743edc 100644
--- a/README.md
+++ b/README.md
@@ -1,33 +1,58 @@
-# hidl-gen user guide
+# hidl-gen
+
+Full documentation can be found here:
+<https://source.android.com/devices/architecture/hidl/>
+
+hidl-gen is a compiler for the HIDL (HAL Interface Design Language) which generates
+C++ and Java endpoints for RPC mechanisms. The main userspace libraries which this
+compiler uses can be found at system/libhidl.
 
 ## 1. Build
 
 ```
-croot
-make hidl-gen
+m hidl-gen
 ```
 
 ## 2. Run
 
+Note that options for hidl-gen expected to be invoked by the build system
+are marked with 'internal' in the help menu.
+
 ```
-hidl-gen -o output-path -L language (-r interface-root) fqname
+hidl-gen -h
 
-output-path: directory to store the output files.
-language: output file for given language. e.g.c++, vts..
-
-fqname: fully qualified name of the input files.
-For singe file input, follow the format: package@version::fileName
-For directory input, follow the format: package@version
-
-interface-root(optional): prefix and root path for fqname.
-If not set, use the default prefix: android.hardware and default root path
-defined in $TOP.
-
-examples:
-
-croot
-hidl-gen -o output -L c++ -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport android.hardware.nfc@1.0::INfc.hal
-hidl-gen -o output -L vts -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport android.hardware.nfc@1.0
-hidl-gen -o test -L c++ -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport android.hardware.nfc@1.0
-hidl-gen -L hash -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport android.hardware.nfc@1.0
+hidl-gen -o output -L c++-impl -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport android.hardware.nfc@1.0
 ```
+
+Some defaults for package roots are also provided
+
+```
+hidl-gen -o output -L c++-impl android.hardware.nfc@1.0
+hidl-gen -o output -L vts android.hardware.nfc@1.0
+hidl-gen -L hash android.hardware.nfc@1.0
+```
+
+Example command for vendor project
+
+```
+hidl-gen -L c++-impl -r vendor.foo:vendor/foo/interfaces vendor.foo.nfc@1.0
+```
+
+See update-makefiles-helper.sh and update-all-google-makefiles.sh for examples
+of how to generate HIDL makefiles (using the -Landroidbp option).
+
+# c2hal
+
+This is a helper tool to convert C headers to valid .hal files.
+
+```
+m c2hal && c2hal -h
+```
+
+# docs
+
+This tool generates html documentation for hal interfaces.
+
+```
+m hidl-doc && hidl-doc -h
+```
\ No newline at end of file
diff --git a/Reference.h b/Reference.h
index 7ddc889..ba39ac4 100644
--- a/Reference.h
+++ b/Reference.h
@@ -113,7 +113,7 @@
         // Valid only while not resolved to prevent confusion when
         // ref.hasLookupFqName() is false while ref,get()->fqName is valid.
         CHECK(!isResolved());
-        return mFqName.isValid();
+        return mFqName != FQName();
     }
 
     template <class OtherT>
diff --git a/ScalarType.cpp b/ScalarType.cpp
index 2e2e7b2..dc02b69 100644
--- a/ScalarType.cpp
+++ b/ScalarType.cpp
@@ -83,7 +83,7 @@
     return kName[mKind];
 }
 
-std::string ScalarType::getJavaWrapperType() const {
+std::string ScalarType::getJavaTypeClass() const {
     static const char *const kName[] = {
         "Boolean",
         "Byte",
@@ -225,7 +225,7 @@
         case KIND_INT16:    // fallthrough
         case KIND_UINT16: {
             // Because Byte and Short doesn't have toHexString, we have to use Integer.toHexString.
-            out << "Integer.toHexString(" << getJavaWrapperType() << ".toUnsignedInt(("
+            out << "Integer.toHexString(" << getJavaTypeClass() << ".toUnsignedInt(("
                 << getJavaType(false /* forInitializer */) << ")(" << name << ")))";
             break;
         }
@@ -233,7 +233,7 @@
         case KIND_UINT32:   // fallthrough
         case KIND_INT64:    // fallthrough
         case KIND_UINT64: {
-            out << getJavaWrapperType() << ".toHexString(" << name << ")";
+            out << getJavaTypeClass() << ".toHexString(" << name << ")";
             break;
         }
         case KIND_FLOAT:    // fallthrough
diff --git a/ScalarType.h b/ScalarType.h
index 86305b8..10fb540 100644
--- a/ScalarType.h
+++ b/ScalarType.h
@@ -54,8 +54,8 @@
             bool specifyNamespaces) const override;
 
     std::string getJavaType(bool forInitializer) const override;
+    std::string getJavaTypeClass() const override;
 
-    std::string getJavaWrapperType() const override;
     std::string getJavaSuffix() const override;
 
     std::string getVtsType() const override;
diff --git a/Scope.cpp b/Scope.cpp
index bff097f..d4953aa 100644
--- a/Scope.cpp
+++ b/Scope.cpp
@@ -81,7 +81,7 @@
 }
 
 LocalIdentifier *Scope::lookupIdentifier(const std::string & /*name*/) const {
-    return NULL;
+    return nullptr;
 }
 
 bool Scope::isScope() const {
@@ -93,10 +93,10 @@
         return static_cast<Interface *>(mTypes[0]);
     }
 
-    return NULL;
+    return nullptr;
 }
 
-bool Scope::containsInterfaces() const {
+bool Scope::definesInterfaces() const {
     for (const NamedType *type : mTypes) {
         if (type->isInterface()) {
             return true;
@@ -177,6 +177,12 @@
     }
 }
 
+void Scope::emitPackageTypeHeaderDefinitions(Formatter& out) const {
+    for (const Type* type : mTypes) {
+        type->emitPackageTypeHeaderDefinitions(out);
+    }
+}
+
 void Scope::emitPackageHwDeclarations(Formatter& out) const {
     for (const Type* type : mTypes) {
         type->emitPackageHwDeclarations(out);
diff --git a/Scope.h b/Scope.h
index e8c9431..bf7d1cd 100644
--- a/Scope.h
+++ b/Scope.h
@@ -51,7 +51,7 @@
     // Returns the single interface or NULL.
     Interface *getInterface() const;
 
-    bool containsInterfaces() const;
+    bool definesInterfaces() const;
 
     const std::vector<Annotation*>& annotations() const;
 
@@ -66,6 +66,7 @@
     void emitTypeDeclarations(Formatter& out) const override;
     void emitGlobalTypeDeclarations(Formatter& out) const override;
     void emitPackageTypeDeclarations(Formatter& out) const override;
+    void emitPackageTypeHeaderDefinitions(Formatter& out) const override;
     void emitPackageHwDeclarations(Formatter& out) const override;
 
     void emitJavaTypeDeclarations(Formatter& out, bool atTopLevel) const override;
diff --git a/StringType.cpp b/StringType.cpp
index a07086f..b4f72be 100644
--- a/StringType.cpp
+++ b/StringType.cpp
@@ -150,9 +150,12 @@
 
 void StringType::emitJavaFieldInitializer(
         Formatter &out, const std::string &fieldName) const {
-    out << "String "
-        << fieldName
-        << " = new String();\n";
+    emitJavaFieldDefaultInitialValue(out, "String " + fieldName);
+}
+
+void StringType::emitJavaFieldDefaultInitialValue(
+        Formatter &out, const std::string &declaredFieldName) const {
+    out << declaredFieldName << " = new String();\n";
 }
 
 void StringType::emitJavaFieldReaderWriter(
@@ -180,7 +183,7 @@
 
         // hidl_string's embedded buffer is never null(able), because it defaults to a
         // buffer containing an empty string.
-        out << fieldName << ".getBytes().length + 1,\n"
+        out << "(" << getJavaTypeCast(fieldName) << ").getBytes().length + 1,\n"
             << blobName
             << ".handle(),\n"
             << offset
diff --git a/StringType.h b/StringType.h
index a4e3691..2050875 100644
--- a/StringType.h
+++ b/StringType.h
@@ -65,6 +65,9 @@
     void emitJavaFieldInitializer(
             Formatter &out, const std::string &fieldName) const override;
 
+    void emitJavaFieldDefaultInitialValue(
+            Formatter &out, const std::string &declaredFieldName) const override;
+
     void emitJavaFieldReaderWriter(
             Formatter &out,
             size_t depth,
diff --git a/Type.cpp b/Type.cpp
index 84be36f..256b052 100644
--- a/Type.cpp
+++ b/Type.cpp
@@ -64,10 +64,6 @@
     return false;
 }
 
-bool Type::isBinder() const {
-    return false;
-}
-
 bool Type::isNamedType() const {
     return false;
 }
@@ -158,9 +154,10 @@
     return ret;
 }
 
-status_t Type::recursivePass(const std::function<status_t(Type*)>& func,
+status_t Type::recursivePass(ParseStage stage, const std::function<status_t(Type*)>& func,
                              std::unordered_set<const Type*>* visited) {
-    if (mIsPostParseCompleted) return OK;
+    if (mParseStage > stage) return OK;
+    if (mParseStage < stage) return UNKNOWN_ERROR;
 
     if (visited->find(this) != visited->end()) return OK;
     visited->insert(this);
@@ -169,21 +166,22 @@
     if (err != OK) return err;
 
     for (auto* nextType : getDefinedTypes()) {
-        err = nextType->recursivePass(func, visited);
+        err = nextType->recursivePass(stage, func, visited);
         if (err != OK) return err;
     }
 
     for (auto* nextRef : getReferences()) {
-        err = nextRef->shallowGet()->recursivePass(func, visited);
+        err = nextRef->shallowGet()->recursivePass(stage, func, visited);
         if (err != OK) return err;
     }
 
     return OK;
 }
 
-status_t Type::recursivePass(const std::function<status_t(const Type*)>& func,
+status_t Type::recursivePass(ParseStage stage, const std::function<status_t(const Type*)>& func,
                              std::unordered_set<const Type*>* visited) const {
-    if (mIsPostParseCompleted) return OK;
+    if (mParseStage > stage) return OK;
+    if (mParseStage < stage) return UNKNOWN_ERROR;
 
     if (visited->find(this) != visited->end()) return OK;
     visited->insert(this);
@@ -192,12 +190,12 @@
     if (err != OK) return err;
 
     for (const auto* nextType : getDefinedTypes()) {
-        err = nextType->recursivePass(func, visited);
+        err = nextType->recursivePass(stage, func, visited);
         if (err != OK) return err;
     }
 
     for (const auto* nextRef : getReferences()) {
-        err = nextRef->shallowGet()->recursivePass(func, visited);
+        err = nextRef->shallowGet()->recursivePass(stage, func, visited);
         if (err != OK) return err;
     }
 
@@ -319,13 +317,13 @@
 }
 
 const ScalarType *Type::resolveToScalarType() const {
-    return NULL;
+    return nullptr;
 }
 
 bool Type::isValidEnumStorageType() const {
     const ScalarType *scalarType = resolveToScalarType();
 
-    if (scalarType == NULL) {
+    if (scalarType == nullptr) {
         return false;
     }
 
@@ -354,9 +352,13 @@
     return false;
 }
 
-void Type::setPostParseCompleted() {
-    CHECK(!mIsPostParseCompleted);
-    mIsPostParseCompleted = true;
+Type::ParseStage Type::getParseStage() const {
+    return mParseStage;
+}
+
+void Type::setParseStage(ParseStage stage) {
+    CHECK(mParseStage < stage);
+    mParseStage = stage;
 }
 
 Scope* Type::parent() {
@@ -368,7 +370,7 @@
 }
 
 std::string Type::getCppType(StorageMode, bool) const {
-    CHECK(!"Should not be here");
+    CHECK(!"Should not be here") << typeName();
     return std::string();
 }
 
@@ -378,26 +380,30 @@
 }
 
 std::string Type::getJavaType(bool /* forInitializer */) const {
-    CHECK(!"Should not be here");
+    CHECK(!"Should not be here") << typeName();
     return std::string();
 }
 
-std::string Type::getJavaWrapperType() const {
+std::string Type::getJavaTypeClass() const {
     return getJavaType();
 }
 
+std::string Type::getJavaTypeCast(const std::string& objName) const {
+    return "(" + getJavaType() + ") " + objName;
+}
+
 std::string Type::getJavaSuffix() const {
-    CHECK(!"Should not be here");
+    CHECK(!"Should not be here") << typeName();
     return std::string();
 }
 
 std::string Type::getVtsType() const {
-    CHECK(!"Should not be here");
+    CHECK(!"Should not be here") << typeName();
     return std::string();
 }
 
 std::string Type::getVtsValueName() const {
-    CHECK(!"Should not be here");
+    CHECK(!"Should not be here") << typeName();
     return std::string();
 }
 
@@ -408,7 +414,7 @@
         bool,
         bool,
         ErrorMode) const {
-    CHECK(!"Should not be here");
+    CHECK(!"Should not be here") << typeName();
 }
 
 void Type::emitResolveReferences(
@@ -419,7 +425,7 @@
         bool,
         bool,
         ErrorMode) const {
-    CHECK(!"Should not be here");
+    CHECK(!"Should not be here") << typeName();
 }
 
 void Type::emitResolveReferencesEmbedded(
@@ -434,7 +440,7 @@
         ErrorMode,
         const std::string &,
         const std::string &) const {
-    CHECK(!"Should not be here");
+    CHECK(!"Should not be here") << typeName();
 }
 
 void Type::emitDump(
@@ -468,10 +474,6 @@
     return needsResolveReferences();
 }
 
-bool Type::useNameInEmitReaderWriterEmbedded(bool) const {
-    return needsEmbeddedReadWrite();
-}
-
 void Type::emitReaderWriterEmbedded(
         Formatter &,
         size_t,
@@ -484,7 +486,7 @@
         ErrorMode,
         const std::string &,
         const std::string &) const {
-    CHECK(!"Should not be here");
+    CHECK(!"Should not be here") << typeName();
 }
 
 void Type::emitJavaReaderWriter(
@@ -510,6 +512,8 @@
         << ";\n";
 }
 
+void Type::emitJavaFieldDefaultInitialValue(Formatter &, const std::string &) const {}
+
 void Type::emitJavaFieldReaderWriter(
         Formatter &,
         size_t,
@@ -518,7 +522,7 @@
         const std::string &,
         const std::string &,
         bool) const {
-    CHECK(!"Should not be here");
+    CHECK(!"Should not be here") << typeName();
 }
 
 void Type::handleError(Formatter &out, ErrorMode mode) const {
@@ -621,6 +625,8 @@
 
 void Type::emitPackageTypeDeclarations(Formatter&) const {}
 
+void Type::emitPackageTypeHeaderDefinitions(Formatter&) const {}
+
 void Type::emitPackageHwDeclarations(Formatter&) const {}
 
 void Type::emitTypeDefinitions(Formatter&, const std::string&) const {}
@@ -665,6 +671,10 @@
     return getCppType(StorageMode_Argument, specifyNamespaces);
 }
 
+std::string Type::getCppTypeCast(const std::string& objName, bool specifyNamespaces) const {
+    return "(" + getCppStackType(specifyNamespaces) + ") " + objName;
+}
+
 void Type::emitJavaReaderWriterWithSuffix(
         Formatter &out,
         const std::string &parcelObj,
diff --git a/Type.h b/Type.h
index a0ed5bc..2462f56 100644
--- a/Type.h
+++ b/Type.h
@@ -42,7 +42,6 @@
     virtual ~Type();
 
     virtual bool isArray() const;
-    virtual bool isBinder() const;
     virtual bool isBitField() const;
     virtual bool isCompoundType() const;
     virtual bool isEnum() const;
@@ -79,11 +78,24 @@
     std::vector<Reference<Type>*> getStrongReferences();
     virtual std::vector<const Reference<Type>*> getStrongReferences() const;
 
+    // Indicate stage of parsing.
+    enum class ParseStage {
+        // Indicate that the source file is being parsed and this object is being filled.
+        PARSE,
+        // Indicate that all source files are parsed, and program is working on type dependencies
+        // and validation.
+        POST_PARSE,
+        // Indicate that parsing is completed, and program is in code-generation stage.
+        COMPLETED,
+    };
+
     // Proceeds recursive pass
     // Makes sure to visit each node only once.
-    status_t recursivePass(const std::function<status_t(Type*)>& func,
+    // If mParseStage < stage, object is not ready for this recursivePass() call
+    // yet, and function will return error.
+    status_t recursivePass(ParseStage stage, const std::function<status_t(Type*)>& func,
                            std::unordered_set<const Type*>* visited);
-    status_t recursivePass(const std::function<status_t(const Type*)>& func,
+    status_t recursivePass(ParseStage stage, const std::function<status_t(const Type*)>& func,
                            std::unordered_set<const Type*>* visited) const;
 
     // Recursive tree pass that completes type declarations
@@ -128,9 +140,9 @@
     bool canCheckEquality(std::unordered_set<const Type*>* visited) const;
     virtual bool deepCanCheckEquality(std::unordered_set<const Type*>* visited) const;
 
-    // Marks that package proceeding is completed
-    // Post parse passes must be proceeded during owner package parsing
-    void setPostParseCompleted();
+    // ParseStage can only be incremented.
+    ParseStage getParseStage() const;
+    void setParseStage(ParseStage stage);
 
     Scope* parent();
     const Scope* parent() const;
@@ -157,13 +169,22 @@
 
     std::string getCppArgumentType(bool specifyNamespaces = true) const;
 
+    std::string getCppTypeCast(const std::string& objName,
+                               bool specifyNamespaces = true) const;
+
     // For an array type, dimensionality information will be accumulated at the
     // end of the returned string.
     // if forInitializer == true, actual dimensions are included, i.e. [3][5],
     // otherwise (and by default), they are omitted, i.e. [][].
     virtual std::string getJavaType(bool forInitializer = false) const;
 
-    virtual std::string getJavaWrapperType() const;
+    // Identical to getJavaType() for most types, except: primitives, in which
+    // case the wrapper type is returned, and generics (such as ArrayList<?>),
+    // where the type specialization is omitted to facilitate use of
+    // instanceof or class.isInstance().
+    virtual std::string getJavaTypeClass() const;
+
+    virtual std::string getJavaTypeCast(const std::string& objName) const;
     virtual std::string getJavaSuffix() const;
 
     virtual std::string getVtsType() const;
@@ -230,8 +251,6 @@
 
     virtual bool useParentInEmitResolveReferencesEmbedded() const;
 
-    virtual bool useNameInEmitReaderWriterEmbedded(bool isReader) const;
-
     virtual void emitJavaReaderWriter(
             Formatter &out,
             const std::string &parcelObj,
@@ -242,6 +261,10 @@
             Formatter &out,
             const std::string &fieldName) const;
 
+    virtual void emitJavaFieldDefaultInitialValue(
+            Formatter &out,
+            const std::string &declaredFieldName) const;
+
     virtual void emitJavaFieldReaderWriter(
             Formatter &out,
             size_t depth,
@@ -261,11 +284,20 @@
     virtual void emitTypeForwardDeclaration(Formatter& out) const;
 
     // Emit any declarations pertaining to this type that have to be
-    // at global scope, i.e. enum class operators.
+    // directly in a namespace, i.e. enum class operators.
     // For android.hardware.foo@1.0::*, this will be in namespace
     // android::hardware::foo::V1_0
     virtual void emitPackageTypeDeclarations(Formatter& out) const;
 
+    // Emit any definitions pertaining to this type that have to be
+    // directly in a namespace. Typically, these are things that are only
+    // used for a small subset of types, so by putting them in the header,
+    // the space cost is moved to the small number of clients that use the
+    // feature.
+    // For android.hardware.foo@1.0::*, this will be in namespace
+    // android::hardware::foo::V1_0
+    virtual void emitPackageTypeHeaderDefinitions(Formatter& out) const;
+
     // Emit any declarations pertaining to this type that have to be
     // at global scope for transport, e.g. read/writeEmbeddedTo/FromParcel
     // For android.hardware.foo@1.0::*, this will be in namespace
@@ -341,7 +373,7 @@
             const std::string &name) const;
 
    private:
-    bool mIsPostParseCompleted = false;
+    ParseStage mParseStage = ParseStage::PARSE;
     Scope* const mParent;
 
     DISALLOW_COPY_AND_ASSIGN(Type);
diff --git a/TypeDef.cpp b/TypeDef.cpp
index dcefc57..2543d49 100644
--- a/TypeDef.cpp
+++ b/TypeDef.cpp
@@ -27,7 +27,7 @@
 
 const ScalarType *TypeDef::resolveToScalarType() const {
     CHECK(!"Should not be here");
-    return NULL;
+    return nullptr;
 }
 
 Type* TypeDef::referencedType() {
diff --git a/VectorType.cpp b/VectorType.cpp
index 03604dc..f883da7 100644
--- a/VectorType.cpp
+++ b/VectorType.cpp
@@ -45,6 +45,9 @@
         return true;
     }
     if (elementType->isCompoundType()) {
+        if (static_cast<const CompoundType*>(elementType)->containsInterface()) {
+            return false;
+        }
         return true;
     }
     if (elementType->isInterface()) {
@@ -72,7 +75,7 @@
 }
 
 bool VectorType::isVectorOfBinders() const {
-    return mElementType->isBinder();
+    return mElementType->isInterface();
 }
 
 bool VectorType::deepCanCheckEquality(std::unordered_set<const Type*>* visited) const {
@@ -110,17 +113,15 @@
 }
 
 std::string VectorType::getJavaType(bool /* forInitializer */) const {
+    const std::string elementJavaType = mElementType->isTemplatedType()
+        ? mElementType->getJavaType()
+        : mElementType->getJavaTypeClass();
 
-    std::string elementJavaType;
-    if (mElementType->isArray()) {
-        elementJavaType = mElementType->getJavaType();
-    } else {
-        elementJavaType = mElementType->getJavaWrapperType();
-    }
+    return "java.util.ArrayList<" + elementJavaType + ">";
+}
 
-    return "java.util.ArrayList<"
-        + elementJavaType
-        + ">";
+std::string VectorType::getJavaTypeClass() const {
+    return "java.util.ArrayList";
 }
 
 std::string VectorType::getVtsType() const {
@@ -544,14 +545,17 @@
 
 void VectorType::emitJavaFieldInitializer(
         Formatter &out, const std::string &fieldName) const {
-    std::string javaType = getJavaType(false /* forInitializer */);
+    const std::string typeName = getJavaType(false /* forInitializer */);
+    const std::string fieldDeclaration = typeName + " " + fieldName;
 
-    out << "final "
-        << javaType
-        << " "
-        << fieldName
+    emitJavaFieldDefaultInitialValue(out, fieldDeclaration);
+}
+
+void VectorType::emitJavaFieldDefaultInitialValue(
+        Formatter &out, const std::string &declaredFieldName) const {
+    out << declaredFieldName
         << " = new "
-        << javaType
+        << getJavaType(false /* forInitializer */)
         << "();\n";
 }
 
@@ -563,13 +567,18 @@
         const std::string &fieldName,
         const std::string &offset,
         bool isReader) const {
+
+    const std::string fieldNameWithCast = isReader
+        ? "(" + getJavaTypeCast(fieldName) + ")"
+        : fieldName;
+
     VectorType::EmitJavaFieldReaderWriterForElementType(
             out,
             depth,
             mElementType.get(),
             parcelName,
             blobName,
-            fieldName,
+            fieldNameWithCast,
             offset,
             isReader);
 }
diff --git a/VectorType.h b/VectorType.h
index 6f6ede7..cc889c9 100644
--- a/VectorType.h
+++ b/VectorType.h
@@ -43,6 +43,7 @@
             bool specifyNamespaces) const override;
 
     std::string getJavaType(bool forInitializer) const override;
+    std::string getJavaTypeClass() const override;
 
     std::string getVtsType() const override;
     std::string getVtsValueName() const override;
@@ -101,6 +102,9 @@
     void emitJavaFieldInitializer(
             Formatter &out, const std::string &fieldName) const override;
 
+    void emitJavaFieldDefaultInitialValue(
+            Formatter &out, const std::string &declaredFieldName) const override;
+
     void emitJavaFieldReaderWriter(
             Formatter &out,
             size_t depth,
diff --git a/build/fqName.go b/build/fqName.go
index 88f7dc0..b5ffeea 100644
--- a/build/fqName.go
+++ b/build/fqName.go
@@ -100,6 +100,9 @@
 func (f *fqName) javaName() string {
 	return f.sanitizedString() + "-java"
 }
+func (f *fqName) javaSharedName() string {
+	return f.sanitizedString() + "-java-shallow"
+}
 func (f *fqName) javaSourcesName() string {
 	return f.sanitizedString() + "-java_gen_java"
 }
@@ -124,3 +127,25 @@
 func (f *fqName) adapterHelperHeadersName() string {
 	return f.string() + "-adapter-helper_genc++_headers"
 }
+
+func (f *fqName) vtsSpecName() string {
+	return f.string() + "-vts.spec"
+}
+func (f *fqName) vtsDriverSourcesName() string {
+	return f.string() + "-vts.driver_genc++"
+}
+func (f *fqName) vtsDriverHeadersName() string {
+	return f.string() + "-vts.driver_genc++_headers"
+}
+func (f *fqName) vtsDriverName() string {
+	return f.string() + "-vts.driver"
+}
+func (f *fqName) vtsProfilerSourcesName() string {
+	return f.string() + "-vts.profiler_genc++"
+}
+func (f *fqName) vtsProfilerHeadersName() string {
+	return f.string() + "-vts.profiler_genc++_headers"
+}
+func (f *fqName) vtsProfilerName() string {
+	return f.string() + "-vts.profiler"
+}
diff --git a/build/hidl_interface.go b/build/hidl_interface.go
index 55cba94..5d77fdb 100644
--- a/build/hidl_interface.go
+++ b/build/hidl_interface.go
@@ -15,9 +15,12 @@
 package hidl
 
 import (
+	"fmt"
+	"sort"
 	"strings"
 	"sync"
 
+	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -28,19 +31,237 @@
 
 var (
 	hidlInterfaceSuffix = "_interface"
+
+	pctx = android.NewPackageContext("android/hidl")
+
+	hidl      = pctx.HostBinToolVariable("hidl", "hidl-gen")
+	vtsc      = pctx.HostBinToolVariable("vtsc", "vtsc")
+	soong_zip = pctx.HostBinToolVariable("soong_zip", "soong_zip")
+
+	hidlRule = pctx.StaticRule("hidlRule", blueprint.RuleParams{
+		Depfile:     "${depfile}",
+		Deps:        blueprint.DepsGCC,
+		Command:     "rm -rf ${genDir} && ${hidl} -R -p . -d ${depfile} -o ${genDir} -L ${language} ${roots} ${fqName}",
+		CommandDeps: []string{"${hidl}"},
+		Description: "HIDL ${language}: ${in} => ${out}",
+	}, "depfile", "fqName", "genDir", "language", "roots")
+
+	hidlSrcJarRule = pctx.StaticRule("hidlSrcJarRule", blueprint.RuleParams{
+		Depfile: "${depfile}",
+		Deps:    blueprint.DepsGCC,
+		Command: "rm -rf ${genDir} && " +
+			"${hidl} -R -p . -d ${depfile} -o ${genDir}/srcs -L ${language} ${roots} ${fqName} && " +
+			"${soong_zip} -o ${genDir}/srcs.srcjar -C ${genDir}/srcs -D ${genDir}/srcs",
+		CommandDeps: []string{"${hidl}", "${soong_zip}"},
+		Description: "HIDL ${language}: ${in} => srcs.srcjar",
+	}, "depfile", "fqName", "genDir", "language", "roots")
+
+	vtsRule = pctx.StaticRule("vtsRule", blueprint.RuleParams{
+		Command:     "rm -rf ${genDir} && ${vtsc} -m${mode} -t${type} ${inputDir}/${packagePath} ${genDir}/${packagePath}",
+		CommandDeps: []string{"${vtsc}"},
+		Description: "VTS ${mode} ${type}: ${in} => ${out}",
+	}, "mode", "type", "inputDir", "genDir", "packagePath")
 )
 
 func init() {
 	android.RegisterModuleType("hidl_interface", hidlInterfaceFactory)
+	android.RegisterMakeVarsProvider(pctx, makeVarsProvider)
+}
+
+type hidlGenProperties struct {
+	Language   string
+	FqName     string
+	Root       string
+	Interfaces []string
+	Inputs     []string
+	Outputs    []string
+}
+
+type hidlGenRule struct {
+	android.ModuleBase
+
+	properties hidlGenProperties
+
+	genOutputDir android.Path
+	genInputs    android.Paths
+	genOutputs   android.WritablePaths
+}
+
+var _ android.SourceFileProducer = (*hidlGenRule)(nil)
+var _ genrule.SourceFileGenerator = (*hidlGenRule)(nil)
+
+func (g *hidlGenRule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	g.genOutputDir = android.PathForModuleGen(ctx)
+
+	for _, input := range g.properties.Inputs {
+		g.genInputs = append(g.genInputs, android.PathForModuleSrc(ctx, input))
+	}
+
+	for _, output := range g.properties.Outputs {
+		g.genOutputs = append(g.genOutputs, android.PathForModuleGen(ctx, output))
+	}
+
+	if g.properties.Language == "vts" && isVtsSpecPackage(ctx.ModuleName()) {
+		vtsList := vtsList(ctx.AConfig())
+		vtsListMutex.Lock()
+		*vtsList = append(*vtsList, g.genOutputs.Paths()...)
+		vtsListMutex.Unlock()
+	}
+
+	var fullRootOptions []string
+	var currentPath android.OptionalPath
+	ctx.VisitDirectDeps(func(dep android.Module) {
+		switch t := dep.(type) {
+		case *hidlInterface:
+			fullRootOptions = append(fullRootOptions, t.properties.Full_root_option)
+		case *hidlPackageRoot:
+			if currentPath.Valid() {
+				panic(fmt.Sprintf("Expecting only one path, but found %v %v", currentPath, t.getCurrentPath()))
+			}
+
+			currentPath = t.getCurrentPath()
+		default:
+			panic(fmt.Sprintf("Unrecognized hidlGenProperties dependency: %T", t))
+		}
+	})
+
+	fullRootOptions = android.FirstUniqueStrings(fullRootOptions)
+
+	inputs := g.genInputs
+	if currentPath.Valid() {
+		inputs = append(inputs, currentPath.Path())
+	}
+
+	rule := hidlRule
+	if g.properties.Language == "java" {
+		rule = hidlSrcJarRule
+	}
+
+	ctx.ModuleBuild(pctx, android.ModuleBuildParams{
+		Rule:            rule,
+		Inputs:          inputs,
+		Output:          g.genOutputs[0],
+		ImplicitOutputs: g.genOutputs[1:],
+		Args: map[string]string{
+			"depfile":  g.genOutputs[0].String() + ".d",
+			"genDir":   g.genOutputDir.String(),
+			"fqName":   g.properties.FqName,
+			"language": g.properties.Language,
+			"roots":    strings.Join(fullRootOptions, " "),
+		},
+	})
+}
+
+func (g *hidlGenRule) GeneratedSourceFiles() android.Paths {
+	return g.genOutputs.Paths()
+}
+
+func (g *hidlGenRule) Srcs() android.Paths {
+	return g.genOutputs.Paths()
+}
+
+func (g *hidlGenRule) GeneratedDeps() android.Paths {
+	return g.genOutputs.Paths()
+}
+
+func (g *hidlGenRule) GeneratedHeaderDirs() android.Paths {
+	return android.Paths{g.genOutputDir}
+}
+
+func (g *hidlGenRule) DepsMutator(ctx android.BottomUpMutatorContext) {
+	ctx.AddDependency(ctx.Module(), nil, g.properties.FqName+hidlInterfaceSuffix)
+	ctx.AddDependency(ctx.Module(), nil, wrap("", g.properties.Interfaces, hidlInterfaceSuffix)...)
+	ctx.AddDependency(ctx.Module(), nil, g.properties.Root)
+}
+
+func hidlGenFactory() android.Module {
+	g := &hidlGenRule{}
+	g.AddProperties(&g.properties)
+	android.InitAndroidModule(g)
+	return g
+}
+
+type vtscProperties struct {
+	Mode        string
+	Type        string
+	SpecName    string // e.g. foo-vts.spec
+	Outputs     []string
+	PackagePath string // e.g. android/hardware/foo/1.0/
+}
+
+type vtscRule struct {
+	android.ModuleBase
+
+	properties vtscProperties
+
+	genOutputDir android.Path
+	genInputDir  android.Path
+	genInputs    android.Paths
+	genOutputs   android.WritablePaths
+}
+
+var _ android.SourceFileProducer = (*vtscRule)(nil)
+var _ genrule.SourceFileGenerator = (*vtscRule)(nil)
+
+func (g *vtscRule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	g.genOutputDir = android.PathForModuleGen(ctx)
+
+	ctx.VisitDirectDeps(func(dep android.Module) {
+		if specs, ok := dep.(*hidlGenRule); ok {
+			g.genInputDir = specs.genOutputDir
+			g.genInputs = specs.genOutputs.Paths()
+		}
+	})
+
+	for _, output := range g.properties.Outputs {
+		g.genOutputs = append(g.genOutputs, android.PathForModuleGen(ctx, output))
+	}
+
+	ctx.ModuleBuild(pctx, android.ModuleBuildParams{
+		Rule:    vtsRule,
+		Inputs:  g.genInputs,
+		Outputs: g.genOutputs,
+		Args: map[string]string{
+			"mode":        g.properties.Mode,
+			"type":        g.properties.Type,
+			"inputDir":    g.genInputDir.String(),
+			"genDir":      g.genOutputDir.String(),
+			"packagePath": g.properties.PackagePath,
+		},
+	})
+}
+
+func (g *vtscRule) GeneratedSourceFiles() android.Paths {
+	return g.genOutputs.Paths()
+}
+
+func (g *vtscRule) Srcs() android.Paths {
+	return g.genOutputs.Paths()
+}
+
+func (g *vtscRule) GeneratedDeps() android.Paths {
+	return g.genOutputs.Paths()
+}
+
+func (g *vtscRule) GeneratedHeaderDirs() android.Paths {
+	return android.Paths{g.genOutputDir}
+}
+
+func (g *vtscRule) DepsMutator(ctx android.BottomUpMutatorContext) {
+	ctx.AddDependency(ctx.Module(), nil, g.properties.SpecName)
+}
+
+func vtscFactory() android.Module {
+	g := &vtscRule{}
+	g.AddProperties(&g.properties)
+	android.InitAndroidModule(g)
+	return g
 }
 
 type hidlInterfaceProperties struct {
 	// Vndk properties for interface library only.
 	cc.VndkProperties
 
-	// The owner of the module
-	Owner *string
-
 	// List of .hal files which compose this interface.
 	Srcs []string
 
@@ -50,7 +271,7 @@
 	// Package root for this package, must be a prefix of name
 	Root string
 
-	// List of non-TypeDef types declared in types.hal.
+	// Unused/deprecated: List of non-TypeDef types declared in types.hal.
 	Types []string
 
 	// Whether to generate the Java library stubs.
@@ -61,16 +282,52 @@
 	// expressed by @export annotations in the hal files.
 	Gen_java_constants bool
 
-	// Don't generate "android.hidl.foo@1.0" C library. Instead
-	// only generate the genrules so that this package can be
-	// included in libhidltransport.
-	Core_interface bool
+	// Whether to generate VTS-related testing libraries.
+	Gen_vts *bool
+
+	// example: -randroid.hardware:hardware/interfaces
+	Full_root_option string `blueprint:"mutated"`
+}
+
+// TODO(b/119771576): These properties are shared by all Android modules, and we are specifically
+// calling these out to be copied to every create module. However, if a new property is added, it
+// could break things because this code has no way to know about that.
+type manuallyInheritCommonProperties struct {
+	Enabled          *bool
+	Compile_multilib *string
+	Target           struct {
+		Host struct {
+			Compile_multilib *string
+		}
+		Android struct {
+			Compile_multilib *string
+		}
+	}
+	Proprietary               *bool
+	Owner                     *string
+	Vendor                    *bool
+	Soc_specific              *bool
+	Device_specific           *bool
+	Product_specific          *bool
+	Product_services_specific *bool
+	Recovery                  *bool
+	Init_rc                   []string
+	Vintf_fragments           []string
+	Required                  []string
+	Notice                    *string
+	Dist                      struct {
+		Targets []string
+		Dest    *string
+		Dir     *string
+		Suffix  *string
+	}
 }
 
 type hidlInterface struct {
 	android.ModuleBase
 
-	properties hidlInterfaceProperties
+	properties              hidlInterfaceProperties
+	inheritCommonProperties manuallyInheritCommonProperties
 }
 
 func processSources(mctx android.LoadHookContext, srcs []string) ([]string, []string, bool) {
@@ -119,67 +376,16 @@
 	return dependencies, javaDependencies, !hasError
 }
 
-func getRootList(mctx android.LoadHookContext, interfaces []string) ([]string, bool) {
-	var roots []string
-	hasError := false
-
-	for _, i := range interfaces {
-		interfaceObject := lookupInterface(i)
-		if interfaceObject == nil {
-			mctx.PropertyErrorf("interfaces", "Cannot find interface "+i)
-			hasError = true
-			continue
-		}
-		root := interfaceObject.properties.Root
-		rootObject := lookupPackageRoot(root)
-		if rootObject == nil {
-			mctx.PropertyErrorf("interfaces", `Cannot find package root specification for package `+
-				`root '%s' needed for module '%s'. Either this is a mispelling of the package `+
-				`root, or a new hidl_package_root module needs to be added. For example, you can `+
-				`fix this error by adding the following to <some path>/Android.bp:
-
-hidl_package_root {
-    name: "%s",
-    path: "<some path>",
-}
-
-This corresponds to the "-r%s:<some path>" option that would be passed into hidl-gen.`, root, i, root, root)
-			hasError = true
-			continue
-		}
-
-		roots = append(roots, root+":"+rootObject.properties.Path)
-	}
-
-	return android.FirstUniqueStrings(roots), !hasError
-}
-
-func removeCoreDependencies(mctx android.LoadHookContext, dependencies []string) ([]string, bool) {
+func removeCoreDependencies(mctx android.LoadHookContext, dependencies []string) []string {
 	var ret []string
-	hasError := false
 
 	for _, i := range dependencies {
-		interfaceObject := lookupInterface(i)
-		if interfaceObject == nil {
-			mctx.PropertyErrorf("interfaces", "Cannot find interface "+i)
-			hasError = true
-			continue
-		}
-
-		if !interfaceObject.properties.Core_interface {
+		if !isCorePackage(i) {
 			ret = append(ret, i)
 		}
 	}
 
-	return ret, !hasError
-}
-
-func hidlGenCommand(lang string, roots []string, name *fqName) *string {
-	cmd := "$(location hidl-gen) -d $(depfile) -o $(genDir)"
-	cmd += " -L" + lang
-	cmd += " " + strings.Join(wrap("-r", roots, ""), " ")
-	cmd += " " + name.string()
-	return &cmd
+	return ret
 }
 
 func hidlInterfaceMutator(mctx android.LoadHookContext, i *hidlInterface) {
@@ -189,7 +395,21 @@
 	}
 
 	if !name.inPackage(i.properties.Root) {
-		mctx.PropertyErrorf("root", "Root, "+i.properties.Root+", for "+name.string()+" must be a prefix.")
+		mctx.PropertyErrorf("root", i.properties.Root+" must be a prefix of  "+name.string()+".")
+	}
+	if lookupPackageRoot(i.properties.Root) == nil {
+		mctx.PropertyErrorf("interfaces", `Cannot find package root specification for package `+
+			`root '%s' needed for module '%s'. Either this is a mispelling of the package `+
+			`root, or a new hidl_package_root module needs to be added. For example, you can `+
+			`fix this error by adding the following to <some path>/Android.bp:
+
+hidl_package_root {
+name: "%s",
+path: "<some path>",
+}
+
+This corresponds to the "-r%s:<some path>" option that would be passed into hidl-gen.`,
+			i.properties.Root, name, i.properties.Root, i.properties.Root)
 	}
 
 	interfaces, types, _ := processSources(mctx, i.properties.Srcs)
@@ -199,17 +419,17 @@
 	}
 
 	dependencies, javaDependencies, _ := processDependencies(mctx, i.properties.Interfaces)
-	roots, _ := getRootList(mctx, append(dependencies, name.string()))
-	cppDependencies, _ := removeCoreDependencies(mctx, dependencies)
+	cppDependencies := removeCoreDependencies(mctx, dependencies)
 
 	if mctx.Failed() {
 		return
 	}
 
-	shouldGenerateLibrary := !i.properties.Core_interface
+	shouldGenerateLibrary := !isCorePackage(name.string())
 	// explicitly true if not specified to give early warning to devs
-	shouldGenerateJava := i.properties.Gen_java == nil || *i.properties.Gen_java
+	shouldGenerateJava := proptools.BoolDefault(i.properties.Gen_java, true)
 	shouldGenerateJavaConstants := i.properties.Gen_java_constants
+	shouldGenerateVts := shouldGenerateLibrary && proptools.BoolDefault(i.properties.Gen_vts, true)
 
 	var libraryIfExists []string
 	if shouldGenerateLibrary {
@@ -217,46 +437,47 @@
 	}
 
 	// TODO(b/69002743): remove filegroups
-	mctx.CreateModule(android.ModuleFactoryAdaptor(genrule.FileGroupFactory), &fileGroupProperties{
-		Name:  proptools.StringPtr(name.fileGroupName()),
-		Owner: i.properties.Owner,
-		Srcs:  i.properties.Srcs,
-	})
+	mctx.CreateModule(android.ModuleFactoryAdaptor(android.FileGroupFactory), &fileGroupProperties{
+		Name: proptools.StringPtr(name.fileGroupName()),
+		Srcs: i.properties.Srcs,
+	}, &i.inheritCommonProperties)
 
-	mctx.CreateModule(android.ModuleFactoryAdaptor(genrule.GenRuleFactory), &genruleProperties{
-		Name:    proptools.StringPtr(name.sourcesName()),
-		Depfile: proptools.BoolPtr(true),
-		Owner:   i.properties.Owner,
-		Tools:   []string{"hidl-gen"},
-		Cmd:     hidlGenCommand("c++-sources", roots, name),
-		Srcs:    i.properties.Srcs,
-		Out: concat(wrap(name.dir(), interfaces, "All.cpp"),
-			wrap(name.dir(), types, ".cpp")),
-	})
-	mctx.CreateModule(android.ModuleFactoryAdaptor(genrule.GenRuleFactory), &genruleProperties{
-		Name:    proptools.StringPtr(name.headersName()),
-		Depfile: proptools.BoolPtr(true),
-		Owner:   i.properties.Owner,
-		Tools:   []string{"hidl-gen"},
-		Cmd:     hidlGenCommand("c++-headers", roots, name),
-		Srcs:    i.properties.Srcs,
-		Out: concat(wrap(name.dir()+"I", interfaces, ".h"),
+	mctx.CreateModule(android.ModuleFactoryAdaptor(hidlGenFactory), &nameProperties{
+		Name: proptools.StringPtr(name.sourcesName()),
+	}, &hidlGenProperties{
+		Language:   "c++-sources",
+		FqName:     name.string(),
+		Root:       i.properties.Root,
+		Interfaces: i.properties.Interfaces,
+		Inputs:     i.properties.Srcs,
+		Outputs:    concat(wrap(name.dir(), interfaces, "All.cpp"), wrap(name.dir(), types, ".cpp")),
+	}, &i.inheritCommonProperties)
+	mctx.CreateModule(android.ModuleFactoryAdaptor(hidlGenFactory), &nameProperties{
+		Name: proptools.StringPtr(name.headersName()),
+	}, &hidlGenProperties{
+		Language:   "c++-headers",
+		FqName:     name.string(),
+		Root:       i.properties.Root,
+		Interfaces: i.properties.Interfaces,
+		Inputs:     i.properties.Srcs,
+		Outputs: concat(wrap(name.dir()+"I", interfaces, ".h"),
 			wrap(name.dir()+"Bs", interfaces, ".h"),
 			wrap(name.dir()+"BnHw", interfaces, ".h"),
 			wrap(name.dir()+"BpHw", interfaces, ".h"),
 			wrap(name.dir()+"IHw", interfaces, ".h"),
 			wrap(name.dir(), types, ".h"),
 			wrap(name.dir()+"hw", types, ".h")),
-	})
+	}, &i.inheritCommonProperties)
 
 	if shouldGenerateLibrary {
 		mctx.CreateModule(android.ModuleFactoryAdaptor(cc.LibraryFactory), &ccProperties{
-			Name:              proptools.StringPtr(name.string()),
-			Owner:             i.properties.Owner,
-			Vendor_available:  proptools.BoolPtr(true),
-			Defaults:          []string{"hidl-module-defaults"},
-			Generated_sources: []string{name.sourcesName()},
-			Generated_headers: []string{name.headersName()},
+			Name:               proptools.StringPtr(name.string()),
+			Recovery_available: proptools.BoolPtr(true),
+			Vendor_available:   proptools.BoolPtr(true),
+			Double_loadable:    proptools.BoolPtr(isDoubleLoadable(name.string())),
+			Defaults:           []string{"hidl-module-defaults"},
+			Generated_sources:  []string{name.sourcesName()},
+			Generated_headers:  []string{name.headersName()},
 			Shared_libs: concat(cppDependencies, []string{
 				"libhidlbase",
 				"libhidltransport",
@@ -272,72 +493,87 @@
 				"libutils",
 			}),
 			Export_generated_headers: []string{name.headersName()},
-		}, &i.properties.VndkProperties)
+		}, &i.properties.VndkProperties, &i.inheritCommonProperties)
 	}
 
 	if shouldGenerateJava {
-		mctx.CreateModule(android.ModuleFactoryAdaptor(genrule.GenRuleFactory), &genruleProperties{
-			Name:    proptools.StringPtr(name.javaSourcesName()),
-			Depfile: proptools.BoolPtr(true),
-			Owner:   i.properties.Owner,
-			Tools:   []string{"hidl-gen"},
-			Cmd:     hidlGenCommand("java", roots, name),
-			Srcs:    i.properties.Srcs,
-			Out: concat(wrap(name.sanitizedDir()+"I", interfaces, ".java"),
-				wrap(name.sanitizedDir(), i.properties.Types, ".java")),
-		})
-		mctx.CreateModule(android.ModuleFactoryAdaptor(java.LibraryFactory(true)), &javaProperties{
-			Name:              proptools.StringPtr(name.javaName()),
-			Owner:             i.properties.Owner,
-			Sdk_version:       proptools.StringPtr("system_current"),
+		mctx.CreateModule(android.ModuleFactoryAdaptor(hidlGenFactory), &nameProperties{
+			Name: proptools.StringPtr(name.javaSourcesName()),
+		}, &hidlGenProperties{
+			Language:   "java",
+			FqName:     name.string(),
+			Root:       i.properties.Root,
+			Interfaces: i.properties.Interfaces,
+			Inputs:     i.properties.Srcs,
+			Outputs:    []string{"srcs.srcjar"},
+		}, &i.inheritCommonProperties)
+
+		commonJavaProperties := javaProperties{
 			Defaults:          []string{"hidl-java-module-defaults"},
 			No_framework_libs: proptools.BoolPtr(true),
+			Installable:       proptools.BoolPtr(true),
 			Srcs:              []string{":" + name.javaSourcesName()},
-			Static_libs:       append(javaDependencies, "hwbinder"),
-		})
+
+			// This should ideally be system_current, but android.hidl.base-V1.0-java is used
+			// to build framework, which is used to build system_current.  Use core_current
+			// plus hwbinder.stubs, which together form a subset of system_current that does
+			// not depend on framework.
+			Sdk_version: proptools.StringPtr("core_current"),
+			Libs:        []string{"hwbinder.stubs"},
+		}
+
+		mctx.CreateModule(android.ModuleFactoryAdaptor(java.LibraryFactory), &javaProperties{
+			Name:        proptools.StringPtr(name.javaName()),
+			Static_libs: javaDependencies,
+		}, &i.inheritCommonProperties, &commonJavaProperties)
+		mctx.CreateModule(android.ModuleFactoryAdaptor(java.LibraryFactory), &javaProperties{
+			Name: proptools.StringPtr(name.javaSharedName()),
+			Libs: javaDependencies,
+		}, &i.inheritCommonProperties, &commonJavaProperties)
 	}
 
 	if shouldGenerateJavaConstants {
-		mctx.CreateModule(android.ModuleFactoryAdaptor(genrule.GenRuleFactory), &genruleProperties{
-			Name:    proptools.StringPtr(name.javaConstantsSourcesName()),
-			Depfile: proptools.BoolPtr(true),
-			Owner:   i.properties.Owner,
-			Tools:   []string{"hidl-gen"},
-			Cmd:     hidlGenCommand("java-constants", roots, name),
-			Srcs:    i.properties.Srcs,
-			Out:     []string{name.sanitizedDir() + "Constants.java"},
-		})
-		mctx.CreateModule(android.ModuleFactoryAdaptor(java.LibraryFactory(true)), &javaProperties{
+		mctx.CreateModule(android.ModuleFactoryAdaptor(hidlGenFactory), &nameProperties{
+			Name: proptools.StringPtr(name.javaConstantsSourcesName()),
+		}, &hidlGenProperties{
+			Language:   "java-constants",
+			FqName:     name.string(),
+			Root:       i.properties.Root,
+			Interfaces: i.properties.Interfaces,
+			Inputs:     i.properties.Srcs,
+			Outputs:    []string{name.sanitizedDir() + "Constants.java"},
+		}, &i.inheritCommonProperties)
+		mctx.CreateModule(android.ModuleFactoryAdaptor(java.LibraryFactory), &javaProperties{
 			Name:              proptools.StringPtr(name.javaConstantsName()),
-			Owner:             i.properties.Owner,
 			Defaults:          []string{"hidl-java-module-defaults"},
 			No_framework_libs: proptools.BoolPtr(true),
 			Srcs:              []string{":" + name.javaConstantsSourcesName()},
-		})
+		}, &i.inheritCommonProperties)
 	}
 
-	mctx.CreateModule(android.ModuleFactoryAdaptor(genrule.GenRuleFactory), &genruleProperties{
-		Name:    proptools.StringPtr(name.adapterHelperSourcesName()),
-		Depfile: proptools.BoolPtr(true),
-		Owner:   i.properties.Owner,
-		Tools:   []string{"hidl-gen"},
-		Cmd:     hidlGenCommand("c++-adapter-sources", roots, name),
-		Srcs:    i.properties.Srcs,
-		Out:     wrap(name.dir()+"A", concat(interfaces, types), ".cpp"),
-	})
-	mctx.CreateModule(android.ModuleFactoryAdaptor(genrule.GenRuleFactory), &genruleProperties{
-		Name:    proptools.StringPtr(name.adapterHelperHeadersName()),
-		Depfile: proptools.BoolPtr(true),
-		Owner:   i.properties.Owner,
-		Tools:   []string{"hidl-gen"},
-		Cmd:     hidlGenCommand("c++-adapter-headers", roots, name),
-		Srcs:    i.properties.Srcs,
-		Out:     wrap(name.dir()+"A", concat(interfaces, types), ".h"),
-	})
+	mctx.CreateModule(android.ModuleFactoryAdaptor(hidlGenFactory), &nameProperties{
+		Name: proptools.StringPtr(name.adapterHelperSourcesName()),
+	}, &hidlGenProperties{
+		Language:   "c++-adapter-sources",
+		FqName:     name.string(),
+		Root:       i.properties.Root,
+		Interfaces: i.properties.Interfaces,
+		Inputs:     i.properties.Srcs,
+		Outputs:    wrap(name.dir()+"A", concat(interfaces, types), ".cpp"),
+	}, &i.inheritCommonProperties)
+	mctx.CreateModule(android.ModuleFactoryAdaptor(hidlGenFactory), &nameProperties{
+		Name: proptools.StringPtr(name.adapterHelperHeadersName()),
+	}, &hidlGenProperties{
+		Language:   "c++-adapter-headers",
+		FqName:     name.string(),
+		Root:       i.properties.Root,
+		Interfaces: i.properties.Interfaces,
+		Inputs:     i.properties.Srcs,
+		Outputs:    wrap(name.dir()+"A", concat(interfaces, types), ".h"),
+	}, &i.inheritCommonProperties)
 
 	mctx.CreateModule(android.ModuleFactoryAdaptor(cc.LibraryFactory), &ccProperties{
 		Name:              proptools.StringPtr(name.adapterHelperName()),
-		Owner:             i.properties.Owner,
 		Vendor_available:  proptools.BoolPtr(true),
 		Defaults:          []string{"hidl-module-defaults"},
 		Generated_sources: []string{name.adapterHelperSourcesName()},
@@ -363,19 +599,19 @@
 		}, wrap("", dependencies, "-adapter-helper"), cppDependencies, libraryIfExists),
 		Export_generated_headers: []string{name.adapterHelperHeadersName()},
 		Group_static_libs:        proptools.BoolPtr(true),
-	})
-	mctx.CreateModule(android.ModuleFactoryAdaptor(genrule.GenRuleFactory), &genruleProperties{
-		Name:    proptools.StringPtr(name.adapterSourcesName()),
-		Depfile: proptools.BoolPtr(true),
-		Owner:   i.properties.Owner,
-		Tools:   []string{"hidl-gen"},
-		Cmd:     hidlGenCommand("c++-adapter-main", roots, name),
-		Srcs:    i.properties.Srcs,
-		Out:     []string{"main.cpp"},
-	})
+	}, &i.inheritCommonProperties)
+	mctx.CreateModule(android.ModuleFactoryAdaptor(hidlGenFactory), &nameProperties{
+		Name: proptools.StringPtr(name.adapterSourcesName()),
+	}, &hidlGenProperties{
+		Language:   "c++-adapter-main",
+		FqName:     name.string(),
+		Root:       i.properties.Root,
+		Interfaces: i.properties.Interfaces,
+		Inputs:     i.properties.Srcs,
+		Outputs:    []string{"main.cpp"},
+	}, &i.inheritCommonProperties)
 	mctx.CreateModule(android.ModuleFactoryAdaptor(cc.TestFactory), &ccProperties{
 		Name:              proptools.StringPtr(name.adapterName()),
-		Owner:             i.properties.Owner,
 		Generated_sources: []string{name.adapterSourcesName()},
 		Shared_libs: []string{
 			"libbase",
@@ -391,38 +627,192 @@
 			name.adapterHelperName(),
 		}, wrap("", dependencies, "-adapter-helper"), cppDependencies, libraryIfExists),
 		Group_static_libs: proptools.BoolPtr(true),
-	})
+	}, &i.inheritCommonProperties)
+
+	if shouldGenerateVts {
+		vtsSpecs := concat(wrap(name.dir(), interfaces, ".vts"), wrap(name.dir(), types, ".vts"))
+
+		mctx.CreateModule(android.ModuleFactoryAdaptor(hidlGenFactory), &nameProperties{
+			Name: proptools.StringPtr(name.vtsSpecName()),
+		}, &hidlGenProperties{
+			Language:   "vts",
+			FqName:     name.string(),
+			Root:       i.properties.Root,
+			Interfaces: i.properties.Interfaces,
+			Inputs:     i.properties.Srcs,
+			Outputs:    vtsSpecs,
+		}, &i.inheritCommonProperties)
+
+		mctx.CreateModule(android.ModuleFactoryAdaptor(vtscFactory), &nameProperties{
+			Name: proptools.StringPtr(name.vtsDriverSourcesName()),
+		}, &vtscProperties{
+			Mode:        "DRIVER",
+			Type:        "SOURCE",
+			SpecName:    name.vtsSpecName(),
+			Outputs:     wrap("", vtsSpecs, ".cpp"),
+			PackagePath: name.dir(),
+		}, &i.inheritCommonProperties)
+		mctx.CreateModule(android.ModuleFactoryAdaptor(vtscFactory), &nameProperties{
+			Name: proptools.StringPtr(name.vtsDriverHeadersName()),
+		}, &vtscProperties{
+			Mode:        "DRIVER",
+			Type:        "HEADER",
+			SpecName:    name.vtsSpecName(),
+			Outputs:     wrap("", vtsSpecs, ".h"),
+			PackagePath: name.dir(),
+		}, &i.inheritCommonProperties)
+		mctx.CreateModule(android.ModuleFactoryAdaptor(cc.LibraryFactory), &ccProperties{
+			Name:                      proptools.StringPtr(name.vtsDriverName()),
+			Defaults:                  []string{"VtsHalDriverDefaults"},
+			Generated_sources:         []string{name.vtsDriverSourcesName()},
+			Generated_headers:         []string{name.vtsDriverHeadersName()},
+			Export_generated_headers:  []string{name.vtsDriverHeadersName()},
+			Shared_libs:               wrap("", cppDependencies, "-vts.driver"),
+			Export_shared_lib_headers: wrap("", cppDependencies, "-vts.driver"),
+			Static_libs:               concat(cppDependencies, libraryIfExists),
+
+			// TODO(b/126244142)
+			Cflags: []string{"-Wno-unused-variable"},
+		}, &i.inheritCommonProperties)
+
+		mctx.CreateModule(android.ModuleFactoryAdaptor(vtscFactory), &nameProperties{
+			Name: proptools.StringPtr(name.vtsProfilerSourcesName()),
+		}, &vtscProperties{
+			Mode:        "PROFILER",
+			Type:        "SOURCE",
+			SpecName:    name.vtsSpecName(),
+			Outputs:     wrap("", vtsSpecs, ".cpp"),
+			PackagePath: name.dir(),
+		}, &i.inheritCommonProperties)
+		mctx.CreateModule(android.ModuleFactoryAdaptor(vtscFactory), &nameProperties{
+			Name: proptools.StringPtr(name.vtsProfilerHeadersName()),
+		}, &vtscProperties{
+			Mode:        "PROFILER",
+			Type:        "HEADER",
+			SpecName:    name.vtsSpecName(),
+			Outputs:     wrap("", vtsSpecs, ".h"),
+			PackagePath: name.dir(),
+		}, &i.inheritCommonProperties)
+		mctx.CreateModule(android.ModuleFactoryAdaptor(cc.LibraryFactory), &ccProperties{
+			Name:                      proptools.StringPtr(name.vtsProfilerName()),
+			Defaults:                  []string{"VtsHalProfilerDefaults"},
+			Generated_sources:         []string{name.vtsProfilerSourcesName()},
+			Generated_headers:         []string{name.vtsProfilerHeadersName()},
+			Export_generated_headers:  []string{name.vtsProfilerHeadersName()},
+			Shared_libs:               wrap("", cppDependencies, "-vts.profiler"),
+			Export_shared_lib_headers: wrap("", cppDependencies, "-vts.profiler"),
+			Static_libs:               concat(cppDependencies, libraryIfExists),
+
+			// TODO(b/126244142)
+			Cflags: []string{"-Wno-unused-variable"},
+		}, &i.inheritCommonProperties)
+	}
 }
 
 func (h *hidlInterface) Name() string {
 	return h.ModuleBase.Name() + hidlInterfaceSuffix
 }
 func (h *hidlInterface) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	visited := false
+	ctx.VisitDirectDeps(func(dep android.Module) {
+		if visited {
+			panic("internal error, multiple dependencies found but only one added")
+		}
+		visited = true
+		h.properties.Full_root_option = dep.(*hidlPackageRoot).getFullPackageRoot()
+	})
+	if !visited {
+		panic("internal error, no dependencies found but dependency added")
+	}
+
 }
 func (h *hidlInterface) DepsMutator(ctx android.BottomUpMutatorContext) {
+	ctx.AddDependency(ctx.Module(), nil, h.properties.Root)
 }
 
-var hidlInterfaceMutex sync.Mutex
-var hidlInterfaces []*hidlInterface
-
 func hidlInterfaceFactory() android.Module {
 	i := &hidlInterface{}
 	i.AddProperties(&i.properties)
+	i.AddProperties(&i.inheritCommonProperties)
 	android.InitAndroidModule(i)
 	android.AddLoadHook(i, func(ctx android.LoadHookContext) { hidlInterfaceMutator(ctx, i) })
 
-	hidlInterfaceMutex.Lock()
-	hidlInterfaces = append(hidlInterfaces, i)
-	hidlInterfaceMutex.Unlock()
-
 	return i
 }
 
-func lookupInterface(name string) *hidlInterface {
-	for _, i := range hidlInterfaces {
-		if i.ModuleBase.Name() == name {
-			return i
+var doubleLoadablePackageNames = []string{
+	"android.frameworks.bufferhub@1.0",
+	"android.hardware.cas@1.0",
+	"android.hardware.cas.native@1.0",
+	"android.hardware.configstore@",
+	"android.hardware.drm@1.0",
+	"android.hardware.drm@1.1",
+	"android.hardware.drm@1.2",
+	"android.hardware.graphics.allocator@",
+	"android.hardware.graphics.bufferqueue@",
+	"android.hardware.media@",
+	"android.hardware.media.omx@",
+	"android.hardware.memtrack@1.0",
+	"android.hardware.neuralnetworks@",
+	"android.hidl.allocator@",
+	"android.hidl.token@",
+	"android.system.suspend@1.0",
+}
+
+func isDoubleLoadable(name string) bool {
+	for _, pkgname := range doubleLoadablePackageNames {
+		if strings.HasPrefix(name, pkgname) {
+			return true
 		}
 	}
-	return nil
+	return false
+}
+
+// packages in libhidltransport
+var coreDependencyPackageNames = []string{
+	"android.hidl.base@",
+	"android.hidl.manager@",
+}
+
+func isCorePackage(name string) bool {
+	for _, pkgname := range coreDependencyPackageNames {
+		if strings.HasPrefix(name, pkgname) {
+			return true
+		}
+	}
+	return false
+}
+
+// TODO(b/126383715): centralize this logic/support filtering in core VTS build
+var coreVtsSpecs = []string{
+	"android.frameworks.",
+	"android.hardware.",
+	"android.hidl.",
+	"android.system.",
+}
+
+func isVtsSpecPackage(name string) bool {
+	for _, pkgname := range coreVtsSpecs {
+		if strings.HasPrefix(name, pkgname) {
+			return true
+		}
+	}
+	return false
+}
+
+var vtsListKey = android.NewOnceKey("vtsList")
+
+func vtsList(config android.Config) *android.Paths {
+	return config.Once(vtsListKey, func() interface{} {
+		return &android.Paths{}
+	}).(*android.Paths)
+}
+
+var vtsListMutex sync.Mutex
+
+func makeVarsProvider(ctx android.MakeVarsContext) {
+	vtsList := vtsList(ctx.Config()).Strings()
+	sort.Strings(vtsList)
+
+	ctx.Strict("VTS_SPEC_FILE_LIST", strings.Join(vtsList, " "))
 }
diff --git a/build/hidl_package_root.go b/build/hidl_package_root.go
index 1f68af4..63a7897 100644
--- a/build/hidl_package_root.go
+++ b/build/hidl_package_root.go
@@ -17,6 +17,8 @@
 import (
 	"sync"
 
+	"github.com/google/blueprint/proptools"
+
 	"android/soong/android"
 )
 
@@ -28,12 +30,37 @@
 	android.ModuleBase
 
 	properties struct {
-		// path to this module from root
-		Path string
+		// Path to the package root from android build root. It is recommended not to set this and
+		// use the current path. This will be deprecated in the future.
+		Path *string
+
+		// True to require a current.txt API file here.
+		//
+		// When false, it uses the file only when it exists.
+		Use_current *bool
 	}
+
+	currentPath android.OptionalPath
+}
+
+func (r *hidlPackageRoot) getFullPackageRoot() string {
+	return "-r" + r.Name() + ":" + *r.properties.Path
+}
+
+func (r *hidlPackageRoot) getCurrentPath() android.OptionalPath {
+	return r.currentPath
 }
 
 func (r *hidlPackageRoot) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	if r.properties.Path == nil {
+		r.properties.Path = proptools.StringPtr(ctx.ModuleDir())
+	}
+
+	if proptools.BoolDefault(r.properties.Use_current, false) {
+		r.currentPath = android.OptionalPathForPath(android.PathForModuleSrc(ctx, "current.txt"))
+	} else {
+		r.currentPath = android.ExistentPathForSource(ctx, ctx.ModuleDir(), "current.txt")
+	}
 }
 func (r *hidlPackageRoot) DepsMutator(ctx android.BottomUpMutatorContext) {
 }
diff --git a/build/properties.go b/build/properties.go
index d2f8a35..349beea 100644
--- a/build/properties.go
+++ b/build/properties.go
@@ -24,21 +24,12 @@
 	Srcs  []string
 }
 
-type genruleProperties struct {
-	Name    *string
-	Owner   *string
-	Tools   []string
-	Cmd     *string
-	Srcs    []string
-	Out     []string
-	Depfile *bool
-}
-
 type ccProperties struct {
 	Name                      *string
 	Owner                     *string
 	Defaults                  []string
 	Vendor_available          *bool
+	Recovery_available        *bool
 	Generated_sources         []string
 	Generated_headers         []string
 	Group_static_libs         *bool
@@ -47,6 +38,8 @@
 	Export_shared_lib_headers []string
 	Export_static_lib_headers []string
 	Export_generated_headers  []string
+	Double_loadable           *bool
+	Cflags                    []string
 }
 
 type javaProperties struct {
@@ -54,6 +47,7 @@
 	Owner             *string
 	Defaults          []string
 	No_framework_libs *bool
+	Installable       *bool
 	Sdk_version       *string
 	Srcs              []string
 	Libs              []string
diff --git a/c2hal/AST.cpp b/c2hal/AST.cpp
index 783a04e..5e7928c 100644
--- a/c2hal/AST.cpp
+++ b/c2hal/AST.cpp
@@ -37,7 +37,7 @@
          const std::string &outputDir,
          const std::string &package,
          bool isOpenGl)
-    : mScanner(NULL),
+    : mScanner(nullptr),
       mPath(path),
       mOutputDir(outputDir),
       mPackage(package),
@@ -47,21 +47,21 @@
 AST::~AST() {
     delete mExpression;
 
-    if(mDeclarations != NULL) {
+    if(mDeclarations != nullptr) {
         for(auto* decl : *mDeclarations) {
             delete decl;
         }
     }
     delete mDeclarations;
 
-    if(mInterfaces != NULL) {
+    if(mInterfaces != nullptr) {
         for(auto* inter : *mInterfaces) {
             delete inter;
         }
     }
     delete mInterfaces;
 
-    if(mIncludes != NULL) {
+    if(mIncludes != nullptr) {
         for(auto* incl : *mIncludes) {
             delete incl;
         }
@@ -116,10 +116,10 @@
 }
 
 void AST::processContents() {
-    CHECK(mDeclarations != NULL);
+    CHECK(mDeclarations != nullptr);
 
     for (auto &declaration : *mDeclarations) {
-        CHECK(declaration != NULL);
+        CHECK(declaration != nullptr);
 
         declaration->processContents(*this);
     }
@@ -209,7 +209,7 @@
             auto var = new EnumVarDeclaration(define->getName(),
                                               define->getExpression());
 
-            define->setExpression(NULL);
+            define->setExpression(nullptr);
 
             constants->push_back(var);
             it = mDeclarations->erase(it);
@@ -233,7 +233,7 @@
 }
 
 status_t AST::generateCode() const {
-    CHECK(mDeclarations != NULL);
+    CHECK(mDeclarations != nullptr);
 
     status_t err;
 
@@ -259,7 +259,7 @@
 
     FILE *file = fopen((getFileDir() + fileName).c_str(), "w");
 
-    if(file == NULL) {
+    if(file == nullptr) {
         return -errno;
     }
 
@@ -280,7 +280,7 @@
 
     FILE *file = fopen((getFileDir() + "types.hal").c_str(), "w");
 
-    if(file == NULL) {
+    if(file == nullptr) {
         return -errno;
     }
 
diff --git a/c2hal/AST.h b/c2hal/AST.h
index c3d6c5c..813efcb 100644
--- a/c2hal/AST.h
+++ b/c2hal/AST.h
@@ -62,18 +62,18 @@
     Scope<Define *> &getDefinesScope();
 
 private:
-    void * mScanner = NULL;
+    void * mScanner = nullptr;
     std::string mPath;
     std::string mOutputDir;
     std::string mPackage;
 
     bool mIsOpenGl;
 
-    Expression* mExpression = NULL;
+    Expression* mExpression = nullptr;
 
-    std::vector<Declaration *> *mDeclarations = NULL;
-    std::vector<CompositeDeclaration *> *mInterfaces = NULL;
-    std::vector<Include *> *mIncludes = NULL;
+    std::vector<Declaration *> *mDeclarations = nullptr;
+    std::vector<CompositeDeclaration *> *mInterfaces = nullptr;
+    std::vector<Include *> *mIncludes = nullptr;
 
     Scope<Define *> mDefinesScope;
 
diff --git a/c2hal/Android.bp b/c2hal/Android.bp
index 3dd9c6a..4c34235 100644
--- a/c2hal/Android.bp
+++ b/c2hal/Android.bp
@@ -36,6 +36,7 @@
     shared_libs: [
         "libbase",
         "liblog",
+        "libhidl-gen-host-utils",
         "libhidl-gen-utils",
     ],
 
diff --git a/c2hal/CompositeDeclaration.cpp b/c2hal/CompositeDeclaration.cpp
index 70cf775..13d04a6 100644
--- a/c2hal/CompositeDeclaration.cpp
+++ b/c2hal/CompositeDeclaration.cpp
@@ -37,7 +37,7 @@
     }
 
 CompositeDeclaration::~CompositeDeclaration() {
-    if(mFieldDeclarations != NULL) {
+    if(mFieldDeclarations != nullptr) {
         for(auto* decl : *mFieldDeclarations) {
             delete decl;
         }
diff --git a/c2hal/Define.cpp b/c2hal/Define.cpp
index 6c3248e..6938226 100644
--- a/c2hal/Define.cpp
+++ b/c2hal/Define.cpp
@@ -53,7 +53,7 @@
     }
 
     mExpression = ast.getExpression();
-    ast.setExpression(NULL);
+    ast.setExpression(nullptr);
 
     mExpressionType = mExpression->getType(ast);
 
diff --git a/c2hal/Define.h b/c2hal/Define.h
index e66159d..6f84974 100644
--- a/c2hal/Define.h
+++ b/c2hal/Define.h
@@ -44,7 +44,7 @@
     const std::string mSlurp;
 
     Expression::Type mExpressionType;
-    Expression* mExpression = NULL;
+    Expression* mExpression = nullptr;
 
     DISALLOW_COPY_AND_ASSIGN(Define);
 };
diff --git a/c2hal/EnumVarDeclaration.cpp b/c2hal/EnumVarDeclaration.cpp
index d0218a3..f2673c4 100644
--- a/c2hal/EnumVarDeclaration.cpp
+++ b/c2hal/EnumVarDeclaration.cpp
@@ -43,7 +43,7 @@
 void EnumVarDeclaration::generateSource(Formatter &out) const {
     out << getName();
 
-    if(mExpression != NULL) {
+    if(mExpression != nullptr) {
         out << " = " << mExpression->toString(StringHelper::kUpperSnakeCase);
     }
 
diff --git a/c2hal/EnumVarDeclaration.h b/c2hal/EnumVarDeclaration.h
index cb62ff3..a6d2d3f 100644
--- a/c2hal/EnumVarDeclaration.h
+++ b/c2hal/EnumVarDeclaration.h
@@ -41,7 +41,7 @@
     void processContents(AST &ast) override;
 
 private:
-    Expression *mExpression = NULL;
+    Expression *mExpression = nullptr;
 
     DISALLOW_COPY_AND_ASSIGN(EnumVarDeclaration);
 };
diff --git a/c2hal/Expression.cpp b/c2hal/Expression.cpp
index 0ab864f..767e625 100644
--- a/c2hal/Expression.cpp
+++ b/c2hal/Expression.cpp
@@ -82,14 +82,14 @@
 struct ParenthesizedExpression : Expression {
     ParenthesizedExpression(Expression* inner)
     : mInner(inner) {}
-    ~ParenthesizedExpression() {
+    ~ParenthesizedExpression() override {
         delete mInner;
     }
 
-    virtual Type getType(const AST &ast) {
+    Type getType(const AST &ast) override {
         return mInner->getType(ast);
     }
-    virtual std::string toString(StringHelper::Case atomCase) {
+    std::string toString(StringHelper::Case atomCase) override {
         return "(" + mInner->toString(atomCase) + ")";
     }
 
@@ -104,20 +104,20 @@
     : mType(type), mValue(value), mIsId(isId)
     {}
 
-    virtual Type getType(const AST &ast) {
+    Type getType(const AST &ast) override {
         if (mType != Type::UNKNOWN) {
             return mType;
         }
 
         Define *define = ast.getDefinesScope().lookup(mValue);
 
-        if (define == NULL) {
+        if (define == nullptr) {
             return Type::UNKNOWN;
         }
 
         return define->getExpressionType();
     }
-    virtual std::string toString(StringHelper::Case atomCase) {
+    std::string toString(StringHelper::Case atomCase) override {
         // do not enforce case if it is not an identifier.
         return mIsId ? StringHelper::ToCase(atomCase, mValue) : mValue;
     }
@@ -134,14 +134,14 @@
     UnaryExpression(std::string op, Expression* rhs)
     : mOp(op), mRhs(rhs)
     {}
-    ~UnaryExpression() {
+    ~UnaryExpression() override {
         delete mRhs;
     }
 
-    virtual Type getType(const AST &ast) {
+    Type getType(const AST &ast) override {
         return mRhs->getType(ast);
     }
-    virtual std::string toString(StringHelper::Case atomCase) {
+    std::string toString(StringHelper::Case atomCase) override {
         return mOp + mRhs->toString(atomCase);
     }
 
@@ -156,15 +156,15 @@
     BinaryExpression(Expression *lhs, std::string op, Expression* rhs)
     : mLhs(lhs), mOp(op), mRhs(rhs)
     {}
-    ~BinaryExpression() {
+    ~BinaryExpression() override {
         delete mLhs;
         delete mRhs;
     }
 
-    virtual Type getType(const AST &ast) {
+    Type getType(const AST &ast) override {
         return coalesceTypes(mLhs->getType(ast), mRhs->getType(ast));
     }
-    virtual std::string toString(StringHelper::Case atomCase) {
+    std::string toString(StringHelper::Case atomCase) override {
         return mLhs->toString(atomCase) + " " + mOp + " " + mRhs->toString(atomCase);
     }
 
@@ -180,16 +180,16 @@
     TernaryExpression(Expression *lhs, Expression *mhs, Expression* rhs)
     : mLhs(lhs), mMhs(mhs), mRhs(rhs)
     {}
-    ~TernaryExpression() {
+    ~TernaryExpression() override {
         delete mLhs;
         delete mMhs;
         delete mRhs;
     }
 
-    virtual Type getType(const AST &ast) {
+    Type getType(const AST &ast) override {
         return coalesceTypes(mMhs->getType(ast), mRhs->getType(ast));
     }
-    virtual std::string toString(StringHelper::Case atomCase) {
+    std::string toString(StringHelper::Case atomCase) override {
         return mLhs->toString(atomCase) + " ? " + mMhs->toString(atomCase) + " : " + mRhs->toString(atomCase);
     }
 
@@ -205,14 +205,14 @@
     ArraySubscript(std::string id, Expression* subscript)
     : mId(id), mSubscript(subscript)
     {}
-    ~ArraySubscript() {
+    ~ArraySubscript() override {
         delete mSubscript;
     }
 
-    virtual Type getType(const AST &) {
+    Type getType(const AST &) override {
         return Type::UNKNOWN;
     }
-    virtual std::string toString(StringHelper::Case atomCase) {
+    std::string toString(StringHelper::Case atomCase) override {
         return mId + "[" + mSubscript->toString(atomCase) + "]";
     }
 
@@ -227,8 +227,8 @@
     FunctionCall(std::string id, std::vector<Expression *> *args)
     : mId(id), mArgs(args)
     {}
-    ~FunctionCall() {
-        if(mArgs != NULL) {
+    ~FunctionCall() override {
+        if(mArgs != nullptr) {
             for(auto* args : *mArgs) {
                 delete args;
             }
@@ -236,10 +236,10 @@
         delete mArgs;
     }
 
-    virtual Type getType(const AST &) {
+    Type getType(const AST &) override {
         return Type::UNKNOWN;
     }
-    virtual std::string toString(StringHelper::Case atomCase) {
+    std::string toString(StringHelper::Case atomCase) override {
         std::string out = mId + "(";
 
         for (auto it = mArgs->begin(); it != mArgs->end(); ++it) {
diff --git a/c2hal/FunctionDeclaration.cpp b/c2hal/FunctionDeclaration.cpp
index 12b7dc0..493b317 100644
--- a/c2hal/FunctionDeclaration.cpp
+++ b/c2hal/FunctionDeclaration.cpp
@@ -35,7 +35,7 @@
 FunctionDeclaration::~FunctionDeclaration() {
     delete mType;
 
-    if(mParams != NULL) {
+    if(mParams != nullptr) {
         for(auto* param : *mParams) {
             delete param;
         }
diff --git a/c2hal/Type.cpp b/c2hal/Type.cpp
index 9e21c01..ea8e6c0 100644
--- a/c2hal/Type.cpp
+++ b/c2hal/Type.cpp
@@ -26,13 +26,13 @@
     {}
 
 Type::~Type() {
-    if(mArrays != NULL) {
+    if(mArrays != nullptr) {
         for(auto* array : *mArrays) {
             delete array;
         }
     }
 
-    if(mQualifiers != NULL) {
+    if(mQualifiers != nullptr) {
         for(auto* qual : *mQualifiers) {
             delete qual;
         }
@@ -115,7 +115,7 @@
 }
 
 const std::string Type::getHidlType() const {
-    if (mQualifiers == NULL) {
+    if (mQualifiers == nullptr) {
         return "";
     }
 
@@ -175,7 +175,7 @@
         }
     }
 
-    if (mArrays != NULL) {
+    if (mArrays != nullptr) {
         for (const auto &array : *mArrays) {
             ss << "[" << array->toString() << "]";
         }
@@ -185,7 +185,7 @@
 }
 
 const std::string Type::getRawQualifierList() const {
-    if (mQualifiers == NULL) {
+    if (mQualifiers == nullptr) {
         return "";
     }
 
@@ -240,13 +240,13 @@
 }
 
 std::string Type::removeLastId() {
-    if(mQualifiers == NULL || mQualifiers->size() == 0) {
+    if(mQualifiers == nullptr || mQualifiers->size() == 0) {
         return "";
     }
 
     Qualifier *last = (*mQualifiers)[mQualifiers->size() - 1];
 
-    if(last == NULL || last->qualification != Qualifier::ID) {
+    if(last == nullptr || last->qualification != Qualifier::ID) {
         return "";
     }
 
diff --git a/c2hal/Type.h b/c2hal/Type.h
index 8cd0477..dc7d13d 100644
--- a/c2hal/Type.h
+++ b/c2hal/Type.h
@@ -104,10 +104,10 @@
     const std::string getRawQualifierList() const;
     const std::string getSpecialTypeName() const;
 
-    std::vector<Qualifier*> *mQualifiers = NULL;
+    std::vector<Qualifier*> *mQualifiers = nullptr;
 
     /* [ expression ] [ expression ] ... [ expression ] */
-    std::vector<Expression*> *mArrays = NULL;
+    std::vector<Expression*> *mArrays = nullptr;
 
     DISALLOW_COPY_AND_ASSIGN(Type);
 };
diff --git a/docs/src/main.kt b/docs/src/main.kt
index 2069d2e..60cf0b7 100644
--- a/docs/src/main.kt
+++ b/docs/src/main.kt
@@ -47,7 +47,7 @@
             if (writer.writeToFile()) println("$LOG_NAME Wrote file: ${writer.path}")
 
         } catch (ex: ParseException) {
-            if (config.skipError) {
+            if (config.warnOnly) {
                 System.err.println("$LOG_NAME Error parsing file, skipping: $fp")
                 continue
             } else {
diff --git a/docs/src/parser/config.kt b/docs/src/parser/config.kt
index 125b4ac..38b0576 100644
--- a/docs/src/parser/config.kt
+++ b/docs/src/parser/config.kt
@@ -28,33 +28,27 @@
     println("""
 Usage: hidl-doc [-i path]
  -i=path  Add input HAL file or directory to parse
- -o=dir   Output directory of generated HTML [${config.outDir}]
+ -o=dir   Output directory of generated HTML
  -x=path  Exclude file or directory from files to parse
  -v       Verbose mode, print parsing info
  -h       Print this help and exit
  Error modes:
  -w       Warn on errors instead of exiting
  -l       Lint. Warn-only and do not generate files
- -e       Error and exit on warnings
- -s       Skip files that encounter parse errors
 """.trim())
 }
 
 object config {
     val files = mutableListOf<File>()
-    var outDir = toPath("~/out/hidl-doc/html")
+    lateinit var outDir: Path
     var verbose = false
     var lintMode = false
     var warnOnly = false
-    var errorOnly = false
-    var skipError = false
 
     override fun toString(): String {
         return """
 verbose: $verbose
 warnOnly: $warnOnly
-errorOnly: $errorOnly
-skipError: $skipError
 outDir: $outDir
 files: $files
 """
@@ -71,26 +65,24 @@
         val dirPathArgs = mutableListOf<Path>()
         val filePathArgs = mutableListOf<Path>()
         val excludedPathArgs = mutableListOf<Path>()
+        var maybeOutDir: Path? = null
 
         val iter = args.iterator()
-        var arg: String
 
         //parse command-line arguments
         while (iter.hasNext()) {
-            arg = iter.next()
+            var arg = iter.next()
 
             when (arg) {
                 "-i" -> {
-                    val path = toPath(iter.next())
+                    val path = Paths.get(iter.next())
                     if (Files.isDirectory(path)) dirPathArgs.add(path) else filePathArgs.add(path)
                 }
-                "-x" -> excludedPathArgs.add(toPath(iter.next()).toAbsolutePath())
-                "-o" -> outDir = toPath(iter.next())
+                "-x" -> excludedPathArgs.add(Paths.get(iter.next()).toAbsolutePath())
+                "-o" -> maybeOutDir = Paths.get(iter.next())
                 "-v" -> verbose = true
                 "-l" -> { lintMode = true; warnOnly = true }
                 "-w" -> warnOnly = true
-                "-e" -> errorOnly = true
-                "-s" -> skipError = true
                 "-h" -> {
                     printUsage()
                     exitProcess(0)
@@ -103,6 +95,12 @@
             }
         }
 
+        if (maybeOutDir == null) {
+            System.err.println("Error: No output directory supplied (-o)")
+            exitProcess(1)
+        }
+        outDir = maybeOutDir
+
         //collect files (explicitly passed and search directories)
         val allFiles = mutableListOf<File>()
 
@@ -148,9 +146,4 @@
                     }
                 }
     }
-
-    private fun toPath(string: String): Path {
-        //replace shell expansion for HOME
-        return Paths.get(string.replaceFirst("~", System.getProperty("user.home")))
-    }
 }
\ No newline at end of file
diff --git a/docs/src/writer/elements/MethodElement.kt b/docs/src/writer/elements/MethodElement.kt
index dcd6c2a..ad5c0ec 100644
--- a/docs/src/writer/elements/MethodElement.kt
+++ b/docs/src/writer/elements/MethodElement.kt
@@ -65,11 +65,11 @@
                 ?.let { formatTextasHTML(it.description, useParagraphs = false) }
                 ?: run {
             val msg = "Missing @${tag.value} doc annotation for '${arg.type} ${arg.name}'"
-            if (config.errorOnly) {
-                throw ParseException(msg, 0)
-            } else {
+            if (config.warnOnly) {
                 warn(msg)
                 "" //return empty string if it can't find it
+            } else {
+                throw ParseException(msg, 0)
             }
         }
     }
diff --git a/generateCpp.cpp b/generateCpp.cpp
index fa2a3ca..06110ca 100644
--- a/generateCpp.cpp
+++ b/generateCpp.cpp
@@ -103,17 +103,43 @@
 static void declareGetService(Formatter &out, const std::string &interfaceName, bool isTry) {
     const std::string functionName = isTry ? "tryGetService" : "getService";
 
+    if (isTry) {
+        DocComment(
+                "This gets the service of this type with the specified instance name. If the\n"
+                "service is currently not available or not in the VINTF manifest on a Trebilized\n"
+                "device, this will return nullptr. This is useful when you don't want to block\n"
+                "during device boot. If getStub is true, this will try to return an unwrapped\n"
+                "passthrough implementation in the same process. This is useful when getting an\n"
+                "implementation from the same partition/compilation group.\n\n"
+                "In general, prefer getService(std::string,bool)")
+                .emit(out);
+    } else {
+        DocComment(
+                "This gets the service of this type with the specified instance name. If the\n"
+                "service is not in the VINTF manifest on a Trebilized device, this will return\n"
+                "nullptr. If the service is not available, this will wait for the service to\n"
+                "become available. If the service is a lazy service, this will start the service\n"
+                "and return when it becomes available. If getStub is true, this will try to\n"
+                "return an unwrapped passthrough implementation in the same process. This is\n"
+                "useful when getting an implementation from the same partition/compilation group.")
+                .emit(out);
+    }
     out << "static ::android::sp<" << interfaceName << "> " << functionName << "("
         << "const std::string &serviceName=\"default\", bool getStub=false);\n";
+    DocComment("Deprecated. See " + functionName + "(std::string, bool)").emit(out);
     out << "static ::android::sp<" << interfaceName << "> " << functionName << "("
         << "const char serviceName[], bool getStub=false)"
         << "  { std::string str(serviceName ? serviceName : \"\");"
         << "      return " << functionName << "(str, getStub); }\n";
+    DocComment("Deprecated. See " + functionName + "(std::string, bool)").emit(out);
     out << "static ::android::sp<" << interfaceName << "> " << functionName << "("
         << "const ::android::hardware::hidl_string& serviceName, bool getStub=false)"
         // without c_str the std::string constructor is ambiguous
         << "  { std::string str(serviceName.c_str());"
         << "      return " << functionName << "(str, getStub); }\n";
+    DocComment("Calls " + functionName +
+               "(\"default\", bool). This is the recommended instance name for singleton services.")
+            .emit(out);
     out << "static ::android::sp<" << interfaceName << "> " << functionName << "("
         << "bool getStub) { return " << functionName << "(\"default\", getStub); }\n";
 }
@@ -122,8 +148,13 @@
     declareGetService(out, interfaceName, true /* isTry */);
     declareGetService(out, interfaceName, false /* isTry */);
 
+    DocComment(
+            "Registers a service with the service manager. For Trebilized devices, the service\n"
+            "must also be in the VINTF manifest.")
+            .emit(out);
     out << "__attribute__ ((warn_unused_result))"
         << "::android::status_t registerAsService(const std::string &serviceName=\"default\");\n";
+    DocComment("Registers for notifications for when a service is registered.").emit(out);
     out << "static bool registerForNotifications(\n";
     out.indent(2, [&] {
         out << "const std::string &serviceName,\n"
@@ -140,8 +171,7 @@
     const std::string interfaceName = fqName.getInterfaceName();
     const std::string functionName = isTry ? "tryGetService" : "getService";
 
-    out << "// static\n"
-        << "::android::sp<" << interfaceName << "> " << interfaceName << "::" << functionName << "("
+    out << "::android::sp<" << interfaceName << "> " << interfaceName << "::" << functionName << "("
         << "const std::string &serviceName, const bool getStub) ";
     out.block([&] {
         out << "return ::android::hardware::details::getServiceInternal<"
@@ -163,20 +193,7 @@
     out << "::android::status_t " << interfaceName << "::registerAsService("
         << "const std::string &serviceName) ";
     out.block([&] {
-        out << "::android::hardware::details::onRegistration(\""
-            << fqName.getPackageAndVersion().string() << "\", \""
-            << interfaceName
-            << "\", serviceName);\n\n";
-        out << "const ::android::sp<::android::hidl::manager::V1_0::IServiceManager> sm\n";
-        out.indent(2, [&] {
-            out << "= ::android::hardware::defaultServiceManager();\n";
-        });
-        out.sIf("sm == nullptr", [&] {
-            out << "return ::android::INVALID_OPERATION;\n";
-        }).endl();
-        out << "::android::hardware::Return<bool> ret = "
-            << "sm->add(serviceName.c_str(), this);\n"
-            << "return ret.isOk() && ret ? ::android::OK : ::android::UNKNOWN_ERROR;\n";
+        out << "return ::android::hardware::details::registerAsServiceInternal(this, serviceName);\n";
     }).endl().endl();
 
     out << "bool " << interfaceName << "::registerForNotifications(\n";
@@ -242,12 +259,14 @@
     out << "\n";
 
     if (iface) {
+        iface->emitDocComment(out);
+
         out << "struct "
             << ifaceName;
 
         const Interface *superType = iface->superType();
 
-        if (superType == NULL) {
+        if (superType == nullptr) {
             out << " : virtual public ::android::RefBase";
         } else {
             out << " : public "
@@ -258,17 +277,28 @@
 
         out.indent();
 
+        DocComment("Type tag for use in template logic that indicates this is a 'pure' class.")
+                .emit(out);
         generateCppTag(out, "android::hardware::details::i_tag");
+
+        DocComment("Fully qualified interface name: \"" + iface->fqName().string() + "\"")
+                .emit(out);
+        out << "static const char* descriptor;\n\n";
+
+        iface->emitTypeDeclarations(out);
+    } else {
+        mRootScope.emitTypeDeclarations(out);
     }
 
-    emitTypeDeclarations(out);
-
     if (iface) {
+        DocComment(
+                "Returns whether this object's implementation is outside of the current process.")
+                .emit(out);
         out << "virtual bool isRemote() const ";
         if (!isIBase()) {
             out << "override ";
         }
-        out << "{ return false; }\n\n";
+        out << "{ return false; }\n";
 
         for (const auto& tuple : iface->allMethodsFromRoot()) {
             const Method* method = tuple.method();
@@ -279,6 +309,7 @@
             const NamedReference<Type>* elidedReturn = method->canElideCallback();
 
             if (elidedReturn == nullptr && returnsValue) {
+                DocComment("Return callback for " + method->name()).emit(out);
                 out << "using "
                     << method->name()
                     << "_cb = std::function<void(";
@@ -311,10 +342,14 @@
             out << ";\n";
         }
 
-        out << "// cast static functions\n";
+        out << "\n// cast static functions\n";
         std::string childTypeResult = iface->getCppResultType();
 
         for (const Interface *superType : iface->typeChain()) {
+            DocComment(
+                    "This performs a checked cast based on what the underlying implementation "
+                    "actually is.")
+                    .emit(out);
             out << "static ::android::hardware::Return<"
                 << childTypeResult
                 << "> castFrom("
@@ -323,11 +358,10 @@
                 << ", bool emitError = false);\n";
         }
 
-        out << "\nstatic const char* descriptor;\n\n";
-
         if (isIBase()) {
-            out << "// skipped getService, registerAsService, registerForNotifications\n\n";
+            out << "\n// skipped getService, registerAsService, registerForNotifications\n\n";
         } else {
+            out << "\n// helper methods for interactions with the hwservicemanager\n";
             declareServiceManagerInteractions(out, iface->localName());
         }
     }
@@ -338,11 +372,22 @@
         out << "};\n\n";
     }
 
+    out << "//\n";
+    out << "// type declarations for package\n";
+    out << "//\n\n";
     mRootScope.emitPackageTypeDeclarations(out);
+    out << "//\n";
+    out << "// type header definitions for package\n";
+    out << "//\n\n";
+    mRootScope.emitPackageTypeHeaderDefinitions(out);
 
     out << "\n";
     enterLeaveNamespace(out, false /* enter */);
+    out << "\n";
 
+    out << "//\n";
+    out << "// global type declarations for package\n";
+    out << "//\n\n";
     mRootScope.emitGlobalTypeDeclarations(out);
 
     out << "\n#endif  // " << guard << "\n";
@@ -387,18 +432,12 @@
     out << "\n#endif  // " << guard << "\n";
 }
 
-void AST::emitTypeDeclarations(Formatter& out) const {
-    return mRootScope.emitTypeDeclarations(out);
-}
-
-static void wrapPassthroughArg(Formatter& out, const NamedReference<Type>* arg,
-                               bool addPrefixToName, std::function<void(void)> handleError) {
+static std::string wrapPassthroughArg(Formatter& out, const NamedReference<Type>* arg,
+                                      std::string name, std::function<void(void)> handleError) {
     if (!arg->type().isInterface()) {
-        return;
+        return name;
     }
-    std::string name = (addPrefixToName ? "_hidl_out_" : "") + arg->name();
-    std::string wrappedName = (addPrefixToName ? "_hidl_out_wrapped_" : "_hidl_wrapped_")
-            + arg->name();
+    std::string wrappedName = "_hidl_wrapped_" + name;
     const Interface &iface = static_cast<const Interface &>(arg->type());
     out << iface.getCppStackType() << " " << wrappedName << ";\n";
     // TODO(elsk): b/33754152 Should not wrap this if object is Bs*
@@ -416,12 +455,14 @@
     }).sElse([&] {
         out << wrappedName << " = " << name << ";\n";
     }).endl().endl();
+
+    return wrappedName;
 }
 
-void AST::generatePassthroughMethod(Formatter& out, const Method* method) const {
+void AST::generatePassthroughMethod(Formatter& out, const Method* method, const Interface* superInterface) const {
     method->generateCppSignature(out);
 
-    out << " {\n";
+    out << " override {\n";
     out.indent();
 
     if (method->isHidlReserved()
@@ -442,20 +483,23 @@
     generateCppInstrumentationCall(
             out,
             InstrumentationEvent::PASSTHROUGH_ENTRY,
-            method);
+            method,
+            superInterface);
 
-
+    std::vector<std::string> wrappedArgNames;
     for (const auto &arg : method->args()) {
-        wrapPassthroughArg(out, arg, false /* addPrefixToName */, [&] {
+        std::string name = wrapPassthroughArg(out, arg, arg->name(), [&] {
             out << "return ::android::hardware::Status::fromExceptionCode(\n";
             out.indent(2, [&] {
                 out << "::android::hardware::Status::EX_TRANSACTION_FAILED,\n"
                     << "\"Cannot wrap passthrough interface.\");\n";
             });
         });
+
+        wrappedArgNames.push_back(name);
     }
 
-    out << "auto _hidl_error = ::android::hardware::Void();\n";
+    out << "::android::hardware::Status _hidl_error = ::android::hardware::Status::ok();\n";
     out << "auto _hidl_return = ";
 
     if (method->isOneway()) {
@@ -464,10 +508,8 @@
                ", mEnableInstrumentation = this->mEnableInstrumentation, "
                "mInstrumentationCallbacks = this->mInstrumentationCallbacks\n"
             << "#endif // __ANDROID_DEBUGGABLE__\n";
-        for (const auto &arg : method->args()) {
-            out << ", "
-                << (arg->type().isInterface() ? "_hidl_wrapped_" : "")
-                << arg->name();
+        for (const std::string& arg : wrappedArgNames) {
+            out << ", " << arg;
         }
         out << "] {\n";
         out.indent();
@@ -480,6 +522,15 @@
     out.join(method->args().begin(), method->args().end(), ", ", [&](const auto &arg) {
         out << (arg->type().isInterface() ? "_hidl_wrapped_" : "") << arg->name();
     });
+
+    std::function<void(void)> kHandlePassthroughError = [&] {
+        out << "_hidl_error = ::android::hardware::Status::fromExceptionCode(\n";
+        out.indent(2, [&] {
+            out << "::android::hardware::Status::EX_TRANSACTION_FAILED,\n"
+                << "\"Cannot wrap passthrough interface.\");\n";
+        });
+    };
+
     if (returnsValue && elidedReturn == nullptr) {
         // never true if oneway since oneway methods don't return values
 
@@ -497,46 +548,54 @@
         generateCppInstrumentationCall(
                 out,
                 InstrumentationEvent::PASSTHROUGH_EXIT,
-                method);
+                method,
+                superInterface);
 
+        std::vector<std::string> wrappedOutNames;
         for (const auto &arg : method->results()) {
-            wrapPassthroughArg(out, arg, true /* addPrefixToName */, [&] {
-                out << "_hidl_error = ::android::hardware::Status::fromExceptionCode(\n";
-                out.indent(2, [&] {
-                    out << "::android::hardware::Status::EX_TRANSACTION_FAILED,\n"
-                        << "\"Cannot wrap passthrough interface.\");\n";
-                });
-                out << "return;\n";
-            });
+            wrappedOutNames.push_back(
+                wrapPassthroughArg(out, arg, "_hidl_out_" + arg->name(), kHandlePassthroughError));
         }
 
         out << "_hidl_cb(";
-        out.join(method->results().begin(), method->results().end(), ", ", [&](const auto &arg) {
-            out << (arg->type().isInterface() ? "_hidl_out_wrapped_" : "_hidl_out_")
-                << arg->name();
-        });
+        out.join(wrappedOutNames.begin(), wrappedOutNames.end(), ", ",
+                 [&](const std::string& arg) { out << arg; });
         out << ");\n";
         out.unindent();
         out << "});\n\n";
     } else {
         out << ");\n\n";
 
-        // used by generateCppInstrumentationCall
         if (elidedReturn != nullptr) {
-            out << "#ifdef __ANDROID_DEBUGGABLE__\n"
-                << elidedReturn->type().getCppResultType() << " _hidl_out_" << elidedReturn->name()
-                << " = _hidl_return;\n"
-                << "#endif // __ANDROID_DEBUGGABLE__\n";
+            const std::string outName = "_hidl_out_" + elidedReturn->name();
+
+            out << elidedReturn->type().getCppResultType() << " " << outName
+                << " = _hidl_return;\n";
+            out << "(void) " << outName << ";\n";
+
+            const std::string wrappedName =
+                wrapPassthroughArg(out, elidedReturn, outName, kHandlePassthroughError);
+
+            if (outName != wrappedName) {
+                // update the original value since it is used by generateCppInstrumentationCall
+                out << outName << " = " << wrappedName << ";\n\n";
+
+                // update the value to be returned
+                out << "_hidl_return = " << outName << "\n;";
+            }
         }
         generateCppInstrumentationCall(
                 out,
                 InstrumentationEvent::PASSTHROUGH_EXIT,
-                method);
+                method,
+                superInterface);
     }
 
     if (method->isOneway()) {
         out.unindent();
         out << "});\n";
+    } else {
+        out << "if (!_hidl_error.isOk()) return _hidl_error;\n";
     }
 
     out << "return _hidl_return;\n";
@@ -573,6 +632,7 @@
 }
 
 void AST::generateTemplatizationLink(Formatter& out) const {
+    DocComment("The pure class is what this class wraps.").emit(out);
     out << "typedef " << mRootScope.getInterface()->localName() << " Pure;\n\n";
 }
 
@@ -633,6 +693,8 @@
 
     out.endl();
     generateTemplatizationLink(out);
+    DocComment("Type tag for use in template logic that indicates this is a 'native' class.")
+            .emit(out);
     generateCppTag(out, "android::hardware::details::bnhw_tag");
 
     out << "::android::sp<" << iface->localName() << "> getImpl() { return _hidl_mImpl; }\n";
@@ -725,6 +787,8 @@
         << "\n\n";
 
     generateTemplatizationLink(out);
+    DocComment("Type tag for use in template logic that indicates this is a 'proxy' class.")
+            .emit(out);
     generateCppTag(out, "android::hardware::details::bphw_tag");
 
     out << "virtual bool isRemote() const override { return true; }\n\n";
@@ -779,9 +843,12 @@
         << mPackage.string() << "::" << baseName
         << "\"\n\n";
 
-    out << "#include <android/log.h>\n";
+    out << "#include <log/log.h>\n";
     out << "#include <cutils/trace.h>\n";
     out << "#include <hidl/HidlTransportSupport.h>\n\n";
+    out << "#include <hidl/Static.h>\n";
+    out << "#include <hwbinder/ProcessState.h>\n";
+    out << "#include <utils/Trace.h>\n";
     if (iface) {
         // This is a no-op for IServiceManager itself.
         out << "#include <android/hidl/manager/1.0/IServiceManager.h>\n";
@@ -998,7 +1065,7 @@
 }
 
 void AST::generateStaticProxyMethodSource(Formatter& out, const std::string& klassName,
-                                          const Method* method) const {
+                                          const Method* method, const Interface* superInterface) const {
     if (method->isHidlReserved() && method->overridesCppImpl(IMPL_PROXY)) {
         return;
     }
@@ -1037,7 +1104,8 @@
     generateCppInstrumentationCall(
             out,
             InstrumentationEvent::CLIENT_API_ENTRY,
-            method);
+            method,
+            superInterface);
 
     out << "::android::hardware::Parcel _hidl_data;\n";
     out << "::android::hardware::Parcel _hidl_reply;\n";
@@ -1091,7 +1159,7 @@
         << " */, _hidl_data, &_hidl_reply";
 
     if (method->isOneway()) {
-        out << ", " << Interface::FLAG_ONEWAY << " /* oneway */";
+        out << ", " << Interface::FLAG_ONE_WAY->cppValue();
     }
     out << ");\n";
 
@@ -1144,7 +1212,8 @@
     generateCppInstrumentationCall(
             out,
             InstrumentationEvent::CLIENT_API_EXIT,
-            method);
+            method,
+            superInterface);
 
     if (elidedReturn != nullptr) {
         out << "_hidl_status.setFromStatusT(_hidl_err);\n";
@@ -1198,8 +1267,8 @@
     out << "}\n\n";
 
     generateMethods(out,
-                    [&](const Method* method, const Interface*) {
-                        generateStaticProxyMethodSource(out, klassName, method);
+                    [&](const Method* method, const Interface* superInterface) {
+                        generateStaticProxyMethodSource(out, klassName, method, superInterface);
                     },
                     false /* include parents */);
 
@@ -1234,11 +1303,12 @@
         << "\") { \n";
     out.indent();
     out << "_hidl_mImpl = _hidl_impl;\n";
-    out << "auto prio = ::android::hardware::details::gServicePrioMap.get("
+    out << "auto prio = ::android::hardware::details::gServicePrioMap->get("
         << "_hidl_impl, {SCHED_NORMAL, 0});\n";
     out << "mSchedPolicy = prio.sched_policy;\n";
     out << "mSchedPriority = prio.prio;\n";
-    out << "setRequestingSid(::android::hardware::details::gServiceSidMap.get(_hidl_impl, false));\n";
+    out << "setRequestingSid(::android::hardware::details::gServiceSidMap->get(_hidl_impl, "
+           "false));\n";
     out.unindent();
 
     out.unindent();
@@ -1271,12 +1341,14 @@
 
     out << klassName << "::~" << klassName << "() ";
     out.block([&]() {
-        out << "::android::hardware::details::gBnMap.eraseIfEqual(_hidl_mImpl.get(), this);\n";
-    }).endl().endl();
+           out << "::android::hardware::details::gBnMap->eraseIfEqual(_hidl_mImpl.get(), this);\n";
+       })
+            .endl()
+            .endl();
 
     generateMethods(out,
-                    [&](const Method* method, const Interface*) {
-                        return generateStaticStubMethodSource(out, iface->fqName(), method);
+                    [&](const Method* method, const Interface* superInterface) {
+                        return generateStaticStubMethodSource(out, iface->fqName(), method, superInterface);
                     },
                     false /* include parents */);
 
@@ -1323,8 +1395,8 @@
 
         out.indent();
 
-        out << "bool _hidl_is_oneway = _hidl_flags & " << Interface::FLAG_ONEWAY
-            << " /* oneway */;\n";
+        out << "bool _hidl_is_oneway = _hidl_flags & " << Interface::FLAG_ONE_WAY->cppValue()
+            << ";\n";
         out << "if (_hidl_is_oneway != " << (method->isOneway() ? "true" : "false") << ") ";
         out.block([&] { out << "return ::android::UNKNOWN_ERROR;\n"; }).endl().endl();
 
@@ -1394,7 +1466,7 @@
 }
 
 void AST::generateStaticStubMethodSource(Formatter& out, const FQName& fqName,
-                                         const Method* method) const {
+                                         const Method* method, const Interface* superInterface) const {
     if (method->isHidlReserved() && method->overridesCppImpl(IMPL_STUB)) {
         return;
     }
@@ -1459,7 +1531,8 @@
     generateCppInstrumentationCall(
             out,
             InstrumentationEvent::SERVER_API_ENTRY,
-            method);
+            method,
+            superInterface);
 
     const bool returnsValue = !method->results().empty();
     const NamedReference<Type>* elidedReturn = method->canElideCallback();
@@ -1488,6 +1561,7 @@
         });
 
         out << ");\n\n";
+
         out << "::android::hardware::writeToParcel(::android::hardware::Status::ok(), "
             << "_hidl_reply);\n\n";
 
@@ -1511,7 +1585,8 @@
         generateCppInstrumentationCall(
                 out,
                 InstrumentationEvent::SERVER_API_EXIT,
-                method);
+                method,
+            superInterface);
 
         out << "_hidl_cb(*_hidl_reply);\n";
     } else {
@@ -1519,7 +1594,8 @@
             out << "bool _hidl_callbackCalled = false;\n\n";
         }
 
-        out << callee << "->" << method->name() << "(";
+        out << "::android::hardware::Return<void> _hidl_ret = " << callee << "->" << method->name()
+            << "(";
 
         out.join(method->args().begin(), method->args().end(), ", ", [&] (const auto &arg) {
             if (arg->type().resultNeedsDeref()) {
@@ -1581,7 +1657,8 @@
             generateCppInstrumentationCall(
                     out,
                     InstrumentationEvent::SERVER_API_EXIT,
-                    method);
+                    method,
+                    superInterface);
 
             out << "_hidl_cb(*_hidl_reply);\n";
 
@@ -1593,9 +1670,12 @@
             generateCppInstrumentationCall(
                     out,
                     InstrumentationEvent::SERVER_API_EXIT,
-                    method);
+                    method,
+                    superInterface);
         }
 
+        out << "_hidl_ret.assertOk();\n";
+
         if (returnsValue) {
             out << "if (!_hidl_callbackCalled) {\n";
             out.indent();
@@ -1669,8 +1749,8 @@
     generateTemplatizationLink(out);
     generateCppTag(out, "android::hardware::details::bs_tag");
 
-    generateMethods(out, [&](const Method* method, const Interface*) {
-        generatePassthroughMethod(out, method);
+    generateMethods(out, [&](const Method* method, const Interface* superInterface) {
+        generatePassthroughMethod(out, method, superInterface);
     });
 
     out.unindent();
@@ -1721,7 +1801,7 @@
     });
 
     for (const Interface *superType : iface->typeChain()) {
-        out << "// static \n::android::hardware::Return<"
+        out << "::android::hardware::Return<"
             << childTypeResult
             << "> "
             << iface->localName()
@@ -1813,12 +1893,6 @@
                 << baseString + "::server\");\n";
             break;
         }
-        case CLIENT_API_ENTRY:
-        {
-            out << "atrace_begin(ATRACE_TAG_HAL, \""
-                << baseString + "::client\");\n";
-            break;
-        }
         case PASSTHROUGH_ENTRY:
         {
             out << "atrace_begin(ATRACE_TAG_HAL, \""
@@ -1826,12 +1900,21 @@
             break;
         }
         case SERVER_API_EXIT:
-        case CLIENT_API_EXIT:
         case PASSTHROUGH_EXIT:
         {
             out << "atrace_end(ATRACE_TAG_HAL);\n";
             break;
         }
+        // client uses scope because of gotos
+        // this isn't done for server because the profiled code isn't alone in its scope
+        // this isn't done for passthrough becuase the profiled boundary isn't even in the same code
+        case CLIENT_API_ENTRY: {
+            out << "::android::ScopedTrace PASTE(___tracer, __LINE__) (ATRACE_TAG_HAL, \""
+                << baseString + "::client\");\n";
+            break;
+        }
+        case CLIENT_API_EXIT:
+            break;
         default:
         {
             CHECK(false) << "Unsupported instrumentation event: " << event;
@@ -1842,7 +1925,8 @@
 void AST::generateCppInstrumentationCall(
         Formatter &out,
         InstrumentationEvent event,
-        const Method *method) const {
+        const Method *method,
+        const Interface* superInterface) const {
     generateCppAtraceCall(out, event, method);
 
     out << "#ifdef __ANDROID_DEBUGGABLE__\n";
@@ -1920,18 +2004,16 @@
         }
     }
 
-    const Interface* iface = mRootScope.getInterface();
-
     out << "for (const auto &callback: mInstrumentationCallbacks) {\n";
     out.indent();
     out << "callback("
         << event_str
         << ", \""
-        << mPackage.package()
+        << superInterface->fqName().package()
         << "\", \""
-        << mPackage.version()
+        << superInterface->fqName().version()
         << "\", \""
-        << iface->localName()
+        << superInterface->localName()
         << "\", \""
         << method->name()
         << "\", &_hidl_args);\n";
diff --git a/generateCppAdapter.cpp b/generateCppAdapter.cpp
index 1fbfb57..1d88d3c 100644
--- a/generateCppAdapter.cpp
+++ b/generateCppAdapter.cpp
@@ -54,7 +54,7 @@
             out << "public:\n";
             out << "typedef " << mockName << " Pure;\n";
 
-            out << klassName << "(::android::sp<" << mockName << "> impl);\n";
+            out << klassName << "(const ::android::sp<" << mockName << ">& impl);\n";
 
             generateMethods(out, [&](const Method* method, const Interface* /* interface */) {
                 if (method->isHidlReserved()) {
@@ -103,8 +103,8 @@
 
         const std::string mockName = getInterface()->fqName().cppName();
 
-        out << klassName << "::" << klassName << "(::android::sp<" << mockName
-            << "> impl) : mImpl(impl) {}";
+        out << klassName << "::" << klassName << "(const ::android::sp<" << mockName
+            << ">& impl) : mImpl(impl) {}";
 
         generateMethods(out, [&](const Method* method, const Interface* /* interface */) {
             generateAdapterMethod(out, method);
@@ -128,13 +128,6 @@
             return;
         }
 
-        // TODO(b/66900959): if we are creating the adapter for a 1.1 IFoo
-        // and we are using a method that takes/returns a 1.0 Callback, but
-        // there exists a 1.1 Callback (or other subclass that is depended
-        // on by this module), then wrap with the adapter subclass adapter
-        // IFF that callback is a subclass. However, if the callback
-        // is 1.0 ICallback, then wrap with a 1.0 adapter.
-
         const Interface* interface = static_cast<const Interface*>(type);
         out << "static_cast<::android::sp<" << interface->fqName().cppName() << ">>("
             << interface->fqName().cppName() << "::castFrom("
diff --git a/generateCppImpl.cpp b/generateCppImpl.cpp
index 18fac6b..1485119 100644
--- a/generateCppImpl.cpp
+++ b/generateCppImpl.cpp
@@ -77,10 +77,8 @@
     const Interface* iface = mRootScope.getInterface();
     const std::string baseName = iface->getBaseName();
 
-    const std::string guard = makeHeaderGuard(baseName, false /* indicateGenerated */);
-
-    out << "#ifndef " << guard << "\n";
-    out << "#define " << guard << "\n\n";
+    out << "// FIXME: your file license if you have one\n\n";
+    out << "#pragma once\n\n";
 
     generateCppPackageInclude(out, mPackage, iface->localName());
 
@@ -131,8 +129,6 @@
 
     out << "}  // namespace implementation\n";
     enterLeaveNamespace(out, false /* leave */);
-
-    out << "\n#endif  // " << guard << "\n";
 }
 
 void AST::generateCppImplSource(Formatter& out) const {
@@ -144,6 +140,7 @@
     const Interface* iface = mRootScope.getInterface();
     const std::string baseName = iface->getBaseName();
 
+    out << "// FIXME: your file license if you have one\n\n";
     out << "#include \"" << baseName << ".h\"\n\n";
 
     enterLeaveNamespace(out, true /* enter */);
diff --git a/generateDependencies.cpp b/generateDependencies.cpp
new file mode 100644
index 0000000..7c47c43
--- /dev/null
+++ b/generateDependencies.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include "AST.h"
+
+#include <android-base/logging.h>
+#include <hidl-util/Formatter.h>
+#include <string>
+#include <vector>
+
+#include "NamedType.h"
+#include "Type.h"
+
+namespace android {
+
+void AST::generateDependencies(Formatter& out) const {
+    std::unordered_set<const Type*> visited;
+    (void)mRootScope.recursivePass(
+        Type::ParseStage::COMPLETED,
+        [&](const Type* type) {
+            if (type != &mRootScope && type->isNamedType()) {
+                out << static_cast<const NamedType*>(type)->fqName().string() << "\n";
+            }
+            return OK;
+        },
+        &visited);
+}
+
+}  // namespace android
diff --git a/generateJava.cpp b/generateJava.cpp
index bdbe25a..b746cc3 100644
--- a/generateJava.cpp
+++ b/generateJava.cpp
@@ -71,6 +71,19 @@
         const std::string& ifaceName,
         const std::string& fqName,
         bool isRetry) {
+    if (isRetry) {
+        DocComment(
+                "This will invoke the equivalent of the C++ getService(std::string) if retry is\n"
+                "true or tryGetService(std::string) if retry is false. If the service is\n"
+                "available on the device and retry is true, this will wait for the service to\n"
+                "start. Otherwise, it will return immediately even if the service is null.")
+                .emit(out);
+    } else {
+        DocComment(
+                "Warning: this will not wait for the interface to come up if it hasn't yet\n"
+                "started. See getService(String,boolean) instead.")
+                .emit(out);
+    }
     out << "public static "
         << ifaceName
         << " getService(String serviceName";
@@ -90,6 +103,14 @@
         out << "));\n";
     }).endl().endl();
 
+    if (isRetry) {
+        DocComment("Calls getService(\"default\",retry).").emit(out);
+    } else {
+        DocComment(
+                "Warning: this will not wait for the interface to come up if it hasn't yet "
+                "started. See getService(String,boolean) instead.")
+                .emit(out);
+    }
     out << "public static "
         << ifaceName
         << " getService(";
@@ -128,9 +149,11 @@
 
     const Interface *superType = iface->superType();
 
+    iface->emitDocComment(out);
+
     out << "public interface " << ifaceName << " extends ";
 
-    if (superType != NULL) {
+    if (superType != nullptr) {
         out << superType->fullJavaName();
     } else {
         out << "android.os.IHwInterface";
@@ -139,12 +162,14 @@
     out << " {\n";
     out.indent();
 
+    DocComment("Fully-qualified interface name for this interface.").emit(out);
     out << "public static final String kInterfaceName = \""
         << mPackage.string()
         << "::"
         << ifaceName
         << "\";\n\n";
 
+    DocComment("Does a checked conversion from a binder to this class.").emit(out);
     out << "/* package private */ static "
         << ifaceName
         << " asInterface(android.os.IHwBinder binder) {\n";
@@ -196,6 +221,7 @@
     out.unindent();
     out << "}\n\n";
 
+    DocComment("Does a checked conversion from any interface to this class.").emit(out);
     out << "public static "
         << ifaceName
         << " castFrom(android.os.IHwInterface iface) {\n";
@@ -213,13 +239,9 @@
     emitGetService(out, ifaceName, iface->fqName().string(), true /* isRetry */);
     emitGetService(out, ifaceName, iface->fqName().string(), false /* isRetry */);
 
-    emitJavaTypeDeclarations(out);
+    iface->emitJavaTypeDeclarations(out, false /* atTopLevel */);
 
     for (const auto &method : iface->methods()) {
-        if (method->isHiddenFromJava()) {
-            continue;
-        }
-
         const bool returnsValue = !method->results().empty();
         const bool needsCallback = method->results().size() > 1;
 
@@ -312,10 +334,6 @@
     for (const auto &tuple : iface->allMethodsFromRoot()) {
         const Method *method = tuple.method();
 
-        if (method->isHiddenFromJava()) {
-            continue;
-        }
-
         const Interface *superInterface = tuple.interface();
         if (prevInterface != superInterface) {
             out << "// Methods from "
@@ -383,7 +401,7 @@
                 << " */, _hidl_request, _hidl_reply, ";
 
             if (method->isOneway()) {
-                out << Interface::FLAG_ONEWAY << " /* oneway */";
+                out << Interface::FLAG_ONE_WAY->javaValue();
             } else {
                 out << "0 /* flags */";
             }
@@ -457,19 +475,15 @@
     out << "}\n\n";
 
     for (Method *method : iface->hidlReservedMethods()) {
-        if (method->isHiddenFromJava()) {
-            continue;
-        }
-
         // b/32383557 this is a hack. We need to change this if we have more reserved methods.
         CHECK_LE(method->results().size(), 1u);
         std::string resultType = method->results().size() == 0 ? "void" :
                 method->results()[0]->type().getJavaType();
-        out << "@Override\npublic final "
-            << resultType
-            << " "
-            << method->name()
-            << "(";
+
+        bool canBeOverriden = method->name() == "debug";
+
+        out << "@Override\npublic " << (canBeOverriden ? "" : "final ") << resultType << " "
+            << method->name() << "(";
         method->emitJavaArgSignature(out);
         out << ") {\n";
 
@@ -535,8 +549,8 @@
 
         out.indent();
 
-        out << "boolean _hidl_is_oneway = (_hidl_flags & " << Interface::FLAG_ONEWAY
-            << " /* oneway */) != 0\n;";
+        out << "boolean _hidl_is_oneway = (_hidl_flags & " << Interface::FLAG_ONE_WAY->javaValue()
+            << ") != 0;\n";
         out << "if (_hidl_is_oneway != " << (method->isOneway() ? "true" : "false") << ") ";
         out.block([&] {
             out << "_hidl_reply.writeStatus(" << UNKNOWN_ERROR << ");\n";
@@ -556,19 +570,6 @@
             << superInterface->fullJavaName()
             << ".kInterfaceName);\n\n";
 
-        if (method->isHiddenFromJava()) {
-            // This is a method hidden from the Java side of things, it must not
-            // return any value and will simply signal success.
-            CHECK(!returnsValue);
-
-            out << "_hidl_reply.writeStatus(android.os.HwParcel.STATUS_SUCCESS);\n";
-            out << "_hidl_reply.send();\n";
-            out << "break;\n";
-            out.unindent();
-            out << "}\n\n";
-            continue;
-        }
-
         for (const auto &arg : method->args()) {
             emitJavaReaderWriter(
                     out,
@@ -671,8 +672,4 @@
     out << "}\n";
 }
 
-void AST::emitJavaTypeDeclarations(Formatter& out) const {
-    mRootScope.emitJavaTypeDeclarations(out, false /* atTopLevel */);
-}
-
 }  // namespace android
diff --git a/generateVts.cpp b/generateVts.cpp
index 8a37ae3..722a656 100644
--- a/generateVts.cpp
+++ b/generateVts.cpp
@@ -53,12 +53,12 @@
     const Interface *iface = AST::getInterface();
 
     out << "component_class: HAL_HIDL\n";
-    out << "component_type_version: " << mPackage.version()
-        << "\n";
     out << "component_name: \""
         << (iface ? iface->localName() : "types")
         << "\"\n\n";
 
+    out << "component_type_version_major: " << mPackage.getPackageMajorVersion() << "\n";
+    out << "component_type_version_minor: " << mPackage.getPackageMinorVersion() << "\n";
     out << "package: \"" << mPackage.package() << "\"\n\n";
 
     // Generate import statement for all imported interface/types.
@@ -78,17 +78,15 @@
         out << "interface: {\n";
         out.indent();
 
-        std::vector<const Interface *> chain = iface->typeChain();
-
         // Generate all the attribute declarations first.
         emitVtsTypeDeclarations(out);
 
         // Generate all the method declarations.
-        for (auto it = chain.rbegin(); it != chain.rend(); ++it) {
-            const Interface *superInterface = *it;
-            superInterface->emitVtsMethodDeclaration(out);
+        for (const Interface* superInterface : iface->superTypeChain()) {
+            superInterface->emitVtsMethodDeclaration(out, true /*isInhereted*/);
         }
 
+        iface->emitVtsMethodDeclaration(out, false /*isInhereted*/);
         out.unindent();
         out << "}\n";
     } else {
diff --git a/hashing/Android.bp b/hashing/Android.bp
new file mode 100644
index 0000000..9f63a27
--- /dev/null
+++ b/hashing/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2018 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.
+
+cc_library {
+    name: "libhidl-gen-hash",
+    host_supported: true,
+    defaults: ["hidl-gen-defaults"],
+    srcs: ["Hash.cpp"],
+    export_include_dirs: ["include"],
+    shared_libs: [
+        "libbase",
+        "libcrypto",
+        "libssl",
+    ],
+}
diff --git a/Hash.cpp b/hashing/Hash.cpp
similarity index 78%
rename from Hash.cpp
rename to hashing/Hash.cpp
index 7eb315b..e88ce17 100644
--- a/Hash.cpp
+++ b/hashing/Hash.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "Hash.h"
+#include <hidl-hash/Hash.h>
 
 #include <algorithm>
 #include <fstream>
@@ -50,7 +50,7 @@
     getMutableHash(path).mHash = kEmptyHash;
 }
 
-static std::vector<uint8_t> sha256File(const std::string &path) {
+static std::vector<uint8_t> sha256File(const std::string& path) {
     std::ifstream stream(path);
     std::stringstream fileStream;
     fileStream << stream.rdbuf();
@@ -58,17 +58,14 @@
 
     std::vector<uint8_t> ret = std::vector<uint8_t>(SHA256_DIGEST_LENGTH);
 
-    SHA256(reinterpret_cast<const uint8_t *>(fileContent.c_str()),
-            fileContent.size(), ret.data());
+    SHA256(reinterpret_cast<const uint8_t*>(fileContent.c_str()), fileContent.size(), ret.data());
 
     return ret;
 }
 
-Hash::Hash(const std::string &path)
-  : mPath(path),
-    mHash(sha256File(path)) {}
+Hash::Hash(const std::string& path) : mPath(path), mHash(sha256File(path)) {}
 
-std::string Hash::hexString(const std::vector<uint8_t> &hash) {
+std::string Hash::hexString(const std::vector<uint8_t>& hash) {
     std::ostringstream s;
     s << std::hex << std::setfill('0');
     for (uint8_t i : hash) {
@@ -81,11 +78,11 @@
     return hexString(mHash);
 }
 
-const std::vector<uint8_t> &Hash::raw() const {
+const std::vector<uint8_t>& Hash::raw() const {
     return mHash;
 }
 
-const std::string &Hash::getPath() const {
+const std::string& Hash::getPath() const {
     return mPath;
 }
 
@@ -94,14 +91,11 @@
 #define SPACES " +"
 #define MAYBE_SPACES " *"
 #define OPTIONAL_COMMENT "(?:#.*)?"
-static const std::regex kHashLine(
-    "(?:"
-        MAYBE_SPACES HASH SPACES FQNAME MAYBE_SPACES
-    ")?"
-    OPTIONAL_COMMENT);
+static const std::regex kHashLine("(?:" MAYBE_SPACES HASH SPACES FQNAME MAYBE_SPACES
+                                  ")?" OPTIONAL_COMMENT);
 
 struct HashFile {
-    static const HashFile *parse(const std::string &path, std::string *err) {
+    static const HashFile* parse(const std::string& path, std::string* err) {
         static std::map<std::string, HashFile*> hashfiles;
         auto it = hashfiles.find(path);
 
@@ -112,7 +106,7 @@
         return it->second;
     }
 
-    std::vector<std::string> lookup(const std::string &fqName) const {
+    std::vector<std::string> lookup(const std::string& fqName) const {
         auto it = hashes.find(fqName);
 
         if (it == hashes.end()) {
@@ -122,18 +116,18 @@
         return it->second;
     }
 
-private:
-    static HashFile *readHashFile(const std::string &path, std::string *err) {
+   private:
+    static HashFile* readHashFile(const std::string& path, std::string* err) {
         std::ifstream stream(path);
         if (!stream) {
             return nullptr;
         }
 
-        HashFile *file = new HashFile();
+        HashFile* file = new HashFile();
         file->path = path;
 
         std::string line;
-        while(std::getline(stream, line)) {
+        while (std::getline(stream, line)) {
             std::smatch match;
             bool valid = std::regex_match(line, match, kHashLine);
 
@@ -164,13 +158,13 @@
     }
 
     std::string path;
-    std::map<std::string,std::vector<std::string>> hashes;
+    std::map<std::string, std::vector<std::string>> hashes;
 };
 
 std::vector<std::string> Hash::lookupHash(const std::string& path, const std::string& interfaceName,
                                           std::string* err, bool* fileExists) {
     *err = "";
-    const HashFile *file = HashFile::parse(path, err);
+    const HashFile* file = HashFile::parse(path, err);
 
     if (file == nullptr || err->size() > 0) {
         if (fileExists != nullptr) *fileExists = false;
@@ -182,4 +176,4 @@
     return file->lookup(interfaceName);
 }
 
-}  // android
+}  // namespace android
diff --git a/include_hash/hidl-hash/Hash.h b/hashing/include/hidl-hash/Hash.h
similarity index 80%
rename from include_hash/hidl-hash/Hash.h
rename to hashing/include/hidl-hash/Hash.h
index e443ef5..edb667e 100644
--- a/include_hash/hidl-hash/Hash.h
+++ b/hashing/include/hidl-hash/Hash.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef HIDL_GEN_HASH_HASH_H_
-#define HIDL_GEN_HASH_HASH_H_
+#pragma once
 
 #include <string>
 #include <vector>
@@ -26,7 +25,7 @@
     static const std::vector<uint8_t> kEmptyHash;
 
     // path to .hal file
-    static const Hash &getHash(const std::string &path);
+    static const Hash& getHash(const std::string& path);
     static void clearHash(const std::string& path);
 
     // returns matching hashes of interfaceName in path
@@ -36,14 +35,14 @@
                                                const std::string& interfaceName, std::string* err,
                                                bool* fileExists = nullptr);
 
-    static std::string hexString(const std::vector<uint8_t> &hash);
+    static std::string hexString(const std::vector<uint8_t>& hash);
     std::string hexString() const;
 
-    const std::vector<uint8_t> &raw() const;
-    const std::string &getPath() const;
+    const std::vector<uint8_t>& raw() const;
+    const std::string& getPath() const;
 
-private:
-    Hash(const std::string &path);
+   private:
+    Hash(const std::string& path);
 
     static Hash& getMutableHash(const std::string& path);
 
@@ -52,6 +51,3 @@
 };
 
 }  // namespace android
-
-#endif  // HIDL_GEN_HASH_HASH_H_
-
diff --git a/hidl-gen_l.ll b/hidl-gen_l.ll
index c94df01..835d8f6 100644
--- a/hidl-gen_l.ll
+++ b/hidl-gen_l.ll
@@ -68,11 +68,6 @@
 
 #define YY_USER_ACTION yylloc->step(); yylloc->columns(yyleng);
 
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wunused-parameter"
-#pragma clang diagnostic ignored "-Wdeprecated-register"
-#pragma clang diagnostic ignored "-Wregister"
-
 %}
 
 %option yylineno
@@ -111,6 +106,7 @@
 "import"            { return token::IMPORT; }
 "interface"         { return token::INTERFACE; }
 "package"           { return token::PACKAGE; }
+"safe_union"        { return token::SAFE_UNION; }
 "struct"            { return token::STRUCT; }
 "typedef"           { return token::TYPEDEF; }
 "union"             { return token::UNION; }
@@ -173,6 +169,7 @@
 "!="                { return(token::NEQ); }
 "?"                 { return('?'); }
 "@"                 { return('@'); }
+"#"                 { return('#'); }
 
 {COMPONENT}         { yylval->str = strdup(yytext); return token::IDENTIFIER; }
 {FQNAME}            { yylval->str = strdup(yytext); return token::FQNAME; }
@@ -193,8 +190,6 @@
 
 %%
 
-#pragma clang diagnostic pop
-
 namespace android {
 
 status_t parseFile(AST* ast, std::unique_ptr<FILE, std::function<void(FILE *)>> file) {
diff --git a/hidl-gen_y.yy b/hidl-gen_y.yy
index f445199..65f4323 100644
--- a/hidl-gen_y.yy
+++ b/hidl-gen_y.yy
@@ -107,13 +107,22 @@
     return true;
 }
 
+bool isValidCompoundTypeField(CompoundType::Style style, const std::string& identifier,
+                              std::string *errorMsg) {
+    // Unions don't support fix-up types; as such, they can't
+    // have name collisions with embedded read/write methods.
+    if (style == CompoundType::STYLE_UNION) { return true; }
+
+    return isValidStructField(identifier, errorMsg);;
+}
+
 bool isValidIdentifier(const std::string& identifier, std::string *errorMsg) {
     static const std::vector<std::string> keywords({
         "uint8_t", "uint16_t", "uint32_t", "uint64_t",
         "int8_t", "int16_t", "int32_t", "int64_t", "bool", "float", "double",
         "interface", "struct", "union", "string", "vec", "enum", "ref", "handle",
         "package", "import", "typedef", "generates", "oneway", "extends",
-        "fmq_sync", "fmq_unsync",
+        "fmq_sync", "fmq_unsync", "safe_union",
     });
     static const std::vector<std::string> cppKeywords({
         "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit",
@@ -234,6 +243,7 @@
 %token<str> STRING_LITERAL "string literal"
 %token<void> TYPEDEF "keyword `typedef`"
 %token<void> UNION "keyword `union`"
+%token<void> SAFE_UNION "keyword `safe_union`"
 %token<templatedType> TEMPLATED "templated type"
 %token<void> ONEWAY "keyword `oneway`"
 %token<str> UNKNOWN "unknown character"
@@ -262,6 +272,8 @@
 /* Precedence level 3, RTL; but we have to use %left here */
 %left UNARY_MINUS UNARY_PLUS '!' '~'
 
+%token '#'
+
 %type<docComment> doc_comments
 
 %type<str> error_stmt error
@@ -284,7 +296,7 @@
 %type<constantExpression> const_expr
 %type<enumValue> enum_value commentable_enum_value
 %type<enumValues> enum_values enum_declaration_body
-%type<typedVars> typed_vars
+%type<typedVars> typed_vars non_empty_typed_vars
 %type<typedVar> typed_var
 %type<method> method_declaration commentable_method_declaration
 %type<compoundStyle> struct_or_union_keyword
@@ -726,7 +738,8 @@
     ;
 
 const_expr
-    : INTEGER                   {
+    : INTEGER
+      {
           $$ = LiteralConstantExpression::tryParse($1);
 
           if ($$ == nullptr) {
@@ -747,6 +760,11 @@
           $$ = new ReferenceConstantExpression(
               Reference<LocalIdentifier>(*$1, convertYYLoc(@1)), $1->string());
       }
+    | fqname '#' IDENTIFIER
+      {
+          $$ = new AttributeConstantExpression(
+              Reference<Type>(*$1, convertYYLoc(@1)), $1->string(), $3);
+      }
     | const_expr '?' const_expr ':' const_expr
       {
           $$ = new TernaryConstantExpression($1, $3, $5);
@@ -834,7 +852,14 @@
       {
           $$ = new TypedVarVector();
       }
-    | typed_var
+    | non_empty_typed_vars
+      {
+          $$ = $1;
+      }
+    ;
+
+non_empty_typed_vars
+    : typed_var
       {
           $$ = new TypedVarVector();
           if (!$$->add($1)) {
@@ -843,7 +868,7 @@
               ast->addSyntaxError();
           }
       }
-    | typed_vars ',' typed_var
+    | non_empty_typed_vars ',' typed_var
       {
           $$ = $1;
           if (!$$->add($3)) {
@@ -876,6 +901,7 @@
 struct_or_union_keyword
     : STRUCT { $$ = CompoundType::STYLE_STRUCT; }
     | UNION { $$ = CompoundType::STYLE_UNION; }
+    | SAFE_UNION { $$ = CompoundType::STYLE_SAFE_UNION; }
     ;
 
 named_struct_or_union_declaration
@@ -929,8 +955,9 @@
           CHECK((*scope)->isCompoundType());
 
           std::string errorMsg;
-          if (static_cast<CompoundType *>(*scope)->style() == CompoundType::STYLE_STRUCT &&
-              !isValidStructField($2, &errorMsg)) {
+          auto style = static_cast<CompoundType *>(*scope)->style();
+
+          if (!isValidCompoundTypeField(style, $2, &errorMsg)) {
               std::cerr << "ERROR: " << errorMsg << " at "
                         << @2 << "\n";
               YYERROR;
@@ -942,9 +969,11 @@
           CHECK((*scope)->isCompoundType());
 
           std::string errorMsg;
-          if (static_cast<CompoundType *>(*scope)->style() == CompoundType::STYLE_STRUCT &&
-              $1 != nullptr && $1->isNamedType() &&
-              !isValidStructField(static_cast<NamedType*>($1)->localName().c_str(), &errorMsg)) {
+          auto style = static_cast<CompoundType *>(*scope)->style();
+
+          if ($1 != nullptr && $1->isNamedType() &&
+              !isValidCompoundTypeField(style, static_cast<NamedType*>(
+                        $1)->localName().c_str(), &errorMsg)) {
               std::cerr << "ERROR: " << errorMsg << " at "
                         << @2 << "\n";
               YYERROR;
diff --git a/host_utils/Android.bp b/host_utils/Android.bp
new file mode 100644
index 0000000..f6e80f8
--- /dev/null
+++ b/host_utils/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2018 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.
+
+cc_library_host_shared {
+    name: "libhidl-gen-host-utils",
+    defaults: ["hidl-gen-defaults"],
+    srcs: [
+        "Formatter.cpp",
+        "StringHelper.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+    ],
+
+    local_include_dirs: ["include/hidl-util"],
+    export_include_dirs: ["include"],
+}
diff --git a/utils/Formatter.cpp b/host_utils/Formatter.cpp
similarity index 96%
rename from utils/Formatter.cpp
rename to host_utils/Formatter.cpp
index 338cb17..b1bc22c 100644
--- a/utils/Formatter.cpp
+++ b/host_utils/Formatter.cpp
@@ -22,10 +22,10 @@
 
 namespace android {
 
-Formatter::Formatter() : mFile(NULL /* invalid */), mIndentDepth(0), mAtStartOfLine(true) {}
+Formatter::Formatter() : mFile(nullptr /* invalid */), mIndentDepth(0), mAtStartOfLine(true) {}
 
 Formatter::Formatter(FILE* file, size_t spacesPerIndent)
-    : mFile(file == NULL ? stdout : file),
+    : mFile(file == nullptr ? stdout : file),
       mIndentDepth(0),
       mSpacesPerIndent(spacesPerIndent),
       mAtStartOfLine(true) {}
@@ -34,7 +34,7 @@
     if (mFile != stdout) {
         fclose(mFile);
     }
-    mFile = NULL;
+    mFile = nullptr;
 }
 
 void Formatter::indent(size_t level) {
diff --git a/utils/StringHelper.cpp b/host_utils/StringHelper.cpp
similarity index 100%
rename from utils/StringHelper.cpp
rename to host_utils/StringHelper.cpp
diff --git a/utils/include/hidl-util/Formatter.h b/host_utils/include/hidl-util/Formatter.h
similarity index 100%
rename from utils/include/hidl-util/Formatter.h
rename to host_utils/include/hidl-util/Formatter.h
diff --git a/utils/include/hidl-util/StringHelper.h b/host_utils/include/hidl-util/StringHelper.h
similarity index 100%
rename from utils/include/hidl-util/StringHelper.h
rename to host_utils/include/hidl-util/StringHelper.h
diff --git a/main.cpp b/main.cpp
index dabbdaa..c03b6fc 100644
--- a/main.cpp
+++ b/main.cpp
@@ -16,6 +16,7 @@
 
 #include "AST.h"
 #include "Coordinator.h"
+#include "Interface.h"
 #include "Scope.h"
 
 #include <android-base/logging.h>
@@ -455,16 +456,15 @@
 bool isSystemProcessSupportedPackage(const FQName& fqName) {
     // Technically, so is hidl IBase + IServiceManager, but
     // these are part of libhidltransport.
-    return fqName.string() == "android.hardware.graphics.common@1.0" ||
-           fqName.string() == "android.hardware.graphics.common@1.1" ||
-           fqName.string() == "android.hardware.graphics.mapper@2.0" ||
-           fqName.string() == "android.hardware.graphics.mapper@2.1" ||
+    return fqName.inPackage("android.hardware.graphics.common") ||
+           fqName.inPackage("android.hardware.graphics.mapper") ||
            fqName.string() == "android.hardware.renderscript@1.0" ||
            fqName.string() == "android.hidl.memory.token@1.0" ||
-           fqName.string() == "android.hidl.memory@1.0";
+           fqName.string() == "android.hidl.memory@1.0" ||
+           fqName.string() == "android.hidl.safe_union@1.0";
 }
 
-bool isSystemPackage(const FQName &package) {
+bool isCoreAndroidPackage(const FQName& package) {
     return package.inPackage("android.hidl") ||
            package.inPackage("android.system") ||
            package.inPackage("android.frameworks") ||
@@ -534,8 +534,7 @@
 
 static status_t generateAndroidBpForPackage(Formatter& out, const FQName& packageFQName,
                                             const Coordinator* coordinator) {
-    CHECK(packageFQName.isValid() && !packageFQName.isFullyQualified() &&
-          packageFQName.name().empty());
+    CHECK(!packageFQName.isFullyQualified() && packageFQName.name().empty());
 
     std::vector<FQName> packageInterfaces;
 
@@ -552,7 +551,7 @@
     for (const auto& fqName : packageInterfaces) {
         AST* ast = coordinator->parse(fqName);
 
-        if (ast == NULL) {
+        if (ast == nullptr) {
             fprintf(stderr, "ERROR: Could not parse %s. Aborting.\n", fqName.string().c_str());
 
             return UNKNOWN_ERROR;
@@ -579,9 +578,15 @@
     err = isTestPackage(packageFQName, coordinator, &generateForTest);
     if (err != OK) return err;
 
-    bool isVndk = !generateForTest && isSystemPackage(packageFQName);
+    bool isCoreAndroid = isCoreAndroidPackage(packageFQName);
+
+    bool isVndk = !generateForTest && isCoreAndroid;
     bool isVndkSp = isVndk && isSystemProcessSupportedPackage(packageFQName);
 
+    // Currently, all platform-provided interfaces are in the VNDK, so if it isn't in the VNDK, it
+    // is device specific and so should be put in the product partition.
+    bool isProduct = !isCoreAndroid;
+
     std::string packageRoot;
     err = coordinator->getPackageRoot(packageFQName, &packageRoot);
     if (err != OK) return err;
@@ -595,9 +600,6 @@
             out << "owner: \"" << coordinator->getOwner() << "\",\n";
         }
         out << "root: \"" << packageRoot << "\",\n";
-        if (isHidlTransportPackage(packageFQName)) {
-            out << "core_interface: true,\n";
-        }
         if (isVndk) {
             out << "vndk: ";
             out.block([&]() {
@@ -607,6 +609,9 @@
                 }
             }) << ",\n";
         }
+        if (isProduct) {
+            out << "product_specific: true,\n";
+        }
         (out << "srcs: [\n").indent([&] {
            for (const auto& fqName : packageInterfaces) {
                out << "\"" << fqName.name() << ".hal\",\n";
@@ -619,25 +624,6 @@
                }
             }) << "],\n";
         }
-        if (typesAST != nullptr) {
-            (out << "types: [\n").indent([&] {
-                std::vector<NamedType *> subTypes = typesAST->getRootScope()->getSubTypes();
-                std::sort(
-                        subTypes.begin(),
-                        subTypes.end(),
-                        [](const NamedType *a, const NamedType *b) -> bool {
-                            return a->fqName() < b->fqName();
-                        });
-
-                for (const auto &type : subTypes) {
-                    if (type->isTypeDef()) {
-                        continue;
-                    }
-
-                    out << "\"" << type->localName() << "\",\n";
-                }
-            }) << "],\n";
-        }
         // Explicity call this out for developers.
         out << "gen_java: " << (genJavaLibrary ? "true" : "false") << ",\n";
         if (genJavaConstants) {
@@ -667,7 +653,7 @@
     for (const auto &fqName : packageInterfaces) {
         AST *ast = coordinator->parse(fqName);
 
-        if (ast == NULL) {
+        if (ast == nullptr) {
             fprintf(stderr,
                     "ERROR: Could not parse %s. Aborting.\n",
                     fqName.string().c_str());
@@ -678,6 +664,7 @@
         ast->getImportedPackages(&importedPackages);
     }
 
+    out << "// FIXME: your file license if you have one\n\n";
     out << "cc_library_shared {\n";
     out.indent([&] {
         out << "// FIXME: this should only be -impl for a passthrough hal.\n"
@@ -785,10 +772,8 @@
 FileGenerator::GenerationFunction generateExportHeaderForPackage(bool forJava) {
     return [forJava](Formatter& out, const FQName& packageFQName,
                      const Coordinator* coordinator) -> status_t {
-        CHECK(packageFQName.isValid()
-                && !packageFQName.package().empty()
-                && !packageFQName.version().empty()
-                && packageFQName.name().empty());
+        CHECK(!packageFQName.package().empty() && !packageFQName.version().empty() &&
+              packageFQName.name().empty());
 
         std::vector<FQName> packageInterfaces;
 
@@ -804,7 +789,7 @@
         for (const auto &fqName : packageInterfaces) {
             AST *ast = coordinator->parse(fqName);
 
-            if (ast == NULL) {
+            if (ast == nullptr) {
                 fprintf(stderr,
                         "ERROR: Could not parse %s. Aborting.\n",
                         fqName.string().c_str());
@@ -874,7 +859,7 @@
     AST* ast = coordinator->parse(fqName, {} /* parsed */,
                                   Coordinator::Enforce::NO_HASH /* enforcement */);
 
-    if (ast == NULL) {
+    if (ast == nullptr) {
         fprintf(stderr, "ERROR: Could not parse %s. Aborting.\n", fqName.string().c_str());
 
         return UNKNOWN_ERROR;
@@ -885,6 +870,31 @@
     return OK;
 }
 
+static status_t generateFunctionCount(Formatter& out, const FQName& fqName,
+                                      const Coordinator* coordinator) {
+    CHECK(fqName.isFullyQualified());
+
+    AST* ast = coordinator->parse(fqName, {} /* parsed */,
+                                  Coordinator::Enforce::NO_HASH /* enforcement */);
+
+    if (ast == nullptr) {
+        fprintf(stderr, "ERROR: Could not parse %s. Aborting.\n", fqName.string().c_str());
+        return UNKNOWN_ERROR;
+    }
+
+    const Interface* interface = ast->getInterface();
+    if (interface == nullptr) {
+        fprintf(stderr, "ERROR: Function count requires interface: %s.\n", fqName.string().c_str());
+        return UNKNOWN_ERROR;
+    }
+
+    // This is wrong for android.hidl.base@1.0::IBase, but in that case, it doesn't matter.
+    // This is just the number of APIs that are added.
+    out << fqName.string() << " " << interface->userDefinedMethods().size() << "\n";
+
+    return OK;
+}
+
 template <typename T>
 std::vector<T> operator+(const std::vector<T>& lhs, const std::vector<T>& rhs) {
     std::vector<T> ret;
@@ -1034,7 +1044,7 @@
     },
     {
         "c++-impl-headers",
-        "c++-impl but headers only",
+        "c++-impl but headers only.",
         OutputMode::NEEDS_DIR,
         Coordinator::Location::DIRECT,
         GenerationGranularity::PER_FILE,
@@ -1043,7 +1053,7 @@
     },
     {
         "c++-impl-sources",
-        "c++-impl but sources only",
+        "c++-impl but sources only.",
         OutputMode::NEEDS_DIR,
         Coordinator::Location::DIRECT,
         GenerationGranularity::PER_FILE,
@@ -1061,7 +1071,7 @@
     },
     {
         "c++-adapter-headers",
-        "c++-adapter but helper headers only",
+        "c++-adapter but helper headers only.",
         OutputMode::NEEDS_DIR,
         Coordinator::Location::GEN_OUTPUT,
         GenerationGranularity::PER_FILE,
@@ -1070,7 +1080,7 @@
     },
     {
         "c++-adapter-sources",
-        "c++-adapter but helper sources only",
+        "c++-adapter but helper sources only.",
         OutputMode::NEEDS_DIR,
         Coordinator::Location::GEN_OUTPUT,
         GenerationGranularity::PER_FILE,
@@ -1079,7 +1089,7 @@
     },
     {
         "c++-adapter-main",
-        "c++-adapter but the adapter binary source only",
+        "c++-adapter but the adapter binary source only.",
         OutputMode::NEEDS_DIR,
         Coordinator::Location::DIRECT,
         GenerationGranularity::PER_PACKAGE,
@@ -1174,13 +1184,43 @@
             },
         }
     },
+    {
+        "function-count",
+        "Prints the total number of functions added by the package or interface.",
+        OutputMode::NOT_NEEDED,
+        Coordinator::Location::STANDARD_OUT,
+        GenerationGranularity::PER_FILE,
+        validateForSource,
+        {
+            {
+                FileGenerator::generateForInterfaces,
+                nullptr /* file name for fqName */,
+                generateFunctionCount,
+            },
+        }
+    },
+    {
+        "dependencies",
+        "Prints all depended types.",
+        OutputMode::NOT_NEEDED,
+        Coordinator::Location::STANDARD_OUT,
+        GenerationGranularity::PER_FILE,
+        validateForSource,
+        {
+            {
+                FileGenerator::alwaysGenerate,
+                nullptr /* file name for fqName */,
+                astGenerationFunction(&AST::generateDependencies),
+            },
+        },
+    },
 };
 // clang-format on
 
 static void usage(const char *me) {
     fprintf(stderr,
             "usage: %s [-p <root path>] -o <output path> -L <language> [-O <owner>] (-r <interface "
-            "root>)+ [-v] [-d <depfile>] FQNAME...\n\n",
+            "root>)+ [-R] [-v] [-d <depfile>] FQNAME...\n\n",
             me);
 
     fprintf(stderr,
@@ -1194,6 +1234,7 @@
     fprintf(stderr, "         -O <owner>: The owner of the module for -Landroidbp(-impl)?.\n");
     fprintf(stderr, "         -o <output path>: Location to output files.\n");
     fprintf(stderr, "         -p <root path>: Android build root, defaults to $ANDROID_BUILD_TOP or pwd.\n");
+    fprintf(stderr, "         -R: Do not add default package roots if not specified in -r.\n");
     fprintf(stderr, "         -r <package:path root>: E.g., android.hardware:hardware/interfaces.\n");
     fprintf(stderr, "         -v: verbose output.\n");
     fprintf(stderr, "         -d <depfile>: location of depfile to write to.\n");
@@ -1214,9 +1255,10 @@
     const OutputHandler* outputFormat = nullptr;
     Coordinator coordinator;
     std::string outputPath;
+    bool suppressDefaultPackagePaths = false;
 
     int res;
-    while ((res = getopt(argc, argv, "hp:o:O:r:L:vd:")) >= 0) {
+    while ((res = getopt(argc, argv, "hp:o:O:r:L:vd:R")) >= 0) {
         switch (res) {
             case 'p': {
                 if (!coordinator.getRootPath().empty()) {
@@ -1276,6 +1318,11 @@
                 break;
             }
 
+            case 'R': {
+                suppressDefaultPackagePaths = true;
+                break;
+            }
+
             case 'L': {
                 if (outputFormat != nullptr) {
                     fprintf(stderr,
@@ -1365,15 +1412,24 @@
 
     coordinator.setOutputPath(outputPath);
 
-    coordinator.addDefaultPackagePath("android.hardware", "hardware/interfaces");
-    coordinator.addDefaultPackagePath("android.hidl", "system/libhidl/transport");
-    coordinator.addDefaultPackagePath("android.frameworks", "frameworks/hardware/interfaces");
-    coordinator.addDefaultPackagePath("android.system", "system/hardware/interfaces");
+    if (!suppressDefaultPackagePaths) {
+        coordinator.addDefaultPackagePath("android.hardware", "hardware/interfaces");
+        coordinator.addDefaultPackagePath("android.hidl", "system/libhidl/transport");
+        coordinator.addDefaultPackagePath("android.frameworks", "frameworks/hardware/interfaces");
+        coordinator.addDefaultPackagePath("android.system", "system/hardware/interfaces");
+    }
 
     for (int i = 0; i < argc; ++i) {
+        const char* arg = argv[i];
+
         FQName fqName;
-        if (!FQName::parse(argv[i], &fqName)) {
-            fprintf(stderr, "ERROR: Invalid fully-qualified name as argument: %s.\n", argv[i]);
+        if (!FQName::parse(arg, &fqName)) {
+            fprintf(stderr, "ERROR: Invalid fully-qualified name as argument: %s.\n", arg);
+            exit(1);
+        }
+
+        if (coordinator.getPackageInterfaceFiles(fqName, nullptr /*fileNames*/) != OK) {
+            fprintf(stderr, "ERROR: Could not get sources for %s.\n", arg);
             exit(1);
         }
 
diff --git a/scripts/gen-docs.sh b/scripts/gen-docs.sh
new file mode 100755
index 0000000..d56ba27
--- /dev/null
+++ b/scripts/gen-docs.sh
@@ -0,0 +1,84 @@
+#!/usr/bin/env bash
+
+# TODO: use the build system to generate docs once we can
+
+# e.x. convertFqName "android.hardware.foo@1.0::foo::IFoo" "\5" -> IFoo
+function convertFqName() {
+    # android.hardware.foo@1.0::foo::IFoo
+    # \1 => android.hardware.foo
+    # \3 => 1
+    # \4 => 0
+    # \5 => IFoo # optional capture group
+    local FQNAME="^(([a-zA-Z0-9]+\.)*[a-zA-Z0-9]+)@([0-9]+).([0-9]+)(::([a-zA-Z0-9]+))?$"
+
+    local fqName="$1"
+    local pattern="$2"
+
+    echo $1 | sed -E "s/$FQNAME/$2/g"
+}
+function assertInterfaceFqName() {
+    if [ -n "$(convertFqName "$1")" ]; then
+        echo "$1 is not an interface fqname"
+        return 1
+    fi
+}
+function javaTarget() { convertFqName "$1" "\1-V\3.\4-java"; }
+function cppTarget()  { convertFqName "$1" "\1@\3.\4"; }
+
+function hidl-gen-output() {
+    local out="$1"
+
+    while read -r fqName; do
+        echo "Generating output for $fqName."
+        hidl-gen -Lc++-headers -o "$out/cpp" "$fqName" || exit 1
+        hidl-gen -Ljava -o "$out/java" "$fqName" 2>/dev/null
+    done
+}
+
+function current-interfaces() {
+    [ $# = 1 ] || { echo "usage: current-interfaces <package root directory>" && return 1; }
+
+    local package_root="$1"
+    [ -d "$package_root" ] || { echo "current-interfaces: directory $package_root does not exist" && return 1; }
+
+    local current_file="$package_root/current.txt"
+    [ -f "$current_file" ] || { echo "current-interfaces: current file $current_file does not exist" && return 1; }
+
+    cat "$current_file" | cut -d '#' -f1 | awk '{print $2}' | sed "/^ *$/d" | sort | uniq
+}
+
+function google-interfaces() {
+    local roots=(
+        hardware/interfaces
+        system/hardware/interfaces
+        frameworks/hardware/interfaces
+        system/libhidl/transport
+    )
+
+    for root in "${roots[@]}"; do
+        current-interfaces "$ANDROID_BUILD_TOP/$root"
+    done | sort | uniq
+}
+
+function google-interface-packages() {
+    google-interfaces | cut -d ':' -f1 | sort | uniq
+}
+
+function hidl-doc-generate-sources() {
+    local outputDir="$1"
+    [ -z "$1" ] && outputDir="gen"
+
+    echo "Generating sources in $(realpath $outputDir)"
+
+    google-interface-packages | hidl-gen-output "$outputDir"
+
+    echo "Deleting implementation-related files from $outputDir."
+    rm $(find "$outputDir/cpp" -\( -name "IHw*.h" \
+                                   -o -name "BnHw*.h" \
+                                   -o -name "BpHw*.h" \
+                                   -o -name "Bs*.h" \
+                                   -o -name "hwtypes.h" \))
+
+    echo "Done: generated sources are in $outputDir"
+}
+
diff --git a/test/Android.bp b/test/Android.bp
new file mode 100644
index 0000000..1459fa6
--- /dev/null
+++ b/test/Android.bp
@@ -0,0 +1,3 @@
+hidl_package_root {
+    name: "hidl.tests",
+}
diff --git a/test/error_test/Android.bp b/test/error_test/Android.bp
index 42345b2..4c8c899 100644
--- a/test/error_test/Android.bp
+++ b/test/error_test/Android.bp
@@ -6,8 +6,6 @@
          "echo 'int main(){return 0;}' > $(genDir)/TODO_b_37575883.cpp",
     out: ["TODO_b_37575883.cpp"],
     srcs: [
-        "hidl_error_test.sh",
-
         "**/*.hal",
         "**/required_error",
     ],
diff --git a/test/error_test/hidl_error_test.sh b/test/error_test/hidl_error_test.sh
index 2f5d522..d747776 100755
--- a/test/error_test/hidl_error_test.sh
+++ b/test/error_test/hidl_error_test.sh
@@ -1,12 +1,17 @@
 #!/bin/bash
 
-if [ $# -ne 1 ]; then
+if [ $# -gt 1 ]; then
     echo "usage: hidl_error_test.sh hidl-gen_path"
     exit 1
 fi
 
-readonly HIDL_GEN_PATH=$1
-readonly HIDL_ERROR_TEST_DIR="$ANDROID_BUILD_TOP/system/tools/hidl/test/error_test"
+readonly HIDL_GEN_PATH=${1:-hidl-gen}
+readonly HIDL_ERROR_TEST_DIR="${ANDROID_BUILD_TOP:-.}/system/tools/hidl/test/error_test"
+
+if [ ! -d $HIDL_ERROR_TEST_DIR ]; then
+    echo "cannot find test directory: $HIDL_ERROR_TEST_DIR"
+    exit 1
+fi
 
 for dir in $(ls -d $HIDL_ERROR_TEST_DIR/*/); do
   package=$(basename $dir)
diff --git a/test/error_test/len_tag_wrong_type/1.0/IFoo.hal b/test/error_test/len_tag_wrong_type/1.0/IFoo.hal
new file mode 100644
index 0000000..9567c2d
--- /dev/null
+++ b/test/error_test/len_tag_wrong_type/1.0/IFoo.hal
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2018 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 test.missing_variable_type@1.0;
+
+interface IFoo {
+    struct A {};
+
+    enum B : uint32_t {
+        C = A#len, // bad (A is not an enum)
+    };
+};
diff --git a/test/error_test/len_tag_wrong_type/1.0/required_error b/test/error_test/len_tag_wrong_type/1.0/required_error
new file mode 100644
index 0000000..b064671
--- /dev/null
+++ b/test/error_test/len_tag_wrong_type/1.0/required_error
@@ -0,0 +1 @@
+len refers to struct A but should refer to an enum
\ No newline at end of file
diff --git a/test/error_test/no_empty_safe_union/1.0/required_error b/test/error_test/no_empty_safe_union/1.0/required_error
new file mode 100644
index 0000000..be35cb5
--- /dev/null
+++ b/test/error_test/no_empty_safe_union/1.0/required_error
@@ -0,0 +1 @@
+Safe union must contain at least two types to be useful
\ No newline at end of file
diff --git a/test/error_test/no_empty_safe_union/1.0/types.hal b/test/error_test/no_empty_safe_union/1.0/types.hal
new file mode 100644
index 0000000..05d319e
--- /dev/null
+++ b/test/error_test/no_empty_safe_union/1.0/types.hal
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2018 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 test.empty_generates@1.0;
+
+safe_union Empty {};
diff --git a/test/error_test/no_leading_comma_argument_list/1.0/IFoo.hal b/test/error_test/no_leading_comma_argument_list/1.0/IFoo.hal
new file mode 100644
index 0000000..a3946eb
--- /dev/null
+++ b/test/error_test/no_leading_comma_argument_list/1.0/IFoo.hal
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2018 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 test.no_leading_comma_argument_list@1.0;
+
+interface IFoo {
+    foo(, int32_t v);
+};
diff --git a/test/error_test/no_leading_comma_argument_list/1.0/required_error b/test/error_test/no_leading_comma_argument_list/1.0/required_error
new file mode 100644
index 0000000..b8795e6
--- /dev/null
+++ b/test/error_test/no_leading_comma_argument_list/1.0/required_error
@@ -0,0 +1 @@
+syntax error, unexpected ','
diff --git a/test/error_test/no_leading_comma_result_list/1.0/IFoo.hal b/test/error_test/no_leading_comma_result_list/1.0/IFoo.hal
new file mode 100644
index 0000000..dab99cb
--- /dev/null
+++ b/test/error_test/no_leading_comma_result_list/1.0/IFoo.hal
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2018 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 test.no_leading_comma_result_list@1.0;
+
+interface IFoo {
+    foo() generates (, int32_t v);
+};
diff --git a/test/error_test/no_leading_comma_result_list/1.0/required_error b/test/error_test/no_leading_comma_result_list/1.0/required_error
new file mode 100644
index 0000000..b8795e6
--- /dev/null
+++ b/test/error_test/no_leading_comma_result_list/1.0/required_error
@@ -0,0 +1 @@
+syntax error, unexpected ','
diff --git a/test/error_test/no_single_element_safe_union/1.0/required_error b/test/error_test/no_single_element_safe_union/1.0/required_error
new file mode 100644
index 0000000..be35cb5
--- /dev/null
+++ b/test/error_test/no_single_element_safe_union/1.0/required_error
@@ -0,0 +1 @@
+Safe union must contain at least two types to be useful
\ No newline at end of file
diff --git a/test/error_test/no_single_element_safe_union/1.0/types.hal b/test/error_test/no_single_element_safe_union/1.0/types.hal
new file mode 100644
index 0000000..b7ace5b
--- /dev/null
+++ b/test/error_test/no_single_element_safe_union/1.0/types.hal
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2018 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 test.empty_generates@1.0;
+
+safe_union Empty { uint8_t a; };
diff --git a/test/error_test/tag_name_does_not_exist/1.0/IFoo.hal b/test/error_test/tag_name_does_not_exist/1.0/IFoo.hal
new file mode 100644
index 0000000..196da53
--- /dev/null
+++ b/test/error_test/tag_name_does_not_exist/1.0/IFoo.hal
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2018 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 test.missing_variable_type@1.0;
+
+interface IFoo {
+    enum B : uint32_t{
+        C = B#tag_does_not_exist, // bad (this tag doesn't exist)
+    };
+};
diff --git a/test/error_test/tag_name_does_not_exist/1.0/required_error b/test/error_test/tag_name_does_not_exist/1.0/required_error
new file mode 100644
index 0000000..5c7ecba
--- /dev/null
+++ b/test/error_test/tag_name_does_not_exist/1.0/required_error
@@ -0,0 +1 @@
+tag_does_not_exist is not a supported tag
\ No newline at end of file
diff --git a/test/hidl_test/Android.bp b/test/hidl_test/Android.bp
index 73110a6..4308ed7 100644
--- a/test/hidl_test/Android.bp
+++ b/test/hidl_test/Android.bp
@@ -23,6 +23,7 @@
     // libs should be used on device.
     static_libs: [
         "libfootest",
+        "libhidl-gen-utils",
         "libpointertest",
         "android.hardware.tests.expression@1.0",
         "android.hardware.tests.foo@1.0",
@@ -34,6 +35,8 @@
         "android.hardware.tests.memory@1.0",
         "android.hardware.tests.multithread@1.0",
         "android.hardware.tests.trie@1.0",
+        "android.hardware.tests.safeunion.cpp@1.0",
+        "android.hardware.tests.safeunion@1.0",
     ],
 
     // impls should never be static, these are used only for testing purposes
@@ -49,6 +52,8 @@
         "android.hardware.tests.memory@1.0-impl",
         "android.hardware.tests.multithread@1.0-impl",
         "android.hardware.tests.trie@1.0-impl",
+        "android.hardware.tests.safeunion.cpp@1.0-impl",
+        "android.hardware.tests.safeunion@1.0-impl",
     ],
 
     group_static_libs: true,
diff --git a/test/hidl_test/Android.mk b/test/hidl_test/Android.mk
index f328223..e949ecd 100644
--- a/test/hidl_test/Android.mk
+++ b/test/hidl_test/Android.mk
@@ -19,6 +19,8 @@
 LOCAL_MODULE_CLASS := NATIVE_TESTS
 LOCAL_SRC_FILES := hidl_test_helper
 
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest64
+
 include $(BUILD_PREBUILT)
 
 include $(CLEAR_VARS)
@@ -26,19 +28,21 @@
 LOCAL_MODULE_CLASS := NATIVE_TESTS
 LOCAL_SRC_FILES := hidl_test
 
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest64
+
 LOCAL_REQUIRED_MODULES := \
     hidl_test_client \
     hidl_test_helper \
     hidl_test_servers
 
-LOCAL_REQUIRED_MODULES_arm64 := hidl_test_servers_32 hidl_test_client_32
-LOCAL_REQUIRED_MODULES_mips64 := hidl_test_servers_32 hidl_test_client_32
-LOCAL_REQUIRED_MODULES_x86_64 := hidl_test_servers_32 hidl_test_client_32
+ifneq ($(TARGET_2ND_ARCH),)
+LOCAL_REQUIRED_MODULES += hidl_test_servers$(TARGET_2ND_ARCH_MODULE_SUFFIX)
+LOCAL_REQUIRED_MODULES += hidl_test_client$(TARGET_2ND_ARCH_MODULE_SUFFIX)
+endif
 
 include $(BUILD_PREBUILT)
 
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := VtsHidlUnitTests
-VTS_CONFIG_SRC_DIR := system/tools/hidl/tests
 -include test/vts/tools/build/Android.host_config.mk
diff --git a/test/hidl_test/hidl_test.h b/test/hidl_test/hidl_test.h
index 0ad17ef..3a2f7f7 100644
--- a/test/hidl_test/hidl_test.h
+++ b/test/hidl_test/hidl_test.h
@@ -2,6 +2,7 @@
 #define HIDL_TEST_H_
 
 #include <android/hardware/tests/bar/1.0/IBar.h>
+#include <android/hardware/tests/baz/1.0/IBaz.h>
 #include <android/hardware/tests/hash/1.0/IHash.h>
 #include <android/hardware/tests/inheritance/1.0/IChild.h>
 #include <android/hardware/tests/inheritance/1.0/IFetcher.h>
@@ -10,11 +11,14 @@
 #include <android/hardware/tests/multithread/1.0/IMultithread.h>
 #include <android/hardware/tests/pointer/1.0/IGraph.h>
 #include <android/hardware/tests/pointer/1.0/IPointer.h>
+#include <android/hardware/tests/safeunion/1.0/ISafeUnion.h>
+#include <android/hardware/tests/safeunion/cpp/1.0/ICppSafeUnion.h>
 #include <android/hardware/tests/trie/1.0/ITrie.h>
 
 template <template <typename Type> class Service>
 void runOnEachServer(void) {
     using ::android::hardware::tests::bar::V1_0::IBar;
+    using ::android::hardware::tests::baz::V1_0::IBaz;
     using ::android::hardware::tests::hash::V1_0::IHash;
     using ::android::hardware::tests::inheritance::V1_0::IChild;
     using ::android::hardware::tests::inheritance::V1_0::IFetcher;
@@ -23,18 +27,23 @@
     using ::android::hardware::tests::multithread::V1_0::IMultithread;
     using ::android::hardware::tests::pointer::V1_0::IGraph;
     using ::android::hardware::tests::pointer::V1_0::IPointer;
+    using ::android::hardware::tests::safeunion::cpp::V1_0::ICppSafeUnion;
+    using ::android::hardware::tests::safeunion::V1_0::ISafeUnion;
     using ::android::hardware::tests::trie::V1_0::ITrie;
 
     Service<IMemoryTest>::run("memory");
     Service<IChild>::run("child");
     Service<IParent>::run("parent");
     Service<IFetcher>::run("fetcher");
+    Service<IBaz>::run("baz");
     Service<IBar>::run("foo");
     Service<IHash>::run("default");
     Service<IGraph>::run("graph");
     Service<IPointer>::run("pointer");
     Service<IMultithread>::run("multithread");
     Service<ITrie>::run("trie");
+    Service<ICppSafeUnion>::run("default");
+    Service<ISafeUnion>::run("safeunion");
 }
 
 #endif  // HIDL_TEST_H_
diff --git a/test/hidl_test/hidl_test_client.cpp b/test/hidl_test/hidl_test_client.cpp
index 718a32c..9a1adcb 100644
--- a/test/hidl_test/hidl_test_client.cpp
+++ b/test/hidl_test/hidl_test_client.cpp
@@ -3,10 +3,11 @@
 #include "FooCallback.h"
 #include "hidl_test.h"
 
+#include <android-base/file.h>
 #include <android-base/logging.h>
 
-#include <android/hidl/manager/1.1/IServiceManager.h>
 #include <android/hidl/manager/1.0/IServiceNotification.h>
+#include <android/hidl/manager/1.2/IServiceManager.h>
 
 #include <android/hidl/allocator/1.0/IAllocator.h>
 #include <android/hidl/memory/1.0/IMemory.h>
@@ -19,6 +20,7 @@
 #include <android/hardware/tests/bar/1.0/IComplicated.h>
 #include <android/hardware/tests/bar/1.0/IImportRules.h>
 #include <android/hardware/tests/baz/1.0/IBaz.h>
+#include <android/hardware/tests/expression/1.0/IExpression.h>
 #include <android/hardware/tests/foo/1.0/BnHwSimple.h>
 #include <android/hardware/tests/foo/1.0/BpHwSimple.h>
 #include <android/hardware/tests/foo/1.0/BsSimple.h>
@@ -32,6 +34,9 @@
 #include <android/hardware/tests/multithread/1.0/IMultithread.h>
 #include <android/hardware/tests/pointer/1.0/IGraph.h>
 #include <android/hardware/tests/pointer/1.0/IPointer.h>
+#include <android/hardware/tests/safeunion/1.0/IOtherInterface.h>
+#include <android/hardware/tests/safeunion/1.0/ISafeUnion.h>
+#include <android/hardware/tests/safeunion/cpp/1.0/ICppSafeUnion.h>
 #include <android/hardware/tests/trie/1.0/ITrie.h>
 
 #include <gtest/gtest.h>
@@ -51,10 +56,12 @@
 #include <condition_variable>
 #include <fstream>
 #include <future>
+#include <limits>
 #include <mutex>
 #include <random>
 #include <set>
 #include <sstream>
+#include <sys/stat.h>
 #include <thread>
 #include <type_traits>
 #include <unordered_set>
@@ -63,6 +70,7 @@
 
 #include <hidl-test/FooHelper.h>
 #include <hidl-test/PointerHelper.h>
+#include <hidl-util/FQName.h>
 
 #include <hidl/ServiceManagement.h>
 #include <hidl/Status.h>
@@ -90,55 +98,69 @@
 
 static HidlEnvironment *gHidlEnvironment = nullptr;
 
+using ::android::Condition;
+using ::android::DELAY_NS;
+using ::android::DELAY_S;
+using ::android::FQName;
+using ::android::MultiDimensionalToString;
+using ::android::Mutex;
+using ::android::ONEWAY_TOLERANCE_NS;
+using ::android::sp;
+using ::android::to_string;
+using ::android::TOLERANCE_NS;
+using ::android::wp;
+using ::android::hardware::GrantorDescriptor;
+using ::android::hardware::hidl_array;
+using ::android::hardware::hidl_death_recipient;
+using ::android::hardware::hidl_handle;
+using ::android::hardware::hidl_memory;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::HidlMemory;
+using ::android::hardware::MQDescriptor;
+using ::android::hardware::MQFlavor;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::tests::bar::V1_0::IBar;
+using ::android::hardware::tests::bar::V1_0::IComplicated;
+using ::android::hardware::tests::baz::V1_0::IBaz;
+using ::android::hardware::tests::expression::V1_0::IExpression;
 using ::android::hardware::tests::foo::V1_0::Abc;
 using ::android::hardware::tests::foo::V1_0::IFoo;
 using ::android::hardware::tests::foo::V1_0::IFooCallback;
 using ::android::hardware::tests::foo::V1_0::ISimple;
 using ::android::hardware::tests::foo::V1_0::implementation::FooCallback;
-using ::android::hardware::tests::bar::V1_0::IBar;
-using ::android::hardware::tests::bar::V1_0::IComplicated;
-using ::android::hardware::tests::baz::V1_0::IBaz;
 using ::android::hardware::tests::hash::V1_0::IHash;
+using ::android::hardware::tests::inheritance::V1_0::IChild;
 using ::android::hardware::tests::inheritance::V1_0::IFetcher;
 using ::android::hardware::tests::inheritance::V1_0::IGrandparent;
 using ::android::hardware::tests::inheritance::V1_0::IParent;
-using ::android::hardware::tests::inheritance::V1_0::IChild;
-using ::android::hardware::tests::pointer::V1_0::IGraph;
-using ::android::hardware::tests::pointer::V1_0::IPointer;
 using ::android::hardware::tests::memory::V1_0::IMemoryTest;
 using ::android::hardware::tests::multithread::V1_0::IMultithread;
+using ::android::hardware::tests::pointer::V1_0::IGraph;
+using ::android::hardware::tests::pointer::V1_0::IPointer;
+using ::android::hardware::tests::safeunion::cpp::V1_0::ICppSafeUnion;
+using ::android::hardware::tests::safeunion::V1_0::IOtherInterface;
+using ::android::hardware::tests::safeunion::V1_0::ISafeUnion;
 using ::android::hardware::tests::trie::V1_0::ITrie;
 using ::android::hardware::tests::trie::V1_0::TrieNode;
-using ::android::hardware::Return;
-using ::android::hardware::Void;
-using ::android::hardware::hidl_array;
-using ::android::hardware::hidl_death_recipient;
-using ::android::hardware::hidl_memory;
-using ::android::hardware::HidlMemory;
-using ::android::hardware::hidl_string;
-using ::android::hardware::hidl_vec;
 using ::android::hidl::allocator::V1_0::IAllocator;
 using ::android::hidl::base::V1_0::IBase;
-using ::android::hidl::manager::V1_1::IServiceManager;
 using ::android::hidl::manager::V1_0::IServiceNotification;
-using ::android::hidl::memory::V1_0::IMemory;
-using ::android::hidl::memory::token::V1_0::IMemoryToken;
+using ::android::hidl::manager::V1_2::IServiceManager;
 using ::android::hidl::memory::block::V1_0::MemoryBlock;
+using ::android::hidl::memory::token::V1_0::IMemoryToken;
+using ::android::hidl::memory::V1_0::IMemory;
 using ::android::hidl::token::V1_0::ITokenManager;
-using ::android::sp;
-using ::android::wp;
-using ::android::to_string;
-using ::android::Mutex;
-using ::android::MultiDimensionalToString;
-using ::android::Condition;
-using ::android::DELAY_S;
-using ::android::DELAY_NS;
-using ::android::TOLERANCE_NS;
-using ::android::ONEWAY_TOLERANCE_NS;
 using std::to_string;
 
+using HandleTypeSafeUnion = ISafeUnion::HandleTypeSafeUnion;
+using InterfaceTypeSafeUnion = ISafeUnion::InterfaceTypeSafeUnion;
+using LargeSafeUnion = ISafeUnion::LargeSafeUnion;
+using SmallSafeUnion = ISafeUnion::SmallSafeUnion;
+
 template <typename T>
-using hidl_enum_iterator = ::android::hardware::hidl_enum_iterator<T>;
+using hidl_enum_range = ::android::hardware::hidl_enum_range<T>;
 
 template <typename T>
 static inline ::testing::AssertionResult isOk(const ::android::hardware::Return<T> &ret) {
@@ -176,6 +198,32 @@
     return ss.str();
 }
 
+// does not check for fd equality
+static void checkNativeHandlesDataEquality(const native_handle_t* reference,
+                                           const native_handle_t* result) {
+    if (reference == nullptr || result == nullptr) {
+        EXPECT_EQ(reference, result);
+        return;
+    }
+
+    ASSERT_EQ(reference->version, result->version);
+    EXPECT_EQ(reference->numFds, result->numFds);
+    EXPECT_EQ(reference->numInts, result->numInts);
+
+    int offset = reference->numFds;
+    int numInts = reference->numInts;
+    EXPECT_ARRAYEQ(&(reference->data[offset]), &(result->data[offset]), numInts);
+}
+
+template <typename T, MQFlavor flavor>
+static void checkMQDescriptorEquality(const MQDescriptor<T, flavor>& expected,
+                                      const MQDescriptor<T, flavor>& actual) {
+    checkNativeHandlesDataEquality(expected.handle(), actual.handle());
+    EXPECT_EQ(expected.grantors().size(), actual.grantors().size());
+    EXPECT_EQ(expected.getQuantum(), actual.getQuantum());
+    EXPECT_EQ(expected.getFlags(), actual.getFlags());
+}
+
 struct Simple : public ISimple {
     Simple(int32_t cookie)
         : mCookie(cookie) {
@@ -270,6 +318,16 @@
     int32_t mCookie;
 };
 
+struct OtherInterface : public IOtherInterface {
+    Return<void> concatTwoStrings(const hidl_string& a, const hidl_string& b,
+                                  concatTwoStrings_cb _hidl_cb) override {
+        hidl_string result = std::string(a) + std::string(b);
+        _hidl_cb(result);
+
+        return Void();
+    }
+};
+
 struct ServiceNotification : public IServiceNotification {
     std::mutex mutex;
     std::condition_variable condition;
@@ -308,6 +366,7 @@
     sp<IMemoryTest> memoryTest;
     sp<IFetcher> fetcher;
     sp<IFoo> foo;
+    sp<IBaz> baz;
     sp<IBaz> dyingBaz;
     sp<IBar> bar;
     sp<IGraph> graphInterface;
@@ -315,6 +374,8 @@
     sp<IPointer> validationPointerInterface;
     sp<IMultithread> multithreadInterface;
     sp<ITrie> trieInterface;
+    sp<ICppSafeUnion> cppSafeunionInterface;
+    sp<ISafeUnion> safeunionInterface;
     TestMode mode;
     bool enableDelayMeasurementTests;
     HidlEnvironment(TestMode mode, bool enableDelayMeasurementTests) :
@@ -352,9 +413,13 @@
         ASSERT_NE(foo, nullptr);
         ASSERT_EQ(foo->isRemote(), mode == BINDERIZED);
 
+        baz = IBaz::getService("baz", mode == PASSTHROUGH /* getStub */);
+        ASSERT_NE(baz, nullptr);
+        ASSERT_EQ(baz->isRemote(), mode == BINDERIZED);
+
         dyingBaz = IBaz::getService("dyingBaz", mode == PASSTHROUGH /* getStub */);
-        ASSERT_NE(foo, nullptr);
-        ASSERT_EQ(foo->isRemote(), mode == BINDERIZED);
+        ASSERT_NE(dyingBaz, nullptr);
+        ASSERT_EQ(dyingBaz->isRemote(), mode == BINDERIZED);
 
         bar = IBar::getService("foo", mode == PASSTHROUGH /* getStub */);
         ASSERT_NE(bar, nullptr);
@@ -380,9 +445,18 @@
         trieInterface = ITrie::getService("trie", mode == PASSTHROUGH /* getStub */);
         ASSERT_NE(trieInterface, nullptr);
         ASSERT_EQ(trieInterface->isRemote(), mode == BINDERIZED);
+
+        cppSafeunionInterface =
+            ICppSafeUnion::getService("default", mode == PASSTHROUGH /* getStub */);
+        ASSERT_NE(cppSafeunionInterface, nullptr);
+        ASSERT_EQ(cppSafeunionInterface->isRemote(), mode == BINDERIZED);
+
+        safeunionInterface = ISafeUnion::getService("safeunion", mode == PASSTHROUGH /* getStub */);
+        ASSERT_NE(safeunionInterface, nullptr);
+        ASSERT_EQ(safeunionInterface->isRemote(), mode == BINDERIZED);
     }
 
-    virtual void SetUp() {
+    void SetUp() override {
         ALOGI("Environment setup beginning...");
         getServices();
         ALOGI("Environment setup complete.");
@@ -397,15 +471,18 @@
     sp<IMemoryTest> memoryTest;
     sp<IFetcher> fetcher;
     sp<IFoo> foo;
+    sp<IBaz> baz;
     sp<IBaz> dyingBaz;
     sp<IBar> bar;
     sp<IGraph> graphInterface;
     sp<IPointer> pointerInterface;
     sp<IPointer> validationPointerInterface;
     sp<ITrie> trieInterface;
+    sp<ICppSafeUnion> cppSafeunionInterface;
+    sp<ISafeUnion> safeunionInterface;
     TestMode mode = TestMode::PASSTHROUGH;
 
-    virtual void SetUp() override {
+    void SetUp() override {
         ALOGI("Test setup beginning...");
         manager = gHidlEnvironment->manager;
         tokenManager = gHidlEnvironment->tokenManager;
@@ -413,12 +490,15 @@
         memoryTest = gHidlEnvironment->memoryTest;
         fetcher = gHidlEnvironment->fetcher;
         foo = gHidlEnvironment->foo;
+        baz = gHidlEnvironment->baz;
         dyingBaz = gHidlEnvironment->dyingBaz;
         bar = gHidlEnvironment->bar;
         graphInterface = gHidlEnvironment->graphInterface;
         pointerInterface = gHidlEnvironment->pointerInterface;
         validationPointerInterface = gHidlEnvironment->validationPointerInterface;
         trieInterface = gHidlEnvironment->trieInterface;
+        cppSafeunionInterface = gHidlEnvironment->cppSafeunionInterface;
+        safeunionInterface = gHidlEnvironment->safeunionInterface;
         mode = gHidlEnvironment->mode;
         ALOGI("Test setup complete");
     }
@@ -435,26 +515,36 @@
     auto handle2 = native_handle_create(0, 1);
     handle->data[0] = 5;
     handle2->data[0] = 6;
-    IFoo::Everything e {
-        .u = {.p = reinterpret_cast<void *>(0x5)},
+    IFoo::Everything e{
+        .u = {.number = 3},
         .number = 10,
         .h = handle,
         .descSync = {std::vector<GrantorDescriptor>(), handle, 5},
         .descUnsync = {std::vector<GrantorDescriptor>(), handle2, 6},
         .mem = hidl_memory("mymem", handle, 5),
-        .p = reinterpret_cast<void *>(0x6),
+        .p = reinterpret_cast<void*>(0x6),
         .vs = {"hello", "world"},
         .multidimArray = hidl_vec<hidl_string>{"hello", "great", "awesome", "nice"}.data(),
         .sArray = hidl_vec<hidl_string>{"awesome", "thanks", "you're welcome"}.data(),
         .anotherStruct = {.first = "first", .last = "last"},
-        .bf = IFoo::BitField::V0 | IFoo::BitField::V2
-    };
+        .bf = IFoo::BitField::V0 | IFoo::BitField::V2};
     LOG(INFO) << toString(e);
     LOG(INFO) << toString(foo);
     // toString is for debugging purposes only; no good EXPECT
     // statement can be written here.
 }
 
+TEST_F(HidlTest, ConstantExpressionTest) {
+    // these tests are written so that these always evaluate to one
+
+    for (const auto value : hidl_enum_range<IExpression::OperatorSanityCheck>()) {
+        EXPECT_EQ(1, static_cast<int32_t>(value));
+    }
+    for (const auto value : hidl_enum_range<IExpression::EnumTagTest>()) {
+        EXPECT_EQ(1, static_cast<int32_t>(value));
+    }
+}
+
 TEST_F(HidlTest, PassthroughLookupTest) {
     // IFoo is special because it returns an interface no matter
     //   what instance name is requested. In general, this is BAD!
@@ -472,30 +562,52 @@
     using SkipsValues = ::android::hardware::tests::foo::V1_0::EnumIterators::SkipsValues;
     using MultipleValues = ::android::hardware::tests::foo::V1_0::EnumIterators::MultipleValues;
 
-    for (const auto value : hidl_enum_iterator<Empty>()) {
+    for (const auto value : hidl_enum_range<Empty>()) {
         (void)value;
-        EXPECT_TRUE(false) << "Empty iterator should not iterate";
+        ADD_FAILURE() << "Empty range should not iterate";
     }
 
-    auto it1 = hidl_enum_iterator<Grandchild>().begin();
+    EXPECT_EQ(hidl_enum_range<Grandchild>().begin(), hidl_enum_range<Grandchild>().cbegin());
+    EXPECT_EQ(hidl_enum_range<Grandchild>().end(), hidl_enum_range<Grandchild>().cend());
+    EXPECT_EQ(hidl_enum_range<Grandchild>().rbegin(), hidl_enum_range<Grandchild>().crbegin());
+    EXPECT_EQ(hidl_enum_range<Grandchild>().rend(), hidl_enum_range<Grandchild>().crend());
+
+    auto it1 = hidl_enum_range<Grandchild>().begin();
     EXPECT_EQ(Grandchild::A, *it1++);
     EXPECT_EQ(Grandchild::B, *it1++);
-    EXPECT_EQ(hidl_enum_iterator<Grandchild>().end(), it1);
+    EXPECT_EQ(hidl_enum_range<Grandchild>().end(), it1);
+    auto it1r = hidl_enum_range<Grandchild>().rbegin();
+    EXPECT_EQ(Grandchild::B, *it1r++);
+    EXPECT_EQ(Grandchild::A, *it1r++);
+    EXPECT_EQ(hidl_enum_range<Grandchild>().rend(), it1r);
 
-    auto it2 = hidl_enum_iterator<SkipsValues>().begin();
+    auto it2 = hidl_enum_range<SkipsValues>().begin();
     EXPECT_EQ(SkipsValues::A, *it2++);
     EXPECT_EQ(SkipsValues::B, *it2++);
     EXPECT_EQ(SkipsValues::C, *it2++);
     EXPECT_EQ(SkipsValues::D, *it2++);
     EXPECT_EQ(SkipsValues::E, *it2++);
-    EXPECT_EQ(hidl_enum_iterator<SkipsValues>().end(), it2);
+    EXPECT_EQ(hidl_enum_range<SkipsValues>().end(), it2);
+    auto it2r = hidl_enum_range<SkipsValues>().rbegin();
+    EXPECT_EQ(SkipsValues::E, *it2r++);
+    EXPECT_EQ(SkipsValues::D, *it2r++);
+    EXPECT_EQ(SkipsValues::C, *it2r++);
+    EXPECT_EQ(SkipsValues::B, *it2r++);
+    EXPECT_EQ(SkipsValues::A, *it2r++);
+    EXPECT_EQ(hidl_enum_range<SkipsValues>().rend(), it2r);
 
-    auto it3 = hidl_enum_iterator<MultipleValues>().begin();
+    auto it3 = hidl_enum_range<MultipleValues>().begin();
     EXPECT_EQ(MultipleValues::A, *it3++);
     EXPECT_EQ(MultipleValues::B, *it3++);
     EXPECT_EQ(MultipleValues::C, *it3++);
     EXPECT_EQ(MultipleValues::D, *it3++);
-    EXPECT_EQ(hidl_enum_iterator<MultipleValues>().end(), it3);
+    EXPECT_EQ(hidl_enum_range<MultipleValues>().end(), it3);
+    auto it3r = hidl_enum_range<MultipleValues>().rbegin();
+    EXPECT_EQ(MultipleValues::D, *it3r++);
+    EXPECT_EQ(MultipleValues::C, *it3r++);
+    EXPECT_EQ(MultipleValues::B, *it3r++);
+    EXPECT_EQ(MultipleValues::A, *it3r++);
+    EXPECT_EQ(hidl_enum_range<MultipleValues>().rend(), it3r);
 }
 
 TEST_F(HidlTest, EnumToStringTest) {
@@ -536,28 +648,6 @@
     ASSERT_NE(manager, nullptr);
 }
 
-TEST_F(HidlTest, HashTest) {
-    static constexpr uint64_t kHashSize = 32u;
-    // unreleased interface has an empty hash
-    uint8_t ihash[kHashSize] = {0};
-    uint8_t ibase[kHashSize] = {189, 218, 182, 24,  77,  122, 52,  109, 166, 160, 125,
-                                192, 130, 140, 241, 154, 105, 111, 76,  170, 54,  17,
-                                197, 31,  46,  20,  86,  90,  20,  180, 15,  217};
-    auto service = IHash::getService(mode == PASSTHROUGH /* getStub */);
-    EXPECT_OK(service->getHashChain([&](const auto& chain) {
-        ASSERT_EQ(chain.size(), 2u);
-        EXPECT_EQ(chain[0].size(), kHashSize);
-        EXPECT_ARRAYEQ(ihash, chain[0], kHashSize);
-        EXPECT_EQ(chain[1].size(), kHashSize);
-        EXPECT_ARRAYEQ(ibase, chain[1], kHashSize);
-    }));
-    EXPECT_OK(manager->getHashChain([&](const auto& managerChain) {
-        EXPECT_EQ(managerChain[managerChain.size() - 1].size(), kHashSize);
-        EXPECT_ARRAYEQ(ibase, managerChain[managerChain.size() - 1], kHashSize)
-            << "Hash for IBase doesn't match!";
-    }));
-}
-
 TEST_F(HidlTest, ServiceListTest) {
     static const std::set<std::string> binderizedSet = {
         "android.hardware.tests.pointer@1.0::IPointer/pointer",
@@ -632,6 +722,30 @@
         }));
 }
 
+TEST_F(HidlTest, ServiceListManifestByInterfaceTest) {
+    // system service
+    EXPECT_OK(manager->listManifestByInterface(IServiceManager::descriptor,
+                                               [](const hidl_vec<hidl_string>& registered) {
+                                                   ASSERT_EQ(1, registered.size());
+                                                   EXPECT_EQ("default", registered[0]);
+                                               }));
+    // vendor service (this is required on all devices)
+    EXPECT_OK(
+        manager->listManifestByInterface("android.hardware.configstore@1.0::ISurfaceFlingerConfigs",
+                                         [](const hidl_vec<hidl_string>& registered) {
+                                             ASSERT_EQ(1, registered.size());
+                                             EXPECT_EQ("default", registered[0]);
+                                         }));
+    // test service that will never be in a manifest
+    EXPECT_OK(manager->listManifestByInterface(
+        IParent::descriptor,
+        [](const hidl_vec<hidl_string>& registered) { ASSERT_EQ(0, registered.size()); }));
+    // invalid service
+    EXPECT_OK(manager->listManifestByInterface(
+        "!(*#&$ASDASLKDJasdlkjfads",
+        [](const hidl_vec<hidl_string>& registered) { ASSERT_EQ(0, registered.size()); }));
+}
+
 TEST_F(HidlTest, SubInterfaceServiceRegistrationTest) {
     using ::android::hardware::interfacesEqual;
 
@@ -675,7 +789,7 @@
 
     std::unique_lock<std::mutex> lock(notification->mutex);
 
-    notification->condition.wait_for(lock, std::chrono::milliseconds(2), [&notification]() {
+    notification->condition.wait_for(lock, std::chrono::milliseconds(500), [&notification]() {
         return notification->getRegistrations().size() >= 2;
     });
 
@@ -731,7 +845,7 @@
 
     std::unique_lock<std::mutex> lock(notification->mutex);
 
-    notification->condition.wait_for(lock, std::chrono::milliseconds(2), [&notification]() {
+    notification->condition.wait_for(lock, std::chrono::milliseconds(500), [&notification]() {
         return notification->getRegistrations().size() >= 2;
     });
 
@@ -747,6 +861,16 @@
         "['" + descriptor + "/" + instanceOne + "', '" + descriptor + "/" + instanceTwo + "']");
 }
 
+TEST_F(HidlTest, DebugDumpTest) {
+    EXPECT_OK(manager->debugDump([](const auto& list) {
+        for (const auto& debugInfo : list) {
+            FQName name;
+            EXPECT_TRUE(FQName::parse(debugInfo.interfaceName, &name)) << debugInfo.interfaceName;
+            EXPECT_TRUE(debugInfo.instanceName.size() > 0);
+        }
+    }));
+}
+
 TEST_F(HidlTest, InterfacesEqualTest) {
     using android::hardware::interfacesEqual;
 
@@ -1196,7 +1320,7 @@
     EXPECT_OK(foo->haveATypeFromAnotherFile(abcParam));
     ALOGI("CLIENT haveATypeFromAnotherFile returned.");
     native_handle_delete(handle);
-    abcParam.z = NULL;
+    abcParam.z = nullptr;
 }
 
 TEST_F(HidlTest, FooHaveSomeStringsTest) {
@@ -1404,6 +1528,26 @@
                 }));
 }
 
+TEST_F(HidlTest, StructWithFmq) {
+    IFoo::WithFmq w = {
+        .scatterGathered =
+            {
+                .descSync = {std::vector<GrantorDescriptor>(), native_handle_create(0, 1), 5},
+            },
+        .containsPointer =
+            {
+                .descSync = {std::vector<GrantorDescriptor>(), native_handle_create(0, 1), 5},
+                .foo = nullptr,
+            },
+    };
+    EXPECT_OK(foo->repeatWithFmq(w, [&](const IFoo::WithFmq& returned) {
+        checkMQDescriptorEquality(w.scatterGathered.descSync, returned.scatterGathered.descSync);
+        checkMQDescriptorEquality(w.containsPointer.descSync, returned.containsPointer.descSync);
+
+        EXPECT_EQ(w.containsPointer.foo, returned.containsPointer.foo);
+    }));
+}
+
 TEST_F(HidlTest, FooNonNullCallbackTest) {
     hidl_array<hidl_string, 5, 3> in;
 
@@ -1512,6 +1656,41 @@
     EXPECT_OK(foo->closeHandles());
 }
 
+TEST_F(HidlTest, BazStructWithInterfaceTest) {
+    using ::android::hardware::interfacesEqual;
+
+    const std::string testString = "Hello, World!";
+    const std::array<int8_t, 7> testArray{-1, -2, -3, 0, 1, 2, 3};
+    const hidl_vec<hidl_string> testStrings{"So", "Many", "Words"};
+    const hidl_vec<bool> testVector{false, true, false, true, true, true};
+
+    hidl_vec<bool> goldenResult(testVector.size());
+    for (size_t i = 0; i < testVector.size(); i++) {
+        goldenResult[i] = !testVector[i];
+    }
+
+    IBaz::StructWithInterface swi;
+    swi.number = 42;
+    swi.array = testArray;
+    swi.oneString = testString;
+    swi.vectorOfStrings = testStrings;
+    swi.dummy = baz;
+
+    EXPECT_OK(baz->haveSomeStructWithInterface(swi, [&](const IBaz::StructWithInterface& swiBack) {
+        EXPECT_EQ(42, swiBack.number);
+        for (size_t i = 0; i < testArray.size(); i++) {
+            EXPECT_EQ(testArray[i], swiBack.array[i]);
+        }
+
+        EXPECT_EQ(testString, std::string(swiBack.oneString));
+        EXPECT_EQ(testStrings, swiBack.vectorOfStrings);
+
+        EXPECT_TRUE(interfacesEqual(swi.dummy, swiBack.dummy));
+        EXPECT_OK(swiBack.dummy->someBoolVectorMethod(
+            testVector, [&](const hidl_vec<bool>& result) { EXPECT_EQ(goldenResult, result); }));
+    }));
+}
+
 struct HidlDeathRecipient : hidl_death_recipient {
     std::mutex mutex;
     std::condition_variable condition;
@@ -1519,7 +1698,7 @@
     bool fired = false;
     uint64_t cookie = 0;
 
-    virtual void serviceDied(uint64_t cookie, const wp<IBase>& who) {
+    void serviceDied(uint64_t cookie, const wp<IBase>& who) override {
         std::unique_lock<std::mutex> lock(mutex);
         fired = true;
         this->cookie = cookie;
@@ -1533,6 +1712,10 @@
     sp<HidlDeathRecipient> recipient2 = new HidlDeathRecipient();
 
     EXPECT_TRUE(dyingBaz->linkToDeath(recipient, 0x1481));
+
+    EXPECT_TRUE(dyingBaz->linkToDeath(recipient, 0x1482));
+    EXPECT_TRUE(dyingBaz->unlinkToDeath(recipient));
+
     EXPECT_TRUE(dyingBaz->linkToDeath(recipient2, 0x2592));
     EXPECT_TRUE(dyingBaz->unlinkToDeath(recipient2));
 
@@ -1549,6 +1732,9 @@
         //do nothing, this is expected
     }
 
+    // further calls fail
+    EXPECT_FAIL(dyingBaz->ping());
+
     std::unique_lock<std::mutex> lock(recipient->mutex);
     recipient->condition.wait_for(lock, std::chrono::milliseconds(100), [&recipient]() {
             return recipient->fired;
@@ -1709,31 +1895,44 @@
 
 TEST_F(HidlTest, InvalidTransactionTest) {
     using ::android::hardware::tests::bar::V1_0::BnHwBar;
-    using ::android::hardware::tests::bar::V1_0::BpHwBar;
     using ::android::hardware::IBinder;
     using ::android::hardware::Parcel;
-    using ::android::status_t;
-    using ::android::OK;
+
+    sp<IBinder> binder = ::android::hardware::toBinder(bar);
 
     Parcel request, reply;
-    sp<IBinder> binder;
-    status_t status = request.writeInterfaceToken(::android::hardware::tests::bar::V1_0::IBar::descriptor);
+    EXPECT_EQ(::android::OK, request.writeInterfaceToken(IBar::descriptor));
+    EXPECT_EQ(::android::UNKNOWN_TRANSACTION, binder->transact(1234, request, &reply));
 
-    EXPECT_EQ(status, OK);
+    EXPECT_OK(bar->ping());  // still works
+}
 
-    if (mode == BINDERIZED) {
-        EXPECT_TRUE(bar->isRemote());
-        binder = ::android::hardware::toBinder<IBar>(bar);
-    } else {
-        // For a local test, just wrap the implementation with a BnHwBar
-        binder = new BnHwBar(bar);
-    }
+TEST_F(HidlTest, EmptyTransactionTest) {
+    using ::android::hardware::IBinder;
+    using ::android::hardware::Parcel;
+    using ::android::hardware::tests::bar::V1_0::BnHwBar;
 
-    status = binder->transact(1234, request, &reply);
+    sp<IBinder> binder = ::android::hardware::toBinder(bar);
 
-    EXPECT_EQ(status, ::android::UNKNOWN_TRANSACTION);
-    // Try another call, to make sure nothing is messed up
-    EXPECT_OK(bar->thisIsNew());
+    Parcel request, reply;
+    EXPECT_EQ(::android::BAD_TYPE, binder->transact(2 /*someBoolMethod*/, request, &reply));
+
+    EXPECT_OK(bar->ping());  // still works
+}
+
+TEST_F(HidlTest, WrongDescriptorTest) {
+    using ::android::hardware::IBinder;
+    using ::android::hardware::Parcel;
+    using ::android::hardware::tests::bar::V1_0::BnHwBar;
+
+    sp<IBinder> binder = ::android::hardware::toBinder(bar);
+
+    Parcel request, reply;
+    // wrong descriptor
+    EXPECT_EQ(::android::OK, request.writeInterfaceToken("not a real descriptor"));
+    EXPECT_EQ(::android::BAD_TYPE, binder->transact(2 /*someBoolMethod*/, request, &reply));
+
+    EXPECT_OK(bar->ping());  // still works
 }
 
 TEST_F(HidlTest, TrieSimpleTest) {
@@ -1802,12 +2001,528 @@
     });
 }
 
+TEST_F(HidlTest, SafeUnionNoInitTest) {
+    EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& safeUnion) {
+        EXPECT_EQ(LargeSafeUnion::hidl_discriminator::noinit, safeUnion.getDiscriminator());
+    }));
+}
+
+TEST_F(HidlTest, SafeUnionSimpleTest) {
+    EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& safeUnion) {
+        EXPECT_OK(safeunionInterface->setA(safeUnion, -5, [&](const LargeSafeUnion& safeUnion) {
+            EXPECT_EQ(LargeSafeUnion::hidl_discriminator::a, safeUnion.getDiscriminator());
+            EXPECT_EQ(-5, safeUnion.a());
+
+            uint64_t max = std::numeric_limits<uint64_t>::max();
+            EXPECT_OK(
+                safeunionInterface->setD(safeUnion, max, [&](const LargeSafeUnion& safeUnion) {
+                    EXPECT_EQ(LargeSafeUnion::hidl_discriminator::d, safeUnion.getDiscriminator());
+                    EXPECT_EQ(max, safeUnion.d());
+                }));
+        }));
+    }));
+}
+
+TEST_F(HidlTest, SafeUnionArrayLikeTypesTest) {
+    const std::array<int64_t, 5> testArray{1, -2, 3, -4, 5};
+    const hidl_vec<uint64_t> testVector{std::numeric_limits<uint64_t>::max()};
+
+    EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& safeUnion) {
+        EXPECT_OK(
+            safeunionInterface->setF(safeUnion, testArray, [&](const LargeSafeUnion& safeUnion) {
+                EXPECT_EQ(LargeSafeUnion::hidl_discriminator::f, safeUnion.getDiscriminator());
+
+                for (size_t i = 0; i < testArray.size(); i++) {
+                    EXPECT_EQ(testArray[i], safeUnion.f()[i]);
+                }
+            }));
+
+        EXPECT_OK(
+            safeunionInterface->setI(safeUnion, testVector, [&](const LargeSafeUnion& safeUnion) {
+                EXPECT_EQ(LargeSafeUnion::hidl_discriminator::i, safeUnion.getDiscriminator());
+                EXPECT_EQ(testVector, safeUnion.i());
+            }));
+    }));
+}
+
+TEST_F(HidlTest, SafeUnionStringTypeTest) {
+    const std::string testString =
+        "This is an inordinately long test string to exercise hidl_string types in safe unions.";
+
+    EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& safeUnion) {
+        EXPECT_OK(safeunionInterface->setG(
+            safeUnion, hidl_string(testString), [&](const LargeSafeUnion& safeUnion) {
+                EXPECT_EQ(LargeSafeUnion::hidl_discriminator::g, safeUnion.getDiscriminator());
+                EXPECT_EQ(testString, std::string(safeUnion.g()));
+            }));
+    }));
+}
+
+TEST_F(HidlTest, SafeUnionCopyConstructorTest) {
+    const hidl_vec<bool> testVector{true, false, true, false, false, false, true,  false,
+                                    true, true,  true, false, false, true,  false, true};
+
+    EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& safeUnion) {
+        EXPECT_OK(
+            safeunionInterface->setH(safeUnion, testVector, [&](const LargeSafeUnion& safeUnion) {
+                LargeSafeUnion safeUnionCopy(safeUnion);
+
+                EXPECT_EQ(LargeSafeUnion::hidl_discriminator::h, safeUnionCopy.getDiscriminator());
+                EXPECT_EQ(testVector, safeUnionCopy.h());
+            }));
+    }));
+}
+
+template <typename T>
+void testZeroInit(const std::string& header) {
+    uint8_t buf[sizeof(T)];
+    memset(buf, 0xFF, sizeof(buf));
+
+    T* t = new (buf) T;
+
+    for (size_t i = 0; i < sizeof(T); i++) {
+        EXPECT_EQ(0, buf[i]) << header << " at offset: " << i;
+    }
+
+    t->~T();
+    t = nullptr;
+
+    memset(buf, 0xFF, sizeof(buf));
+    t = new (buf) T(T());  // copy constructor
+
+    for (size_t i = 0; i < sizeof(T); i++) {
+        EXPECT_EQ(0, buf[i]) << header << " at offset: " << i;
+    }
+
+    t->~T();
+    t = nullptr;
+
+    memset(buf, 0xFF, sizeof(buf));
+    const T aT = T();
+    t = new (buf) T(std::move(aT));  // move constructor
+
+    for (size_t i = 0; i < sizeof(T); i++) {
+        EXPECT_EQ(0, buf[i]) << header << " at offset: " << i;
+    }
+
+    t->~T();
+    t = nullptr;
+}
+
+TEST_F(HidlTest, SafeUnionUninit) {
+    testZeroInit<SmallSafeUnion>("SmallSafeUnion");
+    testZeroInit<LargeSafeUnion>("LargeSafeUnion");
+    testZeroInit<InterfaceTypeSafeUnion>("InterfaceTypeSafeUnion");
+    testZeroInit<HandleTypeSafeUnion>("HandleTypeSafeUnion");
+}
+
+TEST_F(HidlTest, SafeUnionMoveConstructorTest) {
+    sp<IOtherInterface> otherInterface = new OtherInterface();
+    ASSERT_EQ(1, otherInterface->getStrongCount());
+
+    InterfaceTypeSafeUnion safeUnion;
+    safeUnion.c(otherInterface);
+    EXPECT_EQ(2, otherInterface->getStrongCount());
+
+    InterfaceTypeSafeUnion anotherSafeUnion(std::move(safeUnion));
+    EXPECT_EQ(InterfaceTypeSafeUnion::hidl_discriminator::c,
+              anotherSafeUnion.getDiscriminator());
+    EXPECT_EQ(2, otherInterface->getStrongCount());
+}
+
+TEST_F(HidlTest, SafeUnionCopyAssignmentTest) {
+    const hidl_vec<hidl_string> testVector{"So", "Many", "Words"};
+    InterfaceTypeSafeUnion safeUnion;
+    safeUnion.e(testVector);
+
+    InterfaceTypeSafeUnion anotherSafeUnion;
+    anotherSafeUnion = safeUnion;
+
+    EXPECT_EQ(InterfaceTypeSafeUnion::hidl_discriminator::e, anotherSafeUnion.getDiscriminator());
+    EXPECT_EQ(InterfaceTypeSafeUnion::hidl_discriminator::e, safeUnion.getDiscriminator());
+    EXPECT_NE(&(safeUnion.e()), &(anotherSafeUnion.e()));
+    EXPECT_EQ(testVector, anotherSafeUnion.e());
+    EXPECT_EQ(testVector, safeUnion.e());
+}
+
+TEST_F(HidlTest, SafeUnionMoveAssignmentTest) {
+    sp<IOtherInterface> otherInterface = new OtherInterface();
+    ASSERT_EQ(1, otherInterface->getStrongCount());
+
+    InterfaceTypeSafeUnion safeUnion;
+    safeUnion.c(otherInterface);
+    EXPECT_EQ(2, otherInterface->getStrongCount());
+
+    InterfaceTypeSafeUnion anotherSafeUnion;
+    anotherSafeUnion.a(255);
+    anotherSafeUnion = std::move(safeUnion);
+
+    EXPECT_EQ(InterfaceTypeSafeUnion::hidl_discriminator::c,
+              anotherSafeUnion.getDiscriminator());
+    EXPECT_EQ(2, otherInterface->getStrongCount());
+}
+
+TEST_F(HidlTest, SafeUnionMutateTest) {
+    const std::array<int64_t, 5> testArray{-1, -2, -3, -4, -5};
+    const std::string testString = "Test string";
+    LargeSafeUnion safeUnion;
+
+    safeUnion.f(testArray);
+    safeUnion.f()[0] += 10;
+    EXPECT_EQ(testArray[0] + 10, safeUnion.f()[0]);
+
+    safeUnion.j(ISafeUnion::J());
+    safeUnion.j().j3 = testString;
+    EXPECT_EQ(testString, std::string(safeUnion.j().j3));
+}
+
+TEST_F(HidlTest, SafeUnionNestedTest) {
+    SmallSafeUnion smallSafeUnion;
+    smallSafeUnion.a(1);
+
+    EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& safeUnion) {
+        EXPECT_OK(safeunionInterface->setL(
+            safeUnion, smallSafeUnion, [&](const LargeSafeUnion& safeUnion) {
+                EXPECT_EQ(LargeSafeUnion::hidl_discriminator::l, safeUnion.getDiscriminator());
+
+                EXPECT_EQ(SmallSafeUnion::hidl_discriminator::a, safeUnion.l().getDiscriminator());
+                EXPECT_EQ(1, safeUnion.l().a());
+            }));
+    }));
+}
+
+TEST_F(HidlTest, SafeUnionEnumTest) {
+    EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& safeUnion) {
+        EXPECT_OK(safeunionInterface->setM(
+            safeUnion, ISafeUnion::BitField::V1, [&](const LargeSafeUnion& safeUnion) {
+                EXPECT_EQ(LargeSafeUnion::hidl_discriminator::m, safeUnion.getDiscriminator());
+                EXPECT_EQ(ISafeUnion::BitField::V1, safeUnion.m());
+            }));
+    }));
+}
+
+TEST_F(HidlTest, SafeUnionBitFieldTest) {
+    EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& safeUnion) {
+        EXPECT_OK(safeunionInterface->setN(
+            safeUnion, 0 | ISafeUnion::BitField::V1, [&](const LargeSafeUnion& safeUnion) {
+                EXPECT_EQ(LargeSafeUnion::hidl_discriminator::n, safeUnion.getDiscriminator());
+                EXPECT_EQ(0 | ISafeUnion::BitField::V1, safeUnion.n());
+            }));
+    }));
+}
+
+TEST_F(HidlTest, SafeUnionInterfaceTest) {
+    const std::array<int8_t, 7> testArray{-1, -2, -3, 0, 1, 2, 3};
+    const hidl_vec<hidl_string> testVector{"So", "Many", "Words"};
+    const std::string testStringA = "Hello";
+    const std::string testStringB = "World";
+
+    const std::string serviceName = "otherinterface";
+    sp<IOtherInterface> otherInterface = new OtherInterface();
+    EXPECT_EQ(::android::OK, otherInterface->registerAsService(serviceName));
+
+    EXPECT_OK(
+        safeunionInterface->newInterfaceTypeSafeUnion([&](const InterfaceTypeSafeUnion& safeUnion) {
+            EXPECT_EQ(InterfaceTypeSafeUnion::hidl_discriminator::noinit,
+                      safeUnion.getDiscriminator());
+
+            isOk(safeunionInterface->setInterfaceB(
+                safeUnion, testArray, [&](const InterfaceTypeSafeUnion& safeUnion) {
+                    EXPECT_EQ(InterfaceTypeSafeUnion::hidl_discriminator::b,
+                              safeUnion.getDiscriminator());
+
+                    for (size_t i = 0; i < testArray.size(); i++) {
+                        EXPECT_EQ(testArray[i], safeUnion.b()[i]);
+                    }
+
+                    EXPECT_OK(safeunionInterface->setInterfaceC(
+                        safeUnion, otherInterface, [&](const InterfaceTypeSafeUnion& safeUnion) {
+                            EXPECT_EQ(InterfaceTypeSafeUnion::hidl_discriminator::c,
+                                      safeUnion.getDiscriminator());
+
+                            EXPECT_OK(safeUnion.c()->concatTwoStrings(
+                                testStringA, testStringB, [&](const hidl_string& result) {
+                                    EXPECT_EQ(testStringA + testStringB, std::string(result));
+                                }));
+                        }));
+                }));
+
+            EXPECT_OK(safeunionInterface->setInterfaceD(
+                safeUnion, testStringA, [&](const InterfaceTypeSafeUnion& safeUnion) {
+                    EXPECT_EQ(InterfaceTypeSafeUnion::hidl_discriminator::d,
+                              safeUnion.getDiscriminator());
+                    EXPECT_EQ(testStringA, safeUnion.d());
+                }));
+
+            EXPECT_OK(safeunionInterface->setInterfaceE(
+                safeUnion, testVector, [&](const InterfaceTypeSafeUnion& safeUnion) {
+                    EXPECT_EQ(InterfaceTypeSafeUnion::hidl_discriminator::e,
+                              safeUnion.getDiscriminator());
+                    EXPECT_EQ(testVector, safeUnion.e());
+                }));
+        }));
+}
+
+TEST_F(HidlTest, SafeUnionNullHandleTest) {
+    HandleTypeSafeUnion safeUnion;
+
+    EXPECT_OK(safeunionInterface->setHandleA(
+        safeUnion, hidl_handle(nullptr), [&](const HandleTypeSafeUnion& safeUnion) {
+            EXPECT_EQ(HandleTypeSafeUnion::hidl_discriminator::a,
+                      safeUnion.getDiscriminator());
+
+            checkNativeHandlesDataEquality(nullptr, safeUnion.a().getNativeHandle());
+        }));
+}
+
+TEST_F(HidlTest, SafeUnionSimpleHandleTest) {
+    const std::array<int, 6> testData{2, -32, 10, -4329454, 11, 24};
+    native_handle_t* h = native_handle_create(0, testData.size());
+    ASSERT_EQ(sizeof(testData), testData.size() * sizeof(int));
+    std::memcpy(h->data, testData.data(), sizeof(testData));
+
+    std::array<hidl_handle, 5> testArray;
+    for (size_t i = 0; i < testArray.size(); i++) {
+        testArray[i].setTo(native_handle_clone(h), true /* shouldOwn */);
+    }
+
+    std::vector<hidl_handle> testVector(256);
+    for (size_t i = 0; i < testVector.size(); i++) {
+        testVector[i].setTo(native_handle_clone(h), true /* shouldOwn */);
+    }
+
+    EXPECT_OK(
+        safeunionInterface->newHandleTypeSafeUnion([&](const HandleTypeSafeUnion& safeUnion) {
+            EXPECT_OK(safeunionInterface->setHandleA(
+                safeUnion, hidl_handle(h), [&](const HandleTypeSafeUnion& safeUnion) {
+                    EXPECT_EQ(HandleTypeSafeUnion::hidl_discriminator::a,
+                              safeUnion.getDiscriminator());
+
+                    checkNativeHandlesDataEquality(h, safeUnion.a().getNativeHandle());
+                }));
+
+            EXPECT_OK(safeunionInterface->setHandleB(
+                safeUnion, testArray, [&](const HandleTypeSafeUnion& safeUnion) {
+                    EXPECT_EQ(HandleTypeSafeUnion::hidl_discriminator::b,
+                              safeUnion.getDiscriminator());
+
+                    for (size_t i = 0; i < testArray.size(); i++) {
+                        checkNativeHandlesDataEquality(h, safeUnion.b()[i].getNativeHandle());
+                    }
+                }));
+
+            EXPECT_OK(safeunionInterface->setHandleC(
+                safeUnion, testVector, [&](const HandleTypeSafeUnion& safeUnion) {
+                    EXPECT_EQ(HandleTypeSafeUnion::hidl_discriminator::c,
+                              safeUnion.getDiscriminator());
+
+                    for (size_t i = 0; i < testVector.size(); i++) {
+                        checkNativeHandlesDataEquality(h, safeUnion.c()[i].getNativeHandle());
+                    }
+                }));
+        }));
+
+    native_handle_delete(h);
+}
+
+TEST_F(HidlTest, SafeUnionVecOfHandlesWithOneFdTest) {
+    const std::vector<std::string> testStrings{"This ", "is ", "so ", "much ", "data!\n"};
+    const std::string testFileName = "/data/local/tmp/SafeUnionVecOfHandlesWithOneFdTest";
+    const std::array<int, 6> testData{2, -32, 10, -4329454, 11, 24};
+    ASSERT_EQ(sizeof(testData), testData.size() * sizeof(int));
+
+    const std::string goldenResult = std::accumulate(testStrings.begin(),
+                                                     testStrings.end(),
+                                                     std::string());
+
+    int fd = open(testFileName.c_str(), (O_RDWR | O_TRUNC | O_CREAT), (S_IRUSR | S_IWUSR));
+    ASSERT_TRUE(fd >= 0);
+
+    native_handle* h = native_handle_create(1 /* numFds */, testData.size() /* numInts */);
+    std::memcpy(&(h->data[1]), testData.data(), sizeof(testData));
+    h->data[0] = fd;
+
+    hidl_vec<hidl_handle> testHandles(testStrings.size());
+    for (size_t i = 0; i < testHandles.size(); i++) {
+        testHandles[i].setTo(native_handle_clone(h), true /* shouldOwn */);
+    }
+
+    EXPECT_OK(
+        safeunionInterface->newHandleTypeSafeUnion([&](const HandleTypeSafeUnion& safeUnion) {
+            EXPECT_OK(safeunionInterface->setHandleC(
+                safeUnion, testHandles, [&](const HandleTypeSafeUnion& safeUnion) {
+                    EXPECT_EQ(HandleTypeSafeUnion::hidl_discriminator::c,
+                              safeUnion.getDiscriminator());
+
+                    for (size_t i = 0; i < safeUnion.c().size(); i++) {
+                        const native_handle_t* reference = testHandles[i].getNativeHandle();
+                        const native_handle_t* result = safeUnion.c()[i].getNativeHandle();
+                        checkNativeHandlesDataEquality(reference, result);
+
+                        // Original FDs should be dup'd
+                        int resultFd = result->data[0];
+                        EXPECT_NE(reference->data[0], resultFd);
+
+                        EXPECT_TRUE(android::base::WriteStringToFd(testStrings[i], resultFd));
+                        EXPECT_EQ(0, fsync(resultFd));
+                    }
+                }));
+        }));
+
+    std::string result;
+    lseek(fd, 0, SEEK_SET);
+
+    EXPECT_TRUE(android::base::ReadFdToString(fd, &result));
+    EXPECT_EQ(goldenResult, result);
+
+    native_handle_delete(h);
+    EXPECT_EQ(0, close(fd));
+    EXPECT_EQ(0, remove(testFileName.c_str()));
+}
+
+TEST_F(HidlTest, SafeUnionHandleWithMultipleFdsTest) {
+    const std::vector<std::string> testStrings{"This ", "is ", "so ", "much ", "data!\n"};
+    const std::string testFileName = "/data/local/tmp/SafeUnionHandleWithMultipleFdsTest";
+    const std::array<int, 6> testData{2, -32, 10, -4329454, 11, 24};
+    ASSERT_EQ(sizeof(testData), testData.size() * sizeof(int));
+
+    const std::string goldenResult = std::accumulate(testStrings.begin(),
+                                                     testStrings.end(),
+                                                     std::string());
+
+    int fd = open(testFileName.c_str(), (O_RDWR | O_TRUNC | O_CREAT), (S_IRUSR | S_IWUSR));
+    ASSERT_TRUE(fd >= 0);
+
+    const int numFds = testStrings.size();
+    native_handle* h = native_handle_create(numFds, testData.size() /* numInts */);
+    std::memcpy(&(h->data[numFds]), testData.data(), sizeof(testData));
+    for (size_t i = 0; i < numFds; i++) {
+        h->data[i] = fd;
+    }
+
+    hidl_handle testHandle;
+    testHandle.setTo(h, false /* shouldOwn */);
+
+    EXPECT_OK(
+        safeunionInterface->newHandleTypeSafeUnion([&](const HandleTypeSafeUnion& safeUnion) {
+            EXPECT_OK(safeunionInterface->setHandleA(
+                safeUnion, testHandle, [&](const HandleTypeSafeUnion& safeUnion) {
+                    EXPECT_EQ(HandleTypeSafeUnion::hidl_discriminator::a,
+                              safeUnion.getDiscriminator());
+
+                    const native_handle_t* result = safeUnion.a().getNativeHandle();
+                    checkNativeHandlesDataEquality(h, result);
+
+                    for (size_t i = 0; i < result->numFds; i++) {
+                        // Original FDs should be dup'd
+                        int resultFd = result->data[i];
+                        EXPECT_NE(h->data[i], resultFd);
+
+                        EXPECT_TRUE(android::base::WriteStringToFd(testStrings[i], resultFd));
+                        EXPECT_EQ(0, fsync(resultFd));
+                    }
+                }));
+        }));
+
+    std::string result;
+    lseek(fd, 0, SEEK_SET);
+
+    EXPECT_TRUE(android::base::ReadFdToString(fd, &result));
+    EXPECT_EQ(goldenResult, result);
+
+    native_handle_delete(h);
+    EXPECT_EQ(0, close(fd));
+    EXPECT_EQ(0, remove(testFileName.c_str()));
+}
+
+TEST_F(HidlTest, SafeUnionEqualityTest) {
+    EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& one) {
+        EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& two) {
+            EXPECT_TRUE(one == two);
+            EXPECT_FALSE(one != two);
+        }));
+
+        EXPECT_OK(safeunionInterface->setA(one, 1, [&](const LargeSafeUnion& one) {
+            EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& two) {
+                EXPECT_FALSE(one == two);
+                EXPECT_TRUE(one != two);
+            }));
+
+            EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& two) {
+                EXPECT_OK(safeunionInterface->setB(two, 1, [&](const LargeSafeUnion& two) {
+                    EXPECT_FALSE(one == two);
+                    EXPECT_TRUE(one != two);
+                }));
+            }));
+
+            EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& two) {
+                EXPECT_OK(safeunionInterface->setA(two, 2, [&](const LargeSafeUnion& two) {
+                    EXPECT_FALSE(one == two);
+                    EXPECT_TRUE(one != two);
+                }));
+            }));
+
+            EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& two) {
+                EXPECT_OK(safeunionInterface->setA(two, 1, [&](const LargeSafeUnion& two) {
+                    EXPECT_TRUE(one == two);
+                    EXPECT_FALSE(one != two);
+                }));
+            }));
+        }));
+    }));
+}
+
+TEST_F(HidlTest, SafeUnionSimpleDestructorTest) {
+    sp<IOtherInterface> otherInterface = new OtherInterface();
+    ASSERT_EQ(1, otherInterface->getStrongCount());
+
+    {
+        InterfaceTypeSafeUnion safeUnion;
+        safeUnion.c(otherInterface);
+        EXPECT_EQ(2, otherInterface->getStrongCount());
+    }
+
+    EXPECT_EQ(1, otherInterface->getStrongCount());
+}
+
+TEST_F(HidlTest, SafeUnionSwitchActiveComponentsDestructorTest) {
+    sp<IOtherInterface> otherInterface = new OtherInterface();
+    ASSERT_EQ(1, otherInterface->getStrongCount());
+
+    InterfaceTypeSafeUnion safeUnion;
+    safeUnion.c(otherInterface);
+    EXPECT_EQ(2, otherInterface->getStrongCount());
+
+    safeUnion.a(1);
+    EXPECT_EQ(1, otherInterface->getStrongCount());
+}
+
+TEST_F(HidlTest, SafeUnionCppSpecificTest) {
+    ICppSafeUnion::PointerFmqSafeUnion pointerFmqSafeUnion;
+    pointerFmqSafeUnion.fmqSync({std::vector<GrantorDescriptor>(), native_handle_create(0, 1), 5});
+
+    EXPECT_OK(cppSafeunionInterface->repeatPointerFmqSafeUnion(
+        pointerFmqSafeUnion, [&](const ICppSafeUnion::PointerFmqSafeUnion& fmq) {
+            ASSERT_EQ(pointerFmqSafeUnion.getDiscriminator(), fmq.getDiscriminator());
+            checkMQDescriptorEquality(pointerFmqSafeUnion.fmqSync(), fmq.fmqSync());
+        }));
+
+    ICppSafeUnion::FmqSafeUnion fmqSafeUnion;
+    fmqSafeUnion.fmqUnsync({std::vector<GrantorDescriptor>(), native_handle_create(0, 1), 5});
+
+    EXPECT_OK(cppSafeunionInterface->repeatFmqSafeUnion(
+        fmqSafeUnion, [&](const ICppSafeUnion::FmqSafeUnion& fmq) {
+            ASSERT_EQ(fmqSafeUnion.getDiscriminator(), fmq.getDiscriminator());
+            checkMQDescriptorEquality(fmqSafeUnion.fmqUnsync(), fmq.fmqUnsync());
+        }));
+}
+
 class HidlMultithreadTest : public ::testing::Test {
    public:
     sp<IMultithread> multithreadInterface;
     TestMode mode = TestMode::PASSTHROUGH;
 
-    virtual void SetUp() override {
+    void SetUp() override {
         ALOGI("Test setup beginning...");
         multithreadInterface = gHidlEnvironment->multithreadInterface;
         mode = gHidlEnvironment->mode;
@@ -2213,9 +2928,9 @@
     bool b = false;
     bool p = false;
     bool d = false;
-    struct option longopts[] = {{0,0,0,0}};
+    struct option longopts[] = {{nullptr,0,nullptr,0}};
     int res;
-    while ((res = getopt_long(argc, argv, "hbpd", longopts, NULL)) >= 0) {
+    while ((res = getopt_long(argc, argv, "hbpd", longopts, nullptr)) >= 0) {
         switch (res) {
             case 'h': {
                 usage(me);
diff --git a/test/hidl_test/hidl_test_servers.cpp b/test/hidl_test/hidl_test_servers.cpp
index 2862002..3105199 100644
--- a/test/hidl_test/hidl_test_servers.cpp
+++ b/test/hidl_test/hidl_test_servers.cpp
@@ -81,7 +81,7 @@
 
 void signal_handler(int signal) {
     if (signal == SIGTERM) {
-        for (auto p : gPidList) {
+        for (const auto& p : gPidList) {
             killServer(p.second, p.first.c_str());
         }
         exit(0);
diff --git a/test/hidl_test/static_test.cpp b/test/hidl_test/static_test.cpp
index 3222f03..3d9f3f0 100644
--- a/test/hidl_test/static_test.cpp
+++ b/test/hidl_test/static_test.cpp
@@ -115,7 +115,7 @@
 static_assert(IExpression::UInt64LiteralTypeGuessing::noSuffixHex1 == 0x7fffffff, "");
 static_assert(IExpression::UInt64LiteralTypeGuessing::noSuffixHex2 == 0x80000000, "");
 static_assert(IExpression::UInt64LiteralTypeGuessing::noSuffixHex3 == 0xffffffff, "");
-static_assert(IExpression::UInt64LiteralTypeGuessing::longHex1 == 0xffffffffl, "");
+static_assert(IExpression::UInt64LiteralTypeGuessing::longHex1 == 0xffffffffL, "");
 static_assert(IExpression::UInt64LiteralTypeGuessing::longHex2 == 0Xfffffffff, "");
 static_assert(IExpression::UInt64LiteralTypeGuessing::longHex3 == 0x7fffffffffffffff, "");
 static_assert(IExpression::UInt64LiteralTypeGuessing::longHex4 == 0x8000000000000000, "");
@@ -151,7 +151,7 @@
 static_assert(IExpression::Precedence::neg == (-4), "");
 static_assert(IExpression::Precedence::literalL == (-4L), "");
 static_assert(IExpression::Precedence::hex == (0xffffffff), "");
-static_assert(IExpression::Precedence::hexLong == (0xffffffffl), "");
+static_assert(IExpression::Precedence::hexLong == (0xffffffffL), "");
 static_assert(IExpression::Precedence::hexLong2 == (0xfffffffff), "");
 static_assert(IExpression::Precedence::simpleArithmetic == (4 + 1), "");
 static_assert(IExpression::Precedence::simpleArithmetic2 == (2 + 3 - 4), "");
diff --git a/test/host_test/main.cpp b/test/host_test/main.cpp
index 9377306..5028882 100644
--- a/test/host_test/main.cpp
+++ b/test/host_test/main.cpp
@@ -46,22 +46,24 @@
     coordinator.addDefaultPackagePath("a.b", "a3/b3/"); // should take path above
     coordinator.addDefaultPackagePath("a.c", "a4/b4/"); // should succeed
 
-    EXPECT_EQ_OK("a.b", coordinator.getPackageRoot, FQName("a.b.foo@1.0"));
-    EXPECT_EQ_OK("a.c", coordinator.getPackageRoot, FQName("a.c.foo.bar@1.0::IFoo"));
+    EXPECT_EQ_OK("a.b", coordinator.getPackageRoot, FQName("a.b.foo", "1.0"));
+    EXPECT_EQ_OK("a.c", coordinator.getPackageRoot, FQName("a.c.foo.bar", "1.0", "IFoo"));
 
     // getPackagePath(fqname, relative, sanitized, ...)
-    EXPECT_EQ_OK("a1/b1/foo/1.0/", coordinator.getPackagePath, FQName("a.b.foo@1.0"), false, false);
-    EXPECT_EQ_OK("a4/b4/foo/bar/1.0/", coordinator.getPackagePath, FQName("a.c.foo.bar@1.0::IFoo"),
-                 false, false);
-    EXPECT_EQ_OK("a1/b1/foo/V1_0/", coordinator.getPackagePath, FQName("a.b.foo@1.0"), false, true);
-    EXPECT_EQ_OK("a4/b4/foo/bar/V1_0/", coordinator.getPackagePath, FQName("a.c.foo.bar@1.0::IFoo"),
-                 false, true);
-    EXPECT_EQ_OK("foo/1.0/", coordinator.getPackagePath, FQName("a.b.foo@1.0"), true, false);
-    EXPECT_EQ_OK("foo/bar/1.0/", coordinator.getPackagePath, FQName("a.c.foo.bar@1.0::IFoo"), true,
+    EXPECT_EQ_OK("a1/b1/foo/1.0/", coordinator.getPackagePath, FQName("a.b.foo", "1.0"), false,
                  false);
-    EXPECT_EQ_OK("foo/V1_0/", coordinator.getPackagePath, FQName("a.b.foo@1.0"), true, true);
-    EXPECT_EQ_OK("foo/bar/V1_0/", coordinator.getPackagePath, FQName("a.c.foo.bar@1.0::IFoo"), true,
+    EXPECT_EQ_OK("a4/b4/foo/bar/1.0/", coordinator.getPackagePath,
+                 FQName("a.c.foo.bar", "1.0", "IFoo"), false, false);
+    EXPECT_EQ_OK("a1/b1/foo/V1_0/", coordinator.getPackagePath, FQName("a.b.foo", "1.0"), false,
                  true);
+    EXPECT_EQ_OK("a4/b4/foo/bar/V1_0/", coordinator.getPackagePath,
+                 FQName("a.c.foo.bar", "1.0", "IFoo"), false, true);
+    EXPECT_EQ_OK("foo/1.0/", coordinator.getPackagePath, FQName("a.b.foo", "1.0"), true, false);
+    EXPECT_EQ_OK("foo/bar/1.0/", coordinator.getPackagePath, FQName("a.c.foo.bar", "1.0", "IFoo"),
+                 true, false);
+    EXPECT_EQ_OK("foo/V1_0/", coordinator.getPackagePath, FQName("a.b.foo", "1.0"), true, true);
+    EXPECT_EQ_OK("foo/bar/V1_0/", coordinator.getPackagePath, FQName("a.c.foo.bar", "1.0", "IFoo"),
+                 true, true);
 }
 
 TEST_F(HidlGenHostTest, CoordinatorFilepathTest) {
@@ -75,7 +77,7 @@
     EXPECT_EQ(OK, coordinator.addPackagePath("a.b", "a1/b1", &error));
     EXPECT_TRUE(error.empty());
 
-    const static FQName kName = FQName("a.b.c@1.2");
+    const static FQName kName = FQName("a.b.c", "1.2");
 
     // get file names
     EXPECT_EQ_OK("foo/x.y", coordinator.getFilepath, kName, Location::DIRECT, "x.y");
diff --git a/test/host_utils_test/Android.bp b/test/host_utils_test/Android.bp
new file mode 100644
index 0000000..f7962be
--- /dev/null
+++ b/test/host_utils_test/Android.bp
@@ -0,0 +1,24 @@
+// Copyright (C) 2018 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.
+
+cc_test_host {
+    name: "libhidl-gen-host-utils_test",
+    defaults: ["hidl-gen-defaults"],
+
+    shared_libs: [
+        "libhidl-gen-host-utils",
+    ],
+
+    srcs: ["main.cpp"],
+}
diff --git a/test/host_utils_test/main.cpp b/test/host_utils_test/main.cpp
new file mode 100644
index 0000000..8a6009e
--- /dev/null
+++ b/test/host_utils_test/main.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#define LOG_TAG "libhidl-gen-host-utils"
+
+#include <hidl-util/StringHelper.h>
+
+#include <gtest/gtest.h>
+#include <vector>
+
+using ::android::StringHelper;
+
+class LibHidlGenUtilsTest : public ::testing::Test {};
+
+TEST_F(LibHidlGenUtilsTest, EndsWithTest) {
+    EXPECT_TRUE(StringHelper::EndsWith("", ""));
+    EXPECT_TRUE(StringHelper::EndsWith("a", ""));
+    EXPECT_TRUE(StringHelper::EndsWith("b", "b"));
+    EXPECT_TRUE(StringHelper::EndsWith("ab", "b"));
+    EXPECT_TRUE(StringHelper::EndsWith("ab", "ab"));
+    EXPECT_TRUE(StringHelper::EndsWith("abcd", "bcd"));
+    EXPECT_TRUE(StringHelper::EndsWith("abcd", "abcd"));
+    EXPECT_TRUE(StringHelper::EndsWith("abcdefghijk", "ijk"));
+    EXPECT_TRUE(StringHelper::EndsWith("abcdefghijk", "bcdefghijk"));
+
+    EXPECT_FALSE(StringHelper::EndsWith("", "a"));
+    EXPECT_FALSE(StringHelper::EndsWith("b", "a"));
+    EXPECT_FALSE(StringHelper::EndsWith("abcd", "ab"));
+}
+
+TEST_F(LibHidlGenUtilsTest, StartsWithTest) {
+    EXPECT_TRUE(StringHelper::StartsWith("", ""));
+    EXPECT_TRUE(StringHelper::StartsWith("a", ""));
+    EXPECT_TRUE(StringHelper::StartsWith("b", "b"));
+    EXPECT_TRUE(StringHelper::StartsWith("ab", "a"));
+    EXPECT_TRUE(StringHelper::StartsWith("ab", "ab"));
+    EXPECT_TRUE(StringHelper::StartsWith("abcd", "abc"));
+    EXPECT_TRUE(StringHelper::StartsWith("abcd", "abcd"));
+    EXPECT_TRUE(StringHelper::StartsWith("abcdefghijk", "abc"));
+    EXPECT_TRUE(StringHelper::StartsWith("abcdefghijk", "abcdefghij"));
+
+    EXPECT_FALSE(StringHelper::StartsWith("", "a"));
+    EXPECT_FALSE(StringHelper::StartsWith("b", "a"));
+    EXPECT_FALSE(StringHelper::StartsWith("abcd", "cd"));
+}
+
+TEST_F(LibHidlGenUtilsTest, Trim) {
+    EXPECT_EQ("", StringHelper::LTrim("", ""));
+    EXPECT_EQ("", StringHelper::LTrim("", "a"));
+    EXPECT_EQ("", StringHelper::LTrim("a", "a"));
+    EXPECT_EQ("a", StringHelper::LTrim("a", ""));
+    EXPECT_EQ("a", StringHelper::LTrim("a", "b"));
+    EXPECT_EQ("a", StringHelper::LTrim("ba", "b"));
+    EXPECT_EQ("f", StringHelper::LTrim("abcdef", "abcde"));
+    EXPECT_EQ("cdef", StringHelper::LTrim("abcdef", "ab"));
+    EXPECT_EQ("abcdef", StringHelper::LTrim("abcdef", ""));
+
+    EXPECT_EQ("", StringHelper::RTrim("", ""));
+    EXPECT_EQ("", StringHelper::RTrim("", "a"));
+    EXPECT_EQ("", StringHelper::RTrim("a", "a"));
+    EXPECT_EQ("a", StringHelper::RTrim("a", ""));
+    EXPECT_EQ("a", StringHelper::RTrim("a", "b"));
+    EXPECT_EQ("a", StringHelper::RTrim("ab", "b"));
+    EXPECT_EQ("a", StringHelper::RTrim("abcdef", "bcdef"));
+    EXPECT_EQ("abcd", StringHelper::RTrim("abcdef", "ef"));
+    EXPECT_EQ("abcdef", StringHelper::RTrim("abcdef", ""));
+}
+
+TEST_F(LibHidlGenUtilsTest, TrimAll) {
+    EXPECT_EQ("", StringHelper::LTrimAll("", ""));
+    EXPECT_EQ("", StringHelper::LTrimAll("", "a"));
+    EXPECT_EQ("", StringHelper::LTrimAll("", "ab"));
+    EXPECT_EQ("", StringHelper::LTrimAll("a", "a"));
+    EXPECT_EQ("", StringHelper::LTrimAll("aa", "a"));
+    EXPECT_EQ("b", StringHelper::LTrimAll("b", "a"));
+    EXPECT_EQ("b", StringHelper::LTrimAll("aaab", "a"));
+    EXPECT_EQ("c", StringHelper::LTrimAll("ababc", "ab"));
+    EXPECT_EQ("ac", StringHelper::LTrimAll("abac", "ab"));
+
+    EXPECT_EQ("", StringHelper::RTrimAll("", ""));
+    EXPECT_EQ("", StringHelper::RTrimAll("", "a"));
+    EXPECT_EQ("", StringHelper::RTrimAll("", "ab"));
+    EXPECT_EQ("", StringHelper::RTrimAll("a", "a"));
+    EXPECT_EQ("", StringHelper::RTrimAll("aa", "a"));
+    EXPECT_EQ("b", StringHelper::RTrimAll("b", "a"));
+    EXPECT_EQ("b", StringHelper::RTrimAll("baaa", "a"));
+    EXPECT_EQ("c", StringHelper::RTrimAll("cabab", "ab"));
+    EXPECT_EQ("ca", StringHelper::RTrimAll("caba", "ba"));
+}
+
+TEST_F(LibHidlGenUtilsTest, SplitString) {
+    std::vector<std::string> components;
+
+    StringHelper::SplitString("", '.', &components);
+    EXPECT_EQ(std::vector<std::string>({""}), components);
+    StringHelper::SplitString("a.", '.', &components);
+    EXPECT_EQ(std::vector<std::string>({"a", ""}), components);
+    StringHelper::SplitString(".a", '.', &components);
+    EXPECT_EQ(std::vector<std::string>({"", "a"}), components);
+    StringHelper::SplitString("..", '.', &components);
+    EXPECT_EQ(std::vector<std::string>({"", "", ""}), components);
+    StringHelper::SplitString("asdf.asdf", '.', &components);
+    EXPECT_EQ(std::vector<std::string>({"asdf", "asdf"}), components);
+}
+
+TEST_F(LibHidlGenUtilsTest, JoinStrings) {
+    EXPECT_EQ("", StringHelper::JoinStrings({}, ""));
+    EXPECT_EQ("", StringHelper::JoinStrings({}, "a"));
+    EXPECT_EQ("a", StringHelper::JoinStrings({"a"}, ""));
+    EXPECT_EQ("a,b", StringHelper::JoinStrings({"a", "b"}, ","));
+    EXPECT_EQ("ab,", StringHelper::JoinStrings({"ab", ""}, ","));
+    EXPECT_EQ(",ab", StringHelper::JoinStrings({"", "ab"}, ","));
+    EXPECT_EQ("a.,b", StringHelper::JoinStrings({"a", "b"}, ".,"));
+    EXPECT_EQ("a,b,c", StringHelper::JoinStrings({"a", "b", "c"}, ","));
+    EXPECT_EQ("abc.,def.,ghi", StringHelper::JoinStrings({"abc", "def", "ghi"}, ".,"));
+}
+
+int main(int argc, char **argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/test/java_test/Android.bp b/test/java_test/Android.bp
index f7b731d..4f18adf 100644
--- a/test/java_test/Android.bp
+++ b/test/java_test/Android.bp
@@ -4,21 +4,46 @@
 
     cflags: ["-Wall", "-Werror"],
 
+    // Allow dlsym'ing self for statically linked passthrough implementations
+    ldflags: ["-rdynamic"],
+
     shared_libs: [
         "libbase",
+        "libcutils",
         "libhidlbase",
         "libhidltransport",
         "libhwbinder",
         "liblog",
         "libutils",
+    ],
+
+    static_libs: [
         "android.hardware.tests.baz@1.0",
         "android.hardware.tests.expression@1.0",
         "android.hardware.tests.inheritance@1.0",
+        "android.hardware.tests.safeunion@1.0",
     ],
 
-    required: [
+    // impls should never be static, these are used only for testing purposes
+    // and test portability since this test pairs with specific hal
+    // implementations
+    whole_static_libs: [
         "android.hardware.tests.baz@1.0-impl",
+        "android.hardware.tests.safeunion@1.0-impl",
     ],
 
     compile_multilib: "both",
 }
+
+java_test {
+    name: "hidl_test_java_java",
+    srcs: ["**/*.java"],
+
+    static_libs: [
+        "android.hidl.manager-V1.0-java",
+        "android.hardware.tests.baz-V1.0-java",
+        "android.hardware.tests.expression-V1.0-java",
+        "android.hardware.tests.inheritance-V1.0-java",
+        "android.hardware.tests.safeunion-V1.0-java",
+    ],
+}
diff --git a/test/java_test/Android.mk b/test/java_test/Android.mk
index a41fa5d..7e277a4 100644
--- a/test/java_test/Android.mk
+++ b/test/java_test/Android.mk
@@ -1,35 +1,18 @@
 LOCAL_PATH:= $(call my-dir)
 
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_MODULE := hidl_test_java_lib
-LOCAL_MODULE_STEM := hidl_test_java
-LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-LOCAL_JAVA_LIBRARIES :=                          \
-    android.hidl.base-V1.0-java                  \
-    android.hidl.manager-V1.0-java               \
-    android.hardware.tests.baz-V1.0-java         \
-    android.hardware.tests.expression-V1.0-java  \
-    android.hardware.tests.inheritance-V1.0-java
-
-include $(BUILD_JAVA_LIBRARY)
-
-################################################################################
-
-include $(CLEAR_VARS)
 LOCAL_MODULE := hidl_test_java
 LOCAL_MODULE_CLASS := NATIVE_TESTS
 LOCAL_SRC_FILES := hidl_test_java
 
-LOCAL_REQUIRED_MODULES :=                       \
-    hidl_test_java_lib                          \
-    hidl_test_java_native                       \
-    android.hidl.base-V1.0-java                 \
-    android.hidl.manager-V1.0-java              \
-    android.hardware.tests.baz-V1.0-java
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest64
 
-LOCAL_REQUIRED_MODULES_arm64 := hidl_test_java_native_32
-LOCAL_REQUIRED_MODULES_x86_64 := hidl_test_java_native_32
-LOCAL_REQUIRED_MODULES_mips64 := hidl_test_java_native_32
+LOCAL_REQUIRED_MODULES :=                       \
+    hidl_test_java_java                         \
+    hidl_test_java_native
+
+ifneq ($(TARGET_2ND_ARCH),)
+LOCAL_REQUIRED_MODULES += hidl_test_java_native$(TARGET_2ND_ARCH_MODULE_SUFFIX)
+endif
 
 include $(BUILD_PREBUILT)
diff --git a/test/java_test/hidl_test_java b/test/java_test/hidl_test_java
index 1cdfb3f..fdcb00f 100644
--- a/test/java_test/hidl_test_java
+++ b/test/java_test/hidl_test_java
@@ -1,5 +1,4 @@
-base=/system
-export CLASSPATH=$base/framework/hidl_test_java.jar:$base/framework/android.hardware.tests.baz-V1.0-java.jar:$base/framework/android.hidl.base-V1.0-java.jar
+export CLASSPATH=/data/framework/hidl_test_java_java.jar
 export TREBLE_TESTING_OVERRIDE=true
 
 e=0
@@ -13,14 +12,14 @@
         $native -s &
         sleep 1
         NATIVE_PID=$!
-        app_process $base/bin com.android.commands.hidl_test_java.HidlTestJava -c \
+        app_process /data/framework com.android.commands.hidl_test_java.HidlTestJava -c \
             && echo "Java client => native server PASSED" \
             || (echo "Java client => native server FAILED" && false) || e=1
 
         kill $NATIVE_PID 2>/dev/null
 
         # Test Java server with native client
-        app_process $base/bin com.android.commands.hidl_test_java.HidlTestJava -s &
+        app_process /data/framework com.android.commands.hidl_test_java.HidlTestJava -s &
         NATIVE_PID=$!
         $native -c \
             && echo "native client => Java server PASSED" \
@@ -37,4 +36,6 @@
 echo "Summary: $e"
 [ $e -eq 0 ] && echo "All tests PASSED." || echo "Test(s) FAILED."
 
+export TREBLE_TESTING_OVERRIDE=false
+
 exit $e
diff --git a/test/java_test/hidl_test_java_native.cpp b/test/java_test/hidl_test_java_native.cpp
index d155e09..a7467d7 100644
--- a/test/java_test/hidl_test_java_native.cpp
+++ b/test/java_test/hidl_test_java_native.cpp
@@ -17,9 +17,12 @@
 //#define LOG_NDEBUG 0
 #define LOG_TAG "hidl_test_java_native"
 
+#include <android-base/file.h>
 #include <android-base/logging.h>
 
 #include <android/hardware/tests/baz/1.0/IBaz.h>
+#include <android/hardware/tests/safeunion/1.0/IOtherInterface.h>
+#include <android/hardware/tests/safeunion/1.0/ISafeUnion.h>
 
 #include <hidl/LegacySupport.h>
 #include <hidl/ServiceManagement.h>
@@ -27,18 +30,30 @@
 
 #include <hidl/HidlTransportSupport.h>
 #include <hidl/Status.h>
+
+#include <numeric>
+#include <sys/stat.h>
+
 using ::android::sp;
 using ::android::hardware::tests::baz::V1_0::IBase;
 using ::android::hardware::tests::baz::V1_0::IBaz;
 using ::android::hardware::tests::baz::V1_0::IBazCallback;
+using ::android::hardware::tests::safeunion::V1_0::IOtherInterface;
+using ::android::hardware::tests::safeunion::V1_0::ISafeUnion;
 
 using ::android::hardware::hidl_array;
 using ::android::hardware::hidl_vec;
+using ::android::hardware::hidl_handle;
 using ::android::hardware::hidl_string;
 using ::android::hardware::defaultPassthroughServiceImplementation;
 using ::android::hardware::Return;
 using ::android::hardware::Void;
 
+using HandleTypeSafeUnion = ISafeUnion::HandleTypeSafeUnion;
+using InterfaceTypeSafeUnion = ISafeUnion::InterfaceTypeSafeUnion;
+using LargeSafeUnion = ISafeUnion::LargeSafeUnion;
+using SmallSafeUnion = ISafeUnion::SmallSafeUnion;
+
 struct BazCallback : public IBazCallback {
     Return<void> heyItsMe(const sp<IBazCallback> &cb) override;
     Return<void> hey() override;
@@ -57,6 +72,16 @@
     return Void();
 }
 
+struct OtherInterface : public IOtherInterface {
+    Return<void> concatTwoStrings(const hidl_string& a, const hidl_string& b,
+                                  concatTwoStrings_cb _hidl_cb) override {
+        hidl_string result = std::string(a) + std::string(b);
+        _hidl_cb(result);
+
+        return Void();
+    }
+};
+
 using std::to_string;
 
 static void usage(const char *me) {
@@ -73,17 +98,26 @@
 
 struct HidlTest : public ::testing::Test {
     sp<IBaz> baz;
+    sp<ISafeUnion> safeunionInterface;
+    sp<IOtherInterface> otherInterface;
 
     void SetUp() override {
         using namespace ::android::hardware;
 
-        ::android::hardware::details::waitForHwService(
-                IBaz::descriptor, "baz");
-
-        baz = IBaz::getService("baz");
-
-        CHECK(baz != NULL);
+        ::android::hardware::details::waitForHwService(IBaz::descriptor, "default");
+        baz = IBaz::getService();
+        CHECK(baz != nullptr);
         CHECK(baz->isRemote());
+
+        ::android::hardware::details::waitForHwService(ISafeUnion::descriptor, "default");
+        safeunionInterface = ISafeUnion::getService();
+        CHECK(safeunionInterface != nullptr);
+        CHECK(safeunionInterface->isRemote());
+
+        ::android::hardware::details::waitForHwService(IOtherInterface::descriptor, "default");
+        otherInterface = IOtherInterface::getService();
+        CHECK(otherInterface != nullptr);
+        CHECK(otherInterface->isRemote());
     }
 
     void TearDown() override {
@@ -95,6 +129,14 @@
     EXPECT_TRUE(ret.isOk());
 }
 
+template<typename T, typename S>
+static inline bool isArrayEqual(const T arr1, const S arr2, size_t size) {
+    for(size_t i = 0; i < size; i++)
+        if(arr1[i] != arr2[i])
+            return false;
+    return true;
+}
+
 TEST_F(HidlTest, GetDescriptorTest) {
     EXPECT_OK(baz->interfaceDescriptor([&] (const auto &desc) {
         EXPECT_EQ(desc, IBaz::descriptor);
@@ -129,6 +171,22 @@
                }));
 }
 
+TEST_F(HidlTest, SomeOtherBaseMethodInvalidString) {
+    IBase::Foo foo {
+        .y = {
+            .s = "\xff",
+        }
+    };
+
+    auto ret = baz->someOtherBaseMethod(foo, [&](const auto&) {
+        ADD_FAILURE() << "Should not accept invalid UTF-8 String";
+    });
+
+    EXPECT_FALSE(ret.isOk());
+
+    EXPECT_OK(baz->ping());
+}
+
 TEST_F(HidlTest, BazSomeMethodWithFooArraysTest) {
     hidl_array<IBase::Foo, 2> foo;
 
@@ -381,7 +439,7 @@
 }
 
 TEST_F(HidlTest, BazDoQuiteABitMethodTest) {
-    auto result = baz->doQuiteABit(1, 2ll, 3.0f, 4.0);
+    auto result = baz->doQuiteABit(1, 2LL, 3.0f, 4.0);
 
     EXPECT_OK(result);
     EXPECT_EQ(result, 666.5);
@@ -574,6 +632,397 @@
                 in, [&](const auto &out) { EXPECT_EQ(in, out); }));
 }
 
+TEST_F(HidlTest, SafeUnionNoInitTest) {
+    EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& safeUnion) {
+        EXPECT_EQ(LargeSafeUnion::hidl_discriminator::noinit, safeUnion.getDiscriminator());
+    }));
+}
+
+TEST_F(HidlTest, SafeUnionSimpleTest) {
+    EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& safeUnion) {
+        EXPECT_OK(safeunionInterface->setA(safeUnion, -5, [&](const LargeSafeUnion& safeUnion) {
+            EXPECT_EQ(LargeSafeUnion::hidl_discriminator::a, safeUnion.getDiscriminator());
+            EXPECT_EQ(-5, safeUnion.a());
+
+            uint64_t max = std::numeric_limits<uint64_t>::max();
+            EXPECT_OK(
+                safeunionInterface->setD(safeUnion, max, [&](const LargeSafeUnion& safeUnion) {
+                    EXPECT_EQ(LargeSafeUnion::hidl_discriminator::d, safeUnion.getDiscriminator());
+                    EXPECT_EQ(max, safeUnion.d());
+                }));
+        }));
+    }));
+}
+
+TEST_F(HidlTest, SafeUnionArrayLikeTypesTest) {
+    const std::array<int64_t, 5> testArray{1, -2, 3, -4, 5};
+    const hidl_vec<uint64_t> testVector{std::numeric_limits<uint64_t>::max()};
+
+    EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& safeUnion) {
+        EXPECT_OK(
+            safeunionInterface->setF(safeUnion, testArray, [&](const LargeSafeUnion& safeUnion) {
+                EXPECT_EQ(LargeSafeUnion::hidl_discriminator::f, safeUnion.getDiscriminator());
+
+                for (size_t i = 0; i < testArray.size(); i++) {
+                    EXPECT_EQ(testArray[i], safeUnion.f()[i]);
+                }
+            }));
+
+        EXPECT_OK(
+            safeunionInterface->setI(safeUnion, testVector, [&](const LargeSafeUnion& safeUnion) {
+                EXPECT_EQ(LargeSafeUnion::hidl_discriminator::i, safeUnion.getDiscriminator());
+                EXPECT_EQ(testVector, safeUnion.i());
+            }));
+    }));
+}
+
+TEST_F(HidlTest, SafeUnionStringTypeTest) {
+    const std::string testString =
+        "This is an inordinately long test string to exercise hidl_string types in safe unions.";
+
+    EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& safeUnion) {
+        EXPECT_OK(safeunionInterface->setG(
+            safeUnion, hidl_string(testString), [&](const LargeSafeUnion& safeUnion) {
+                EXPECT_EQ(LargeSafeUnion::hidl_discriminator::g, safeUnion.getDiscriminator());
+                EXPECT_EQ(testString, std::string(safeUnion.g()));
+            }));
+    }));
+}
+
+TEST_F(HidlTest, SafeUnionNestedTest) {
+    SmallSafeUnion smallSafeUnion;
+    smallSafeUnion.a(1);
+
+    EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& safeUnion) {
+        EXPECT_OK(safeunionInterface->setL(
+            safeUnion, smallSafeUnion, [&](const LargeSafeUnion& safeUnion) {
+                EXPECT_EQ(LargeSafeUnion::hidl_discriminator::l, safeUnion.getDiscriminator());
+
+                EXPECT_EQ(SmallSafeUnion::hidl_discriminator::a, safeUnion.l().getDiscriminator());
+                EXPECT_EQ(1, safeUnion.l().a());
+            }));
+    }));
+}
+
+// does not check for fd equality
+static void checkNativeHandlesDataEquality(const native_handle_t* reference,
+                                       const native_handle_t* result) {
+    if (reference == nullptr || result == nullptr) {
+        EXPECT_EQ(reference == nullptr, result == nullptr);
+        return;
+    }
+
+    ASSERT_NE(reference, result);
+    ASSERT_EQ(reference->version, result->version);
+    EXPECT_EQ(reference->numFds, result->numFds);
+    EXPECT_EQ(reference->numInts, result->numInts);
+
+    int offset = reference->numFds;
+    int numInts = reference->numInts;
+    EXPECT_TRUE(isArrayEqual(&(reference->data[offset]), &(result->data[offset]), numInts));
+}
+
+TEST_F(HidlTest, SafeUnionInterfaceNullHandleTest) {
+    InterfaceTypeSafeUnion safeUnion;
+
+    EXPECT_OK(safeunionInterface->setInterfaceF(
+        safeUnion, hidl_handle(nullptr), [&](const InterfaceTypeSafeUnion& safeUnion) {
+            EXPECT_EQ(InterfaceTypeSafeUnion::hidl_discriminator::f,
+                      safeUnion.getDiscriminator());
+
+            checkNativeHandlesDataEquality(nullptr, safeUnion.f().getNativeHandle());
+        }));
+}
+
+TEST_F(HidlTest, SafeUnionInterfaceTest) {
+    const std::array<int8_t, 7> testArray{-1, -2, -3, 0, 1, 2, 3};
+    const hidl_vec<hidl_string> testVector{"So", "Many", "Words"};
+    const std::string testStringA = "Hello";
+    const std::string testStringB = "World";
+
+    const std::array<int, 6> testHandleData{2, -32, 10, -4329454, 11, 24};
+    native_handle_t* h = native_handle_create(0, testHandleData.size());
+    CHECK(sizeof(testHandleData) == testHandleData.size() * sizeof(int));
+    std::memcpy(h->data, testHandleData.data(), sizeof(testHandleData));
+
+    std::vector<hidl_handle> testHandlesVector(256);
+    for (size_t i = 0; i < testHandlesVector.size(); i++) {
+        testHandlesVector[i].setTo(native_handle_clone(h), true /* shouldOwn */);
+    }
+
+    EXPECT_OK(
+        safeunionInterface->newInterfaceTypeSafeUnion([&](const InterfaceTypeSafeUnion& safeUnion) {
+            EXPECT_EQ(InterfaceTypeSafeUnion::hidl_discriminator::noinit,
+                      safeUnion.getDiscriminator());
+
+            EXPECT_OK(safeunionInterface->setInterfaceB(
+                safeUnion, testArray, [&](const InterfaceTypeSafeUnion& safeUnion) {
+                    EXPECT_EQ(InterfaceTypeSafeUnion::hidl_discriminator::b,
+                              safeUnion.getDiscriminator());
+
+                    for (size_t i = 0; i < testArray.size(); i++) {
+                        EXPECT_EQ(testArray[i], safeUnion.b()[i]);
+                    }
+                }));
+
+            EXPECT_OK(safeunionInterface->setInterfaceD(
+                safeUnion, testStringA, [&](const InterfaceTypeSafeUnion& safeUnion) {
+                    EXPECT_EQ(InterfaceTypeSafeUnion::hidl_discriminator::d,
+                              safeUnion.getDiscriminator());
+                    EXPECT_EQ(testStringA, safeUnion.d());
+                }));
+
+            EXPECT_OK(safeunionInterface->setInterfaceE(
+                safeUnion, testVector, [&](const InterfaceTypeSafeUnion& safeUnion) {
+                    EXPECT_EQ(InterfaceTypeSafeUnion::hidl_discriminator::e,
+                              safeUnion.getDiscriminator());
+                    EXPECT_EQ(testVector, safeUnion.e());
+                }));
+
+            EXPECT_OK(safeunionInterface->setInterfaceF(
+                safeUnion, hidl_handle(h), [&](const InterfaceTypeSafeUnion& safeUnion) {
+                    EXPECT_EQ(InterfaceTypeSafeUnion::hidl_discriminator::f,
+                              safeUnion.getDiscriminator());
+
+                    const native_handle_t* result = safeUnion.f().getNativeHandle();
+                    checkNativeHandlesDataEquality(h, result);
+                }));
+
+            EXPECT_OK(safeunionInterface->setInterfaceG(
+                safeUnion, testHandlesVector, [&](const InterfaceTypeSafeUnion& safeUnion) {
+                    EXPECT_EQ(InterfaceTypeSafeUnion::hidl_discriminator::g,
+                              safeUnion.getDiscriminator());
+
+                    for (size_t i = 0; i < testHandlesVector.size(); i++) {
+                        checkNativeHandlesDataEquality(h, safeUnion.g()[i].getNativeHandle());
+                    }
+                }));
+        }));
+
+    // Same-process interface calls are not supported in Java, so we use
+    // a safe_union instance bound to this (client) process instead of
+    // safeunionInterface to exercise this test-case. Ref: b/110957763.
+    InterfaceTypeSafeUnion safeUnion;
+    safeUnion.c(otherInterface);
+
+    EXPECT_EQ(InterfaceTypeSafeUnion::hidl_discriminator::c, safeUnion.getDiscriminator());
+    EXPECT_OK(safeUnion.c()->concatTwoStrings(
+        hidl_string(testStringA), hidl_string(testStringB), [&](const hidl_string& result) {
+            EXPECT_EQ(testStringA + testStringB, std::string(result));
+        }));
+
+    native_handle_delete(h);
+}
+
+TEST_F(HidlTest, SafeUnionNullHandleTest) {
+    HandleTypeSafeUnion safeUnion;
+
+    EXPECT_OK(safeunionInterface->setHandleA(
+        safeUnion, hidl_handle(nullptr), [&](const HandleTypeSafeUnion& safeUnion) {
+            EXPECT_EQ(HandleTypeSafeUnion::hidl_discriminator::a,
+                      safeUnion.getDiscriminator());
+
+            checkNativeHandlesDataEquality(nullptr, safeUnion.a().getNativeHandle());
+        }));
+}
+
+TEST_F(HidlTest, SafeUnionSimpleHandleTest) {
+    const std::array<int, 6> testData{2, -32, 10, -4329454, 11, 24};
+    native_handle_t* h = native_handle_create(0, testData.size());
+    ASSERT_EQ(sizeof(testData), testData.size() * sizeof(int));
+    std::memcpy(h->data, testData.data(), sizeof(testData));
+
+    std::array<hidl_handle, 5> testArray;
+    for (size_t i = 0; i < testArray.size(); i++) {
+        testArray[i].setTo(native_handle_clone(h), true /* shouldOwn */);
+    }
+
+    std::vector<hidl_handle> testVector(256);
+    for (size_t i = 0; i < testVector.size(); i++) {
+        testVector[i].setTo(native_handle_clone(h), true /* shouldOwn */);
+    }
+
+    EXPECT_OK(
+        safeunionInterface->newHandleTypeSafeUnion([&](const HandleTypeSafeUnion& safeUnion) {
+            EXPECT_OK(safeunionInterface->setHandleA(
+                safeUnion, hidl_handle(h), [&](const HandleTypeSafeUnion& safeUnion) {
+                    EXPECT_EQ(HandleTypeSafeUnion::hidl_discriminator::a,
+                              safeUnion.getDiscriminator());
+
+                    checkNativeHandlesDataEquality(h, safeUnion.a().getNativeHandle());
+                }));
+
+            EXPECT_OK(safeunionInterface->setHandleB(
+                safeUnion, testArray, [&](const HandleTypeSafeUnion& safeUnion) {
+                    EXPECT_EQ(HandleTypeSafeUnion::hidl_discriminator::b,
+                              safeUnion.getDiscriminator());
+
+                    for (size_t i = 0; i < testArray.size(); i++) {
+                        checkNativeHandlesDataEquality(h, safeUnion.b()[i].getNativeHandle());
+                    }
+                }));
+
+            EXPECT_OK(safeunionInterface->setHandleC(
+                safeUnion, testVector, [&](const HandleTypeSafeUnion& safeUnion) {
+                    EXPECT_EQ(HandleTypeSafeUnion::hidl_discriminator::c,
+                              safeUnion.getDiscriminator());
+
+                    for (size_t i = 0; i < testVector.size(); i++) {
+                        checkNativeHandlesDataEquality(h, safeUnion.c()[i].getNativeHandle());
+                    }
+                }));
+        }));
+
+    native_handle_delete(h);
+}
+
+TEST_F(HidlTest, SafeUnionVecOfHandlesWithOneFdTest) {
+    const std::vector<std::string> testStrings{"This ", "is ", "so ", "much ", "data!\n"};
+    const std::string testFileName = "/data/local/tmp/SafeUnionVecOfHandlesWithOneFdTest";
+    const std::array<int, 6> testData{2, -32, 10, -4329454, 11, 24};
+    ASSERT_EQ(sizeof(testData), testData.size() * sizeof(int));
+
+    const std::string goldenResult = std::accumulate(testStrings.begin(),
+                                                     testStrings.end(),
+                                                     std::string());
+
+    int fd = open(testFileName.c_str(), (O_RDWR | O_TRUNC | O_CREAT), (S_IRUSR | S_IWUSR));
+    ASSERT_TRUE(fd >= 0);
+
+    native_handle* h = native_handle_create(1 /* numFds */, testData.size() /* numInts */);
+    std::memcpy(&(h->data[1]), testData.data(), sizeof(testData));
+    h->data[0] = fd;
+
+    hidl_vec<hidl_handle> testHandles(testStrings.size());
+    for (size_t i = 0; i < testHandles.size(); i++) {
+        testHandles[i].setTo(native_handle_clone(h), true /* shouldOwn */);
+    }
+
+    EXPECT_OK(
+        safeunionInterface->newHandleTypeSafeUnion([&](const HandleTypeSafeUnion& safeUnion) {
+            EXPECT_OK(safeunionInterface->setHandleC(
+                safeUnion, testHandles, [&](const HandleTypeSafeUnion& safeUnion) {
+                    EXPECT_EQ(HandleTypeSafeUnion::hidl_discriminator::c,
+                              safeUnion.getDiscriminator());
+
+                    for (size_t i = 0; i < safeUnion.c().size(); i++) {
+                        const native_handle_t* reference = testHandles[i].getNativeHandle();
+                        const native_handle_t* result = safeUnion.c()[i].getNativeHandle();
+                        checkNativeHandlesDataEquality(reference, result);
+
+                        // Original FDs should be dup'd
+                        int resultFd = result->data[0];
+                        EXPECT_NE(reference->data[0], resultFd);
+
+                        EXPECT_TRUE(android::base::WriteStringToFd(testStrings[i], resultFd));
+                        EXPECT_EQ(0, fsync(resultFd));
+                    }
+                }));
+        }));
+
+    std::string result;
+    lseek(fd, 0, SEEK_SET);
+
+    EXPECT_TRUE(android::base::ReadFdToString(fd, &result));
+    EXPECT_EQ(goldenResult, result);
+
+    native_handle_delete(h);
+    EXPECT_EQ(0, close(fd));
+    EXPECT_EQ(0, remove(testFileName.c_str()));
+}
+
+TEST_F(HidlTest, SafeUnionHandleWithMultipleFdsTest) {
+    const std::vector<std::string> testStrings{"This ", "is ", "so ", "much ", "data!\n"};
+    const std::string testFileName = "/data/local/tmp/SafeUnionHandleWithMultipleFdsTest";
+    const std::array<int, 6> testData{2, -32, 10, -4329454, 11, 24};
+    ASSERT_EQ(sizeof(testData), testData.size() * sizeof(int));
+
+    const std::string goldenResult = std::accumulate(testStrings.begin(),
+                                                     testStrings.end(),
+                                                     std::string());
+
+    int fd = open(testFileName.c_str(), (O_RDWR | O_TRUNC | O_CREAT), (S_IRUSR | S_IWUSR));
+    ASSERT_TRUE(fd >= 0);
+
+    const int numFds = testStrings.size();
+    native_handle* h = native_handle_create(numFds, testData.size() /* numInts */);
+    std::memcpy(&(h->data[numFds]), testData.data(), sizeof(testData));
+    for (size_t i = 0; i < numFds; i++) {
+        h->data[i] = fd;
+    }
+
+    hidl_handle testHandle;
+    testHandle.setTo(h, false /* shouldOwn */);
+
+    EXPECT_OK(
+        safeunionInterface->newHandleTypeSafeUnion([&](const HandleTypeSafeUnion& safeUnion) {
+            EXPECT_OK(safeunionInterface->setHandleA(
+                safeUnion, testHandle, [&](const HandleTypeSafeUnion& safeUnion) {
+                    EXPECT_EQ(HandleTypeSafeUnion::hidl_discriminator::a,
+                              safeUnion.getDiscriminator());
+
+                    const native_handle_t* result = safeUnion.a().getNativeHandle();
+                    checkNativeHandlesDataEquality(h, result);
+
+                    for (size_t i = 0; i < result->numFds; i++) {
+                        // Original FDs should be dup'd
+                        int resultFd = result->data[i];
+                        EXPECT_NE(h->data[i], resultFd);
+
+                        EXPECT_TRUE(android::base::WriteStringToFd(testStrings[i], resultFd));
+                        EXPECT_EQ(0, fsync(resultFd));
+                    }
+                }));
+        }));
+
+    std::string result;
+    lseek(fd, 0, SEEK_SET);
+
+    EXPECT_TRUE(android::base::ReadFdToString(fd, &result));
+    EXPECT_EQ(goldenResult, result);
+
+    native_handle_delete(h);
+    EXPECT_EQ(0, close(fd));
+    EXPECT_EQ(0, remove(testFileName.c_str()));
+}
+
+TEST_F(HidlTest, SafeUnionEqualityTest) {
+    EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& one) {
+        EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& two) {
+            EXPECT_TRUE(one == two);
+            EXPECT_FALSE(one != two);
+        }));
+
+        EXPECT_OK(safeunionInterface->setA(one, 1, [&](const LargeSafeUnion& one) {
+            EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& two) {
+                EXPECT_FALSE(one == two);
+                EXPECT_TRUE(one != two);
+            }));
+
+            EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& two) {
+                EXPECT_OK(safeunionInterface->setB(two, 1, [&](const LargeSafeUnion& two) {
+                    EXPECT_FALSE(one == two);
+                    EXPECT_TRUE(one != two);
+                }));
+            }));
+
+            EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& two) {
+                EXPECT_OK(safeunionInterface->setA(two, 2, [&](const LargeSafeUnion& two) {
+                    EXPECT_FALSE(one == two);
+                    EXPECT_TRUE(one != two);
+                }));
+            }));
+
+            EXPECT_OK(safeunionInterface->newLargeSafeUnion([&](const LargeSafeUnion& two) {
+                EXPECT_OK(safeunionInterface->setA(two, 1, [&](const LargeSafeUnion& two) {
+                    EXPECT_TRUE(one == two);
+                    EXPECT_FALSE(one != two);
+                }));
+            }));
+        }));
+    }));
+}
+
 int main(int argc, char **argv) {
     setenv("TREBLE_TESTING_OVERRIDE", "true", true);
 
@@ -622,6 +1071,19 @@
         return status;
     }
 
-    return defaultPassthroughServiceImplementation<IBaz>("baz");
+    ::android::status_t status;
+    configureRpcThreadpool(1, true);
 
+    status = registerPassthroughServiceImplementation<IBaz>();
+    CHECK(status == ::android::OK) << "IBaz didn't register";
+
+    status = registerPassthroughServiceImplementation<ISafeUnion>();
+    CHECK(status == ::android::OK) << "ISafeUnion didn't register";
+
+    sp<IOtherInterface> otherInterface = new OtherInterface();
+    status = otherInterface->registerAsService();
+    CHECK(status == ::android::OK) << "IOtherInterface didn't register";
+
+    joinRpcThreadpool();
+    return 0;
 }
diff --git a/test/java_test/src/com/android/commands/hidl_test_java/HidlTestJava.java b/test/java_test/src/com/android/commands/hidl_test_java/HidlTestJava.java
index f670732..0f1394c 100644
--- a/test/java_test/src/com/android/commands/hidl_test_java/HidlTestJava.java
+++ b/test/java_test/src/com/android/commands/hidl_test_java/HidlTestJava.java
@@ -20,13 +20,26 @@
 import android.hardware.tests.baz.V1_0.IBase;
 import android.hardware.tests.baz.V1_0.IBaz;
 import android.hardware.tests.baz.V1_0.IQuux;
+import android.hardware.tests.baz.V1_0.IBaz.MyHandle;
 import android.hardware.tests.baz.V1_0.IBaz.NestedStruct;
 import android.hardware.tests.baz.V1_0.IBazCallback;
+import android.hardware.tests.safeunion.V1_0.IOtherInterface;
+import android.hardware.tests.safeunion.V1_0.ISafeUnion;
+import android.hardware.tests.safeunion.V1_0.ISafeUnion.HandleTypeSafeUnion;
+import android.hardware.tests.safeunion.V1_0.ISafeUnion.InterfaceTypeSafeUnion;
+import android.hardware.tests.safeunion.V1_0.ISafeUnion.LargeSafeUnion;
+import android.hardware.tests.safeunion.V1_0.ISafeUnion.SmallSafeUnion;
 import android.os.HwBinder;
+import android.os.NativeHandle;
 import android.os.RemoteException;
 import android.os.HidlSupport;
 import android.util.Log;
 
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.NoSuchElementException;
@@ -46,7 +59,7 @@
         System.exit(exitCode);
     }
 
-    public int run(String[] args) throws RemoteException {
+    public int run(String[] args) throws RemoteException, IOException {
         if (args[0].equals("-c")) {
             client();
         } else if (args[0].equals("-s")) {
@@ -214,7 +227,269 @@
         ExpectTrue(!HidlSupport.deepEquals(l, r));
     }
 
-    private void client() throws RemoteException {
+    private void runClientSafeUnionTests() throws RemoteException, IOException {
+        ISafeUnion safeunionInterface = ISafeUnion.getService();
+
+        {
+            // SafeUnionNoInitTest
+            LargeSafeUnion safeUnion = safeunionInterface.newLargeSafeUnion();
+            ExpectTrue(safeUnion.getDiscriminator() == LargeSafeUnion.hidl_discriminator.noinit);
+        }
+        {
+            // SafeUnionSimpleTest
+            LargeSafeUnion safeUnion = safeunionInterface.newLargeSafeUnion();
+
+            safeUnion = safeunionInterface.setA(safeUnion, (byte) -5);
+            ExpectTrue(safeUnion.getDiscriminator() == LargeSafeUnion.hidl_discriminator.a);
+            ExpectTrue(safeUnion.a() == (byte) -5);
+
+            safeUnion = safeunionInterface.setD(safeUnion, Long.MAX_VALUE);
+            ExpectTrue(safeUnion.getDiscriminator() == LargeSafeUnion.hidl_discriminator.d);
+            ExpectTrue(safeUnion.d() == Long.MAX_VALUE);
+        }
+        {
+            // SafeUnionArrayLikeTypesTest
+            long[] testArray = new long[] {1, -2, 3, -4, 5};
+            ArrayList<Long> testVector = new ArrayList<Long>(Arrays.asList(Long.MAX_VALUE));
+
+            LargeSafeUnion safeUnion = safeunionInterface.newLargeSafeUnion();
+            safeUnion = safeunionInterface.setF(safeUnion, testArray);
+            ExpectTrue(safeUnion.getDiscriminator() == LargeSafeUnion.hidl_discriminator.f);
+            ExpectDeepEq(testArray, safeUnion.f());
+
+            safeUnion = safeunionInterface.newLargeSafeUnion();
+            safeUnion = safeunionInterface.setI(safeUnion, testVector);
+            ExpectTrue(safeUnion.getDiscriminator() == LargeSafeUnion.hidl_discriminator.i);
+            ExpectDeepEq(testVector, safeUnion.i());
+        }
+        {
+            // SafeUnionStringTypeTest
+            String testString = "This is an inordinately long test string.";
+
+            LargeSafeUnion safeUnion = safeunionInterface.newLargeSafeUnion();
+            safeUnion = safeunionInterface.setG(safeUnion, testString);
+            ExpectTrue(safeUnion.getDiscriminator() == LargeSafeUnion.hidl_discriminator.g);
+            ExpectDeepEq(testString, safeUnion.g());
+        }
+        {
+            // SafeUnionNestedTest
+            SmallSafeUnion smallSafeUnion = new SmallSafeUnion();
+            smallSafeUnion.a((byte) 1);
+
+            LargeSafeUnion safeUnion = safeunionInterface.newLargeSafeUnion();
+            safeUnion = safeunionInterface.setL(safeUnion, smallSafeUnion);
+            ExpectTrue(safeUnion.getDiscriminator() == LargeSafeUnion.hidl_discriminator.l);
+            ExpectTrue(safeUnion.l().getDiscriminator() == SmallSafeUnion.hidl_discriminator.a);
+            ExpectTrue(safeUnion.l().a() == (byte) 1);
+        }
+        {
+            // SafeUnionEnumTest
+            LargeSafeUnion safeUnion = safeunionInterface.newLargeSafeUnion();
+            safeUnion = safeunionInterface.setM(safeUnion, ISafeUnion.BitField.V1);
+            ExpectTrue(safeUnion.getDiscriminator() == LargeSafeUnion.hidl_discriminator.m);
+            ExpectTrue(safeUnion.m() == ISafeUnion.BitField.V1);
+        }
+        {
+            // SafeUnionBitFieldTest
+            LargeSafeUnion safeUnion = safeunionInterface.newLargeSafeUnion();
+            safeUnion = safeunionInterface.setN(safeUnion, ISafeUnion.BitField.V1);
+            ExpectTrue(safeUnion.getDiscriminator() == LargeSafeUnion.hidl_discriminator.n);
+            ExpectTrue(safeUnion.n() == ISafeUnion.BitField.V1);
+        }
+        {
+            // SafeUnionInterfaceNullNativeHandleTest
+            InterfaceTypeSafeUnion safeUnion = new InterfaceTypeSafeUnion();
+
+            safeUnion = safeunionInterface.setInterfaceF(safeUnion, null);
+            ExpectTrue(safeUnion.getDiscriminator() == InterfaceTypeSafeUnion.hidl_discriminator.f);
+            ExpectTrue(safeUnion.f() == null);
+        }
+        {
+            // SafeUnionInterfaceTest
+            byte[] testArray = new byte[] {-1, -2, -3, 0, 1, 2, 3};
+            ArrayList<String> testVector = new ArrayList(Arrays.asList("So", "Many", "Words"));
+            String testStringA = "Hello";
+            String testStringB = "World";
+
+            IOtherInterface otherInterface = IOtherInterface.getService();
+
+            ArrayList<NativeHandle> testHandlesVector = new ArrayList<>();
+            for (int i = 0; i < 128; i++) {
+                testHandlesVector.add(new NativeHandle());
+            }
+
+            InterfaceTypeSafeUnion safeUnion = safeunionInterface.newInterfaceTypeSafeUnion();
+            safeUnion = safeunionInterface.setInterfaceB(safeUnion, testArray);
+            ExpectTrue(safeUnion.getDiscriminator() == InterfaceTypeSafeUnion.hidl_discriminator.b);
+            ExpectDeepEq(testArray, safeUnion.b());
+
+            safeUnion.c(otherInterface);
+            ExpectTrue(safeUnion.getDiscriminator() == InterfaceTypeSafeUnion.hidl_discriminator.c);
+            ExpectTrue(HidlSupport.interfacesEqual(otherInterface, safeUnion.c()));
+            String result = safeUnion.c().concatTwoStrings(testStringA, testStringB);
+            Expect(result, testStringA + testStringB);
+
+            safeUnion = safeunionInterface.setInterfaceD(safeUnion, testStringA);
+            ExpectTrue(safeUnion.getDiscriminator() == InterfaceTypeSafeUnion.hidl_discriminator.d);
+            Expect(testStringA, safeUnion.d());
+
+            safeUnion = safeunionInterface.setInterfaceE(safeUnion, testVector);
+            ExpectTrue(safeUnion.getDiscriminator() == InterfaceTypeSafeUnion.hidl_discriminator.e);
+            ExpectDeepEq(testVector, safeUnion.e());
+
+            safeUnion = safeunionInterface.setInterfaceG(safeUnion, testHandlesVector);
+            ExpectTrue(safeUnion.getDiscriminator() == InterfaceTypeSafeUnion.hidl_discriminator.g);
+            ExpectTrue(safeUnion.g().size() == testHandlesVector.size());
+
+            for (int i = 0; i < testHandlesVector.size(); i++) {
+                ExpectFalse(safeUnion.g().get(i).hasSingleFileDescriptor());
+            }
+        }
+        {
+            // SafeUnionNullNativeHandleTest
+            HandleTypeSafeUnion safeUnion = new HandleTypeSafeUnion();
+
+            safeUnion = safeunionInterface.setHandleA(safeUnion, null);
+            ExpectTrue(safeUnion.getDiscriminator() == HandleTypeSafeUnion.hidl_discriminator.a);
+            ExpectTrue(safeUnion.a() == null);
+        }
+        {
+            // SafeUnionDefaultNativeHandleTest
+            NativeHandle[] testHandlesArray = new NativeHandle[5];
+            for (int i = 0; i < testHandlesArray.length; i++) {
+                testHandlesArray[i] = new NativeHandle();
+            }
+
+            ArrayList<NativeHandle> testHandlesList = new ArrayList<NativeHandle>(
+                Arrays.asList(testHandlesArray));
+
+            HandleTypeSafeUnion safeUnion = safeunionInterface.newHandleTypeSafeUnion();
+            safeUnion = safeunionInterface.setHandleA(safeUnion, new NativeHandle());
+            ExpectTrue(safeUnion.getDiscriminator() == HandleTypeSafeUnion.hidl_discriminator.a);
+            ExpectFalse(safeUnion.a().hasSingleFileDescriptor());
+
+            safeUnion = safeunionInterface.setHandleB(safeUnion, testHandlesArray);
+            ExpectTrue(safeUnion.getDiscriminator() == HandleTypeSafeUnion.hidl_discriminator.b);
+            ExpectTrue(safeUnion.b().length == testHandlesArray.length);
+
+            for (int i = 0; i < testHandlesArray.length; i++) {
+                ExpectFalse(safeUnion.b()[i].hasSingleFileDescriptor());
+            }
+
+            safeUnion = safeunionInterface.setHandleC(safeUnion, testHandlesList);
+            ExpectTrue(safeUnion.getDiscriminator() == HandleTypeSafeUnion.hidl_discriminator.c);
+            ExpectTrue(safeUnion.c().size() == testHandlesList.size());
+
+            for (int i = 0; i < testHandlesList.size(); i++) {
+                ExpectFalse(safeUnion.c().get(i).hasSingleFileDescriptor());
+            }
+        }
+        {
+            // SafeUnionNativeHandleWithFdTest
+            final String testFileName = "/data/local/tmp/SafeUnionNativeHandleWithFdTest";
+            final String[] testStrings = {"This ", "is ", "so ", "much ", "data!\n"};
+            File file = new File(testFileName);
+
+            if (file.exists()) { ExpectTrue(file.delete()); }
+            ExpectTrue(file.createNewFile());
+
+            StringBuilder builder = new StringBuilder();
+            for (String testString : testStrings) {
+                builder.append(testString);
+            }
+            final String goldenResult = builder.toString();
+
+            ArrayList<NativeHandle> testHandlesList = new ArrayList<NativeHandle>();
+            FileOutputStream fos = new FileOutputStream(file);
+            for (int i = 0; i < testStrings.length; i++) {
+                testHandlesList.add(new NativeHandle(fos.getFD(), false /*own*/));
+            }
+
+            HandleTypeSafeUnion safeUnion = safeunionInterface.newHandleTypeSafeUnion();
+            safeUnion = safeunionInterface.setHandleC(safeUnion, testHandlesList);
+            for (int i = 0; i < safeUnion.c().size(); i++) {
+                ExpectTrue(safeUnion.c().get(i).hasSingleFileDescriptor());
+
+                // If you want to copy it out of the binder buffer or save it, it needs to be duped.
+                // This isn't necessary for the test since it is kept open for the binder window.
+                NativeHandle handle = safeUnion.c().get(i);
+                if (i%2 == 0) handle = handle.dup();
+
+                // Original fd is duped if not dup'd above
+                FileDescriptor resultFd = handle.getFileDescriptor();
+                ExpectTrue(resultFd.getInt$() != fos.getFD().getInt$());
+
+                FileOutputStream otherFos = new FileOutputStream(resultFd);
+                otherFos.write(testStrings[i].getBytes());
+                otherFos.flush();
+
+                otherFos.close();
+
+                if (i%2 == 0) handle.close();
+            }
+
+            byte[] resultData = new byte[(int) file.length()];
+            FileInputStream fis = new FileInputStream(file);
+            fis.read(resultData);
+
+            String result = new String(resultData);
+            Expect(result, goldenResult);
+
+            fis.close();
+            fos.close();
+            ExpectTrue(file.delete());
+        }
+        {
+            // SafeUnionEqualityTest
+            LargeSafeUnion one = safeunionInterface.newLargeSafeUnion();
+            LargeSafeUnion two = safeunionInterface.newLargeSafeUnion();
+            ExpectTrue(one.equals(two));
+
+            one = safeunionInterface.setA(one, (byte) 1);
+            ExpectFalse(one.equals(two));
+
+            two = safeunionInterface.setB(two, (byte) 1);
+            ExpectFalse(one.equals(two));
+
+            two = safeunionInterface.setA(two, (byte) 2);
+            ExpectFalse(one.equals(two));
+
+            two = safeunionInterface.setA(two, (byte) 1);
+            ExpectTrue(one.equals(two));
+        }
+        {
+            // SafeUnionDeepEqualityTest
+            ArrayList<Long> testVectorA = new ArrayList(Arrays.asList(1L, 2L, 3L));
+            ArrayList<Long> testVectorB = new ArrayList(Arrays.asList(2L, 1L, 3L));
+
+            LargeSafeUnion one = safeunionInterface.newLargeSafeUnion();
+            LargeSafeUnion two = safeunionInterface.newLargeSafeUnion();
+
+            one = safeunionInterface.setI(one, testVectorA);
+            two = safeunionInterface.setI(two, testVectorB);
+            ExpectFalse(one.equals(two));
+
+            two = safeunionInterface.setI(two, (ArrayList<Long>) testVectorA.clone());
+            ExpectTrue(one.equals(two));
+        }
+        {
+            // SafeUnionHashCodeTest
+            ArrayList<Boolean> testVector =
+                new ArrayList(Arrays.asList(true, false, false, true, true));
+
+            LargeSafeUnion one = safeunionInterface.newLargeSafeUnion();
+            LargeSafeUnion two = safeunionInterface.newLargeSafeUnion();
+
+            one = safeunionInterface.setH(one, testVector);
+            two = safeunionInterface.setA(two, (byte) -5);
+            ExpectFalse(one.hashCode() == two.hashCode());
+
+            two = safeunionInterface.setH(two, (ArrayList<Boolean>) testVector.clone());
+            ExpectTrue(one.hashCode() == two.hashCode());
+        }
+    }
+
+    private void client() throws RemoteException, IOException {
 
         ExpectDeepEq(null, null);
         ExpectDeepNe(null, new String());
@@ -271,7 +546,7 @@
 
         {
             // Test access through base interface binder.
-            IBase baseProxy = IBase.getService("baz");
+            IBase baseProxy = IBase.getService();
             baseProxy.someBaseMethod();
 
             IBaz bazProxy = IBaz.castFrom(baseProxy);
@@ -285,13 +560,13 @@
 
         {
             // Test waiting API
-            IBase baseProxyA = IBaz.getService("baz", true /* retry */);
+            IBase baseProxyA = IBaz.getService(true /* retry */);
             ExpectTrue(baseProxyA != null);
-            IBase baseProxyB = IBaz.getService("baz", false /* retry */);
+            IBase baseProxyB = IBaz.getService(false /* retry */);
             ExpectTrue(baseProxyB != null);
         }
 
-        IBaz proxy = IBaz.getService("baz");
+        IBaz proxy = IBaz.getService();
 
         proxy.ping();
 
@@ -705,7 +980,7 @@
             ArrayList<byte[]> in = new ArrayList<byte[]>();
 
             int k = 0;
-            for (int i = 0; i < in.size(); ++i) {
+            for (int i = 0; i < 8; ++i) {
                 byte[] elem = new byte[128];
                 for (int j = 0; j < 128; ++j, ++k) {
                     elem[j] = (byte)k;
@@ -714,7 +989,34 @@
             }
 
             ArrayList<byte[]> out = proxy.testByteVecs(in);
-            ExpectTrue(in.equals(out));
+
+            ExpectDeepEq(in, out);
+        }
+
+        {
+            // testByteVecs w/ mismatched element lengths.
+
+            ArrayList<byte[]> in = new ArrayList<byte[]>();
+
+            int k = 0;
+            for (int i = 0; i < 8; ++i) {
+                byte[] elem = new byte[128 - i];
+                for (int j = 0; j < (128 - i); ++j, ++k) {
+                    elem[j] = (byte)k;
+                }
+                in.add(elem);
+            }
+
+            boolean failedAsItShould = false;
+
+            try {
+                ArrayList<byte[]> out = proxy.testByteVecs(in);
+            }
+            catch (IllegalArgumentException e) {
+                failedAsItShould = true;
+            }
+
+            ExpectTrue(failedAsItShould);
         }
 
         {
@@ -723,7 +1025,7 @@
             ArrayList<boolean[]> in = new ArrayList<boolean[]>();
 
             int k = 0;
-            for (int i = 0; i < in.size(); ++i) {
+            for (int i = 0; i < 8; ++i) {
                 boolean[] elem = new boolean[128];
                 for (int j = 0; j < 128; ++j, ++k) {
                     elem[j] = (k & 4) != 0;
@@ -732,7 +1034,7 @@
             }
 
             ArrayList<boolean[]> out = proxy.testBooleanVecs(in);
-            ExpectTrue(in.equals(out));
+            ExpectDeepEq(in, out);
         }
 
         {
@@ -741,7 +1043,7 @@
             ArrayList<double[]> in = new ArrayList<double[]>();
 
             int k = 0;
-            for (int i = 0; i < in.size(); ++i) {
+            for (int i = 0; i < 8; ++i) {
                 double[] elem = new double[128];
                 for (int j = 0; j < 128; ++j, ++k) {
                     elem[j] = k;
@@ -750,16 +1052,15 @@
             }
 
             ArrayList<double[]> out = proxy.testDoubleVecs(in);
-            ExpectTrue(in.equals(out));
+            ExpectDeepEq(in, out);
         }
-
         {
             // testProxyEquals
             // TODO(b/68727931): test passthrough services as well.
 
-            IBase proxy1 = IBase.getService("baz");
-            IBase proxy2 = IBase.getService("baz");
-            IBaz proxy3 = IBaz.getService("baz");
+            IBase proxy1 = IBase.getService();
+            IBase proxy2 = IBase.getService();
+            IBaz proxy3 = IBaz.getService();
             IBazCallback callback1 = new BazCallback();
             IBazCallback callback2 = new BazCallback();
             IServiceManager manager = IServiceManager.getService();
@@ -789,7 +1090,7 @@
             ExpectFalse(set.contains(manager));
         }
         {
-            IBaz baz = IBaz.getService("baz");
+            IBaz baz = IBaz.getService();
             ExpectTrue(baz != null);
             IBaz.StructWithInterface swi = new IBaz.StructWithInterface();
             swi.dummy = baz;
@@ -801,6 +1102,8 @@
             ExpectTrue(swi_back.number == 12345678);
         }
 
+        runClientSafeUnionTests();
+
         // --- DEATH RECIPIENT TESTING ---
         // This must always be done last, since it will kill the native server process
         HidlDeathRecipient recipient1 = new HidlDeathRecipient();
@@ -808,7 +1111,12 @@
 
         final int cookie1 = 0x1481;
         final int cookie2 = 0x1482;
+        final int cookie3 = 0x1483;
         ExpectTrue(proxy.linkToDeath(recipient1, cookie1));
+
+        ExpectTrue(proxy.linkToDeath(recipient1, cookie3));
+        ExpectTrue(proxy.unlinkToDeath(recipient1));
+
         ExpectTrue(proxy.linkToDeath(recipient2, cookie2));
         ExpectTrue(proxy.unlinkToDeath(recipient2));
         try {
@@ -1087,11 +1395,242 @@
         }
     }
 
+    class SafeUnion extends ISafeUnion.Stub {
+        @Override
+        public LargeSafeUnion newLargeSafeUnion() {
+            Log.d(TAG, "SERVER: newLargeSafeUnion");
+            return new LargeSafeUnion();
+        }
+
+        @Override
+        public LargeSafeUnion setA(LargeSafeUnion safeUnion, byte a) {
+            Log.d(TAG, "SERVER: setA(" + a + ")");
+            safeUnion.a(a);
+
+            return safeUnion;
+        }
+
+        @Override
+        public LargeSafeUnion setB(LargeSafeUnion safeUnion, short b) {
+            Log.d(TAG, "SERVER: setB(" + b + ")");
+            safeUnion.b(b);
+
+            return safeUnion;
+        }
+
+        @Override
+        public LargeSafeUnion setC(LargeSafeUnion safeUnion, int c) {
+            Log.d(TAG, "SERVER: setC(" + c + ")");
+            safeUnion.c(c);
+
+            return safeUnion;
+        }
+
+        @Override
+        public LargeSafeUnion setD(LargeSafeUnion safeUnion, long d) {
+            Log.d(TAG, "SERVER: setD(" + d + ")");
+            safeUnion.d(d);
+
+            return safeUnion;
+        }
+
+        @Override
+        public LargeSafeUnion setE(LargeSafeUnion safeUnion, byte[/* 13 */] e) {
+            Log.d(TAG, "SERVER: setE(" + e + ")");
+            safeUnion.e(e);
+
+            return safeUnion;
+        }
+
+        @Override
+        public LargeSafeUnion setF(LargeSafeUnion safeUnion, long[/* 5 */] f) {
+            Log.d(TAG, "SERVER: setF(" + f + ")");
+            safeUnion.f(f);
+
+            return safeUnion;
+        }
+
+        @Override
+        public LargeSafeUnion setG(LargeSafeUnion safeUnion, String g) {
+            Log.d(TAG, "SERVER: setG(" + g + ")");
+            safeUnion.g(g);
+
+            return safeUnion;
+        }
+
+        @Override
+        public LargeSafeUnion setH(LargeSafeUnion safeUnion, ArrayList<Boolean> h) {
+            Log.d(TAG, "SERVER: setH(" + h + ")");
+            safeUnion.h(h);
+
+            return safeUnion;
+        }
+
+        @Override
+        public LargeSafeUnion setI(LargeSafeUnion safeUnion, ArrayList<Long> i) {
+            Log.d(TAG, "SERVER: setI(" + i + ")");
+            safeUnion.i(i);
+
+            return safeUnion;
+        }
+
+        @Override
+        public LargeSafeUnion setJ(LargeSafeUnion safeUnion, ISafeUnion.J j) {
+            Log.d(TAG, "SERVER: setJ(" + j + ")");
+            safeUnion.j(j);
+
+            return safeUnion;
+        }
+
+        @Override
+        public LargeSafeUnion setK(LargeSafeUnion safeUnion, LargeSafeUnion.K k) {
+            Log.d(TAG, "SERVER: setK(" + k + ")");
+            safeUnion.k(k);
+
+            return safeUnion;
+        }
+
+        @Override
+        public LargeSafeUnion setL(LargeSafeUnion safeUnion, SmallSafeUnion l) {
+            Log.d(TAG, "SERVER: setL(" + l + ")");
+            safeUnion.l(l);
+
+            return safeUnion;
+        }
+
+        @Override
+        public LargeSafeUnion setM(LargeSafeUnion safeUnion, byte m) {
+            Log.d(TAG, "SERVER: setM(" + m + ")");
+            safeUnion.m(m);
+
+            return safeUnion;
+        }
+
+        @Override
+        public LargeSafeUnion setN(LargeSafeUnion safeUnion, byte n) {
+            Log.d(TAG, "SERVER: setN(" + n + ")");
+            safeUnion.n(n);
+
+            return safeUnion;
+        }
+
+        @Override
+        public InterfaceTypeSafeUnion newInterfaceTypeSafeUnion() {
+            Log.d(TAG, "SERVER: newInterfaceTypeSafeUnion");
+            return new InterfaceTypeSafeUnion();
+        }
+
+        @Override
+        public InterfaceTypeSafeUnion setInterfaceA(InterfaceTypeSafeUnion safeUnion, int a) {
+            Log.d(TAG, "SERVER: setInterfaceA(" + a + ")");
+            safeUnion.a(a);
+
+            return safeUnion;
+        }
+
+        @Override
+        public InterfaceTypeSafeUnion setInterfaceB(
+            InterfaceTypeSafeUnion safeUnion, byte[/* 7 */] b) {
+            Log.d(TAG, "SERVER: setInterfaceB(" + b + ")");
+            safeUnion.b(b);
+
+            return safeUnion;
+        }
+
+        @Override
+        public InterfaceTypeSafeUnion setInterfaceC(
+            InterfaceTypeSafeUnion safeUnion, IOtherInterface c) {
+            Log.d(TAG, "SERVER: setInterfaceC(" + c + ")");
+            safeUnion.c(c);
+
+            return safeUnion;
+        }
+
+        @Override
+        public InterfaceTypeSafeUnion setInterfaceD(InterfaceTypeSafeUnion safeUnion, String d) {
+            Log.d(TAG, "SERVER: setInterfaceD(" + d + ")");
+            safeUnion.d(d);
+
+            return safeUnion;
+        }
+
+        @Override
+        public InterfaceTypeSafeUnion setInterfaceE(
+            InterfaceTypeSafeUnion safeUnion, ArrayList<String> e) {
+            Log.d(TAG, "SERVER: setInterfaceE(" + e + ")");
+            safeUnion.e(e);
+
+            return safeUnion;
+        }
+
+        @Override
+        public InterfaceTypeSafeUnion setInterfaceF(
+            InterfaceTypeSafeUnion safeUnion, NativeHandle f) {
+            Log.d(TAG, "SERVER: setInterfaceF(" + f + ")");
+            safeUnion.f(f);
+
+            return safeUnion;
+        }
+
+        @Override
+        public InterfaceTypeSafeUnion setInterfaceG(
+            InterfaceTypeSafeUnion safeUnion, ArrayList<NativeHandle> g) {
+            Log.d(TAG, "SERVER: setInterfaceG(" + g + ")");
+            safeUnion.g(g);
+
+            return safeUnion;
+        }
+
+        @Override
+        public HandleTypeSafeUnion newHandleTypeSafeUnion() {
+            Log.d(TAG, "SERVER: newHandleTypeSafeUnion");
+            return new HandleTypeSafeUnion();
+        }
+
+        @Override
+        public HandleTypeSafeUnion setHandleA(HandleTypeSafeUnion safeUnion, NativeHandle a) {
+            Log.d(TAG, "SERVER: setHandleA(" + a + ")");
+            safeUnion.a(a);
+
+            return safeUnion;
+        }
+
+        @Override
+        public HandleTypeSafeUnion setHandleB(HandleTypeSafeUnion safeUnion, NativeHandle[] b) {
+            Log.d(TAG, "SERVER: setHandleB(" + b + ")");
+            safeUnion.b(b);
+
+            return safeUnion;
+        }
+
+        @Override
+        public HandleTypeSafeUnion setHandleC(HandleTypeSafeUnion safeUnion,
+                                              ArrayList<NativeHandle> c) {
+            Log.d(TAG, "SERVER: setHandleC(" + c + ")");
+            safeUnion.c(c);
+
+            return safeUnion;
+        }
+    }
+
+    class OtherInterface extends IOtherInterface.Stub {
+        @Override
+        public String concatTwoStrings(String a, String b) {
+            return a.concat(b);
+        }
+    }
+
     private void server() throws RemoteException {
         HwBinder.configureRpcThreadpool(1, true);
 
         Baz baz = new Baz();
-        baz.registerAsService("baz");
+        baz.registerAsService("default");
+
+        SafeUnion safeunionInterface = new SafeUnion();
+        safeunionInterface.registerAsService("default");
+
+        OtherInterface otherInterface = new OtherInterface();
+        otherInterface.registerAsService("default");
 
         HwBinder.joinRpcThreadpool();
     }
diff --git a/test/lazy_test/Android.bp b/test/lazy_test/Android.bp
new file mode 100644
index 0000000..8764b30
--- /dev/null
+++ b/test/lazy_test/Android.bp
@@ -0,0 +1,15 @@
+cc_test {
+    name: "hidl_lazy_test",
+    defaults: ["hidl-gen-defaults"],
+    srcs: ["main.cpp"],
+
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libhidlbase",
+        "libhidltransport",
+        "libhidlmemory",
+        "libhwbinder",
+        "libutils",
+    ],
+}
diff --git a/test/lazy_test/main.cpp b/test/lazy_test/main.cpp
new file mode 100644
index 0000000..7126e85
--- /dev/null
+++ b/test/lazy_test/main.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2019, 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.
+ */
+
+#include <cstdlib>
+#include <ctime>
+#include <iostream>
+#include <numeric>
+#include <string>
+#include <thread>
+
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+#include <hidl/HidlSupport.h>
+#include <hidl/HidlTransportSupport.h>
+#include <hidl/HidlTransportUtils.h>
+#include <hwbinder/IPCThreadState.h>
+
+using ::android::sp;
+using ::android::hardware::IPCThreadState;
+using ::android::hidl::base::V1_0::IBase;
+
+static std::string gDescriptor;
+static std::string gInstance;
+
+sp<IBase> getHal() {
+    return ::android::hardware::details::getRawServiceInternal(gDescriptor, gInstance,
+                                                               true /*retry*/, false /*getStub*/);
+}
+
+static constexpr size_t NUM_IMMEDIATE_GET_UNGETS = 100;
+TEST(LazyHidl, GetUnget) {
+    for (size_t i = 0; i < NUM_IMMEDIATE_GET_UNGETS; i++) {
+        IPCThreadState::self()->flushCommands();
+        sp<IBase> hal = getHal();
+        ASSERT_NE(nullptr, hal.get());
+        EXPECT_TRUE(hal->ping().isOk());
+    }
+}
+
+static std::vector<size_t> waitTimes(size_t numTimes, size_t maxWait) {
+    std::vector<size_t> times(numTimes);
+    for (size_t i = 0; i < numTimes; i++) {
+        times[i] = (size_t)(rand() % (maxWait + 1));
+    }
+    return times;
+}
+
+static void testWithTimes(const std::vector<size_t>& waitTimes) {
+    std::cout << "Note runtime expected from sleeps: "
+              << std::accumulate(waitTimes.begin(), waitTimes.end(), 0) << " second(s)."
+              << std::endl;
+
+    for (size_t sleepTime : waitTimes) {
+        IPCThreadState::self()->flushCommands();
+        std::cout << "Thread waiting " << sleepTime << " while not holding HAL." << std::endl;
+        sleep(sleepTime);
+        sp<IBase> hal = getHal();
+        ASSERT_NE(nullptr, hal.get());
+        ASSERT_TRUE(hal->ping().isOk());
+    }
+}
+
+static constexpr size_t NUM_TIMES_GET_UNGET = 5;
+static constexpr size_t MAX_WAITING_DURATION = 10;
+static constexpr size_t NUM_CONCURRENT_THREADS = 5;
+TEST(LazyHidl, GetWithWaitConcurrent) {
+    std::vector<std::vector<size_t>> threadWaitTimes(NUM_CONCURRENT_THREADS);
+
+    for (size_t i = 0; i < threadWaitTimes.size(); i++) {
+        threadWaitTimes[i] = waitTimes(NUM_TIMES_GET_UNGET, MAX_WAITING_DURATION);
+    }
+
+    std::vector<std::thread> threads(NUM_CONCURRENT_THREADS);
+    for (size_t i = 0; i < threads.size(); i++) {
+        threads[i] = std::thread(testWithTimes, threadWaitTimes[i]);
+    }
+
+    for (auto& thread : threads) {
+        thread.join();
+    }
+}
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+
+    srand(time(nullptr));
+
+    if (argc != 3) {
+        std::cerr << "Usage: lazy_test fqname instance" << std::endl;
+        return 1;
+    }
+
+    gDescriptor = argv[1];
+    gInstance = argv[2];
+
+    return RUN_ALL_TESTS();
+}
\ No newline at end of file
diff --git a/test/run_all_device_tests.sh b/test/run_all_device_tests.sh
index a32558a..139f086 100755
--- a/test/run_all_device_tests.sh
+++ b/test/run_all_device_tests.sh
@@ -21,9 +21,14 @@
     )
 
     $ANDROID_BUILD_TOP/build/soong/soong_ui.bash --make-mode -j \
-        ${RUN_TIME_TESTS[*]} ${SCRIPT_TESTS[*]} || return
+        ${RUN_TIME_TESTS[*]} ${SCRIPT_TESTS[*]} || exit 1
 
-    adb sync || return
+    # TODO(b/129507417): build with supported configurations
+    mkdir -p $ANDROID_PRODUCT_OUT/data/framework/hidl_test_java.jar || exit 1
+    cp $ANDROID_PRODUCT_OUT/testcases/hidl_test_java_java/arm64/hidl_test_java_java.jar \
+       $ANDROID_PRODUCT_OUT/data/framework/hidl_test_java_java.jar || exit 1
+
+    adb sync data || exit 1
 
     local BITNESS=("nativetest" "nativetest64")
 
@@ -48,6 +53,7 @@
         for failed in ${FAILED_TESTS[@]}; do
             echo "FAILED TEST: $failed"
         done
+        exit 1
     else
         echo "SUCCESS"
     fi
diff --git a/test/run_all_host_tests.sh b/test/run_all_host_tests.sh
index 442efd7..24e294e 100755
--- a/test/run_all_host_tests.sh
+++ b/test/run_all_host_tests.sh
@@ -8,11 +8,13 @@
         hidl_export_test \
         hidl_hash_test \
         hidl_impl_test \
+        hidl_system_api_test \
         android.hardware.tests.foo@1.0-vts.driver \
         android.hardware.tests.foo@1.0-vts.profiler)
 
     local RUN_TIME_TESTS=(\
         libhidl-gen-utils_test \
+        libhidl-gen-host-utils_test \
         hidl-gen-host_test \
     )
 
@@ -41,4 +43,4 @@
     fi
 }
 
-run
\ No newline at end of file
+run
diff --git a/test/system_api_test/Android.bp b/test/system_api_test/Android.bp
new file mode 100644
index 0000000..fff313a
--- /dev/null
+++ b/test/system_api_test/Android.bp
@@ -0,0 +1,25 @@
+// Copyright (C) 2018 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.
+
+// hidl-generated libs should only depend on @SystemApi
+java_library {
+    name: "hidl_system_api_test",
+    sdk_version: "system_current",
+
+    static_libs: [
+        "android.hidl.base-V1.0-java",
+        "android.hidl.manager-V1.0-java",
+        "android.hardware.tests.baz-V1.0-java",
+    ],
+}
diff --git a/test/utils_test/main.cpp b/test/utils_test/main.cpp
index b2c8576..ee0556f 100644
--- a/test/utils_test/main.cpp
+++ b/test/utils_test/main.cpp
@@ -17,119 +17,15 @@
 #define LOG_TAG "libhidl-gen-utils"
 
 #include <hidl-util/FqInstance.h>
-#include <hidl-util/StringHelper.h>
 
 #include <gtest/gtest.h>
 #include <vector>
 
 using ::android::FqInstance;
-using ::android::StringHelper;
+using ::android::FQName;
 
 class LibHidlGenUtilsTest : public ::testing::Test {};
 
-TEST_F(LibHidlGenUtilsTest, EndsWithTest) {
-    EXPECT_TRUE(StringHelper::EndsWith("", ""));
-    EXPECT_TRUE(StringHelper::EndsWith("a", ""));
-    EXPECT_TRUE(StringHelper::EndsWith("b", "b"));
-    EXPECT_TRUE(StringHelper::EndsWith("ab", "b"));
-    EXPECT_TRUE(StringHelper::EndsWith("ab", "ab"));
-    EXPECT_TRUE(StringHelper::EndsWith("abcd", "bcd"));
-    EXPECT_TRUE(StringHelper::EndsWith("abcd", "abcd"));
-    EXPECT_TRUE(StringHelper::EndsWith("abcdefghijk", "ijk"));
-    EXPECT_TRUE(StringHelper::EndsWith("abcdefghijk", "bcdefghijk"));
-
-    EXPECT_FALSE(StringHelper::EndsWith("", "a"));
-    EXPECT_FALSE(StringHelper::EndsWith("b", "a"));
-    EXPECT_FALSE(StringHelper::EndsWith("abcd", "ab"));
-}
-
-TEST_F(LibHidlGenUtilsTest, StartsWithTest) {
-    EXPECT_TRUE(StringHelper::StartsWith("", ""));
-    EXPECT_TRUE(StringHelper::StartsWith("a", ""));
-    EXPECT_TRUE(StringHelper::StartsWith("b", "b"));
-    EXPECT_TRUE(StringHelper::StartsWith("ab", "a"));
-    EXPECT_TRUE(StringHelper::StartsWith("ab", "ab"));
-    EXPECT_TRUE(StringHelper::StartsWith("abcd", "abc"));
-    EXPECT_TRUE(StringHelper::StartsWith("abcd", "abcd"));
-    EXPECT_TRUE(StringHelper::StartsWith("abcdefghijk", "abc"));
-    EXPECT_TRUE(StringHelper::StartsWith("abcdefghijk", "abcdefghij"));
-
-    EXPECT_FALSE(StringHelper::StartsWith("", "a"));
-    EXPECT_FALSE(StringHelper::StartsWith("b", "a"));
-    EXPECT_FALSE(StringHelper::StartsWith("abcd", "cd"));
-}
-
-TEST_F(LibHidlGenUtilsTest, Trim) {
-    EXPECT_EQ("", StringHelper::LTrim("", ""));
-    EXPECT_EQ("", StringHelper::LTrim("", "a"));
-    EXPECT_EQ("", StringHelper::LTrim("a", "a"));
-    EXPECT_EQ("a", StringHelper::LTrim("a", ""));
-    EXPECT_EQ("a", StringHelper::LTrim("a", "b"));
-    EXPECT_EQ("a", StringHelper::LTrim("ba", "b"));
-    EXPECT_EQ("f", StringHelper::LTrim("abcdef", "abcde"));
-    EXPECT_EQ("cdef", StringHelper::LTrim("abcdef", "ab"));
-    EXPECT_EQ("abcdef", StringHelper::LTrim("abcdef", ""));
-
-    EXPECT_EQ("", StringHelper::RTrim("", ""));
-    EXPECT_EQ("", StringHelper::RTrim("", "a"));
-    EXPECT_EQ("", StringHelper::RTrim("a", "a"));
-    EXPECT_EQ("a", StringHelper::RTrim("a", ""));
-    EXPECT_EQ("a", StringHelper::RTrim("a", "b"));
-    EXPECT_EQ("a", StringHelper::RTrim("ab", "b"));
-    EXPECT_EQ("a", StringHelper::RTrim("abcdef", "bcdef"));
-    EXPECT_EQ("abcd", StringHelper::RTrim("abcdef", "ef"));
-    EXPECT_EQ("abcdef", StringHelper::RTrim("abcdef", ""));
-}
-
-TEST_F(LibHidlGenUtilsTest, TrimAll) {
-    EXPECT_EQ("", StringHelper::LTrimAll("", ""));
-    EXPECT_EQ("", StringHelper::LTrimAll("", "a"));
-    EXPECT_EQ("", StringHelper::LTrimAll("", "ab"));
-    EXPECT_EQ("", StringHelper::LTrimAll("a", "a"));
-    EXPECT_EQ("", StringHelper::LTrimAll("aa", "a"));
-    EXPECT_EQ("b", StringHelper::LTrimAll("b", "a"));
-    EXPECT_EQ("b", StringHelper::LTrimAll("aaab", "a"));
-    EXPECT_EQ("c", StringHelper::LTrimAll("ababc", "ab"));
-    EXPECT_EQ("ac", StringHelper::LTrimAll("abac", "ab"));
-
-    EXPECT_EQ("", StringHelper::RTrimAll("", ""));
-    EXPECT_EQ("", StringHelper::RTrimAll("", "a"));
-    EXPECT_EQ("", StringHelper::RTrimAll("", "ab"));
-    EXPECT_EQ("", StringHelper::RTrimAll("a", "a"));
-    EXPECT_EQ("", StringHelper::RTrimAll("aa", "a"));
-    EXPECT_EQ("b", StringHelper::RTrimAll("b", "a"));
-    EXPECT_EQ("b", StringHelper::RTrimAll("baaa", "a"));
-    EXPECT_EQ("c", StringHelper::RTrimAll("cabab", "ab"));
-    EXPECT_EQ("ca", StringHelper::RTrimAll("caba", "ba"));
-}
-
-TEST_F(LibHidlGenUtilsTest, SplitString) {
-    std::vector<std::string> components;
-
-    StringHelper::SplitString("", '.', &components);
-    EXPECT_EQ(std::vector<std::string>({""}), components);
-    StringHelper::SplitString("a.", '.', &components);
-    EXPECT_EQ(std::vector<std::string>({"a", ""}), components);
-    StringHelper::SplitString(".a", '.', &components);
-    EXPECT_EQ(std::vector<std::string>({"", "a"}), components);
-    StringHelper::SplitString("..", '.', &components);
-    EXPECT_EQ(std::vector<std::string>({"", "", ""}), components);
-    StringHelper::SplitString("asdf.asdf", '.', &components);
-    EXPECT_EQ(std::vector<std::string>({"asdf", "asdf"}), components);
-}
-
-TEST_F(LibHidlGenUtilsTest, JoinStrings) {
-    EXPECT_EQ("", StringHelper::JoinStrings({}, ""));
-    EXPECT_EQ("", StringHelper::JoinStrings({}, "a"));
-    EXPECT_EQ("a", StringHelper::JoinStrings({"a"}, ""));
-    EXPECT_EQ("a,b", StringHelper::JoinStrings({"a", "b"}, ","));
-    EXPECT_EQ("ab,", StringHelper::JoinStrings({"ab", ""}, ","));
-    EXPECT_EQ(",ab", StringHelper::JoinStrings({"", "ab"}, ","));
-    EXPECT_EQ("a.,b", StringHelper::JoinStrings({"a", "b"}, ".,"));
-    EXPECT_EQ("a,b,c", StringHelper::JoinStrings({"a", "b", "c"}, ","));
-    EXPECT_EQ("abc.,def.,ghi", StringHelper::JoinStrings({"abc", "def", "ghi"}, ".,"));
-}
-
 TEST_F(LibHidlGenUtilsTest, FqInstance1) {
     FqInstance e;
     ASSERT_TRUE(e.setTo("android.hardware.foo@1.0::IFoo/instance"));
@@ -190,6 +86,36 @@
     ASSERT_FALSE(e.hasInstance());
 }
 
+TEST_F(LibHidlGenUtilsTest, FqInstanceSetToByComponent) {
+    FqInstance e;
+    ASSERT_TRUE(e.setTo("android.hardware.foo", 1, 0, "IFoo", "default"));
+    EXPECT_EQ("android.hardware.foo@1.0::IFoo/default", e.string());
+    ASSERT_TRUE(e.setTo("android.hardware.foo", 1, 0, "IFoo"));
+    EXPECT_EQ("android.hardware.foo@1.0::IFoo", e.string());
+    ASSERT_TRUE(e.setTo("android.hardware.foo", 1, 0));
+    EXPECT_EQ("android.hardware.foo@1.0", e.string());
+    ASSERT_TRUE(e.setTo(1, 0, "IFoo", "default"));
+    EXPECT_EQ("@1.0::IFoo/default", e.string());
+    ASSERT_TRUE(e.setTo(1, 0, "IFoo"));
+    EXPECT_EQ("@1.0::IFoo", e.string());
+    ASSERT_TRUE(e.setTo("IFoo", "default"));
+    EXPECT_EQ("IFoo/default", e.string());
+}
+
+TEST_F(LibHidlGenUtilsTest, FqDefaultVersion) {
+    FQName n;
+    FqInstance i;
+
+    ASSERT_TRUE(FQName::parse("IFoo.test", &n));
+    EXPECT_EQ((std::make_pair<size_t, size_t>(0u, 0u)), n.getVersion());
+    ASSERT_TRUE(i.setTo("IFoo.test"));
+    EXPECT_EQ((std::make_pair<size_t, size_t>(0u, 0u)), i.getVersion());
+    ASSERT_TRUE(FQName::parse("package@1.2::IFoo", &n));
+    EXPECT_EQ((std::make_pair<size_t, size_t>(1u, 2u)), n.getVersion());
+    ASSERT_TRUE(i.setTo("package@1.2::IFoo"));
+    EXPECT_EQ((std::make_pair<size_t, size_t>(1u, 2u)), i.getVersion());
+}
+
 int main(int argc, char **argv) {
     ::testing::InitGoogleTest(&argc, argv);
     return RUN_ALL_TESTS();
diff --git a/test/vendor/1.0/Android.bp b/test/vendor/1.0/Android.bp
index 8519fa5..ebf8c70 100644
--- a/test/vendor/1.0/Android.bp
+++ b/test/vendor/1.0/Android.bp
@@ -4,6 +4,7 @@
     name: "hidl.tests.vendor@1.0",
     owner: "some-owner-name",
     root: "hidl.tests",
+    product_specific: true,
     srcs: [
         "types.hal",
         "IVendor.hal",
@@ -12,12 +13,10 @@
         "android.hardware.tests.baz@1.0",
         "android.hidl.base@1.0",
     ],
-    types: [
-        "Bar",
-        "Foo",
-        "FooToo",
-    ],
     gen_java: true,
     gen_java_constants: true,
+
+    // tests that license is copied
+    notice: "FAKE_NOTICE_FILE",
 }
 
diff --git a/test/vendor/1.0/FAKE_NOTICE_FILE b/test/vendor/1.0/FAKE_NOTICE_FILE
new file mode 100644
index 0000000..68b43bf
--- /dev/null
+++ b/test/vendor/1.0/FAKE_NOTICE_FILE
@@ -0,0 +1 @@
+I am notifying you of something! Wheeee!
diff --git a/test/vendor/1.1/Android.bp b/test/vendor/1.1/Android.bp
index 80cecb2..fcf8e17 100644
--- a/test/vendor/1.1/Android.bp
+++ b/test/vendor/1.1/Android.bp
@@ -4,6 +4,7 @@
     name: "hidl.tests.vendor@1.1",
     owner: "some-owner-name",
     root: "hidl.tests",
+    product_specific: true,
     srcs: [
         "IVendor.hal",
     ],
diff --git a/test/vendor/1.1/IVendor.hal b/test/vendor/1.1/IVendor.hal
index 437d79f..b574e33 100644
--- a/test/vendor/1.1/IVendor.hal
+++ b/test/vendor/1.1/IVendor.hal
@@ -23,5 +23,10 @@
 import android.frameworks.displayservice@1.0;
 
 interface IVendor extends @1.0::IVendor {
+    safe_union Test {
+        int64_t foo;
+        int32_t bar;
+    };
+
     doAdditionalFunctionality();
 };
diff --git a/test/vendor/Android.bp b/test/vendor/Android.bp
deleted file mode 100644
index dfcd349..0000000
--- a/test/vendor/Android.bp
+++ /dev/null
@@ -1,4 +0,0 @@
-hidl_package_root {
-    name: "hidl.tests",
-    path: "system/tools/hidl/test",
-}
diff --git a/update-makefiles-helper.sh b/update-makefiles-helper.sh
index 30f38a6..0d1676b 100755
--- a/update-makefiles-helper.sh
+++ b/update-makefiles-helper.sh
@@ -20,11 +20,11 @@
   shift 1
 
   for package_root in "$@"; do
-      dir=$(package_root_to_root $package_root)
-      if [ ! -d $root_or_cwd$dir ] ; then
-        echo "Where is $dir?";
-        return 1;
-      fi
+    dir=$(package_root_to_root $package_root)
+    if [ ! -d $root_or_cwd$dir ] ; then
+      echo "Where is $dir?";
+      return 1;
+    fi
   done
 }
 
@@ -47,7 +47,7 @@
 # Usage: get_root_arguments [package:root ...]
 function get_root_arguments() {
   for package_root in "$@"; do
-      echo "-r $package_root"
+    printf "%s" "-r$package_root "
   done
 }
 
@@ -61,6 +61,18 @@
 }
 
 ##
+# Returns the number of processors to run on, on this machine
+function get_num_processors() {
+  if command -v nproc >/dev/null 2>&1; then
+    PROCS=$(nproc --all 2>/dev/null) && echo $PROCS && return 0
+  elif command -v sysctl >/dev/null 2>&1; then
+    PROCS=$(sysctl -n hw.logicalcpu 2>/dev/null) && echo $PROCS && return 0
+  fi
+
+  echo 1
+}
+
+##
 # Helps manage the package root of a HAL directory.
 # Should be called from the android root directory.
 #
@@ -68,10 +80,15 @@
 # Where the first package root is the current one.
 #
 function do_makefiles_update() {
+  if ! command -v hidl-gen 1>/dev/null; then
+    echo "Cannot find hidl-gen, try lunching or making it ('m hidl-gen')?"
+    exit 1
+  fi
+
   local owner=
   if [[ "$1" = "-O" ]]; then
-      owner="$2"
-      shift 2
+    owner="$2"
+    shift 2
   fi
 
   local root_or_cwd=${ANDROID_BUILD_TOP%%/}${ANDROID_BUILD_TOP:+/}
@@ -86,9 +103,19 @@
   local packages=$(get_packages $current_dir $current_package) || return 1
   local root_arguments=$(get_root_arguments $@) || return 1
 
-  for p in $packages; do
-    echo "Updating $p";
-    hidl-gen -O "$owner" -Landroidbp $root_arguments $p;
-    rc=$?; if [[ $rc != 0 ]]; then return $rc; fi
-  done
+  function __update_internal() {
+    local owner="$1"
+    local root_arguments="$2"
+    local package="$3"
+    echo "Updating $package"
+    hidl-gen -O "$owner" -Landroidbp $root_arguments $package || {
+      echo "Command failed: hidl-gen -O \"$owner\" -Landroidbp $root_arguments $package";
+      return 1;
+    }
+  }
+  export -f __update_internal
+
+  echo "$packages" |\
+      xargs -P $(get_num_processors) -I {} \
+      bash -c "__update_internal \"$owner\" \"$root_arguments\" \"{}\"" || return 1
 }
diff --git a/utils/Android.bp b/utils/Android.bp
index 7d97c75..e9fd0c3 100644
--- a/utils/Android.bp
+++ b/utils/Android.bp
@@ -15,12 +15,11 @@
 cc_library {
     name: "libhidl-gen-utils",
     host_supported: true,
+    recovery_available: true,
     defaults: ["hidl-gen-defaults"],
     srcs: [
         "FQName.cpp",
-        "Formatter.cpp",
         "FqInstance.cpp",
-        "StringHelper.cpp",
     ],
     shared_libs: [
         "libbase",
diff --git a/utils/FQName.cpp b/utils/FQName.cpp
index 8f56af8..efa835a 100644
--- a/utils/FQName.cpp
+++ b/utils/FQName.cpp
@@ -16,10 +16,9 @@
 
 #include "FQName.h"
 
-#include "StringHelper.h"
-
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
+#include <android-base/strings.h>
 #include <iostream>
 #include <regex>
 #include <sstream>
@@ -31,53 +30,41 @@
 
 namespace android {
 
-FQName::FQName()
-    : mValid(false),
-      mIsIdentifier(false) {
-}
-
-// TODO(b/73774955): delete
-FQName::FQName(const std::string &s)
-    : mValid(false),
-      mIsIdentifier(false) {
-    (void)setTo(s);
-}
+FQName::FQName() : mIsIdentifier(false) {}
 
 bool FQName::parse(const std::string& s, FQName* into) {
     return into->setTo(s);
 }
 
-FQName::FQName(
-        const std::string &package,
-        const std::string &version,
-        const std::string &name,
-        const std::string &valueName)
-    : mValid(true),
-      mIsIdentifier(false),
-      mPackage(package),
-      mName(name),
-      mValueName(valueName) {
-    CHECK(setVersion(version)) << version;
+FQName::FQName(const std::string& package, const std::string& version, const std::string& name,
+               const std::string& valueName) {
+    size_t majorVer, minorVer;
+    CHECK(parseVersion(version, &majorVer, &minorVer));
+    CHECK(setTo(package, majorVer, minorVer, name, valueName)) << string();
+}
 
-    // Check if this is actually a valid fqName
+bool FQName::setTo(const std::string& package, size_t majorVer, size_t minorVer,
+                   const std::string& name, const std::string& valueName) {
+    mPackage = package;
+    mMajor = majorVer;
+    mMinor = minorVer;
+    mName = name;
+    mValueName = valueName;
+
     FQName other;
-    CHECK(parse(this->string(), &other)) << this->string();
-    CHECK((*this) == other) << this->string() << " " << other.string();
+    if (!parse(string(), &other)) return false;
+    if ((*this) != other) return false;
+    mIsIdentifier = other.isIdentifier();
+    return true;
 }
 
 FQName::FQName(const FQName& other)
-    : mValid(other.mValid),
-      mIsIdentifier(other.mIsIdentifier),
+    : mIsIdentifier(other.mIsIdentifier),
       mPackage(other.mPackage),
       mMajor(other.mMajor),
       mMinor(other.mMinor),
       mName(other.mName),
-      mValueName(other.mValueName) {
-}
-
-bool FQName::isValid() const {
-    return mValid;
-}
+      mValueName(other.mValueName) {}
 
 bool FQName::isIdentifier() const {
     return mIsIdentifier;
@@ -170,9 +157,7 @@
     // package without version is not allowed.
     CHECK(invalid || mPackage.empty() || !version().empty());
 
-    // TODO(b/73774955): remove isValid and users
-    // of old FQName constructors
-    return mValid = !invalid;
+    return !invalid;
 }
 
 const std::string& FQName::package() const {
@@ -199,7 +184,6 @@
 }
 
 void FQName::clear() {
-    mValid = true;
     mIsIdentifier = false;
     mPackage.clear();
     clearVersion();
@@ -207,36 +191,47 @@
     mValueName.clear();
 }
 
-bool FQName::setVersion(const std::string& v) {
+void FQName::clearVersion(size_t* majorVer, size_t* minorVer) {
+    *majorVer = *minorVer = 0;
+}
+
+bool FQName::parseVersion(const std::string& majorStr, const std::string& minorStr,
+                          size_t* majorVer, size_t* minorVer) {
+    bool versionParseSuccess = ::android::base::ParseUint(majorStr, majorVer) &&
+                               ::android::base::ParseUint(minorStr, minorVer);
+    if (!versionParseSuccess) {
+        LOG(ERROR) << "numbers in " << majorStr << "." << minorStr << " are out of range.";
+    }
+    return versionParseSuccess;
+}
+
+bool FQName::parseVersion(const std::string& v, size_t* majorVer, size_t* minorVer) {
     static const std::regex kREVer("(" RE_MAJOR ")[.](" RE_MINOR ")");
 
     if (v.empty()) {
-        clearVersion();
+        clearVersion(majorVer, minorVer);
         return true;
     }
 
     std::smatch match;
     if (!std::regex_match(v, match, kREVer)) {
-        return mValid = false;
+        return false;
     }
     CHECK_EQ(match.size(), 3u);
 
-    return parseVersion(match.str(1), match.str(2));
+    return parseVersion(match.str(1), match.str(2), majorVer, minorVer);
+}
+
+bool FQName::setVersion(const std::string& v) {
+    return parseVersion(v, &mMajor, &mMinor);
 }
 
 void FQName::clearVersion() {
-    mMajor = mMinor = 0;
+    clearVersion(&mMajor, &mMinor);
 }
 
 bool FQName::parseVersion(const std::string& majorStr, const std::string& minorStr) {
-    bool versionParseSuccess =
-        ::android::base::ParseUint(majorStr, &mMajor) &&
-        ::android::base::ParseUint(minorStr, &mMinor);
-    if (!versionParseSuccess) {
-        LOG(ERROR) << "numbers in " << majorStr << "." << minorStr << " are out of range.";
-        mValid = false;
-    }
-    return versionParseSuccess;
+    return parseVersion(majorStr, minorStr, &mMajor, &mMinor);
 }
 
 const std::string& FQName::name() const {
@@ -278,8 +273,6 @@
 }
 
 std::string FQName::string() const {
-    CHECK(mValid) << mPackage << atVersion() << mName;
-
     std::string out;
     out.append(mPackage);
     out.append(atVersion());
@@ -380,13 +373,12 @@
     getPackageAndVersionComponents(&components, true /* cpp_compatible */);
 
     if (!mName.empty()) {
-        std::vector<std::string> nameComponents;
-        StringHelper::SplitString(mName, '.', &nameComponents);
+        std::vector<std::string> nameComponents = base::Split(mName, ".");
 
         components.insert(components.end(), nameComponents.begin(), nameComponents.end());
     }
 
-    return StringHelper::JoinStrings(components, "_");
+    return base::Join(components, "_");
 }
 
 std::string FQName::cppNamespace() const {
@@ -394,26 +386,24 @@
     getPackageAndVersionComponents(&components, true /* cpp_compatible */);
 
     std::string out = "::";
-    out += StringHelper::JoinStrings(components, "::");
+    out += base::Join(components, "::");
 
     return out;
 }
 
 std::string FQName::cppLocalName() const {
-    std::vector<std::string> components;
-    StringHelper::SplitString(mName, '.', &components);
+    std::vector<std::string> components = base::Split(mName, ".");
 
-    return StringHelper::JoinStrings(components, "::")
+    return base::Join(components, "::")
             + (mValueName.empty() ? "" : ("::" + mValueName));
 }
 
 std::string FQName::cppName() const {
     std::string out = cppNamespace();
 
-    std::vector<std::string> components;
-    StringHelper::SplitString(name(), '.', &components);
+    std::vector<std::string> components = base::Split(name(), ".");
     out += "::";
-    out += StringHelper::JoinStrings(components, "::");
+    out += base::Join(components, "::");
     if (!mValueName.empty()) {
         out  += "::" + mValueName;
     }
@@ -425,7 +415,7 @@
     std::vector<std::string> components;
     getPackageAndVersionComponents(&components, true /* cpp_compatible */);
 
-    return StringHelper::JoinStrings(components, ".");
+    return base::Join(components, ".");
 }
 
 std::string FQName::javaName() const {
@@ -434,7 +424,7 @@
 }
 
 void FQName::getPackageComponents(std::vector<std::string> *components) const {
-    StringHelper::SplitString(package(), '.', components);
+    *components = base::Split(package(), ".");
 }
 
 void FQName::getPackageAndVersionComponents(
@@ -460,6 +450,10 @@
     return mMajor > 0;
 }
 
+std::pair<size_t, size_t> FQName::getVersion() const {
+    return {mMajor, mMinor};
+}
+
 FQName FQName::withVersion(size_t major, size_t minor) const {
     FQName ret(*this);
     ret.mMajor = major;
@@ -519,8 +513,7 @@
     std::vector<std::string> components;
     getPackageComponents(&components);
 
-    std::vector<std::string> inComponents;
-    StringHelper::SplitString(package, '.', &inComponents);
+    std::vector<std::string> inComponents = base::Split(package, ".");
 
     if (inComponents.size() > components.size()) {
         return false;
diff --git a/utils/FqInstance.cpp b/utils/FqInstance.cpp
index 0b2b8f8..c0793f7 100644
--- a/utils/FqInstance.cpp
+++ b/utils/FqInstance.cpp
@@ -39,7 +39,7 @@
 }
 
 std::pair<size_t, size_t> FqInstance::getVersion() const {
-    return {getMajorVersion(), getMinorVersion()};
+    return mFqName.getVersion();
 }
 
 bool FqInstance::hasVersion() const {
@@ -62,11 +62,7 @@
     return !mInstance.empty();
 }
 
-bool FqInstance::setTo(const std::string& s) {
-    auto pos = s.find(INSTANCE_SEP);
-    if (!mFqName.setTo(s.substr(0, pos))) return false;
-    mInstance = pos == std::string::npos ? std::string{} : s.substr(pos + 1);
-
+bool FqInstance::isValid() const {
     bool hasPkg = hasPackage();
     bool hasVer = hasVersion();
     bool hasIntf = hasInterface();
@@ -91,12 +87,19 @@
     return !hasInst;
 }
 
+bool FqInstance::setTo(const std::string& s) {
+    auto pos = s.find(INSTANCE_SEP);
+    if (!mFqName.setTo(s.substr(0, pos))) return false;
+    mInstance = pos == std::string::npos ? std::string{} : s.substr(pos + 1);
+
+    return isValid();
+}
+
 bool FqInstance::setTo(const std::string& package, size_t majorVer, size_t minorVer,
                        const std::string& interface, const std::string& instance) {
-    std::stringstream ss;
-    ss << package << "@" << majorVer << "." << minorVer << "::" << interface << INSTANCE_SEP
-       << instance;
-    return setTo(ss.str());
+    if (!mFqName.setTo(package, majorVer, minorVer, interface)) return false;
+    mInstance = instance;
+    return isValid();
 }
 
 bool FqInstance::setTo(size_t majorVer, size_t minorVer, const std::string& interface,
@@ -105,7 +108,7 @@
 }
 
 bool FqInstance::setTo(const std::string& interface, const std::string& instance) {
-    return setTo(interface + INSTANCE_SEP + instance);
+    return setTo(0u, 0u, interface, instance);
 }
 
 std::string FqInstance::string() const {
@@ -126,4 +129,8 @@
     return !(*this == other);
 }
 
+bool FqInstance::inPackage(const std::string& package) const {
+    return mFqName.inPackage(package);
+}
+
 }  // namespace android
diff --git a/utils/include/hidl-util/FQName.h b/utils/include/hidl-util/FQName.h
index 3e31431..416340a 100644
--- a/utils/include/hidl-util/FQName.h
+++ b/utils/include/hidl-util/FQName.h
@@ -28,19 +28,18 @@
 
     explicit FQName();
 
-    // TODO(b/73774955): delete
-    explicit FQName(const std::string &s);
-
     FQName(const std::string& package, const std::string& version, const std::string& name = "",
            const std::string& valueName = "");
 
     FQName(const FQName& other);
 
-    bool isValid() const;
     bool isIdentifier() const;
 
     // Returns false if string isn't a valid FQName object.
     __attribute__((warn_unused_result)) bool setTo(const std::string& s);
+    __attribute__((warn_unused_result)) bool setTo(const std::string& package, size_t majorVer,
+                                                   size_t minorVer, const std::string& name = "",
+                                                   const std::string& valueName = "");
 
     void applyDefaults(
             const std::string &defaultPackage,
@@ -55,6 +54,8 @@
     std::string sanitizedVersion() const;
     // Return true only if version is present.
     bool hasVersion() const;
+    // Return pair of (major, minor) version. Defaults to 0, 0.
+    std::pair<size_t, size_t> getVersion() const;
 
     FQName withVersion(size_t major, size_t minor) const;
 
@@ -225,9 +226,6 @@
     FQName downRev() const;
 
    private:
-    // TODO(b/73774955): remove
-    bool mValid;
-
     bool mIsIdentifier;
     std::string mPackage;
     // mMajor == 0 means empty.
@@ -241,6 +239,15 @@
     __attribute__((warn_unused_result)) bool setVersion(const std::string& v);
     __attribute__((warn_unused_result)) bool parseVersion(const std::string& majorStr,
                                                           const std::string& minorStr);
+    __attribute__((warn_unused_result)) static bool parseVersion(const std::string& majorStr,
+                                                                 const std::string& minorStr,
+                                                                 size_t* majorVer,
+                                                                 size_t* minorVer);
+    __attribute__((warn_unused_result)) static bool parseVersion(const std::string& v,
+                                                                 size_t* majorVer,
+                                                                 size_t* minorVer);
+    static void clearVersion(size_t* majorVer, size_t* minorVer);
+
     void clearVersion();
 };
 
diff --git a/utils/include/hidl-util/FqInstance.h b/utils/include/hidl-util/FqInstance.h
index bdd0043..982bae0 100644
--- a/utils/include/hidl-util/FqInstance.h
+++ b/utils/include/hidl-util/FqInstance.h
@@ -48,6 +48,12 @@
     bool hasInterface() const;
     bool hasInstance() const;
 
+    // If this is android.hardware@1.0::IFoo
+    // package = "and" -> false
+    // package = "android" -> true
+    // package = "android.hardware@1.0" -> false
+    bool inPackage(const std::string& package) const;
+
     // Return true if valid:
     // android.hardware.foo@1.0::IFoo/instance
     // @1.0::IFoo/instance
@@ -66,15 +72,21 @@
     __attribute__((warn_unused_result)) bool setTo(const std::string& s);
 
     // Convenience method for the following formats:
+    // android.hardware.foo@1.0
+    // android.hardware.foo@1.0::IFoo
     // android.hardware.foo@1.0::IFoo/default
-    // @1.0::IFoo/default
-    // IFoo/default
     __attribute__((warn_unused_result)) bool setTo(const std::string& package, size_t majorVer,
-                                                   size_t minorVer, const std::string& interface,
-                                                   const std::string& instance);
+                                                   size_t minorVer,
+                                                   const std::string& interface = "",
+                                                   const std::string& instance = "");
+    // Convenience method for the following formats:
+    // @1.0::IFoo
+    // @1.0::IFoo/default
     __attribute__((warn_unused_result)) bool setTo(size_t majorVer, size_t minorVer,
                                                    const std::string& interface,
-                                                   const std::string& instance);
+                                                   const std::string& instance = "");
+    // Convenience method for the following formats:
+    // IFoo/default
     __attribute__((warn_unused_result)) bool setTo(const std::string& interface,
                                                    const std::string& instance);
 
@@ -90,6 +102,9 @@
    private:
     FQName mFqName;
     std::string mInstance;
+
+    // helper to setTo() to determine that the FqInstance is actually valid.
+    bool isValid() const;
 };
 
 }  // namespace android