Add plumbing to allow querying all resources referenced from (protobuf-)XML

These are needed to validate resource references, i.e. references should be to
internal definitions or public resources from direct dependencies.

This entails adding a field for the protos to android/xml/*XmlResourceValue,
which is technically redundant to the existing strings/maps.  The latter can
be removed once we stop parsing XML---presently the XML parsing is only done
in AAR generation for the benefit of merging res/ directories.

PiperOrigin-RevId: 289551860
diff --git a/src/test/java/com/google/devtools/build/android/DataValueFileTest.java b/src/test/java/com/google/devtools/build/android/DataValueFileTest.java
index 0b22e6c..28479c1 100644
--- a/src/test/java/com/google/devtools/build/android/DataValueFileTest.java
+++ b/src/test/java/com/google/devtools/build/android/DataValueFileTest.java
@@ -35,21 +35,24 @@
         DataValueFile.of(
             Visibility.UNKNOWN,
             DataSource.of(DependencyInfo.UNKNOWN, fs.getPath("val1")),
-            /*fingerprint=*/ null);
+            /*fingerprint=*/ null,
+            /*rootXmlNode=*/ null);
     DataValueFile val2a =
         DataValueFile.of(
             Visibility.UNKNOWN,
             DataSource.of(
                 DependencyInfo.create("lib2a", DependencyInfo.DependencyType.UNKNOWN),
                 fs.getPath("val2")),
-            /*fingerprint=*/ null);
+            /*fingerprint=*/ null,
+            /*rootXmlNode=*/ null);
     DataValueFile val2b =
         DataValueFile.of(
             Visibility.UNKNOWN,
             DataSource.of(
                 DependencyInfo.create("lib2b", DependencyInfo.DependencyType.UNKNOWN),
                 fs.getPath("val2")),
-            /*fingerprint=*/ null);
+            /*fingerprint=*/ null,
+            /*rootXmlNode=*/ null);
 
     assertThat(val1.valueEquals(val2a)).isFalse();
     assertThat(val2a.valueEquals(val2b)).isTrue();
@@ -61,17 +64,20 @@
         DataValueFile.of(
             Visibility.UNKNOWN,
             DataSource.of(DependencyInfo.UNKNOWN, fs.getPath("asdf")),
-            HashCode.fromInt(1));
+            HashCode.fromInt(1),
+            /*rootXmlNode=*/ null);
     DataValueFile val2a =
         DataValueFile.of(
             Visibility.UNKNOWN,
             DataSource.of(DependencyInfo.UNKNOWN, fs.getPath("qwerty")),
-            HashCode.fromInt(2));
+            HashCode.fromInt(2),
+            /*rootXmlNode=*/ null);
     DataValueFile val2b =
         DataValueFile.of(
             Visibility.UNKNOWN,
             DataSource.of(DependencyInfo.UNKNOWN, fs.getPath("hunter2")),
-            HashCode.fromInt(2));
+            HashCode.fromInt(2),
+            /*rootXmlNode=*/ null);
 
     assertThat(val1.valueEquals(val2a)).isFalse();
     assertThat(val2a.valueEquals(val2b)).isTrue();
@@ -80,9 +86,15 @@
   @Test
   public void valueEquals_checkVisibility() throws Exception {
     DataSource dataSource = DataSource.of(DependencyInfo.UNKNOWN, fs.getPath("x"));
-    DataValueFile val1 = DataValueFile.of(Visibility.PRIVATE, dataSource, /*fingerprint=*/ null);
-    DataValueFile val2a = DataValueFile.of(Visibility.PUBLIC, dataSource, /*fingerprint=*/ null);
-    DataValueFile val2b = DataValueFile.of(Visibility.PUBLIC, dataSource, /*fingerprint=*/ null);
+    DataValueFile val1 =
+        DataValueFile.of(
+            Visibility.PRIVATE, dataSource, /*fingerprint=*/ null, /*rootXmlNode=*/ null);
+    DataValueFile val2a =
+        DataValueFile.of(
+            Visibility.PUBLIC, dataSource, /*fingerprint=*/ null, /*rootXmlNode=*/ null);
+    DataValueFile val2b =
+        DataValueFile.of(
+            Visibility.PUBLIC, dataSource, /*fingerprint=*/ null, /*rootXmlNode=*/ null);
 
     assertThat(val1.valueEquals(val2a)).isFalse();
     assertThat(val2a.valueEquals(val2b)).isTrue();
diff --git a/src/test/java/com/google/devtools/build/android/ParsedAndroidDataBuilder.java b/src/test/java/com/google/devtools/build/android/ParsedAndroidDataBuilder.java
index 35c5b9b..62689ff 100644
--- a/src/test/java/com/google/devtools/build/android/ParsedAndroidDataBuilder.java
+++ b/src/test/java/com/google/devtools/build/android/ParsedAndroidDataBuilder.java
@@ -151,7 +151,8 @@
             KeyValueConsumer<DataKey, DataResource> consumer) {
           consumer.accept(
               factory.parse(rawKey),
-              DataValueFile.of(Visibility.UNKNOWN, source, /*fingerprint=*/ null));
+              DataValueFile.of(
+                  Visibility.UNKNOWN, source, /*fingerprint=*/ null, /*rootXmlNode=*/ null));
         }
 
         @Override
