Reapply "Initial setup for doing legacy resource shrinking in R8"

This reverts commit 8ea20ac4540f3800f13f5f54c52d3456bf9409fe.

Bug: 305892375
Bug: 287398085

Change-Id: If6e6ee396cd6dcee4619b733d94f6dd456e4b7b6
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/ResourceShrinkerImpl.kt b/src/resourceshrinker/java/com/android/build/shrinker/ResourceShrinkerImpl.kt
index 1136085..153f1ce 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/ResourceShrinkerImpl.kt
+++ b/src/resourceshrinker/java/com/android/build/shrinker/ResourceShrinkerImpl.kt
@@ -301,6 +301,11 @@
         .any { it.isReachable }
 }
 
+fun ResourceStore.isJarPathReachable(path: String) : Boolean {
+    val (_, folder, name) = path.split('/', limit = 3)
+    return isJarPathReachable(folder, name);
+}
+
 private fun ResourceStore.getResourceId(
     folder: String,
     name: String
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/graph/ProtoResourcesGraphBuilder.kt b/src/resourceshrinker/java/com/android/build/shrinker/graph/ProtoResourcesGraphBuilder.kt
index 130e585..a0a1f76 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/graph/ProtoResourcesGraphBuilder.kt
+++ b/src/resourceshrinker/java/com/android/build/shrinker/graph/ProtoResourcesGraphBuilder.kt
@@ -21,6 +21,7 @@
 import com.android.aapt.Resources.FileReference
 import com.android.aapt.Resources.FileReference.Type.PROTO_XML
 import com.android.aapt.Resources.Reference
+import com.android.aapt.Resources.ResourceTable
 import com.android.aapt.Resources.XmlAttribute
 import com.android.aapt.Resources.XmlElement
 import com.android.aapt.Resources.XmlNode
@@ -56,12 +57,23 @@
  * @param resourceTable path to resource table in proto format.
  */
 class ProtoResourcesGraphBuilder(
-    private val resourceRoot: Path,
-    private val resourceTable: Path
+    private val resourceRoot: ResFolderFileTree,
+    private val resourceTableProducer: (ResourceShrinkerModel) -> ResourceTable
 ) : ResourcesGraphBuilder {
 
+    constructor(resourceRootPath: Path, resourceTablePath: Path) : this(
+        object : ResFolderFileTree {
+            override fun getEntryByName(pathInRes: String): ByteArray {
+                val lazyVal : ByteArray by lazy {
+                    Files.readAllBytes(resourceRootPath.resolve(pathInRes))
+                }
+                return lazyVal
+            }
+        },
+        { model -> model.readResourceTable(resourceTablePath) }
+    )
     override fun buildGraph(model: ResourceShrinkerModel) {
-        model.readResourceTable(resourceTable).entriesSequence()
+        resourceTableProducer(model).entriesSequence()
             .map { (id, _, _, entry) ->
                 model.resourceStore.getResource(id)?.let {
                     ReferencesForResourceFinder(resourceRoot, model, entry, it)
@@ -71,9 +83,12 @@
             .forEach { it.findReferences() }
     }
 }
+interface ResFolderFileTree {
+    fun getEntryByName(pathInRes: String) : ByteArray
+}
 
 private class ReferencesForResourceFinder(
-    private val resourcesRoot: Path,
+    private val resourcesRoot: ResFolderFileTree,
     private val model: ResourceShrinkerModel,
     private val entry: Entry,
     private val current: Resource
@@ -196,10 +211,9 @@
     }
 
     private fun findFromFile(file: FileReference) {
-        val path = resourcesRoot.resolve(file.path.substringAfter("res/"))
-        val bytes: ByteArray by lazy { Files.readAllBytes(path) }
+        val bytes = resourcesRoot.getEntryByName(file.path.substringAfter("res/"))
         val content: String by lazy { String(bytes, StandardCharsets.UTF_8) }
-        val extension = Ascii.toLowerCase(path.fileName.toString()).substringAfter('.')
+        val extension = Ascii.toLowerCase(file.path.substringAfterLast('.'))
         when {
             file.type == PROTO_XML -> fillFromXmlNode(XmlNode.parseFrom(bytes))
             extension in listOf("html", "htm") -> webTokenizers.tokenizeHtml(content)
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
new file mode 100644
index 0000000..9fd4163
--- /dev/null
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
@@ -0,0 +1,270 @@
+// Copyright (c) 2023, 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.build.shrinker.r8integration;
+
+import static java.nio.charset.StandardCharsets.UTF_16BE;
+import static java.nio.charset.StandardCharsets.UTF_16LE;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.android.aapt.Resources.ResourceTable;
+import com.android.aapt.Resources.XmlNode;
+import com.android.build.shrinker.NoDebugReporter;
+import com.android.build.shrinker.ResourceShrinkerImplKt;
+import com.android.build.shrinker.ResourceTableUtilKt;
+import com.android.build.shrinker.graph.ProtoResourcesGraphBuilder;
+import com.android.build.shrinker.graph.ResFolderFileTree;
+import com.android.build.shrinker.r8integration.R8ResourceShrinkerState.R8ResourceShrinkerModel;
+import com.android.build.shrinker.usages.DexFileAnalysisCallback;
+import com.android.build.shrinker.usages.ProtoAndroidManifestUsageRecorderKt;
+import com.android.build.shrinker.usages.R8ResourceShrinker;
+import com.android.build.shrinker.usages.ToolsAttributeUsageRecorderKt;
+import com.android.ide.common.resources.usage.ResourceStore;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.xml.parsers.ParserConfigurationException;
+import org.jetbrains.annotations.NotNull;
+import org.xml.sax.SAXException;
+
+public class LegacyResourceShrinker {
+  private final Map<Integer, byte[]> dexInputs;
+  private final List<PathAndBytes> resFolderInputs;
+  private final List<PathAndBytes> xmlInputs;
+  private final PathAndBytes manifest;
+  private final PathAndBytes resourceTable;
+
+  public static class Builder {
+
+    private final Map<Integer, byte[]> dexInputs = new HashMap<>();
+    private final List<PathAndBytes> resFolderInputs = new ArrayList<>();
+    private final List<PathAndBytes> xmlInputs = new ArrayList<>();
+
+    private PathAndBytes manifest;
+    private PathAndBytes resourceTable;
+
+    private Builder() {}
+
+    public Builder setManifest(Path path, byte[] bytes) {
+      this.manifest = new PathAndBytes(bytes, path);
+      return this;
+    }
+
+    public Builder setResourceTable(Path path, byte[] bytes) {
+      this.resourceTable = new PathAndBytes(bytes, path);
+      return this;
+    }
+
+    public Builder addDexInput(int index, byte[] bytes) {
+      dexInputs.put(index, bytes);
+      return this;
+    }
+
+    public Builder addResFolderInput(Path path, byte[] bytes) {
+      resFolderInputs.add(new PathAndBytes(bytes, path));
+      return this;
+    }
+
+    public Builder addXmlInput(Path path, byte[] bytes) {
+      xmlInputs.add(new PathAndBytes(bytes, path));
+      return this;
+    }
+
+    public LegacyResourceShrinker build() {
+      assert manifest != null && resourceTable != null;
+      return new LegacyResourceShrinker(
+          dexInputs, resFolderInputs, manifest, resourceTable, xmlInputs);
+    }
+  }
+
+  private LegacyResourceShrinker(
+      Map<Integer, byte[]> dexInputs,
+      List<PathAndBytes> resFolderInputs,
+      PathAndBytes manifest,
+      PathAndBytes resourceTable,
+      List<PathAndBytes> xmlInputs) {
+    this.dexInputs = dexInputs;
+    this.resFolderInputs = resFolderInputs;
+    this.manifest = manifest;
+    this.resourceTable = resourceTable;
+    this.xmlInputs = xmlInputs;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public ShrinkerResult run() throws IOException, ParserConfigurationException, SAXException {
+    R8ResourceShrinkerModel model = new R8ResourceShrinkerModel(NoDebugReporter.INSTANCE, false);
+    ResourceTable loadedResourceTable = ResourceTable.parseFrom(resourceTable.bytes);
+    model.instantiateFromResourceTable(loadedResourceTable);
+    for (Entry<Integer, byte[]> entry : dexInputs.entrySet()) {
+      // The analysis needs an origin for the dex files, synthesize an easy recognizable one.
+      Path inMemoryR8 = Paths.get("in_memory_r8_classes" + entry.getKey() + ".dex");
+      R8ResourceShrinker.runResourceShrinkerAnalysis(
+          entry.getValue(), inMemoryR8, new DexFileAnalysisCallback(inMemoryR8, model));
+    }
+    ProtoAndroidManifestUsageRecorderKt.recordUsagesFromNode(
+        XmlNode.parseFrom(manifest.bytes), model);
+    for (PathAndBytes xmlInput : xmlInputs) {
+      ToolsAttributeUsageRecorderKt.processRawXml(getUtfReader(xmlInput.getBytes()), model);
+    }
+    new ProtoResourcesGraphBuilder(
+            new ResFolderFileTree() {
+              Map<String, PathAndBytes> pathToBytes =
+                  new ImmutableMap.Builder<String, PathAndBytes>()
+                      .putAll(
+                          xmlInputs.stream()
+                              .collect(Collectors.toMap(PathAndBytes::getPathWithoutRes, a -> a)))
+                      .putAll(
+                          resFolderInputs.stream()
+                              .collect(Collectors.toMap(PathAndBytes::getPathWithoutRes, a -> a)))
+                      .build();
+
+              @Override
+              public byte[] getEntryByName(@NotNull String pathInRes) {
+                return pathToBytes.get(pathInRes).getBytes();
+              }
+            },
+            unused -> loadedResourceTable)
+        .buildGraph(model);
+    ResourceStore resourceStore = model.getResourceStore();
+    resourceStore.processToolsAttributes();
+    model.keepPossiblyReferencedResources();
+    ImmutableSet.Builder<String> resEntriesToKeep = new ImmutableSet.Builder<>();
+    for (PathAndBytes xmlInput : Iterables.concat(xmlInputs, resFolderInputs)) {
+      if (ResourceShrinkerImplKt.isJarPathReachable(resourceStore, xmlInput.path.toString())) {
+        resEntriesToKeep.add(xmlInput.path.toString());
+      }
+    }
+    List<Integer> resourceIdsToRemove =
+        model.getResourceStore().getResources().stream()
+            .filter(r -> !r.isReachable())
+            .map(r -> r.value)
+            .collect(Collectors.toList());
+    ResourceTable shrunkenResourceTable =
+        ResourceTableUtilKt.nullOutEntriesWithIds(loadedResourceTable, resourceIdsToRemove);
+    return new ShrinkerResult(resEntriesToKeep.build(), shrunkenResourceTable.toByteArray());
+  }
+
+  // Lifted from com/android/utils/XmlUtils.java which we can't easily update internal dependency
+  // for.
+  /**
+   * Returns a character reader for the given bytes, which must be a UTF encoded file.
+   *
+   * <p>The reader does not need to be closed by the caller (because the file is read in full in one
+   * shot and the resulting array is then wrapped in a byte array input stream, which does not need
+   * to be closed.)
+   */
+  public static Reader getUtfReader(byte[] bytes) throws IOException {
+    int length = bytes.length;
+    if (length == 0) {
+      return new StringReader("");
+    }
+
+    switch (bytes[0]) {
+      case (byte) 0xEF:
+        {
+          if (length >= 3 && bytes[1] == (byte) 0xBB && bytes[2] == (byte) 0xBF) {
+            // UTF-8 BOM: EF BB BF: Skip it
+            return new InputStreamReader(new ByteArrayInputStream(bytes, 3, length - 3), UTF_8);
+          }
+          break;
+        }
+      case (byte) 0xFE:
+        {
+          if (length >= 2 && bytes[1] == (byte) 0xFF) {
+            // UTF-16 Big Endian BOM: FE FF
+            return new InputStreamReader(new ByteArrayInputStream(bytes, 2, length - 2), UTF_16BE);
+          }
+          break;
+        }
+      case (byte) 0xFF:
+        {
+          if (length >= 2 && bytes[1] == (byte) 0xFE) {
+            if (length >= 4 && bytes[2] == (byte) 0x00 && bytes[3] == (byte) 0x00) {
+              // UTF-32 Little Endian BOM: FF FE 00 00
+              return new InputStreamReader(
+                  new ByteArrayInputStream(bytes, 4, length - 4), "UTF-32LE");
+            }
+
+            // UTF-16 Little Endian BOM: FF FE
+            return new InputStreamReader(new ByteArrayInputStream(bytes, 2, length - 2), UTF_16LE);
+          }
+          break;
+        }
+      case (byte) 0x00:
+        {
+          if (length >= 4
+              && bytes[0] == (byte) 0x00
+              && bytes[1] == (byte) 0x00
+              && bytes[2] == (byte) 0xFE
+              && bytes[3] == (byte) 0xFF) {
+            // UTF-32 Big Endian BOM: 00 00 FE FF
+            return new InputStreamReader(
+                new ByteArrayInputStream(bytes, 4, length - 4), "UTF-32BE");
+          }
+          break;
+        }
+    }
+
+    // No byte order mark: Assume UTF-8 (where the BOM is optional).
+    return new InputStreamReader(new ByteArrayInputStream(bytes), UTF_8);
+  }
+
+  public static class ShrinkerResult {
+    private final Set<String> resFolderEntriesToKeep;
+    private final byte[] resourceTableInProtoFormat;
+
+    public ShrinkerResult(Set<String> resFolderEntriesToKeep, byte[] resourceTableInProtoFormat) {
+      this.resFolderEntriesToKeep = resFolderEntriesToKeep;
+      this.resourceTableInProtoFormat = resourceTableInProtoFormat;
+    }
+
+    public byte[] getResourceTableInProtoFormat() {
+      return resourceTableInProtoFormat;
+    }
+
+    public Set<String> getResFolderEntriesToKeep() {
+      return resFolderEntriesToKeep;
+    }
+  }
+
+  private static class PathAndBytes {
+    private final byte[] bytes;
+    private final Path path;
+
+    private PathAndBytes(byte[] bytes, Path path) {
+      this.bytes = bytes;
+      this.path = path;
+    }
+
+    public Path getPath() {
+      return path;
+    }
+
+    public String getPathWithoutRes() {
+      assert path.toString().startsWith("res/");
+      return path.toString().substring(4);
+    }
+
+    public byte[] getBytes() {
+      return bytes;
+    }
+  }
+}
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 5f9bec6..d8ac6a9 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
@@ -32,7 +32,7 @@
     r8ResourceShrinkerModel.instantiateFromResourceTable(inputStream);
   }
 
-  private static class R8ResourceShrinkerModel extends ResourceShrinkerModel {
+  public static class R8ResourceShrinkerModel extends ResourceShrinkerModel {
 
     public R8ResourceShrinkerModel(
         ShrinkerDebugReporter debugReporter, boolean supportMultipackages) {
@@ -40,25 +40,29 @@
     }
 
     // Similar to instantiation in ProtoResourceTableGatherer, but using an inputstream.
-    public void instantiateFromResourceTable(InputStream inputStream) {
+    void instantiateFromResourceTable(InputStream inputStream) {
       try {
         ResourceTable resourceTable = ResourceTable.parseFrom(inputStream);
-        ResourceTableUtilKt.entriesSequence(resourceTable)
-            .iterator()
-            .forEachRemaining(
-                entryWrapper -> {
-                  ResourceType resourceType = ResourceType.fromClassName(entryWrapper.getType());
-                  if (resourceType != ResourceType.STYLEABLE) {
-                    this.addResource(
-                        resourceType,
-                        entryWrapper.getPackageName(),
-                        ResourcesUtil.resourceNameToFieldName(entryWrapper.getEntry().getName()),
-                        entryWrapper.getId());
-                  }
-                });
+        instantiateFromResourceTable(resourceTable);
       } catch (IOException ex) {
         throw new RuntimeException(ex);
       }
     }
+
+    void instantiateFromResourceTable(ResourceTable resourceTable) {
+      ResourceTableUtilKt.entriesSequence(resourceTable)
+          .iterator()
+          .forEachRemaining(
+              entryWrapper -> {
+                ResourceType resourceType = ResourceType.fromClassName(entryWrapper.getType());
+                if (resourceType != ResourceType.STYLEABLE) {
+                  this.addResource(
+                      resourceType,
+                      entryWrapper.getPackageName(),
+                      ResourcesUtil.resourceNameToFieldName(entryWrapper.getEntry().getName()),
+                      entryWrapper.getId());
+                }
+              });
+    }
   }
 }
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/usages/DexUsageRecorder.kt b/src/resourceshrinker/java/com/android/build/shrinker/usages/DexUsageRecorder.kt
index 3f1a676..5d6a0df 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/usages/DexUsageRecorder.kt
+++ b/src/resourceshrinker/java/com/android/build/shrinker/usages/DexUsageRecorder.kt
@@ -56,7 +56,7 @@
     }
 }
 
-private class DexFileAnalysisCallback(
+class DexFileAnalysisCallback(
         private val path: Path,
         private val model: ResourceShrinkerModel
 ) : AnalysisCallback {
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/usages/ProtoAndroidManifestUsageRecorder.kt b/src/resourceshrinker/java/com/android/build/shrinker/usages/ProtoAndroidManifestUsageRecorder.kt
index 4e6aa3e..77bb8f6 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/usages/ProtoAndroidManifestUsageRecorder.kt
+++ b/src/resourceshrinker/java/com/android/build/shrinker/usages/ProtoAndroidManifestUsageRecorder.kt
@@ -34,25 +34,25 @@
         recordUsagesFromNode(root, model)
     }
 
-    private fun recordUsagesFromNode(node: XmlNode, model: ResourceShrinkerModel) {
-        // Records only resources from element attributes that have reference items with resolved
-        // ids or names.
-        if (!node.hasElement()) {
-            return
-        }
-        node.element.attributeList.asSequence()
-            .filter { it.hasCompiledItem() }
-            .map { it.compiledItem }
-            .filter { it.hasRef() }
-            .map { it.ref }
-            .flatMap {
-                // If resource id is available prefer this id to name.
-                when {
-                    it.id != 0 -> listOfNotNull(model.resourceStore.getResource(it.id))
-                    else -> model.resourceStore.getResourcesFromUrl("@${it.name}")
-                }.asSequence()
-            }
-            .forEach { ResourceUsageModel.markReachable(it) }
-        node.element.childList.forEach { recordUsagesFromNode(it, model) }
+}
+fun recordUsagesFromNode(node: XmlNode, model: ResourceShrinkerModel) {
+    // Records only resources from element attributes that have reference items with resolved
+    // ids or names.
+    if (!node.hasElement()) {
+        return
     }
+    node.element.attributeList.asSequence()
+        .filter { it.hasCompiledItem() }
+        .map { it.compiledItem }
+        .filter { it.hasRef() }
+        .map { it.ref }
+        .flatMap {
+            // If resource id is available prefer this id to name.
+            when {
+                it.id != 0 -> listOfNotNull(model.resourceStore.getResource(it.id))
+                else -> model.resourceStore.getResourcesFromUrl("@${it.name}")
+            }.asSequence()
+        }
+        .forEach { ResourceUsageModel.markReachable(it) }
+    node.element.childList.forEach { recordUsagesFromNode(it, model) }
 }
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/usages/ToolsAttributeUsageRecorder.kt b/src/resourceshrinker/java/com/android/build/shrinker/usages/ToolsAttributeUsageRecorder.kt
index 0dae39a..8e43cd8 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/usages/ToolsAttributeUsageRecorder.kt
+++ b/src/resourceshrinker/java/com/android/build/shrinker/usages/ToolsAttributeUsageRecorder.kt
@@ -16,9 +16,11 @@
 
 package com.android.build.shrinker.usages
 
+import com.android.SdkConstants.TOOLS_NS_NAME
 import com.android.SdkConstants.VALUE_STRICT
 import com.android.build.shrinker.ResourceShrinkerModel
 import com.android.utils.XmlUtils
+import com.google.common.collect.ImmutableMap
 import com.google.common.collect.ImmutableMap.copyOf
 import java.io.Reader
 import java.nio.file.Files
@@ -37,9 +39,6 @@
  * @param rawResourcesPath path to folder with resources in raw format.
  */
 class ToolsAttributeUsageRecorder(val rawResourcesPath: Path) : ResourceUsageRecorder {
-    companion object {
-        private val TOOLS_NAMESPACE = "http://schemas.android.com/tools"
-    }
 
     override fun recordUsages(model: ResourceShrinkerModel) {
         Files.walk(rawResourcesPath)
@@ -48,42 +47,48 @@
     }
 
     private fun processRawXml(path: Path, model: ResourceShrinkerModel) {
-        processResourceToolsAttributes(path).forEach { key, value ->
-            when (key) {
-                "keep" -> model.resourceStore.recordKeepToolAttribute(value)
-                "discard" -> model.resourceStore.recordDiscardToolAttribute(value)
-                "shrinkMode" ->
-                    if (value == VALUE_STRICT) {
-                        model.resourceStore.safeMode = false
-                    }
-            }
-        }
-    }
-
-    private fun processResourceToolsAttributes(path: Path): Map<String, String> {
-        val toolsAttributes = mutableMapOf<String, String>()
-        XmlUtils.getUtfReader(path.toFile()).use { reader: Reader ->
-            val factory = XMLInputFactory.newInstance()
-            val xmlStreamReader = factory.createXMLStreamReader(reader)
-
-            var rootElementProcessed = false
-            while (!rootElementProcessed && xmlStreamReader.hasNext()) {
-                xmlStreamReader.next()
-                if (xmlStreamReader.isStartElement) {
-                    if (xmlStreamReader.localName == "resources") {
-                        for (i in 0 until xmlStreamReader.attributeCount) {
-                            if (xmlStreamReader.getAttributeNamespace(i) == TOOLS_NAMESPACE) {
-                                toolsAttributes.put(
-                                    xmlStreamReader.getAttributeLocalName(i),
-                                    xmlStreamReader.getAttributeValue(i)
-                                )
-                            }
-                        }
-                    }
-                    rootElementProcessed = true
-                }
-            }
-        }
-        return copyOf(toolsAttributes)
+        processRawXml(XmlUtils.getUtfReader(path.toFile()), model)
     }
 }
+
+fun processRawXml(reader: Reader, model: ResourceShrinkerModel) {
+    processResourceToolsAttributes(reader).forEach { key, value ->
+        when (key) {
+            "keep" -> model.resourceStore.recordKeepToolAttribute(value)
+            "discard" -> model.resourceStore.recordDiscardToolAttribute(value)
+            "shrinkMode" ->
+                if (value == VALUE_STRICT) {
+                    model.resourceStore.safeMode = false
+                }
+        }
+    }
+}
+
+fun processResourceToolsAttributes(utfReader: Reader?): ImmutableMap<String, String> {
+    val toolsAttributes = mutableMapOf<String, String>()
+    utfReader.use { reader: Reader? ->
+        val factory = XMLInputFactory.newInstance()
+        val xmlStreamReader = factory.createXMLStreamReader(reader)
+
+        var rootElementProcessed = false
+        while (!rootElementProcessed && xmlStreamReader.hasNext()) {
+            xmlStreamReader.next()
+            if (xmlStreamReader.isStartElement) {
+                if (xmlStreamReader.localName == "resources") {
+                    for (i in 0 until xmlStreamReader.attributeCount) {
+                        val namespace = "http://schemas.android.com/tools"
+                        if (xmlStreamReader.getAttributeNamespace(i) == namespace) {
+                            toolsAttributes.put(
+                                xmlStreamReader.getAttributeLocalName(i),
+                                xmlStreamReader.getAttributeValue(i)
+                            )
+                        }
+                    }
+                }
+                rootElementProcessed = true
+            }
+        }
+    }
+    return copyOf(toolsAttributes)
+}
+