Null out names of unused resources in optimized shrinking

These are not needed when the resource can no longer be looked up.

Bug: 287398085
Change-Id: Ibb13cb1dbe6da8bc90f5aae374bcb469fbc2e0c2
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/ResourceTableUtil.kt b/src/resourceshrinker/java/com/android/build/shrinker/ResourceTableUtil.kt
index 4327997..88bb5e4 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/ResourceTableUtil.kt
+++ b/src/resourceshrinker/java/com/android/build/shrinker/ResourceTableUtil.kt
@@ -33,20 +33,28 @@
 
 internal fun Resources.ResourceTable.nullOutEntriesWithIds(ids: List<Int>)
     : Resources.ResourceTable {
+    return nullOutEntriesWithIds(ids, false)
+}
+
+internal fun Resources.ResourceTable.nullOutEntriesWithIds(
+    ids: List<Int>, pruneResourceNames: Boolean): Resources.ResourceTable {
     if (ids.isEmpty()) {
         return this
     }
     val packageMappings = calculatePackageMappings(ids)
     val tableBuilder = this.toBuilder()
-    tableBuilder.packageBuilderList.forEach{
+    tableBuilder.packageBuilderList.forEach {
         val typeMappings = packageMappings[it.packageId.id]
         if (typeMappings != null) {
-            it.typeBuilderList.forEach { type->
+            it.typeBuilderList.forEach { type ->
                 val entryList = typeMappings[type.typeId.id]
                 if (entryList != null) {
                     type.entryBuilderList.forEach { entry ->
                         if (entryList.contains(entry.entryId.id)) {
                             entry.clearConfigValue()
+                            if (pruneResourceNames) {
+                                entry.clearName();
+                            }
                             if (entry.hasOverlayableItem()) {
                                 entry.clearOverlayableItem()
                             }
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
index 11bb767..29a3399 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
@@ -177,7 +177,8 @@
         (resourceTable, featureSplit) ->
             shrunkenTables.put(
                 featureSplit,
-                ResourceTableUtilKt.nullOutEntriesWithIds(resourceTable, resourceIdsToRemove)));
+                ResourceTableUtilKt.nullOutEntriesWithIds(
+                    resourceTable, resourceIdsToRemove, true)));
 
     return new ShrinkerResult(resEntriesToKeep, shrunkenTables);
   }
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index 8237f9b..7454ce3 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -9,6 +9,7 @@
 import static org.junit.Assert.assertNotNull;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils;
 import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.ResourceTableInspector;
 import com.android.tools.r8.dexsplitter.SplitterTestBase.SplitRunner;
 import com.android.tools.r8.profile.art.model.ExternalArtProfile;
@@ -173,6 +174,12 @@
     return self();
   }
 
+  public String dumpResources() throws IOException {
+    ProcessResult processResult = AndroidResourceTestingUtils.dumpWithAapt2(resourceShrinkerOutput);
+    assert processResult.exitCode == 0;
+    return processResult.stdout;
+  }
+
   public <E extends Throwable> R8TestCompileResult inspectShrunkenResourcesForFeature(
       Consumer<ResourceTableInspector> consumer, String featureName) throws IOException {
     Path path = resourceShrinkerOutputForFeatures.get(featureName);
diff --git a/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java b/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
index b4a170a..df7461e 100644
--- a/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
+++ b/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
@@ -532,8 +532,12 @@
     }
   }
 
-  public static void dumpWithAapt2(Path path) throws IOException {
-    System.out.println(ToolHelper.runAapt2("dump", "resources", path.toString()));
+  public static ProcessResult dumpWithAapt2(Path path) throws IOException {
+    return ToolHelper.runAapt2("dump", "resources", path.toString());
+  }
+
+  public static void dumpWithAapt2ToStdout(Path path) throws IOException {
+    System.out.println(dumpWithAapt2(path));
   }
 
   public static void compileWithAapt2(
diff --git a/src/test/java/com/android/tools/r8/androidresources/TestNameRemovalInResourceTable.java b/src/test/java/com/android/tools/r8/androidresources/TestNameRemovalInResourceTable.java
new file mode 100644
index 0000000..b9d73a0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/TestNameRemovalInResourceTable.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.androidresources;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TestNameRemovalInResourceTable extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean optimized;
+
+  @Parameters(name = "{0}, optimized: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDefaultDexRuntime().withAllApiLevels().build(),
+        BooleanUtils.values());
+  }
+
+  public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+    return new AndroidTestResourceBuilder()
+        .withSimpleManifestAndAppNameString()
+        .addRClassInitializeWithDefaultValues(R.string.class, R.drawable.class)
+        .build(temp);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8TestCompileResult r8TestCompileResult =
+        testForR8(parameters.getBackend())
+            .setMinApi(parameters)
+            .addProgramClasses(FooBar.class)
+            .addAndroidResources(getTestResources(temp))
+            .applyIf(optimized, R8TestBuilder::enableOptimizedShrinking)
+            .addKeepMainRule(FooBar.class)
+            .compile();
+    r8TestCompileResult
+        .inspectShrunkenResources(
+            resourceTableInspector -> {
+              resourceTableInspector.assertContainsResourceWithName("string", "bar");
+              resourceTableInspector.assertContainsResourceWithName("string", "foo");
+              resourceTableInspector.assertContainsResourceWithName("drawable", "foobar");
+              resourceTableInspector.assertDoesNotContainResourceWithName(
+                  "string", "unused_string");
+              resourceTableInspector.assertDoesNotContainResourceWithName(
+                  "drawable", "unused_drawable");
+            })
+        .run(parameters.getRuntime(), FooBar.class)
+        .assertSuccess();
+    String dumpResources = r8TestCompileResult.dumpResources();
+    if (optimized) {
+      assertFalse(dumpResources.contains("unused_string"));
+      assertFalse(dumpResources.contains("unused_drawable"));
+    } else {
+      assertTrue(dumpResources.contains("unused_string"));
+      assertTrue(dumpResources.contains("unused_drawable"));
+    }
+  }
+
+  public static class FooBar {
+
+    public static void main(String[] args) {
+      if (System.currentTimeMillis() == 0) {
+        System.out.println(R.drawable.foobar);
+        System.out.println(R.string.bar);
+        System.out.println(R.string.foo);
+      }
+    }
+  }
+
+  public static class R {
+
+    public static class string {
+
+      public static int bar;
+      public static int foo;
+      public static int unused_string;
+    }
+
+    public static class drawable {
+
+      public static int foobar;
+      public static int unused_drawable;
+    }
+  }
+}