@@ -159,7 +160,8 @@
           target.accept(
               RelativeAssetPath.Factory.of(chooseRoot(defaultRoot).resolve("assets"))
                   .create(source.getPath()),
-              DataValueFile.of(Visibility.UNKNOWN, source, /*fingerprint=*/ null));
+              DataValueFile.of(
+                  Visibility.UNKNOWN, source, /*fingerprint=*/ null, /*rootXmlNode=*/ null));
         }
       };
     }
diff --git a/src/test/java/com/google/devtools/build/android/ParsedAndroidDataTest.java b/src/test/java/com/google/devtools/build/android/ParsedAndroidDataTest.java
index 0e30e05..b03ba57 100644
--- a/src/test/java/com/google/devtools/build/android/ParsedAndroidDataTest.java
+++ b/src/test/java/com/google/devtools/build/android/ParsedAndroidDataTest.java
@@ -101,9 +101,16 @@
                 ImmutableSet.of(
                     MergeConflict.of(
                         key,
-                        DataValueFile.of(Visibility.UNKNOWN, assetSource, /*fingerprint=*/ null),
                         DataValueFile.of(
-                            Visibility.UNKNOWN, otherAssetSource, /*fingerprint=*/ null))),
+                            Visibility.UNKNOWN,
+                            assetSource,
+                            /*fingerprint=*/ null,
+                            /*rootXmlNode=*/ null),
+                        DataValueFile.of(
+                            Visibility.UNKNOWN,
+                            otherAssetSource,
+                            /*fingerprint=*/ null,
+                            /*rootXmlNode=*/ null))),
                 ImmutableMap.<DataKey, DataResource>of(),
                 ImmutableMap.<DataKey, DataResource>of(),
                 ImmutableMap.<DataKey, DataAsset>of(
@@ -111,7 +118,8 @@
                     DataValueFile.of(
                         Visibility.UNKNOWN,
                         otherAssetSource.overwrite(assetSource),
-                        /*fingerprint=*/ null))));
+                        /*fingerprint=*/ null,
+                        /*rootXmlNode=*/ null))));
   }
 
   @Test
