build.gradle.kts 44 KB

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