Adds the Android manifest merger as an option for android_binary rules. The merger that is used (legacy or android) is controlled by the manifest_merger attribute on android_binary and the default is controlled by the --android_manifest_merger flag.

RELNOTES: The Android manifest merger is now available as an option for android_binary rules. The merger will honor tools annotations in AndroidManifest.xml and will perform placeholder substitutions using the values specified in android_binary.manifest_values. The merger may be selected by setting the manifest_merger attribute on android_binary.

--
MOS_MIGRATED_REVID=125603954
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
index 500c601..c9530a6 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
@@ -191,25 +191,6 @@
       LocalResourceContainer.validateRuleContext(ruleContext);
       ApplicationManifest ruleManifest = androidSemantics.getManifestForRule(ruleContext);
 
-      String applicationId = null;
-      String versionCode = null;
-      String versionName = null;
-      Map<String, String> manifestValues =
-          ruleContext.attributes().get("manifest_values", Type.STRING_DICT);
-      if (manifestValues != null) {
-        if (manifestValues.containsKey("applicationId")) {
-          applicationId = manifestValues.get("applicationId");
-        }
-        if (manifestValues.containsKey("versionCode")) {
-          versionCode = ruleContext.expandMakeVariables("manifest_values",
-              manifestValues.get("versionCode"));
-        }
-        if (manifestValues.containsKey("versionName")) {
-          versionName = ruleContext.expandMakeVariables("manifest_values",
-              manifestValues.get("versionName"));
-        }
-      }
-
       applicationManifest = ruleManifest.mergeWith(ruleContext, resourceDeps);
 
       resourceApk = applicationManifest.packWithDataAndResources(
@@ -223,9 +204,6 @@
           ruleContext.getTokenizedStringListAttr("nocompress_extensions"),
           ruleContext.attributes().get("crunch_png", Type.BOOLEAN),
           ruleContext.getTokenizedStringListAttr("densities"),
-          applicationId,
-          versionCode,
-          versionName,
           false, /* incremental */
           ProguardHelper.getProguardConfigArtifact(ruleContext, ""),
           createMainDexProguardSpec(ruleContext),
@@ -247,9 +225,6 @@
               ruleContext.getTokenizedStringListAttr("nocompress_extensions"),
               ruleContext.attributes().get("crunch_png", Type.BOOLEAN),
               ruleContext.getTokenizedStringListAttr("densities"),
-              applicationId,
-              versionCode,
-              versionName,
               true, /* incremental */
               ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental"),
               null, /* mainDexProguardCfg */
@@ -270,9 +245,6 @@
               ruleContext.getTokenizedStringListAttr("nocompress_extensions"),
               ruleContext.attributes().get("crunch_png", Type.BOOLEAN),
               ruleContext.getTokenizedStringListAttr("densities"),
-              applicationId,
-              versionCode,
-              versionName,
               true, /* incremental */
               ProguardHelper.getProguardConfigArtifact(ruleContext, "instant_run"),
               null, /* mainDexProguardCfg */
@@ -293,9 +265,6 @@
               ruleContext.getTokenizedStringListAttr("nocompress_extensions"),
               ruleContext.attributes().get("crunch_png", Type.BOOLEAN),
               ruleContext.getTokenizedStringListAttr("densities"),
-              applicationId,
-              versionCode,
-              versionName,
               true, /* incremental */
               ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental_split"),
               null, /* mainDexProguardCfg */
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryOnlyRule.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryOnlyRule.java
index a558b05..e6e116d 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryOnlyRule.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryOnlyRule.java
@@ -26,6 +26,7 @@
 import com.google.devtools.build.lib.packages.Attribute.AllowedValueSet;
 import com.google.devtools.build.lib.packages.RuleClass;
 import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
+import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidManifestMerger;
 import com.google.devtools.build.lib.rules.android.AndroidRuleClasses.MultidexMode;
 
 /**
@@ -35,15 +36,40 @@
   @Override
   public RuleClass build(RuleClass.Builder builder, final RuleDefinitionEnvironment env) {
     return builder
-//        /* <!-- #BLAZE_RULE(android_binary).ATTRIBUTE(manifest_values) -->
-//        A dictionary of values to be overridden in the manifest. Any instance of ${name} in the
-//        manifest will be replaced with the value corresponding to name in this dictionary.
-//        applicationId, versionCode, versionName, minSdkVersion, targetSdkVersion and
-//        maxSdkVersion will also override the corresponding attributes of the manifest and
-//        uses-sdk tags. packageName will be ignored and will be set from either applicationId if
-//        specified or the package in manifest.
-//        <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
-        .add(attr("manifest_values", STRING_DICT).undocumented("not ready for production use"))
+        /* <!-- #BLAZE_RULE(android_binary).ATTRIBUTE(manifest_merger) -->
+        Select the manifest merger to use for this rule.<br/>
+        Possible values:
+        <ul>
+            <li><code>manifest_merger = "legacy"</code>: Use the legacy manifest merger. Does not
+              allow features of the android merger like placeholder substitution and tools
+              attributes for defining merge behavior. Removes all
+              <code>&lt;uses-permission&gt;</code> and <code>&lt;uses-permission-sdk-23&gt;</code>
+              tags. Performs a tag-level merge.</li>
+            <li><code>manifest_merger = "android"</code>: Use the android manifest merger. Allows
+              features like placeholder substitution and tools attributes for defining merge
+              behavior. Follows the semantics from
+              <a href="http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger">
+              the documentation</a> except it has been modified to also remove all
+              <code>&lt;uses-permission&gt;</code> and <code>&lt;uses-permission-sdk-23&gt;</code>
+              tags. Performs an attribute-level merge.</li>
+            <li><code>manifest_merger = "auto"</code>: Merger is controlled by the
+              <a href="../blaze-user-manual.html#flag--android_manifest_merger">
+              --android_manifest_merger</a> flag.</li>
+        </ul>
+        <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+        .add(attr("manifest_merger", STRING)
+            .allowedValues(new AllowedValueSet(AndroidManifestMerger.getAttributeValues()))
+            .value(AndroidManifestMerger.getRuleAttributeDefault()))
+        /* <!-- #BLAZE_RULE(android_binary).ATTRIBUTE(manifest_values) -->
+        A dictionary of values to be overridden in the manifest. Any instance of ${name} in the
+        manifest will be replaced with the value corresponding to name in this dictionary.
+        applicationId, versionCode, versionName, minSdkVersion, targetSdkVersion and
+        maxSdkVersion will also override the corresponding attributes of the manifest and
+        uses-sdk tags. packageName will be ignored and will be set from either applicationId if
+        specified or the package in manifest. When manifest_merger is set to legacy, only
+        applicationId, versionCode and versionName will have any effect.
+        <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+        .add(attr("manifest_values", STRING_DICT))
         /* <!-- #BLAZE_RULE(android_binary).ATTRIBUTE(nocompress_extensions) -->
         A list of file extension to leave uncompressed in apk.
         <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java
index cdc30bf..d8f7cbb 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java
@@ -98,6 +98,16 @@
   }
 
   /**
+   * Converter for {@link AndroidManifestMerger}
+   */
+  public static final class AndroidManifestMergerConverter
+      extends EnumConverter<AndroidManifestMerger> {
+    public AndroidManifestMergerConverter() {
+      super(AndroidManifestMerger.class, "android manifest merger");
+    }
+  }
+
+  /**
    * Value used to avoid multiple configurations from conflicting.
    *
    * <p>This is set to {@code ANDROID} in Android configurations and to {@code MAIN} otherwise. This
@@ -139,6 +149,30 @@
     }
   }
 
+  /** Types of android manifest mergers. */
+  public enum AndroidManifestMerger {
+    LEGACY,
+    ANDROID;
+
+    public static List<String> getAttributeValues() {
+      return ImmutableList.of(LEGACY.name().toLowerCase(), ANDROID.name().toLowerCase(),
+          getRuleAttributeDefault());
+    }
+
+    public static String getRuleAttributeDefault() {
+      return "auto";
+    }
+
+    public static AndroidManifestMerger fromString(String value) {
+      for (AndroidManifestMerger merger : AndroidManifestMerger.values()) {
+        if (merger.name().equalsIgnoreCase(value)) {
+          return merger;
+        }
+      }
+      return null;
+    }
+  }
+
   /**
    * Android configuration options.
    */
@@ -296,6 +330,14 @@
         help = "Enables the use of an obfuscation map when generating the main dex jar file")
     public boolean useProguardPreviousObfuscationMap;
 
