Compare commits

..

2 Commits

Author SHA1 Message Date
Alex Hart
0cc5a81ef9 Bump version to 7.59.2 2025-10-09 16:57:05 -03:00
Michelle Tang
b96ae9f5d6 Put poll receive support behind feature flag. 2025-10-09 16:52:46 -03:00
4771 changed files with 86815 additions and 329959 deletions

View File

@@ -20,7 +20,6 @@ ktlint_standard_unnecessary-parentheses-before-trailing-lambda = disabled
ktlint_standard_value-parameter-comment = disabled
ktlint_standard_class-signature = disabled
ktlint_standard_function-expression-body = disabled
ktlint_standard_blank-line-between-when-conditions = disabled
# Disable ktlint on generated source code, see
# https://github.com/JLLeitschuh/ktlint-gradle/issues/746

23
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 60
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 7
issues:
exemptLabels:
- acknowledged
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
This issue has been closed due to inactivity.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 1

View File

@@ -28,7 +28,7 @@ jobs:
cache: gradle
- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@v5
uses: gradle/wrapper-validation-action@v3
- name: Build with Gradle
run: ./gradlew qa

View File

@@ -32,7 +32,7 @@ jobs:
run: echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "ndk;${{ env.NDK_VERSION }}"
- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@v5
uses: gradle/wrapper-validation-action@v1
- name: Cache base apk
id: cache-base

View File

@@ -1,20 +1,21 @@
name: Check reproducible-build Dockerfile
name: Reproducible Build Check
on:
schedule:
- cron: "0 5 * * *"
- cron: '0 5 * * *'
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Build image
run: |
cd reproducible-builds
docker build -t signal-android .
- name: Test build
run: docker run --memory=12g --rm -v "$(pwd)":/project -w /project signal-android ./gradlew --no-daemon --max-workers=1 -Dorg.gradle.jvmargs="-Xmx4g -XX:MaxMetaspaceSize=512m" -Dkotlin.compiler.execution.strategy=in-process bundlePlayProdRelease
- uses: actions/checkout@v4
- name: Build image
run: cd reproducible-builds && docker build -t signal-android . && cd ..
- name: Test build
run: docker run --rm -v $(pwd):/project -w /project signal-android ./gradlew clean assemblePlayProdRelease

View File

@@ -1,37 +0,0 @@
name: Mark stale issues and PRs
on:
schedule:
- cron: '0 2 * * *' # daily at 02:00 UTC
workflow_dispatch:
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
actions: write
steps:
- uses: actions/stale@v10
with:
days-before-stale: 60
days-before-close: 7
exempt-issue-labels: 'acknowledged'
exempt-pr-labels: 'acknowledged'
stale-issue-label: 'wontfix'
stale-pr-label: 'wontfix'
stale-issue-message: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions!
stale-pr-message: >
This pull request has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions!
close-issue-message: >
This issue has been closed due to inactivity.
close-pr-message: >
This pull request has been closed due to inactivity.
operations-per-run: 150

2
.gitignore vendored
View File

@@ -32,5 +32,3 @@ maps.key
/local/
kls_database.db
.kotlin
lefthook-local.yml
sample-videos/

View File

@@ -1,2 +1 @@
java openjdk-17.0.2
uv latest

View File

