Add support for empty dependency lists in private resource validation

Previously, in the private resource validation, there was no differentiation
between an empty list (no private resources) and null (private resource flag
ommitted) in the input, so the optimization was not run on a target with zero
private resource dependencies. Add logic to differentiate between an empty list
and null, mainly using `omit_if_empty = False` in the bzl.

Also, enable private resource validation optimization for android_application

PiperOrigin-RevId: 976362556
Change-Id: I39df29c8f7f307d623f499dbdf6f5fa37e4ddb15
diff --git a/rules/android_application/android_application_rule.bzl b/rules/android_application/android_application_rule.bzl
index 76fc8b8..8f7b764 100644
--- a/rules/android_application/android_application_rule.bzl
+++ b/rules/android_application/android_application_rule.bzl
@@ -133,6 +133,7 @@
         transitive_compiled_assets = [res.transitive_compiled_assets],
         transitive_resource_files = [res.transitive_resource_files],
         transitive_compiled_resources = [res.transitive_compiled_resources],
+        transitive_compiled_resources_with_public_xml = [res.transitive_compiled_resources_with_public_xml],
         transitive_r_txts = [res.transitive_r_txts],
         additional_apks_to_link_against = [base_apk],
         proto_format = True,  # required for aab.
diff --git a/rules/busybox.bzl b/rules/busybox.bzl
index b618ace..41c3e49 100644
--- a/rules/busybox.bzl
+++ b/rules/busybox.bzl
@@ -353,13 +353,15 @@
     transitive_input_files.extend(transitive_assets)
     transitive_input_files.extend(transitive_compiled_assets)
     transitive_input_files.extend(transitive_compiled_resources)
-    if transitive_compiled_resources_with_public_xml:
-        compiled_dep_with_public_xml = depset(transitive = transitive_compiled_resources_with_public_xml)
-        args.add_joined(
-            "--compiledDepWithPublicXml",
-            compiled_dep_with_public_xml,
-            join_with = ":",
-        )
+    compiled_dep_with_public_xml = depset(transitive = transitive_compiled_resources_with_public_xml)
+
+    # An empty list explicitly indicates that there are no public resources, causing visibility validation to be skipped.
+    args.add_joined(
+        "--compiledDepWithPublicXml",
+        compiled_dep_with_public_xml,
+        join_with = ":",
+        omit_if_empty = False,
+    )
     transitive_input_files.extend(transitive_manifests)
     transitive_input_files.extend(transitive_r_txts)
     args.add(
@@ -652,12 +654,14 @@
         join_with = ":",
     )
     transitive_input_files.append(transitive_compiled_resources)
-    if transitive_compiled_resources_with_public_xml:
-        args.add_joined(
-            "--compiledDepWithPublicXml",
-            transitive_compiled_resources_with_public_xml,
-            join_with = ":",
-        )
+
+    # An empty list explicitly indicates that there are no public resources, causing visibility validation to be skipped.
+    args.add_joined(
+        "--compiledDepWithPublicXml",
+        transitive_compiled_resources_with_public_xml,
+        join_with = ":",
+        omit_if_empty = False,
+    )
     args.add("--manifest", manifest)
     input_files.append(manifest)
     if java_package:
diff --git a/src/tools/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java b/src/tools/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java
index 982e0f7..3d4c4e6 100644
--- a/src/tools/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java
+++ b/src/tools/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java
@@ -136,7 +136,7 @@
         names = "--compiledDepWithPublicXml",
         listConverter = CompatPathListConverter.class,
         description = "Compiled resource dependencies containing explicit public.xml declarations.")
-    public List<Path> compiledDepsWithPublicXml = ImmutableList.of();
+    public List<Path> compiledDepsWithPublicXml;
 
     @Parameter(
         names = "--packageId",
@@ -434,7 +434,7 @@
       }
 
       ImmutableList<CompiledResources> visibilityDeps =
-          !options.compiledDepsWithPublicXml.isEmpty()
+          options.compiledDepsWithPublicXml != null
               ? options.compiledDepsWithPublicXml.stream()
                   .map(CompiledResources::from)
                   .collect(toImmutableList())
diff --git a/src/tools/java/com/google/devtools/build/android/ValidateAndLinkResourcesAction.java b/src/tools/java/com/google/devtools/build/android/ValidateAndLinkResourcesAction.java
index 4923545..c7161aa 100644
--- a/src/tools/java/com/google/devtools/build/android/ValidateAndLinkResourcesAction.java
+++ b/src/tools/java/com/google/devtools/build/android/ValidateAndLinkResourcesAction.java
@@ -62,7 +62,7 @@
         names = "--compiledDepWithPublicXml",
         listConverter = Converters.CompatPathListConverter.class,
         description = "Compiled resource dependencies containing explicit public.xml declarations.")