+    @Option(name = "android_manifest_merger",
+        defaultValue = "legacy",
+        category = "semantics",
+        converter = AndroidManifestMergerConverter.class,
+        help = "Selects the manifest merger to use for android_binary rules. Flag to help the"
+            + "transition to the Android manifest merger from the legacy merger.")
+    public AndroidManifestMerger manifestMerger;
+
     @Override
     public void addAllLabels(Multimap<String, Label> labelMap) {
       if (androidCrosstoolTop != null) {
@@ -366,6 +408,7 @@
   private final boolean allowAndroidLibraryDepsWithoutSrcs;
   private final boolean useAndroidResourceShrinking;
   private final boolean useProguardPreviousObfuscationMap;
+  private final AndroidManifestMerger manifestMerger;
 
   AndroidConfiguration(Options options, Label androidSdk) {
     this.sdk = androidSdk;
@@ -389,6 +432,7 @@
     this.allowAndroidLibraryDepsWithoutSrcs = options.allowAndroidLibraryDepsWithoutSrcs;
     this.useAndroidResourceShrinking = options.useAndroidResourceShrinking;
     this.useProguardPreviousObfuscationMap = options.useProguardPreviousObfuscationMap;
+    this.manifestMerger = options.manifestMerger;
   }
 
   public String getCpu() {
@@ -465,6 +509,10 @@
     return useProguardPreviousObfuscationMap;
   }
 
+  public AndroidManifestMerger getManifestMerger() {
+    return manifestMerger;
+  }
+
   @Override
   public void addGlobalMakeVariables(ImmutableMap.Builder<String, String> globalMakeEnvBuilder) {
     globalMakeEnvBuilder.put("ANDROID_CPU", cpu);
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java
index 8630825..964db8a 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java
@@ -25,7 +25,6 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.packages.BuildType;
-import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
 import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
 import com.google.devtools.build.lib.rules.android.AndroidLibraryAarProvider.Aar;
 import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceContainer;
@@ -78,7 +77,8 @@
 
     final ResourceApk resourceApk;
     if (definesLocalResources) {
-      ApplicationManifest applicationManifest = androidSemantics.getManifestForRule(ruleContext);
+      ApplicationManifest applicationManifest = androidSemantics.getManifestForRule(ruleContext)
+          .renamePackage(ruleContext, AndroidCommon.getJavaPackage(ruleContext));
       resourceApk = applicationManifest.packWithDataAndResources(
           null, /* resourceApk -- not needed for library */
           ruleContext,
@@ -90,11 +90,8 @@
           ImmutableList.<String>of(), /* uncompressedExtensions */
           false, /* crunchPng */
           ImmutableList.<String>of(), /* densities */
-          null /* applicationId */,
-          null /* versionCode */,
-          null /* versionName */,
-          false,
-          null /* proguardCfgOut */,
+          false, /* incremental */
+          null, /* proguardCfgOut */
           null, /* mainDexProguardCfg */
           ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_PROCESSED_MANIFEST),
           null /* mergedResourcesOut */);
@@ -145,7 +142,8 @@
     } else {
       // there are no local resources and resources attribute was not specified either
       aar = null;
-      ApplicationManifest applicationManifest = ApplicationManifest.generatedManifest(ruleContext);
+      ApplicationManifest applicationManifest = ApplicationManifest.generatedManifest(ruleContext)
+          .renamePackage(ruleContext, AndroidCommon.getJavaPackage(ruleContext));
 
       String javaPackage = AndroidCommon.getJavaPackage(ruleContext);
 
@@ -200,7 +198,7 @@
       .add(ProguardSpecProvider.class, new ProguardSpecProvider(transitiveProguardConfigs))
       .addOutputGroup(OutputGroupProvider.HIDDEN_TOP_LEVEL, transitiveProguardConfigs)
       .add(AndroidLibraryAarProvider.class, new AndroidLibraryAarProvider(
-                  aar, transitiveAars.build()))
+          aar, transitiveAars.build()))
       .build();
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
index 306a922..0f2008b 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
@@ -165,11 +165,10 @@
       "//tools/android:incremental_stub_application";
   public static final String DEFAULT_INCREMENTAL_SPLIT_STUB_APPLICATION =
       "//tools/android:incremental_split_stub_application";
-  public static final String DEFAULT_RESOURCES_PROCESSOR =
-      "//tools/android:resources_processor";
-  public static final String DEFAULT_RESOURCE_SHRINKER =
-      "//tools/android:resource_shrinker";
   public static final String DEFAULT_AAR_GENERATOR = "//tools/android:aar_generator";
+  public static final String DEFAULT_MANIFEST_MERGER = "//tools/android:manifest_merger";
+  public static final String DEFAULT_RESOURCES_PROCESSOR = "//tools/android:resources_processor";
+  public static final String DEFAULT_RESOURCE_SHRINKER = "//tools/android:resource_shrinker";
   public static final String DEFAULT_SDK = "//tools/android:sdk";
 
   /**
@@ -398,12 +397,14 @@
     @Override
     public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) {
       return builder
+          .add(attr("$android_aar_generator", LABEL).cfg(HOST).exec().value(
+              env.getToolsLabel(DEFAULT_AAR_GENERATOR)))
+          .add(attr("$android_manifest_merger", LABEL).cfg(HOST).exec().value(
+              env.getToolsLabel(DEFAULT_MANIFEST_MERGER)))
           .add(attr("$android_resources_processor", LABEL).cfg(HOST).exec().value(
               env.getToolsLabel(DEFAULT_RESOURCES_PROCESSOR)))
           .add(attr("$android_resource_shrinker", LABEL).cfg(HOST).exec().value(
               env.getToolsLabel(DEFAULT_RESOURCE_SHRINKER)))
-          .add(attr("$android_aar_generator", LABEL).cfg(HOST).exec().value(
-              env.getToolsLabel(DEFAULT_AAR_GENERATOR)))
           .build();
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java b/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java
index 296f4af..8992776 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java
@@ -13,8 +13,12 @@
 // limitations under the License.
 package com.google.devtools.build.lib.rules.android;
 
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static com.google.devtools.build.lib.syntax.Type.STRING;
+
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -26,6 +30,7 @@
 import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.analysis.config.CompilationMode;
+import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidManifestMerger;
 import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceContainer;
 import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceType;
 import com.google.devtools.build.lib.syntax.Type;
@@ -33,6 +38,7 @@
 
 import java.util.List;
 import java.util.Map;
+import java.util.TreeMap;
 
 import javax.annotation.Nullable;
 
@@ -44,9 +50,8 @@
       ruleContext.attributeError("manifest", "a resources or manifest attribute is mandatory.");
       return null;
     }
-    return new ApplicationManifest(Iterables.getOnlyElement(
-        resources.getDirectAndroidResources())
-        .getManifest());
+    return new ApplicationManifest(
+        ruleContext, Iterables.getOnlyElement(resources.getDirectAndroidResources()).getManifest());
   }
 
   public ApplicationManifest createSplitManifest(
@@ -67,7 +72,7 @@
         .addArgument(splitName)
         .addArgument(hasCode ? "--hascode" : "--nohascode");
 
-    String overridePackage = getOverridePackage(ruleContext);
+    String overridePackage = manifestValues.get("applicationId");
     if (overridePackage != null) {
       builder
           .addArgument("--override_package")
@@ -75,31 +80,7 @@
     }
 
     ruleContext.registerAction(builder.build(ruleContext));
-    return new ApplicationManifest(result);
-  }
-
-  private String getOverridePackage(RuleContext ruleContext) {
-    // It seems that we sometimes rename the app for God-knows-what reason. If that is the case,
-    // pass this information to the stubifier script.
-    if (ruleContext.attributes().isAttributeValueExplicitlySpecified("manifest_values")) {
-      Map<String, String> manifestValues =
-          ruleContext.attributes().get("manifest_values", Type.STRING_DICT);
-      if (manifestValues.containsKey("applicationId")) {
-        return manifestValues.get("applicationId");
-      }
-    }
-    if (ruleContext.attributes().isAttributeValueExplicitlySpecified("application_id")) {
-      return ruleContext.attributes().get("application_id", Type.STRING);
-    }
-
-    AndroidResourcesProvider resourcesProvider = AndroidCommon.getAndroidResources(ruleContext);
-    if (resourcesProvider != null) {
-      ResourceContainer resourceContainer = Iterables.getOnlyElement(
-          resourcesProvider.getDirectAndroidResources());
-      return resourceContainer.getRenameManifestPackage();
-    } else {
-      return null;
-    }
+    return new ApplicationManifest(ruleContext, result);
   }
 
   public ApplicationManifest addMobileInstallStubApplication(RuleContext ruleContext)
@@ -121,7 +102,7 @@
         .addOutputArgument(ruleContext.getImplicitOutputArtifact(
             AndroidRuleClasses.MOBILE_INSTALL_STUB_APPLICATION_DATA));
 
-    String overridePackage = getOverridePackage(ruleContext);
+    String overridePackage = manifestValues.get("applicationId");
     if (overridePackage != null) {
       builder.addArgument("--override_package");
       builder.addArgument(overridePackage);
@@ -129,7 +110,7 @@
 
     ruleContext.registerAction(builder.build(ruleContext));
 
-    return new ApplicationManifest(stubManifest);
+    return new ApplicationManifest(ruleContext, stubManifest);
   }
 
   public ApplicationManifest addInstantRunStubApplication(RuleContext ruleContext)
@@ -150,15 +131,17 @@
 
     ruleContext.registerAction(builder.build(ruleContext));
 
-    return new ApplicationManifest(stubManifest);
-  }
-  
-  public static ApplicationManifest fromRule(RuleContext ruleContext) {
-    return new ApplicationManifest(ruleContext.getPrerequisiteArtifact("manifest", Mode.TARGET));
+    return new ApplicationManifest(ruleContext, stubManifest);
   }
 
-  public static ApplicationManifest fromExplicitManifest(Artifact manifest) {
-    return new ApplicationManifest(manifest);
+  public static ApplicationManifest fromRule(RuleContext ruleContext) {
+    return new ApplicationManifest(
+        ruleContext, ruleContext.getPrerequisiteArtifact("manifest", Mode.TARGET));
+  }
+
+  public static ApplicationManifest fromExplicitManifest(
+      RuleContext ruleContext, Artifact manifest) {
+    return new ApplicationManifest(ruleContext, manifest);
   }
 
   /**
@@ -184,26 +167,78 @@
         "</manifest>");
     ruleContext.getAnalysisEnvironment().registerAction(new FileWriteAction(
         ruleContext.getActionOwner(), generatedManifest, contents, false /* makeExecutable */));
