AAPT2: Process <java-symbols> and private symbol package

Need to introduce the idea of multiple levels of visibility to support <java-symbol>.

Public, Private, Undefined.

Public means it is accessible from outside and requires an ID assigned.
Private means that we explicitly want this to be a symbol (show up in R.java), but not visible
to other packages. No ID required.

Undefined is any normal resource. When --private-symbols is specified in the link phase,
these resources will not show up in R.java.

Change-Id: Icba89221e08e685dee7683786aa7112baf28c856
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 84f4385..d864f66 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -124,17 +124,18 @@
             }
 
             for (const ResourceEntry* entry : sortedEntries) {
-                ResourceId id = {
-                        package->id ? package->id.value() : uint8_t(0),
-                        type->id ? type->id.value() : uint8_t(0),
-                        entry->id ? entry->id.value() : uint16_t(0)
-                };
+                ResourceId id(package->id ? package->id.value() : uint8_t(0),
+                              type->id ? type->id.value() : uint8_t(0),
+                              entry->id ? entry->id.value() : uint16_t(0));
+                ResourceName name(package->name, type->type, entry->name);
 
-                ResourceName name = { package->name, type->type, entry->name };
                 std::cout << "    spec resource " << id << " " << name;
-                if (entry->publicStatus.isPublic) {
-                    std::cout << " PUBLIC";
+                switch (entry->symbolStatus.state) {
+                case SymbolState::kPublic: std::cout << " PUBLIC"; break;
+                case SymbolState::kPrivate: std::cout << " _PRIVATE_"; break;
+                default: break;
                 }
+
                 std::cout << std::endl;
 
                 PrintVisitor visitor;
diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/JavaClassGenerator.cpp
index 84a4125..cdf1b6a 100644
--- a/tools/aapt2/JavaClassGenerator.cpp
+++ b/tools/aapt2/JavaClassGenerator.cpp
@@ -77,6 +77,18 @@
     return output;
 }
 
+bool JavaClassGenerator::skipSymbol(SymbolState state) {
+    switch (mOptions.types) {
+    case JavaClassGeneratorOptions::SymbolTypes::kAll:
+        return false;
+    case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate:
+        return state == SymbolState::kUndefined;
+    case JavaClassGeneratorOptions::SymbolTypes::kPublic:
+        return state != SymbolState::kPublic;
+    }
+    return true;
+}
+
 void JavaClassGenerator::generateStyleable(const StringPiece16& packageNameToGenerate,
                                            const std::u16string& entryName,
                                            const Styleable* styleable,
@@ -121,7 +133,7 @@
         // We may reference IDs from other packages, so prefix the entry name with
         // the package.
         const ResourceNameRef& itemName = sortedAttributes[i].second;
-        if (packageNameToGenerate != itemName.package) {
+        if (!itemName.package.empty() && packageNameToGenerate != itemName.package) {
             *out << "_" << transform(itemName.package);
         }
         *out << "_" << transform(itemName.entry) << " = " << i << ";\n";
@@ -137,7 +149,11 @@
     std::u16string unmangledPackage;
     std::u16string unmangledName;
     for (const auto& entry : type->entries) {
-        ResourceId id = { package->id.value(), type->id.value(), entry->id.value() };
+        if (skipSymbol(entry->symbolStatus.state)) {
+            continue;
+        }
+
+        ResourceId id(package->id.value(), type->id.value(), entry->id.value());
         assert(id.isValid());
 
         unmangledName = entry->name;
@@ -157,7 +173,7 @@
         }
 
         if (!isValidSymbol(unmangledName)) {
-            ResourceNameRef resourceName = { packageNameToGenerate, type->type, unmangledName };
+            ResourceNameRef resourceName(packageNameToGenerate, type->type, unmangledName);
             std::stringstream err;
             err << "invalid symbol name '" << resourceName << "'";
             mError = err.str();
@@ -177,13 +193,24 @@
 }
 
 bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, std::ostream* out) {
-    generateHeader(packageNameToGenerate, out);
+    return generate(packageNameToGenerate, packageNameToGenerate, out);
+}
+
+bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate,
+                                  const StringPiece16& outPackageName, std::ostream* out) {
+    generateHeader(outPackageName, out);
 
     *out << "public final class R {\n";
 
     for (const auto& package : mTable->packages) {
         for (const auto& type : package->types) {
-            *out << "    public static final class " << type->type << " {\n";
+            StringPiece16 typeStr;
+            if (type->type == ResourceType::kAttrPrivate) {
+                typeStr = toString(ResourceType::kAttr);
+            } else {
+                typeStr = toString(type->type);
+            }
+            *out << "    public static final class " << typeStr << " {\n";
             if (!generateType(packageNameToGenerate, package.get(), type.get(), out)) {
                 return false;
             }
@@ -196,4 +223,6 @@
     return true;
 }
 
+
+
 } // namespace aapt
diff --git a/tools/aapt2/JavaClassGenerator.h b/tools/aapt2/JavaClassGenerator.h
index 682bacf..e53a765 100644
--- a/tools/aapt2/JavaClassGenerator.h
+++ b/tools/aapt2/JavaClassGenerator.h
@@ -33,6 +33,17 @@
      * on resource entries. Default is true.
      */
     bool useFinal = true;
+
+    enum class SymbolTypes {
+        kAll,
+        kPublicPrivate,
+        kPublic,
+    };
+
+    /*
+     *
+     */
+    SymbolTypes types = SymbolTypes::kAll;
 };
 
 /*
@@ -49,7 +60,11 @@
      * We need to generate these symbols in a separate file.
      * Returns true on success.
      */
-    bool generate(const StringPiece16& package, std::ostream* out);
+    bool generate(const StringPiece16& packageNameToGenerate, std::ostream* out);
+
+    bool generate(const StringPiece16& packageNameToGenerate,
+                  const StringPiece16& outputPackageName,
+                  std::ostream* out);
 
     const std::string& getError() const;
 
@@ -64,6 +79,8 @@
                            const Styleable* styleable,
                            std::ostream* out);
 