@@ -357,9 +365,15 @@
                     MergeConflict.of(
                         drawableMenu,
                         DataValueFile.of(
-                            Visibility.UNKNOWN, rootDrawableMenuPath, /*fingerprint=*/ null),
+                            Visibility.UNKNOWN,
+                            rootDrawableMenuPath,
+                            /*fingerprint=*/ null,
+                            /*rootXmlNode=*/ null),
                         DataValueFile.of(
-                            Visibility.UNKNOWN, otherRootDrawableMenuPath, /*fingerprint=*/ null)),
+                            Visibility.UNKNOWN,
+                            otherRootDrawableMenuPath,
+                            /*fingerprint=*/ null,
+                            /*rootXmlNode=*/ null)),
                     MergeConflict.of(
                         stringExit,
                         DataResourceXml.createWithNoNamespace(
@@ -382,7 +396,8 @@
                     DataValueFile.of(
                         Visibility.UNKNOWN,
                         otherRootDrawableMenuPath.overwrite(rootDrawableMenuPath),
-                        /*fingerprint=*/ null), // value
+                        /*fingerprint=*/ null,
+                        /*rootXmlNode=*/ null), // value
                     attributeFoo, // key
                     DataResourceXml.createWithNoNamespace(
                         otherRootValuesPath.overwrite(rootValuesPath),
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java b/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java
index 69f4030..8645985 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java
@@ -32,6 +32,7 @@
 import com.android.aapt.Resources.Package;
 import com.android.aapt.Resources.ResourceTable;
 import com.android.aapt.Resources.Value;
+import com.android.aapt.Resources.XmlNode;
 import com.android.ide.common.resources.configuration.CountryCodeQualifier;
 import com.android.ide.common.resources.configuration.DensityQualifier;
 import com.android.ide.common.resources.configuration.FolderConfiguration;
@@ -286,7 +287,11 @@
               // TODO(b/26297204): use visibility from ResourceTable instead of UNKNOWN
               DataResource dataResource =
                   resourceValue.getItem().hasFile()
-                      ? DataValueFile.of(Visibility.UNKNOWN, dataSource, /*fingerprint=*/ null)
+                      ? DataValueFile.of(
+                          Visibility.UNKNOWN,
+                          dataSource,
+                          /*fingerprint=*/ null,
+                          /*rootXmlNode=*/ null)
                       : DataResourceXml.from(
                           resourceValue,
                           Visibility.UNKNOWN,
@@ -528,7 +533,12 @@
 
     // TODO(b/26297204): use visibility from ResourceTable instead of UNKNOWN
     consumers.overwritingConsumer.accept(
-        fqn, DataValueFile.of(Visibility.UNKNOWN, dataSource, compiledFileWithData.fingerprint()));
+        fqn,
+        DataValueFile.of(
+            Visibility.UNKNOWN,
+            dataSource,
+            compiledFileWithData.fingerprint(),
+            compiledFileWithData.rootXmlNode()));
 
     for (CompiledFile.Symbol exportedSymbol : compiledFile.getExportedSymbolList()) {
       if (exportedSymbol.getResourceName().startsWith("android:")) {
@@ -628,6 +638,20 @@
     consumeResourceTable(dependencyInfo, consumers, resourceTable);
   }
 
+  public Map<DataKey, DataResource> read(DependencyInfo dependencyInfo, Path inPath) {
+    Map<DataKey, DataResource> resources = new LinkedHashMap<>();
+    read(
+        dependencyInfo,
+        inPath,
+        KeyValueConsumers.of(
+            resources::put,
+            resources::put,
+            (key, value) -> {
+              throw new IllegalStateException(String.format("Unexpected asset in %s", inPath));
+            }));
+    return resources;
+  }
+
   @Override
   public void read(DependencyInfo dependencyInfo, Path inPath, KeyValueConsumers consumers) {
     Stopwatch timer = Stopwatch.createStarted();
@@ -721,22 +745,27 @@
             long payloadSize = dataInputStream.readLong();
             byte[] headerBytes = readBytesAndSkipPadding(dataInputStream, headerSize);
 
+            CompiledFile compiledFile =
+                CompiledFile.parseFrom(headerBytes, ExtensionRegistry.getEmptyRegistry());
+
             HashCode fingerprint;
+            XmlNode rootXmlNode;
             if (includeFileContentsForValidation) {
               byte[] payloadBytes = readBytesAndSkipPadding(dataInputStream, (int) payloadSize);
               fingerprint = Hashing.goodFastHash(/*minimumBits=*/ 64).hashBytes(payloadBytes);
-              // TODO(b/26297204): verify that XML payloads reference accessible resources
+              rootXmlNode =
+                  compiledFile.getType() == Resources.FileReference.Type.PROTO_XML
+                      ? XmlNode.parseFrom(payloadBytes, ExtensionRegistry.getEmptyRegistry())
+                      : null;
             } else {
               ByteStreams.skipFully(
                   dataInputStream,
                   ((payloadSize + 1) / AAPT_FLAT_FILE_ALIGNMENT) * AAPT_FLAT_FILE_ALIGNMENT);
               fingerprint = null;
+              rootXmlNode = null;
             }
 
-            compiledFiles.add(
-                CompiledFileWithData.create(
-                    CompiledFile.parseFrom(headerBytes, ExtensionRegistry.getEmptyRegistry()),
-                    fingerprint));
+            compiledFiles.add(CompiledFileWithData.create(compiledFile, fingerprint, rootXmlNode));
           }
 
           break; // TODO(b/146528588): remove this line
@@ -832,9 +861,13 @@
     @Nullable
     abstract HashCode fingerprint();
 
-    static CompiledFileWithData create(CompiledFile compiledFile, @Nullable HashCode fingerprint) {
+    @Nullable
+    abstract XmlNode rootXmlNode();
+
+    static CompiledFileWithData create(
+        CompiledFile compiledFile, @Nullable HashCode fingerprint, @Nullable XmlNode xmlNode) {
       return new AutoValue_AndroidCompiledDataDeserializer_CompiledFileWithData(
-          compiledFile, fingerprint);
+          compiledFile, fingerprint, xmlNode);
     }
   }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidParsedDataDeserializer.java b/src/tools/android/java/com/google/devtools/build/android/AndroidParsedDataDeserializer.java
index f86f58e..8cd327e 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AndroidParsedDataDeserializer.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidParsedDataDeserializer.java
@@ -138,7 +138,9 @@
         KeyValueConsumer<DataKey, DataValue> value =
             (KeyValueConsumer<DataKey, DataValue>) entry.getValue();
         value.accept(
-            entry.getKey(), DataValueFile.of(Visibility.UNKNOWN, source, /*fingerprint=*/ null));
+            entry.getKey(),
+            DataValueFile.of(
+                Visibility.UNKNOWN, source, /*fingerprint=*/ null, /*rootXmlNode=*/ null));
       }
     }
   }
diff --git a/src/tools/android/java/com/google/devtools/build/android/DataResource.java b/src/tools/android/java/com/google/devtools/build/android/DataResource.java
index 1605392..d5768cf 100644
--- a/src/tools/android/java/com/google/devtools/build/android/DataResource.java
+++ b/src/tools/android/java/com/google/devtools/build/android/DataResource.java
@@ -13,7 +13,10 @@
 // limitations under the License.
 package com.google.devtools.build.android;
 
