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/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..5a2f5f0 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) {