Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c07aa702 authored by Ryan Mitchell's avatar Ryan Mitchell
Browse files

Add ResourcesProvider.loadFromDirectory

This API allows a directory to be loaded as if it was a zipped APK.
This is a substitute for the DirectoryAssetProvider API that
currently does not work in the native layer.

Bug: 142716192
Test: atest FrameworksResourceLoaderTests
Change-Id: Ia13e15653e75b421423dd56f9fe89e183ab4cb9a
parent 349695f3
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -12871,6 +12871,7 @@ package android.content.res.loader {
    method @Nullable public android.content.res.loader.AssetsProvider getAssetsProvider();
    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.ParcelFileDescriptor, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromDirectory(@NonNull String, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromSplit(@NonNull android.content.Context, @NonNull String) throws java.io.IOException;
    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromTable(@NonNull android.os.ParcelFileDescriptor, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
  }
+18 −0
Original line number Diff line number Diff line
@@ -86,11 +86,15 @@ public final class ApkAssets {
    /** The path used to load the apk assets represents an resources.arsc file. */
    private static final int FORMAT_ARSC = 2;

    /** the path used to load the apk assets represents a directory. */
    private static final int FORMAT_DIR = 3;

    // Format types that change how the apk assets are loaded.
    @IntDef(prefix = { "FORMAT_" }, value = {
            FORMAT_APK,
            FORMAT_IDMAP,
            FORMAT_ARSC,
            FORMAT_DIR
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface FormatType {}
@@ -226,6 +230,20 @@ public final class ApkAssets {
        return new ApkAssets(FORMAT_ARSC, fd, friendlyName, offset, length, flags);
    }

    /**
     * Creates a new ApkAssets instance from the given directory path. The directory should have the
     * file structure of an APK.
     *
     * @param path The path to a directory on disk.
     * @param flags flags that change the behavior of loaded apk assets
     * @return a new instance of ApkAssets.
     * @throws IOException if a disk I/O error or parsing error occurred.
     */
    public static @NonNull ApkAssets loadFromDir(@NonNull String path,
            @PropertyFlags int flags) throws IOException {
        return new ApkAssets(FORMAT_DIR, path, flags);
    }

    /**
     * Generates an entirely empty ApkAssets. Needed because the ApkAssets instance and presence
     * is required for a lot of APIs, and it's easier to have a non-null reference rather than
+16 −0
Original line number Diff line number Diff line
@@ -212,6 +212,22 @@ public class ResourcesProvider implements AutoCloseable, Closeable {
                null);
    }

    /**
     * Creates a ResourcesProvider from a directory path.
     *
     * File-based resources will be resolved within the directory as if the directory is an APK.
     *
     * @param path the path of the directory to treat as an APK
     * @param assetsProvider the assets provider that overrides the loading of file-based resources
     */
    @NonNull
    public static ResourcesProvider loadFromDirectory(@NonNull String path,
            @Nullable AssetsProvider assetsProvider) throws IOException {
        return new ResourcesProvider(ApkAssets.loadFromDir(path, ApkAssets.PROPERTY_LOADER),
                assetsProvider);
    }


    private ResourcesProvider(@NonNull ApkAssets apkAssets,
            @Nullable AssetsProvider assetsProvider) {
        this.mApkAssets = apkAssets;
+8 −2
Original line number Diff line number Diff line
@@ -48,6 +48,9 @@ enum : format_type_t {

  // The path used to load the apk assets represents an resources.arsc file.
  FORMAT_ARSC = 2,

  // The path used to load the apk assets represents the a directory.
  FORMAT_DIRECTORY = 3,
};

static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t format,
@@ -70,6 +73,9 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t forma
    case FORMAT_ARSC:
      apk_assets = ApkAssets::LoadTable(path.c_str(), property_flags);
      break;
    case FORMAT_DIRECTORY:
      apk_assets = ApkAssets::LoadFromDir(path.c_str(), property_flags);
      break;
    default:
      const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
      jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
@@ -224,8 +230,8 @@ static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring fil
  }

  const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
  std::unique_ptr<Asset> asset = apk_assets->Open(path_utf8.c_str(),
                                                  Asset::AccessMode::ACCESS_RANDOM);
  std::unique_ptr<Asset> asset = apk_assets->GetAssetsProvider()->Open(
      path_utf8.c_str(),Asset::AccessMode::ACCESS_RANDOM);
  if (asset == nullptr) {
    jniThrowException(env, "java/io/FileNotFoundException", path_utf8.c_str());
    return 0;
+37 −0
Original line number Diff line number Diff line
@@ -28,6 +28,8 @@ import org.junit.After
import org.junit.Before
import java.io.Closeable
import java.io.FileOutputStream
import java.io.File
import java.util.zip.ZipInputStream

abstract class ResourceLoaderTestBase {
    protected val PROVIDER_ONE: String = "FrameworksResourceLoaderTests_ProviderOne"
@@ -134,7 +136,41 @@ abstract class ResourceLoaderTestBase {
        DataType.SPLIT -> {
            ResourcesProvider.loadFromSplit(context, "${this}_Split")
        }
        DataType.DIRECTORY -> {
            ResourcesProvider.loadFromDirectory(zipToDir("${this}.apk").absolutePath, null)
        }
    }


    /** Extracts an archive-based asset into a directory on disk. */
    private fun zipToDir(name : String, suffix : String = "") : File {
        val root = File(context.filesDir, name.split('.')[0] + suffix)
        if (root.exists()) {
            return root
        }

        root.mkdir()
        ZipInputStream(context.assets.open(name)).use { zis ->
            while (true) {
                val entry = zis.nextEntry ?: break
                val file = File(root, entry.name)
                if (entry.isDirectory) {
                    continue
                }

                file.parentFile.mkdirs()
                file.outputStream().use { output ->
                    var b = zis.read()
                    while (b != -1) {
                        output.write(b)
                        b = zis.read()
                    }
                }
            }
        }
        return root
    }


    /** Loads the asset into a temporary file stored in RAM. */
    private fun loadAssetIntoMemory(asset: AssetFileDescriptor,
@@ -175,5 +211,6 @@ abstract class ResourceLoaderTestBase {
        ARSC_RAM_MEMORY,
        ARSC_RAM_MEMORY_OFFSETS,
        SPLIT,
        DIRECTORY
    }
}
Loading