| // 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.tools.r8; |
| |
| import com.android.tools.r8.AndroidResourceInput.Kind; |
| import com.android.tools.r8.keepanno.annotations.KeepForApi; |
| import com.android.tools.r8.origin.ArchiveEntryOrigin; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.origin.PathOrigin; |
| import com.android.tools.r8.utils.FileUtils; |
| import com.google.common.io.ByteStreams; |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.nio.charset.StandardCharsets; |
| import java.nio.file.Path; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Enumeration; |
| import java.util.List; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipFile; |
| |
| /** |
| * Provides android resources from an archive. |
| * |
| * <p>The descriptor index is built eagerly upon creating the provider and subsequent requests for |
| * resources in the descriptor set will then force the read of zip entry contents. |
| */ |
| @KeepForApi |
| public class ArchiveProtoAndroidResourceProvider implements AndroidResourceProvider { |
| private final Path archive; |
| private final Origin origin; |
| |
| private static final String MANIFEST_NAME = "AndroidManifest.xml"; |
| private static final String RESOURCE_TABLE = "resources.pb"; |
| private static final String RES_FOLDER = "res/"; |
| private static final String RES_RAW_FOLDER = RES_FOLDER + "raw/"; |
| private static final String XML_SUFFIX = ".xml"; |
| |
| /** |
| * Creates an android resource provider from an archive. |
| * |
| * @param archive Zip archive to provide resources from. |
| * @param origin Origin of the archive. |
| */ |
| public ArchiveProtoAndroidResourceProvider(Path archive, Origin origin) { |
| this.archive = archive; |
| this.origin = origin; |
| } |
| |
| /** |
| * Creates an android resource provider from an archive. |
| * |
| * @param archive Zip archive to provide resources from. |
| */ |
| public ArchiveProtoAndroidResourceProvider(Path archive) { |
| this(archive, new PathOrigin(archive)); |
| } |
| |
| @Override |
| public Collection<AndroidResourceInput> getAndroidResources() throws ResourceException { |
| try (ZipFile zipFile = FileUtils.createZipFile(archive.toFile(), StandardCharsets.UTF_8)) { |
| List<AndroidResourceInput> resources = new ArrayList<>(); |
| final Enumeration<? extends ZipEntry> entries = zipFile.entries(); |
| while (entries.hasMoreElements()) { |
| ZipEntry entry = entries.nextElement(); |
| String name = entry.getName(); |
| Kind kind = getKindFromName(name); |
| ByteAndroidResourceInput resource = |
| new ByteAndroidResourceInput( |
| name, |
| kind, |
| ByteStreams.toByteArray(zipFile.getInputStream(entry)), |
| new ArchiveEntryOrigin(name, origin)); |
| resources.add(resource); |
| // We explicitly add any res/raw folder xml file also as a keep rule file. |
| if (kind == Kind.XML_FILE && name.startsWith(RES_RAW_FOLDER)) { |
| ByteAndroidResourceInput keepResource = |
| new ByteAndroidResourceInput( |
| name, |
| Kind.KEEP_RULE_FILE, |
| ByteStreams.toByteArray(zipFile.getInputStream(entry)), |
| new ArchiveEntryOrigin(name, origin)); |
| resources.add(keepResource); |
| } |
| } |
| return resources; |
| } catch (IOException e) { |
| throw new ResourceException(origin, e); |
| } |
| } |
| |
| private Kind getKindFromName(String name) { |
| if (name.equals(MANIFEST_NAME)) { |
| return Kind.MANIFEST; |
| } |
| if (name.equals(RESOURCE_TABLE)) { |
| return Kind.RESOURCE_TABLE; |
| } |
| if (!name.startsWith(RES_FOLDER)) { |
| return Kind.UNKNOWN; |
| } |
| if (name.endsWith(XML_SUFFIX)) { |
| return Kind.XML_FILE; |
| } |
| return Kind.RES_FOLDER_FILE; |
| } |
| |
| private static class ByteAndroidResourceInput implements AndroidResourceInput { |
| |
| private final String name; |
| private final Kind kind; |
| private final byte[] bytes; |
| private final Origin origin; |
| |
| public ByteAndroidResourceInput(String name, Kind kind, byte[] bytes, Origin origin) { |
| this.name = name; |
| this.kind = kind; |
| this.bytes = bytes; |
| this.origin = origin; |
| } |
| |
| @Override |
| public ResourcePath getPath() { |
| return () -> name; |
| } |
| |
| @Override |
| public Kind getKind() { |
| return kind; |
| } |
| |
| @Override |
| public InputStream getByteStream() throws ResourceException { |
| return new ByteArrayInputStream(bytes); |
| } |
| |
| @Override |
| public Origin getOrigin() { |
| return origin; |
| } |
| } |
| } |