// Copyright 2015 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 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.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.FileProvider;
import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
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.cmdline.Label;
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;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.vfs.PathFragment;

import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.annotation.Nullable;

/** Represents a AndroidManifest, that may have been merged from dependencies. */
public final class ApplicationManifest {
  public static ApplicationManifest fromResourcesRule(RuleContext ruleContext) {
    final AndroidResourcesProvider resources = AndroidCommon.getAndroidResources(ruleContext);
    if (resources == null) {
      ruleContext.attributeError("manifest", "a resources or manifest attribute is mandatory.");
      return null;
    }
    return new ApplicationManifest(
        ruleContext, Iterables.getOnlyElement(resources.getDirectAndroidResources()).getManifest());
  }

  public ApplicationManifest createSplitManifest(
      RuleContext ruleContext, String splitName, boolean hasCode) {
    // aapt insists that manifests be called AndroidManifest.xml, even though they have to be
    // explicitly designated as manifests on the command line
    Artifact result = AndroidBinary.getDxArtifact(
        ruleContext, "split_" + splitName + "/AndroidManifest.xml");
    SpawnAction.Builder builder = new SpawnAction.Builder()
        .setExecutable(ruleContext.getExecutablePrerequisite("$build_split_manifest", Mode.HOST))
        .setProgressMessage("Creating manifest for split " + splitName)
        .setMnemonic("AndroidBuildSplitManifest")
        .addArgument("--main_manifest")
        .addInputArgument(manifest)
        .addArgument("--split_manifest")
        .addOutputArgument(result)
        .addArgument("--split")
        .addArgument(splitName)
        .addArgument(hasCode ? "--hascode" : "--nohascode");

    String overridePackage = manifestValues.get("applicationId");
    if (overridePackage != null) {
      builder
          .addArgument("--override_package")
          .addArgument(overridePackage);
    }

    ruleContext.registerAction(builder.build(ruleContext));
    return new ApplicationManifest(ruleContext, result);
  }

  public ApplicationManifest addMobileInstallStubApplication(RuleContext ruleContext)
      throws InterruptedException {

    Artifact stubManifest = ruleContext.getImplicitOutputArtifact(
            AndroidRuleClasses.MOBILE_INSTALL_STUB_APPLICATION_MANIFEST);

    SpawnAction.Builder builder = new SpawnAction.Builder()
        .setExecutable(ruleContext.getExecutablePrerequisite("$stubify_manifest", Mode.HOST))
        .setProgressMessage("Injecting mobile install stub application")
        .setMnemonic("InjectMobileInstallStubApplication")
        .addArgument("--mode=mobile_install")
        .addArgument("--input_manifest")
        .addInputArgument(manifest)
        .addArgument("--output_manifest")
        .addOutputArgument(stubManifest)
        .addArgument("--output_datafile")
        .addOutputArgument(ruleContext.getImplicitOutputArtifact(
            AndroidRuleClasses.MOBILE_INSTALL_STUB_APPLICATION_DATA));

    String overridePackage = manifestValues.get("applicationId");
    if (overridePackage != null) {
      builder.addArgument("--override_package");
      builder.addArgument(overridePackage);
    }

    ruleContext.registerAction(builder.build(ruleContext));

    return new ApplicationManifest(ruleContext, stubManifest);
  }

