From ebed8a576202baf4a63812996d71f2a0758b4831 Mon Sep 17 00:00:00 2001 From: Wook Song Date: Wed, 3 Jul 2024 19:31:44 +0900 Subject: [PATCH] App/Activity: Let the FileProvider load bundled model files This patch makes the ModelFileProvider pre-load the model files, which are packaged into the APK file. Signed-off-by: Wook Song --- .../src/main/AndroidManifest.xml | 2 +- .../ml/inference/offloading/MainActivity.kt | 74 ++++++++----------- .../offloading/providers/ModelFileProvider.kt | 56 ++++++++++++++ 3 files changed, 86 insertions(+), 46 deletions(-) create mode 100644 ml_inference_offloading/src/main/java/ai/nnstreamer/ml/inference/offloading/providers/ModelFileProvider.kt diff --git a/ml_inference_offloading/src/main/AndroidManifest.xml b/ml_inference_offloading/src/main/AndroidManifest.xml index 972c4c0..8846c1d 100644 --- a/ml_inference_offloading/src/main/AndroidManifest.xml +++ b/ml_inference_offloading/src/main/AndroidManifest.xml @@ -37,7 +37,7 @@ android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true" - android:name="androidx.core.content.FileProvider"> + android:name=".providers.ModelFileProvider"> diff --git a/ml_inference_offloading/src/main/java/ai/nnstreamer/ml/inference/offloading/MainActivity.kt b/ml_inference_offloading/src/main/java/ai/nnstreamer/ml/inference/offloading/MainActivity.kt index 7155914..85070e8 100644 --- a/ml_inference_offloading/src/main/java/ai/nnstreamer/ml/inference/offloading/MainActivity.kt +++ b/ml_inference_offloading/src/main/java/ai/nnstreamer/ml/inference/offloading/MainActivity.kt @@ -7,7 +7,6 @@ import android.content.Intent import android.content.ServiceConnection import android.os.Bundle import android.os.IBinder -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -22,7 +21,6 @@ import androidx.compose.ui.Modifier import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import java.io.File -import java.io.IOException // todo: Define DTO with generality and extensibility data class ModelInfo( @@ -89,29 +87,6 @@ class MainActivity : ComponentActivity() { } } - private fun copyFilesToExternalDir() { - val am = resources.assets - - am.list("models/")?.let { fileArray -> - // Copy files into app-specific directory. - fileArray.forEach { fileName -> - try { - val inFile = am.open("models/$fileName") - inFile.use { stream -> - val outDir = getExternalFilesDir(null).toString() - File(outDir, fileName).outputStream().use { - stream.copyTo(it) - } - } - } catch (e: IOException) { - Log.e(TAG, "Failed to copy file: $fileName") - e.printStackTrace() - return - } - } - } - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { @@ -124,31 +99,40 @@ class MainActivity : ComponentActivity() { } } setContentView(R.layout.activity_main) - - copyFilesToExternalDir() startForegroundService(Intent(this, MainService::class.java)) // todo: Use database instead of just ArrayList val modelList = ArrayList() - val path = getExternalFilesDir(null)!!.absolutePath - - val mobileNet = File("$path/mobilenet_v1_1.0_224_quant.tflite") - val mobileNetFilter = - "other/tensor,format=static,dimension=(string)3:224:224:1,type=uint8,framerate=0/1 ! " + - "tensor_filter framework=tensorflow-lite model=" + mobileNet.absolutePath + " ! " + - "other/tensor,format=static,dimension=(string)1001:1,type=uint8,framerate=0/1" - val mobileNetInfo = ModelInfo("MobileNet", mobileNetFilter) - modelList.add(mobileNetInfo) - - val yolov8 = File("$path/yolov8s_float32.tflite") - val yolov8Filter = - "other/tensors,num_tensors=1,format=static,dimensions=3:224:224:1,types=float32,framerate=0/1 ! " + - "tensor_filter framework=tensorflow-lite model=" + yolov8.absolutePath + " ! " + - "other/tensors,num_tensors=1,types=float32,format=static,dimensions=1029:84:1,framerate=0/1" - - val yolov8Info = ModelInfo("Yolov8", yolov8Filter) - modelList.add(yolov8Info) + val privateExternalRoot = getExternalFilesDir(null) + val privateExternalModels = privateExternalRoot?.resolve("models") + + if (privateExternalModels?.isDirectory == true) { + privateExternalModels.list()?.onEach { + when (it.toString()) { + // todo: Changes below are temporarily + "mobilenet_v1_1.0_224_quant.tflite" -> { + val mobileNet = + File("$privateExternalModels/$it") + val mobileNetFilter = + "other/tensor,format=static,dimension=(string)3:224:224:1,type=uint8,framerate=0/1 ! " + + "tensor_filter framework=tensorflow-lite model=" + mobileNet.absolutePath + " ! " + + "other/tensor,format=static,dimension=(string)1001:1,type=uint8,framerate=0/1" + val mobileNetInfo = ModelInfo("MobileNet", mobileNetFilter) + modelList.add(mobileNetInfo) + } + "yolov8s_float32.tflite" -> { + val yolov8 = File("$privateExternalModels/$it") + val yolov8Filter = + "other/tensors,num_tensors=1,format=static,dimensions=3:224:224:1,types=float32,framerate=0/1 ! " + + "tensor_filter framework=tensorflow-lite model=" + yolov8.absolutePath + " ! " + + "other/tensors,num_tensors=1,types=float32,format=static,dimensions=1029:84:1,framerate=0/1" + val yolov8Info = ModelInfo("Yolov8", yolov8Filter) + modelList.add(yolov8Info) + } + } + } + } val recyclerView = findViewById(R.id.model_list) recyclerView.adapter = ModelAdapter(modelList) recyclerView.layoutManager = LinearLayoutManager(this) diff --git a/ml_inference_offloading/src/main/java/ai/nnstreamer/ml/inference/offloading/providers/ModelFileProvider.kt b/ml_inference_offloading/src/main/java/ai/nnstreamer/ml/inference/offloading/providers/ModelFileProvider.kt new file mode 100644 index 0000000..309075f --- /dev/null +++ b/ml_inference_offloading/src/main/java/ai/nnstreamer/ml/inference/offloading/providers/ModelFileProvider.kt @@ -0,0 +1,56 @@ +package ai.nnstreamer.ml.inference.offloading.providers + +import ai.nnstreamer.ml.inference.offloading.R +import android.content.Context +import androidx.core.content.FileProvider +import java.io.FileOutputStream +import java.io.IOException + +class ModelFileProvider : FileProvider(R.xml.file_paths) { + companion object { + val assetPaths = listOf("models") + } + + private fun copyAssetsToExternal(ctx: Context, path: String) { + val resAssets = ctx.resources.assets + val externalDir = ctx.getExternalFilesDir(null)?.resolve(path) + ?: throw IOException("Failed to resolve $path in the App's private external storage") + + try { + if (!externalDir.isDirectory && !externalDir.mkdir()) { + throw IOException("Failed to create $externalDir") + } + } catch (e: SecurityException) { + val msg = e.message ?: "none" + + throw IOException("Failed to access $externalDir for the security reason (details: $msg)") + } + + + resAssets.list(path)?.run { + filterNotNull().onEach { file -> + val os = FileOutputStream(externalDir.resolve(file)) + + resAssets.open("$path/$file").use { stream -> + stream.copyTo(os) + } + } + } + } + + @Override + override fun onCreate(): Boolean { + context?.run { + assetPaths.onEach { path -> + try { + copyAssetsToExternal(this, path) + } catch (e: Exception) { + throw RuntimeException("Failed to preload $path in the APK's assets directory") + } + } + } + + return super.onCreate() + } + +}