diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b9c2100..8aeacae 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -8,6 +8,7 @@ plugins { apply { plugin() plugin() + plugin() } android { diff --git a/buildSrc/src/main/kotlin/im/angry/openeuicc/build/MagiskModule.kt b/buildSrc/src/main/kotlin/im/angry/openeuicc/build/MagiskModule.kt new file mode 100644 index 0000000..1ff19a2 --- /dev/null +++ b/buildSrc/src/main/kotlin/im/angry/openeuicc/build/MagiskModule.kt @@ -0,0 +1,105 @@ +package im.angry.openeuicc.build + +import com.android.build.api.dsl.ApplicationDefaultConfig +import org.gradle.api.Plugin +import org.gradle.api.Project +import java.io.ByteArrayOutputStream +import java.net.URI +import java.net.URL +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream +import kotlin.io.path.Path +import kotlin.io.path.name + + +class MagiskModule : Plugin { + override fun apply(target: Project) { + target.tasks.register("assembleMagiskModule") { + group = "magisk" + description = "Assembles the Magisk Module" + + project.layout.buildDirectory + .file("outputs/magisk-module.zip") + .get().asFile + .writeBytes(buildMagiskModule(target)) + } + } +} + +private fun buildMagiskModule(target: Project) = buildZipFile { + val defaultConfig = target.defaultConfig + val appId = defaultConfig.applicationId + val apkPath = "system/system_ext/priv-app/OpenEUICC/OpenEUICC.apk" + put("module.prop") { + val module = buildList { + add("id" to appId.toString()) + add("name" to "OpenEUICC") + add("version" to defaultConfig.versionName + defaultConfig.versionNameSuffix.orEmpty()) + add("versionCode" to defaultConfig.versionCode.toString()) + add("author" to "Peter Cai") + add("description" to "OpenEUICC provides system-level eUICC integration") + add("updateJson" to "") + } + module + .joinToString("\n") { (key, value) -> "${key}=${value}" } + .encodeToByteArray() + } + put("customize.sh") { + val copiedApkPath = "\$TMPDIR/${Path(apkPath).name}" + val script = buildString { + appendLine("chmod u+x \"\$MODPATH/uninstall.sh\"") + + appendLine("cp \"\$MODPATH/$apkPath\" \"$copiedApkPath\"") + appendLine("pm install -r \"$copiedApkPath\"") + appendLine("rm -f \"$copiedApkPath\"") + + appendLine("pm grant \"${appId}\" android.permission.READ_PHONE_STATE") + } + script.encodeToByteArray() + } + put("uninstall.sh") { + "pm uninstall ${appId}\n".encodeToByteArray() + } + put("META-INF/com/google/android/update-binary") { + val installerUri = + URI("https://github.com/topjohnwu/Magisk/raw/bf4ed29/scripts/module_installer.sh") + val connection = URL.of(installerUri, null).openConnection() + connection.inputStream.readBytes() + } + put("META-INF/com/google/android/updater-script") { + "#MAGISK\n".encodeToByteArray() + } + put(apkPath) { + target.layout.buildDirectory + .file("outputs/apk/debug/app-debug.apk") + .get().asFile + .readBytes() + } + put("system/system_ext/etc/permissions/privapp_whitelist_${appId}.xml") { + target.rootProject + .file("privapp_whitelist_${appId}.xml") + .readBytes() + } +} + +private fun buildZipFile(builderAction: MutableMap ByteArray>.() -> Unit): ByteArray { + val out = ByteArrayOutputStream() + ZipOutputStream(out).use { + for ((name, invoke) in buildMap(builderAction)) { + val entry = ZipEntry(name) + entry.time = 0 // reproducible builds + it.putNextEntry(entry) + it.write(entry.invoke()) + it.closeEntry() + } + } + return out.toByteArray() +} + + +private val Project.defaultConfig: ApplicationDefaultConfig + get() = when (val android = extensions.findByName("android")) { + is com.android.build.gradle.AppExtension -> android.defaultConfig + is com.android.build.api.dsl.ApplicationExtension -> android.defaultConfig + else -> throw Exception("Unavailable Android configuration") + }