cpython/Android/testbed/app/src/main/java/org/python/testbed/MainActivity.kt

package org.python.testbed

import android.content.Context
import android.os.*
import android.system.Os
import android.widget.TextView
import androidx.appcompat.app.*
import java.io.*


// Launching the tests from an activity is OK for a quick check, but for
// anything more complicated it'll be more convenient to use `android.py test`
// to launch the tests via PythonSuite.
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val status = PythonTestRunner(this).run("-W -uall")
        findViewById<TextView>(R.id.tvHello).text = "Exit status $status"
    }
}


class PythonTestRunner(val context: Context) {
    /** @param args Extra arguments for `python -m test`.
     * @return The Python exit status: zero if the tests passed, nonzero if
     * they failed. */
    fun run(args: String = "") : Int {
        Os.setenv("PYTHON_ARGS", args, true)

        // Python needs this variable to help it find the temporary directory,
        // but Android only sets it on API level 33 and later.
        Os.setenv("TMPDIR", context.cacheDir.toString(), false)

        val pythonHome = extractAssets()
        System.loadLibrary("main_activity")
        redirectStdioToLogcat()

        // The main module is in src/main/python/main.py.
        return runPython(pythonHome.toString(), "main")
    }

    private fun extractAssets() : File {
        val pythonHome = File(context.filesDir, "python")
        if (pythonHome.exists() && !pythonHome.deleteRecursively()) {
            throw RuntimeException("Failed to delete $pythonHome")
        }
        extractAssetDir("python", context.filesDir)
        return pythonHome
    }

    private fun extractAssetDir(path: String, targetDir: File) {
        val names = context.assets.list(path)
            ?: throw RuntimeException("Failed to list $path")
        val targetSubdir = File(targetDir, path)
        if (!targetSubdir.mkdirs()) {
            throw RuntimeException("Failed to create $targetSubdir")
        }

        for (name in names) {
            val subPath = "$path/$name"
            val input: InputStream
            try {
                input = context.assets.open(subPath)
            } catch (e: FileNotFoundException) {
                extractAssetDir(subPath, targetDir)
                continue
            }
            input.use {
                File(targetSubdir, name).outputStream().use { output ->
                    input.copyTo(output)
                }
            }
        }
    }

    private external fun redirectStdioToLogcat()
    private external fun runPython(home: String, runModule: String) : Int
}