| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064 |
- /* _____ _
- * |_ _| |_ _ _ ___ ___ _ __ __ _
- * | | | ' \| '_/ -_) -_) ' \/ _` |_
- * |_| |_||_|_| \___\___|_|_|_\__,_(_)
- *
- * Threema for Android
- * Copyright (c) 2014-2025 Threema GmbH
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- import com.android.build.gradle.internal.api.ApkVariantOutputImpl
- import config.PublicKeys
- import config.setProductNames
- import org.gradle.api.tasks.testing.logging.TestExceptionFormat
- import utils.*
- plugins {
- alias(libs.plugins.sonarqube)
- alias(libs.plugins.kotlin.serialization)
- alias(libs.plugins.rust.android)
- id("com.android.application")
- id("kotlin-android")
- alias(libs.plugins.ksp)
- alias(libs.plugins.compose.compiler)
- alias(libs.plugins.stem)
- }
- // only apply the plugin if we are dealing with an AppGallery build
- if (gradle.startParameter.taskRequests.toString().contains("Hms")) {
- logger.info("enabling hms plugin")
- apply {
- plugin("com.huawei.agconnect")
- }
- }
- /**
- * Only use the scheme "<major>.<minor>.<patch>" for the appVersion
- */
- val appVersion = "6.1.3"
- /**
- * betaSuffix with leading dash (e.g. `-beta1`).
- * Should be one of (alpha|beta|rc) and an increasing number, or empty for a regular release.
- * Note: in nightly builds this will be overwritten with a nightly version "-n12345"
- */
- val betaSuffix = ""
- val defaultVersionCode = 1087
- /**
- * Map with keystore paths (if found).
- */
- val keystores: Map<String, KeystoreConfig?> = mapOf(
- "debug" to findKeystore(projectDir, "debug"),
- "release" to findKeystore(projectDir, "threema"),
- "hms_release" to findKeystore(projectDir, "threema_hms"),
- "onprem_release" to findKeystore(projectDir, "onprem"),
- "blue_release" to findKeystore(projectDir, "threema_blue"),
- )
- android {
- // NOTE: When adjusting compileSdkVersion, buildToolsVersion or ndkVersion,
- // make sure to adjust them in `scripts/Dockerfile` as well!
- compileSdk = 35
- buildToolsVersion = "35.0.0"
- ndkVersion = "25.2.9519653"
- defaultConfig {
- // https://developer.android.com/training/testing/espresso/setup#analytics
- with(testInstrumentationRunnerArguments) {
- put("notAnnotation", "ch.threema.app.DangerousTest")
- put("disableAnalytics", "true")
- }
- minSdk = 21
- //noinspection OldTargetApi
- targetSdk = 34
- vectorDrawables.useSupportLibrary = true
- applicationId = "ch.threema.app"
- testApplicationId = "$applicationId.test"
- versionCode = defaultVersionCode
- versionName = "$appVersion$betaSuffix"
- setProductNames(
- appName = "Threema",
- )
- // package name used for sync adapter - needs to match mime types below
- stringResValue("package_name", applicationId!!)
- stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.$applicationId.profile")
- stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.$applicationId.call")
- intBuildConfigField("MAX_GROUP_SIZE", 256)
- stringBuildConfigField("CHAT_SERVER_PREFIX", "g-")
- stringBuildConfigField("CHAT_SERVER_IPV6_PREFIX", "ds.g-")
- stringBuildConfigField("CHAT_SERVER_SUFFIX", ".0.threema.ch")
- intArrayBuildConfigField("CHAT_SERVER_PORTS", intArrayOf(5222, 443))
- stringBuildConfigField("MEDIA_PATH", "Threema")
- booleanBuildConfigField("CHAT_SERVER_GROUPS", true)
- booleanBuildConfigField("DISABLE_CERT_PINNING", false)
- booleanBuildConfigField("VIDEO_CALLS_ENABLED", true)
- // This public key is pinned for the chat server protocol.
- byteArrayBuildConfigField("SERVER_PUBKEY", PublicKeys.prodServer)
- byteArrayBuildConfigField("SERVER_PUBKEY_ALT", PublicKeys.prodServerAlt)
- stringBuildConfigField("GIT_HASH", getGitHash())
- stringBuildConfigField("DIRECTORY_SERVER_URL", "https://apip.threema.ch/")
- stringBuildConfigField("DIRECTORY_SERVER_IPV6_URL", "https://ds-apip.threema.ch/")
- stringBuildConfigField("WORK_SERVER_URL", null)
- stringBuildConfigField("WORK_SERVER_IPV6_URL", null)
- stringBuildConfigField("MEDIATOR_SERVER_URL", "wss://mediator-{deviceGroupIdPrefix4}.threema.ch/{deviceGroupIdPrefix8}")
- // Base blob url used for "download" and "done" calls
- stringBuildConfigField("BLOB_SERVER_URL", "https://blobp-{blobIdPrefix}.threema.ch")
- stringBuildConfigField("BLOB_SERVER_IPV6_URL", "https://ds-blobp-{blobIdPrefix}.threema.ch")
- // Specific blob url used for "upload" calls
- stringBuildConfigField("BLOB_SERVER_URL_UPLOAD", "https://blobp-upload.threema.ch/upload")
- stringBuildConfigField("BLOB_SERVER_IPV6_URL_UPLOAD", "https://ds-blobp-upload.threema.ch/upload")
- // Base blob mirror url used for "download", "upload", "done"
- stringBuildConfigField("BLOB_MIRROR_SERVER_URL", "https://blob-mirror-{deviceGroupIdPrefix4}.threema.ch/{deviceGroupIdPrefix8}")
- stringBuildConfigField("AVATAR_FETCH_URL", "https://avatar.threema.ch/")
- stringBuildConfigField("SAFE_SERVER_URL", "https://safe-{backupIdPrefix8}.threema.ch/")
- stringBuildConfigField("WEB_SERVER_URL", "https://web.threema.ch/")
- stringBuildConfigField("APP_RATING_URL", "https://threema.com/app-rating/android/{rating}")
- stringBuildConfigField("MAP_STYLES_URL", "https://map.threema.ch/styles/threema/style.json")
- stringBuildConfigField("MAP_POI_AROUND_URL", "https://poi.threema.ch/around/{latitude}/{longitude}/{radius}/")
- stringBuildConfigField("MAP_POI_NAMES_URL", "https://poi.threema.ch/names/{latitude}/{longitude}/{query}/")
- byteArrayBuildConfigField("THREEMA_PUSH_PUBLIC_KEY", PublicKeys.threemaPush)
- stringBuildConfigField("ONPREM_ID_PREFIX", "O")
- stringBuildConfigField("LOG_TAG", "3ma")
- stringBuildConfigField("DEFAULT_APP_THEME", "2")
- stringArrayBuildConfigField("ONPREM_CONFIG_TRUSTED_PUBLIC_KEYS", emptyArray())
- booleanBuildConfigField("MD_SYNC_DISTRIBUTION_LISTS", false)
- booleanBuildConfigField("EDIT_MESSAGES_ENABLED", true)
- booleanBuildConfigField("DELETE_MESSAGES_ENABLED", true)
- booleanBuildConfigField("AVAILABILITY_STATUS_ENABLED", false)
- // config fields for action URLs / deep links
- stringBuildConfigField("uriScheme", "threema")
- stringBuildConfigField("actionUrl", "go.threema.ch")
- stringBuildConfigField("contactActionUrl", "threema.id")
- stringBuildConfigField("groupLinkActionUrl", "threema.group")
- // The OPPF url must be null in the default config. Do not change this.
- stringBuildConfigField("PRESET_OPPF_URL", null)
- with(manifestPlaceholders) {
- put("uriScheme", "threema")
- put("contactActionUrl", "threema.id")
- put("groupLinkActionUrl", "threema.group")
- put("actionUrl", "go.threema.ch")
- put("callMimeType", "vnd.android.cursor.item/vnd.$applicationId.call")
- }
- testInstrumentationRunner = "$applicationId.ThreemaTestRunner"
- // Only include language resources for those languages
- androidResources.localeFilters.addAll(
- setOf(
- "en",
- "be-rBY",
- "bg",
- "ca",
- "cs",
- "de",
- "es",
- "fr",
- "gsw",
- "hu",
- "it",
- "ja",
- "nl-rNL",
- "no",
- "pl",
- "pt-rBR",
- "ru",
- "sk",
- "tr",
- "uk",
- "zh-rCN",
- "zh-rTW",
- ),
- )
- }
- splits {
- abi {
- isEnable = true
- reset()
- if (project.hasProperty("noAbiSplits")) {
- isUniversalApk = true
- } else {
- include("armeabi-v7a", "x86", "arm64-v8a", "x86_64")
- isUniversalApk = project.hasProperty("buildUniversalApk")
- }
- }
- }
- // Assign different version code for each output
- android.applicationVariants.all {
- outputs.all {
- if (this is ApkVariantOutputImpl) {
- val abi = getFilter("ABI")
- val abiVersionCode = when (abi) {
- "armeabi-v7a" -> 2
- "arm64-v8a" -> 3
- "x86" -> 8
- "x86_64" -> 9
- else -> 0
- }
- versionCodeOverride = abiVersionCode * 1_000_000 + defaultVersionCode
- }
- }
- }
- namespace = "ch.threema.app"
- flavorDimensions.add("default")
- productFlavors {
- create("none")
- create("store_google")
- create("store_threema") {
- stringResValue("shop_download_filename", "Threema-update.apk")
- }
- create("store_google_work") {
- versionName = "${appVersion}k$betaSuffix"
- applicationId = "ch.threema.app.work"
- testApplicationId = "$applicationId.test"
- setProductNames(appName = "Threema Work")
- stringResValue("package_name", applicationId!!)
- stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.$applicationId.profile")
- stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.$applicationId.call")
- stringBuildConfigField("CHAT_SERVER_PREFIX", "w-")
- stringBuildConfigField("CHAT_SERVER_IPV6_PREFIX", "ds.w-")
- stringBuildConfigField("MEDIA_PATH", "ThreemaWork")
- stringBuildConfigField("WORK_SERVER_URL", "https://apip-work.threema.ch/")
- stringBuildConfigField("WORK_SERVER_IPV6_URL", "https://ds-apip-work.threema.ch/")
- stringBuildConfigField("APP_RATING_URL", "https://threema.com/app-rating/android-work/{rating}")
- stringBuildConfigField("LOG_TAG", "3mawrk")
- stringBuildConfigField("DEFAULT_APP_THEME", "2")
- // config fields for action URLs / deep links
- stringBuildConfigField("uriScheme", "threemawork")
- stringBuildConfigField("actionUrl", "work.threema.ch")
- with(manifestPlaceholders) {
- put("uriScheme", "threemawork")
- put("actionUrl", "work.threema.ch")
- put("callMimeType", "vnd.android.cursor.item/vnd.$applicationId.call")
- }
- }
- create("green") {
- applicationId = "ch.threema.app.green"
- testApplicationId = "$applicationId.test"
- setProductNames(appName = "Threema Green")
- stringResValue("package_name", applicationId!!)
- stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.$applicationId.profile")
- stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.$applicationId.call")
- stringBuildConfigField("MEDIA_PATH", "ThreemaGreen")
- stringBuildConfigField("CHAT_SERVER_SUFFIX", ".0.test.threema.ch")
- // This public key is pinned for the chat server protocol.
- byteArrayBuildConfigField("SERVER_PUBKEY", PublicKeys.sandboxServer)
- byteArrayBuildConfigField("SERVER_PUBKEY_ALT", PublicKeys.sandboxServer)
- stringBuildConfigField("DIRECTORY_SERVER_URL", "https://apip.test.threema.ch/")
- stringBuildConfigField("DIRECTORY_SERVER_IPV6_URL", "https://ds-apip.test.threema.ch/")
- stringBuildConfigField("MEDIATOR_SERVER_URL", "wss://mediator-{deviceGroupIdPrefix4}.test.threema.ch/{deviceGroupIdPrefix8}")
- stringBuildConfigField("AVATAR_FETCH_URL", "https://avatar.test.threema.ch/")
- stringBuildConfigField("APP_RATING_URL", "https://test.threema.com/app-rating/android/{rating}")
- stringBuildConfigField("MAP_STYLES_URL", "https://map.test.threema.ch/styles/threema/style.json")
- stringBuildConfigField("MAP_POI_AROUND_URL", "https://poi.test.threema.ch/around/{latitude}/{longitude}/{radius}/")
- stringBuildConfigField("MAP_POI_NAMES_URL", "https://poi.test.threema.ch/names/{latitude}/{longitude}/{query}/")
- stringBuildConfigField("BLOB_MIRROR_SERVER_URL", "https://blob-mirror-{deviceGroupIdPrefix4}.test.threema.ch/{deviceGroupIdPrefix8}")
- }
- create("sandbox_work") {
- versionName = "${appVersion}k$betaSuffix"
- applicationId = "ch.threema.app.sandbox.work"
- testApplicationId = "$applicationId.test"
- setProductNames(
- appName = "Threema Sandbox Work",
- appNameDesktop = "Threema Blue",
- )
- stringResValue("package_name", applicationId!!)
- stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.$applicationId.profile")
- stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.$applicationId.call")
- stringBuildConfigField("CHAT_SERVER_PREFIX", "w-")
- stringBuildConfigField("CHAT_SERVER_IPV6_PREFIX", "ds.w-")
- stringBuildConfigField("CHAT_SERVER_SUFFIX", ".0.test.threema.ch")
- stringBuildConfigField("MEDIA_PATH", "ThreemaWorkSandbox")
- // This public key is pinned for the chat server protocol.
- byteArrayBuildConfigField("SERVER_PUBKEY", PublicKeys.sandboxServer)
- byteArrayBuildConfigField("SERVER_PUBKEY_ALT", PublicKeys.sandboxServer)
- stringBuildConfigField("DIRECTORY_SERVER_URL", "https://apip.test.threema.ch/")
- stringBuildConfigField("DIRECTORY_SERVER_IPV6_URL", "https://ds-apip.test.threema.ch/")
- stringBuildConfigField("WORK_SERVER_URL", "https://apip-work.test.threema.ch/")
- stringBuildConfigField("WORK_SERVER_IPV6_URL", "https://ds-apip-work.test.threema.ch/")
- stringBuildConfigField("MEDIATOR_SERVER_URL", "wss://mediator-{deviceGroupIdPrefix4}.test.threema.ch/{deviceGroupIdPrefix8}")
- stringBuildConfigField("AVATAR_FETCH_URL", "https://avatar.test.threema.ch/")
- stringBuildConfigField("APP_RATING_URL", "https://test.threema.com/app-rating/android-work/{rating}")
- stringBuildConfigField("MAP_STYLES_URL", "https://map.test.threema.ch/styles/threema/style.json")
- stringBuildConfigField("MAP_POI_AROUND_URL", "https://poi.test.threema.ch/around/{latitude}/{longitude}/{radius}/")
- stringBuildConfigField("MAP_POI_NAMES_URL", "https://poi.test.threema.ch/names/{latitude}/{longitude}/{query}/")
- stringBuildConfigField("LOG_TAG", "3mawrk")
- stringBuildConfigField("DEFAULT_APP_THEME", "2")
- stringBuildConfigField("BLOB_MIRROR_SERVER_URL", "https://blob-mirror-{deviceGroupIdPrefix4}.test.threema.ch/{deviceGroupIdPrefix8}")
- // config fields for action URLs / deep links
- stringBuildConfigField("uriScheme", "threemawork")
- stringBuildConfigField("actionUrl", "work.test.threema.ch")
- stringBuildConfigField("MD_CLIENT_DOWNLOAD_URL", "https://three.ma/mdw")
- with(manifestPlaceholders) {
- put("uriScheme", "threemawork")
- put("actionUrl", "work.test.threema.ch")
- }
- }
- create("onprem") {
- versionName = "${appVersion}o$betaSuffix"
- applicationId = "ch.threema.app.onprem"
- testApplicationId = "$applicationId.test"
- setProductNames(
- appName = "Threema OnPrem",
- shortAppName = "Threema",
- companyName = "Threema",
- )
- stringResValue("package_name", applicationId!!)
- stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.$applicationId.profile")
- stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.$applicationId.call")
- intBuildConfigField("MAX_GROUP_SIZE", 256)
- stringBuildConfigField("CHAT_SERVER_PREFIX", "")
- stringBuildConfigField("CHAT_SERVER_IPV6_PREFIX", "")
- stringBuildConfigField("CHAT_SERVER_SUFFIX", null)
- stringBuildConfigField("MEDIA_PATH", "ThreemaOnPrem")
- booleanBuildConfigField("CHAT_SERVER_GROUPS", false)
- byteArrayBuildConfigField("SERVER_PUBKEY", null)
- byteArrayBuildConfigField("SERVER_PUBKEY_ALT", null)
- stringBuildConfigField("DIRECTORY_SERVER_URL", null)
- stringBuildConfigField("DIRECTORY_SERVER_IPV6_URL", null)
- stringBuildConfigField("BLOB_SERVER_URL", null)
- stringBuildConfigField("BLOB_SERVER_IPV6_URL", null)
- stringBuildConfigField("BLOB_SERVER_URL_UPLOAD", null)
- stringBuildConfigField("BLOB_SERVER_IPV6_URL_UPLOAD", null)
- stringBuildConfigField("BLOB_MIRROR_SERVER_URL", null)
- stringArrayBuildConfigField("ONPREM_CONFIG_TRUSTED_PUBLIC_KEYS", PublicKeys.onPremTrusted)
- stringBuildConfigField("LOG_TAG", "3maop")
- // config fields for action URLs / deep links
- val uriScheme = "threemaonprem"
- val actionUrl = "onprem.threema.ch"
- stringBuildConfigField("uriScheme", uriScheme)
- stringBuildConfigField("actionUrl", actionUrl)
- stringBuildConfigField("PRESET_OPPF_URL", null)
- stringBuildConfigField("MD_CLIENT_DOWNLOAD_URL", "https://three.ma/mdo")
- with(manifestPlaceholders) {
- put("uriScheme", uriScheme)
- put("actionUrl", actionUrl)
- put("callMimeType", "vnd.android.cursor.item/vnd.$applicationId.call")
- }
- }
- create("blue") {
- // Essentially like sandbox work, but with a different icon and application id, used for internal testing
- versionName = "${appVersion}b$betaSuffix"
- // The app was previously named `red`. The app id remains unchanged to still be able to install updates.
- applicationId = "ch.threema.app.red"
- testApplicationId = "ch.threema.app.blue.test"
- setProductNames(appName = "Threema Blue")
- stringResValue("package_name", applicationId!!)
- stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.ch.threema.app.blue.profile")
- stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.ch.threema.app.blue.call")
- stringBuildConfigField("CHAT_SERVER_PREFIX", "w-")
- stringBuildConfigField("CHAT_SERVER_IPV6_PREFIX", "ds.w-")
- stringBuildConfigField("CHAT_SERVER_SUFFIX", ".0.test.threema.ch")
- stringBuildConfigField("MEDIA_PATH", "ThreemaBlue")
- // This public key is pinned for the chat server protocol.
- byteArrayBuildConfigField("SERVER_PUBKEY", PublicKeys.sandboxServer)
- byteArrayBuildConfigField("SERVER_PUBKEY_ALT", PublicKeys.sandboxServer)
- stringBuildConfigField("DIRECTORY_SERVER_URL", "https://apip.test.threema.ch/")
- stringBuildConfigField("DIRECTORY_SERVER_IPV6_URL", "https://ds-apip.test.threema.ch/")
- stringBuildConfigField("WORK_SERVER_URL", "https://apip-work.test.threema.ch/")
- stringBuildConfigField("WORK_SERVER_IPV6_URL", "https://ds-apip-work.test.threema.ch/")
- stringBuildConfigField("MEDIATOR_SERVER_URL", "wss://mediator-{deviceGroupIdPrefix4}.test.threema.ch/{deviceGroupIdPrefix8}")
- stringBuildConfigField("AVATAR_FETCH_URL", "https://avatar.test.threema.ch/")
- stringBuildConfigField("APP_RATING_URL", "https://test.threema.com/app-rating/android-work/{rating}")
- stringBuildConfigField("MAP_STYLES_URL", "https://map.test.threema.ch/styles/threema/style.json")
- stringBuildConfigField("MAP_POI_AROUND_URL", "https://poi.test.threema.ch/around/{latitude}/{longitude}/{radius}/")
- stringBuildConfigField("MAP_POI_NAMES_URL", "https://poi.test.threema.ch/names/{latitude}/{longitude}/{query}/")
- stringBuildConfigField("LOG_TAG", "3mablue")
- stringBuildConfigField("BLOB_MIRROR_SERVER_URL", "https://blob-mirror-{deviceGroupIdPrefix4}.test.threema.ch/{deviceGroupIdPrefix8}")
- // config fields for action URLs / deep links
- stringBuildConfigField("uriScheme", "threemablue")
- stringBuildConfigField("actionUrl", "blue.threema.ch")
- with(manifestPlaceholders) {
- put("uriScheme", "threemablue")
- put("actionUrl", "blue.threema.ch")
- put("callMimeType", "vnd.android.cursor.item/vnd.ch.threema.app.blue.call")
- }
- }
- create("hms") {
- applicationId = "ch.threema.app.hms"
- }
- create("hms_work") {
- versionName = "${appVersion}k$betaSuffix"
- applicationId = "ch.threema.app.work.hms"
- testApplicationId = "ch.threema.app.work.test.hms"
- setProductNames(appName = "Threema Work")
- stringResValue("package_name", "ch.threema.app.work")
- stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.ch.threema.app.work.profile")
- stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.ch.threema.app.work.call")
- stringBuildConfigField("CHAT_SERVER_PREFIX", "w-")
- stringBuildConfigField("CHAT_SERVER_IPV6_PREFIX", "ds.w-")
- stringBuildConfigField("MEDIA_PATH", "ThreemaWork")
- stringBuildConfigField("WORK_SERVER_URL", "https://apip-work.threema.ch/")
- stringBuildConfigField("WORK_SERVER_IPV6_URL", "https://ds-apip-work.threema.ch/")
- stringBuildConfigField("APP_RATING_URL", "https://threema.com/app-rating/android-work/{rating}")
- stringBuildConfigField("LOG_TAG", "3mawrk")
- stringBuildConfigField("DEFAULT_APP_THEME", "2")
- // config fields for action URLs / deep links
- stringBuildConfigField("uriScheme", "threemawork")
- stringBuildConfigField("actionUrl", "work.threema.ch")
- with(manifestPlaceholders) {
- put("uriScheme", "threemawork")
- put("actionUrl", "work.threema.ch")
- put("callMimeType", "vnd.android.cursor.item/vnd.ch.threema.app.work.call")
- }
- }
- create("libre") {
- versionName = "${appVersion}l$betaSuffix"
- applicationId = "ch.threema.app.libre"
- testApplicationId = "$applicationId.test"
- stringResValue("package_name", applicationId!!)
- setProductNames(
- appName = "Threema Libre",
- appNameDesktop = "Threema",
- )
- stringBuildConfigField("MEDIA_PATH", "ThreemaLibre")
- }
- }
- signingConfigs {
- // Debug config
- keystores["debug"]
- ?.let { keystore ->
- getByName("debug") {
- storeFile = keystore.storeFile
- }
- }
- ?: run {
- logger.warn("No debug keystore found. Falling back to locally generated keystore.")
- }
- // Release config
- keystores["release"]
- ?.let { keystore ->
- create("release") {
- apply(keystore)
- }
- }
- ?: run {
- logger.warn("No release keystore found. Falling back to locally generated keystore.")
- }
- // Release config
- keystores["hms_release"]
- ?.let { keystore ->
- create("hms_release") {
- apply(keystore)
- }
- }
- ?: run {
- logger.warn("No hms keystore found. Falling back to locally generated keystore.")
- }
- // Onprem release config
- keystores["onprem_release"]
- ?.let { keystore ->
- create("onprem_release") {
- apply(keystore)
- }
- }
- ?: run {
- logger.warn("No onprem keystore found. Falling back to locally generated keystore.")
- }
- // Blue release config
- keystores["blue_release"]
- ?.let { keystore ->
- create("blue_release") {
- apply(keystore)
- }
- }
- ?: run {
- logger.warn("No blue keystore found. Falling back to locally generated keystore.")
- }
- // Note: Libre release is signed with HSM, no config here
- }
- sourceSets {
- getByName("main") {
- assets.srcDirs("assets")
- jniLibs.srcDirs("libs")
- res.srcDir("src/main/res-rendezvous")
- }
- // Based on Google services
- getByName("none") {
- java.srcDir("src/google_services_based/java")
- }
- getByName("store_google") {
- java.srcDir("src/google_services_based/java")
- }
- getByName("store_google_work") {
- java.srcDir("src/google_services_based/java")
- }
- getByName("store_threema") {
- java.srcDir("src/google_services_based/java")
- }
- getByName("libre") {
- assets.srcDirs("src/foss_based/assets")
- java.srcDir("src/foss_based/java")
- }
- getByName("onprem") {
- java.srcDir("src/google_services_based/java")
- }
- getByName("green") {
- java.srcDir("src/google_services_based/java")
- manifest.srcFile("src/store_google/AndroidManifest.xml")
- }
- getByName("sandbox_work") {
- java.srcDir("src/google_services_based/java")
- res.srcDir("src/store_google_work/res")
- manifest.srcFile("src/store_google_work/AndroidManifest.xml")
- }
- getByName("blue") {
- java.srcDir("src/google_services_based/java")
- res.srcDir("src/blue/res")
- }
- // Based on Huawei services
- getByName("hms") {
- java.srcDir("src/hms_services_based/java")
- }
- getByName("hms_work") {
- java.srcDir("src/hms_services_based/java")
- res.srcDir("src/store_google_work/res")
- }
- // FOSS, no proprietary services
- getByName("libre") {
- assets.srcDirs("src/foss_based/assets")
- java.srcDir("src/foss_based/java")
- }
- }
- buildTypes {
- debug {
- isDebuggable = true
- isJniDebuggable = false
- ndk {
- debugSymbolLevel = "FULL"
- }
- enableUnitTestCoverage = false
- enableAndroidTestCoverage = false
- if (keystores["debug"] != null) {
- signingConfig = signingConfigs["debug"]
- }
- }
- release {
- isDebuggable = false
- isJniDebuggable = false
- isMinifyEnabled = true
- isShrinkResources = false // Caused inconsistencies between local and CI builds
- vcsInfo.include = false // For reproducible builds independent from git history
- proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-project.txt")
- ndk {
- debugSymbolLevel = "FULL" // 'SYMBOL_TABLE'
- }
- if (keystores["release"] != null) {
- val releaseSigningConfig = signingConfigs["release"]
- productFlavors["store_google"].signingConfig = releaseSigningConfig
- productFlavors["store_google_work"].signingConfig = releaseSigningConfig
- productFlavors["store_threema"].signingConfig = releaseSigningConfig
- productFlavors["green"].signingConfig = releaseSigningConfig
- productFlavors["sandbox_work"].signingConfig = releaseSigningConfig
- productFlavors["none"].signingConfig = releaseSigningConfig
- }
- if (keystores["hms_release"] != null) {
- val hmsReleaseSigningConfig = signingConfigs["hms_release"]
- productFlavors["hms"].signingConfig = hmsReleaseSigningConfig
- productFlavors["hms_work"].signingConfig = hmsReleaseSigningConfig
- }
- if (keystores["onprem_release"] != null) {
- productFlavors["onprem"].signingConfig = signingConfigs["onprem_release"]
- }
- if (keystores["blue_release"] != null) {
- productFlavors["blue"].signingConfig = signingConfigs["blue_release"]
- }
- // Note: Libre release is signed with HSM, no config here
- }
- }
- externalNativeBuild {
- ndkBuild {
- path("jni/Android.mk")
- }
- }
- packaging {
- jniLibs {
- // replacement for extractNativeLibs in AndroidManifest
- useLegacyPackaging = true
- }
- resources {
- excludes.addAll(
- setOf(
- "META-INF/DEPENDENCIES.txt",
- "META-INF/LICENSE.txt",
- "META-INF/LICENSE.md",
- "META-INF/LICENSE-notice.md",
- "META-INF/NOTICE.txt",
- "META-INF/NOTICE",
- "META-INF/LICENSE",
- "META-INF/DEPENDENCIES",
- "META-INF/notice.txt",
- "META-INF/license.txt",
- "META-INF/dependencies.txt",
- "META-INF/LGPL2.1",
- "**/*.proto",
- "DebugProbesKt.bin",
- ),
- )
- }
- }
- testOptions {
- // Disable animations in instrumentation tests
- animationsDisabled = true
- unitTests {
- all { test ->
- test.outputs.upToDateWhen { false }
- test.testLogging {
- events("passed", "skipped", "failed", "standardOut", "standardError")
- exceptionFormat = TestExceptionFormat.FULL
- }
- test.jvmArgs = test.jvmArgs!! + listOf(
- "--add-opens=java.base/java.util=ALL-UNNAMED",
- "--add-opens=java.base/java.util.stream=ALL-UNNAMED",
- "--add-opens=java.base/java.lang=ALL-UNNAMED",
- "-Xmx4096m",
- )
- }
- // By default, local unit tests throw an exception any time the code you are testing tries to access
- // Android platform APIs (unless you mock Android dependencies yourself or with a testing
- // framework like Mockito). However, you can enable the following property so that the test
- // returns either null or zero when accessing platform APIs, rather than throwing an exception.
- isReturnDefaultValues = true
- }
- }
- compileOptions {
- isCoreLibraryDesugaringEnabled = true
- sourceCompatibility = JavaVersion.VERSION_11
- targetCompatibility = JavaVersion.VERSION_11
- }
- java {
- toolchain {
- languageVersion.set(JavaLanguageVersion.of(17))
- }
- }
- kotlin {
- jvmToolchain(17)
- }
- androidResources {
- noCompress.add("png")
- }
- lint {
- // if true, stop the gradle build if errors are found
- abortOnError = true
- // if true, check all issues, including those that are off by default
- checkAllWarnings = true
- // check dependencies
- checkDependencies = true
- // set to true to have all release builds run lint on issues with severity=fatal
- // and abort the build (controlled by abortOnError above) if fatal issues are found
- checkReleaseBuilds = true
- // turn off checking the given issue id's
- disable.addAll(setOf("TypographyFractions", "TypographyQuotes", "RtlHardcoded", "RtlCompat", "RtlEnabled"))
- // Set the severity of the given issues to error
- error.addAll(setOf("Wakelock", "TextViewEdits", "ResourceAsColor"))
- // Set the severity of the given issues to fatal (which means they will be
- // checked during release builds (even if the lint target is not included)
- fatal.addAll(setOf("NewApi", "InlinedApi"))
- ignoreWarnings = false
- // if true, don't include source code lines in the error output
- noLines = false
- // if true, show all locations for an error, do not truncate lists, etc.
- showAll = true
- // Set the severity of the given issues to warning
- warning.add("MissingTranslation")
- // if true, treat all warnings as errors
- warningsAsErrors = false
- // file to write report to (if not specified, defaults to lint-results.xml)
- xmlOutput = file("lint-report.xml")
- // if true, generate an XML report for use by for example Jenkins
- xmlReport = true
- }
- buildFeatures {
- compose = true
- buildConfig = true
- }
- }
- // Only build relevant buildType / flavor combinations
- androidComponents {
- beforeVariants { variant ->
- val name = variant.name
- if (variant.buildType == "release" && ("green" in name || "sandbox_work" in name)) {
- variant.enable = false
- }
- }
- }
- dependencies {
- configurations.all {
- // Prefer modules that are part of this build (multi-project or composite build)
- // over external modules
- resolutionStrategy.preferProjectModules()
- // Alternatively, we can fail eagerly on version conflict to see the conflicts
- // resolutionStrategy.failOnVersionConflict()
- }
- coreLibraryDesugaring(libs.desugarJdkLibs)
- implementation(project(":domain"))
- implementation(project(":common"))
- implementation(libs.sqlcipher.android)
- implementation(libs.subsamplingScaleImageView)
- implementation(libs.opencsv)
- implementation(libs.zip4j)
- implementation(libs.taptargetview)
- implementation(libs.commonsIo)
- implementation(libs.commonsText)
- implementation(libs.slf4j.api)
- implementation(libs.androidImageCropper)
- implementation(libs.trustkit)
- implementation(libs.fastscroll)
- implementation(libs.ezVcard)
- implementation(libs.gestureViews)
- // AndroidX / Jetpack support libraries
- implementation(libs.androidx.preference)
- implementation(libs.androidx.recyclerview)
- implementation(libs.androidx.palette)
- implementation(libs.androidx.swiperefreshlayout)
- implementation(libs.androidx.core)
- implementation(libs.androidx.appcompat)
- implementation(libs.androidx.constraintlayout)
- implementation(libs.androidx.biometric)
- implementation(libs.androidx.work.runtime)
- implementation(libs.androidx.fragment)
- implementation(libs.androidx.activity)
- implementation(libs.androidx.sqlite)
- implementation(libs.androidx.concurrent.futures)
- implementation(libs.androidx.camera2)
- implementation(libs.androidx.camera.lifecycle)
- implementation(libs.androidx.camera.view)
- implementation(libs.androidx.camera.video)
- implementation(libs.androidx.media)
- implementation(libs.androidx.media3.exoplayer)
- implementation(libs.androidx.media3.ui)
- implementation(libs.androidx.media3.session)
- implementation(libs.androidx.lifecycle.viewmodel)
- implementation(libs.androidx.lifecycle.livedata)
- implementation(libs.androidx.lifecycle.runtime)
- implementation(libs.androidx.lifecycle.viewmodel.savedstate)
- implementation(libs.androidx.lifecycle.service)
- implementation(libs.androidx.lifecycle.process)
- implementation(libs.androidx.lifecycle.commonJava8)
- implementation(libs.androidx.lifecycle.extensions)
- implementation(libs.androidx.paging.runtime)
- implementation(libs.androidx.sharetarget)
- implementation(libs.androidx.room.runtime)
- implementation(libs.androidx.window)
- implementation(libs.androidx.splashscreen)
- ksp(libs.androidx.room.compiler)
- // Jetpack Compose
- implementation(platform(libs.compose.bom))
- implementation(libs.androidx.material3)
- implementation(libs.androidx.materialIconsExtended)
- implementation(libs.androidx.ui.tooling.preview)
- implementation(libs.androidx.activity.compose)
- implementation(libs.androidx.lifecycle.viewmodel.compose)
- implementation(libs.androidx.lifecycle.runtime.compose)
- debugImplementation(libs.androidx.ui.tooling)
- androidTestImplementation(platform(libs.compose.bom))
- implementation(libs.bcprov.jdk15to18)
- implementation(libs.material)
- implementation(libs.zxing)
- implementation(libs.libphonenumber)
- // webclient dependencies
- implementation(libs.msgpack.core)
- implementation(libs.jackson.core)
- implementation(libs.nvWebsocket.client)
- implementation(libs.streamsupport.cfuture)
- implementation(libs.saltyrtc.client) {
- exclude(group = "org.json")
- }
- implementation(libs.chunkedDc)
- implementation(libs.webrtcAndroid)
- implementation(libs.saltyrtc.taskWebrtc) {
- exclude(module = "saltyrtc-client")
- }
- // Glide components
- implementation(libs.glide)
- ksp(libs.glide.compiler)
- annotationProcessor(libs.glide.compiler)
- // Kotlin
- implementation(libs.kotlin.stdlib)
- implementation(libs.kotlinx.coroutines.android)
- implementation(libs.kotlinx.serialization.json)
- testImplementation(libs.kotlin.test)
- androidTestImplementation(libs.kotlin.test)
- // use leak canary in debug builds if requested
- if (project.hasProperty("leakCanary")) {
- debugImplementation(libs.leakcanary)
- }
- // test dependencies
- testImplementation(libs.junit)
- testImplementation(testFixtures(project(":domain")))
- // custom test helpers, shared between unit test and android tests
- testImplementation(project(":test-helpers"))
- androidTestImplementation(project(":test-helpers"))
- testImplementation(libs.mockito.powermock.api)
- testImplementation(libs.mockito.powermock.junit4RuleAgent)
- testImplementation(libs.mockito.powermock.junit4Rule)
- testImplementation(libs.mockito.powermock.junit4)
- testImplementation(libs.mockk)
- androidTestImplementation(libs.mockkAndroid)
- // add JSON support to tests without mocking
- testImplementation(libs.json)
- testImplementation(libs.archunit.junit4)
- androidTestImplementation(testFixtures(project(":domain")))
- androidTestImplementation(libs.androidx.test.rules)
- androidTestImplementation(libs.fastlane.screengrab) {
- exclude(group = "androidx.annotation", module = "annotation")
- }
- androidTestImplementation(libs.androidx.espresso.core) {
- exclude(group = "androidx.annotation", module = "annotation")
- }
- androidTestImplementation(libs.androidx.test.runner) {
- exclude(group = "androidx.annotation", module = "annotation")
- }
- androidTestImplementation(libs.androidx.junit)
- androidTestImplementation(libs.androidx.espresso.contrib) {
- exclude(group = "androidx.annotation", module = "annotation")
- exclude(group = "androidx.appcompat", module = "appcompat")
- exclude(group = "androidx.legacy", module = "legacy-support-v4")
- exclude(group = "com.google.android.material", module = "material")
- exclude(group = "androidx.recyclerview", module = "recyclerview")
- exclude(group = "org.checkerframework", module = "checker")
- exclude(module = "protobuf-lite")
- }
- androidTestImplementation(libs.androidx.espresso.intents) {
- exclude(group = "androidx.annotation", module = "annotation")
- }
- androidTestImplementation(libs.androidx.test.uiautomator)
- androidTestImplementation(libs.androidx.test.core)
- androidTestImplementation(libs.mockito.core)
- androidTestImplementation(libs.kotlinx.coroutines.test)
- testImplementation(libs.kotlinx.coroutines.test)
- // Google Play Services and related libraries
- "noneImplementation"(libs.playServices.base)
- "store_googleImplementation"(libs.playServices.base)
- "store_google_workImplementation"(libs.playServices.base)
- "store_threemaImplementation"(libs.playServices.base)
- "onpremImplementation"(libs.playServices.base)
- "greenImplementation"(libs.playServices.base)
- "sandbox_workImplementation"(libs.playServices.base)
- "blueImplementation"(libs.playServices.base)
- fun ExternalModuleDependency.excludeFirebaseDependencies() {
- exclude(group = "com.google.firebase", module = "firebase-core")
- exclude(group = "com.google.firebase", module = "firebase-analytics")
- exclude(group = "com.google.firebase", module = "firebase-measurement-connector")
- }
- "noneImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
- "store_googleImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
- "store_google_workImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
- "store_threemaImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
- "onpremImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
- "greenImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
- "sandbox_workImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
- "blueImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
- // Google Assistant Voice Action verification library
- "noneImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
- "store_googleImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
- "store_google_workImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
- "onpremImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
- "store_threemaImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
- "greenImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
- "sandbox_workImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
- "blueImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
- // Maplibre (may have transitive dependencies on Google location services)
- "noneImplementation"(libs.maplibre)
- "store_googleImplementation"(libs.maplibre)
- "store_google_workImplementation"(libs.maplibre)
- "store_threemaImplementation"(libs.maplibre)
- "libreImplementation"(libs.maplibre) {
- exclude(group = "com.google.android.gms")
- }
- "onpremImplementation"(libs.maplibre)
- "greenImplementation"(libs.maplibre)
- "sandbox_workImplementation"(libs.maplibre)
- "blueImplementation"(libs.maplibre)
- "hmsImplementation"(libs.maplibre)
- "hms_workImplementation"(libs.maplibre)
- // Huawei related libraries (only for hms* build variants)
- // Exclude agconnect dependency, we'll replace it with the vendored version below
- "hmsImplementation"(libs.hmsPush) {
- exclude(group = "com.huawei.agconnect")
- }
- "hms_workImplementation"(libs.hmsPush) {
- exclude(group = "com.huawei.agconnect")
- }
- "hmsImplementation"(group = "", name = "agconnect-core-1.9.1.301", ext = "aar")
- "hms_workImplementation"(group = "", name = "agconnect-core-1.9.1.301", ext = "aar")
- }
- // Define the cargo attributes. These will be used by the rust-android plugin that will create the
- // 'cargoBuild' task that builds native libraries that will be added to the apk. Note that the
- // kotlin bindings are created in the domain module. Building native libraries with rust-android
- // cannot be done in any other module than 'app'.
- cargo {
- prebuiltToolchains = true
- targetDirectory = "$projectDir/build/generated/source/libthreema"
- module = "$projectDir/../domain/libthreema" // must contain Cargo.toml
- libname = "libthreema" // must match the Cargo.toml's package name
- profile = "release"
- pythonCommand = "python3"
- targets = listOf("x86_64", "arm64", "arm", "x86")
- features {
- defaultAnd(arrayOf("uniffi"))
- }
- extraCargoBuildArguments = listOf("--lib", "--target-dir", "$projectDir/build/generated/source/libthreema")
- verbose = false
- }
- afterEvaluate {
- // The `cargoBuild` task isn't available until after evaluation.
- android.applicationVariants.configureEach {
- val variantName = name.replaceFirstChar { it.uppercase() }
- // Set the dependency so that cargoBuild is executed before the native libs are merged
- tasks["merge${variantName}NativeLibs"].dependsOn(tasks["cargoBuild"])
- }
- }
- sonarqube {
- properties {
- property("sonar.sources", "src/main/, ../scripts/, ../scripts-internal/")
- property(
- "sonar.exclusions",
- "src/main/java/ch/threema/localcrypto/**, src/test/java/ch/threema/localcrypto/**, src/*/res/, src/*/res-rendezvous/",
- )
- property("sonar.tests", "src/test/")
- property("sonar.sourceEncoding", "UTF-8")
- property("sonar.verbose", "true")
- property("sonar.projectKey", "android-client")
- property("sonar.projectName", "Threema for Android")
- }
- }
- androidStem {
- includeLocalizedOnlyTemplates = true
- }
- tasks.withType<Test> {
- // Necessary to load the dynamic libthreema library in unit tests
- systemProperty("jna.library.path", "${project.projectDir}/../domain/libthreema/target/release")
- }
- // Set up Gradle tasks to fetch screenshots on UI test failures
- // See https://medium.com/stepstone-tech/how-to-capture-screenshots-for-failed-ui-tests-9927eea6e1e4
- val reportsDirectory = "${layout.buildDirectory}/reports/androidTests/connected"
- val screenshotsDirectory = "/sdcard/testfailures/screenshots/"
- val clearScreenshotsTask = task<Exec>("clearScreenshots") {
- executable = android.adbExecutable.toString()
- args("shell", "rm", "-r", screenshotsDirectory)
- }
- val createScreenshotsDirectoryTask = task<Exec>("createScreenshotsDirectory") {
- group = "reporting"
- executable = android.adbExecutable.toString()
- args("shell", "mkdir", "-p", screenshotsDirectory)
- }
- val fetchScreenshotsTask = task<Exec>("fetchScreenshots") {
- group = "reporting"
- executable = android.adbExecutable.toString()
- args("pull", "$screenshotsDirectory.", reportsDirectory)
- finalizedBy(clearScreenshotsTask)
- dependsOn(createScreenshotsDirectoryTask)
- doFirst {
- file(reportsDirectory).mkdirs()
- }
- }
- tasks.whenTaskAdded {
- if (name == "connectedDebugAndroidTest") {
- finalizedBy(fetchScreenshotsTask)
- }
- }
|