-    return new ApplicationManifest(generatedManifest);
+    return new ApplicationManifest(ruleContext, generatedManifest);
+  }
+
+  private static ImmutableMap<String, String> getManifestValues(RuleContext context) {
+    Map<String, String> manifestValues = new TreeMap<>();
+    // applicationId is set from manifest_values or android_resources.rename_manifest_package
+    // with descending priority.
+    AndroidResourcesProvider resourcesProvider = AndroidCommon.getAndroidResources(context);
+    if (resourcesProvider != null) {
+      ResourceContainer resourceContainer = Iterables.getOnlyElement(
+          resourcesProvider.getDirectAndroidResources());
+      if (resourceContainer.getRenameManifestPackage() != null) {
+        manifestValues.put("applicationId", resourceContainer.getRenameManifestPackage());
+      }
+    }
+    if (context.attributes().isAttributeValueExplicitlySpecified("manifest_values")) {
+      manifestValues.putAll(context.attributes().get("manifest_values", Type.STRING_DICT));
+    }
+
+    for (String variable : manifestValues.keySet()) {
+      manifestValues.put(
+          variable, context.expandMakeVariables("manifest_values", manifestValues.get(variable)));
+    }
+    return ImmutableMap.copyOf(manifestValues);
   }
 
   private final Artifact manifest;
