190 lines
8.2 KiB
Groovy
190 lines
8.2 KiB
Groovy
apply plugin: 'com.android.application'
|
|
apply plugin: 'org.jetbrains.kotlin.plugin.compose'
|
|
|
|
/* Optional .env next to this project: S3/update settings and signing (see .env.example).
|
|
Environment variables take precedence over .env values. */
|
|
def dotenv = [:]
|
|
def envFile = rootProject.file('.env')
|
|
if (envFile.exists()) {
|
|
envFile.eachLine { line ->
|
|
def trimmed = line.trim()
|
|
if (trimmed && !trimmed.startsWith('#') && trimmed.contains('=')) {
|
|
def idx = trimmed.indexOf('=')
|
|
dotenv[trimmed.substring(0, idx).trim()] = trimmed.substring(idx + 1).trim()
|
|
}
|
|
}
|
|
}
|
|
def envOr = { String key, String defaultValue ->
|
|
(System.getenv(key) ?: dotenv[key] ?: defaultValue)
|
|
}
|
|
|
|
def updateBaseUrl = envOr('S3_PUBLIC_BASE_URL', '').replace('\\', '\\\\').replace('"', '\\"')
|
|
|
|
def keystoreFile = envOr('KEYSTORE_FILE', '')
|
|
def keystorePassword = envOr('KEYSTORE_PASSWORD', '')
|
|
/* note: locals must not be named keyAlias/keyPassword - those names would
|
|
shadow the signingConfig methods inside the signingConfigs block */
|
|
def keystoreKeyAlias = envOr('KEY_ALIAS', '')
|
|
def keystoreKeyPassword = envOr('KEY_PASSWORD', '')
|
|
|
|
android {
|
|
namespace = 'org.strongswan.android'
|
|
|
|
/* minSdk >= 23 flips AGP's default to uncompressed .so (extractNativeLibs=false):
|
|
* the APK grew 12 -> 23 MB. Updates are downloaded over cellular, so keep
|
|
* the libraries compressed as before. */
|
|
packaging {
|
|
jniLibs {
|
|
useLegacyPackaging = true
|
|
}
|
|
}
|
|
|
|
defaultConfig {
|
|
applicationId "one.shx.strongswan.ext"
|
|
compileSdk = 37
|
|
minSdk = 24
|
|
targetSdk = 36
|
|
|
|
versionCode 159
|
|
versionName "2.14.6"
|
|
|
|
/* Base URL of the update storage, trailing slash included */
|
|
buildConfigField "String", "UPDATE_BASE_URL", "\"${updateBaseUrl}\""
|
|
|
|
/* S3 credentials used by the app for authenticated update downloads
|
|
(same keys as the publish script). They are obfuscated with XOR +
|
|
Base64 at build time to hinder trivial string extraction.
|
|
Note: obfuscation is not real protection - an APK can always be
|
|
reverse engineered, so rotate these keys if they leak. */
|
|
def updateS3Access = envOr('S3_ACCESS_KEY', '')
|
|
def updateS3Secret = envOr('S3_SECRET_KEY', '')
|
|
def updateXorKey = new byte[16]
|
|
System.arraycopy(java.security.MessageDigest.getInstance('SHA-256')
|
|
.digest('shx-update-credentials'.getBytes('UTF-8')), 0, updateXorKey, 0, 16)
|
|
def xorB64 = { String value ->
|
|
if (!value) { return '' }
|
|
def bytes = value.getBytes('UTF-8')
|
|
for (int i = 0; i < bytes.length; i++) {
|
|
bytes[i] = (byte)(bytes[i] ^ updateXorKey[i % updateXorKey.length])
|
|
}
|
|
java.util.Base64.getEncoder().encodeToString(bytes)
|
|
}
|
|
buildConfigField "String", "UPDATE_S3_ACCESS_KEY", "\"${xorB64(updateS3Access)}\""
|
|
buildConfigField "String", "UPDATE_S3_SECRET_KEY", "\"${xorB64(updateS3Secret)}\""
|
|
buildConfigField "String", "UPDATE_S3_KEY_XOR", "\"${java.util.Base64.getEncoder().encodeToString(updateXorKey)}\""
|
|
buildConfigField "String", "UPDATE_S3_REGION", "\"${envOr('S3_REGION', 'us-east-1')}\""
|
|
|
|
externalNativeBuild {
|
|
ndkBuild {
|
|
arguments '-j' + Runtime.runtime.availableProcessors()
|
|
/* deep checkout paths (e.g. git worktrees) overflow the Windows command line; opt in with -PshortNdkCommands */
|
|
if (project.hasProperty('shortNdkCommands')) {
|
|
arguments 'APP_SHORT_COMMANDS=true', 'LOCAL_SHORT_COMMANDS=true'
|
|
}
|
|
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64'
|
|
}
|
|
}
|
|
}
|
|
|
|
ndkVersion = "27.3.13750724"
|
|
|
|
externalNativeBuild {
|
|
ndkBuild {
|
|
path 'src/main/jni/Android.mk'
|
|
}
|
|
}
|
|
|
|
tasks.withType(JavaCompile).configureEach {
|
|
compileTask ->
|
|
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
|
|
}
|
|
|
|
signingConfigs {
|
|
release {
|
|
if (keystoreFile) {
|
|
storeFile rootProject.file(keystoreFile)
|
|
storePassword keystorePassword
|
|
keyAlias keystoreKeyAlias
|
|
keyPassword keystoreKeyPassword
|
|
}
|
|
}
|
|
}
|
|
|
|
buildTypes {
|
|
release {
|
|
/* R8 shrinking + resource shrinking: strips dead code/resources and
|
|
obfuscates. The JNI surface is kept via proguard-rules.txt.
|
|
Smoke-test a release build on a device before publishing. */
|
|
minifyEnabled true
|
|
shrinkResources true
|
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.txt'
|
|
if (keystoreFile) {
|
|
signingConfig signingConfigs.release
|
|
} else {
|
|
/* fall back to the debug key so that release APKs update debug-signed
|
|
installations in place (no uninstall, data is preserved) */
|
|
def debugKeystore = new File(System.getProperty('user.home'), '.android/debug.keystore')
|
|
if (debugKeystore.exists()) {
|
|
signingConfig signingConfigs.debug
|
|
println "WARNING: KEYSTORE_FILE not set (see .env.example) - signing the release APK with the debug key (${debugKeystore})"
|
|
} else {
|
|
println "WARNING: no signing configuration - the release APK will be UNSIGNED (set KEYSTORE_FILE in .env or run a debug build once)"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
compileOptions {
|
|
targetCompatibility JavaVersion.VERSION_11
|
|
sourceCompatibility JavaVersion.VERSION_11
|
|
}
|
|
buildFeatures {
|
|
compose true
|
|
buildConfig true
|
|
}
|
|
}
|
|
|
|
dependencies {
|
|
implementation 'androidx.appcompat:appcompat:1.7.1'
|
|
implementation 'androidx.core:core:1.17.0'
|
|
implementation 'androidx.core:core-splashscreen:1.0.1'
|
|
implementation 'androidx.lifecycle:lifecycle-process:2.9.4'
|
|
implementation 'androidx.lifecycle:lifecycle-runtime-compose:2.9.4'
|
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.9.4'
|
|
implementation 'androidx.preference:preference:1.2.1'
|
|
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0'
|
|
implementation 'androidx.work:work-runtime-ktx:2.10.1'
|
|
implementation 'com.google.android.material:material:1.13.0'
|
|
|
|
/* BOM 2026.09.00 = compose 1.12. The Expressive API (MaterialExpressiveTheme,
|
|
MotionScheme.expressive, LoadingIndicator, MaterialShapes) lives only in
|
|
material3 1.5.0-alpha/beta, so material3 is pinned explicitly (needs AGP 9.1,
|
|
compileSdk 37, minSdk 24). Unpin once 1.5.0 is stable. */
|
|
def composeBom = platform('androidx.compose:compose-bom:2026.09.00')
|
|
implementation composeBom
|
|
implementation 'androidx.compose.ui:ui'
|
|
implementation 'androidx.compose.material3:material3:1.5.0-beta01'
|
|
/* adaptive-navigation-suite (NavigationSuiteScaffold) requires minSdk 23+ (material3 1.5 requires 24) */
|
|
implementation 'androidx.compose.material3:material3-adaptive-navigation-suite:1.5.0-beta01'
|
|
/* list-detail layout for Profiles on expanded widths */
|
|
implementation 'androidx.compose.material3.adaptive:adaptive-layout:1.3.0'
|
|
implementation 'androidx.compose.material3.adaptive:adaptive-navigation:1.3.0'
|
|
implementation 'androidx.compose.material:material-icons-extended'
|
|
/* home screen widget (status + connect button) */
|
|
implementation 'androidx.glance:glance-appwidget:1.2.0'
|
|
implementation 'androidx.glance:glance-material3:1.2.0'
|
|
implementation 'androidx.activity:activity-compose:1.11.0'
|
|
|
|
debugImplementation 'androidx.compose.ui:ui-tooling'
|
|
implementation 'androidx.compose.ui:ui-tooling-preview'
|
|
|
|
testImplementation 'junit:junit:4.13.2'
|
|
testImplementation 'org.assertj:assertj-core:3.27.6'
|
|
testImplementation 'org.mockito:mockito-core:5.20.0'
|
|
|
|
androidTestImplementation 'androidx.test.ext:junit:1.3.0'
|
|
androidTestImplementation 'androidx.test:runner:1.7.0'
|
|
androidTestImplementation 'org.assertj:assertj-core:3.27.6'
|
|
androidTestImplementation composeBom
|
|
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
|
|
}
|