+import com.android.aapt.Resources.Reference;
+import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.android.AndroidResourceMerger.MergingException;
+import com.google.devtools.build.android.resources.Visibility;
 
 /** Represents an Android Resource parsed from an xml or binary file. */
 public interface DataResource extends DataValue {
@@ -35,4 +38,10 @@
 
   /** Overwrite another {@link DataResource}. */
   DataResource overwrite(DataResource other);
+
+  /** Visibility of this resource as denoted by a {@code <public>} tag, or lack thereof. */
+  Visibility getVisibility();
+
+  /** Resources referenced via XML attributes or proxying resource definitions. */
+  ImmutableList<Reference> getReferencedResources();
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java b/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java
index 447ab50..e78025c 100644
--- a/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java
+++ b/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java
@@ -17,10 +17,12 @@
 import static com.android.resources.ResourceType.ID;
 import static com.android.resources.ResourceType.PUBLIC;
 
+import com.android.aapt.Resources.Reference;
 import com.android.aapt.Resources.Value;
 import com.android.resources.ResourceType;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.android.AndroidCompiledDataDeserializer.ReferenceResolver;
 import com.google.devtools.build.android.FullyQualifiedName.Factory;
@@ -452,4 +454,14 @@
     DataResourceXml other = (DataResourceXml) value;
     return xml.compareMergePriorityTo(other.xml);
   }
+
+  @Override
+  public Visibility getVisibility() {
+    return xml.getVisibility();
+  }
+
+  @Override
+  public ImmutableList<Reference> getReferencedResources() {
+    return xml.getReferencedResources();
+  }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/DataValueFile.java b/src/tools/android/java/com/google/devtools/build/android/DataValueFile.java
index 1386179..74949b6 100644
--- a/src/tools/android/java/com/google/devtools/build/android/DataValueFile.java
+++ b/src/tools/android/java/com/google/devtools/build/android/DataValueFile.java
@@ -13,11 +13,15 @@
 // limitations under the License.
 package com.google.devtools.build.android;
 
+import com.android.aapt.Resources.Reference;
+import com.android.aapt.Resources.XmlNode;
 import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
 import com.google.common.hash.HashCode;
 import com.google.devtools.build.android.AndroidResourceMerger.MergingException;
 import com.google.devtools.build.android.proto.SerializeFormat;
 import com.google.devtools.build.android.resources.Visibility;
+import com.google.devtools.build.android.xml.ProtoXmlUtils;
 import com.google.protobuf.CodedOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -35,22 +39,35 @@
   private final Visibility visibility;
   private final DataSource source;
   @Nullable private final HashCode fingerprint;
+  // Set only for XML-based resources, and only when reading the output of aapt2.
+  @Nullable private final XmlNode rootXmlNode;
 
-  private DataValueFile(Visibility visibility, DataSource source, @Nullable HashCode fingerprint) {
+  private DataValueFile(
+      Visibility visibility,
+      DataSource source,
+      @Nullable HashCode fingerprint,
+      @Nullable XmlNode rootXmlNode) {
     this.visibility = visibility;
     this.source = source;
     this.fingerprint = fingerprint;
+    this.rootXmlNode = rootXmlNode;
   }
 
   @Deprecated
   public static DataValueFile of(Path source) {
     return of(
-        Visibility.UNKNOWN, DataSource.of(DependencyInfo.UNKNOWN, source), /*fingerprint=*/ null);
+        Visibility.UNKNOWN,
+        DataSource.of(DependencyInfo.UNKNOWN, source),
+        /*fingerprint=*/ null,
+        /*rootXmlNode=*/ null);
   }
 
   public static DataValueFile of(
-      Visibility visibility, DataSource source, @Nullable HashCode fingerprint) {
-    return new DataValueFile(visibility, source, fingerprint);
+      Visibility visibility,
+      DataSource source,
+      @Nullable HashCode fingerprint,
+      @Nullable XmlNode rootXmlNode) {
+    return new DataValueFile(visibility, source, fingerprint, rootXmlNode);
   }
 
   /** Creates a {@link DataValueFile} from a {@link SerializeFormat#DataValue}. */
@@ -117,7 +134,8 @@
     if (equals(resource)) {
       return this;
     }
-    return new DataValueFile(visibility, source.overwrite(resource.source()), fingerprint);
+    return new DataValueFile(
+        visibility, source.overwrite(resource.source()), fingerprint, rootXmlNode);
   }
 
   @Override
@@ -125,7 +143,8 @@
     if (equals(asset)) {
       return this;
     }
-    return new DataValueFile(visibility, source.overwrite(asset.source()), fingerprint);
+    return new DataValueFile(
+        visibility, source.overwrite(asset.source()), fingerprint, rootXmlNode);
   }
 
   @Override
@@ -135,7 +154,7 @@
 
   @Override
   public DataValue update(DataSource source) {
-    return new DataValueFile(visibility, source, fingerprint);
+    return new DataValueFile(visibility, source, fingerprint, rootXmlNode);
   }
 
   @Override
@@ -168,4 +187,18 @@
   public int compareMergePriorityTo(DataValue value) {
     return 0;
   }