  public ApplicationManifest addInstantRunStubApplication(RuleContext ruleContext)
      throws InterruptedException {

    Artifact stubManifest = ruleContext.getImplicitOutputArtifact(
        AndroidRuleClasses.INSTANT_RUN_STUB_APPLICATION_MANIFEST);

    SpawnAction.Builder builder = new SpawnAction.Builder()
        .setExecutable(ruleContext.getExecutablePrerequisite("$stubify_manifest", Mode.HOST))
        .setProgressMessage("Injecting instant run stub application")
        .setMnemonic("InjectInstantRunStubApplication")
        .addArgument("--mode=instant_run")
        .addArgument("--input_manifest")
        .addInputArgument(manifest)
        .addArgument("--output_manifest")
        .addOutputArgument(stubManifest);

    ruleContext.registerAction(builder.build(ruleContext));

    return new ApplicationManifest(ruleContext, stubManifest);
  }

  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);
  }

  /**
   * Generates an empty manifest for a rule that does not directly specify resources.
   *
   * <p><strong>Note:</strong> This generated manifest can then be used as the primary manifest
   * when merging with dependencies.
   *
   * @return the generated ApplicationManifest
   */
  public static ApplicationManifest generatedManifest(RuleContext ruleContext) {
    Artifact generatedManifest = ruleContext.getUniqueDirectoryArtifact(
        ruleContext.getRule().getName() + "_generated", new PathFragment("AndroidManifest.xml"),
        ruleContext.getBinOrGenfilesDirectory());

    String manifestPackage = AndroidCommon.getJavaPackage(ruleContext);
    String contents = Joiner.on("\n").join(
        "<?xml version=\"1.0\" encoding=\"utf-8\"?>",
        "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"",
        "          package=\"" + manifestPackage + "\">",
        "   <application>",
        "   </application>",
        "</manifest>");
    ruleContext.getAnalysisEnvironment().registerAction(new FileWriteAction(
        ruleContext.getActionOwner(), generatedManifest, contents, false /* makeExecutable */));
    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(RuleContext ruleContext, Artifact manifest) {
    this.manifest = manifest;
    this.manifestValues = getManifestValues(ruleContext);
  }

  public ApplicationManifest mergeWith(RuleContext ruleContext, ResourceDependencies resourceDeps) {
    Map<Artifact, Label> mergeeManifests = getMergeeManifests(resourceDeps.getResources());

    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 (!mergeeManifests.isEmpty()) {
        Artifact outputManifest = ruleContext.getUniqueDirectoryArtifact(
            ruleContext.getRule().getName() + "_merged", "AndroidManifest.xml",
            ruleContext.getBinOrGenfilesDirectory());
        AndroidManifestMergeHelper.createMergeManifestAction(ruleContext, getManifest(),
            mergeeManifests.keySet(), ImmutableList.of("all"), outputManifest);
        return new ApplicationManifest(ruleContext, outputManifest);
      }
    } else {
      if (!mergeeManifests.isEmpty() || !manifestValues.isEmpty()) {
        Artifact outputManifest = ruleContext.getUniqueDirectoryArtifact(
            ruleContext.getRule().getName() + "_merged", "AndroidManifest.xml",
            ruleContext.getBinOrGenfilesDirectory());
        Artifact mergeLog = ruleContext.getUniqueDirectoryArtifact(
            ruleContext.getRule().getName() + "_merged", "manifest_merger_log.txt",
            ruleContext.getBinOrGenfilesDirectory());
        new ManifestMergerActionBuilder(ruleContext)
            .setManifest(getManifest())
            .setMergeeManifests(mergeeManifests)
            .setLibrary(false)
            .setManifestValues(manifestValues)
            .setCustomPackage(AndroidCommon.getJavaPackage(ruleContext))
            .setManifestOutput(outputManifest)
            .setLogOut(mergeLog)
            .build(ruleContext);
        return new ApplicationManifest(ruleContext, outputManifest);
      }
    }
    return this;
  }

  private static Map<Artifact, Label> getMergeeManifests(
      Iterable<ResourceContainer> resourceContainers) {
    ImmutableSortedMap.Builder<Artifact, Label> builder =
        ImmutableSortedMap.orderedBy(Artifact.EXEC_PATH_COMPARATOR);
    for (ResourceContainer r : resourceContainers) {
      if (r.isManifestExported()) {
        builder.put(r.getManifest(), r.getLabel());
      }
    }
    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(
      Artifact resourceApk,
      RuleContext ruleContext,
      ResourceDependencies resourceDeps,
      Artifact rTxt,
      boolean incremental,
      Artifact proguardCfg) throws InterruptedException {
    LocalResourceContainer data = new LocalResourceContainer.Builder(ruleContext)
        .withAssets(
            AndroidCommon.getAssetDir(ruleContext),
            ruleContext.getPrerequisitesIf(
                // TODO(bazel-team): Remove the ResourceType construct.
                ResourceType.ASSETS.getAttribute(),
                Mode.TARGET,
                FileProvider.class)).build();

    return createApk(
        ruleContext,
        false, /* isLibrary */
        resourceDeps,
        ImmutableList.<String>of(), /* configurationFilters */
        ImmutableList.<String>of(), /* uncompressedExtensions */
        true, /* crunchPng */
        ImmutableList.<String>of(), /* densities */
        incremental,
        new AndroidResourceContainerBuilder()
            .withData(data)
            .withManifest(getManifest())
            .withROutput(rTxt)
            .buildFromRule(ruleContext, resourceApk),
        data,
        proguardCfg,
        null, /* Artifact mainDexProguardCfg */
        null, /* Artifact manifestOut */
        null /* Artifact mergedResources */,
        null /* Artifact dataBindingInfoZip */);
  }

  /** Packages up the manifest with resource and assets from the LocalResourceContainer. */
  public ResourceApk packWithDataAndResources(
      RuleContext ruleContext,
      LocalResourceContainer data,
      ResourceDependencies resourceDeps,
      Artifact rTxt,
      Artifact symbolsTxt,
      Artifact manifestOut,
      Artifact mergedResources,
      boolean alwaysExportManifest) throws InterruptedException {
    if (ruleContext.hasErrors()) {
      return null;
    }
    return createApk(
        ruleContext,
        true, /* isLibrary */
        resourceDeps,
        ImmutableList.<String>of(), /* List<String> configurationFilters */
        ImmutableList.<String>of(), /* List<String> uncompressedExtensions */
        false, /* crunchPng */
        ImmutableList.<String>of(), /* List<String> densities */
        false, /* incremental */
        new AndroidResourceContainerBuilder()
            .withData(data)
            .withManifest(getManifest())
            .withROutput(rTxt)
            .withSymbolsFile(symbolsTxt)
            .useJavaPackageFromManifest(true)
            .buildFromRule(ruleContext, null, alwaysExportManifest),
        data,
        null, /* Artifact proguardCfg */
        null, /* Artifact mainDexProguardCfg */
        manifestOut,
        mergedResources,
        null /* Artifact dataBindingInfoZip */);
  }

  /** Packages up the manifest with resource and assets from the rule and dependent resources. */
  public ResourceApk packWithDataAndResources(
      @Nullable Artifact resourceApk,
      RuleContext ruleContext,
      boolean isLibrary,
      ResourceDependencies resourceDeps,
      Artifact rTxt,
      Artifact symbolsTxt,
      List<String> configurationFilters,
      List<String> uncompressedExtensions,
      boolean crunchPng,
      List<String> densities,
      boolean incremental,
      Artifact proguardCfg,
      @Nullable Artifact mainDexProguardCfg,
      Artifact manifestOut,
      Artifact mergedResources,
      Artifact dataBindingInfoZip) throws InterruptedException {
    LocalResourceContainer data = new LocalResourceContainer.Builder(ruleContext)
        .withAssets(
            AndroidCommon.getAssetDir(ruleContext),
            ruleContext.getPrerequisitesIf(
                // TODO(bazel-team): Remove the ResourceType construct.
                ResourceType.ASSETS.getAttribute(),
                Mode.TARGET,
                FileProvider.class))
        .withResources(
            ruleContext.getPrerequisites(
                "resource_files",
                Mode.TARGET,
                FileProvider.class)).build();
    if (ruleContext.hasErrors()) {
      return null;
    }
    return createApk(
        ruleContext,
        isLibrary,
        resourceDeps,
        configurationFilters,
        uncompressedExtensions,
        crunchPng,
        densities,
        incremental,
        new AndroidResourceContainerBuilder()
            .withData(data)
            .withManifest(getManifest())
            .withROutput(rTxt)
            .withSymbolsFile(symbolsTxt)
            .buildFromRule(ruleContext, resourceApk),
        data,
        proguardCfg,
        mainDexProguardCfg,
        manifestOut,
        mergedResources, dataBindingInfoZip);
  }

  private ResourceApk createApk(
      RuleContext ruleContext,
      boolean isLibrary,
      ResourceDependencies resourceDeps,
      List<String> configurationFilters,
      List<String> uncompressedExtensions,
      boolean crunchPng,
      List<String> densities,
      boolean incremental,
      ResourceContainer maybeInlinedResourceContainer,
      LocalResourceContainer data,
      Artifact proguardCfg,
      @Nullable Artifact mainDexProguardCfg,
      Artifact manifestOut,
      Artifact mergedResources,
      Artifact dataBindingInfoZip) throws InterruptedException {
    ResourceContainer resourceContainer = checkForInlinedResources(
        maybeInlinedResourceContainer,
        resourceDeps.getResources(),  // TODO(bazel-team): Figure out if we really need to check
        // the ENTIRE transitive closure, or just the direct dependencies. Given that each rule with
        // resources would check for inline resources, we can rely on the previous rule to have
        // checked its dependencies.
        ruleContext);
    if (ruleContext.hasErrors()) {
      return null;
    }

    ResourceContainer processed;
    if (isLibrary && AndroidCommon.getAndroidConfig(ruleContext).useParallelResourceProcessing()) {
      // android_library should only build the APK one way (!incremental).
      Preconditions.checkArgument(!incremental);
      Artifact rJavaClassJar = ruleContext.getImplicitOutputArtifact(
          AndroidRuleClasses.ANDROID_RESOURCES_CLASS_JAR);

      if (resourceContainer.getSymbolsTxt() != null) {
        new AndroidResourceParsingActionBuilder(ruleContext)
            .setParse(data)
            .setOutput(resourceContainer.getSymbolsTxt())
            .build(ruleContext);
      }

      AndroidResourceMergingActionBuilder resourcesMergerBuilder =
          new AndroidResourceMergingActionBuilder(ruleContext)
              .setJavaPackage(resourceContainer.getJavaPackage())
              .withPrimary(resourceContainer)
              .withDependencies(resourceDeps)
              .setMergedResourcesOut(mergedResources)
              .setManifestOut(manifestOut)
              .setClassJarOut(rJavaClassJar);
      ResourceContainer merged = resourcesMergerBuilder.build(ruleContext);

      AndroidResourceValidatorActionBuilder validatorBuilder =
          new AndroidResourceValidatorActionBuilder(ruleContext)
              .setJavaPackage(merged.getJavaPackage())
              .setDebug(
                  ruleContext.getConfiguration().getCompilationMode() != CompilationMode.OPT)
              .setMergedResources(mergedResources)
              .withPrimary(merged)
              .setSourceJarOut(merged.getJavaSourceJar())
              .setRTxtOut(merged.getRTxt());
      processed = validatorBuilder.build(ruleContext);
    } else {
      AndroidResourcesProcessorBuilder builder =
          new AndroidResourcesProcessorBuilder(ruleContext)
              .setLibrary(isLibrary)
              .setApkOut(resourceContainer.getApk())
              .setConfigurationFilters(configurationFilters)
              .setUncompressedExtensions(uncompressedExtensions)
              .setCrunchPng(crunchPng)
              .setJavaPackage(resourceContainer.getJavaPackage())
              .setDebug(ruleContext.getConfiguration().getCompilationMode() != CompilationMode.OPT)
              .setManifestOut(manifestOut)
              .setMergedResourcesOut(mergedResources)
              .withPrimary(resourceContainer)
              .withDependencies(resourceDeps)
              .setDensities(densities)
              .setProguardOut(proguardCfg)
              .setMainDexProguardOut(mainDexProguardCfg)
              .setDataBindingInfoZip(dataBindingInfoZip)
              .setApplicationId(manifestValues.get("applicationId"))
              .setVersionCode(manifestValues.get("versionCode"))
              .setVersionName(manifestValues.get("versionName"));
      if (!incremental) {
        builder
            .setRTxtOut(resourceContainer.getRTxt())
            .setSymbolsTxt(resourceContainer.getSymbolsTxt())
            .setSourceJarOut(resourceContainer.getJavaSourceJar());
      }
      processed = builder.build(ruleContext);
    }

    return new ResourceApk(
        resourceContainer.getApk(), processed.getJavaSourceJar(), processed.getJavaClassJar(),
        resourceDeps, processed, processed.getManifest(),
        proguardCfg, mainDexProguardCfg, false);
  }

  private static ResourceContainer checkForInlinedResources(ResourceContainer resourceContainer,
      Iterable<ResourceContainer> resourceContainers, RuleContext ruleContext) {
    // Dealing with Android library projects
    if (Iterables.size(resourceContainers) > 1) {
      if (resourceContainer.getConstantsInlined()
          && !resourceContainer.getArtifacts(ResourceType.RESOURCES).isEmpty()) {
        ruleContext.ruleError("This android binary depends on an android "
            + "library project, so the resources '"
            + AndroidCommon.getAndroidResources(ruleContext).getLabel()
            + "' should have the attribute inline_constants set to 0");
        return null;
      }
    }
    return resourceContainer;
  }

  /** Uses the resource apk from the resources attribute, as opposed to recompiling. */
  public ResourceApk useCurrentResources(
      RuleContext ruleContext, Artifact proguardCfg, @Nullable Artifact mainDexProguardCfg) {
    ResourceContainer resourceContainer = Iterables.getOnlyElement(
        AndroidCommon.getAndroidResources(ruleContext).getDirectAndroidResources());

    new AndroidAaptActionHelper(
        ruleContext,
        resourceContainer.getManifest(),
        Lists.newArrayList(resourceContainer))
        .createGenerateProguardAction(proguardCfg, mainDexProguardCfg);

    return new ResourceApk(
        resourceContainer.getApk(),
        null /* javaSrcJar */,
        null /* javaClassJar */,
        ResourceDependencies.empty(),
        resourceContainer,
        manifest,
        proguardCfg,
        mainDexProguardCfg,
        false);
  }

  /**
   * Packages up the manifest with resources, and generates the R.java.
   * @throws InterruptedException
   *
   * @deprecated in favor of {@link ApplicationManifest#packWithDataAndResources}.
   */
  @Deprecated
  public ResourceApk packWithResources(
      Artifact resourceApk,
      RuleContext ruleContext,
      ResourceDependencies resourceDeps,
      boolean createSource,
      Artifact proguardCfg,
      @Nullable Artifact mainDexProguardCfg) throws InterruptedException {

    TransitiveInfoCollection resourcesPrerequisite =
        ruleContext.getPrerequisite("resources", Mode.TARGET);
    ResourceContainer resourceContainer = Iterables.getOnlyElement(
        resourcesPrerequisite.getProvider(AndroidResourcesProvider.class)
        .getDirectAndroidResources());
    // It's ugly, but flattening now is more performant given the rest of the checks.
    List<ResourceContainer> resourceContainers =
        ImmutableList.<ResourceContainer>builder()
        //.add(resourceContainer)
        .addAll(resourceDeps.getResources()).build();

    // Dealing with Android library projects
    if (Iterables.size(resourceDeps.getResources()) > 1) {
      if (resourceContainer.getConstantsInlined()
          && !resourceContainer.getArtifacts(ResourceType.RESOURCES).isEmpty()) {
        ruleContext.ruleError("This android_binary depends on an android_library, so the"
            + " resources '" + AndroidCommon.getAndroidResources(ruleContext).getLabel()
            + "' should have the attribute inline_constants set to 0");
        return null;
      }
    }

    // This binary depends on a library project, so we need to regenerate the
    // resources. The resulting sources and apk will combine all the resources
    // contained in the transitive closure of the binary.
    AndroidAaptActionHelper aaptActionHelper = new AndroidAaptActionHelper(ruleContext,
        getManifest(), Lists.newArrayList(resourceContainers));

    List<String> resourceConfigurationFilters =
        ruleContext.getTokenizedStringListAttr("resource_configuration_filters");
    List<String> uncompressedExtensions =
        ruleContext.getTokenizedStringListAttr("nocompress_extensions");

    ImmutableList.Builder<String> additionalAaptOpts = ImmutableList.<String>builder();

    for (String extension : uncompressedExtensions) {
      additionalAaptOpts.add("-0").add(extension);
    }
    if (!resourceConfigurationFilters.isEmpty()) {
      additionalAaptOpts.add("-c").add(Joiner.on(",").join(resourceConfigurationFilters));
    }

    Artifact javaSourcesJar = null;

    if (createSource) {
      javaSourcesJar =
        ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_JAVA_SOURCE_JAR);
      aaptActionHelper.createGenerateResourceSymbolsAction(
          javaSourcesJar, null, resourceContainer.getJavaPackage(), true);
    }

    List<String> densities = ruleContext.getTokenizedStringListAttr("densities");
    aaptActionHelper.createGenerateApkAction(resourceApk,
        resourceContainer.getRenameManifestPackage(), additionalAaptOpts.build(), densities);

    ResourceContainer updatedResources = ResourceContainer.create(
        ruleContext.getLabel(),
        resourceContainer.getJavaPackage(),
        resourceContainer.getRenameManifestPackage(),
        resourceContainer.getConstantsInlined(),
        resourceApk,
        getManifest(),
        javaSourcesJar,
        null, /* javaClassJar */
        resourceContainer.getArtifacts(ResourceType.ASSETS),
        resourceContainer.getArtifacts(ResourceType.RESOURCES),
        resourceContainer.getRoots(ResourceType.ASSETS),
        resourceContainer.getRoots(ResourceType.RESOURCES),
        resourceContainer.isManifestExported(),
        resourceContainer.getRTxt(), null);

    aaptActionHelper.createGenerateProguardAction(proguardCfg, mainDexProguardCfg);

    return new ResourceApk(resourceApk,
        updatedResources.getJavaSourceJar(),
        updatedResources.getJavaClassJar(),
        resourceDeps, updatedResources, manifest, proguardCfg, mainDexProguardCfg, true);
  }

  public Artifact getManifest() {
    return manifest;
  }
}
