From 4b854bea1fe005b2708c97f0ae07b09d17c27c68 Mon Sep 17 00:00:00 2001 From: Joel Menchavez Date: Fri, 23 Jan 2026 16:21:08 +0800 Subject: [PATCH 1/3] NDK feature initial --- .../assets/AssetsInstallationHelper.kt | 18 +- .../androidide/assets/BaseAssetsInstaller.kt | 61 ++++- .../assets/BundledAssetsInstaller.kt | 2 +- .../androidide/assets/SplitAssetsInstaller.kt | 4 +- .../itsaky/androidide/utils/Environment.java | 5 + .../androidide/idetooltips/TooltipTag.kt | 1 + .../template_ndk_activity.png | Bin 0 -> 9322 bytes resources/src/main/res/values/strings.xml | 1 + .../base/AndroidModuleTemplateBuilder.kt | 2 +- .../base/modules/android/buildGradle.kt | 221 ++++++++++++++++++ .../templates/base/ndkExtensions.kt | 107 +++++++++ .../templates/impl/TemplateProviderImpl.kt | 4 +- .../impl/ndkActivity/ndkActivityTemplate.kt | 65 ++++++ .../templates/impl/ndkActivity/ndkSources.kt | 123 ++++++++++ 14 files changed, 601 insertions(+), 13 deletions(-) create mode 100644 resources/src/main/res/drawable-xxxhdpi/template_ndk_activity.png create mode 100644 templates-api/src/main/java/com/itsaky/androidide/templates/base/ndkExtensions.kt create mode 100644 templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkActivityTemplate.kt create mode 100644 templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkSources.kt diff --git a/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt b/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt index ab4b246070..ed6bde4ebc 100644 --- a/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt +++ b/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt @@ -176,14 +176,18 @@ object AssetsInstallationHelper { val freeStorage = getAvailableStorage(File(DEFAULT_ROOT)) val snapshot = - buildString { - entryStatusMap.forEach { (entry, status) -> - appendLine("$entry ${if (status == STATUS_FINISHED) "✓" else ""}") + if (percent == 100.0) { + "Post install processing in progress...." + } else { + buildString { + entryStatusMap.forEach { (entry, status) -> + appendLine("$entry ${if (status == STATUS_FINISHED) "✓" else ""}") + } + appendLine("--------------------") + appendLine("Progress: ${formatPercent(percent)}") + appendLine("Installed: ${formatBytes(installedSize)} / ${formatBytes(totalSize)}") + appendLine("Remaining storage: ${formatBytes(freeStorage)}") } - appendLine("--------------------") - appendLine("Progress: ${formatPercent(percent)}") - appendLine("Installed: ${formatBytes(installedSize)} / ${formatBytes(totalSize)}") - appendLine("Remaining storage: ${formatBytes(freeStorage)}") } if (snapshot != previousSnapshot) { diff --git a/app/src/main/java/com/itsaky/androidide/assets/BaseAssetsInstaller.kt b/app/src/main/java/com/itsaky/androidide/assets/BaseAssetsInstaller.kt index c29126ba40..b8f1a717cf 100644 --- a/app/src/main/java/com/itsaky/androidide/assets/BaseAssetsInstaller.kt +++ b/app/src/main/java/com/itsaky/androidide/assets/BaseAssetsInstaller.kt @@ -1,15 +1,74 @@ package com.itsaky.androidide.assets import android.content.Context -import com.itsaky.androidide.utils.Environment +import org.slf4j.LoggerFactory +import java.io.File import java.nio.file.Path +import com.termux.shared.termux.TermuxConstants +import com.itsaky.androidide.utils.Environment +import kotlin.system.measureTimeMillis + abstract class BaseAssetsInstaller : AssetsInstaller { + private val logger = LoggerFactory.getLogger(BaseAssetsInstaller::class.java) override suspend fun postInstall( context: Context, stagingDir: Path ) { Environment.AAPT2.setExecutable(true) + + installNdk( + File(Environment.ANDROID_HOME, Environment.NDK_TAR_XZ), + Environment.ANDROID_HOME + ) + } + + private fun installNdk(archiveFile: File, outputDir: File): Boolean { + if (!archiveFile.exists()) { + logger.debug("NDK installable package not found: ${archiveFile.absolutePath}") + return false + } + + logger.debug("Starting installation of ${archiveFile.absolutePath}") + + var exitCode: Int + var result: String + val elapsed = measureTimeMillis { + val processBuilder = ProcessBuilder( + "${TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH}/bash", + "-c", + "tar -xJf ${archiveFile.absolutePath} -C ${outputDir.absolutePath} --no-same-owner" + ) + .redirectErrorStream(true) + + val env = processBuilder.environment() + env["PATH"] = "${TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH}:${env["PATH"]}" + + val process = processBuilder.start() + + result = process.inputStream.bufferedReader().use { it.readText() } + exitCode = process.waitFor() + } + + return if (exitCode == 0) { + logger.debug("Extraction of ${archiveFile.absolutePath} successful took ${elapsed}ms : $result") + + if (archiveFile.exists()) { + val deleted = archiveFile.delete() + if (deleted) { + logger.debug("${archiveFile.absolutePath} deleted successfully.") + } else { + logger.debug("Failed to delete ${archiveFile.absolutePath}.") + } + deleted + } else { + logger.debug("Archive file not found for deletion.") + false + } + } else { + logger.error("Extraction failed with code $exitCode: $result") + false + } } } diff --git a/app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt b/app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt index d007788bc6..405181d192 100644 --- a/app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt +++ b/app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt @@ -201,7 +201,7 @@ data object BundledAssetsInstaller : BaseAssetsInstaller() { override fun expectedSize(entryName: String): Long = when (entryName) { GRADLE_DISTRIBUTION_ARCHIVE_NAME -> 63399283L - ANDROID_SDK_ZIP -> 53226785L + ANDROID_SDK_ZIP -> 254814511L DOCUMENTATION_DB -> 297763377L LOCAL_MAVEN_REPO_ARCHIVE_ZIP_NAME -> 97485855L AssetsInstallationHelper.BOOTSTRAP_ENTRY_NAME -> 124120151L diff --git a/app/src/main/java/com/itsaky/androidide/assets/SplitAssetsInstaller.kt b/app/src/main/java/com/itsaky/androidide/assets/SplitAssetsInstaller.kt index a3c6189aca..12acac4f31 100644 --- a/app/src/main/java/com/itsaky/androidide/assets/SplitAssetsInstaller.kt +++ b/app/src/main/java/com/itsaky/androidide/assets/SplitAssetsInstaller.kt @@ -65,7 +65,7 @@ data object SplitAssetsInstaller : BaseAssetsInstaller() { logger.debug("Completed extracting '{}' to dir: {}", entry.name, destDir) } - AssetsInstallationHelper.BOOTSTRAP_ENTRY_NAME -> { + AssetsInstallationHelper.BOOTSTRAP_ENTRY_NAME -> { logger.debug("Extracting 'bootstrap.zip' to dir: {}", stagingDir) val result = retryOnceOnNoSuchFile( @@ -173,7 +173,7 @@ data object SplitAssetsInstaller : BaseAssetsInstaller() { override fun expectedSize(entryName: String): Long = when (entryName) { GRADLE_DISTRIBUTION_ARCHIVE_NAME -> 137260932L - ANDROID_SDK_ZIP -> 85024182L + ANDROID_SDK_ZIP -> 286625871L DOCUMENTATION_DB -> 224296960L LOCAL_MAVEN_REPO_ARCHIVE_ZIP_NAME -> 215389106L AssetsInstallationHelper.BOOTSTRAP_ENTRY_NAME -> 456462823L diff --git a/common/src/main/java/com/itsaky/androidide/utils/Environment.java b/common/src/main/java/com/itsaky/androidide/utils/Environment.java index c10a4b2d17..152db4ef11 100755 --- a/common/src/main/java/com/itsaky/androidide/utils/Environment.java +++ b/common/src/main/java/com/itsaky/androidide/utils/Environment.java @@ -112,6 +112,9 @@ public final class Environment { "PL", "PT", "RO", "SK", "SI", "ES", "SE" }; + public static String NDK_TAR_XZ = "ndk-cmake.tar.xz"; + public static File NDK_DIR; + public static String getArchitecture() { return IDEBuildConfigProvider.getInstance().getCpuAbiName(); } @@ -176,6 +179,8 @@ public static void init(Context context) { KEYSTORE_RELEASE = new File(KEYSTORE_DIR, KEYSTORE_RELEASE_NAME); KEYSTORE_PROPERTIES = new File(KEYSTORE_DIR, KEYSTORE_PROPERTIES_NAME); + NDK_DIR = new File(ANDROID_HOME,"ndk"); + isInitialized.set(true); } diff --git a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt index a39533fb1b..c5a7149961 100644 --- a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt +++ b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt @@ -45,6 +45,7 @@ object TooltipTag { const val TEMPLATE_BASIC_ACTIVITY = "template.basic.activity" const val TEMPLATE_NO_ACTIVITY = "template.no.activity" const val TEMPLATE_NAV_DRAWER_ACTIVITY = "template.navdrawer.activity" + const val TEMPLATE_NDK_ACTIVITY = "template.ndk.activity" // Editor screen const val EDITOR_PROJECT_OVERVIEW = "project.overview" diff --git a/resources/src/main/res/drawable-xxxhdpi/template_ndk_activity.png b/resources/src/main/res/drawable-xxxhdpi/template_ndk_activity.png new file mode 100644 index 0000000000000000000000000000000000000000..d15002c6642472e75ade6bec5de3c8d92c156647 GIT binary patch literal 9322 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 literal 0 HcmV?d00001 diff --git a/resources/src/main/res/values/strings.xml b/resources/src/main/res/values/strings.xml index d5dc4be95c..d11c05df9b 100644 --- a/resources/src/main/res/values/strings.xml +++ b/resources/src/main/res/values/strings.xml @@ -125,6 +125,7 @@ No Activity Legacy Project (No AndroidX) Code on the Go Plugin + NDK Activity Plugin Name diff --git a/templates-api/src/main/java/com/itsaky/androidide/templates/base/AndroidModuleTemplateBuilder.kt b/templates-api/src/main/java/com/itsaky/androidide/templates/base/AndroidModuleTemplateBuilder.kt index 7116ef236e..0f641014b3 100644 --- a/templates-api/src/main/java/com/itsaky/androidide/templates/base/AndroidModuleTemplateBuilder.kt +++ b/templates-api/src/main/java/com/itsaky/androidide/templates/base/AndroidModuleTemplateBuilder.kt @@ -30,7 +30,7 @@ import com.itsaky.androidide.templates.base.util.stringRes import com.squareup.javapoet.TypeSpec import java.io.File -class AndroidModuleTemplateBuilder : ModuleTemplateBuilder() { +open class AndroidModuleTemplateBuilder : ModuleTemplateBuilder() { /** * Set whether this Android module is a Jetpack Compose module or not. diff --git a/templates-api/src/main/java/com/itsaky/androidide/templates/base/modules/android/buildGradle.kt b/templates-api/src/main/java/com/itsaky/androidide/templates/base/modules/android/buildGradle.kt index a0eacf7149..72be971037 100644 --- a/templates-api/src/main/java/com/itsaky/androidide/templates/base/modules/android/buildGradle.kt +++ b/templates-api/src/main/java/com/itsaky/androidide/templates/base/modules/android/buildGradle.kt @@ -392,3 +392,224 @@ private fun ktPluginGroovy(): String { // hardcoded like this return "id 'org.jetbrains.kotlin.android' version '${KOTLIN_VERSION}'" } + +fun AndroidModuleTemplateBuilder.ndkBuildGradleSrcKts( + isComposeModule: Boolean, + ndkVersion: String, + abiFilters: List, + cppFlags: String +): String = """ +import java.util.Properties +import java.io.FileInputStream + +plugins { + ${androidPlugin(isComposeModule)} + ${ktPlugin(isComposeModule)} +} + +val keystorePropsFile = rootProject.file("${Environment.KEYSTORE_PROPERTIES_NAME}") +val keystoreProps = Properties() + +if (keystorePropsFile.exists()) { + keystoreProps.load(FileInputStream(keystorePropsFile)) +} + +val hasValidSigningProps = keystorePropsFile.exists().also { exists -> + if (exists) { + FileInputStream(keystorePropsFile).use { keystoreProps.load(it) } + } +}.let { + listOf("${Environment.KEYSTORE_PROP_STOREFILE}", "${Environment.KEYSTORE_PROP_STOREPWD}", + "${Environment.KEYSTORE_PROP_KEYALIAS}", "${Environment.KEYSTORE_PROP_KEYPWD}").all { key -> + keystoreProps[key] != null + } +} + + +android { + namespace = "${data.packageName}" + compileSdk = ${if (isComposeModule) data.versions.composeSdk.api else data.versions.targetSdk.api} + + // disable linter + lint { + checkReleaseBuilds = false + } + + signingConfigs { + if (hasValidSigningProps) { + create("release") { + storeFile = rootProject.file(keystoreProps["${Environment.KEYSTORE_PROP_STOREFILE}"] as String) + storePassword = keystoreProps["${Environment.KEYSTORE_PROP_STOREPWD}"] as String + keyAlias = keystoreProps["${Environment.KEYSTORE_PROP_KEYALIAS}"] as String + keyPassword = keystoreProps["${Environment.KEYSTORE_PROP_KEYPWD}"] as String + } + } + } + + defaultConfig { + applicationId = "${data.packageName}" + minSdk = ${data.versions.minSdk.api} + targetSdk = ${if (isComposeModule) data.versions.composeSdk.api else data.versions.targetSdk.api} + versionCode = 1 + versionName = "1.0" + + vectorDrawables { + useSupportLibrary = true + } + + ndk { + abiFilters += ${abiFilters.joinToString(",") { "\"$it\"" }} + } + + externalNativeBuild { + cmake { + cppFlags += "$cppFlags" + } + } + } + + ndkVersion = "$ndkVersion" + + externalNativeBuild { + cmake { + path = file("src/main/cpp/CMakeLists.txt") + } + } + + compileOptions { + sourceCompatibility = ${data.versions.javaSource()} + targetCompatibility = ${data.versions.javaTarget()} + } + + buildTypes { + release { + if (hasValidSigningProps) { + signingConfig = signingConfigs.getByName("release") + } + isMinifyEnabled = true + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + + buildFeatures { + ${if (!isComposeModule) "viewBinding = true" else ""} + ${if (isComposeModule) "compose = true" else ""} + } + ${composeConfigKts()} +} + +tasks.withType { + options.compilerArgs.add("-Xlint:deprecation") +} + +${ktJvmTarget()} +${dependencies()} +""".trimIndent() + +fun AndroidModuleTemplateBuilder.ndkBuildGradleSrcGroovy( + isComposeModule: Boolean, + ndkVersion: String, + abiFilters: List, + cppFlags: String +): String = """ +import java.util.Properties +import java.io.FileInputStream + +plugins { + id '${androidPlugin}' + ${ktPlugin(isComposeModule)} +} + +def keystorePropsFile = rootProject.file("${Environment.KEYSTORE_PROPERTIES_NAME}") +def keystoreProps = new Properties() +if (keystorePropsFile.exists()) { + keystoreProps.load(new FileInputStream(keystorePropsFile)) +} + +def hasValidSigningProps = false +if (keystorePropsFile.exists()) { + keystoreProps.load(new FileInputStream(keystorePropsFile)) + + def requiredKeys = ["${Environment.KEYSTORE_PROP_STOREFILE}", "${Environment.KEYSTORE_PROP_STOREPWD}", + "${Environment.KEYSTORE_PROP_KEYALIAS}", "${Environment.KEYSTORE_PROP_KEYPWD}"] + hasValidSigningProps = requiredKeys.every { key -> keystoreProps[key] } +} + + +android { + namespace = '${data.packageName}' + compileSdk = ${data.versions.compileSdk.api} + + // disable linter + lint { + checkReleaseBuilds = false + } + + if (hasValidSigningProps) { + signingConfigs { + release { + storeFile = rootProject.file((String) keystoreProps["${Environment.KEYSTORE_PROP_STOREFILE}"]) + storePassword = keystoreProps["${Environment.KEYSTORE_PROP_STOREPWD}"] + keyAlias = keystoreProps["${Environment.KEYSTORE_PROP_KEYALIAS}"] + keyPassword = keystoreProps["${Environment.KEYSTORE_PROP_KEYPWD}"] + } + } + } + + defaultConfig { + applicationId = "${data.packageName}" + minSdk = ${data.versions.minSdk.api} + targetSdk = ${data.versions.targetSdk.api} + versionCode = 1 + versionName = "1.0" + + vectorDrawables { + useSupportLibrary = true + } + + ndk { + abiFilters += [${abiFilters.joinToString(",") { "\"$it\"" }}] + } + + externalNativeBuild { + cmake { + cppFlags += "$cppFlags" + } + } + } + + ndkVersion "$ndkVersion" + + externalNativeBuild { + cmake { + path file("src/main/cpp/CMakeLists.txt") + } + } + + buildTypes { + release { + if (hasValidSigningProps) { + signingConfig = signingConfigs.release + } + minifyEnabled = true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility = ${data.versions.javaSource()} + targetCompatibility = ${data.versions.javaTarget()} + } + + buildFeatures { + ${if (!isComposeModule) "viewBinding = true" else ""} + ${if (isComposeModule) "compose = true" else ""} + } + ${composeConfigGroovy()} +} +tasks.withType(JavaCompile).configureEach { + options.compilerArgs += "-Xlint:deprecation" +} +${ktJvmTarget()} +${dependencies()} +""".trimIndent() \ No newline at end of file diff --git a/templates-api/src/main/java/com/itsaky/androidide/templates/base/ndkExtensions.kt b/templates-api/src/main/java/com/itsaky/androidide/templates/base/ndkExtensions.kt new file mode 100644 index 0000000000..ae71d7d385 --- /dev/null +++ b/templates-api/src/main/java/com/itsaky/androidide/templates/base/ndkExtensions.kt @@ -0,0 +1,107 @@ +package com.itsaky.androidide.templates.base + +import com.itsaky.androidide.templates.ModuleTemplate +import com.itsaky.androidide.templates.RecipeExecutor +import com.itsaky.androidide.templates.base.util.AndroidManifestBuilder.ConfigurationType.APPLICATION_ATTR +import com.itsaky.androidide.templates.base.modules.android.ndkBuildGradleSrcKts +import com.itsaky.androidide.templates.base.modules.android.ndkBuildGradleSrcGroovy + +import com.itsaky.androidide.templates.SrcSet +import com.itsaky.androidide.templates.base.util.SourceWriter +import java.io.File + +class NdkModuleTemplateBuilder( + private val ndkVersion: String? = null, + private val abiFilters: List? = null, + private val cppFlags: String? = null +) : AndroidModuleTemplateBuilder() { + + fun srcNativeFilePath(srcSet: SrcSet, fileName: String): File { + // place under src/main/cpp + val cppDir = File(javaSrc(srcSet), "../cpp") + if (!cppDir.exists()) { + cppDir.mkdirs() + } + return File(cppDir, fileName) + } + + inline fun writeCpp( + writer: SourceWriter, + fileName: String, + crossinline cppSrcProvider: () -> String + ) { + val src = cppSrcProvider() + if (src.isNotBlank() && fileName.isNotBlank()) { + executor.save(src, srcNativeFilePath(SrcSet.Main, fileName)) + } + } + + inline fun writeCMakeList( + writer: SourceWriter, + fileName: String, + crossinline cmakeSrcProvider: () -> String + ) { + val src = cmakeSrcProvider() + if (src.isNotBlank() && fileName.isNotBlank()) { + executor.save(src, srcNativeFilePath(SrcSet.Main, fileName)) + } + } + + override fun RecipeExecutor.buildGradle() { + val gradleContent = if (data.useKts) { + ndkBuildGradleSrcKts( + isComposeModule, + ndkVersion ?: "", + abiFilters ?: emptyList(), + cppFlags ?: "" + ) + } else { + ndkBuildGradleSrcGroovy( + isComposeModule, + ndkVersion ?: "", + abiFilters ?: emptyList(), + cppFlags ?: "" + ) + } + save(gradleContent, buildGradleFile()) + } +} + + +inline fun ProjectTemplateBuilder.defaultAppModuleWithNdk( + name: String = ":app", + ndkVersion: String? = null, + abiFilters: List? = null, + cppFlags: String? = null, + addAndroidX: Boolean = true, + copyDefAssets: Boolean = true, + crossinline block: NdkModuleTemplateBuilder.() -> Unit +) { + val module = NdkModuleTemplateBuilder(ndkVersion, abiFilters, cppFlags).apply { + _name = name + + templateName = 0 + thumb = 0 + + preRecipe = commonPreRecipe { return@commonPreRecipe defModule } + postRecipe = commonPostRecipe { + if (copyDefAssets) { + copyDefaultRes() + + manifest { + configure(APPLICATION_ATTR) { + androidAttribute("dataExtractionRules", "@xml/data_extraction_rules") + androidAttribute("fullBackupContent", "@xml/backup_rules") + } + } + } + } + + if (addAndroidX) baseAndroidXDependencies() + + block() + }.build() as ModuleTemplate + + modules.add(module) +} + diff --git a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/TemplateProviderImpl.kt b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/TemplateProviderImpl.kt index cd957b4667..70312efa4f 100644 --- a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/TemplateProviderImpl.kt +++ b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/TemplateProviderImpl.kt @@ -30,6 +30,7 @@ import com.itsaky.androidide.templates.impl.noActivity.noActivityProjectTemplate import com.itsaky.androidide.templates.impl.noAndroidXActivity.noAndroidXActivityProject import com.itsaky.androidide.templates.impl.pluginProject.pluginProjectTemplate import com.itsaky.androidide.templates.impl.tabbedActivity.tabbedActivityProject +import com.itsaky.androidide.templates.impl.ndkActivity.ndkActivityProject /** * Default implementation of the [ITemplateProvider]. @@ -57,7 +58,8 @@ class TemplateProviderImpl : ITemplateProvider { tabbedActivityProject(), noAndroidXActivityProject(), composeActivityProject(), - pluginProjectTemplate() + pluginProjectTemplate(), + ndkActivityProject() ) private fun initializeTemplates() { diff --git a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkActivityTemplate.kt b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkActivityTemplate.kt new file mode 100644 index 0000000000..550305b15e --- /dev/null +++ b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkActivityTemplate.kt @@ -0,0 +1,65 @@ +package com.itsaky.androidide.templates.impl.ndkActivity + +import com.itsaky.androidide.idetooltips.TooltipTag +import com.itsaky.androidide.templates.ProjectTemplate +import com.itsaky.androidide.templates.base.AndroidModuleTemplateBuilder +import com.itsaky.androidide.templates.base.defaultAppModuleWithNdk +import com.itsaky.androidide.templates.base.util.AndroidModuleResManager.ResourceType.LAYOUT +import com.itsaky.androidide.templates.base.util.SourceWriter +import com.itsaky.androidide.templates.impl.R +import com.itsaky.androidide.templates.impl.base.createRecipe +import com.itsaky.androidide.templates.impl.base.emptyThemesAndColors +import com.itsaky.androidide.templates.impl.base.writeMainActivity +import com.itsaky.androidide.templates.impl.baseProjectImpl +import com.itsaky.androidide.templates.impl.emptyActivity.emptyLayoutSrc +import com.itsaky.androidide.utils.Environment +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +fun ndkActivityProject(): ProjectTemplate? { + + if (!Environment.NDK_DIR.exists()) { + return null + } + + return baseProjectImpl { + templateName = R.string.template_ndk + thumb = R.drawable.template_ndk_activity + tooltipTag = TooltipTag.TEMPLATE_NDK_ACTIVITY + + defaultAppModuleWithNdk( + ndkVersion = "29.0.14206865", + abiFilters = listOf("arm64-v8a"), + cppFlags = "-std=c++17" + ) + { + recipe = createRecipe { + sources { + writeNdkActivity(this) + writeCpp(this, fileName = "native-lib.cpp") { ndkCpp() } + writeCMakeList(this, fileName = "CMakeLists.txt") { ndkCMakeLists() } + } + + res { writeNdkActivity() } + } + } + } +} + + +internal fun AndroidModuleTemplateBuilder.writeNdkActivity() { + res.apply { + // layout/activity_main.xml + writeXmlResource("activity_main", LAYOUT, source = ::emptyLayoutSrc) + emptyThemesAndColors() + } +} + +internal fun AndroidModuleTemplateBuilder.writeNdkActivity( + writer: SourceWriter +) { + writeMainActivity(writer, ::ndkActivitySrcKt, ::ndkActivitySrcJava) +} + + + diff --git a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkSources.kt b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkSources.kt new file mode 100644 index 0000000000..5e9adfa50b --- /dev/null +++ b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkSources.kt @@ -0,0 +1,123 @@ +package com.itsaky.androidide.templates.impl.ndkActivity + +import com.itsaky.androidide.templates.base.AndroidModuleTemplateBuilder +import com.itsaky.androidide.templates.base.NdkModuleTemplateBuilder +import com.itsaky.androidide.templates.impl.base.baseLayoutContentMain + +internal fun emptyLayoutSrc() = baseLayoutContentMain() + +internal fun AndroidModuleTemplateBuilder.ndkActivitySrcKt(): String { + return """ +package ${data.packageName} + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import ${data.packageName}.databinding.ActivityMainBinding + +public class MainActivity : AppCompatActivity() { + + private var _binding: ActivityMainBinding? = null + + private val binding: ActivityMainBinding + get() = checkNotNull(_binding) { "Activity has been destroyed" } + + // Load the native library + static { + System.loadLibrary("native-lib"); + } + + // Declare the native method + public native String stringFromJNI(); + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Inflate and get instance of binding + _binding = ActivityMainBinding.inflate(layoutInflater) + + // set content view to binding's root + setContentView(binding.root) + + // Call JNI method and display result + binding.textView.setText(stringFromJNI()); + } + + override fun onDestroy() { + super.onDestroy() + _binding = null + } +} +""" +} + +internal fun AndroidModuleTemplateBuilder.ndkActivitySrcJava(): String { + return """ +package ${data.packageName}; + +import androidx.appcompat.app.AppCompatActivity; +import android.os.Bundle; +import ${data.packageName}.databinding.ActivityMainBinding; + +public class MainActivity extends AppCompatActivity { + private ActivityMainBinding binding; + + // Load the native library + static { + System.loadLibrary("native-lib"); + } + + // Declare the native method + public native String stringFromJNI(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Inflate and get instance of binding + binding = ActivityMainBinding.inflate(getLayoutInflater()); + + // set content view to binding's root + setContentView(binding.getRoot()); + + // Call JNI method and display result + binding.textView.setText(stringFromJNI()); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + this.binding = null; + } +} +""" +} + +internal fun NdkModuleTemplateBuilder.ndkCpp(): String { + val jniPrefix = data.packageName.replace('.', '_') + return """ +#include +#include + +extern "C" JNIEXPORT jstring JNICALL +Java_${jniPrefix}_MainActivity_stringFromJNI( + JNIEnv* env, + jobject /* this */) { + return env->NewStringUTF("Hello from C++"); +} +""".trimIndent() +} + +internal fun NdkModuleTemplateBuilder.ndkCMakeLists(): String { + val appName = data.packageName.substringAfterLast('.') + return """ +cmake_minimum_required(VERSION 3.10.2) + +project("$appName") + +add_library(native-lib SHARED native-lib.cpp) + +find_library(log-lib log) + +target_link_libraries(native-lib ${'$'}{log-lib}) +""".trimIndent() +} From 2838530e70b0a82899ef3f853c45a1102a761392 Mon Sep 17 00:00:00 2001 From: Joel Menchavez Date: Fri, 23 Jan 2026 16:25:31 +0800 Subject: [PATCH 2/3] unused imports --- .../templates/impl/ndkActivity/ndkActivityTemplate.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkActivityTemplate.kt b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkActivityTemplate.kt index 550305b15e..3e352b3bda 100644 --- a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkActivityTemplate.kt +++ b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkActivityTemplate.kt @@ -13,8 +13,6 @@ import com.itsaky.androidide.templates.impl.base.writeMainActivity import com.itsaky.androidide.templates.impl.baseProjectImpl import com.itsaky.androidide.templates.impl.emptyActivity.emptyLayoutSrc import com.itsaky.androidide.utils.Environment -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext fun ndkActivityProject(): ProjectTemplate? { From 8e7411b31fa64460204a1dab4d2f5269727b39ac Mon Sep 17 00:00:00 2001 From: Joel Menchavez Date: Fri, 23 Jan 2026 18:57:51 +0800 Subject: [PATCH 3/3] fix bugs --- .../assets/AssetsInstallationHelper.kt | 2 +- .../androidide/assets/BaseAssetsInstaller.kt | 7 ++++++- .../com/itsaky/androidide/utils/Environment.java | 2 +- .../templates/impl/ndkActivity/ndkSources.kt | 16 +++++++++------- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt b/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt index ed6bde4ebc..d29f2172e5 100644 --- a/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt +++ b/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt @@ -176,7 +176,7 @@ object AssetsInstallationHelper { val freeStorage = getAvailableStorage(File(DEFAULT_ROOT)) val snapshot = - if (percent == 100.0) { + if (percent >= 99.99) { "Post install processing in progress...." } else { buildString { diff --git a/app/src/main/java/com/itsaky/androidide/assets/BaseAssetsInstaller.kt b/app/src/main/java/com/itsaky/androidide/assets/BaseAssetsInstaller.kt index b8f1a717cf..8c1511278d 100644 --- a/app/src/main/java/com/itsaky/androidide/assets/BaseAssetsInstaller.kt +++ b/app/src/main/java/com/itsaky/androidide/assets/BaseAssetsInstaller.kt @@ -4,6 +4,7 @@ import android.content.Context import org.slf4j.LoggerFactory import java.io.File import java.nio.file.Path +import java.util.concurrent.TimeUnit import com.termux.shared.termux.TermuxConstants import com.itsaky.androidide.utils.Environment import kotlin.system.measureTimeMillis @@ -48,7 +49,11 @@ abstract class BaseAssetsInstaller : AssetsInstaller { val process = processBuilder.start() result = process.inputStream.bufferedReader().use { it.readText() } - exitCode = process.waitFor() + val completed = process.waitFor(2, TimeUnit.MINUTES) + exitCode = if (completed) process.exitValue() else { + process.destroyForcibly() + -1 + } } return if (exitCode == 0) { diff --git a/common/src/main/java/com/itsaky/androidide/utils/Environment.java b/common/src/main/java/com/itsaky/androidide/utils/Environment.java index 152db4ef11..11a6916d90 100755 --- a/common/src/main/java/com/itsaky/androidide/utils/Environment.java +++ b/common/src/main/java/com/itsaky/androidide/utils/Environment.java @@ -112,7 +112,7 @@ public final class Environment { "PL", "PT", "RO", "SK", "SI", "ES", "SE" }; - public static String NDK_TAR_XZ = "ndk-cmake.tar.xz"; + public static final String NDK_TAR_XZ = "ndk-cmake.tar.xz"; public static File NDK_DIR; public static String getArchitecture() { diff --git a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkSources.kt b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkSources.kt index 5e9adfa50b..d6b9efffcc 100644 --- a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkSources.kt +++ b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkSources.kt @@ -14,21 +14,23 @@ import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import ${data.packageName}.databinding.ActivityMainBinding -public class MainActivity : AppCompatActivity() { +class MainActivity : AppCompatActivity() { private var _binding: ActivityMainBinding? = null private val binding: ActivityMainBinding get() = checkNotNull(_binding) { "Activity has been destroyed" } + // Declare the native method + private external fun stringFromJNI(): String + // Load the native library - static { - System.loadLibrary("native-lib"); + companion object { + init { + System.loadLibrary("native-lib"); + } } - // Declare the native method - public native String stringFromJNI(); - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -39,7 +41,7 @@ public class MainActivity : AppCompatActivity() { setContentView(binding.root) // Call JNI method and display result - binding.textView.setText(stringFromJNI()); + binding.textView.text = stringFromJNI() } override fun onDestroy() {