build.gradle.kts 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157
  1. import com.android.build.gradle.internal.api.ApkVariantOutputImpl
  2. import com.android.build.gradle.internal.tasks.factory.dependsOn
  3. import config.BuildFeatureFlags
  4. import config.PublicKeys
  5. import config.SentryConfig
  6. import config.setProductNames
  7. import config.setSentryConfig
  8. import org.gradle.api.tasks.testing.logging.TestExceptionFormat
  9. import org.gradle.kotlin.dsl.lintChecks
  10. import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
  11. import utils.*
  12. plugins {
  13. alias(libs.plugins.sonarqube)
  14. alias(libs.plugins.kotlin.serialization)
  15. alias(libs.plugins.rust.android)
  16. id("com.android.application")
  17. id("kotlin-android")
  18. id("kotlin-parcelize")
  19. alias(libs.plugins.ksp)
  20. alias(libs.plugins.compose.compiler)
  21. alias(libs.plugins.stem)
  22. }
  23. // only apply the plugin if we are dealing with an AppGallery build
  24. if (gradle.startParameter.taskRequests.toString().contains("Hms")) {
  25. logger.info("enabling hms plugin")
  26. apply {
  27. plugin("com.huawei.agconnect")
  28. }
  29. }
  30. /**
  31. * Only use the scheme "<major>.<minor>.<patch>" for the appVersion
  32. */
  33. val appVersion = "6.5.0"
  34. /**
  35. * betaSuffix with leading dash (e.g. `-beta1`).
  36. * Should be one of (alpha|beta|rc) and an increasing number, or empty for a regular release.
  37. * Note: in nightly builds this will be overwritten with a nightly version "-n12345"
  38. */
  39. val betaSuffix = ""
  40. val defaultVersionCode = 1208
  41. /**
  42. * Map with keystore paths (if found).
  43. */
  44. val keystores: Map<String, KeystoreConfig?> = mapOf(
  45. "debug" to findKeystore(projectDir, "debug"),
  46. "release" to findKeystore(projectDir, "threema"),
  47. "hms_release" to findKeystore(projectDir, "threema_hms"),
  48. "onprem_release" to findKeystore(projectDir, "onprem"),
  49. "blue_release" to findKeystore(projectDir, "threema_blue"),
  50. )
  51. android {
  52. // NOTE: When adjusting compileSdk, buildToolsVersion or ndkVersion, make sure to also adjust
  53. // `scripts/Dockerfile`, `commonAndroid/build.gradle.kts`, and the image in `.gitlab-ci.yml`
  54. compileSdk = 36
  55. buildToolsVersion = "35.0.0"
  56. ndkVersion = "28.2.13676358"
  57. defaultConfig {
  58. // https://developer.android.com/training/testing/espresso/setup#analytics
  59. with(testInstrumentationRunnerArguments) {
  60. put("notAnnotation", "ch.threema.app.DangerousTest")
  61. put("disableAnalytics", "true")
  62. }
  63. minSdk = 24
  64. targetSdk = 36
  65. vectorDrawables.useSupportLibrary = true
  66. applicationId = "ch.threema.app"
  67. testApplicationId = "$applicationId.test"
  68. versionCode = defaultVersionCode
  69. versionName = "$appVersion$betaSuffix"
  70. setProductNames(
  71. appName = "Threema",
  72. )
  73. intBuildConfigField("DEFAULT_VERSION_CODE", defaultVersionCode)
  74. // package name used for sync adapter - needs to match mime types below
  75. stringResValue("package_name", applicationId!!)
  76. stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.$applicationId.profile")
  77. stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.$applicationId.call")
  78. intBuildConfigField("MAX_GROUP_SIZE", 256)
  79. stringBuildConfigField("CHAT_SERVER_PREFIX", "g-")
  80. stringBuildConfigField("CHAT_SERVER_IPV6_PREFIX", "ds.g-")
  81. stringBuildConfigField("CHAT_SERVER_SUFFIX", ".0.threema.ch")
  82. intArrayBuildConfigField("CHAT_SERVER_PORTS", intArrayOf(5222, 443))
  83. stringBuildConfigField("MEDIA_PATH", "Threema")
  84. booleanBuildConfigField("CHAT_SERVER_GROUPS", true)
  85. booleanBuildConfigField("DISABLE_CERT_PINNING", false)
  86. booleanBuildConfigField("VIDEO_CALLS_ENABLED", true)
  87. // This public key is pinned for the chat server protocol.
  88. byteArrayBuildConfigField("SERVER_PUBKEY", PublicKeys.prodServer)
  89. byteArrayBuildConfigField("SERVER_PUBKEY_ALT", PublicKeys.prodServerAlt)
  90. stringBuildConfigField("GIT_HASH", getGitHash())
  91. stringBuildConfigField("GIT_BRANCH", getGitBranch())
  92. stringBuildConfigField("DIRECTORY_SERVER_URL", "https://apip.threema.ch/")
  93. stringBuildConfigField("DIRECTORY_SERVER_IPV6_URL", "https://ds-apip.threema.ch/")
  94. stringBuildConfigField("WORK_SERVER_URL_LEGACY", null)
  95. stringBuildConfigField("WORK_SERVER_IPV6_URL_LEGACY", null)
  96. stringBuildConfigField("WORK_SERVER_URL", null)
  97. stringBuildConfigField("MEDIATOR_SERVER_URL", "wss://mediator-{deviceGroupIdPrefix4}.threema.ch/{deviceGroupIdPrefix8}/")
  98. // Base blob url used for "download" and "done" calls
  99. stringBuildConfigField("BLOB_SERVER_URL", "https://blobp-{blobIdPrefix}.threema.ch")
  100. stringBuildConfigField("BLOB_SERVER_IPV6_URL", "https://ds-blobp-{blobIdPrefix}.threema.ch")
  101. // Specific blob url used for "upload" calls
  102. stringBuildConfigField("BLOB_SERVER_URL_UPLOAD", "https://blobp-upload.threema.ch/upload")
  103. stringBuildConfigField("BLOB_SERVER_IPV6_URL_UPLOAD", "https://ds-blobp-upload.threema.ch/upload")
  104. // Base blob mirror url used for "download", "upload", "done"
  105. stringBuildConfigField("BLOB_MIRROR_SERVER_URL", "https://blob-mirror-{deviceGroupIdPrefix4}.threema.ch/{deviceGroupIdPrefix8}")
  106. stringBuildConfigField("AVATAR_FETCH_URL", "https://avatar.threema.ch/")
  107. stringBuildConfigField("SAFE_SERVER_URL", "https://safe-{backupIdPrefix8}.threema.ch/")
  108. stringBuildConfigField("WEB_SERVER_URL", "https://web.threema.ch/")
  109. stringBuildConfigField("APP_RATING_URL", "https://threema.com/app-rating/android/{rating}")
  110. stringBuildConfigField("MAP_STYLES_URL", "https://map.threema.ch/styles/threema/style.json")
  111. stringBuildConfigField("MAP_POI_AROUND_URL", "https://poi.threema.ch/around/{latitude}/{longitude}/{radius}/")
  112. stringBuildConfigField("MAP_POI_NAMES_URL", "https://poi.threema.ch/names/{latitude}/{longitude}/{query}/")
  113. byteArrayBuildConfigField("THREEMA_PUSH_PUBLIC_KEY", PublicKeys.threemaPush)
  114. stringBuildConfigField("ONPREM_ID_PREFIX", "O")
  115. stringBuildConfigField("LOG_TAG", "3ma")
  116. stringBuildConfigField("DEFAULT_APP_THEME", "2")
  117. stringArrayBuildConfigField("ONPREM_CONFIG_TRUSTED_PUBLIC_KEYS", emptyArray())
  118. booleanBuildConfigField("MD_SYNC_DISTRIBUTION_LISTS", false)
  119. booleanBuildConfigField("AVAILABILITY_STATUS_ENABLED", false)
  120. booleanBuildConfigField("CROSS_PLATFORM_BACKUPS_ENABLED", BuildFeatureFlags["cross_platform_backups"] ?: false)
  121. booleanBuildConfigField("NEW_CONVERSATION_SCREEN_ENABLED", BuildFeatureFlags["new_conversation_screen"] ?: false)
  122. // config fields for action URLs / deep links
  123. stringBuildConfigField("uriScheme", "threema")
  124. stringBuildConfigField("actionUrl", "go.threema.ch")
  125. stringBuildConfigField("contactActionUrl", "threema.id")
  126. // The OPPF url must be null in the default config. Do not change this.
  127. stringBuildConfigField("PRESET_OPPF_URL", null)
  128. with(manifestPlaceholders) {
  129. put("uriScheme", "threema")
  130. put("contactActionUrl", "threema.id")
  131. put("actionUrl", "go.threema.ch")
  132. put("callMimeType", "vnd.android.cursor.item/vnd.$applicationId.call")
  133. }
  134. testInstrumentationRunner = "$applicationId.ThreemaTestRunner"
  135. // Only include language resources for those languages
  136. androidResources.localeFilters.addAll(
  137. setOf(
  138. "en",
  139. "be-rBY",
  140. "bg",
  141. "ca",
  142. "cs",
  143. "de",
  144. "es",
  145. "fr",
  146. "gsw",
  147. "hu",
  148. "it",
  149. "ja",
  150. "nl-rNL",
  151. "no",
  152. "pl",
  153. "pt-rBR",
  154. "ru",
  155. "sk",
  156. "tr",
  157. "uk",
  158. "zh-rCN",
  159. "zh-rTW",
  160. ),
  161. )
  162. }
  163. splits {
  164. abi {
  165. isEnable = true
  166. reset()
  167. if (project.hasProperty("noAbiSplits")) {
  168. isUniversalApk = true
  169. } else {
  170. include("armeabi-v7a", "x86", "arm64-v8a", "x86_64")
  171. isUniversalApk = project.hasProperty("buildUniversalApk")
  172. }
  173. }
  174. }
  175. // Assign different version code for each output
  176. android.applicationVariants.all {
  177. outputs.all {
  178. if (this is ApkVariantOutputImpl) {
  179. val abi = getFilter("ABI")
  180. val abiVersionCode = when (abi) {
  181. "armeabi-v7a" -> 2
  182. "arm64-v8a" -> 3
  183. "x86" -> 8
  184. "x86_64" -> 9
  185. else -> 0
  186. }
  187. versionCodeOverride = abiVersionCode * 1_000_000 + defaultVersionCode
  188. }
  189. }
  190. }
  191. namespace = "ch.threema.app"
  192. flavorDimensions.add("default")
  193. productFlavors {
  194. create("none") {
  195. setSentryConfig(null)
  196. }
  197. create("store_google") {
  198. setSentryConfig(SentryConfig.PRODUCTION)
  199. }
  200. create("store_threema") {
  201. versionName = "${appVersion}s$betaSuffix"
  202. stringResValue("shop_download_filename", "Threema-update.apk")
  203. setSentryConfig(SentryConfig.PRODUCTION)
  204. }
  205. create("store_google_work") {
  206. versionName = "${appVersion}k$betaSuffix"
  207. applicationId = "ch.threema.app.work"
  208. testApplicationId = "$applicationId.test"
  209. setProductNames(appName = "Threema Work")
  210. stringResValue("package_name", applicationId!!)
  211. stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.$applicationId.profile")
  212. stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.$applicationId.call")
  213. stringBuildConfigField("CHAT_SERVER_PREFIX", "w-")
  214. stringBuildConfigField("CHAT_SERVER_IPV6_PREFIX", "ds.w-")
  215. stringBuildConfigField("MEDIA_PATH", "ThreemaWork")
  216. stringBuildConfigField("WORK_SERVER_URL_LEGACY", "https://apip-work.threema.ch/")
  217. stringBuildConfigField("WORK_SERVER_IPV6_URL_LEGACY", "https://ds-apip-work.threema.ch/")
  218. stringBuildConfigField("WORK_SERVER_URL", "https://work.threema.ch/")
  219. stringBuildConfigField("APP_RATING_URL", "https://threema.com/app-rating/android-work/{rating}")
  220. stringBuildConfigField("LOG_TAG", "3mawrk")
  221. stringBuildConfigField("DEFAULT_APP_THEME", "2")
  222. booleanBuildConfigField("AVAILABILITY_STATUS_ENABLED", true)
  223. // config fields for action URLs / deep links
  224. stringBuildConfigField("uriScheme", "threemawork")
  225. stringBuildConfigField("actionUrl", "work.threema.ch")
  226. stringBuildConfigField("SCREENSHOT_TEST_WORK_USERNAME", LocalProperties.getString("screenshotTestWorkUsername"))
  227. stringBuildConfigField("SCREENSHOT_TEST_WORK_PASSWORD", LocalProperties.getString("screenshotTestWorkPassword"))
  228. with(manifestPlaceholders) {
  229. put("uriScheme", "threemawork")
  230. put("actionUrl", "work.threema.ch")
  231. put("callMimeType", "vnd.android.cursor.item/vnd.$applicationId.call")
  232. }
  233. setSentryConfig(SentryConfig.PRODUCTION)
  234. }
  235. create("green") {
  236. applicationId = "ch.threema.app.green"
  237. testApplicationId = "$applicationId.test"
  238. setProductNames(appName = "Threema Green")
  239. stringResValue("package_name", applicationId!!)
  240. stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.$applicationId.profile")
  241. stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.$applicationId.call")
  242. stringBuildConfigField("MEDIA_PATH", "ThreemaGreen")
  243. stringBuildConfigField("CHAT_SERVER_SUFFIX", ".0.test.threema.ch")
  244. // This public key is pinned for the chat server protocol.
  245. byteArrayBuildConfigField("SERVER_PUBKEY", PublicKeys.sandboxServer)
  246. byteArrayBuildConfigField("SERVER_PUBKEY_ALT", PublicKeys.sandboxServer)
  247. stringBuildConfigField("DIRECTORY_SERVER_URL", "https://apip.test.threema.ch/")
  248. stringBuildConfigField("DIRECTORY_SERVER_IPV6_URL", "https://ds-apip.test.threema.ch/")
  249. stringBuildConfigField("MEDIATOR_SERVER_URL", "wss://mediator-{deviceGroupIdPrefix4}.test.threema.ch/{deviceGroupIdPrefix8}/")
  250. stringBuildConfigField("BLOB_SERVER_URL", "https://blobp-{blobIdPrefix}.test.threema.ch")
  251. stringBuildConfigField("BLOB_SERVER_IPV6_URL", "https://ds-blobp-{blobIdPrefix}.test.threema.ch")
  252. stringBuildConfigField("BLOB_SERVER_URL_UPLOAD", "https://blobp-upload.test.threema.ch/upload")
  253. stringBuildConfigField("BLOB_SERVER_IPV6_URL_UPLOAD", "https://ds-blobp-upload.test.threema.ch/upload")
  254. stringBuildConfigField("AVATAR_FETCH_URL", "https://avatar.test.threema.ch/")
  255. stringBuildConfigField("APP_RATING_URL", "https://test.threema.com/app-rating/android/{rating}")
  256. stringBuildConfigField("MAP_STYLES_URL", "https://map.test.threema.ch/styles/threema/style.json")
  257. stringBuildConfigField("MAP_POI_AROUND_URL", "https://poi.test.threema.ch/around/{latitude}/{longitude}/{radius}/")
  258. stringBuildConfigField("MAP_POI_NAMES_URL", "https://poi.test.threema.ch/names/{latitude}/{longitude}/{query}/")
  259. stringBuildConfigField("BLOB_MIRROR_SERVER_URL", "https://blob-mirror-{deviceGroupIdPrefix4}.test.threema.ch/{deviceGroupIdPrefix8}")
  260. setSentryConfig(SentryConfig.SANDBOX)
  261. }
  262. create("sandbox_work") {
  263. versionName = "${appVersion}k$betaSuffix"
  264. applicationId = "ch.threema.app.sandbox.work"
  265. testApplicationId = "$applicationId.test"
  266. setProductNames(
  267. appName = "Threema Sandbox Work",
  268. appNameDesktop = "Threema Blue",
  269. )
  270. stringResValue("package_name", applicationId!!)
  271. stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.$applicationId.profile")
  272. stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.$applicationId.call")
  273. stringBuildConfigField("CHAT_SERVER_PREFIX", "w-")
  274. stringBuildConfigField("CHAT_SERVER_IPV6_PREFIX", "ds.w-")
  275. stringBuildConfigField("CHAT_SERVER_SUFFIX", ".0.test.threema.ch")
  276. stringBuildConfigField("MEDIA_PATH", "ThreemaWorkSandbox")
  277. // This public key is pinned for the chat server protocol.
  278. byteArrayBuildConfigField("SERVER_PUBKEY", PublicKeys.sandboxServer)
  279. byteArrayBuildConfigField("SERVER_PUBKEY_ALT", PublicKeys.sandboxServer)
  280. stringBuildConfigField("DIRECTORY_SERVER_URL", "https://apip.test.threema.ch/")
  281. stringBuildConfigField("DIRECTORY_SERVER_IPV6_URL", "https://ds-apip.test.threema.ch/")
  282. stringBuildConfigField("WORK_SERVER_URL_LEGACY", "https://apip-work.test.threema.ch/")
  283. stringBuildConfigField("WORK_SERVER_IPV6_URL_LEGACY", "https://ds-apip-work.test.threema.ch/")
  284. stringBuildConfigField("WORK_SERVER_URL", "https://work.test.threema.ch/")
  285. stringBuildConfigField("MEDIATOR_SERVER_URL", "wss://mediator-{deviceGroupIdPrefix4}.test.threema.ch/{deviceGroupIdPrefix8}/")
  286. stringBuildConfigField("BLOB_SERVER_URL", "https://blobp-{blobIdPrefix}.test.threema.ch")
  287. stringBuildConfigField("BLOB_SERVER_IPV6_URL", "https://ds-blobp-{blobIdPrefix}.test.threema.ch")
  288. stringBuildConfigField("BLOB_SERVER_URL_UPLOAD", "https://blobp-upload.test.threema.ch/upload")
  289. stringBuildConfigField("BLOB_SERVER_IPV6_URL_UPLOAD", "https://ds-blobp-upload.test.threema.ch/upload")
  290. stringBuildConfigField("AVATAR_FETCH_URL", "https://avatar.test.threema.ch/")
  291. stringBuildConfigField("APP_RATING_URL", "https://test.threema.com/app-rating/android-work/{rating}")
  292. stringBuildConfigField("MAP_STYLES_URL", "https://map.test.threema.ch/styles/threema/style.json")
  293. stringBuildConfigField("MAP_POI_AROUND_URL", "https://poi.test.threema.ch/around/{latitude}/{longitude}/{radius}/")
  294. stringBuildConfigField("MAP_POI_NAMES_URL", "https://poi.test.threema.ch/names/{latitude}/{longitude}/{query}/")
  295. stringBuildConfigField("LOG_TAG", "3mawrk")
  296. stringBuildConfigField("DEFAULT_APP_THEME", "2")
  297. stringBuildConfigField("BLOB_MIRROR_SERVER_URL", "https://blob-mirror-{deviceGroupIdPrefix4}.test.threema.ch/{deviceGroupIdPrefix8}")
  298. // config fields for action URLs / deep links
  299. stringBuildConfigField("uriScheme", "threemawork")
  300. stringBuildConfigField("actionUrl", "work.test.threema.ch")
  301. booleanBuildConfigField("AVAILABILITY_STATUS_ENABLED", true)
  302. stringBuildConfigField("MD_CLIENT_DOWNLOAD_URL", "https://three.ma/mdw")
  303. with(manifestPlaceholders) {
  304. put("uriScheme", "threemawork")
  305. put("actionUrl", "work.test.threema.ch")
  306. }
  307. setSentryConfig(SentryConfig.SANDBOX)
  308. }
  309. create("onprem") {
  310. versionName = "${appVersion}o$betaSuffix"
  311. applicationId = "ch.threema.app.onprem"
  312. testApplicationId = "$applicationId.test"
  313. setProductNames(
  314. appName = "Threema OnPrem",
  315. shortAppName = "Threema",
  316. companyName = "Threema",
  317. )
  318. stringResValue("package_name", applicationId!!)
  319. stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.$applicationId.profile")
  320. stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.$applicationId.call")
  321. intBuildConfigField("MAX_GROUP_SIZE", 256)
  322. stringBuildConfigField("CHAT_SERVER_PREFIX", "")
  323. stringBuildConfigField("CHAT_SERVER_IPV6_PREFIX", "")
  324. stringBuildConfigField("CHAT_SERVER_SUFFIX", null)
  325. stringBuildConfigField("MEDIA_PATH", "ThreemaOnPrem")
  326. booleanBuildConfigField("CHAT_SERVER_GROUPS", false)
  327. byteArrayBuildConfigField("SERVER_PUBKEY", null)
  328. byteArrayBuildConfigField("SERVER_PUBKEY_ALT", null)
  329. stringBuildConfigField("DIRECTORY_SERVER_URL", null)
  330. stringBuildConfigField("DIRECTORY_SERVER_IPV6_URL", null)
  331. stringBuildConfigField("BLOB_SERVER_URL", null)
  332. stringBuildConfigField("BLOB_SERVER_IPV6_URL", null)
  333. stringBuildConfigField("BLOB_SERVER_URL_UPLOAD", null)
  334. stringBuildConfigField("BLOB_SERVER_IPV6_URL_UPLOAD", null)
  335. stringBuildConfigField("BLOB_MIRROR_SERVER_URL", null)
  336. stringArrayBuildConfigField("ONPREM_CONFIG_TRUSTED_PUBLIC_KEYS", PublicKeys.onPremTrusted)
  337. stringBuildConfigField("LOG_TAG", "3maop")
  338. // config fields for action URLs / deep links
  339. val uriScheme = "threemaonprem"
  340. val actionUrl = "onprem.threema.ch"
  341. stringBuildConfigField("uriScheme", uriScheme)
  342. stringBuildConfigField("actionUrl", actionUrl)
  343. stringBuildConfigField("PRESET_OPPF_URL", null)
  344. stringBuildConfigField("MD_CLIENT_DOWNLOAD_URL", "https://three.ma/mdo")
  345. stringBuildConfigField("SCREENSHOT_TEST_ONPREM_USERNAME", LocalProperties.getString("screenshotTestOnPremUsername"))
  346. stringBuildConfigField("SCREENSHOT_TEST_ONPREM_PASSWORD", LocalProperties.getString("screenshotTestOnPremPassword"))
  347. stringBuildConfigField("SCREENSHOT_TEST_ONPREM_SERVER_URL", LocalProperties.getString("screenshotTestOnPremServerUrl"))
  348. with(manifestPlaceholders) {
  349. put("uriScheme", uriScheme)
  350. put("actionUrl", actionUrl)
  351. put("callMimeType", "vnd.android.cursor.item/vnd.$applicationId.call")
  352. }
  353. setSentryConfig(null)
  354. }
  355. create("blue") {
  356. // Essentially like sandbox work, but with a different icon and application id, used for internal testing
  357. versionName = "${appVersion}b$betaSuffix"
  358. // The app was previously named `red`. The app id remains unchanged to still be able to install updates.
  359. applicationId = "ch.threema.app.red"
  360. testApplicationId = "ch.threema.app.blue.test"
  361. setProductNames(appName = "Threema Blue")
  362. stringResValue("package_name", applicationId!!)
  363. stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.ch.threema.app.blue.profile")
  364. stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.ch.threema.app.blue.call")
  365. stringBuildConfigField("CHAT_SERVER_PREFIX", "w-")
  366. stringBuildConfigField("CHAT_SERVER_IPV6_PREFIX", "ds.w-")
  367. stringBuildConfigField("CHAT_SERVER_SUFFIX", ".0.test.threema.ch")
  368. stringBuildConfigField("MEDIA_PATH", "ThreemaBlue")
  369. // This public key is pinned for the chat server protocol.
  370. byteArrayBuildConfigField("SERVER_PUBKEY", PublicKeys.sandboxServer)
  371. byteArrayBuildConfigField("SERVER_PUBKEY_ALT", PublicKeys.sandboxServer)
  372. stringBuildConfigField("DIRECTORY_SERVER_URL", "https://apip.test.threema.ch/")
  373. stringBuildConfigField("DIRECTORY_SERVER_IPV6_URL", "https://ds-apip.test.threema.ch/")
  374. stringBuildConfigField("WORK_SERVER_URL_LEGACY", "https://apip-work.test.threema.ch/")
  375. stringBuildConfigField("WORK_SERVER_IPV6_URL_LEGACY", "https://ds-apip-work.test.threema.ch/")
  376. stringBuildConfigField("WORK_SERVER_URL", "https://work.test.threema.ch/")
  377. stringBuildConfigField("MEDIATOR_SERVER_URL", "wss://mediator-{deviceGroupIdPrefix4}.test.threema.ch/{deviceGroupIdPrefix8}/")
  378. stringBuildConfigField("BLOB_SERVER_URL", "https://blobp-{blobIdPrefix}.test.threema.ch")
  379. stringBuildConfigField("BLOB_SERVER_IPV6_URL", "https://ds-blobp-{blobIdPrefix}.test.threema.ch")
  380. stringBuildConfigField("BLOB_SERVER_URL_UPLOAD", "https://blobp-upload.test.threema.ch/upload")
  381. stringBuildConfigField("BLOB_SERVER_IPV6_URL_UPLOAD", "https://ds-blobp-upload.test.threema.ch/upload")
  382. stringBuildConfigField("AVATAR_FETCH_URL", "https://avatar.test.threema.ch/")
  383. stringBuildConfigField("APP_RATING_URL", "https://test.threema.com/app-rating/android-work/{rating}")
  384. stringBuildConfigField("MAP_STYLES_URL", "https://map.test.threema.ch/styles/threema/style.json")
  385. stringBuildConfigField("MAP_POI_AROUND_URL", "https://poi.test.threema.ch/around/{latitude}/{longitude}/{radius}/")
  386. stringBuildConfigField("MAP_POI_NAMES_URL", "https://poi.test.threema.ch/names/{latitude}/{longitude}/{query}/")
  387. stringBuildConfigField("LOG_TAG", "3mablue")
  388. stringBuildConfigField("BLOB_MIRROR_SERVER_URL", "https://blob-mirror-{deviceGroupIdPrefix4}.test.threema.ch/{deviceGroupIdPrefix8}")
  389. booleanBuildConfigField("AVAILABILITY_STATUS_ENABLED", true)
  390. // config fields for action URLs / deep links
  391. stringBuildConfigField("uriScheme", "threemablue")
  392. stringBuildConfigField("actionUrl", "blue.threema.ch")
  393. with(manifestPlaceholders) {
  394. put("uriScheme", "threemablue")
  395. put("actionUrl", "blue.threema.ch")
  396. put("callMimeType", "vnd.android.cursor.item/vnd.ch.threema.app.blue.call")
  397. }
  398. setSentryConfig(SentryConfig.SANDBOX)
  399. }
  400. create("hms") {
  401. applicationId = "ch.threema.app.hms"
  402. setSentryConfig(SentryConfig.PRODUCTION)
  403. }
  404. create("hms_work") {
  405. versionName = "${appVersion}k$betaSuffix"
  406. applicationId = "ch.threema.app.work.hms"
  407. testApplicationId = "ch.threema.app.work.test.hms"
  408. setProductNames(appName = "Threema Work")
  409. stringResValue("package_name", "ch.threema.app.work")
  410. stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.ch.threema.app.work.profile")
  411. stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.ch.threema.app.work.call")
  412. stringBuildConfigField("CHAT_SERVER_PREFIX", "w-")
  413. stringBuildConfigField("CHAT_SERVER_IPV6_PREFIX", "ds.w-")
  414. stringBuildConfigField("MEDIA_PATH", "ThreemaWork")
  415. stringBuildConfigField("WORK_SERVER_URL_LEGACY", "https://apip-work.threema.ch/")
  416. stringBuildConfigField("WORK_SERVER_IPV6_URL_LEGACY", "https://ds-apip-work.threema.ch/")
  417. stringBuildConfigField("WORK_SERVER_URL", "https://work.threema.ch/")
  418. stringBuildConfigField("APP_RATING_URL", "https://threema.com/app-rating/android-work/{rating}")
  419. stringBuildConfigField("LOG_TAG", "3mawrk")
  420. stringBuildConfigField("DEFAULT_APP_THEME", "2")
  421. booleanBuildConfigField("AVAILABILITY_STATUS_ENABLED", true)
  422. // config fields for action URLs / deep links
  423. stringBuildConfigField("uriScheme", "threemawork")
  424. stringBuildConfigField("actionUrl", "work.threema.ch")
  425. with(manifestPlaceholders) {
  426. put("uriScheme", "threemawork")
  427. put("actionUrl", "work.threema.ch")
  428. put("callMimeType", "vnd.android.cursor.item/vnd.ch.threema.app.work.call")
  429. }
  430. setSentryConfig(SentryConfig.PRODUCTION)
  431. }
  432. create("libre") {
  433. versionName = "${appVersion}l$betaSuffix"
  434. applicationId = "ch.threema.app.libre"
  435. testApplicationId = "$applicationId.test"
  436. stringResValue("package_name", applicationId!!)
  437. setProductNames(
  438. appName = "Threema Libre",
  439. appNameDesktop = "Threema",
  440. )
  441. stringBuildConfigField("MEDIA_PATH", "ThreemaLibre")
  442. setSentryConfig(null)
  443. }
  444. }
  445. signingConfigs {
  446. // Debug config
  447. keystores["debug"]
  448. ?.let { keystore ->
  449. getByName("debug") {
  450. storeFile = keystore.storeFile
  451. }
  452. }
  453. ?: run {
  454. logger.warn("No debug keystore found. Falling back to locally generated keystore.")
  455. }
  456. // Release config
  457. keystores["release"]
  458. ?.let { keystore ->
  459. create("release") {
  460. apply(keystore)
  461. }
  462. }
  463. ?: run {
  464. logger.warn("No release keystore found. Falling back to locally generated keystore.")
  465. }
  466. // Release config
  467. keystores["hms_release"]
  468. ?.let { keystore ->
  469. create("hms_release") {
  470. apply(keystore)
  471. }
  472. }
  473. ?: run {
  474. logger.warn("No hms keystore found. Falling back to locally generated keystore.")
  475. }
  476. // Onprem release config
  477. keystores["onprem_release"]
  478. ?.let { keystore ->
  479. create("onprem_release") {
  480. apply(keystore)
  481. }
  482. }
  483. ?: run {
  484. logger.warn("No onprem keystore found. Falling back to locally generated keystore.")
  485. }
  486. // Blue release config
  487. keystores["blue_release"]
  488. ?.let { keystore ->
  489. create("blue_release") {
  490. apply(keystore)
  491. }
  492. }
  493. ?: run {
  494. logger.warn("No blue keystore found. Falling back to locally generated keystore.")
  495. }
  496. // Note: Libre release is signed with HSM, no config here
  497. }
  498. sourceSets {
  499. getByName("main") {
  500. assets.srcDirs("assets")
  501. jniLibs.srcDirs("libs")
  502. res.srcDir("src/main/res-rendezvous")
  503. java.srcDir("./build/generated/source/protobuf/main/java")
  504. java.srcDir("./build/generated/source/protobuf/main/kotlin")
  505. }
  506. // Include Dev features only in debug builds and in release builds for flavors used only internally
  507. val devFlavors = arrayOf("blue", "none")
  508. productFlavors.names
  509. .flatMap { productFlavor ->
  510. buildList {
  511. add("${productFlavor}Debug" to true)
  512. if (productFlavor != "green" && productFlavor != "sandbox_work") {
  513. add("${productFlavor}Release" to (productFlavor in devFlavors))
  514. }
  515. }
  516. }
  517. .forEach { (name, needsDevFeatures) ->
  518. create(name) {
  519. val srcPath = if (needsDevFeatures) "src/with_dev_support" else "src/without_dev_support"
  520. java.srcDir("$srcPath/java")
  521. manifest.srcFile("$srcPath/AndroidManifest.xml")
  522. res.srcDir("$srcPath/res")
  523. }
  524. }
  525. // Based on Google services
  526. getByName("none") {
  527. java.srcDir("src/google_services_based/java")
  528. }
  529. getByName("store_google") {
  530. java.srcDir("src/google_services_based/java")
  531. }
  532. getByName("store_google_work") {
  533. java.srcDir("src/google_services_based/java")
  534. }
  535. getByName("store_threema") {
  536. java.srcDir("src/google_services_based/java")
  537. }
  538. getByName("libre") {
  539. assets.srcDirs("src/foss_based/assets")
  540. java.srcDir("src/foss_based/java")
  541. }
  542. getByName("onprem") {
  543. java.srcDir("src/google_services_based/java")
  544. }
  545. getByName("green") {
  546. java.srcDir("src/google_services_based/java")
  547. manifest.srcFile("src/store_google/AndroidManifest.xml")
  548. }
  549. getByName("sandbox_work") {
  550. java.srcDir("src/google_services_based/java")
  551. res.srcDir("src/store_google_work/res")
  552. manifest.srcFile("src/store_google_work/AndroidManifest.xml")
  553. }
  554. getByName("blue") {
  555. java.srcDir("src/google_services_based/java")
  556. res.srcDir("src/blue/res")
  557. }
  558. // Based on Huawei services
  559. getByName("hms") {
  560. java.srcDir("src/hms_services_based/java")
  561. }
  562. getByName("hms_work") {
  563. java.srcDir("src/hms_services_based/java")
  564. res.srcDir("src/store_google_work/res")
  565. }
  566. // FOSS, no proprietary services
  567. getByName("libre") {
  568. assets.srcDirs("src/foss_based/assets")
  569. java.srcDir("src/foss_based/java")
  570. }
  571. }
  572. buildTypes {
  573. debug {
  574. isDebuggable = true
  575. ndk {
  576. debugSymbolLevel = "FULL"
  577. }
  578. enableUnitTestCoverage = false
  579. enableAndroidTestCoverage = false
  580. if (keystores["debug"] != null) {
  581. signingConfig = signingConfigs["debug"]
  582. }
  583. }
  584. release {
  585. isDebuggable = false
  586. isMinifyEnabled = true
  587. isShrinkResources = false // Caused inconsistencies between local and CI builds
  588. vcsInfo.include = false // For reproducible builds independent from git history
  589. proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-project.txt")
  590. ndk {
  591. debugSymbolLevel = "FULL" // 'SYMBOL_TABLE'
  592. }
  593. if (keystores["release"] != null) {
  594. val releaseSigningConfig = signingConfigs["release"]
  595. productFlavors["store_google"].signingConfig = releaseSigningConfig
  596. productFlavors["store_google_work"].signingConfig = releaseSigningConfig
  597. productFlavors["store_threema"].signingConfig = releaseSigningConfig
  598. productFlavors["green"].signingConfig = releaseSigningConfig
  599. productFlavors["sandbox_work"].signingConfig = releaseSigningConfig
  600. productFlavors["none"].signingConfig = releaseSigningConfig
  601. }
  602. if (keystores["hms_release"] != null) {
  603. val hmsReleaseSigningConfig = signingConfigs["hms_release"]
  604. productFlavors["hms"].signingConfig = hmsReleaseSigningConfig
  605. productFlavors["hms_work"].signingConfig = hmsReleaseSigningConfig
  606. }
  607. if (keystores["onprem_release"] != null) {
  608. productFlavors["onprem"].signingConfig = signingConfigs["onprem_release"]
  609. }
  610. if (keystores["blue_release"] != null) {
  611. productFlavors["blue"].signingConfig = signingConfigs["blue_release"]
  612. }
  613. // Note: Libre release is signed with HSM, no config here
  614. }
  615. }
  616. packaging {
  617. jniLibs {
  618. // replacement for extractNativeLibs in AndroidManifest
  619. useLegacyPackaging = true
  620. }
  621. resources {
  622. excludes.addAll(
  623. setOf(
  624. "META-INF/DEPENDENCIES.txt",
  625. "META-INF/LICENSE.txt",
  626. "META-INF/LICENSE.md",
  627. "META-INF/LICENSE-notice.md",
  628. "META-INF/NOTICE.txt",
  629. "META-INF/NOTICE",
  630. "META-INF/LICENSE",
  631. "META-INF/DEPENDENCIES",
  632. "META-INF/notice.txt",
  633. "META-INF/license.txt",
  634. "META-INF/dependencies.txt",
  635. "META-INF/LGPL2.1",
  636. "**/*.proto",
  637. "DebugProbesKt.bin",
  638. ),
  639. )
  640. }
  641. }
  642. testOptions {
  643. // Disable animations in instrumentation tests
  644. animationsDisabled = true
  645. unitTests {
  646. all { test ->
  647. test.outputs.upToDateWhen { false }
  648. test.testLogging {
  649. events("passed", "skipped", "failed", "standardOut", "standardError")
  650. exceptionFormat = TestExceptionFormat.FULL
  651. }
  652. test.jvmArgs = test.jvmArgs!! + listOf(
  653. "--add-opens=java.base/java.util=ALL-UNNAMED",
  654. "--add-opens=java.base/java.util.stream=ALL-UNNAMED",
  655. "--add-opens=java.base/java.lang=ALL-UNNAMED",
  656. "-Xmx4096m",
  657. )
  658. }
  659. // By default, local unit tests throw an exception any time the code you are testing tries to access
  660. // Android platform APIs (unless you mock Android dependencies yourself or with a testing
  661. // framework like Mockk). However, you can enable the following property so that the test
  662. // returns either null or zero when accessing platform APIs, rather than throwing an exception.
  663. isReturnDefaultValues = true
  664. }
  665. }
  666. compileOptions {
  667. isCoreLibraryDesugaringEnabled = true
  668. sourceCompatibility = JavaVersion.VERSION_11
  669. targetCompatibility = JavaVersion.VERSION_11
  670. }
  671. java {
  672. toolchain {
  673. languageVersion.set(JavaLanguageVersion.of(21))
  674. }
  675. }
  676. kotlin {
  677. jvmToolchain(21)
  678. }
  679. androidResources {
  680. noCompress.add("png")
  681. }
  682. lint {
  683. // if true, stop the gradle build if errors are found
  684. abortOnError = true
  685. // if true, check all issues, including those that are off by default
  686. checkAllWarnings = true
  687. // check dependencies
  688. checkDependencies = true
  689. // set to true to have all release builds run lint on issues with severity=fatal
  690. // and abort the build (controlled by abortOnError above) if fatal issues are found
  691. checkReleaseBuilds = true
  692. // turn off checking the given issue id's
  693. disable.addAll(setOf("TypographyFractions", "TypographyQuotes", "RtlHardcoded", "RtlCompat", "RtlEnabled"))
  694. // Set the severity of the given issues to error
  695. error.addAll(setOf("Wakelock", "TextViewEdits", "ResourceAsColor"))
  696. // Set the severity of the given issues to fatal (which means they will be
  697. // checked during release builds (even if the lint target is not included)
  698. fatal.addAll(setOf("NewApi", "InlinedApi", "LoggerName"))
  699. ignoreWarnings = false
  700. // if true, don't include source code lines in the error output
  701. noLines = false
  702. // if true, show all locations for an error, do not truncate lists, etc.
  703. showAll = true
  704. // Set the severity of the given issues to warning
  705. warning.add("MissingTranslation")
  706. // if true, treat all warnings as errors
  707. warningsAsErrors = false
  708. // file to write report to (if not specified, defaults to lint-results.xml)
  709. xmlOutput = file("lint-report.xml")
  710. // if true, generate an XML report for use by for example Jenkins
  711. xmlReport = true
  712. }
  713. buildFeatures {
  714. compose = true
  715. buildConfig = true
  716. }
  717. }
  718. composeCompiler {
  719. includeSourceInformation = true
  720. stabilityConfigurationFiles.add(rootProject.layout.projectDirectory.file("stability_config.conf"))
  721. }
  722. // Only build relevant buildType / flavor combinations
  723. androidComponents {
  724. beforeVariants { variant ->
  725. val name = variant.name
  726. if (variant.buildType == "release" && ("green" in name || "sandbox_work" in name)) {
  727. variant.enable = false
  728. }
  729. }
  730. }
  731. dependencies {
  732. configurations.all {
  733. // Prefer modules that are part of this build (multi-project or composite build)
  734. // over external modules
  735. resolutionStrategy.preferProjectModules()
  736. // Alternatively, we can fail eagerly on version conflict to see the conflicts
  737. // resolutionStrategy.failOnVersionConflict()
  738. }
  739. coreLibraryDesugaring(libs.desugarJdkLibs)
  740. implementation(project(":domain"))
  741. implementation(project("::commonAndroid"))
  742. implementation(project(":common"))
  743. lintChecks(project(":lint-rules"))
  744. // Dependency Injection
  745. implementation(libs.koin.android)
  746. implementation(libs.koin.androidCompat)
  747. implementation(libs.koin.compose)
  748. testImplementation(libs.koin.test)
  749. testImplementation(libs.koin.test.junit4)
  750. // Database
  751. implementation(libs.sqlcipher.android)
  752. // AndroidX / Jetpack support libraries
  753. implementation(libs.androidx.preference)
  754. implementation(libs.androidx.recyclerview)
  755. implementation(libs.androidx.palette)
  756. implementation(libs.androidx.swiperefreshlayout)
  757. implementation(libs.androidx.core)
  758. implementation(libs.androidx.appcompat)
  759. implementation(libs.androidx.constraintlayout)
  760. implementation(libs.androidx.biometric)
  761. implementation(libs.androidx.work.runtime)
  762. implementation(libs.androidx.fragment)
  763. implementation(libs.androidx.activity)
  764. implementation(libs.androidx.sqlite)
  765. implementation(libs.androidx.concurrent.futures)
  766. implementation(libs.androidx.camera2)
  767. implementation(libs.androidx.camera.lifecycle)
  768. implementation(libs.androidx.camera.view)
  769. implementation(libs.androidx.camera.video)
  770. implementation(libs.androidx.media)
  771. implementation(libs.androidx.media3.exoplayer)
  772. implementation(libs.androidx.media3.ui)
  773. implementation(libs.androidx.media3.session)
  774. implementation(libs.androidx.lifecycle.viewmodel)
  775. implementation(libs.androidx.lifecycle.livedata)
  776. implementation(libs.androidx.lifecycle.runtime)
  777. implementation(libs.androidx.lifecycle.viewmodel.savedstate)
  778. implementation(libs.androidx.lifecycle.service)
  779. implementation(libs.androidx.lifecycle.process)
  780. implementation(libs.androidx.lifecycle.commonJava8)
  781. implementation(libs.androidx.lifecycle.extensions)
  782. implementation(libs.androidx.paging.runtime)
  783. implementation(libs.androidx.sharetarget)
  784. implementation(libs.androidx.room.runtime)
  785. implementation(libs.androidx.window)
  786. implementation(libs.androidx.splashscreen)
  787. ksp(libs.androidx.room.compiler)
  788. // Jetpack Compose
  789. implementation(platform(libs.compose.bom))
  790. implementation(libs.androidx.material3)
  791. implementation(libs.androidx.ui.tooling.preview)
  792. implementation(libs.androidx.activity.compose)
  793. implementation(libs.androidx.fragment.compose)
  794. implementation(libs.androidx.lifecycle.viewmodel.compose)
  795. implementation(libs.androidx.lifecycle.runtime.compose)
  796. debugImplementation(libs.androidx.ui.tooling)
  797. debugImplementation(libs.androidx.ui.test.manifest)
  798. androidTestImplementation(platform(libs.compose.bom))
  799. // UI / Views
  800. implementation(libs.subsamplingScaleImageView)
  801. implementation(libs.taptargetview)
  802. implementation(libs.fastscroll)
  803. implementation(libs.gestureViews)
  804. implementation(libs.material)
  805. implementation(libs.androidImageCropper)
  806. implementation(libs.zxing)
  807. // Cryptography
  808. implementation(libs.bcprov.jdk15to18)
  809. // Files / data processing
  810. implementation(libs.opencsv)
  811. implementation(libs.zip4j)
  812. implementation(libs.slf4j.api)
  813. implementation(libs.ezVcard)
  814. implementation(libs.libphonenumber)
  815. // webclient dependencies
  816. implementation(libs.msgpack.core)
  817. implementation(libs.jackson.core)
  818. implementation(libs.nvWebsocket.client)
  819. implementation(libs.saltyrtc.client) {
  820. exclude(group = "org.json")
  821. }
  822. implementation(libs.chunkedDc)
  823. implementation(libs.webrtcAndroid)
  824. implementation(libs.saltyrtc.taskWebrtc) {
  825. exclude(module = "saltyrtc-client")
  826. }
  827. // Glide components
  828. implementation(libs.glide)
  829. ksp(libs.glide.ksp)
  830. // Kotlin
  831. implementation(libs.kotlin.stdlib)
  832. implementation(libs.kotlinx.coroutines.android)
  833. implementation(libs.kotlinx.serialization.json)
  834. testImplementation(libs.kotlin.test)
  835. androidTestImplementation(libs.kotlin.test)
  836. // use leak canary in debug builds if requested
  837. if (project.hasProperty("leakCanary")) {
  838. debugImplementation(libs.leakcanary)
  839. }
  840. // test dependencies
  841. testImplementation(libs.junit)
  842. testImplementation(testFixtures(project(":domain")))
  843. // custom test helpers, shared between unit test and android tests
  844. testImplementation(project(":test-helpers"))
  845. androidTestImplementation(project(":test-helpers"))
  846. testImplementation(libs.mockk)
  847. androidTestImplementation(libs.mockkAndroid)
  848. // add JSON support to tests without mocking
  849. testImplementation(libs.json)
  850. testImplementation(libs.archunit.junit4)
  851. androidTestImplementation(testFixtures(project(":domain")))
  852. androidTestImplementation(libs.androidx.test.rules)
  853. androidTestImplementation(libs.fastlane.screengrab) {
  854. exclude(group = "androidx.annotation", module = "annotation")
  855. }
  856. androidTestImplementation(libs.androidx.espresso.core) {
  857. exclude(group = "androidx.annotation", module = "annotation")
  858. }
  859. androidTestImplementation(libs.androidx.test.runner) {
  860. exclude(group = "androidx.annotation", module = "annotation")
  861. }
  862. androidTestImplementation(libs.androidx.junit)
  863. androidTestImplementation(libs.androidx.espresso.contrib) {
  864. exclude(group = "androidx.annotation", module = "annotation")
  865. exclude(group = "androidx.appcompat", module = "appcompat")
  866. exclude(group = "androidx.legacy", module = "legacy-support-v4")
  867. exclude(group = "com.google.android.material", module = "material")
  868. exclude(group = "androidx.recyclerview", module = "recyclerview")
  869. exclude(group = "org.checkerframework", module = "checker")
  870. exclude(module = "protobuf-lite")
  871. }
  872. androidTestImplementation(libs.androidx.espresso.intents) {
  873. exclude(group = "androidx.annotation", module = "annotation")
  874. }
  875. androidTestImplementation(libs.androidx.test.uiautomator)
  876. androidTestImplementation(libs.androidx.test.core)
  877. androidTestImplementation(libs.kotlinx.coroutines.test)
  878. androidTestImplementation(libs.androidx.ui.test.junit4)
  879. testImplementation(libs.kotlinx.coroutines.test)
  880. // Google Play Services and related libraries
  881. "noneImplementation"(libs.playServices.base)
  882. "store_googleImplementation"(libs.playServices.base)
  883. "store_google_workImplementation"(libs.playServices.base)
  884. "store_threemaImplementation"(libs.playServices.base)
  885. "onpremImplementation"(libs.playServices.base)
  886. "greenImplementation"(libs.playServices.base)
  887. "sandbox_workImplementation"(libs.playServices.base)
  888. "blueImplementation"(libs.playServices.base)
  889. fun ExternalModuleDependency.excludeFirebaseDependencies() {
  890. exclude(group = "com.google.firebase", module = "firebase-core")
  891. exclude(group = "com.google.firebase", module = "firebase-analytics")
  892. exclude(group = "com.google.firebase", module = "firebase-measurement-connector")
  893. }
  894. "noneImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
  895. "store_googleImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
  896. "store_google_workImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
  897. "store_threemaImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
  898. "onpremImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
  899. "greenImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
  900. "sandbox_workImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
  901. "blueImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
  902. // Google Assistant Voice Action verification library
  903. "noneImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
  904. "store_googleImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
  905. "store_google_workImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
  906. "onpremImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
  907. "store_threemaImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
  908. "greenImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
  909. "sandbox_workImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
  910. "blueImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
  911. // Maplibre (may have transitive dependencies on Google location services)
  912. "noneImplementation"(libs.maplibre)
  913. "store_googleImplementation"(libs.maplibre)
  914. "store_google_workImplementation"(libs.maplibre)
  915. "store_threemaImplementation"(libs.maplibre)
  916. "libreImplementation"(libs.maplibre) {
  917. exclude(group = "com.google.android.gms")
  918. }
  919. "onpremImplementation"(libs.maplibre)
  920. "greenImplementation"(libs.maplibre)
  921. "sandbox_workImplementation"(libs.maplibre)
  922. "blueImplementation"(libs.maplibre)
  923. "hmsImplementation"(libs.maplibre)
  924. "hms_workImplementation"(libs.maplibre)
  925. // Huawei related libraries (only for hms* build variants)
  926. // Exclude agconnect dependency, we'll replace it with the vendored version below
  927. "hmsImplementation"(libs.hmsPush) {
  928. exclude(group = "com.huawei.agconnect")
  929. }
  930. "hms_workImplementation"(libs.hmsPush) {
  931. exclude(group = "com.huawei.agconnect")
  932. }
  933. "hmsImplementation"(group = "", name = "agconnect-core-1.9.1.301", ext = "aar")
  934. "hms_workImplementation"(group = "", name = "agconnect-core-1.9.1.301", ext = "aar")
  935. }
  936. // Define the cargo attributes. These will be used by the rust-android plugin that will create the
  937. // 'cargoBuild' task that builds native libraries that will be added to the apk. Note that the
  938. // kotlin bindings are created in the domain module. Building native libraries with rust-android
  939. // cannot be done in any other module than 'app'.
  940. cargo {
  941. prebuiltToolchains = true
  942. targetDirectory = "$projectDir/build/generated/source/libthreema"
  943. module = "$projectDir/../domain/libthreema" // must contain Cargo.toml
  944. libname = "libthreema" // must match the Cargo.toml's package name
  945. profile = "release"
  946. pythonCommand = "python3"
  947. targets = listOf("x86_64", "arm64", "arm", "x86")
  948. features {
  949. defaultAnd(arrayOf("uniffi"))
  950. }
  951. extraCargoBuildArguments = listOf("--lib", "--target-dir", "$projectDir/build/generated/source/libthreema", "--locked")
  952. verbose = false
  953. }
  954. afterEvaluate {
  955. // The `cargoBuild` task isn't available until after evaluation.
  956. android.applicationVariants.configureEach {
  957. val variantName = name.replaceFirstChar { it.uppercase() }
  958. // Set the dependency so that cargoBuild is executed before the native libs are merged
  959. tasks["merge${variantName}NativeLibs"].dependsOn(tasks["cargoBuild"])
  960. }
  961. }
  962. sonarqube {
  963. properties {
  964. property(
  965. "sonar.sources",
  966. listOf(
  967. "src/main/",
  968. "../scripts/",
  969. "../scripts-internal/",
  970. )
  971. .joinToString(separator = ", "),
  972. )
  973. property(
  974. "sonar.exclusions",
  975. listOf(
  976. "src/**/res/",
  977. "src/**/res-rendezvous/",
  978. "**/emojis/EmojiParser.kt",
  979. "**/emojis/EmojiSpritemap.kt",
  980. )
  981. .joinToString(separator = ", "),
  982. )
  983. property("sonar.tests", "src/test/")
  984. property("sonar.sourceEncoding", "UTF-8")
  985. property("sonar.verbose", "true")
  986. property("sonar.projectKey", "android-client")
  987. property("sonar.projectName", "Threema for Android")
  988. }
  989. }
  990. androidStem {
  991. includeLocalizedOnlyTemplates = true
  992. }
  993. tasks.register<Exec>("compileProto") {
  994. group = "build"
  995. description = "generate class bindings from protobuf files in the 'protobuf' directory"
  996. workingDir(project.projectDir)
  997. commandLine("./compile-proto.sh")
  998. }
  999. project.tasks.preBuild.dependsOn("compileProto")
  1000. tasks.withType<Test> {
  1001. // Necessary to load the dynamic libthreema library in unit tests
  1002. systemProperty("jna.library.path", "${project.projectDir}/../domain/libthreema/target/release")
  1003. }
  1004. // Set up Gradle tasks to fetch screenshots on UI test failures
  1005. // See https://medium.com/stepstone-tech/how-to-capture-screenshots-for-failed-ui-tests-9927eea6e1e4
  1006. val reportsDirectory = "${layout.buildDirectory}/reports/androidTests/connected"
  1007. val screenshotsDirectory = "/sdcard/testfailures/screenshots/"
  1008. val clearScreenshotsTask = tasks.register<Exec>("clearScreenshots") {
  1009. executable = android.adbExecutable.toString()
  1010. args("shell", "rm", "-r", screenshotsDirectory)
  1011. }
  1012. val createScreenshotsDirectoryTask = tasks.register<Exec>("createScreenshotsDirectory") {
  1013. group = "reporting"
  1014. executable = android.adbExecutable.toString()
  1015. args("shell", "mkdir", "-p", screenshotsDirectory)
  1016. }
  1017. val fetchScreenshotsTask = tasks.register<Exec>("fetchScreenshots") {
  1018. group = "reporting"
  1019. executable = android.adbExecutable.toString()
  1020. args("pull", "$screenshotsDirectory.", reportsDirectory)
  1021. finalizedBy(clearScreenshotsTask)
  1022. dependsOn(createScreenshotsDirectoryTask)
  1023. doFirst {
  1024. file(reportsDirectory).mkdirs()
  1025. }
  1026. }
  1027. tasks.whenTaskAdded {
  1028. if (name == "connectedDebugAndroidTest") {
  1029. finalizedBy(fetchScreenshotsTask)
  1030. }
  1031. }
  1032. // Let the compose compiler generate stability reports
  1033. tasks.withType<KotlinCompile>().configureEach {
  1034. compilerOptions {
  1035. val composeCompilerReportsPath = "${project.layout.buildDirectory.get().dir("compose_compiler").asFile.absolutePath}/reports"
  1036. freeCompilerArgs.addAll(
  1037. listOf(
  1038. "-P",
  1039. "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=$composeCompilerReportsPath",
  1040. ),
  1041. )
  1042. }
  1043. }