+    bool skipSymbol(SymbolState state);
+
     ResourceTable* mTable;
     JavaClassGeneratorOptions mOptions;
     std::string mError;
diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp
index 48fcf8c..becf99b 100644
--- a/tools/aapt2/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/JavaClassGenerator_test.cpp
@@ -65,6 +65,87 @@
               output.find("public static final int hey_dude_cool_attr = 0;"));
 }
 
+TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"android", 0x01)
+            .addSimple(u"@android:id/one", ResourceId(0x01020000))
+            .addSimple(u"@android:id/com.foo$two", ResourceId(0x01020001))
+            .build();
+
+    JavaClassGenerator generator(table.get(), {});
+    std::stringstream out;
+    ASSERT_TRUE(generator.generate(u"android", u"com.android.internal", &out));
+
+    std::string output = out.str();
+    EXPECT_NE(std::string::npos, output.find("package com.android.internal;"));
+    EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
+    EXPECT_EQ(std::string::npos, output.find("two"));
+    EXPECT_EQ(std::string::npos, output.find("com_foo$two"));
+}
+
+TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"android", 0x01)
+            .addSimple(u"@android:^attr-private/one", ResourceId(0x01010000))
+            .build();
+
+    JavaClassGenerator generator(table.get(), {});
+    std::stringstream out;
+    ASSERT_TRUE(generator.generate(u"android", &out));
+
+    std::string output = out.str();
+    EXPECT_NE(std::string::npos, output.find("public static final class attr"));
+    EXPECT_EQ(std::string::npos, output.find("public static final class ^attr-private"));
+}
+
+TEST(JavaClassGeneratorTest, OnlyWritePublicResources) {
+    StdErrDiagnostics diag;
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"android", 0x01)
+            .addSimple(u"@android:id/one", ResourceId(0x01020000))
+            .addSimple(u"@android:id/two", ResourceId(0x01020001))
+            .addSimple(u"@android:id/three", ResourceId(0x01020002))
+            .build();
+    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:id/one"), {}, {},
+                                      SymbolState::kPublic, &diag));
+    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:id/two"), {}, {},
+                                      SymbolState::kPrivate, &diag));
+
+    JavaClassGeneratorOptions options;
+    options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
+    {
+        JavaClassGenerator generator(table.get(), options);
+        std::stringstream out;
+        ASSERT_TRUE(generator.generate(u"android", &out));
+        std::string output = out.str();
+        EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
+        EXPECT_EQ(std::string::npos, output.find("two"));
+        EXPECT_EQ(std::string::npos, output.find("three"));
+    }
+
+    options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
+    {
+        JavaClassGenerator generator(table.get(), options);
+        std::stringstream out;
+        ASSERT_TRUE(generator.generate(u"android", &out));
+        std::string output = out.str();
+        EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
+        EXPECT_NE(std::string::npos, output.find("public static final int two = 0x01020001;"));
+        EXPECT_EQ(std::string::npos, output.find("three"));
+    }
+
+    options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
+    {
+        JavaClassGenerator generator(table.get(), options);
+        std::stringstream out;
+        ASSERT_TRUE(generator.generate(u"android", &out));
+        std::string output = out.str();
+        EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
+        EXPECT_NE(std::string::npos, output.find("public static final int two = 0x01020001;"));
+        EXPECT_NE(std::string::npos, output.find("public static final int three = 0x01020002;"));
+    }
+}
+
 /*
  * TODO(adamlesinski): Re-enable this once we get merging working again.
  * TEST(JavaClassGeneratorTest, EmitPackageMangledSymbols) {
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 63629f0..bfef9d0 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -18,10 +18,11 @@
 #include "ResourceTable.h"
 #include "ResourceUtils.h"
 #include "ResourceValues.h"
-#include "util/Util.h"
 #include "ValueVisitor.h"
 #include "XmlPullParser.h"
 
+#include "util/Util.h"
+
 #include <sstream>
 
 namespace aapt {
@@ -184,7 +185,7 @@
     ResourceName name;
     Source source;
     ResourceId id;
-    bool markPublic = false;
+    SymbolState symbolState = SymbolState::kUndefined;
     std::unique_ptr<Value> value;
     std::list<ParsedResource> childResources;
 };
@@ -192,8 +193,10 @@
 // Recursively adds resources to the ResourceTable.
 static bool addResourcesToTable(ResourceTable* table, const ConfigDescription& config,
                                 IDiagnostics* diag, ParsedResource* res) {
-    if (res->markPublic && !table->markPublic(res->name, res->id, res->source, diag)) {
-        return false;
+    if (res->symbolState != SymbolState::kUndefined) {
+        if (!table->setSymbolState(res->name, res->id, res->source, res->symbolState, diag)) {
+            return false;
+        }
     }
 
     if (!res->value) {
@@ -318,6 +321,8 @@
             result = parseAttr(parser, &parsedResource);
         } else if (elementName == u"public") {
             result = parsePublic(parser, &parsedResource);
+        } else if (elementName == u"java-symbol" || elementName == u"symbol") {
+            result = parseSymbol(parser, &parsedResource);
         } else {
             mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber()))
                         << "unknown resource type '" << elementName << "'");
@@ -506,7 +511,29 @@
         outResource->value = util::make_unique<Id>();
     }
 
-    outResource->markPublic = true;
+    outResource->symbolState = SymbolState::kPublic;
+    return true;
+}
+
+bool ResourceParser::parseSymbol(XmlPullParser* parser, ParsedResource* outResource) {
+    const Source source = mSource.withLine(parser->getLineNumber());
+
+    Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
+    if (!maybeType) {
+        mDiag->error(DiagMessage(source) << "<" << parser->getElementName() << "> must have a "
+                     "'type' attribute");
+        return false;
+    }
+
+    const ResourceType* parsedType = parseResourceType(maybeType.value());
+    if (!parsedType) {
+        mDiag->error(DiagMessage(source) << "invalid resource type '" << maybeType.value()
+                     << "' in <" << parser->getElementName() << ">");
+        return false;
+    }
+
+    outResource->name.type = *parsedType;
+    outResource->symbolState = SymbolState::kPrivate;
     return true;
 }
 
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 5ccd47f..34c68d7 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -77,6 +77,7 @@
     bool parseColor(XmlPullParser* parser, ParsedResource* outResource);
     bool parsePrimitive(XmlPullParser* parser, ParsedResource* outResource);
     bool parsePublic(XmlPullParser* parser, ParsedResource* outResource);
+    bool parseSymbol(XmlPullParser* parser, ParsedResource* outResource);
     bool parseAttr(XmlPullParser* parser, ParsedResource* outResource);
     bool parseAttrImpl(XmlPullParser* parser, ParsedResource* outResource, bool weak);
     Maybe<Attribute::Symbol> parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag);
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index e32fb5e..84674e8 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -220,6 +220,16 @@
                            kValidNameMangledChars, diag);
 }
 
+bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
+                                            const ResourceId id,
+                                            const ConfigDescription& config,
+                                            const Source& source,
+                                            std::unique_ptr<Value> value,
+                                            IDiagnostics* diag) {
+    return addResourceImpl(name, id, config, source, std::move(value),
+                           kValidNameMangledChars, diag);
+}
+
 bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
                                     const ConfigDescription& config, const Source& source,
                                     std::unique_ptr<Value> value, const char16_t* validChars,
@@ -305,19 +315,25 @@
     return true;
 }
 
-bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId resId,
-                               const Source& source, IDiagnostics* diag) {
-    return markPublicImpl(name, resId, source, kValidNameChars, diag);
+bool ResourceTable::setSymbolState(const ResourceNameRef& name, const ResourceId resId,
+                                   const Source& source, SymbolState state, IDiagnostics* diag) {
+    return setSymbolStateImpl(name, resId, source, state, kValidNameChars, diag);
 }
 
-bool ResourceTable::markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId,
-                                           const Source& source, IDiagnostics* diag) {
-    return markPublicImpl(name, resId, source, kValidNameMangledChars, diag);
+bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId resId,
+                                               const Source& source, SymbolState state,
+                                               IDiagnostics* diag) {
+    return setSymbolStateImpl(name, resId, source, state, kValidNameMangledChars, diag);
 }
 
-bool ResourceTable::markPublicImpl(const ResourceNameRef& name, const ResourceId resId,
-                                   const Source& source, const char16_t* validChars,
-                                   IDiagnostics* diag) {
+bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const ResourceId resId,
+                                       const Source& source, SymbolState state,
+                                       const char16_t* validChars, IDiagnostics* diag) {
+    if (state == SymbolState::kUndefined) {
+        // Nothing to do.
+        return true;
+    }
+
     auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
     if (badCharIter != name.entry.end()) {
         diag->error(DiagMessage(source)
@@ -371,9 +387,18 @@
         return false;
     }
 
-    type->publicStatus.isPublic = true;
-    entry->publicStatus.isPublic = true;
-    entry->publicStatus.source = source;
+    // Only mark the type state as public, it doesn't care about being private.
+    if (state == SymbolState::kPublic) {
+        type->symbolStatus.state = SymbolState::kPublic;
+    }
+
+    // Downgrading to a private symbol from a public one is not allowed.
+    if (entry->symbolStatus.state != SymbolState::kPublic) {
+        if (entry->symbolStatus.state != state) {
+            entry->symbolStatus.state = state;
+            entry->symbolStatus.source = source;
+        }
+    }
 
     if (resId.isValid()) {
         package->id = resId.packageId();
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 60fed2f..be90936 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -31,11 +31,17 @@
 
 namespace aapt {
 
+enum class SymbolState {
+    kUndefined,
+    kPublic,
+    kPrivate
+};
+
 /**
  * The Public status of a resource.
  */
