import java.util.regex.Matcher import java.util.regex.Pattern apply plugin: 'com.android.application' android { ndkVersion "21.4.7075529" namespace = "se.leap.bitmaskclient" compileOptions { targetCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17 } viewBinding { enabled = true } packaging { jniLibs { useLegacyPackaging = true } } buildFeatures { buildConfig true aidl true } defaultConfig { applicationId "se.leap.bitmaskclient" // the factor 1000 is used so that gplay users can upgrade from split apks ((current version number - 1) * 1000) + n // to extracted bundle apks, supplied by google // however we don't calculate the versionCode here, because F-Droid doesn't like that versionCode 170000 versionName "1.2.0" compileSdk 34 minSdkVersion 21 targetSdkVersion 34 vectorDrawables.useSupportLibrary = true buildConfigField 'boolean', 'openvpn3', 'false' //Build Config Fields for default donation details //This is the default donation URL and should be set to the donation page of LEAP // and this should not be set/altered anywhere else. buildConfigField 'String', 'default_donation_url', '"https://riseuplabs.org/leap"' //The field to enable donations in the app. buildConfigField 'boolean', 'enable_donation', 'false' //The field to enable donation reminder popup in the app if enable_donation is set to 'false' this will be disabled. buildConfigField 'boolean', 'enable_donation_reminder', 'true' //The duration in days to trigger the donation reminder buildConfigField 'int', 'donation_reminder_duration', '7' //skip the account creation / login screen if the provider offers anonymous vpn usage, use directly the anonymous cert instead buildConfigField 'boolean', 'priotize_anonymous_usage', 'false' //allow manual gateway selection buildConfigField 'boolean', 'allow_manual_gateway_selection', 'true' // decide if we use obfsvpn or shapeshifter as obfs4 lib buildConfigField 'boolean', 'use_obfsvpn', 'true' // obfsvpn Debugging config fields to pin and configure a particular proxy buildConfigField "String", "obfsvpn_port", '""' buildConfigField "String", "obfsvpn_ip", '""' buildConfigField "String", "obfsvpn_cert", '""' buildConfigField 'boolean', 'obfsvpn_use_kcp', 'false' // default to UDP usage buildConfigField 'boolean', 'prefer_udp', 'false' // static update url pointing to the latest stable release apk buildConfigField "String", "update_apk_url", '"https://dl.bitmask.net/client/android/Bitmask-Android-latest.apk"' // the the pgp signature file of the apk buildConfigField "String", "signature_url", '"https://dl.bitmask.net/client/android/Bitmask-Android-latest.apk.sig"' // the version file contains the versionCode of the latest release buildConfigField "String", "version_file_url", '"https://dl.bitmask.net/client/android/versioncode.txt"' // if center_actionbar_title is set to true, the actionbar title and subtitle will be centered, otherwise default buildConfigField 'boolean', 'actionbar_center_title', 'true' buildConfigField 'boolean', 'actionbar_capitalize_title', 'true' //ignore the following configs, only used in custom flavor buildConfigField 'String', 'donation_url', 'null' buildConfigField "String", "customProviderUrl", '""' buildConfigField "String", "customProviderIp", '""' buildConfigField "String", "customProviderApiIp", '""' buildConfigField "String", "geoipUrl", '""' buildConfigField "String", "customProviderMotdUrl", '""' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" dexOptions { jumboMode true multiDexEnabled true } ndk { abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' } bundle { language { enableSplit = true } density { enableSplit = true } abi { enableSplit = true } } } flavorDimensions "branding", "implementation", "abi" productFlavors { productFlavors.all { ext.appName = null ext.splitApk = false } production { dimension "implementation" } normal { dimension "branding" appName = "Bitmask" splitApk = true } custom { dimension "branding" //************************************************************************** //************************************************************************** //Configurations for custom branded app. //Change the package name as needed, e.g. "org.example.myapp" applicationId "se.leap.riseupvpn" //Set app name here appName = "Riseup VPN" //Provider base url, e.g. '"https://example.com"' def customProviderUrl = '"https://black.riseup.net"' buildConfigField "String", "customProviderUrl", customProviderUrl //static ip address of provider, using a commercially validated CA certificate to serve the provider.json def customProviderIp = '""' buildConfigField "String", "customProviderIp", customProviderIp //static ip address of the provider api, using a self signed certificate to serve provider.json, eip-service.json etc. def customProviderApiIp = '""' buildConfigField "String", "customProviderApiIp", customProviderApiIp def geoipUrl = '"https://menshen.riseup.net/json"' buildConfigField "String", "geoipUrl", geoipUrl //URL for the message of the day, see https://0xacab.org/leap/motd#motd-message-of-the-day def customProviderMotdUrl = '"https://static.riseup.net/vpn/motd.json"' buildConfigField "String", "customProviderMotdUrl", customProviderMotdUrl //Change the versionCode as needed //versionCode 1 //Change the versionName as needed //versionName "0.9.9RC1" //skip the account creation / login screen if the provider offers anonymous vpn usage, use directly the anonymous cert instead buildConfigField 'boolean', 'priotize_anonymous_usage', 'true' //allow manual gateway selection buildConfigField 'boolean', 'allow_manual_gateway_selection', 'true' // decide if we use obfsvpn or shapeshifter as obfs4 lib buildConfigField 'boolean', 'use_obfsvpn', 'true' // obfsvpn Debugging config fields to pin and configure a particular proxy buildConfigField "String", "obfsvpn_port", '"443"' buildConfigField "String", "obfsvpn_ip", '"159.223.173.205"' buildConfigField "String", "obfsvpn_cert", '"8nuAbPJwFrKc/29KcCfL5LBuEWxQrjBASYXdUbwcm9d9pKseGK4r2Tg47e23+t6WghxGGw"' buildConfigField 'boolean', 'obfsvpn_use_kcp', 'true' // default to UDP usage buildConfigField 'boolean', 'prefer_udp', 'true' //Build Config Fields for automatic apk update checks // static update url pointing to the latest stable release apk def apkURL = '"https://dl.bitmask.net/RiseupVPN/android/RiseupVPN-Android-latest.apk"' buildConfigField "String", "update_apk_url", apkURL // the the pgp signature file of the apk def signatureURL = '"https://dl.bitmask.net/RiseupVPN/android/RiseupVPN-Android-latest.apk.sig"' buildConfigField "String", "signature_url", signatureURL // the version file should contain a single line with the versionCode of the latest release buildConfigField "String", "version_file_url", '"https://dl.bitmask.net/client/android/versioncode.txt"' //'"https://dl.bitmask.net/RiseupVPN/android/versioncode.txt"' //Build Config Fields for default donation details //This is the donation URL and should be set to the relevant donation page. buildConfigField 'String', 'donation_url', '"https://riseup.net/vpn/donate"' //The field to enable donations in the app. buildConfigField 'boolean', 'enable_donation', 'true' //The field to enable donation reminder popup in the app if enable_donation is set to 'false' this will be disabled. buildConfigField 'boolean', 'enable_donation_reminder', 'true' //The duration in days to trigger the donation reminder buildConfigField 'int', 'donation_reminder_duration', '7' // ActionBar design // if center_actionbar_title is set to true, the actionbar title and subtitle will be centered, otherwise default buildConfigField 'boolean', 'actionbar_center_title', 'true' buildConfigField 'boolean', 'actionbar_capitalize_title', 'false' // Build apks for each architecture, in addition to one 'fat' apk containing libraries for all all architectures // enable this if you're publishing in gplay ext { splitApk = true } //************************************************************************** //************************************************************************** } fatweb { dimension "abi" ext { abiVersionCode = 0 abiFilter = "web" } } fat { dimension "abi" ext { abiVersionCode = 0 abiFilter = "" } } x86 { dimension "abi" ndk { abiFilters "x86" } ext { abiVersionCode = 1 abiFilter = "x86" } } armv7 { dimension "abi" ndk { abiFilters "armeabi-v7a" } ext { abiVersionCode = 2 abiFilter = "armeabi-v7a" } } x86_64 { dimension "abi" ndk { abiFilters "x86_64" } ext { abiVersionCode = 3 abiFilter = "x86_64" } } arm64 { dimension "abi" ndk { abiFilters "arm64-v8a" } ext { abiVersionCode = 4 abiFilter = "arm64-v8a" } } } buildTypes { buildTypes.all { ext.appSuffix = "" } release { //runProguard true //ndk.debugSymbolLevel = "full" //minifyEnabled = true //shrinkResources true buildConfigField "Boolean", "DEBUG_MODE", "false" } beta { initWith release applicationIdSuffix ".beta" appSuffix = " Beta" buildConfigField "Boolean", "DEBUG_MODE", "true" testCoverageEnabled = false // tor-android doesn't know this build-type, fallback to release in that case matchingFallbacks = ['release'] } debug { testCoverageEnabled = false buildConfigField "Boolean", "DEBUG_MODE", "true" } } lintOptions { abortOnError false } sourceSets { main { assets.srcDirs = ['assets', 'ovpnlibs/assets', '../ics-openvpn/main/build/ovpnassets', ] jniLibs.srcDirs = ['../ics-openvpn/main/build/intermediates/cmake/skeletonOvpn2Release/obj/'] jni.srcDirs = [] //disable automatic ndk-build } debug { assets.srcDirs = ['src/debug/assets'] } test { resources.srcDirs += ['src/test/resources'] } fatweb { java.srcDirs += ['src/fatweb/java'] } fat { java.srcDirs += ['src/notFatweb/java'] } x86 { java.srcDirs += ['src/notFatweb/java'] } x86_64 { java.srcDirs += ['src/notFatweb/java'] } armv7 { java.srcDirs += ['src/notFatweb/java'] } arm64 { java.srcDirs += ['src/notFatweb/java'] } } /** * BUILD VARAIANTS: * ================= * Development builds: * -------------------- * customProductionFatDebug -> branded development build, includes all ABIs * normalProductionFatDebug -> Bitmask development build, includes all ABIS * customProductionFatwebDebug -> branded development build, includes all ABIs, for distribution through a download page * normalProductionFatWebDebug -> Bitmask development build, includes all ABIS, for distribution through a download page * * Branded Releases: * ----------------- * customProductionFatBeta -> branded build, includes all ABI's, Beta release * customProductionFatRelease -> branded build, includes all ABI's, stable release (-> F-Droid, GPlay if not splitApk is set to true) * customProductionFatwebRelease -> branded build, includes all ABI's, stable release (-> F-Droid, GPlay if not splitApk is set to true), for distribution through a download page * * Bitmask Beta releases: * ---------------------- * normalProductionArm64Beta -> Bitmask build, only for ABI arm64, for GPlay Beta channel with split apks (1 of 4) * normalProductionArmv7Beta -> Bitmask build, only for ABI armeabi-v7a, for GPlay Beta channel with split apks (2 of 4) * normalProductionX86Beta -> Bitmask build, only for ABI x86, for GPlay Beta channel with split apks (3 of 4) * normalProductionX86_64Beta -> Bitmask build, only for ABI x86 64 bit, for GPlay Beta channel with split apks (4 of 4) * normalProductionFatBeta -> Bitmask build, including all ABIS, for izzysoft's F-Droid repo and beta link on download page * * Bitmask Stable releases: * ------------------------ * normalProductionArm64Release -> Bitmask build, only for ABI arm64, for GPlay releases with split apks (1 of 4) * normalProductionArmv7Release -> Bitmask build, only for ABI armeabi-v7a, for GPlay releases with split apks (2 of 4) * normalProductionX86Release -> Bitmask build, only for ABI x86, for GPlay releases with split apks (3 of 4) * normalProductionX86_64Release -> Bitmask build, only for ABI x86 64 bit, for GPlay releases with split apks (4 of 4) * normalProductionFatRelease -> Bitmask build, including all ABIS, for official F-Droid repo * normalProductionFatWebRelease -> Bitmask build, including all ABIS, for distribution through a download page */ variantFilter { variant -> def names = variant.flavors*.name def buildTypeName = variant.buildType.name // flavorDimensions "branding" -> 0, "implementation" -> 1, "abi" -> 2 def supportsSplitApk = variant.flavors[0].splitApk def branding = variant.flavors[0].name // To check for a certain build type, use variant.buildType.name == "" if (((buildTypeName.contains("debug") && !(names.contains("fatweb") || names.contains("fat"))) || (names.contains("fatweb") && buildTypeName.contains("beta")) || (!supportsSplitApk && !names.contains("fat"))) ) { // Gradle ignores any variants that satisfy the conditions above. setIgnore(true) } } } dependencies { testImplementation 'junit:junit:4.13.2' //outdated mockito-core version due to powermock dependency testImplementation 'org.mockito:mockito-core:3.6.0' testImplementation('org.powermock:powermock-api-mockito2:2.0.9') // { exclude group: 'junit' exclude group: 'org.mockito' } testImplementation 'org.powermock:powermock-module-junit4:2.0.9' testImplementation 'org.powermock:powermock-core:2.0.9' testImplementation 'org.powermock:powermock-module-junit4-rule:2.0.9' testImplementation group: 'com.tngtech.java', name: 'junit-dataprovider', version: '1.10.0' androidTestImplementation 'org.mockito:mockito-core:3.9.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.0' androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.0' androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' androidTestImplementation 'tools.fastlane:screengrab:2.1.1' testImplementation 'tools.fastlane:screengrab:2.1.1' testImplementation 'org.json:json:20180813' androidTestImplementation 'androidx.test.ext:junit:1.1.4' debugImplementation 'com.squareup.leakcanary:leakcanary-android-core:2.9.1' debugImplementation 'androidx.fragment:fragment-testing:1.6.2' debugImplementation 'androidx.test:core:1.5.0' implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.squareup.okhttp3:okhttp:4.10.0' implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.10.0' implementation 'org.conscrypt:conscrypt-android:2.5.2' implementation 'androidx.security:security-crypto:1.1.0-alpha06' implementation 'androidx.legacy:legacy-support-core-utils:1.0.0' implementation 'androidx.annotation:annotation:1.7.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.10.0' implementation 'androidx.fragment:fragment:1.6.2' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' implementation 'de.hdodenhof:circleimageview:3.1.0' //implementation 'info.guardianproject:tor-android:0.4.5.7' //implementation 'info.guardianproject:jtorctl:0.4.5.7' implementation project(path: ':tor-android:tor-android-binary') fatwebImplementation project(path: ':lib-bitmask-core-web') fatImplementation project(path: ':lib-bitmask-core') x86Implementation project(path: ':lib-bitmask-core-x86') x86_64Implementation project(path: ':lib-bitmask-core-x86_64') armv7Implementation project(path: ':lib-bitmask-core-armv7') arm64Implementation project(path: ':lib-bitmask-core-arm64') } android.applicationVariants.all { variant -> // configure app name and apk file name for different build variants def flavors = variant.productFlavors // flavorDimensions "branding" -> 0, "implementation" -> 1, "abi" -> 2 def branding = flavors[0] def trimmedAppName = branding.appName.replaceAll(' ', '') def abiDimension = flavors[2] def abiFilter = abiDimension.abiFilter if (abiFilter.length() > 0) { abiFilter = "_"+abiFilter } def buildType = variant.buildType def tag = getTag() if (tag.length() > 0) { tag = "_"+tag } variant.resValue "string", "app_name", "\"${branding.appName}${buildType.appSuffix}\"" variant.outputs.all { output -> output.outputFileName = "${trimmedAppName}${abiFilter}_${buildType.name}${tag}.apk" } // reconfigure version codes for split builds variant.outputs.each { output -> // if not a fat build if (abiDimension.abiVersionCode > 0) { output.versionCodeOverride = android.defaultConfig.versionCode + abiDimension.abiVersionCode } } // remove unrelated abi specific assets variant.mergeAssetsProvider.get().doLast { // if not a fat build if (abiDimension.abiVersionCode > 0) { def filesToDelete = fileTree(dir: variant.mergeAssets.outputDir, excludes: ["*pie_openvpn.${abiDimension.abiFilter}", 'urls/', '*.url', '*.json', '*.pem', 'fronts']) delete(filesToDelete) } } } subprojects { afterEvaluate {project -> if (project.hasProperty("android")) { android { compileSdkVersion 33 } } } } task updateIcsOpenVpn( type: Exec ) { commandLine 'git', 'submodule', 'sync' commandLine 'git', 'submodule', 'update', '--init', '--recursive' } def getTag() { String commit = "git log --pretty=format:'%h' -n 1".execute().text.trim().replaceAll("'", "") return ("git describe --tags --exact-match "+ commit).execute().text.trim() } task cleanNative( type: Delete ) { def shouldClean = getCurrentFlavorForBetaOrRelease() == "production" println "cleanNative: " + shouldClean if (shouldClean) { def dirName = "obj" file( dirName ).list().each{ f -> delete "${dirName}/${f}" } } } task updateSdkLicences ( type: Exec ) { println "say yes to licenses" commandLine 'sh', 'yes', '|', 'sdkmanager', '--licenses' } def getCurrentFlavorForBetaOrRelease() { Gradle gradle = getGradle() String tskReqStr = gradle.getStartParameter().getTaskRequests().toString() Pattern pattern; if( tskReqStr.contains( "assemble" ) ) pattern = Pattern.compile("assemble(\\w+)(Beta|Release)") else pattern = Pattern.compile("generate(\\w+)(Beta|Release)") Matcher matcher = pattern.matcher( tskReqStr ) if( matcher.find() ) return matcher.group(1).toLowerCase() else { return ""; } }