+
+  @Override
+  public Visibility getVisibility() {
+    return visibility;
+  }
+
+  @Override
+  public ImmutableList<Reference> getReferencedResources() {
+    if (rootXmlNode == null) {
+      return ImmutableList.of();
+    } else {
+      return ProtoXmlUtils.getAllResourceReferences(rootXmlNode);
+    }
+  }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/XmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/XmlResourceValue.java
index eae002b..2cc7f09 100644
--- a/src/tools/android/java/com/google/devtools/build/android/XmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/XmlResourceValue.java
@@ -13,6 +13,9 @@
 // limitations under the License.
 package com.google.devtools.build.android;
 
+import com.android.aapt.Resources.Reference;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.android.resources.Visibility;
 import com.google.devtools.build.android.xml.Namespaces;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -59,4 +62,10 @@
 
   /** Returns a representation of the xml value as a string suitable for conflict messages. */
   String asConflictStringWith(DataSource source);
+
+  /** Visibility of this resource as denoted by a {@code <public>} tag, or lack thereof. */
+  Visibility getVisibility();
+
+  /** Resources referenced via XML attributes or proxying resource definitions. */
+  ImmutableList<Reference> getReferencedResources();
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java
index 51d0cb6..51f7a9c 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java
@@ -16,6 +16,7 @@
 import com.android.aapt.Resources.Array;
 import com.android.aapt.Resources.Array.Element;
 import com.android.aapt.Resources.Item;
+import com.android.aapt.Resources.Reference;
 import com.android.aapt.Resources.Value;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.MoreObjects;
@@ -92,16 +93,20 @@
   }
 
   private final Visibility visibility;
+  private final Array array;
+  // TODO(b/112848607): remove the weakly-typed "values" member in favor of "array" above.
   private final ImmutableList<String> values;
   private final ArrayType arrayType;
   private final ImmutableMap<String, String> attributes;
 
   private ArrayXmlResourceValue(
       Visibility visibility,
+      Array array,
       ArrayType arrayType,
       List<String> values,
       Map<String, String> attributes) {
     this.visibility = visibility;
+    this.array = array;
     this.arrayType = arrayType;
     this.values = ImmutableList.copyOf(values);
     this.attributes = ImmutableMap.copyOf(attributes);
@@ -118,7 +123,8 @@
 
   public static XmlResourceValue of(
       ArrayType arrayType, List<String> values, ImmutableMap<String, String> attributes) {
-    return new ArrayXmlResourceValue(Visibility.UNKNOWN, arrayType, values, attributes);
+    return new ArrayXmlResourceValue(
+        Visibility.UNKNOWN, Array.getDefaultInstance(), arrayType, values, attributes);
   }
 
   @SuppressWarnings("deprecation")
@@ -145,7 +151,7 @@
       }
     }
 
-    return new ArrayXmlResourceValue(visibility, ArrayType.ARRAY, items, ImmutableMap.of());
+    return new ArrayXmlResourceValue(visibility, array, ArrayType.ARRAY, items, ImmutableMap.of());
   }
 
   @Override
@@ -195,6 +201,7 @@
     }
     ArrayXmlResourceValue other = (ArrayXmlResourceValue) obj;
     return Objects.equals(visibility, other.visibility)
+        // TODO(b/112848607): include the "array" proto in comparison; right now it's redundant.
         && Objects.equals(arrayType, other.arrayType)
         && Objects.equals(values, other.values)
         && Objects.equals(attributes, other.attributes);
@@ -257,4 +264,17 @@
   public String asConflictStringWith(DataSource source) {
     return source.asConflictString();
   }
+
+  @Override
+  public Visibility getVisibility() {
+    return visibility;
+  }
+
+  @Override
+  public ImmutableList<Reference> getReferencedResources() {
+    return array.getElementList().stream()
+        .filter(element -> element.getItem().hasRef())
+        .map(element -> element.getItem().getRef())
+        .collect(ImmutableList.toImmutableList());
+  }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java
index f983ff0..dc8113a 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java
@@ -18,6 +18,7 @@
 
 import com.android.aapt.Resources.Attribute;
 import com.android.aapt.Resources.Attribute.Symbol;
+import com.android.aapt.Resources.Reference;
 import com.android.aapt.Resources.Value;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.MoreObjects;
@@ -817,4 +818,14 @@
         "%s [format(s): %s], [weak: %s]",
         source.asConflictString(), String.join("|", this.formats.keySet()), weak);
   }
+
+  @Override
+  public Visibility getVisibility() {
+    return visibility;
+  }
+
+  @Override
+  public ImmutableList<Reference> getReferencedResources() {
+    return ImmutableList.of();
+  }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java
index 1bdf9d0..dd316ce 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java
@@ -13,11 +13,13 @@
 // limitations under the License.
 package com.google.devtools.build.android.xml;
 
+import com.android.aapt.Resources.Reference;
 import com.android.aapt.Resources.Value;
 import com.android.resources.ResourceType;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.android.AndroidDataWritingVisitor;
 import com.google.devtools.build.android.AndroidDataWritingVisitor.StartTag;
 import com.google.devtools.build.android.AndroidResourceSymbolSink;
@@ -173,4 +175,14 @@
   public String asConflictStringWith(DataSource source) {
     return source.asConflictString();
   }
