Final tweaks and fixes to enable aapt2 for Blaze.
Implemented processing databinding for compile passes

RELNOTES: None
PiperOrigin-RevId: 166215052
diff --git a/src/test/java/com/google/devtools/build/android/DependencyAndroidDataTest.java b/src/test/java/com/google/devtools/build/android/DependencyAndroidDataTest.java
index 2a83860..8fcbbc0 100644
--- a/src/test/java/com/google/devtools/build/android/DependencyAndroidDataTest.java
+++ b/src/test/java/com/google/devtools/build/android/DependencyAndroidDataTest.java
@@ -59,7 +59,7 @@
   @Test public void flagFullParse() throws Exception{
     Truth.assertThat(
             DependencyAndroidData.valueOf(
-                "res#otherres:assets#otherassets:AndroidManifest.xml:r.txt:symbols.bin:static.library.ap_",
+                "res#otherres:assets#otherassets:AndroidManifest.xml:r.txt:static.library.ap_:symbols.bin",
                 fileSystem))
         .isEqualTo(
             new DependencyAndroidData(
diff --git a/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java b/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java
index 5e16841..d975051 100644
--- a/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java
+++ b/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java
@@ -19,6 +19,7 @@
 import com.android.utils.StdLogger;
 import com.google.common.base.Stopwatch;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.devtools.build.android.AndroidResourceMerger.MergingException;
 import com.google.devtools.build.android.AndroidResourceProcessingAction.Options;
@@ -86,13 +87,29 @@
       final Path densityManifest = tmp.resolve("manifest-filtered/AndroidManifest.xml");
 
       final Path processedManifest = tmp.resolve("manifest-processed/AndroidManifest.xml");
+      final Path dummyManifest = tmp.resolve("manifest-aapt-dummy/AndroidManifest.xml");
       final Path databindingResourcesRoot =
           Files.createDirectories(tmp.resolve("android_data_binding_resources"));
+      final Path databindingMetaData =
+          Files.createDirectories(tmp.resolve("android_data_binding_metadata"));
       final Path compiledResources = Files.createDirectories(tmp.resolve("compiled"));
+      final Path staticLinkedOut = Files.createDirectories(tmp.resolve("static-linked"));
       final Path linkedOut = Files.createDirectories(tmp.resolve("linked"));
 
+      Path generatedSources = null;
+      if (options.srcJarOutput != null || options.rOutput != null || options.symbolsOut != null) {
+        generatedSources = tmp.resolve("generated_resources");
+      }
+
       logger.fine(String.format("Setup finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
 
+      List<DependencyAndroidData> data =
+          ImmutableSet.<DependencyAndroidData>builder()
+              .addAll(options.directData)
+              .addAll(options.transitiveData)
+              .build()
+              .asList();
+
       // Checks for merge conflicts.
       MergedAndroidData mergedAndroidData =
           AndroidResourceMerger.mergeData(
@@ -125,7 +142,8 @@
         CompiledResources compiled =
             options
                 .primaryData
-                .processDataBindings(options.dataBindingInfoOut, databindingResourcesRoot)
+                .processDataBindings(options.dataBindingInfoOut, options.packageForR,
+                    databindingResourcesRoot)
                 .compile(compiler, compiledResources)
                 .processManifest(
                     manifest ->
diff --git a/src/tools/android/java/com/google/devtools/build/android/AaptCommandBuilder.java b/src/tools/android/java/com/google/devtools/build/android/AaptCommandBuilder.java
index a0549c8..6e9c9a4 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AaptCommandBuilder.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AaptCommandBuilder.java
@@ -244,10 +244,12 @@
    * @throws IOException when the process cannot execute.
    */
   public String execute(String action) throws IOException {
-    StringBuilder processLog = new StringBuilder();
-    final Process process = new ProcessBuilder().command(build()).redirectErrorStream(true).start();
+    final StringBuilder processLog = new StringBuilder();
+    List<String> command = build();
+
+    final Process process = new ProcessBuilder().command(command).redirectErrorStream(true).start();
     processLog.append("Command: ");
-    Joiner.on(" ").appendTo(processLog, build());
+    Joiner.on("\n\t").appendTo(processLog, command);
     processLog.append("\n");
     final InputStreamReader stdout =
         new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8);
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java b/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java
index f48a796..1f9d500 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java
@@ -112,7 +112,7 @@
       }
       readEntriesSegment(consumers, in, currentFileSystem, header);
     } catch (IOException e) {
-      throw new DeserializationException(e);
+      throw new DeserializationException("Error deserializing " + inPath, e);
     } finally {
       logger.fine(
           String.format("Deserialized in merged in %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidManifestProcessor.java b/src/tools/android/java/com/google/devtools/build/android/AndroidManifestProcessor.java
index be6738f..cd9e6f8 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AndroidManifestProcessor.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidManifestProcessor.java
@@ -246,13 +246,29 @@
       Path manifest,
       Path processedManifest) {
 
-    ManifestMerger2.MergeType mergeType = ManifestMerger2.MergeType.APPLICATION;
-
-    String newManifestPackage = applicationId;
-
-    if (versionCode != -1 || versionName != null || newManifestPackage != null) {
+    if (versionCode != -1 || versionName != null || applicationId != null) {
       processManifest(
-          versionCode, versionName, manifest, processedManifest, mergeType, newManifestPackage);
+          versionCode, versionName, manifest, processedManifest, MergeType.APPLICATION,
+          applicationId);
+      return processedManifest;
+    }
+    return manifest;
+  }
+
+  /** Processes the manifest for a library and return the manifest Path. */
+  public Path processLibraryManifest(
+      String newManifestPackage,
+      Path manifest,
+      Path processedManifest) {
+
+    if (newManifestPackage != null) {
+      processManifest(
+          -1 /* versionCode */,
+          null /* versionName */,
+          manifest,
+          processedManifest,
+          MergeType.LIBRARY,
+          newManifestPackage);
       return processedManifest;
     }
     return manifest;
@@ -377,4 +393,17 @@
       throw new ManifestProcessingException(e);
     }
   }
+
+  public static Path writeDummyManifestForAapt(Path dummyManifest, String packageForR) {
+    try {
+      Files.createDirectories(dummyManifest.getParent());
+      return Files.write(dummyManifest, String.format(
+          "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+              + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\""
+              + " package=\"%s\">"
+              + "</manifest>", packageForR).getBytes(UTF_8));
+    } catch (IOException e) {
+      throw new ManifestProcessingException(e);
+    }
+  }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java
index 58ca71b..6c8b86b 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java
@@ -13,8 +13,6 @@
 // limitations under the License.
 package com.google.devtools.build.android;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
-
 import android.databinding.AndroidDataBinding;
 import android.databinding.cli.ProcessXmlOptions;
 import com.android.annotations.NonNull;
@@ -689,13 +687,8 @@
     }
   }
 
-  public void writeDummyManifestForAapt(Path dummyManifest, String packageForR) throws IOException {
-    Files.createDirectories(dummyManifest.getParent());
-    Files.write(dummyManifest, String.format(
-        "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
-            + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\""
-            + " package=\"%s\">"
-            + "</manifest>", packageForR).getBytes(UTF_8));
+  public static void writeDummyManifestForAapt(Path dummyManifest, String packageForR) {
+    AndroidManifestProcessor.writeDummyManifestForAapt(dummyManifest, packageForR);
   }
 
   /**
diff --git a/src/tools/android/java/com/google/devtools/build/android/CompileLibraryResourcesAction.java b/src/tools/android/java/com/google/devtools/build/android/CompileLibraryResourcesAction.java
index a6afad5..7c19745 100644
--- a/src/tools/android/java/com/google/devtools/build/android/CompileLibraryResourcesAction.java
+++ b/src/tools/android/java/com/google/devtools/build/android/CompileLibraryResourcesAction.java
@@ -14,14 +14,13 @@
 
 package com.google.devtools.build.android;
 
-import com.android.builder.core.VariantType;
 import com.android.repository.Revision;
 import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
 import com.google.devtools.build.android.Converters.ExistingPathConverter;
 import com.google.devtools.build.android.Converters.PathConverter;
 import com.google.devtools.build.android.Converters.RevisionConverter;
+import com.google.devtools.build.android.Converters.UnvalidatedAndroidDirectoriesConverter;
 import com.google.devtools.build.android.aapt2.ResourceCompiler;
 import com.google.devtools.common.options.Option;
 import com.google.devtools.common.options.OptionDocumentationCategory;
@@ -29,13 +28,9 @@
 import com.google.devtools.common.options.OptionsBase;
 import com.google.devtools.common.options.OptionsParser;
 import java.io.Closeable;
-import java.io.IOException;
 import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executors;
 import java.util.logging.Logger;
 
 /** Compiles resources using aapt2 and archives them to zip. */
@@ -46,14 +41,13 @@
     @Option(
       documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
       effectTags = {OptionEffectTag.UNKNOWN},
-      name = "resource",
-      defaultValue = "",
-      allowMultiple = true,
-      converter = ExistingPathConverter.class,
+      name = "resources",
+      defaultValue = "null",
+      converter = UnvalidatedAndroidDirectoriesConverter.class,
       category = "input",
       help = "The resources to compile with aapt2."
     )
-    public List<Path> resources;
+    public UnvalidatedAndroidDirectories resources;
 
     @Option(
       documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
@@ -140,72 +134,25 @@
     Preconditions.checkNotNull(options.output);
     Preconditions.checkNotNull(options.aapt2);
 
-    try (ScopedTemporaryDirectory scopedTmp =
-        new ScopedTemporaryDirectory("android_resources_tmp")) {
+    final ListeningExecutorService defaultService = ExecutorServiceCloser.createDefaultService();
+    try (Closeable serviceCloser = ExecutorServiceCloser.createWith(defaultService);
+        ScopedTemporaryDirectory scopedTmp =
+            new ScopedTemporaryDirectory("android_resources_tmp")) {
       final Path tmp = scopedTmp.getPath();
       final Path databindingResourcesRoot =
           Files.createDirectories(tmp.resolve("android_data_binding_resources"));
-      final Path databindingMetaData =
-          Files.createDirectories(tmp.resolve("android_data_binding_metadata"));
       final Path compiledResources = Files.createDirectories(tmp.resolve("compiled"));
-      // The reported availableProcessors may be higher than the actual resources
-      // (on a shared system). On the other hand, a lot of the work is I/O, so it's not completely
-      // CPU bound. As a compromise, divide by 2 the reported availableProcessors.
-      int numThreads = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
-      final ListeningExecutorService executorService =
-          MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(numThreads));
-      try (final Closeable closeable = ExecutorServiceCloser.createWith(executorService)) {
-        final ResourceCompiler compiler =
-            ResourceCompiler.create(
-                executorService, compiledResources, options.aapt2, options.buildToolsVersion);
-        for (final Path resource :
-            maybeProcessDataBindings(
-                databindingResourcesRoot,
-                databindingMetaData,
-                options.dataBindingInfoOut,
-                options.manifest,
-                options.packagePath,
-                options.resources)) {
-          compiler.queueDirectoryForCompilation(resource);
-        }
-        AndroidResourceOutputs.archiveCompiledResources(
-            options.output,
-            databindingResourcesRoot,
-            compiledResources,
-            compiler.getCompiledArtifacts());
-      }
+
+      final ResourceCompiler compiler =
+          ResourceCompiler.create(
+              defaultService, compiledResources, options.aapt2, options.buildToolsVersion);
+      options
+          .resources
+          .toData(options.manifest)
+          .processDataBindings(
+              options.dataBindingInfoOut, options.packagePath, databindingResourcesRoot)
+          .compile(compiler, compiledResources)
+          .copyResourcesZipTo(options.output);
     }
   }
-
-  private static List<Path> maybeProcessDataBindings(
-      Path resourceRoot,
-      Path databindingMetaData,
-      Path dataBindingInfoOut,
-      Path manifest,
-      String packagePath,
-      List<Path> resources)
-      throws IOException {
-    if (dataBindingInfoOut == null) {
-      return resources;
-    }
-
-    Preconditions.checkNotNull(manifest);
-    Preconditions.checkNotNull(packagePath);
-
-    List<Path> processed = new ArrayList<>();
-    for (Path resource : resources) {
-      processed.add(
-          AndroidResourceProcessor.processDataBindings(
-              resourceRoot,
-              resource,
-              databindingMetaData,
-              VariantType.LIBRARY,
-              packagePath,
-              manifest,
-              false));
-    }
-
-    AndroidResourceOutputs.archiveDirectory(databindingMetaData, dataBindingInfoOut);
-    return processed;
-  }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java b/src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java
index f7ed888..72f0ab5 100644
--- a/src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java
+++ b/src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java
@@ -38,7 +38,7 @@
   private static final Pattern VALID_REGEX = Pattern.compile(".*:.*:.+:.+(:.*){0,2}");
 
   public static final String EXPECTED_FORMAT =
-      "resources[#resources]:assets[#assets]:manifest:r.txt:symbols.bin:static.library.ap_";
+      "resources[#resources]:assets[#assets]:manifest:r.txt:static.library.ap_:symbols.bin";
 
   public static DependencyAndroidData valueOf(String text) {
     return valueOf(text, FileSystems.getDefault());
@@ -55,15 +55,24 @@
     Path rTxt = exists(fileSystem.getPath(parts[3]));
     ImmutableList<Path> assetDirs =
         parts[1].length() == 0 ? ImmutableList.<Path>of() : splitPaths(parts[1], fileSystem);
+    StaticLibrary staticLibrary = null;
+    Path symbolsBin = null;
+
+    if (parts.length == 6) { // contains symbols bin and static library
+      staticLibrary = StaticLibrary.from(exists(fileSystem.getPath(parts[4])), rTxt, assetDirs);
+      symbolsBin = exists(fileSystem.getPath(parts[5]));
+    } else if (parts.length == 5) { // contains symbols bin
+      symbolsBin = exists(fileSystem.getPath(parts[4]));
+    }
+
+
     return new DependencyAndroidData(
         splitPaths(parts[0], fileSystem),
         assetDirs,
         exists(fileSystem.getPath(parts[2])),
         rTxt,
-        parts.length > 4 ? fileSystem.getPath(parts[4]) : null,
-        parts.length > 5
-            ? StaticLibrary.from(exists(fileSystem.getPath(parts[5])), rTxt, assetDirs)
-            : null);
+        symbolsBin,
+        staticLibrary);
   }
 
   private final Path manifest;
diff --git a/src/tools/android/java/com/google/devtools/build/android/DeserializationException.java b/src/tools/android/java/com/google/devtools/build/android/DeserializationException.java
index ba001b3..34bc707 100644
--- a/src/tools/android/java/com/google/devtools/build/android/DeserializationException.java
+++ b/src/tools/android/java/com/google/devtools/build/android/DeserializationException.java
@@ -35,6 +35,11 @@
     this.isLegacy = false;
   }
 
+  public DeserializationException(String message, IOException e) {
+    super(message, e);
+    this.isLegacy = false;
+  }
+
   public boolean isLegacy() {
     return isLegacy;
   }
diff --git a/src/tools/android/java/com/google/devtools/build/android/ExecutorServiceCloser.java b/src/tools/android/java/com/google/devtools/build/android/ExecutorServiceCloser.java
index cc99342..24d7739 100644
--- a/src/tools/android/java/com/google/devtools/build/android/ExecutorServiceCloser.java
+++ b/src/tools/android/java/com/google/devtools/build/android/ExecutorServiceCloser.java
@@ -32,7 +32,8 @@
   public void close() throws IOException {
     List<Runnable> unfinishedTasks = executorService.shutdownNow();
     if (!unfinishedTasks.isEmpty()) {
-      throw new IOException("Shutting down the executor with unfinished tasks:" + unfinishedTasks);
+      throw new IOException(
+          "Shutting down the executor with unfinished tasks:" + unfinishedTasks.size());
     }
   }
 
diff --git a/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidData.java b/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidData.java
index 7497654..736c9d4 100644
--- a/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidData.java
+++ b/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidData.java
@@ -13,7 +13,9 @@
 // limitations under the License.
 package com.google.devtools.build.android;
 
+import com.android.builder.core.VariantType;
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.android.aapt2.CompiledResources;
 import com.google.devtools.build.android.aapt2.ResourceCompiler;
@@ -21,10 +23,12 @@
 import java.nio.file.FileSystem;
 import java.nio.file.FileSystems;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.ExecutionException;
 import java.util.regex.Pattern;
+import javax.annotation.Nullable;
 
 /**
  * Android data that has yet to be merged and validated, the primary data for the Processor.
@@ -45,8 +49,7 @@
   @VisibleForTesting
   static UnvalidatedAndroidData valueOf(String text, FileSystem fileSystem) {
     if (!VALID_REGEX.matcher(text).find()) {
-      throw new IllegalArgumentException(
-          text + " is not in the format '" + EXPECTED_FORMAT + "'");
+      throw new IllegalArgumentException(text + " is not in the format '" + EXPECTED_FORMAT + "'");
     }
     String[] parts = text.split(":");
     return new UnvalidatedAndroidData(
@@ -111,16 +114,44 @@
         assetDirs);
   }
 
+  /* Processes the resources for databinding annotations if dataBindingOut is defined. */
   public UnvalidatedAndroidData processDataBindings(
-      Path dataBindingInfoOut, Path dataBindingWorkingDirectory) {
+      @Nullable Path dataBindingOut, String packagePath, Path dataBindingWorkingDirectory)
+      throws IOException {
 
-    return new UnvalidatedAndroidData(resourceDirs, assetDirs, manifest) {
+    if (dataBindingOut == null) {
+      return this;
+    }
+
+    Preconditions.checkNotNull(manifest);
+    Preconditions.checkNotNull(packagePath);
+
+    final List<Path> processed = new ArrayList<>();
+    final Path metadataWorkingDirectory = dataBindingWorkingDirectory.resolve("metadata");
+    final Path databindingResourceRoot = dataBindingWorkingDirectory.resolve("resources");
+    for (Path resource : resourceDirs) {
+      processed.add(
+          AndroidResourceProcessor.processDataBindings(
+              databindingResourceRoot,
+              resource,
+              metadataWorkingDirectory,
+              VariantType.LIBRARY,
+              packagePath,
+              manifest,
+              false));
+    }
+
+    AndroidResourceOutputs.archiveDirectory(
+        metadataWorkingDirectory, dataBindingOut);
+
+    return new UnvalidatedAndroidData(ImmutableList.copyOf(processed), assetDirs, manifest) {
       @Override
       protected CompiledResources archiveCompiledResources(
           List<Path> resources, Path workingDirectory, Path output) throws IOException {
+        // Update the archiving to ensure that the resources are correctly placed.
         return CompiledResources.from(
             AndroidResourceOutputs.archiveCompiledResources(
-                output, dataBindingWorkingDirectory, workingDirectory, resources),
+                output, databindingResourceRoot, workingDirectory, resources),
             manifest,
             assetDirs);
       }
diff --git a/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java b/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java
index 5723f6e..8be10be 100644
--- a/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java
+++ b/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java
@@ -107,4 +107,7 @@
         && Objects.equals(other.assetDirs, assetDirs);
   }
 
+  public UnvalidatedAndroidData toData(Path manifest) {
+    return new UnvalidatedAndroidData(resourceDirs, assetDirs, manifest);
+  }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/ValidateAndLinkResourcesAction.java b/src/tools/android/java/com/google/devtools/build/android/ValidateAndLinkResourcesAction.java
index bca1bc9..9886be2 100644
--- a/src/tools/android/java/com/google/devtools/build/android/ValidateAndLinkResourcesAction.java
+++ b/src/tools/android/java/com/google/devtools/build/android/ValidateAndLinkResourcesAction.java
@@ -14,6 +14,7 @@
 // Copyright 2017 The Bazel Authors. All rights reserved.
 package com.google.devtools.build.android;
 
+import com.google.common.base.Preconditions;
 import com.google.devtools.build.android.aapt2.Aapt2ConfigOptions;
 import com.google.devtools.build.android.aapt2.CompiledResources;
 import com.google.devtools.build.android.aapt2.ResourceLinker;
@@ -34,6 +35,34 @@
   /** Action configuration options. */
   public static class Options extends OptionsBase {
     @Option(
+      name = "compiled",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      defaultValue = "null",
+      converter = Converters.ExistingPathConverter.class,
+      category = "input",
+      help = "Compiled resources to link.",
+      deprecationWarning = "Use --resources."
+    )
+    // TODO(b/64570523): Still used by blaze. Will be removed as part of the command line cleanup.
+    @Deprecated
+    public Path compiled;
+
+    @Option(
+      name = "manifest",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      defaultValue = "null",
+      converter = Converters.ExistingPathConverter.class,
+      category = "input",
+      help = "Manifest for the library.",
+      deprecationWarning = "Use --resources."
+    )
+    // TODO(b/64570523): Still used by blaze. Will be removed as part of the command line cleanup.
+    @Deprecated
+    public Path manifest;
+
+    @Option(
       name = "resources",
       documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
       effectTags = {OptionEffectTag.UNKNOWN},
@@ -69,6 +98,16 @@
     public List<StaticLibrary> libraries;
 
     @Option(
+      name = "packageForR",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      defaultValue = "null",
+      category = "input",
+      help = "Package for the resources."
+    )
+    public String packageForR;
+
+    @Option(
       name = "staticLibraryOut",
       documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
       effectTags = {OptionEffectTag.UNKNOWN},
@@ -89,6 +128,17 @@
       help = "R.txt out."
     )
     public Path rTxtOut;
+
+    @Option(
+      name = "sourceJarOut",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      converter = Converters.PathConverter.class,
+      defaultValue = "null",
+      category = "output",
+      help = "Generated java classes from the resources."
+    )
+    public Path sourceJarOut;
   }
 
   public static void main(String[] args) throws Exception {
@@ -102,12 +152,28 @@
 
     try (ScopedTemporaryDirectory scopedTmp =
         new ScopedTemporaryDirectory("android_resources_tmp")) {
+      CompiledResources resources =
+          // TODO(b/64570523): Remove when the flags are standardized.
+          Optional.ofNullable(options.resources)
+              .orElseGet(
+                  () ->
+                      CompiledResources.from(
+                          Preconditions.checkNotNull(options.compiled),
+                          Preconditions.checkNotNull(options.manifest)))
+              // We need to make the manifest aapt safe (w.r.t., placeholders). For now, just stub
+              // it out.
+              .processManifest(
+                  manifest ->
+                      AndroidManifestProcessor.writeDummyManifestForAapt(
+                          scopedTmp.getPath().resolve("manifest-aapt-dummy/AndroidManifest.xml"),
+                          options.packageForR));
 
       ResourceLinker.create(aapt2Options.aapt2, scopedTmp.getPath())
           .dependencies(Optional.ofNullable(options.deprecatedLibraries).orElse(options.libraries))
           .buildVersion(aapt2Options.buildToolsVersion)
-          .linkStatically(options.resources)
+          .linkStatically(resources)
           .copyLibraryTo(options.staticLibraryOut)
+          .copySourceJarTo(options.sourceJarOut)
           .copyRTxtTo(options.rTxtOut);
     }
   }
diff --git a/src/tools/android/java/com/google/devtools/build/android/aapt2/Aapt2ConfigOptions.java b/src/tools/android/java/com/google/devtools/build/android/aapt2/Aapt2ConfigOptions.java
index ada69bc..0225041 100644
--- a/src/tools/android/java/com/google/devtools/build/android/aapt2/Aapt2ConfigOptions.java
+++ b/src/tools/android/java/com/google/devtools/build/android/aapt2/Aapt2ConfigOptions.java
@@ -18,11 +18,14 @@
 import com.android.repository.Revision;
 import com.google.devtools.build.android.Converters.ExistingPathConverter;
 import com.google.devtools.build.android.Converters.RevisionConverter;
+import com.google.devtools.common.options.Converters.CommaSeparatedOptionListConverter;
 import com.google.devtools.common.options.Option;
 import com.google.devtools.common.options.OptionDocumentationCategory;
 import com.google.devtools.common.options.OptionEffectTag;
 import com.google.devtools.common.options.OptionsBase;
+import com.google.devtools.common.options.TriState;
 import java.nio.file.Path;
+import java.util.List;
 
 /** Aaprt2 specific configuration options. */
 public class Aapt2ConfigOptions extends OptionsBase {
@@ -58,4 +61,100 @@
     help = "Path to the android jar for resource packaging and building apks."
   )
   public Path androidJar;
+
+  @Option(
+      name = "annotationJar",
+      defaultValue = "null",
+      converter = ExistingPathConverter.class,
+      category = "tool",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help = "Path to the android jar for resource packaging and building apks."
+  )
+  public Path annotationJar;
+
+
+  @Option(
+      name = "useAaptCruncher",
+      defaultValue = "auto",
+      category = "config",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help =
+          "Use the legacy aapt cruncher, defaults to true for non-LIBRARY packageTypes. "
+              + " LIBRARY packages do not benefit from the additional processing as the resources"
+              + " will need to be reprocessed during the generation of the final apk. See"
+              + " https://code.google.com/p/android/issues/detail?id=67525 for a discussion of the"
+              + " different png crunching methods."
+  )
+  public TriState useAaptCruncher;
+
+  @Option(
+      name = "uncompressedExtensions",
+      defaultValue = "",
+      converter = CommaSeparatedOptionListConverter.class,
+      category = "config",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help = "A list of file extensions not to compress."
+  )
+  public List<String> uncompressedExtensions;
+
+  @Option(
+      name = "assetsToIgnore",
+      defaultValue = "",
+      converter = CommaSeparatedOptionListConverter.class,
+      category = "config",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help = "A list of assets extensions to ignore."
+  )
+  public List<String> assetsToIgnore;
+
+  @Option(
+      name = "debug",
+      defaultValue = "false",
+      category = "config",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help = "Indicates if it is a debug build."
+  )
+  public boolean debug;
+
+  @Option(
+      name = "resourceConfigs",
+      defaultValue = "",
+      converter = CommaSeparatedOptionListConverter.class,
+      category = "config",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help = "A list of resource config filters to pass to aapt."
+  )
+  public List<String> resourceConfigs;
+
+  private static final String ANDROID_SPLIT_DOCUMENTATION_URL =
+      "https://developer.android.com/guide/topics/resources/providing-resources.html"
+          + "#QualifierRules";
+
+  @Option(
+      name = "split",
+      defaultValue = "required but ignored due to allowMultiple",
+      category = "config",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      allowMultiple = true,
+      help =
+          "An individual split configuration to pass to aapt."
+              + " Each split is a list of configuration filters separated by commas."
+              + " Configuration filters are lists of configuration qualifiers separated by dashes,"
+              + " as used in resource directory names and described on the Android developer site: "
+              + ANDROID_SPLIT_DOCUMENTATION_URL
+              + " For example, a split might be 'en-television,en-xxhdpi', containing English"
+              + " assets which either are for TV screens or are extra extra high resolution."
+              + " Multiple splits can be specified by passing this flag multiple times."
+              + " Each split flag will produce an additional output file, named by replacing the"
+              + " commas in the split specification with underscores, and appending the result to"
+              + " the output package name following an underscore."
+  )
+  public List<String> splits;
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/aapt2/CompiledResources.java b/src/tools/android/java/com/google/devtools/build/android/aapt2/CompiledResources.java
index 81d94fb..aa13dc3 100644
--- a/src/tools/android/java/com/google/devtools/build/android/aapt2/CompiledResources.java
+++ b/src/tools/android/java/com/google/devtools/build/android/aapt2/CompiledResources.java
@@ -15,6 +15,8 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.android.ManifestContainer;
+import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
 import java.util.Optional;
@@ -53,13 +55,21 @@
     return resources;
   }
 
+  /** Copies resources archive to a path and returns the new {@link CompiledResources} */
+  public CompiledResources copyResourcesZipTo(Path destination) throws IOException {
+    return new CompiledResources(Files.copy(resources, destination), manifest, assetsDirs);
+  }
+
   @Override
   public Path getManifest() {
     return manifest;
   }
 
   public List<String> getAssetsStrings() {
-    return assetsDirs.stream().map(Path::toString).collect(Collectors.toList());
+    return assetsDirs
+        .stream()
+        .map(Path::toString)
+        .collect(Collectors.toList());
   }
 
   public CompiledResources processManifest(Function<Path, Path> processManifest) {
diff --git a/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceCompiler.java b/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceCompiler.java
index e13dc61..6084f67 100644
--- a/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceCompiler.java
+++ b/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceCompiler.java
@@ -16,6 +16,7 @@
 
 import com.android.builder.core.VariantType;
 import com.android.repository.Revision;
+import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
@@ -55,22 +56,38 @@
 
     @Override
     public Path call() throws Exception {
-      logger.fine(
-          new AaptCommandBuilder(aapt2)
-              .forBuildToolsVersion(buildToolsVersion)
-              .forVariantType(VariantType.LIBRARY)
-              .add("compile")
-              .add("-v")
-              .add("--legacy")
-              .add("-o", compiledResourcesOut.toString())
-              .add(file.toString())
-              .execute("Compiling " + file));
+        logger.fine(
+            new AaptCommandBuilder(aapt2)
+                .forBuildToolsVersion(buildToolsVersion)
+                .forVariantType(VariantType.LIBRARY)
+                .add("compile")
+                .add("-v")
+                .add("--legacy")
+                .add("-o", compiledResourcesOut.toString())
+                .add(file.toString())
+                .execute("Compiling " + file));
+
+
       String type = file.getParent().getFileName().toString();
       String filename = file.getFileName().toString();
       if (type.startsWith("values")) {
-        filename = filename.substring(0, filename.indexOf('.')) + ".arsc";
+        filename =
+            (filename.indexOf('.') != -1 ? filename.substring(0, filename.indexOf('.')) : filename)
+                + ".arsc";
       }
-      return compiledResourcesOut.resolve(type + "_" + filename + ".flat");
+
+      final Path compiledResourcePath =
+          compiledResourcesOut.resolve(type + "_" + filename + ".flat");
+      Preconditions.checkArgument(
+          Files.exists(compiledResourcePath),
+          "%s does not exists after aapt2 ran.",
+          compiledResourcePath);
+      return compiledResourcePath;
+    }
+
+    @Override
+    public String toString() {
+      return "ResourceCompiler.CompileTask(" + file + ")";
     }
   }
 
@@ -95,7 +112,8 @@
 
     @Override
     public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-      if (!Files.isDirectory(file)) {
+      // Ignore directories and "hidden" files that start with .
+      if (!Files.isDirectory(file) && !file.getFileName().toString().startsWith(".")) {
         tasks.add(
             executorService.submit(
                 new CompileTask(
diff --git a/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceLinker.java b/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceLinker.java
index fa9edc3..dc6ab34 100644
--- a/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceLinker.java
+++ b/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceLinker.java
@@ -21,6 +21,7 @@
 import com.google.common.collect.Iterables;
 import com.google.common.io.ByteStreams;
 import com.google.devtools.build.android.AaptCommandBuilder;
+import com.google.devtools.build.android.AndroidResourceOutputs;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -40,7 +41,7 @@
     }
   }
 
-  private static final Logger logger = Logger.getLogger(ResourceLinker.class.getName());
+  private static Logger logger = Logger.getLogger(ResourceLinker.class.getName());
 
   private final Path aapt2;
   private final Path workingDirectory;
@@ -62,9 +63,10 @@
     return new ResourceLinker(aapt2, workingDirectory);
   }
 
-  /** Dependent static to be linked against. */
-  public ResourceLinker dependencies(List<StaticLibrary> linkAgainst) {
-    this.linkAgainst = linkAgainst;
+
+  /** Dependent static libraries to be linked to. */
+  public ResourceLinker dependencies(List<StaticLibrary> libraries) {
+    this.linkAgainst = libraries;
     return this;
   }
 
@@ -86,9 +88,8 @@
 
   public ResourceLinker filterToDensity(List<String> densitiesToFilter) {
     if (densitiesToFilter.size() > 1) {
-      throw new UnsupportedOperationException("Multiple densities not yet supported with aapt2");
-    }
-    if (densitiesToFilter.size() > 0) {
+      logger.warning("Multiple densities not yet supported with aapt2");
+    } else if (densitiesToFilter.size() > 0) {
       density = Iterables.getOnlyElement(densitiesToFilter);
     }
     return this;
@@ -102,7 +103,9 @@
    */
   public StaticLibrary linkStatically(CompiledResources resources) {
     final Path outPath = workingDirectory.resolve("lib.ap_");
-    Path rTxt = workingDirectory.resolve("R.txt");
+    final Path rTxt = workingDirectory.resolve("R.txt");
+    final Path sourceJar = workingDirectory.resolve("r.srcjar");
+    Path javaSourceDirectory = workingDirectory.resolve("java");
 
     try {
       logger.fine(
@@ -117,12 +120,16 @@
               .thenAdd("--no-version-vectors")
               .addRepeated("-R", unzipCompiledResources(resources.getZip()))
               .addRepeated("-I", StaticLibrary.toPathStrings(linkAgainst))
+              .add("--java", javaSourceDirectory)
               .add("--auto-add-overlay")
               .add("-o", outPath)
-              .add("--java", workingDirectory.resolve("java")) // java needed to create R.txt
+              .add("--java", javaSourceDirectory)
               .add("--output-text-symbols", rTxt)
               .execute(String.format("Statically linking %s", resources)));
-      return StaticLibrary.from(outPath, rTxt);
+
+      AndroidResourceOutputs.createSrcJar(javaSourceDirectory, sourceJar, true /* staticIds */);
+
+      return StaticLibrary.from(outPath, rTxt, ImmutableList.of(), sourceJar);
     } catch (IOException e) {
       throw new LinkError(e);
     }
@@ -144,7 +151,7 @@
               .whenVersionIsAtLeast(new Revision(23))
               .thenAdd("--no-version-vectors")
               .add("--no-static-lib-packages")
-              .when(Level.FINE.equals(logger.getLevel()))
+              .when(logger.getLevel() == Level.FINE)
               .thenAdd("-v")
               .add("--manifest", compiled.getManifest())
               .add("--auto-add-overlay")
diff --git a/src/tools/android/java/com/google/devtools/build/android/aapt2/StaticLibrary.java b/src/tools/android/java/com/google/devtools/build/android/aapt2/StaticLibrary.java
index fc574f8..54ba743 100644
--- a/src/tools/android/java/com/google/devtools/build/android/aapt2/StaticLibrary.java
+++ b/src/tools/android/java/com/google/devtools/build/android/aapt2/StaticLibrary.java
@@ -24,6 +24,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
+import java.util.function.Function;
 import javax.annotation.Nullable;
 
 /** A static library generated by aapt2. */
@@ -32,44 +33,74 @@
   private final Path library;
   private final Optional<Path> rTxt;
   private final Optional<List<Path>> assets;
+  private final Optional<Path> sourceJar;
 
-  private StaticLibrary(Path library, Optional<Path> rTxt, Optional<List<Path>> assets) {
+  private StaticLibrary(
+      Path library, Optional<Path> rTxt, Optional<List<Path>> assets, Optional<Path> sourceJar) {
     this.library = library;
     this.rTxt = rTxt;
     this.assets = assets;
+    this.sourceJar = sourceJar;
   }
 
   public static StaticLibrary from(Path library) {
-    return of(library, Optional.empty(), Optional.empty());
+    return of(library, Optional.empty(), Optional.empty(), Optional.empty());
   }
 
   public static StaticLibrary from(Path library, Path rTxt) {
-    return of(library, Optional.of(rTxt), Optional.empty());
+    return of(library, Optional.of(rTxt), Optional.empty(), Optional.empty());
   }
 
-  private static StaticLibrary of(Path library, Optional<Path> rTxt, Optional<List<Path>> assets) {
-    return new StaticLibrary(library, rTxt, assets);
+  private static StaticLibrary of(
+      Path library, Optional<Path> rTxt, Optional<List<Path>> assets, Optional<Path> sourceJar) {
+    return new StaticLibrary(library, rTxt, assets, sourceJar);
+  }
+
+  public static StaticLibrary from(
+      Path library, Path rTxt, ImmutableList<Path> assetDirs) {
+    return of(
+        library,
+        Optional.ofNullable(rTxt),
+        Optional.ofNullable(assetDirs),
+        Optional.empty());
+  }
+
+  public static StaticLibrary from(
+      Path library, Path rTxt, ImmutableList<Path> assetDirs, Path sourceJar) {
+    return of(
+        library,
+        Optional.ofNullable(rTxt),
+        Optional.ofNullable(assetDirs),
+        Optional.ofNullable(sourceJar));
+  }
+
+  public static Collection<String> toPathStrings(List<StaticLibrary> libraries) {
+    return Lists.transform(libraries, StaticLibrary::asLibraryPathString);
   }
 
   public StaticLibrary copyLibraryTo(Path target) throws IOException {
-    return of(Files.copy(library, target), rTxt, assets);
+    return of(Files.copy(library, target), rTxt, assets, sourceJar);
   }
 
   public StaticLibrary copyRTxtTo(@Nullable final Path target) {
-    return of(
-        library,
-        rTxt.map(
-            input -> {
-              if (target == null) {
-                return input;
-              }
-              try {
-                return Files.copy(input, target);
-              } catch (IOException e) {
-                throw new RuntimeException(e);
-              }
-            }),
-        assets);
+    return of(library, rTxt.map(copyTo(target)), assets, sourceJar);
+  }
+
+  public StaticLibrary copySourceJarTo(@Nullable final Path target) {
+    return of(library, rTxt, assets, sourceJar.map(copyTo(target)));
+  }
+
+  private Function<Path, Path> copyTo(Path target) {
+    return input -> {
+      if (target == null) {
+        return input;
+      }
+      try {
+        return Files.copy(input, target);
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    };
   }
 
   @VisibleForTesting
@@ -99,14 +130,6 @@
     return assets.orElse(ImmutableList.of());
   }
 
-  public static Collection<String> toPathStrings(List<StaticLibrary> libraries) {
-    return Lists.transform(libraries, StaticLibrary::asLibraryPathString);
-  }
-
-  public static StaticLibrary from(Path library, Path rTxt, ImmutableList<Path> assetDirs) {
-    return of(library, Optional.ofNullable(rTxt), Optional.ofNullable(assetDirs));
-  }
-
   public static Collection<String> toAssetPaths(List<StaticLibrary> libraries) {
     return libraries
         .stream()