Skip to content

Commit

Permalink
Updates implementation to use streams
Browse files Browse the repository at this point in the history
  • Loading branch information
garanj committed Jul 23, 2024
1 parent 50aa3c9 commit bf519c0
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public Boolean isWatchFaceXml() {

public Boolean isDrawable() { return "drawable".equals(resourceType); }

public Boolean isFont() { return resourceType.equals("font"); }
public Boolean isFont() { return "font".equals(resourceType); }

public Boolean isAsset() { return "asset".equals(resourceType); }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
Expand All @@ -32,33 +31,29 @@
* Where obfuscation has been applied, the mapping is derived, for the creation of the
* AndroidResource objects. For example, for a logical resource res/raw/watchface.xml, an APK
* may in fact store this as res/aB.xml. The resources.arsc file contains this mapping, and the
* AndroidResourceTable provides a list of resources with their logical types, names and data.
* AndroidResourceLoader provides a stream of resources with their logical types, names and data.
* <p>
* Note that more than one AndroidResource object can exist for the given dimensions. For example,
* if there is a drawable and a drawable-fr folder, then there may be multiple AndroidResource
* entries for drawables with the same type, name and extension. The configuration detail, e.g.
* "fr" or "default", is not currently exposed in the AndroidResource objects as it isn't used.
*/
public class AndroidResourceTable {
public class AndroidResourceLoader {
// Only certain resource types are of interest to the evaluator, notably, not string resources.
private static final Set<String> RESOURCE_TYPES = Set.of("raw", "xml", "drawable", "font", "asset");
private static final String RESOURCES_FILE_NAME = "resources.arsc";
private final List<AndroidResource> resourceTable;

private AndroidResourceTable(List<AndroidResource> resources) {
this.resourceTable = resources;
}
private AndroidResourceLoader() { }

/**
* Creates the table from a path to an AAB structure on the file system.
* Creates a resource stream from a path to an AAB structure on the file system.
*
* @param aabPath The path to the root of the AAB directory.
* @return The constructed table.
* @return A stream of Android resource objects.
* @throws IOException when the resources file cannot be found, or other IO errors occur.
*/
static Stream<AndroidResource> streamFromAabDirectory(Path aabPath) throws IOException {
Stream<Path> childrenFilesStream = java.nio.file.Files.walk(aabPath);
ArrayList<AndroidResource> resources = new ArrayList<>();
int relativePathOffset = aabPath.toString().length() + 1;

return childrenFilesStream
Expand Down Expand Up @@ -93,37 +88,12 @@ static Stream<AndroidResource> streamFromAabDirectory(Path aabPath) throws IOExc
}

/**
* Creates the table from an APK file.
* Creates a stream of resource objects from the AAB file.
*
* @param zipFile The zip file object representing the APK.
* @return The constructed table.
* @throws IOException when the resources file cannot be found, or other IO errors occur.
*/
// static Stream<AndroidResource> createFromApkFile(ZipFile zipFile) throws IOException {
// ZipEntry arscEntry = new ZipEntry(RESOURCES_FILE_NAME);
//
// AndroidResourceTable table;
// try (InputStream is = zipFile.getInputStream(arscEntry)) {
// Function<Path, byte[]> fn = (Path path) -> {
// try {
// return zipFile.getInputStream(new ZipEntry(path.toString())).readAllBytes();
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// };
// table = createTable(is, fn);
// }
// return table;
// }

/**
* Creates the table from an APK file.
*
* @param aabZipFile The zip file object representing the APK.
* @return The constructed table.
* @param aabZipFile The zip file object representing the AAB.
* @return A stream of resource objects.
*/
static Stream<AndroidResource> streamFromAabFile(ZipFile aabZipFile) {
ArrayList<AndroidResource> resources = new ArrayList<>();
return aabZipFile.stream()
.map(
zipEntry -> {
Expand Down Expand Up @@ -157,14 +127,13 @@ static Stream<AndroidResource> streamFromAabFile(ZipFile aabZipFile) {
}

/**
* Creates the table from a base split entry within an archive
* Creates a resource stream from a base split entry within an archive
*
* @param baseSplitZipStream The zip entry for the base split.
* @return The constructed table.
* @return A stream of resource objects.
* @throws IOException when the resources file cannot be found, or other IO errors occur.
*/
static Stream<AndroidResource> streamFromMokkaZip(ZipInputStream baseSplitZipStream) throws IOException {
List<AndroidResource> resources = new ArrayList<>();

Iterator<AndroidResource> iterator =
new Iterator<AndroidResource>() {
Expand All @@ -176,13 +145,11 @@ public boolean hasNext() {
zipEntry = baseSplitZipStream.getNextEntry();
// Advance over entries in the zip that aren't relevant.
while (zipEntry != null && !AndroidResource.isValidResourcePath(zipEntry.getName())) {
System.out.println("Not valid:" + (zipEntry.getName()));
zipEntry = baseSplitZipStream.getNextEntry();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
System.out.println("Has next:" + (zipEntry != null));
return zipEntry != null;
}

Expand All @@ -204,26 +171,14 @@ public AndroidResource next() {
false);
}

/** Read all bytes from an input stream to a new byte array. */
static byte[] readAllBytes(InputStream inputStream) throws IOException {
int len;
byte[] buffer = new byte[1024];
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while ((len = inputStream.read(buffer)) > 0) {
bos.write(buffer, 0, len);
}
return bos.toByteArray();
}


/**
* Creates the table from an InputStream.
* Creates a resource stream from an APK zip file.
*
* APK files can have their resources obfuscated, so it is necessary to extract the mapping
* between the original path and the path in the obfuscated zip.
*
* @param is The InputStream
* @param fileDataProducer A lambda that returns the raw bytes of a file, given a path, which
* may represent a zip file, or a directory (for example), that is the
* source of those bytes.
* @return The constructed table.
* @param zipFile The APK zip file
* @return A stream of resource objects.
* @throws IOException when errors loading resources occur.
*/
static Stream<AndroidResource> streamFromApkFile(ZipFile zipFile) throws IOException {
Expand All @@ -247,29 +202,36 @@ static Stream<AndroidResource> streamFromApkFile(ZipFile zipFile) throws IOExcep
.toList();

return typeChunks.stream()
.flatMap(c -> c.getEntries().values().stream())
.filter(t -> RESOURCE_TYPES.contains(t.typeName()))
.filter(t -> t.value().type() == BinaryResourceValue.Type.STRING)
.map(entry -> {
Path path = Path.of(stringPool.getString(entry.value().data()));
byte[] data = null;
try {
data = zipFile.getInputStream(new ZipEntry(path.toString())).readAllBytes();
} catch (IOException e) {
throw new RuntimeException(e);
}
.flatMap(c -> c.getEntries().values().stream())
.filter(t -> RESOURCE_TYPES.contains(t.typeName()))
.filter(t -> t.value().type() == BinaryResourceValue.Type.STRING)
.map(entry -> {
Path path = Path.of(stringPool.getString(entry.value().data()));
byte[] data = null;
try {
data = zipFile.getInputStream(new ZipEntry(path.toString())).readAllBytes();
} catch (IOException e) {
throw new RuntimeException(e);
}

return new AndroidResource(
entry.parent().getTypeName(),
entry.key(),
Files.getFileExtension(path.toString()),
path,
data
);
});
return new AndroidResource(
entry.parent().getTypeName(),
entry.key(),
Files.getFileExtension(path.toString()),
path,
data
);
});
}

public List<AndroidResource> getAllResources() {
return this.resourceTable;
/** Read all bytes from an input stream to a new byte array. */
static byte[] readAllBytes(InputStream inputStream) throws IOException {
int len;
byte[] buffer = new byte[1024];
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while ((len = inputStream.read(buffer)) > 0) {
bos.write(buffer, 0, len);
}
return bos.toByteArray();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ static InputPackage openFromAabDirectory(File aabDirectory) {
@Override
public Stream<AndroidResource> getWatchFaceFiles() {
try {
AndroidResourceTable table = AndroidResourceTable.createFromAabDirectory(rootPath);
return table.getAllResources().stream();
return AndroidResourceLoader.streamFromAabDirectory(rootPath);
} catch (IOException e) {
throw new RuntimeException(e);
}
Expand All @@ -118,8 +117,7 @@ static InputPackage openFromApkFile(String apkPath) throws IOException {
@Override
public Stream<AndroidResource> getWatchFaceFiles() {
try {
AndroidResourceTable table = AndroidResourceTable.createFromApkFile(zipFile);
return table.getAllResources().stream();
return AndroidResourceLoader.streamFromApkFile(zipFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
Expand All @@ -145,8 +143,7 @@ static InputPackage openFromAabFile(String aabPath) throws IOException {
return new InputPackage() {
@Override
public Stream<AndroidResource> getWatchFaceFiles() {
AndroidResourceTable table = AndroidResourceTable.createFromAabFile(zipFile);
return table.getAllResources().stream();
return AndroidResourceLoader.streamFromAabFile(zipFile);
}

@Override
Expand Down Expand Up @@ -183,8 +180,7 @@ static InputPackage openFromMokkaZip(String zipPath) throws IOException {
@Override
public Stream<AndroidResource> getWatchFaceFiles() {
try {
AndroidResourceTable table = AndroidResourceTable.createFromMokkaZip(baseSplitApkZip);
return table.getAllResources().stream();
return AndroidResourceLoader.streamFromMokkaZip(baseSplitApkZip);
} catch (IOException e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ Optional<DrawableResourceDetails> getDrawableResourceDetails(String name) throws
return fromPackageFile(
new InputPackage.PackageFile(
FileSystems.getDefault().getPath("res", "drawable", name),
AndroidResourceTable.readAllBytes(is)));
AndroidResourceLoader.readAllBytes(is)));
}
}
}

0 comments on commit bf519c0

Please sign in to comment.