+
+  @Override
+  public Visibility getVisibility() {
+    return visibility;
+  }
+
+  @Override
+  public ImmutableList<Reference> getReferencedResources() {
+    return ImmutableList.of();
+  }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java
index 0f5acbb..d76cd19 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java
@@ -14,8 +14,10 @@
 package com.google.devtools.build.android.xml;
 
 import com.android.aapt.Resources.Plural;
+import com.android.aapt.Resources.Reference;
 import com.android.aapt.Resources.Value;
 import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.xml.XmlEscapers;
 import com.google.devtools.build.android.AndroidDataWritingVisitor;
@@ -58,14 +60,18 @@
   private static final QName PLURALS = QName.valueOf("plurals");
 
   private final Visibility visibility;
+  private final Plural plural;
+  // TODO(b/112848607): remove the weakly-typed "values" member in favor of "plural" above.
   private final ImmutableMap<String, String> values;
   private final ImmutableMap<String, String> attributes;
 
   private PluralXmlResourceValue(
       Visibility visibility,
+      Plural plural,
       ImmutableMap<String, String> attributes,
       ImmutableMap<String, String> values) {
     this.visibility = visibility;
+    this.plural = plural;
     this.attributes = attributes;
     this.values = values;
   }
@@ -76,7 +82,8 @@
 
   public static XmlResourceValue createWithAttributesAndValues(
       ImmutableMap<String, String> attributes, ImmutableMap<String, String> values) {
-    return new PluralXmlResourceValue(Visibility.UNKNOWN, attributes, values);
+    return new PluralXmlResourceValue(
+        Visibility.UNKNOWN, Plural.getDefaultInstance(), attributes, values);
   }
 
   @Override
@@ -124,6 +131,7 @@
     }
     PluralXmlResourceValue other = (PluralXmlResourceValue) obj;
     return Objects.equals(visibility, other.visibility)
+        // TODO(b/112848607): include the "plural" proto in comparison; right now it's redundant.
         && Objects.equals(values, other.values)
         && Objects.equals(attributes, other.attributes);
   }
@@ -158,7 +166,8 @@
       items.put(name, value);
     }
 
-    return new PluralXmlResourceValue(visibility, ImmutableMap.of(), ImmutableMap.copyOf(items));
+    return new PluralXmlResourceValue(
+        visibility, plural, ImmutableMap.of(), ImmutableMap.copyOf(items));
   }
 
   @Override
@@ -195,4 +204,17 @@
   public String asConflictStringWith(DataSource source) {
     return source.asConflictString();
   }
+
+  @Override
+  public Visibility getVisibility() {
+    return visibility;
+  }
+
+  @Override
+  public ImmutableList<Reference> getReferencedResources() {
+    return plural.getEntryList().stream()
+        .filter(entry -> entry.getItem().hasRef())
+        .map(entry -> entry.getItem().getRef())
+        .collect(ImmutableList.toImmutableList());
+  }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/PublicXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/PublicXmlResourceValue.java
index 6de5158..c188401 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/PublicXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/PublicXmlResourceValue.java
@@ -14,10 +14,12 @@
 package com.google.devtools.build.android.xml;
 
 import com.android.SdkConstants;
+import com.android.aapt.Resources.Reference;
 import com.android.resources.ResourceType;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import com.google.devtools.build.android.AndroidDataWritingVisitor;
@@ -28,6 +30,7 @@
 import com.google.devtools.build.android.XmlResourceValue;
 import com.google.devtools.build.android.XmlResourceValues;
 import com.google.devtools.build.android.proto.SerializeFormat;
+import com.google.devtools.build.android.resources.Visibility;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.EnumMap;
@@ -185,4 +188,15 @@
   public String asConflictStringWith(DataSource source) {
     return source.asConflictString();
   }
+
+  @Override
+  public Visibility getVisibility() {
+    // <public id="..."> itself is not a value
+    return Visibility.UNKNOWN;
+  }
+
+  @Override
+  public ImmutableList<Reference> getReferencedResources() {
+    return ImmutableList.of();
+  }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/ResourcesAttribute.java b/src/tools/android/java/com/google/devtools/build/android/xml/ResourcesAttribute.java
index 0979c9b..62086fb 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/ResourcesAttribute.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/ResourcesAttribute.java
@@ -13,8 +13,10 @@
 // limitations under the License.
 package com.google.devtools.build.android.xml;
 
+import com.android.aapt.Resources.Reference;
 import com.google.common.base.Joiner;
 import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.android.AndroidDataWritingVisitor;
 import com.google.devtools.build.android.AndroidResourceSymbolSink;
@@ -25,6 +27,7 @@
 import com.google.devtools.build.android.XmlResourceValues;
 import com.google.devtools.build.android.proto.SerializeFormat;
 import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml;
+import com.google.devtools.build.android.resources.Visibility;
 import com.google.errorprone.annotations.Immutable;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -189,4 +192,14 @@
     }
     return source.asConflictString();
   }