-    public List<Path> compiledDepsWithPublicXml = ImmutableList.of();
+    public List<Path> compiledDepsWithPublicXml;
 
     /**
      * TODO(b/64570523): Still used by blaze. Will be removed as part of the command line cleanup.
@@ -158,7 +158,7 @@
       ImmutableList<CompiledResources> includes =
           options.compiledDeps.stream().map(CompiledResources::from).collect(toImmutableList());
       ImmutableList<CompiledResources> visibilityIncludes =
-          !options.compiledDepsWithPublicXml.isEmpty()
+          options.compiledDepsWithPublicXml != null
               ? options.compiledDepsWithPublicXml.stream()
                   .map(CompiledResources::from)
                   .collect(toImmutableList())
diff --git a/src/tools/javatests/com/google/devtools/build/android/ValidateAndLinkResourcesActionTest.java b/src/tools/javatests/com/google/devtools/build/android/ValidateAndLinkResourcesActionTest.java
index 99f66de..aae3126 100644
--- a/src/tools/javatests/com/google/devtools/build/android/ValidateAndLinkResourcesActionTest.java
+++ b/src/tools/javatests/com/google/devtools/build/android/ValidateAndLinkResourcesActionTest.java
@@ -285,6 +285,66 @@
   }
 
   @Test
+  public void visibilityCheck_emptyVisibilityDepsSkipsCheck() throws Exception {
+    Map<String, String> libFiles = new HashMap<>();
+    libFiles.put(
+        "values/values.xml",
+        "<resources><string name=\"lib_string\">@string/private_string</string></resources>");
+
+    CompiledResources lib =
+        createCompiledResources("lib", libFiles, "<manifest package=\"com.lib\"/>");
+
+    // When visibilityDeps is empty, checkVisibilityOfResourceReferences should succeed without
+    // throwing
+    ValidateAndLinkResourcesAction.checkVisibilityOfResourceReferences(
+        XmlUtils.getAllResourceReferences(XmlNode.getDefaultInstance()), lib, ImmutableList.of());
+  }
+
+  @Test
+  public void testCompiledDepWithPublicXml_omittedFallsBackToCompiledDeps() throws Exception {
+    Map<String, String> depFiles = new HashMap<>();
+    depFiles.put(
+        "values/public.xml",
+        "<resources><public name=\"public_string\" type=\"string\"/></resources>");
+    depFiles.put(
+        "values/strings.xml",
+        "<resources><string name=\"private_string\">hello</string></resources>");
+
+    CompiledResources dep =
+        createCompiledResources("dep", depFiles, "<manifest package=\"com.dep\"/>");
+
+    Map<String, String> libFiles = new HashMap<>();
+    libFiles.put(
+        "values/values.xml",
+        "<resources><string name=\"lib_string\">@string/private_string</string></resources>");
+
+    CompiledResources lib =
+        createCompiledResources("lib", libFiles, "<manifest package=\"com.lib\"/>");
+
+    Path outLib = tempDir.resolve("lib.apk");
+    Path outSrcJar = tempDir.resolve("r.srcjar");
+    Path outRTxt = tempDir.resolve("R.txt");
+
+    String[] args =
+        new String[] {
+          "--aapt2",
+          aapt2.toString(),
+          "--resources",
+          lib.getZip().toString() + ":" + lib.getManifest().toString(),
+          "--compiledDep",
+          dep.getZip().toString(),
+          "--staticLibraryOut",
+          outLib.toString(),
+          "--sourceJarOut",
+          outSrcJar.toString(),
+          "--rTxtOut",
+          outRTxt.toString()
+        };
+
+    assertThrows(UserException.class, () -> ValidateAndLinkResourcesAction.main(args));
+  }
+
+  @Test
   public void testCheckVisibilityOfResourceReferences_directoryWithoutPublicNotPrivate()
       throws Exception {
     Map<String, String> depFiles = new HashMap<>();
diff --git a/test/rules/resources/BUILD b/test/rules/resources/BUILD
index ed0e5d2..ffeb951 100644
--- a/test/rules/resources/BUILD
+++ b/test/rules/resources/BUILD
@@ -1306,6 +1306,8 @@
                 "bazel-out/k8-fastbuild/bin/test/rules/resources/manifest_without_resources_symbols/R.aapt2.txt",
                 "bazel-out/k8-fastbuild/bin/test/rules/resources/manifest_without_resources_symbols/symbols.zip",
             ]),
+            "--compiledDepWithPublicXml",
+            "",
             "--primaryData",
             ":".join([
                 "",