-struct Public {
-    bool isPublic = false;
+struct Symbol {
+    SymbolState state = SymbolState::kUndefined;
     Source source;
     std::u16string comment;
 };
@@ -71,7 +77,7 @@
      * Whether this resource is public (and must maintain the same
      * entry ID across builds).
      */
-    Public publicStatus;
+    Symbol symbolStatus;
 
     /**
      * The resource's values for each configuration.
@@ -100,7 +106,7 @@
      * Whether this type is public (and must maintain the same
      * type ID across builds).
      */
-    Public publicStatus;
+    Symbol symbolStatus;
 
     /**
      * List of resources for this type.
@@ -171,10 +177,15 @@
                                  const Source& source, std::unique_ptr<Value> value,
                                  IDiagnostics* diag);
 
-    bool markPublic(const ResourceNameRef& name, const ResourceId resId, const Source& source,
-                    IDiagnostics* diag);
-    bool markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId,
-                                const Source& source, IDiagnostics* diag);
+    bool addResourceAllowMangled(const ResourceNameRef& name, const ResourceId id,
+                                 const ConfigDescription& config,
+                                 const Source& source, std::unique_ptr<Value> value,
+                                 IDiagnostics* diag);
+
+    bool setSymbolState(const ResourceNameRef& name, const ResourceId resId, const Source& source,
+                        SymbolState state, IDiagnostics* diag);
+    bool setSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId resId,
+                                    const Source& source, SymbolState state, IDiagnostics* diag);
     struct SearchResult {
         ResourceTablePackage* package;
         ResourceTableType* type;
@@ -217,8 +228,9 @@
                          const ConfigDescription& config, const Source& source,
                          std::unique_ptr<Value> value, const char16_t* validChars,
                          IDiagnostics* diag);
-    bool markPublicImpl(const ResourceNameRef& name, const ResourceId resId,
-                        const Source& source, const char16_t* validChars, IDiagnostics* diag);
+    bool setSymbolStateImpl(const ResourceNameRef& name, const ResourceId resId,
+                            const Source& source, SymbolState state, const char16_t* validChars,
+                            IDiagnostics* diag);
 };
 
 } // namespace aapt
diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
index af0afef..38cda09 100644
--- a/tools/aapt2/flatten/ResourceTypeExtensions.h
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -134,7 +134,14 @@
 
 struct Public_entry {
     uint16_t entryId;
-    uint16_t res0;
+
+    enum : uint16_t {
+        kUndefined = 0,
+        kPublic = 1,
+        kPrivate = 2,
+    };
+
+    uint16_t state;
     android::ResStringPool_ref key;
     android::ResStringPool_ref source;
     uint32_t sourceLine;
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 75cbd5d..095552a 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -300,7 +300,7 @@
 
         T* result = buffer->nextBlock<T>();
         ResTable_entry* outEntry = (ResTable_entry*)(result);
-        if (entry->entry->publicStatus.isPublic) {
+        if (entry->entry->symbolStatus.state == SymbolState::kPublic) {
             outEntry->flags |= ResTable_entry::FLAG_PUBLIC;
         }
 
@@ -455,7 +455,7 @@
 
             // Populate the config masks for this entry.
 
-            if (entry->publicStatus.isPublic) {
+            if (entry->symbolStatus.state == SymbolState::kPublic) {
                 configMasks[entry->id.value()] |=
                         util::hostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
             }
@@ -480,17 +480,31 @@
         publicHeader->typeId = type->id.value();
 
         for (ResourceEntry* entry : *sortedEntries) {
-            if (entry->publicStatus.isPublic) {
+            if (entry->symbolStatus.state != SymbolState::kUndefined) {
                 // Write the public status of this entry.
                 Public_entry* publicEntry = publicWriter.nextBlock<Public_entry>();
                 publicEntry->entryId = util::hostToDevice32(entry->id.value());
                 publicEntry->key.index = util::hostToDevice32(mKeyPool.makeRef(
                         entry->name).getIndex());
                 publicEntry->source.index = util::hostToDevice32(mSourcePool->makeRef(
-                        util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex());
-                if (entry->publicStatus.source.line) {
+                        util::utf8ToUtf16(entry->symbolStatus.source.path)).getIndex());
+                if (entry->symbolStatus.source.line) {
                     publicEntry->sourceLine = util::hostToDevice32(
-                            entry->publicStatus.source.line.value());
+                            entry->symbolStatus.source.line.value());
+                }
+
+                switch (entry->symbolStatus.state) {
+                case SymbolState::kPrivate:
+                    publicEntry->state = Public_entry::kPrivate;
+                    break;
+
+                case SymbolState::kPublic:
+                    publicEntry->state = Public_entry::kPublic;
+                    break;
+
+                default:
+                    assert(false && "should not serialize any other state");
+                    break;
                 }
 
                 // Don't hostToDevice until the last step.
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index fa321a0..b84f2e0 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -52,7 +52,7 @@
     bool staticLib = false;
     bool verbose = false;
     bool outputToDirectory = false;
-    Maybe<std::string> privateSymbols;
+    Maybe<std::u16string> privateSymbols;
 };
 
 struct LinkContext : public IAaptContext {
@@ -328,13 +328,14 @@
         return true;
     }
 
-    bool writeJavaFile(ResourceTable* table, const StringPiece16& package) {
+    bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
+                       const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
         if (!mOptions.generateJavaClassPath) {
             return true;
         }
 
         std::string outPath = mOptions.generateJavaClassPath.value();
-        file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(package)));
+        file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage)));
         file::mkdirs(outPath);
         file::appendPath(&outPath, "R.java");
 
@@ -344,13 +345,8 @@
             return false;
         }
 
-        JavaClassGeneratorOptions javaOptions;
-        if (mOptions.staticLib) {
-            javaOptions.useFinal = false;
-        }
-
         JavaClassGenerator generator(table, javaOptions);
-        if (!generator.generate(mContext.getCompilationPackage(), &fout)) {
+        if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
             mContext.getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
             return false;
         }
@@ -652,8 +648,34 @@
         }
 
         if (mOptions.generateJavaClassPath) {
-            if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage())) {
-                return 1;
+            JavaClassGeneratorOptions options;
+            if (mOptions.staticLib) {
+                options.useFinal = false;
+            }
+
+            if (mOptions.privateSymbols) {
+                // If we defined a private symbols package, we only emit Public symbols
+                // to the original package, and private and public symbols to the private package.
+
+                options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
+                if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage(),
+                                   mContext.getCompilationPackage(), options)) {
+                    return 1;
+                }
+
+                options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
+                if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage(),
+                                   mOptions.privateSymbols.value(), options)) {
+                    return 1;
+                }
+
+            } else {
+                // Emit Everything.
+
+                if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage(),
+                                   mContext.getCompilationPackage(), options)) {
+                    return 1;
+                }
             }
         }
 
@@ -680,6 +702,7 @@
 
 int link(const std::vector<StringPiece>& args) {
     LinkOptions options;
+    Maybe<std::string> privateSymbolsPackage;
     Flags flags = Flags()
             .requiredFlag("-o", "Output path", &options.outputPath)
             .requiredFlag("--manifest", "Path to the Android manifest to build",
@@ -698,13 +721,17 @@
             .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
             .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
                           "private symbols. If not specified, public and private symbols will "
-                          "use the application's package name", &options.privateSymbols)
+                          "use the application's package name", &privateSymbolsPackage)
             .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
 
     if (!flags.parse("aapt2 link", args, &std::cerr)) {
         return 1;
     }
 
+    if (privateSymbolsPackage) {
+        options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
+    }
+
     LinkCommand cmd = { options };
     return cmd.run(flags.getArgs());
 }
diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp
index db20bcb..5a2f5f07 100644
--- a/tools/aapt2/link/PrivateAttributeMover.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover.cpp
@@ -61,7 +61,7 @@
             continue;
         }
 
-        if (!type->publicStatus.isPublic) {
+        if (type->symbolStatus.state != SymbolState::kPublic) {
             // No public attributes, so we can safely leave these private attributes where they are.
             return true;
         }
@@ -71,7 +71,7 @@
 
         moveIf(type->entries, std::back_inserter(privAttrType->entries),
                [](const std::unique_ptr<ResourceEntry>& entry) -> bool {
-                   return !entry->publicStatus.isPublic;
+                   return entry->symbolStatus.state != SymbolState::kPublic;
                });
         break;
     }
diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp
index 8173c30..a2f8d19 100644
--- a/tools/aapt2/link/PrivateAttributeMover_test.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp
@@ -31,10 +31,12 @@
             .addSimple(u"@android:attr/publicB")
             .addSimple(u"@android:attr/privateB")
             .build();
-    ASSERT_TRUE(table->markPublic(test::parseNameOrDie(u"@android:attr/publicA"),
-                                  ResourceId(0x01010000), {}, context->getDiagnostics()));
-    ASSERT_TRUE(table->markPublic(test::parseNameOrDie(u"@android:attr/publicB"),
-                                      ResourceId(0x01010002), {}, context->getDiagnostics()));
+    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:attr/publicA"),
+                                      ResourceId(0x01010000), {}, SymbolState::kPublic,
+                                      context->getDiagnostics()));
+    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:attr/publicB"),
+                                      ResourceId(0x01010002), {}, SymbolState::kPublic,
+                                      context->getDiagnostics()));
 
     PrivateAttributeMover mover;
     ASSERT_TRUE(mover.consume(context.get(), table.get()));
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index c0356e5..b4fb996 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -249,10 +249,13 @@
     for (auto& package : table->packages) {
         for (auto& type : package->types) {
             for (auto& entry : type->entries) {
-                // A public entry with no values will not be encoded properly.
-                if (entry->publicStatus.isPublic && entry->values.empty()) {
-                    context->getDiagnostics()->error(DiagMessage(entry->publicStatus.source)
-                                                     << "No value for public resource");
+                // Symbol state information may be lost if there is no value for the resource.
+                if (entry->symbolStatus.state != SymbolState::kUndefined && entry->values.empty()) {
+                    context->getDiagnostics()->error(
+                            DiagMessage(entry->symbolStatus.source)
+                            << "no definition for declared symbol '"
+                            << ResourceNameRef(package->name, type->type, entry->name)
+                            << "'");
                     error = true;
                 }
 
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 0d63b97..db52546 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -69,8 +69,8 @@
 
     for (auto& srcType : srcPackage->types) {
         ResourceTableType* dstType = mMasterPackage->findOrCreateType(srcType->type);
-        if (srcType->publicStatus.isPublic) {
-            if (dstType->publicStatus.isPublic && dstType->id && srcType->id
+        if (srcType->symbolStatus.state == SymbolState::kPublic) {
+            if (dstType->symbolStatus.state == SymbolState::kPublic && dstType->id && srcType->id
                     && dstType->id.value() == srcType->id.value()) {
                 // Both types are public and have different IDs.
                 mContext->getDiagnostics()->error(DiagMessage(src)
@@ -81,7 +81,7 @@
                 continue;
             }
 
-            dstType->publicStatus = std::move(srcType->publicStatus);
+            dstType->symbolStatus = std::move(srcType->symbolStatus);
             dstType->id = srcType->id;
         }
 
@@ -94,20 +94,29 @@
                 dstEntry = dstType->findOrCreateEntry(srcEntry->name);
             }
 
-            if (srcEntry->publicStatus.isPublic) {
-                if (dstEntry->publicStatus.isPublic && dstEntry->id && srcEntry->id
-                        && dstEntry->id.value() != srcEntry->id.value()) {
-                    // Both entries are public and have different IDs.
-                    mContext->getDiagnostics()->error(DiagMessage(src)
-                                                      << "can not merge entry '"
-                                                      << srcEntry->name
-                                                      << "': conflicting public IDs");
-                    error = true;
-                    continue;
+            if (srcEntry->symbolStatus.state != SymbolState::kUndefined) {
+                if (srcEntry->symbolStatus.state == SymbolState::kPublic) {
+                    if (dstEntry->symbolStatus.state == SymbolState::kPublic &&
+                            dstEntry->id && srcEntry->id &&
+                            dstEntry->id.value() != srcEntry->id.value()) {
+                        // Both entries are public and have different IDs.
+                        mContext->getDiagnostics()->error(DiagMessage(src)
+                                                          << "can not merge entry '"
+                                                          << srcEntry->name
+                                                          << "': conflicting public IDs");
+                        error = true;
+                        continue;
+                    }
+
+                    if (srcEntry->id) {
+                        dstEntry->id = srcEntry->id;
+                    }
                 }
 
-                dstEntry->publicStatus = std::move(srcEntry->publicStatus);
-                dstEntry->id = srcEntry->id;
+                if (dstEntry->symbolStatus.state != SymbolState::kPublic &&
+                        dstEntry->symbolStatus.state != srcEntry->symbolStatus.state) {
+                    dstEntry->symbolStatus = std::move(srcEntry->symbolStatus);
+                }
             }
 
             for (ResourceConfigValue& srcValue : srcEntry->values) {
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 0d8d8b5..0383c44 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -87,8 +87,9 @@
 
     ResourceTableBuilder& addValue(const StringPiece16& name, ResourceId id,
                                    const ConfigDescription& config, std::unique_ptr<Value> value) {
-        bool result = mTable->addResource(parseNameOrDie(name), id, config, {}, std::move(value),
-                                          &mDiagnostics);
+        ResourceName resName = parseNameOrDie(name);
+        bool result = mTable->addResourceAllowMangled(resName, id, config, {}, std::move(value),
+                                                      &mDiagnostics);
         assert(result);
         return *this;
     }
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index ac91865..c7a715e 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -429,7 +429,19 @@
             source.line = util::deviceToHost32(entry->sourceLine);
         }
 
-        if (!mTable->markPublicAllowMangled(name, resId, source, mContext->getDiagnostics())) {
+        SymbolState state = SymbolState::kUndefined;
+        switch (util::deviceToHost16(entry->state)) {
+        case Public_entry::kPrivate:
+            state = SymbolState::kPrivate;
+            break;
+
+        case Public_entry::kPublic:
+            state = SymbolState::kPublic;
+            break;
+        }
+
+        if (!mTable->setSymbolStateAllowMangled(name, resId, source, state,
+                                                mContext->getDiagnostics())) {
             return false;
         }
 
@@ -564,8 +576,9 @@
         }
 
         if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
-            if (!mTable->markPublicAllowMangled(name, resId, mSource.withLine(0),
-                                                mContext->getDiagnostics())) {
+            if (!mTable->setSymbolStateAllowMangled(name, resId, mSource.withLine(0),
+                                                    SymbolState::kPublic,
+                                                    mContext->getDiagnostics())) {
                 return false;
             }
         }