+  private final ImmutableMap<String, String> manifestValues;
 
-  private ApplicationManifest(Artifact manifest) {
+  private ApplicationManifest(RuleContext ruleContext, Artifact manifest) {
     this.manifest = manifest;
+    this.manifestValues = getManifestValues(ruleContext);
   }
 
-  public ApplicationManifest mergeWith(RuleContext ruleContext,
-      ResourceDependencies resourceDeps) {
+  public ApplicationManifest mergeWith(RuleContext ruleContext, ResourceDependencies resourceDeps) {
     Iterable<Artifact> mergeeManifests = getMergeeManifests(resourceDeps.getResources());
-    if (!Iterables.isEmpty(mergeeManifests)) {
-      Iterable<Artifact> exportedManifests = mergeeManifests;
-      Artifact outputManifest = ruleContext.getUniqueDirectoryArtifact(
-          ruleContext.getRule().getName() + "_merged", "AndroidManifest.xml",
-          ruleContext.getBinOrGenfilesDirectory());
-      AndroidManifestMergeHelper.createMergeManifestAction(ruleContext, getManifest(),
-          exportedManifests, ImmutableList.of("all"), outputManifest);
-      return new ApplicationManifest(outputManifest);
+
+    boolean legacy = true;
+    if (ruleContext.isLegalFragment(AndroidConfiguration.class)
+        && ruleContext.getRule().isAttrDefined("manifest_merger", STRING)) {
+      AndroidManifestMerger merger = AndroidManifestMerger.fromString(
+          ruleContext.attributes().get("manifest_merger", STRING));
+      if (merger == null) {
+        merger = ruleContext.getFragment(AndroidConfiguration.class).getManifestMerger();
+      }
+      legacy = merger == AndroidManifestMerger.LEGACY;
+    }
+
+    if (legacy) {
+      if (!Iterables.isEmpty(mergeeManifests)) {
+        Artifact outputManifest = ruleContext.getUniqueDirectoryArtifact(
+            ruleContext.getRule().getName() + "_merged", "AndroidManifest.xml",
+            ruleContext.getBinOrGenfilesDirectory());
+        AndroidManifestMergeHelper.createMergeManifestAction(ruleContext, getManifest(),
+            mergeeManifests, ImmutableList.of("all"), outputManifest);
+        return new ApplicationManifest(ruleContext, outputManifest);
+      }
+    } else {
+      if (!Iterables.isEmpty(mergeeManifests) || !manifestValues.isEmpty()) {
+        Artifact outputManifest = ruleContext.getUniqueDirectoryArtifact(
+            ruleContext.getRule().getName() + "_merged", "AndroidManifest.xml",
+            ruleContext.getBinOrGenfilesDirectory());
+        new ManifestMergerActionBuilder(ruleContext)
+            .setManifest(getManifest())
+            .setMergeeManifests(mergeeManifests)
+            .setLibrary(false)
+            .setManifestValues(manifestValues)
+            .setCustomPackage(AndroidCommon.getJavaPackage(ruleContext))
+            .setManifestOutput(outputManifest)
+            .build(ruleContext);
+        return new ApplicationManifest(ruleContext, outputManifest);
+      }
     }
     return this;
   }
@@ -220,6 +255,22 @@
     return builder.build();
   }
 
+  public ApplicationManifest renamePackage(RuleContext ruleContext, String customPackage) {
+    if (isNullOrEmpty(customPackage)) {
+      return this;
+    }
+    Artifact outputManifest = ruleContext.getUniqueDirectoryArtifact(
+        ruleContext.getRule().getName() + "_renamed", "AndroidManifest.xml",
+        ruleContext.getBinOrGenfilesDirectory());
+    new ManifestMergerActionBuilder(ruleContext)
+        .setManifest(getManifest())
+        .setLibrary(true)
+        .setCustomPackage(customPackage)
+        .setManifestOutput(outputManifest)
+        .build(ruleContext);
+    return new ApplicationManifest(ruleContext, outputManifest);
+  }
+
   /** Packages up the manifest with assets from the rule and dependent resources.
    * @throws InterruptedException */
   public ResourceApk packWithAssets(
@@ -244,14 +295,11 @@
         false, /* isLibrary */
         resourceDeps,
         rTxt,
-        null, /* symbolsTxt */
+        null, /* Artifact symbolsTxt */
         ImmutableList.<String>of(), /* configurationFilters */
         ImmutableList.<String>of(), /* uncompressedExtensions */
         true, /* crunchPng */
         ImmutableList.<String>of(), /* densities */
-        null, /* String applicationId */
-        null, /* String versionCode */
-        null, /* String versionName */
         incremental,
         data,
         proguardCfg,
@@ -272,9 +320,6 @@
       List<String> uncompressedExtensions,
       boolean crunchPng,
       List<String> densities,
-      String applicationId,
-      String versionCode,
-      String versionName,
       boolean incremental,
       Artifact proguardCfg,
       @Nullable Artifact mainDexProguardCfg,
@@ -307,9 +352,6 @@
         uncompressedExtensions,
         crunchPng,
         densities,
-        applicationId,
-        versionCode,
-        versionName,
         incremental,
         data,
         proguardCfg,
@@ -329,9 +371,6 @@
       List<String> uncompressedExtensions,
       boolean crunchPng,
       List<String> densities,
-      String applicationId,
-      String versionCode,
-      String versionName,
       boolean incremental,
       LocalResourceContainer data,
       Artifact proguardCfg,
@@ -370,9 +409,9 @@
             .setDensities(densities)
             .setProguardOut(proguardCfg)
             .setMainDexProguardOut(mainDexProguardCfg)
-            .setApplicationId(applicationId)
-            .setVersionCode(versionCode)
-            .setVersionName(versionName);
+            .setApplicationId(manifestValues.get("applicationId"))
+            .setVersionCode(manifestValues.get("versionCode"))
+            .setVersionName(manifestValues.get("versionName"));
 
     if (!incremental) {
       builder
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/LocalResourceContainer.java b/src/main/java/com/google/devtools/build/lib/rules/android/LocalResourceContainer.java
index acd5cf1..971ccf7 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/LocalResourceContainer.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/LocalResourceContainer.java
@@ -45,10 +45,7 @@
       "assets",
       "assets_dir",
       "inline_constants",
-      "exports_manifest",
-      "application_id",
-      "version_name",
-      "version_code"
+      "exports_manifest"
   };
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ManifestMergerActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/ManifestMergerActionBuilder.java
new file mode 100644
index 0000000..2117440
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/ManifestMergerActionBuilder.java
@@ -0,0 +1,138 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.rules.android;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext;
+import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Builder for creating manifest merger actions.
+ */
+public class ManifestMergerActionBuilder {
+  private final RuleContext ruleContext;
+  private final SpawnAction.Builder spawnActionBuilder;
+
+  private Artifact manifest;
+  private List<Artifact> mergeeManifests;
+  private boolean isLibrary;
+  private Map<String, String> manifestValues;
+  private String customPackage;
+  private Artifact manifestOutput;
+
+  public ManifestMergerActionBuilder(RuleContext ruleContext) {
+    this.ruleContext = ruleContext;
+    this.spawnActionBuilder = new SpawnAction.Builder();
+  }
+
+  public ManifestMergerActionBuilder setManifest(Artifact manifest) {
+    this.manifest = manifest;
+    return this;
+  }
+
+  public ManifestMergerActionBuilder setMergeeManifests(Iterable<Artifact> mergeeManifests) {
+    this.mergeeManifests = ImmutableList.copyOf(mergeeManifests);
+    return this;
+  }
+
+  public ManifestMergerActionBuilder setLibrary(boolean isLibrary) {
+    this.isLibrary = isLibrary;
+    return this;
+  }
+
+  public ManifestMergerActionBuilder setManifestValues(Map<String, String> manifestValues) {
+    this.manifestValues = manifestValues;
+    return this;
+  }
+
+  public ManifestMergerActionBuilder setCustomPackage(String customPackage) {
+    this.customPackage = customPackage;
+    return this;
+  }
+
+  public ManifestMergerActionBuilder setManifestOutput(Artifact manifestOutput) {
+    this.manifestOutput = manifestOutput;
+    return this;
+  }
+
+  public void build(ActionConstructionContext context) {
+    NestedSetBuilder<Artifact> inputs = NestedSetBuilder.naiveLinkOrder();
+    ImmutableList.Builder<Artifact> outputs = ImmutableList.builder();
+    CustomCommandLine.Builder builder = new CustomCommandLine.Builder();
+
+    inputs.addAll(ruleContext.getExecutablePrerequisite("$android_manifest_merger", Mode.HOST)
+        .getRunfilesSupport()
+        .getRunfilesArtifactsWithoutMiddlemen());
+
+    builder.addExecPath("--manifest", manifest);
+    inputs.add(manifest);
+
+    if (mergeeManifests != null && !mergeeManifests.isEmpty()) {
+      builder.addJoinExecPaths("--mergeeManifests", ":", mergeeManifests);
+      inputs.addAll(mergeeManifests);
+    }
+
+    if (isLibrary) {
+      builder.add("--mergeType").add("LIBRARY");
+    }
+
+    if (manifestValues != null && !manifestValues.isEmpty()) {
+      builder.add("--manifestValues").add(mapToDictionaryString(manifestValues));
+    }
+
+    if (customPackage != null && !customPackage.isEmpty()) {
+      builder.add("--customPackage").add(customPackage);
+    }
+
+    builder.addExecPath("--manifestOutput", manifestOutput);
+    outputs.add(manifestOutput);
+
+    ruleContext.registerAction(
+        this.spawnActionBuilder
+            .addTransitiveInputs(inputs.build())
+            .addOutputs(outputs.build())
+            .setCommandLine(builder.build())
+            .setExecutable(
+                ruleContext.getExecutablePrerequisite("$android_manifest_merger", Mode.HOST))
+            .setProgressMessage("Merging manifest")
+            .setMnemonic("ManifestMerger")
+            .build(context));
+  }
+
+  private <K, V> String mapToDictionaryString(Map<K, V> map) {
+    StringBuilder sb = new StringBuilder();
+    Iterator<Entry<K, V>> iter = map.entrySet().iterator();
+    while (iter.hasNext()) {
+      Entry<K, V> entry = iter.next();
+      sb.append(entry.getKey().toString().replace(":", "\\:").replace(",", "\\,"));
+      sb.append(':');
+      sb.append(entry.getValue().toString().replace(":", "\\:").replace(",", "\\,"));
+      if (iter.hasNext()) {
+        sb.append(',');
+      }
+    }
+    return sb.toString();
+  }
+}
+
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
index 2ac21a7..6606fc2 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
@@ -161,6 +161,7 @@
         .add("sh_binary(name = 'aar_generator', srcs = ['empty.sh'])")
         .add("sh_binary(name = 'dexbuilder', srcs = ['empty.sh'])")
         .add("sh_binary(name = 'dexmerger', srcs = ['empty.sh'])")
+        .add("sh_binary(name = 'manifest_merger', srcs = ['empty.sh'])")
         .add("sh_binary(name = 'resources_processor', srcs = ['empty.sh'])")
         .add("sh_binary(name = 'resource_shrinker', srcs = ['empty.sh'])")
         .add("android_library(name = 'incremental_stub_application')")
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java
index 69f58d6..9bbef3d 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java
@@ -13,6 +13,8 @@
 // limitations under the License.
 package com.google.devtools.build.android;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import com.google.common.base.Stopwatch;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -30,6 +32,7 @@
 import com.google.devtools.common.options.TriState;
 
 import com.android.builder.core.VariantConfiguration;
+import com.android.builder.core.VariantConfiguration.Type;
 import com.android.ide.common.internal.AaptCruncher;
 import com.android.ide.common.internal.CommandLineRunner;
 import com.android.ide.common.internal.LoggedErrorException;
@@ -40,6 +43,7 @@
 import java.io.IOException;
 import java.nio.file.FileSystem;
 import java.nio.file.FileSystems;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
@@ -226,6 +230,7 @@
       final Path filteredResources = tmp.resolve("resources-filtered");
       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");
 
       Path generatedSources = null;
       if (options.srcJarOutput != null || options.rOutput != null
@@ -266,7 +271,7 @@
       LOGGER.fine(String.format("Density filtering finished at %sms",
           timer.elapsed(TimeUnit.MILLISECONDS)));
 
-      final MergedAndroidData processedManifestData = resourceProcessor.processManifest(
+      MergedAndroidData processedData = resourceProcessor.processManifest(
           options.packageType,
           options.packageForR,
           options.applicationId,
@@ -275,6 +280,24 @@
           filteredData,
           processedManifest);
 
+      // Write manifestOutput now before the dummy manifest is created.
+      if (options.manifestOutput != null) {
+        resourceProcessor.copyManifestToOutput(processedData, options.manifestOutput);
+      }
+
+      if (options.packageType == Type.LIBRARY) {
+        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>", options.packageForR).getBytes(UTF_8));
+        processedData = new MergedAndroidData(
+            processedData.getResourceDir(),
+            processedData.getAssetDir(),
+            dummyManifest);
+      }
+
       resourceProcessor.processResources(
           aaptConfigOptions.aapt,
           aaptConfigOptions.androidJar,
@@ -285,20 +308,17 @@
           new FlagAaptOptions(aaptConfigOptions),
           aaptConfigOptions.resourceConfigs,
           aaptConfigOptions.splits,
-          processedManifestData,
+          processedData,
           data,
           generatedSources,
           options.packagePath,
           options.proguardOutput,
           options.mainDexProguardOutput,
           options.resourcesOutput != null
-              ? processedManifestData.getResourceDir().resolve("values").resolve("public.xml")
+              ? processedData.getResourceDir().resolve("values").resolve("public.xml")
               : null);
       LOGGER.fine(String.format("aapt finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
 
-      if (options.manifestOutput != null) {
-        resourceProcessor.copyManifestToOutput(processedManifestData, options.manifestOutput);
-      }
       if (options.srcJarOutput != null) {
         resourceProcessor.createSrcJar(generatedSources, options.srcJarOutput,
             VariantConfiguration.Type.LIBRARY == options.packageType);
@@ -312,8 +332,8 @@
             VariantConfiguration.Type.LIBRARY == options.packageType);
       }
       if (options.resourcesOutput != null) {
-        resourceProcessor.createResourcesZip(processedManifestData.getResourceDir(),
-            processedManifestData.getAssetDir(), options.resourcesOutput);
+        resourceProcessor.createResourcesZip(processedData.getResourceDir(),
+            processedData.getAssetDir(), options.resourcesOutput);
       }
       LOGGER.fine(String.format("Packaging finished at %sms",
           timer.elapsed(TimeUnit.MILLISECONDS)));
diff --git a/src/tools/android/java/com/google/devtools/build/android/BUILD.tools b/src/tools/android/java/com/google/devtools/build/android/BUILD.tools
index 1d5da32..d844ff9 100644
--- a/src/tools/android/java/com/google/devtools/build/android/BUILD.tools
+++ b/src/tools/android/java/com/google/devtools/build/android/BUILD.tools
@@ -6,6 +6,14 @@
 )
 
 java_binary(
+    name = "AarGeneratorAction",
+    main_class = "com.google.devtools.build.android.AarGeneratorAction",
+    runtime_deps = [
+        ":classes",
+    ],
+)
+
+java_binary(
     name = "AndroidResourceCompilationAction",
     main_class = "com.google.devtools.build.android.AndroidResourceCompilationAction",
     runtime_deps = [
@@ -22,16 +30,16 @@
 )
 
 java_binary(
-    name = "ResourceShrinkerAction",
-    main_class = "com.google.devtools.build.android.ResourceShrinkerAction",
+    name = "ManifestMergerAction",
+    main_class = "com.google.devtools.build.android.ManifestMergerAction",
     runtime_deps = [
         ":classes",
     ],
 )
 
 java_binary(
-    name = "AarGeneratorAction",
-    main_class = "com.google.devtools.build.android.AarGeneratorAction",
+    name = "ResourceShrinkerAction",
+    main_class = "com.google.devtools.build.android.ResourceShrinkerAction",
     runtime_deps = [
         ":classes",
     ],
diff --git a/src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction.java b/src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction.java
index 4a8f0a5..ce1884d 100644
--- a/src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction.java
+++ b/src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction.java
@@ -15,6 +15,7 @@
 
 import static java.util.logging.Level.SEVERE;
 
+import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.android.Converters.ExistingPathConverter;
 import com.google.devtools.build.android.Converters.ExistingPathListConverter;
 import com.google.devtools.build.android.Converters.MergeTypeConverter;
@@ -27,6 +28,11 @@
 import com.android.manifmerger.ManifestMerger2.MergeType;
 import com.android.utils.StdLogger;
 
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -36,6 +42,16 @@
 import java.util.Map;
 import java.util.logging.Logger;
 
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
 /**
  * An action to perform manifest merging using the Gradle manifest merger.
  *
@@ -102,11 +118,34 @@
     public Path manifestOutput;
   }
 
+  private static final String[] PERMISSION_TAGS =
+      new String[] {"uses-permission", "uses-permission-sdk-23"};
   private static final StdLogger stdLogger = new StdLogger(StdLogger.Level.WARNING);
   private static final Logger logger = Logger.getLogger(ManifestMergerAction.class.getName());
 
   private static Options options;
 
+  private static Path removePermissions(Path manifest, Path outputDir)
+      throws IOException, ParserConfigurationException, TransformerConfigurationException,
+          TransformerException, TransformerFactoryConfigurationError, SAXException {
+    DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+    Document doc = docBuilder.parse(manifest.toFile());
+    for (String tag : PERMISSION_TAGS) {
+      NodeList permissions = doc.getElementsByTagName(tag);
+      if (permissions != null) {
+        for (int i = permissions.getLength() - 1; i >= 0; i--) {
+          Node permission = permissions.item(i);
+          permission.getParentNode().removeChild(permission);
+        }
+      }
+    }
+    Path output = outputDir.resolve(manifest.getFileName());
+    TransformerFactory.newInstance().newTransformer().transform(
+        new DOMSource(doc),
+        new StreamResult(output.toFile()));
+    return output;
+  }
+
   public static void main(String[] args) throws Exception {
     OptionsParser optionsParser = OptionsParser.newOptionsParser(Options.class);
     optionsParser.parseAndExitUponError(args);
@@ -117,10 +156,18 @@
     try {
       Path mergedManifest;
       if (options.mergeType == MergeType.APPLICATION) {
+        // Remove uses-permission tags from mergees before the merge.
+        Path tmp = Files.createTempDirectory("manifest_merge_tmp");
+        tmp.toFile().deleteOnExit();
+        ImmutableList.Builder<Path> mergeeManifests = ImmutableList.builder();
+        for (Path mergeeManifest : options.mergeeManifests) {
+          mergeeManifests.add(removePermissions(mergeeManifest, tmp));
+        }
+
         // Ignore custom package at the binary level.
         mergedManifest = resourceProcessor.mergeManifest(
             options.manifest,
-            options.mergeeManifests,
+            mergeeManifests.build(),
             options.mergeType,
             options.manifestValues,
             options.manifestOutput);
diff --git a/tools/android/BUILD b/tools/android/BUILD
index b6e7cb4..1056ebc 100644
--- a/tools/android/BUILD
+++ b/tools/android/BUILD
@@ -22,6 +22,12 @@
 )
 
 sh_binary(
+    name = "manifest_merger",
+    srcs = ["manifest_merger.sh"],
+    data = ["//src/tools/android/java/com/google/devtools/build/android:ManifestMergerAction"],
+)
+
+sh_binary(
     name = "resources_processor",
     srcs = ["resources_processor.sh"],
     data = ["//src/tools/android/java/com/google/devtools/build/android:AndroidResourceProcessingAction"],
diff --git a/tools/android/manifest_merger.sh b/tools/android/manifest_merger.sh
new file mode 100755
index 0000000..755bf4c
--- /dev/null
+++ b/tools/android/manifest_merger.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+# Copyright 2016 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+exec "${TEST_SRCDIR-$0.runfiles}/bazel_tools/src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction" "$@"