@@ -62,7 +62,7 @@ You will need to [sign our CLA](https://signal.org/cla/) before your pull reques
### Follow the Code Style Guidelines
Ensure that your code adheres to the [Code Style Guidelines](https://github.com/signalapp/Signal-Android/wiki/Code-Style-Guidelines) before submitting a pull request.
You can run `./gradlew format` to automatically format your code. See `lefthook.yml` for instructions on how to run this as a git hook.
You can run `./gradlew format` to automatically check conformance with some of these guidelines.
### Submit finished and well-tested pull requests
Please do not submit pull requests that are still a work in progress. Pull requests should be thoroughly tested and ready to merge before they are submitted.

View File

@@ -1,18 +1,14 @@
# Signal Android
Signal is a simple, powerful, and secure messenger that uses your phone's data connection (WiFi/3G/4G/5G) to communicate securely.
Signal is a simple, powerful, and secure messenger.
Millions of people use Signal every day for free and instantaneous communication anywhere in the world. Send and receive high-fidelity messages, participate in HD voice/video calls, and explore a growing set of new features that help you stay connected.
Signal uses your phone's data connection (WiFi/3G/4G/5G) to communicate securely. Millions of people use Signal every day for free and instantaneous communication anywhere in the world. Send and receive high-fidelity messages, participate in HD voice/video calls, and explore a growing set of new features that help you stay connected. Signals advanced privacy-preserving technology is always enabled, so you can focus on sharing the moments that matter with the people who matter to you.
Signals advanced privacy-preserving technology is always enabled, so you can focus on sharing the moments that matter with the people who matter to you.
Currently available on the [Play Store](https://play.google.com/store/apps/details?id=org.thoughtcrime.securesms) and [signal.org](https://signal.org/android/apk/).
Currently available on the Play Store and [signal.org](https://signal.org/android/apk/).
<a href='https://play.google.com/store/apps/details?id=org.thoughtcrime.securesms&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' src='https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png' height='80px'/></a>
Also available on [iOS](https://github.com/signalapp/signal-ios) and [Desktop](https://github.com/signalapp/signal-desktop).
## Contributing Bug Reports
## Contributing Bug reports
We use GitHub for bug tracking. Please search the existing issues for your bug and create a new one if the issue is not yet tracked!
https://github.com/signalapp/Signal-Android/issues
@@ -25,11 +21,6 @@ https://play.google.com/apps/testing/org.thoughtcrime.securesms
If you're interested in a life of peace and tranquility, stick with the standard releases.
## Contributing Translations
Interested in helping translate Signal? Contribute here:
https://community.signalusers.org/c/translation-feedback/
## Contributing Code
If you're new to the Signal codebase, we recommend going through our issues and picking out a simple bug to fix in order to get yourself familiar. Also please have a look at the [CONTRIBUTING.md](https://github.com/signalapp/Signal-Android/blob/main/CONTRIBUTING.md), that might answer some of your questions.

View File

@@ -2,10 +2,9 @@
import com.android.build.api.dsl.ManagedVirtualDevice
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import java.time.Instant
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.util.Locale
import java.io.FileInputStream
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Properties
plugins {
@@ -14,7 +13,6 @@ plugins {
alias(libs.plugins.ktlint)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.kotlinx.serialization)
alias(benchmarkLibs.plugins.baselineprofile)
id("androidx.navigation.safeargs")
id("kotlin-parcelize")
id("com.squareup.wire")
@@ -24,36 +22,16 @@ plugins {
apply(from = "static-ips.gradle.kts")
val canonicalVersionCode = 1675
val canonicalVersionName = "8.6.2"
val currentHotfixVersion = 0
val canonicalVersionCode = 1596
val canonicalVersionName = "7.59.2"
val currentHotfixVersion = 1
val maxHotfixVersions = 100
// We don't want versions to ever end in 0 so that they don't conflict with nightly versions
val possibleHotfixVersions = (0 until maxHotfixVersions).toList().filter { it % 10 != 0 }
val debugKeystorePropertiesProvider = providers.of(PropertiesFileValueSource::class.java) {
parameters.file.set(rootProject.layout.projectDirectory.file("keystore.debug.properties"))
}
val languagesProvider = providers.of(LanguageListValueSource::class.java) {
parameters.resDir.set(layout.projectDirectory.dir("src/main/res"))
}
val languagesForBuildConfigProvider = languagesProvider.map { languages ->
languages.joinToString(separator = ", ") { language -> "\"$language\"" }
}
val localPropertiesFile = File(rootProject.projectDir, "local.properties")
val localProperties: Properties? = if (localPropertiesFile.exists()) {
Properties().apply { localPropertiesFile.inputStream().use { load(it) } }
} else {
null
}
val quickstartCredentialsDir: String? = localProperties?.getProperty("quickstart.credentials.dir")
val benchmarkBackupFile: String? = localProperties?.getProperty("benchmark.backup.file")
val keystores: Map<String, Properties?> = mapOf("debug" to loadKeystoreProperties("keystore.debug.properties"))
val selectableVariants = listOf(
"nightlyBackupRelease",
"nightlyBackupSpinner",
"nightlyProdSpinner",
"nightlyProdPerf",
"nightlyProdRelease",
@@ -62,8 +40,6 @@ val selectableVariants = listOf(
"playProdSpinner",
"playProdCanary",
"playProdPerf",
"playProdMocked",
"playProdNonMinifiedMocked",
"playProdBenchmark",
"playProdInstrumentation",
"playProdRelease",
@@ -73,14 +49,18 @@ val selectableVariants = listOf(
"playStagingPerf",
"playStagingInstrumentation",
"playStagingRelease",
"playProdQuickstart",
"playStagingQuickstart",
"websiteProdSpinner",
"websiteProdRelease",
"githubProdSpinner",
"githubProdRelease"
"websiteProdRelease"
)
val signalBuildToolsVersion: String by rootProject.extra
val signalCompileSdkVersion: String by rootProject.extra
val signalTargetSdkVersion: Int by rootProject.extra
val signalMinSdkVersion: Int by rootProject.extra
val signalNdkVersion: String by rootProject.extra
val signalJavaVersion: JavaVersion by rootProject.extra
val signalKotlinJvmTarget: String by rootProject.extra
wire {
kotlin {
javaInterop = true
@@ -91,23 +71,22 @@ wire {
}
protoPath {
srcDir("${project.rootDir}/lib/libsignal-service/src/main/protowire")
srcDir("${project.rootDir}/lib/archive/src/main/protowire")
srcDir("${project.rootDir}/libsignal-service/src/main/protowire")
}
// Handled by libsignal
prune("signalservice.DecryptionErrorMessage")
}
ktlint {
version.set("1.5.0")
version.set("1.2.1")
}
android {
namespace = "org.thoughtcrime.securesms"
buildToolsVersion = libs.versions.buildTools.get()
compileSdkVersion = libs.versions.compileSdk.get()
ndkVersion = libs.versions.ndk.get()
buildToolsVersion = signalBuildToolsVersion
compileSdkVersion = signalCompileSdkVersion
ndkVersion = signalNdkVersion
flavorDimensions += listOf("distribution", "environment")
testBuildType = "instrumentation"
@@ -115,12 +94,12 @@ android {
android.bundle.language.enableSplit = false
kotlinOptions {
jvmTarget = libs.versions.kotlinJvmTarget.get()
jvmTarget = signalKotlinJvmTarget
freeCompilerArgs = listOf("-Xjvm-default=all")
suppressWarnings = true
}
debugKeystorePropertiesProvider.orNull?.let { properties ->
keystores["debug"]?.let { properties ->
signingConfigs.getByName("debug").apply {
storeFile = file("${project.rootDir}/${properties.getProperty("storeFile")}")
storePassword = properties.getProperty("storePassword")
@@ -160,8 +139,8 @@ android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.toVersion(libs.versions.javaVersion.get())
targetCompatibility = JavaVersion.toVersion(libs.versions.javaVersion.get())
sourceCompatibility = signalJavaVersion
targetCompatibility = signalJavaVersion
}
packaging {
@@ -183,8 +162,7 @@ android {
"META-INF/LICENSE-notice.md",
"META-INF/proguard/androidx-annotations.pro",
"**/*.dylib",
"**/*.dll",
"**/*.proto"
"**/*.dll"
)
}
}
@@ -200,14 +178,11 @@ android {
}
defaultConfig {
if (currentHotfixVersion >= maxHotfixVersions) {
throw AssertionError("Hotfix version offset is too large!")
}
versionCode = (canonicalVersionCode * maxHotfixVersions) + possibleHotfixVersions[currentHotfixVersion]
versionCode = (canonicalVersionCode * maxHotfixVersions) + currentHotfixVersion
versionName = canonicalVersionName
minSdk = libs.versions.minSdk.get().toInt()
targetSdk = libs.versions.targetSdk.get().toInt()
minSdk = signalMinSdkVersion
targetSdk = signalTargetSdkVersion
vectorDrawables.useSupportLibrary = true
project.ext.set("archivesBaseName", "Signal")
@@ -240,13 +215,14 @@ android {
buildConfigField("String[]", "SIGNAL_CDSI_IPS", rootProject.extra["cdsi_ips"] as String)
buildConfigField("String[]", "SIGNAL_SVR2_IPS", rootProject.extra["svr2_ips"] as String)
buildConfigField("String", "SIGNAL_AGENT", "\"OWA\"")
buildConfigField("String", "SVR2_MRENCLAVE_LEGACY", "\"29cd63c87bea751e3bfd0fbd401279192e2e5c99948b4ee9437eafc4968355fb\"")
buildConfigField("String", "SVR2_MRENCLAVE", "\"1240acbd4aa26974184844c8a46b1022d3957ac8a76c1fd8f5b1a15141ee0708\"")
buildConfigField("String", "SVR2_MRENCLAVE_LEGACY_LEGACY", "\"9314436a9a144992bb3680770ea5fd7934a7ffd29257844a33763a238903d570\"")
buildConfigField("String", "SVR2_MRENCLAVE_LEGACY", "\"093be9ea32405e85ae28dbb48eb668aebeb7dbe29517b9b86ad4bec4dfe0e6a6\"")
buildConfigField("String", "SVR2_MRENCLAVE", "\"29cd63c87bea751e3bfd0fbd401279192e2e5c99948b4ee9437eafc4968355fb\"")
buildConfigField("String[]", "UNIDENTIFIED_SENDER_TRUST_ROOTS", "new String[]{ \"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\", \"BUkY0I+9+oPgDCn4+Ac6Iu813yvqkDr/ga8DzLxFxuk6\"}")
buildConfigField("String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P+NameAZYOD12qRkxosQQP5uux6B2nRyZ7sAV54DgFyLiRcq1FvwKw2EPQdk4HDoePrO/RNUbyNddnM/mMgj4FW65xCoT1LmjrIjsv/Ggdlx46ueczhMgtBunx1/w8k8V+l8LVZ8gAT6wkU5J+DPQalQguMg12Jzug3q4TbdHiGCmD9EunCwOmsLuLJkz6EcSYXtrlDEnAM+hicw7iergYLLlMXpfTdGxJCWJmP4zqUFeTTmsmhsjGBt7NiEB/9pFFEB3pSbf4iiUukw63Eo8Aqnf4iwob6X1QviCWuc8t0LUlT9vALgh/f2DPVOOmR0RW6bgRvc7DSF20V/omg+YBw==\"")
buildConfigField("String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AByD873dTilmOSG0TjKrvpeaKEsUmIO8Vx9BeMmftwUs9v7ikPwM8P3OHyT0+X3EUMZrSe9VUp26Wai51Q9I8mdk0hX/yo7CeFGJyzoOqn8e/i4Ygbn5HoAyXJx5eXfIbqpc0bIxzju4H/HOQeOpt6h742qii5u/cbwOhFZCsMIbElZTaeU+BWMBQiZHIGHT5IE0qCordQKZ5iPZom0HeFa8Yq0ShuEyAl0WINBiY6xE3H/9WnvzXBbMuuk//eRxXgzO8ieCeK8FwQNxbfXqZm6Ro1cMhCOF3u7xoX83QhpN\"")
buildConfigField("String", "BACKUP_SERVER_PUBLIC_PARAMS", "\"AJwNSU55fsFCbgaxGRD11wO1juAs8Yr5GF8FPlGzzvdJJIKH5/4CC7ZJSOe3yL2vturVaRU2Cx0n751Vt8wkj1bozK3CBV1UokxV09GWf+hdVImLGjXGYLLhnI1J2TWEe7iWHyb553EEnRb5oxr9n3lUbNAJuRmFM7hrr0Al0F0wrDD4S8lo2mGaXe0MJCOM166F8oYRQqpFeEHfiLnxA1O8ZLh7vMdv4g9jI5phpRBTsJ5IjiJrWeP0zdIGHEssUeprDZ9OUJ14m0v61eYJMKsf59Bn+mAT2a7YfB+Don9O\"")
buildConfigField("String[]", "LANGUAGES", "new String[]{ ${languagesForBuildConfigProvider.get()} }")
buildConfigField("String[]", "LANGUAGES", "new String[]{ ${languageList().map { "\"$it\"" }.joinToString(separator = ", ")} }")
buildConfigField("int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode")
buildConfigField("String", "DEFAULT_CURRENCIES", "\"EUR,AUD,GBP,CAD,CNY\"")
buildConfigField("String", "GIPHY_API_KEY", "\"3o6ZsYH6U6Eri53TXy\"")
@@ -263,7 +239,6 @@ android {
buildConfigField("String", "STRIPE_PUBLISHABLE_KEY", "\"pk_live_6cmGZopuTsV8novGgJJW9JpC00vLIgtQ1D\"")
buildConfigField("boolean", "TRACING_ENABLED", "false")
buildConfigField("boolean", "LINK_DEVICE_UX_ENABLED", "false")
buildConfigField("boolean", "USE_STRING_ID", "false")
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
@@ -285,7 +260,7 @@ android {
buildTypes {
getByName("debug") {
if (debugKeystorePropertiesProvider.orNull != null) {
if (keystores["debug"] != null) {
signingConfig = signingConfigs["debug"]
}
isDefault = true
@@ -318,7 +293,6 @@ android {
manifestPlaceholders["mapsKey"] = getMapsKey()
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Debug\"")
buildConfigField("boolean", "LINK_DEVICE_UX_ENABLED", "true")
}
getByName("release") {
@@ -344,6 +318,7 @@ android {
isMinifyEnabled = false
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Spinner\"")
buildConfigField("boolean", "LINK_DEVICE_UX_ENABLED", "true")
}
create("perf") {
@@ -362,25 +337,8 @@ android {
isDebuggable = false
isMinifyEnabled = true
matchingFallbacks += "debug"
applicationIdSuffix = ".benchmark"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Benchmark\"")
buildConfigField("boolean", "TRACING_ENABLED", "true")
buildConfigField("String[]", "UNIDENTIFIED_SENDER_TRUST_ROOTS", "new String[]{ \"BVT/2gHqbrG1xzuIypLIOjFgMtihrMld1/5TGADL6Dhv\"}")
manifestPlaceholders["applicationClass"] = "org.thoughtcrime.securesms.BenchmarkApplicationContext"
}
create("mocked") {
initWith(getByName("debug"))
isDefault = false
isDebuggable = false
isMinifyEnabled = true
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Benchmark\"")
buildConfigField("boolean", "TRACING_ENABLED", "true")
manifestPlaceholders["applicationClass"] = "org.thoughtcrime.securesms.ApplicationContext"
}
create("canary") {
@@ -390,14 +348,6 @@ android {
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Canary\"")
}
create("quickstart") {
initWith(getByName("debug"))
isDefault = false
isMinifyEnabled = false
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Quickstart\"")
}
}
productFlavors {
@@ -416,20 +366,18 @@ android {
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"website\"")
}
create("github") {
dimension = "distribution"
buildConfigField("boolean", "MANAGES_APP_UPDATES", "false")
buildConfigField("String", "APK_UPDATE_MANIFEST_URL", "null")
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"github\"")
}
create("nightly") {
val apkUpdateManifestUrl = if (file("${project.rootDir}/nightly-url.txt").exists()) {
file("${project.rootDir}/nightly-url.txt").readText().trim()
} else {
"<unset>"
}
dimension = "distribution"
versionNameSuffix = "-nightly-untagged-${getGitHash()}"
buildConfigField("boolean", "MANAGES_APP_UPDATES", "false")
buildConfigField("String", "APK_UPDATE_MANIFEST_URL", "null")
versionNameSuffix = "-nightly-untagged-${getDateSuffix()}"
buildConfigField("boolean", "MANAGES_APP_UPDATES", "true")
buildConfigField("String", "APK_UPDATE_MANIFEST_URL", "\"${apkUpdateManifestUrl}\"")
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"nightly\"")
buildConfigField("boolean", "LINK_DEVICE_UX_ENABLED", "true")
}
create("prod") {
@@ -453,8 +401,9 @@ android {
buildConfigField("String", "SIGNAL_CDN3_URL", "\"https://cdn3-staging.signal.org\"")
buildConfigField("String", "SIGNAL_CDSI_URL", "\"https://cdsi.staging.signal.org\"")
buildConfigField("String", "SIGNAL_SVR2_URL", "\"https://svr2.staging.signal.org\"")
buildConfigField("String", "SVR2_MRENCLAVE_LEGACY", "\"a75542d82da9f6914a1e31f8a7407053b99cc99a0e7291d8fbd394253e19b036\"")
buildConfigField("String", "SVR2_MRENCLAVE", "\"97f151f6ed078edbbfd72fa9cae694dcc08353f1f5e8d9ccd79a971b10ffc535\"")
buildConfigField("String", "SVR2_MRENCLAVE_LEGACY_LEGACY", "\"38e01eff4fe357dc0b0e8ef7a44b4abc5489fbccba3a78780f3872c277f62bf3\"")
buildConfigField("String", "SVR2_MRENCLAVE_LEGACY", "\"2e8cefe6e3f389d8426adb24e9b7fb7adf10902c96f06f7bbcee36277711ed91\"")
buildConfigField("String", "SVR2_MRENCLAVE", "\"a75542d82da9f6914a1e31f8a7407053b99cc99a0e7291d8fbd394253e19b036\"")
buildConfigField("String[]", "UNIDENTIFIED_SENDER_TRUST_ROOTS", "new String[]{\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\", \"BYhU6tPjqP46KGZEzRs1OL4U39V5dlPJ/X09ha4rErkm\"}")
buildConfigField("String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUjlENAErBme1YHmOSpU6tr6doJ66dPzVAWIanmO/5mgjNEDeK7DDqQdB1xd03HT2Qs2TxY3kCK8aAb/0iM0HQiXjxZ9HIgYhbtvGEnDKW5ILSUydqH/KBhW4Pb0jZWnqN/YgbWDKeJxnDbYcUob5ZY5Lt5ZCMKuaGUvCJRrCtuugSMaqjowCGRempsDdJEt+cMaalhZ6gczklJB/IbdwENW9KeVFPoFNFzhxWUIS5ML9riVYhAtE6JE5jX0xiHNVIIPthb458cfA8daR0nYfYAUKogQArm0iBezOO+mPk5vCNWI+wwkyFCqNDXz/qxl1gAntuCJtSfq9OC3NkdhQlgYQ==\"")
buildConfigField("String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AHILOIrFPXX9laLbalbA9+L1CXpSbM/bTJXZGZiuyK1JaI6dK5FHHWL6tWxmHKYAZTSYmElmJ5z2A5YcirjO/yfoemE03FItyaf8W1fE4p14hzb5qnrmfXUSiAIVrhaXVwIwSzH6RL/+EO8jFIjJ/YfExfJ8aBl48CKHgu1+A6kWynhttonvWWx6h7924mIzW0Czj2ROuh4LwQyZypex4GuOPW8sgIT21KNZaafgg+KbV7XM1x1tF3XA17B4uGUaDbDw2O+nR1+U5p6qHPzmJ7ggFjSN6Utu+35dS1sS0P9N\"")
@@ -468,6 +417,17 @@ android {
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"Staging\"")
buildConfigField("String", "STRIPE_PUBLISHABLE_KEY", "\"pk_test_sngOd8FnXNkpce9nPXawKrJD00kIDngZkD\"")
}
create("backup") {
initWith(getByName("staging"))
dimension = "environment"
applicationIdSuffix = ".backup"
buildConfigField("boolean", "MANAGES_APP_UPDATES", "true")
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"Backup\"")
}
}
lint {
@@ -477,70 +437,42 @@ android {
ignoreWarnings = true
quiet = true
disable += "LintError"
lintConfig = rootProject.file("lint.xml")
}
applicationVariants.all {
outputs
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
.forEach { output ->
if (output.baseName.contains("nightly")) {
var tag = getCurrentGitTag()
if (!tag.isNullOrEmpty()) {
if (tag.startsWith("v")) {
tag = tag.substring(1)
}
output.versionNameOverride = tag
output.outputFileName = output.outputFileName.replace(".apk", "-${output.versionNameOverride}.apk")
} else {
output.outputFileName = output.outputFileName.replace(".apk", "-$versionName.apk")
}
} else {
output.outputFileName = output.outputFileName.replace(".apk", "-$versionName.apk")
if (currentHotfixVersion >= maxHotfixVersions) {
throw AssertionError("Hotfix version is too large!")
}
}
}
}
androidComponents {
beforeVariants { variant ->
variant.enable = variant.name in selectableVariants
}
onVariants(selector().all()) { variant: com.android.build.api.variant.ApplicationVariant ->
onVariants { variant ->
// Include the test-only library on debug builds.
if (variant.buildType != "instrumentation") {
variant.packaging.jniLibs.excludes.add("**/libsignal_jni_testing.so")
}
// Starting with minSdk 23, Android leaves native libraries uncompressed, which is fine for the Play Store, but not for our self-distributed APKs.
// This reverts it to the legacy behavior, compressing the native libraries, and drastically reducing the APK file size.
if (variant.name.contains("website", ignoreCase = true) || variant.name.contains("github", ignoreCase = true)) {
variant.packaging.jniLibs.useLegacyPackaging.set(true)
}
// Version overrides
if (variant.name.contains("nightly", ignoreCase = true)) {
var tag = getNightlyTagForCurrentCommit()
if (!tag.isNullOrEmpty()) {
if (tag.startsWith("v")) {
tag = tag.substring(1)
}
// We add a multiple of maxHotfixVersions to nightlies to ensure we're always at least that many versions ahead
val nightlyBuffer = (5 * maxHotfixVersions)
val nightlyVersionCode = (canonicalVersionCode * maxHotfixVersions) + (getNightlyBuildNumber(tag) * 10) + nightlyBuffer
variant.outputs.forEach { output ->
output.versionName.set("$tag | ${getLastCommitDateTimeUtc()}")
output.versionCode.set(nightlyVersionCode)
}
}
}
}
onVariants(selector().withBuildType("quickstart")) { variant ->
val environment = variant.flavorName?.let { name ->
when {
name.contains("staging", ignoreCase = true) -> "staging"
name.contains("prod", ignoreCase = true) -> "prod"
else -> "prod"
}
} ?: "prod"
val taskProvider = tasks.register<CopyQuickstartCredentialsTask>("copyQuickstartCredentials${variant.name.capitalize()}") {
if (quickstartCredentialsDir != null) {
inputDir.set(File(quickstartCredentialsDir))
}
filePrefix.set("${environment}_")
}
variant.sources.assets?.addGeneratedSourceDirectory(taskProvider) { it.outputDir }
}
onVariants(selector().withBuildType("benchmark")) { variant ->
val taskProvider = tasks.register<CopyBenchmarkBackupTask>("copyBenchmarkBackup${variant.name.capitalize()}") {
if (benchmarkBackupFile != null) {
inputFile.set(File(benchmarkBackupFile))
}
}
variant.sources.assets?.addGeneratedSourceDirectory(taskProvider) { it.outputDir }
}
}
@@ -553,41 +485,6 @@ android {
java.srcDir(path)
}
}
sourceSets {
getByName("mocked") {
java.srcDir("$projectDir/src/benchmarkShared/java")
manifest.srcFile("$projectDir/src/benchmarkShared/AndroidManifest.xml")
}
getByName("benchmark") {
java.srcDir("$projectDir/src/benchmarkShared/java")
manifest.srcFile("$projectDir/src/benchmarkShared/AndroidManifest.xml")
}
}
applicationVariants.configureEach {
outputs.configureEach {
if (this is com.android.build.gradle.internal.api.BaseVariantOutputImpl) {
val fileVersionName = versionName.substringBefore(" |")
outputFileName = outputFileName.replace(".apk", "-$fileVersionName.apk")
}
}
}
}
baselineProfile {
warnings {
disabledVariants = false
}
mergeIntoMain = true
variants.create("mocked") {
from(project(":baseline-profile"))
}
dexLayoutOptimization = false
}
dependencies {
@@ -595,29 +492,23 @@ dependencies {
ktlintRuleset(libs.ktlint.twitter.compose)
coreLibraryDesugaring(libs.android.tools.desugar)
implementation(project(":lib:archive"))
implementation(project(":lib:libsignal-service"))
implementation(project(":lib:paging"))
implementation(project(":core:util"))
implementation(project(":lib:glide"))
implementation(project(":lib:video"))
implementation(project(":lib:device-transfer"))
implementation(project(":lib:image-editor"))
implementation(project(":lib:donations"))
implementation(project(":lib:debuglogs-viewer"))
implementation(project(":lib:contacts"))
implementation(project(":lib:qr"))
implementation(project(":lib:sticky-header-grid"))
implementation(project(":lib:photoview"))
implementation(project(":lib:blurhash"))
implementation(project(":core:ui"))
implementation(project(":core:models"))
implementation(project(":core:models-jvm"))
implementation(project(":feature:camera"))
implementation(project(":feature:registration"))
implementation(project(":lib:apng"))
implementation(project(":libsignal-service"))
implementation(project(":paging"))
implementation(project(":core-util"))
implementation(project(":glide-config"))
implementation(project(":video"))
implementation(project(":device-transfer"))
implementation(project(":image-editor"))
implementation(project(":donations"))
implementation(project(":debuglogs-viewer"))
implementation(project(":contacts"))
implementation(project(":qr"))
implementation(project(":sticky-header-grid"))
implementation(project(":photoview"))
implementation(project(":core-ui"))
implementation(libs.androidx.fragment.ktx)
implementation(libs.androidx.fragment.compose)
implementation(libs.androidx.appcompat) {
version {
strictly("1.6.1")
@@ -639,8 +530,6 @@ dependencies {
implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.navigation3.runtime)
implementation(libs.androidx.navigation3.ui)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.lifecycle.livedata.ktx)
implementation(libs.androidx.lifecycle.process)
@@ -657,7 +546,6 @@ dependencies {
implementation(libs.androidx.concurrent.futures)
implementation(libs.androidx.autofill)
implementation(libs.androidx.biometric)
implementation(libs.androidx.core.telecom)
implementation(libs.androidx.sharetarget)
implementation(libs.androidx.profileinstaller)
implementation(libs.androidx.asynclayoutinflater)
@@ -718,10 +606,9 @@ dependencies {
implementation(libs.androidx.credentials.compat)
implementation(libs.kotlinx.serialization.json)
implementation(project(":lib:billing"))
implementation(project(":feature:media-send"))
implementation(project(":billing"))
"spinnerImplementation"(project(":lib:spinner"))
"spinnerImplementation"(project(":spinner"))
"canaryImplementation"(libs.square.leakcanary)
@@ -747,10 +634,9 @@ dependencies {
}
testImplementation(testLibs.conscrypt.openjdk.uber)
testImplementation(testLibs.mockk)
testImplementation(testFixtures(project(":lib:libsignal-service")))
testImplementation(testFixtures(project(":libsignal-service")))
testImplementation(testLibs.espresso.core)
testImplementation(testLibs.kotlinx.coroutines.test)
testImplementation(testLibs.sqlite.jdbc)
testImplementation(libs.androidx.compose.ui.test.junit4)
"perfImplementation"(libs.androidx.compose.ui.test.manifest)
@@ -764,34 +650,37 @@ dependencies {
androidTestImplementation(testLibs.androidx.test.ext.junit.ktx)
androidTestImplementation(testLibs.assertk)
androidTestImplementation(testLibs.mockk.android)
androidTestImplementation(testLibs.square.okhttp.mockserver)
androidTestImplementation(testLibs.diff.utils)
androidTestUtil(testLibs.androidx.test.orchestrator)
}
tasks.withType<Test>().configureEach {
testLogging {
events("failed")
exceptionFormat = TestExceptionFormat.FULL
showCauses = true
showExceptions = true
showStackTraces = true
fun assertIsGitRepo() {
if (!file("${project.rootDir}/.git").exists()) {
throw IllegalStateException("Must be a git repository to guarantee reproducible builds! (git hash is part of APK)")
}
}
fun getLastCommitTimestamp(): String {
assertIsGitRepo()
return providers.exec {
commandLine("git", "log", "-1", "--pretty=format:%ct")
}.standardOutput.asText.get() + "000"
}
fun getGitHash(): String {
assertIsGitRepo()
return providers.exec {
commandLine("git", "rev-parse", "HEAD")
}.standardOutput.asText.get().trim().substring(0, 12)
}
fun getNightlyTagForCurrentCommit(): String? {
fun getCurrentGitTag(): String? {
assertIsGitRepo()
val output = providers.exec {
commandLine("git", "tag", "--points-at", "HEAD")
}.standardOutput.asText.get().trim()
@@ -804,140 +693,65 @@ fun getNightlyTagForCurrentCommit(): String? {
}
}
fun getNightlyBuildNumber(tag: String?): Int {
if (tag == null) {
return 0
tasks.withType<Test>().configureEach {
testLogging {
events("failed")
exceptionFormat = TestExceptionFormat.FULL
showCauses = true
showExceptions = true
showStackTraces = true
}
val match = Regex("-(\\d{3})$").find(tag)
return match?.groupValues?.get(1)?.toIntOrNull() ?: 0
}
fun getLastCommitDateTimeUtc(): String {
val timestamp = providers.exec {
commandLine("git", "log", "-1", "--pretty=format:%ct")
}.standardOutput.asText.get().trim().toLong()
val instant = Instant.ofEpochSecond(timestamp)
val formatter = DateTimeFormatter.ofPattern("MMM d '@' HH:mm 'UTC'", Locale.US)
.withZone(ZoneOffset.UTC)
return formatter.format(instant)
gradle.taskGraph.whenReady {
if (gradle.startParameter.taskNames.any { it.contains("nightly", ignoreCase = true) }) {
if (!file("${project.rootDir}/nightly-url.txt").exists()) {
throw GradleException("Missing required file: nightly-url.txt")
}
}
}
fun loadKeystoreProperties(filename: String): Properties? {
val keystorePropertiesFile = file("${project.rootDir}/$filename")
return if (keystorePropertiesFile.exists()) {
val keystoreProperties = Properties()
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
keystoreProperties
} else {
null
}
}
fun getDateSuffix(): String {
return SimpleDateFormat("yyyy-MM-dd-HH:mm").format(Date())
}
fun getMapsKey(): String {
return providers
.gradleProperty("mapsKey")
.orElse(providers.environmentVariable("MAPS_KEY"))
.orElse("AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U")
.get()
}
val mapKey = file("${project.rootDir}/maps.key")
abstract class LanguageListValueSource : ValueSource<List<String>, LanguageListValueSource.Params> {
interface Params : ValueSourceParameters {
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
val resDir: DirectoryProperty
}
override fun obtain(): List<String> {
// In API 35, language codes for Hebrew and Indonesian now use the ISO 639-1 code ("he" and "id").
// However, the value resources still only support the outdated code ("iw" and "in") so we have
// to manually indicate that we support these languages.
val updatedLanguageCodes = listOf("he", "id")
val resRoot = parameters.resDir.asFile.get()
val languages = resRoot
.walkTopDown()
.filter { it.isFile && it.name == "strings.xml" }
.mapNotNull { stringFile -> stringFile.parentFile?.name }
.map { valuesFolderName -> valuesFolderName.removePrefix("values-") }
.filter { valuesFolderName -> valuesFolderName != "values" }
.map { languageCode -> languageCode.replace("-r", "_") }
.toList()
.distinct()
.sorted()
return languages + updatedLanguageCodes + "en"
return if (mapKey.exists()) {
mapKey.readLines()[0]
} else {
"AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"
}
}
abstract class PropertiesFileValueSource : ValueSource<Properties?, PropertiesFileValueSource.Params> {
interface Params : ValueSourceParameters {
@get:InputFile
@get:Optional
@get:PathSensitive(PathSensitivity.RELATIVE)
val file: RegularFileProperty
}
fun Project.languageList(): List<String> {
// In API 35, language codes for Hebrew and Indonesian now use the ISO 639-1 code ("he" and "id").
// However, the value resources still only support the outdated code ("iw" and "in") so we have
// to manually indicate that we support these languages.
val updatedLanguageCodes = listOf("he", "id")
override fun obtain(): Properties? {
val f: File = parameters.file.asFile.get()
if (!f.exists()) return null
return Properties().apply {
f.inputStream().use { load(it) }
}
}
return fileTree("src/main/res") { include("**/strings.xml") }
.map { stringFile -> stringFile.parentFile.name }
.map { valuesFolderName -> valuesFolderName.replace("values-", "") }
.filter { valuesFolderName -> valuesFolderName != "values" }
.map { languageCode -> languageCode.replace("-r", "_") }
.distinct()
.sorted() + updatedLanguageCodes + "en"
}
fun String.capitalize(): String {
return this.replaceFirstChar { it.uppercase() }
}
abstract class CopyQuickstartCredentialsTask : DefaultTask() {
@get:InputDirectory
@get:Optional
abstract val inputDir: DirectoryProperty
@get:Input
abstract val filePrefix: Property<String>
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@TaskAction
fun copy() {
if (!inputDir.isPresent) {
throw GradleException("quickstart.credentials.dir is not set in local.properties. This is required for quickstart builds.")
}
val prefix = filePrefix.get()
val candidates = inputDir.get().asFile.listFiles()
?.filter { it.extension == "json" && it.name.startsWith(prefix) }
?: emptyList()
if (candidates.isEmpty()) {
throw GradleException("No credential files matching '$prefix*.json' found in ${inputDir.get().asFile}. Add files like '${prefix}account1.json' to your credentials directory.")
}
val chosen = candidates.random()
logger.lifecycle("Selected quickstart credential: ${chosen.name}")
val dest = outputDir.get().asFile.resolve("quickstart")
dest.mkdirs()
chosen.copyTo(dest.resolve(chosen.name), overwrite = true)
}
}
abstract class CopyBenchmarkBackupTask : DefaultTask() {
@get:InputFile
@get:Optional
abstract val inputFile: RegularFileProperty
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@TaskAction
fun copy() {
val dest = outputDir.get().asFile.resolve("backups")
dest.mkdirs()
if (!inputFile.isPresent) {
logger.lifecycle("benchmark.backup.file is not set in local.properties. Benchmark tests using backup data will crash at runtime.")
return
}
val backupFile = inputFile.get().asFile
logger.lifecycle("Using benchmark backup: ${backupFile.absolutePath} (${backupFile.length() / 1024}KB)")
backupFile.copyTo(dest.resolve("backup.binproto"), overwrite = true)
}
}

View File

@@ -22,9 +22,7 @@
<issue id="CanvasSize" severity="error" />
<issue id="HardcodedText" severity="error" />
<issue id="VectorRaster" severity="error">
<ignore path="*/donate_with_googlepay_button_content.xml" /> <!-- Externally provided by Google -->
</issue>
<issue id="VectorRaster" severity="error" />
<issue id="ButtonOrder" severity="error" />
<issue id="ExtraTranslation" severity="warning" />
<issue id="UnspecifiedImmutableFlag" severity="error" />

Some files were not shown because too many files have changed in this diff Show More