+
+  @Override
+  public Visibility getVisibility() {
+    return Visibility.UNKNOWN;
+  }
+
+  @Override
+  public ImmutableList<Reference> getReferencedResources() {
+    return ImmutableList.of();
+  }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java
index 99b81d37..1a17f3c1 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java
@@ -15,11 +15,13 @@
 
 import com.android.aapt.Resources.Item;
 import com.android.aapt.Resources.Primitive;
+import com.android.aapt.Resources.Reference;
 import com.android.aapt.Resources.StyledString;
 import com.android.aapt.Resources.StyledString.Span;
 import com.android.aapt.Resources.Value;
 import com.android.resources.ResourceType;
 import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.xml.XmlEscapers;
 import com.google.devtools.build.android.AndroidDataWritingVisitor;
@@ -110,7 +112,9 @@
   }
 
   private final Visibility visibility;
+  private final Item item;
   private final ImmutableMap<String, String> attributes;
+  // TODO(b/112848607): remove untyped "value" String in favor of "item" above.
   @Nullable private final String value;
   private final Type valueType;
 
@@ -138,16 +142,19 @@
 
   public static XmlResourceValue of(
       Type valueType, ImmutableMap<String, String> attributes, @Nullable String value) {
-    return new SimpleXmlResourceValue(Visibility.UNKNOWN, valueType, attributes, value);
+    return new SimpleXmlResourceValue(
+        Visibility.UNKNOWN, valueType, Item.getDefaultInstance(), attributes, value);
   }
 
   private SimpleXmlResourceValue(
       Visibility visibility,
       Type valueType,
+      Item item,
       ImmutableMap<String, String> attributes,
       String value) {
     this.visibility = visibility;
     this.valueType = valueType;
+    this.item = item;
     this.value = value;
     this.attributes = attributes;
   }
@@ -209,6 +216,7 @@
     return new SimpleXmlResourceValue(
         visibility,
         Type.valueOf(resourceType.toString().toUpperCase(Locale.ENGLISH)),
+        item,
         attributes.build(),
         stringValue);
   }
@@ -326,6 +334,7 @@
     return Objects.equals(visibility, other.visibility)
         && Objects.equals(valueType, other.valueType)
         && Objects.equals(attributes, other.attributes)
+        // TODO(b/112848607): include the "item" proto in comparison; right now it's redundant.
         && Objects.equals(value, other.value);
   }
 
@@ -355,4 +364,14 @@
     }
     return source.asConflictString();
   }
+
+  @Override
+  public Visibility getVisibility() {
+    return visibility;
+  }
+
+  @Override
+  public ImmutableList<Reference> getReferencedResources() {
+    return item.hasRef() ? ImmutableList.of(item.getRef()) : ImmutableList.of();
+  }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java
index 36c3b2b..39212c0 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java
@@ -13,10 +13,12 @@
 // limitations under the License.
 package com.google.devtools.build.android.xml;
 
+import com.android.aapt.Resources.Reference;
 import com.android.aapt.Resources.Style;
 import com.android.aapt.Resources.Value;
 import com.google.common.base.Function;
 import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.android.AndroidDataWritingVisitor;
 import com.google.devtools.build.android.AndroidDataWritingVisitor.ValuesResourceDefinition;
@@ -66,11 +68,15 @@
       };
 
   private final Visibility visibility;
+  private final Style style;
+  // TODO(b/112848607): remove parent/values in favor of "style" above, or replace the Strings with
+  // stronger types.
   private final String parent;
   private final ImmutableMap<String, String> values;
 
   public static StyleXmlResourceValue of(String parent, Map<String, String> values) {
-    return new StyleXmlResourceValue(Visibility.UNKNOWN, parent, ImmutableMap.copyOf(values));
+    return new StyleXmlResourceValue(
+        Visibility.UNKNOWN, Style.getDefaultInstance(), parent, ImmutableMap.copyOf(values));
   }
 
   @SuppressWarnings("deprecation")
@@ -92,12 +98,16 @@
 
     Map<String, String> items = itemMapFromProto(style);
 
-    return new StyleXmlResourceValue(visibility, parent, ImmutableMap.copyOf(items));
+    return new StyleXmlResourceValue(visibility, style, parent, ImmutableMap.copyOf(items));
   }
 
   private StyleXmlResourceValue(
-      Visibility visibility, @Nullable String parent, ImmutableMap<String, String> values) {
+      Visibility visibility,
+      Style style,
+      @Nullable String parent,
+      ImmutableMap<String, String> values) {
     this.visibility = visibility;
+    this.style = style;
     this.parent = parent;
     this.values = values;
   }
@@ -188,6 +198,7 @@
     StyleXmlResourceValue other = (StyleXmlResourceValue) obj;
     return Objects.equals(visibility, other.visibility)
         && Objects.equals(parent, other.parent)
+        // TODO(b/112848607): include the "style" proto in comparison; right now it's redundant.
         && Objects.equals(values, other.values);
   }
 
@@ -213,4 +224,24 @@
   public String asConflictStringWith(DataSource source) {
     return source.asConflictString();
   }
