import java.util.concurrent.ExecutionException import java.util.regex.Matcher import java.util.regex.Pattern apply plugin: 'com.android.application' def appName = 'Bitmask' android { compileSdkVersion 27 buildToolsVersion '27.0.3' compileOptions { targetCompatibility 1.8 sourceCompatibility 1.8 } defaultConfig { applicationId "se.leap.bitmaskclient" versionCode 134 versionName "0.9.8" resValue "string", "app_name", appName 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://leap.se/en/about-us/donate"' //This is the donation URL and should be set to the relevant donation page. buildConfigField 'String', 'donation_url', 'null' //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', '30' //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' //ignore the following config, only used in custom flavor buildConfigField "String", "customProviderUrl", '""' testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" dexOptions { jumboMode true multiDexEnabled true } } signingConfigs { release { storeFile project.hasProperty('storeFileProperty') ? file(storeFileProperty) : null storePassword project.hasProperty('storePasswordProperty') ? storePasswordProperty : "" keyAlias project.hasProperty('keyAliasProperty') ? keyAliasProperty : "" keyPassword project.hasProperty('keyPasswordProperty') ? keyPasswordProperty : "" } } flavorDimensions "branding", "implementation" productFlavors { production { dimension "implementation" } insecure { dimension "implementation" } normal { dimension "branding" } custom { dimension "branding" //************************************************************************** //************************************************************************** //Configurations for custom branded app. //Change the package name as needed, e.g. "org.example.myapp" applicationId "net.riseup.black" //Set app name here appName = "Riseup VPN" resValue "string", "app_name", appName //Provider base url, e.g. '"https://example.com"' def customProviderUrl = '"https://riseup.net"' buildConfigField "String", "customProviderUrl", customProviderUrl //Change the versionCode as needed versionCode 1 //Change the versionName as needed versionName "1.0" //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' //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', '30' //************************************************************************** //************************************************************************** } } buildTypes { release { //runProguard true if(signingConfigs.contains(release)) signingConfig signingConfigs.release.isSigningReady() ? signingConfigs.release : signingConfigs.debug } beta { initWith release applicationIdSuffix ".beta" resValue "string", "app_name", appName + " Beta" } debug { testCoverageEnabled = true } } lintOptions { abortOnError false } sourceSets { main { assets.srcDirs = ['assets', 'ovpnlibs/assets', '../ics-openvpn/main/build/ovpnassets'] jniLibs.srcDirs = ['../ics-openvpn/main/build/intermediates/cmake/noovpn3/release/obj'] jni.srcDirs = [] //disable automatic ndk-build } debug { assets.srcDirs = ['src/debug/assets'] } test { resources.srcDirs += ['src/test/resources'] java.srcDirs += ['src/sharedTest/java'] } androidTest { java.srcDirs += ['src/sharedTest/java'] } } } dependencies { testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.8.9' testImplementation('org.powermock:powermock-api-mockito2:1.7.3') { exclude group: 'junit' exclude group: 'org.mockito' } testImplementation 'org.powermock:powermock-module-junit4:1.7.3' testImplementation 'org.powermock:powermock-core:1.7.3' testImplementation 'org.powermock:powermock-module-junit4-rule:1.7.3' androidTestImplementation 'org.mockito:mockito-core:2.8.9' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-intents:3.0.1' //TODO: remove that library androidTestImplementation 'com.jayway.android.robotium:robotium-solo:5.6.3' testImplementation 'junit:junit:4.12' testImplementation 'org.json:json:20170516' debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4' releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' betaImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' annotationProcessor 'com.jakewharton:butterknife:6.1.0' annotationProcessor 'com.squareup.dagger:dagger-compiler:1.2.2' implementation 'com.jakewharton:butterknife:6.1.0' //TODO: replace that library compileOnly 'com.squareup.dagger:dagger-compiler:1.2.2' //TODO: remove that library? implementation 'com.github.pedrovgs:renderers:1.5' implementation 'com.intellij:annotations:12.0' implementation 'com.google.code.gson:gson:2.8.2' implementation 'com.squareup.okhttp3:okhttp:3.9.0' implementation "com.android.support:support-core-utils:27.0.2" implementation 'com.android.support:support-annotations:27.0.2' implementation 'com.android.support:support-v4:27.0.2' implementation 'com.android.support:appcompat-v7:27.0.2' implementation 'com.android.support:design:27.0.2' implementation 'com.android.support:support-fragment:27.0.2' implementation 'com.android.support.constraint:constraint-layout:1.0.2' implementation 'com.android.support:multidex:1.0.2' implementation 'com.android.support:cardview-v7:27.0.2' } // Ensure the no-op dependency is always used in JVM tests. configurations.all { config -> if (config.name.contains('UnitTest')) { config.resolutionStrategy.eachDependency { details -> if (details.requested.group == 'com.squareup.leakcanary' && details.requested.name == 'leakcanary-android') { details.useTarget(group: details.requested.group, name: 'leakcanary-android-no-op', version: details.requested.version) } } } resolutionStrategy.force "com.android.support:support-annotations:27.0.2" resolutionStrategy.force "com.android.support:support-v4:27.0.2" resolutionStrategy.force "com.android.support:support-core-utils:27.0.2" resolutionStrategy.force "com.android.support:appcompat-v7:27.0.2" resolutionStrategy.force "com.android.support:design:27.0.2" resolutionStrategy.force "com.android.support:support-fragment:27.0.2" } def processFileInplace(file, Closure processText) { def text = file.text file.write(processText(text)) } task copyIcsOpenVPNClasses( type: Copy ) { println "copyIcsOpenVPNClasses" from ('../ics-openvpn/main/') { include '**/LaunchVPN.java' include '**/OpenVPNSservice.java' include '**/VpnProfile.java' include '**/DisconnectVPN.java' include '**/VpnProfile.java' include '**/SeekBarTicks.java' include '**/core/**.java' include '**/activities/BaseActivity.java' include '**/APIVpnProfile.java' include '**/aidl/**/api/**.aidl' include '**/aidl/**/core/**.aidl' includeEmptyDirs = false filter { line -> line.replaceAll('de.blinkt.openvpn.R', 'se.leap.bitmaskclient.R') } filter { line -> line.replaceAll('de.blinkt.openvpn.BuildConfig', 'se.leap.bitmaskclient.BuildConfig') } filter { line -> line.replace('package de.blinkt.openvpn;', 'package de.blinkt.openvpn;\n\nimport se.leap.bitmaskclient.R;') } } into '.' } task copyIcsOpenVPNXml( type: Copy ) { println "copyIcsOpenVPNXml" from ('../ics-openvpn/main/') { include '**/strings.xml' include '**/refs.xml' include '**/white_rect.xml' include '**/plurals.xml' includeEmptyDirs = false rename 'strings.xml', 'strings-icsopenvpn.xml' rename 'plurals.xml', 'plurals-icsopenvpn.xml' filter { line -> line.replaceAll('.*name="app".*', '') } } into '.' } task copyIcsOpenVPNImages( type: Copy ) { println "copyIcsOpenVPNImages" from ('../ics-openvpn/main/') { include '**/ic_filter*.png' include '**/ic_delete*.png' include '**/ic_share*.png' include '**/ic_close*.png' include '**/ic_edit*.png' include '**/ic_check*.png' include '**/ic_pause*.png' include '**/ic_play*.png' include '**/ic_content_copy_white_*.png' include '**/ic_add_circle_outline_white_*.png' include '**/ic_warning_black_*.png' include '**/ic_add_circle_outline_grey600_*.png' include '**/ic_archive_grey600_*.png' include '**/ic_receipt_white_*.png' include '**/ic_sort_white_*.png' include '**/ic_content_copy_white_*.png' include '**/ic_archive_white_*.png' include '**/ic_menu_archive*.png' include '**/vpn_item_settings*.png' include '**/ic_menu_log*.png' include '**/ic_menu_copy_holo_light*.png' includeEmptyDirs = false } into '.' } // thanks to http://pleac.sourceforge.net/pleac_groovy/fileaccess.html task removeDuplicatedStrings() { println "removeDuplicatedStrings" new File('.').eachFileRecurse { if(it.name.equals('strings.xml') || it.name.equals('plurals.xml')) { replaceDuplicatesForSource(it, it.name.substring(0, it.name.lastIndexOf('.'))) } } } def replaceDuplicatesForSource(File it, String type) { def ics_openvpn_file = file(it.absolutePath.replace(type+'.xml', type+'-icsopenvpn.xml')) if(ics_openvpn_file.exists()) { def ics_openvpn_strings_names = (new XmlParser()).parse(ics_openvpn_file) def current_file = it ics_openvpn_strings_names.string.each { processFileInplace(current_file) { text -> text.replaceAll('.*name=\"' + it.attribute('name') + '\".*(\n)*.*string>.*\n+', '') } } } } task mergeUntranslatable( type: Copy ) { println "mergeUntranslatable" from ('../ics-openvpn/main/') { include '**/untranslatable.xml' rename 'untranslatable.xml', 'untranslatable-icsopenvpn.xml' } into '.' def bitmask_untranslatable = file('src/main/res/values/untranslatable.xml') def ics_openvpn_untranslatable = new File(bitmask_untranslatable.path.replace('untranslatable.xml', 'untranslatable-icsopenvpn.xml')) ics_openvpn_untranslatable.createNewFile() def string_continuation = false; ics_openvpn_untranslatable.eachLine { text -> if(text.contains('string name=')) { if(!bitmask_untranslatable.text.contains(text)) bitmask_untranslatable << text if(text.contains('')) string_continuation = true } else if(string_continuation) { bitmask_untranslatable << text } if(text.contains('')) { string_continuation = false bitmask_untranslatable << System.getProperty("line.separator") } } bitmask_untranslatable.write(bitmask_untranslatable.text.replaceAll("", "")) bitmask_untranslatable << "" delete ics_openvpn_untranslatable } task copyIcsOpenVPNFiles( type: Copy ) { println "copyIcsOpenVPNFiles" copyIcsOpenVPNClasses.execute() copyIcsOpenVPNXml.execute() copyIcsOpenVPNImages.execute() //mergeUntranslatable.execute() removeDuplicatedStrings.execute() } task updateIcsOpenVpn( type: Exec ) { commandLine 'git', 'submodule', 'sync' commandLine 'git', 'submodule', 'update', '--init', '--recursive' copyIcsOpenVPNFiles.execute() } 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 ""; } } def getCurrentFlavor() { Gradle gradle = getGradle() String tskReqStr = gradle.getStartParameter().getTaskRequests().toString() Pattern pattern; if (tskReqStr.contains("assemble")) pattern = Pattern.compile("assemble(\\w+)(Beta|Release|Debug)") else pattern = Pattern.compile("generate(\\w+)(Beta|Release|Debug)") Matcher matcher = pattern.matcher(tskReqStr) if (matcher.find()) return matcher.group(1).toLowerCase() else { return ""; } } task checkApplicationIdForCustomFlavor(type: Exec) { def currFlavor = getCurrentFlavor() if (currFlavor.contains("custom")) { android.applicationVariants.all { variant -> def mergedFlavor = variant.mergedFlavor if (variant.flavorName.toString().equalsIgnoreCase(currFlavor) && mergedFlavor.getApplicationId().equalsIgnoreCase("org.sample.custom")) throw new ExecutionException("ERROR: please change the applicationId(org.sample.custom) if you want to build a custom branded app!") } } } task checkAppNameForCustomFlavor(type: Exec) { def currFlavor = getCurrentFlavor() if (currFlavor.contains("custom") && appName.equalsIgnoreCase("custom")) throw new ExecutionException("ERROR: please change the appName(Custom) if you want to build a custom branded app!") }