Skip to content

Commit

Permalink
App/Activity: Let the FileProvider load bundled model files
Browse files Browse the repository at this point in the history
This patch makes the ModelFileProvider pre-load the model files, which
are packaged into the APK file. According to this change, a test case
for verifyng the number of models preloaded has also been updated.

Signed-off-by: Wook Song <[email protected]>
  • Loading branch information
wooksong committed Jul 5, 2024
1 parent ec583f5 commit 34d82a4
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 54 deletions.
2 changes: 1 addition & 1 deletion ml_inference_offloading/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true"
android:name="androidx.core.content.FileProvider">
android:name=".providers.ModelFileProvider">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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 {
Expand All @@ -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<ModelInfo>()
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<RecyclerView>(R.id.model_list)
recyclerView.adapter = ModelAdapter(modelList)
recyclerView.layoutManager = LinearLayoutManager(this)
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package ai.nnstreamer.ml.inference.offloading


import ai.nnstreamer.ml.inference.offloading.providers.ModelFileProvider
import androidx.recyclerview.widget.RecyclerView

import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
Expand All @@ -13,13 +12,22 @@ import org.robolectric.RobolectricTestRunner
class ActivityUnitTest {
@Test
fun testModelListLength() {
val activityController = Robolectric.buildActivity(MainActivity::class.java).create()
val activity = activityController.get()
val recyclerview = activity.findViewById<RecyclerView>(R.id.model_list)
val adapter = recyclerview.adapter
val testModelFileProvider =
Robolectric.buildContentProvider(ModelFileProvider::class.java).create().get()
val activity = testModelFileProvider?.let {
val activityController =
Robolectric.buildActivity(MainActivity::class.java).create()

activityController.get()
}

activity?.run {
val recyclerview = this.findViewById<RecyclerView>(R.id.model_list)
val numModelsInAssets =
this.applicationContext.resources.assets.list("models")?.size ?: -1
val numModels = recyclerview.adapter?.itemCount ?: 0

adapter?.run {
assertEquals(2, adapter.itemCount)
assertEquals(numModelsInAssets, numModels)
}
}
}

0 comments on commit 34d82a4

Please sign in to comment.