+
+  @Override
+  public Visibility getVisibility() {
+    return visibility;
+  }
+
+  @Override
+  public ImmutableList<Reference> getReferencedResources() {
+    ImmutableList.Builder<Reference> result = ImmutableList.builder();
+    if (style.hasParent()) {
+      result.add(style.getParent());
+    }
+    for (Style.Entry entry : style.getEntryList()) {
+      result.add(entry.getKey());
+      if (entry.getItem().hasRef()) {
+        result.add(entry.getItem().getRef());
+      }
+    }
+    return result.build();
+  }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java
index 0e17fe1..e996e42 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java
@@ -13,11 +13,13 @@
 // limitations under the License.
 package com.google.devtools.build.android.xml;
 
+import com.android.aapt.Resources.Reference;
 import com.android.aapt.Resources.Styleable;
 import com.android.aapt.Resources.Value;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.android.AndroidCompiledDataDeserializer.ReferenceResolver;
@@ -83,11 +85,15 @@
           };
 
   private final Visibility visibility;
-  private final ImmutableMap<FullyQualifiedName, Boolean> attrs;
+  private final Styleable styleable;
+  // TODO(b/145837824,b/112848607): change to a set, if not removing this outright.  Per the Javadoc
+  // for this class, the "should inline" bit is used to mimic how AAPT1 assigns IDs.
+  private final ImmutableMap<FullyQualifiedName, /*shouldInline=*/ Boolean> attrs;
 
   private StyleableXmlResourceValue(
-      Visibility visibility, ImmutableMap<FullyQualifiedName, Boolean> attrs) {
+      Visibility visibility, Styleable styleable, ImmutableMap<FullyQualifiedName, Boolean> attrs) {
     this.visibility = visibility;
+    this.styleable = styleable;
     this.attrs = attrs;
   }
 
@@ -111,11 +117,13 @@
   }
 
   public static XmlResourceValue of(Map<FullyQualifiedName, Boolean> attrs) {
-    return new StyleableXmlResourceValue(Visibility.UNKNOWN, ImmutableMap.copyOf(attrs));
+    return new StyleableXmlResourceValue(
+        Visibility.UNKNOWN, Styleable.getDefaultInstance(), ImmutableMap.copyOf(attrs));
   }
 
   public static XmlResourceValue of(Visibility visibility, Map<FullyQualifiedName, Boolean> attrs) {
-    return new StyleableXmlResourceValue(visibility, ImmutableMap.copyOf(attrs));
+    return new StyleableXmlResourceValue(
+        visibility, Styleable.getDefaultInstance(), ImmutableMap.copyOf(attrs));
   }
 
   @Override
@@ -186,7 +194,7 @@
       }
     }
 
-    return of(visibility, ImmutableMap.copyOf(attributes));
+    return new StyleableXmlResourceValue(visibility, styleable, ImmutableMap.copyOf(attributes));
   }
 
   @Override
@@ -199,6 +207,7 @@
     if (!(obj instanceof StyleableXmlResourceValue)) {
       return false;
     }
+    // TODO(b/112848607): include the "styleable" proto in comparison; right now it's redundant.
     StyleableXmlResourceValue other = (StyleableXmlResourceValue) obj;
     return Objects.equals(visibility, other.visibility) && Objects.equals(attrs, other.attrs);
   }
@@ -226,10 +235,10 @@
     if (!(value instanceof StyleableXmlResourceValue)) {
       throw new IllegalArgumentException(value + "is not combinable with " + this);
     }
-    StyleableXmlResourceValue styleable = (StyleableXmlResourceValue) value;
+    StyleableXmlResourceValue other = (StyleableXmlResourceValue) value;
     Map<FullyQualifiedName, Boolean> combined = new LinkedHashMap<>();
     combined.putAll(attrs);
-    for (Map.Entry<FullyQualifiedName, Boolean> attr : styleable.attrs.entrySet()) {
+    for (Map.Entry<FullyQualifiedName, Boolean> attr : other.attrs.entrySet()) {
       if (combined.containsKey(attr.getKey())) {
         // if either attr is defined in the styleable, the attr will be defined in the styleable.
         if (attr.getValue() || combined.get(attr.getKey())) {
@@ -243,7 +252,9 @@
     }
     // TODO(b/26297204): test that this makes sense and works
     return new StyleableXmlResourceValue(
-        Visibility.merge(visibility, styleable.visibility), ImmutableMap.copyOf(combined));
+        Visibility.merge(visibility, other.visibility),
+        styleable.toBuilder().mergeFrom(other.styleable).build(),
+        ImmutableMap.copyOf(combined));
   }
 
   @Override
@@ -255,4 +266,16 @@
   public String asConflictStringWith(DataSource source) {
     return source.asConflictString();
   }
+
+  @Override
+  public Visibility getVisibility() {
+    return visibility;
+  }
+
+  @Override
+  public ImmutableList<Reference> getReferencedResources() {
+    return styleable.getEntryList().stream()
+        .map(entry -> entry.getAttr())
+        .collect(ImmutableList.toImmutableList());
+  }
 }