Source added

This commit is contained in:
Fr4nz D13trich 2025-11-20 09:26:33 +01:00
parent b2864b500e
commit ba28ca859e
8352 changed files with 1487182 additions and 1 deletions

10
app/.tx/config Normal file
View file

@ -0,0 +1,10 @@
[main]
host = https://www.transifex.com
lang_map = da_DK:da-rDK,fil:tl,he:iw,id:in,kn_IN:kn-rIN,pa_PK:pa-rPK,pt_BR:pt-rBR,pt_PT:pt,qu_EC:qu-rEC,sv_SE:sv-rSE,zh_CN:zh-rCN,zh_HK:zh-rHK,zh_TW:zh-rTW
[signal-android.master]
file_filter = src/main/res/values-<lang>/strings.xml
source_file = src/main/res/values/strings.xml
source_lang = en
type = ANDROID

756
app/build.gradle.kts Normal file
View file

@ -0,0 +1,756 @@
@file:Suppress("UnstableApiUsage")
import com.android.build.api.dsl.ManagedVirtualDevice
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import java.io.FileInputStream
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Properties
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.ktlint)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.kotlinx.serialization)
id("androidx.navigation.safeargs")
id("kotlin-parcelize")
id("com.squareup.wire")
id("translations")
id("licenses")
}
apply(from = "static-ips.gradle.kts")
val canonicalVersionCode = 1619
val canonicalVersionName = "7.65.4"
val currentHotfixVersion = 0
val maxHotfixVersions = 100
val keystores: Map<String, Properties?> = mapOf("debug" to loadKeystoreProperties("keystore.debug.properties"))
val selectableVariants = listOf(
"nightlyBackupRelease",
"nightlyBackupSpinner",
"nightlyProdSpinner",
"nightlyProdPerf",
"nightlyProdRelease",
"nightlyStagingRelease",
"playProdDebug",
"playProdSpinner",
"playProdCanary",
"playProdPerf",
"playProdBenchmark",
"playProdInstrumentation",
"playProdRelease",
"playStagingDebug",
"playStagingCanary",
"playStagingSpinner",
"playStagingPerf",
"playStagingInstrumentation",
"playStagingRelease",
"websiteProdSpinner",
"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
}
sourcePath {
srcDir("src/main/protowire")
}
protoPath {
srcDir("${project.rootDir}/libsignal-service/src/main/protowire")
}
// Handled by libsignal
prune("signalservice.DecryptionErrorMessage")
}
ktlint {
version.set("1.2.1")
}
android {
namespace = "org.thoughtcrime.securesms"
buildToolsVersion = signalBuildToolsVersion
compileSdkVersion = signalCompileSdkVersion
ndkVersion = signalNdkVersion
flavorDimensions += listOf("distribution", "environment")
testBuildType = "instrumentation"
android.bundle.language.enableSplit = false
kotlinOptions {
jvmTarget = signalKotlinJvmTarget
freeCompilerArgs = listOf("-Xjvm-default=all")
suppressWarnings = true
}
keystores["debug"]?.let { properties ->
signingConfigs.getByName("debug").apply {
storeFile = file("${project.rootDir}/${properties.getProperty("storeFile")}")
storePassword = properties.getProperty("storePassword")
keyAlias = properties.getProperty("keyAlias")
keyPassword = properties.getProperty("keyPassword")
}
}
testOptions {
execution = "ANDROIDX_TEST_ORCHESTRATOR"
unitTests {
isIncludeAndroidResources = true
}
managedDevices {
devices {
create<ManagedVirtualDevice>("pixel3api30") {
device = "Pixel 3"
apiLevel = 30
systemImageSource = "google-atd"
require64Bit = false
}
}
}
}
sourceSets {
getByName("test") {
java.srcDir("$projectDir/src/testShared")
}
getByName("androidTest") {
java.srcDir("$projectDir/src/testShared")
}
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = signalJavaVersion
targetCompatibility = signalJavaVersion
}
packaging {
jniLibs {
excludes += setOf(
"**/*.dylib",
"**/*.dll"
)
}
resources {
excludes += setOf(
"LICENSE.txt",
"LICENSE",
"NOTICE",
"asm-license.txt",
"META-INF/LICENSE",
"META-INF/LICENSE.md",
"META-INF/NOTICE",
"META-INF/LICENSE-notice.md",
"META-INF/proguard/androidx-annotations.pro",
"**/*.dylib",
"**/*.dll"
)
}
}
buildFeatures {
buildConfig = true
viewBinding = true
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.4"
}
defaultConfig {
versionCode = (canonicalVersionCode * maxHotfixVersions) + currentHotfixVersion
versionName = canonicalVersionName
minSdk = signalMinSdkVersion
targetSdk = signalTargetSdkVersion
vectorDrawables.useSupportLibrary = true
project.ext.set("archivesBaseName", "Signal")
manifestPlaceholders["mapsKey"] = "AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"
buildConfigField("long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L")
buildConfigField("String", "GIT_HASH", "\"${getGitHash()}\"")
buildConfigField("String", "SIGNAL_URL", "\"https://chat.signal.org\"")
buildConfigField("String", "STORAGE_URL", "\"https://storage.signal.org\"")
buildConfigField("String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\"")
buildConfigField("String", "SIGNAL_CDN2_URL", "\"https://cdn2.signal.org\"")
buildConfigField("String", "SIGNAL_CDN3_URL", "\"https://cdn3.signal.org\"")
buildConfigField("String", "SIGNAL_CDSI_URL", "\"https://cdsi.signal.org\"")
buildConfigField("String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\"")
buildConfigField("String", "SIGNAL_SVR2_URL", "\"https://svr2.signal.org\"")
buildConfigField("String", "SIGNAL_SFU_URL", "\"https://sfu.voip.signal.org\"")
buildConfigField("String", "SIGNAL_STAGING_SFU_URL", "\"https://sfu.staging.voip.signal.org\"")
buildConfigField("String[]", "SIGNAL_SFU_INTERNAL_NAMES", "new String[]{\"Test\", \"Staging\", \"Development\"}")
buildConfigField("String[]", "SIGNAL_SFU_INTERNAL_URLS", "new String[]{\"https://sfu.test.voip.signal.org\", \"https://sfu.staging.voip.signal.org\", \"https://sfu.staging.test.voip.signal.org\"}")
buildConfigField("String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\"")
buildConfigField("int", "CONTENT_PROXY_PORT", "443")
buildConfigField("String[]", "SIGNAL_SERVICE_IPS", rootProject.extra["service_ips"] as String)
buildConfigField("String[]", "SIGNAL_STORAGE_IPS", rootProject.extra["storage_ips"] as String)
buildConfigField("String[]", "SIGNAL_CDN_IPS", rootProject.extra["cdn_ips"] as String)
buildConfigField("String[]", "SIGNAL_CDN2_IPS", rootProject.extra["cdn2_ips"] as String)
buildConfigField("String[]", "SIGNAL_CDN3_IPS", rootProject.extra["cdn3_ips"] as String)
buildConfigField("String[]", "SIGNAL_SFU_IPS", rootProject.extra["sfu_ips"] as String)
buildConfigField("String[]", "SIGNAL_CONTENT_PROXY_IPS", rootProject.extra["content_proxy_ips"] as String)
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", "\"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[]{ ${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\"")
buildConfigField("String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/registration/generate.html\"")
buildConfigField("String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/challenge/generate.html\"")
buildConfigField("org.signal.libsignal.net.Network.Environment", "LIBSIGNAL_NET_ENV", "org.signal.libsignal.net.Network.Environment.PRODUCTION")
buildConfigField("int", "LIBSIGNAL_LOG_LEVEL", "org.signal.libsignal.protocol.logging.SignalProtocolLogger.INFO")
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"unset\"")
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"unset\"")
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"unset\"")
buildConfigField("String", "BADGE_STATIC_ROOT", "\"https://updates2.signal.org/static/badges/\"")
buildConfigField("String", "STRIPE_BASE_URL", "\"https://api.stripe.com/v1\"")
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", "true")
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
}
resourceConfigurations += listOf()
splits {
abi {
isEnable = !project.hasProperty("generateBaselineProfile")
reset()
include("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
isUniversalApk = true
}
}
testInstrumentationRunner = "org.thoughtcrime.securesms.testing.SignalTestRunner"
testInstrumentationRunnerArguments["clearPackageData"] = "true"
}
buildTypes {
getByName("debug") {
if (keystores["debug"] != null) {
signingConfig = signingConfigs["debug"]
}
isDefault = true
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android.txt"),
"proguard/proguard-firebase-messaging.pro",
"proguard/proguard-google-play-services.pro",
"proguard/proguard-jackson.pro",
"proguard/proguard-sqlite.pro",
"proguard/proguard-appcompat-v7.pro",
"proguard/proguard-square-okhttp.pro",
"proguard/proguard-square-okio.pro",
"proguard/proguard-rounded-image-view.pro",
"proguard/proguard-glide.pro",
"proguard/proguard-shortcutbadger.pro",
"proguard/proguard-retrofit.pro",
"proguard/proguard-klinker.pro",
"proguard/proguard-mobilecoin.pro",
"proguard/proguard-retrolambda.pro",
"proguard/proguard-okhttp.pro",
"proguard/proguard-ez-vcard.pro",
"proguard/proguard.cfg"
)
testProguardFiles(
"proguard/proguard-automation.pro",
"proguard/proguard.cfg"
)
manifestPlaceholders["mapsKey"] = getMapsKey()
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Debug\"")
buildConfigField("boolean", "LINK_DEVICE_UX_ENABLED", "true")
}
getByName("release") {
isMinifyEnabled = true
proguardFiles(*buildTypes["debug"].proguardFiles.toTypedArray())
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Release\"")
}
create("instrumentation") {
initWith(getByName("debug"))
isDefault = false
isMinifyEnabled = false
matchingFallbacks += "debug"
applicationIdSuffix = ".instrumentation"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Instrumentation\"")
buildConfigField("String", "STRIPE_BASE_URL", "\"http://127.0.0.1:8080/stripe\"")
}
create("spinner") {
initWith(getByName("debug"))
isDefault = false
isMinifyEnabled = false
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Spinner\"")
}
create("perf") {
initWith(getByName("debug"))
isDefault = false
isDebuggable = false
isMinifyEnabled = true
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Perf\"")
buildConfigField("boolean", "TRACING_ENABLED", "true")
}
create("benchmark") {
initWith(getByName("debug"))
isDefault = false
isDebuggable = false
isMinifyEnabled = true
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Benchmark\"")
buildConfigField("boolean", "TRACING_ENABLED", "true")
}
create("canary") {
initWith(getByName("debug"))
isDefault = false
isMinifyEnabled = false
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Canary\"")
}
}
productFlavors {
create("play") {
dimension = "distribution"
isDefault = true
buildConfigField("boolean", "MANAGES_APP_UPDATES", "false")
buildConfigField("String", "APK_UPDATE_MANIFEST_URL", "null")
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"play\"")
}
create("website") {
dimension = "distribution"
buildConfigField("boolean", "MANAGES_APP_UPDATES", "true")
buildConfigField("String", "APK_UPDATE_MANIFEST_URL", "\"https://updates.signal.org/android/latest.json\"")
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"website\"")
}
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-${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") {
dimension = "environment"
isDefault = true
buildConfigField("String", "MOBILE_COIN_ENVIRONMENT", "\"mainnet\"")
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"Prod\"")
}
create("staging") {
dimension = "environment"
applicationIdSuffix = ".staging"
buildConfigField("String", "SIGNAL_URL", "\"https://chat.staging.signal.org\"")
buildConfigField("String", "STORAGE_URL", "\"https://storage-staging.signal.org\"")
buildConfigField("String", "SIGNAL_CDN_URL", "\"https://cdn-staging.signal.org\"")
buildConfigField("String", "SIGNAL_CDN2_URL", "\"https://cdn2-staging.signal.org\"")
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", "\"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\"")
buildConfigField("String", "BACKUP_SERVER_PUBLIC_PARAMS", "\"AHYrGb9IfugAAJiPKp+mdXUx+OL9zBolPYHYQz6GI1gWjpEu5me3zVNSvmYY4zWboZHif+HG1sDHSuvwFd0QszSwuSF4X4kRP3fJREdTZ5MCR0n55zUppTwfHRW2S4sdQ0JGz7YDQIJCufYSKh0pGNEHL6hv79Agrdnr4momr3oXdnkpVBIp3HWAQ6IbXQVSG18X36GaicI1vdT0UFmTwU2KTneluC2eyL9c5ff8PcmiS+YcLzh0OKYQXB5ZfQ06d6DiINvDQLy75zcfUOniLAj0lGJiHxGczin/RXisKSR8\"")
buildConfigField("String", "MOBILE_COIN_ENVIRONMENT", "\"testnet\"")
buildConfigField("String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/staging/registration/generate.html\"")
buildConfigField("String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/staging/challenge/generate.html\"")
buildConfigField("org.signal.libsignal.net.Network.Environment", "LIBSIGNAL_NET_ENV", "org.signal.libsignal.net.Network.Environment.STAGING")
buildConfigField("int", "LIBSIGNAL_LOG_LEVEL", "org.signal.libsignal.protocol.logging.SignalProtocolLogger.DEBUG")
buildConfigField("boolean", "USE_STRING_ID", "false")
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 {
abortOnError = true
baseline = file("lint-baseline.xml")
checkReleaseBuilds = false
ignoreWarnings = true
quiet = true
disable += "LintError"
}
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 { variant ->
// Include the test-only library on debug builds.
if (variant.buildType != "instrumentation") {
variant.packaging.jniLibs.excludes.add("**/libsignal_jni_testing.so")
}
}
}
val releaseDir = "$projectDir/src/release/java"
val debugDir = "$projectDir/src/debug/java"
android.buildTypes.configureEach {
val path = if (name == "release") releaseDir else debugDir
sourceSets.named(name) {
java.srcDir(path)
}
}
}
dependencies {
lintChecks(project(":lintchecks"))
ktlintRuleset(libs.ktlint.twitter.compose)
coreLibraryDesugaring(libs.android.tools.desugar)
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.appcompat) {
version {
strictly("1.6.1")
}
}
implementation(libs.androidx.window.window)
implementation(libs.androidx.window.java)
implementation(libs.androidx.recyclerview)
implementation(libs.material.material)
implementation(libs.androidx.legacy.support)
implementation(libs.androidx.preference)
implementation(libs.androidx.legacy.preference)
implementation(libs.androidx.gridlayout)
implementation(libs.androidx.exifinterface)
implementation(libs.androidx.compose.rxjava3)
implementation(libs.androidx.compose.runtime.livedata)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.lifecycle.livedata.ktx)
implementation(libs.androidx.lifecycle.process)
implementation(libs.androidx.lifecycle.viewmodel.savedstate)
implementation(libs.androidx.lifecycle.common.java8)
implementation(libs.androidx.lifecycle.reactivestreams.ktx)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.camera.core)
implementation(libs.androidx.camera.camera2)
implementation(libs.androidx.camera.extensions)
implementation(libs.androidx.camera.lifecycle)
implementation(libs.androidx.camera.view)
implementation(libs.androidx.concurrent.futures)
implementation(libs.androidx.autofill)
implementation(libs.androidx.biometric)
implementation(libs.androidx.sharetarget)
implementation(libs.androidx.profileinstaller)
implementation(libs.androidx.asynclayoutinflater)
implementation(libs.androidx.asynclayoutinflater.appcompat)
implementation(libs.androidx.emoji2)
implementation(libs.firebase.messaging) {
exclude(group = "com.google.firebase", module = "firebase-core")
exclude(group = "com.google.firebase", module = "firebase-analytics")
exclude(group = "com.google.firebase", module = "firebase-measurement-connector")
}
implementation(libs.google.play.services.maps)
implementation(libs.google.play.services.auth)
implementation(libs.google.signin)
implementation(libs.bundles.media3)
implementation(libs.conscrypt.android)
implementation(libs.signal.aesgcmprovider)
implementation(libs.libsignal.android)
implementation(libs.mobilecoin)
implementation(libs.signal.ringrtc)
implementation(libs.leolin.shortcutbadger)
implementation(libs.emilsjolander.stickylistheaders)
implementation(libs.glide.glide)
implementation(libs.roundedimageview)
implementation(libs.materialish.progress)
implementation(libs.greenrobot.eventbus)
implementation(libs.google.zxing.android.integration)
implementation(libs.google.zxing.core)
implementation(libs.google.flexbox)
implementation(libs.subsampling.scale.image.view) {
exclude(group = "com.android.support", module = "support-annotations")
}
implementation(libs.android.tooltips) {
exclude(group = "com.android.support", module = "appcompat-v7")
}
implementation(libs.stream)
implementation(libs.lottie)
implementation(libs.lottie.compose)
implementation(libs.signal.android.database.sqlcipher)
implementation(libs.androidx.sqlite)
testImplementation(libs.androidx.sqlite.framework)
implementation(libs.google.ez.vcard) {
exclude(group = "com.fasterxml.jackson.core")
exclude(group = "org.freemarker")
}
implementation(libs.dnsjava)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.accompanist.permissions)
implementation(libs.accompanist.drawablepainter)
implementation(libs.kotlin.stdlib.jdk8)
implementation(libs.kotlin.reflect)
implementation(libs.kotlinx.coroutines.play.services)
implementation(libs.kotlinx.coroutines.rx3)
implementation(libs.jackson.module.kotlin)
implementation(libs.rxjava3.rxandroid)
implementation(libs.rxjava3.rxkotlin)
implementation(libs.rxdogtag)
implementation(libs.androidx.credentials)
implementation(libs.androidx.credentials.compat)
implementation(libs.kotlinx.serialization.json)
implementation(project(":billing"))
"spinnerImplementation"(project(":spinner"))
"canaryImplementation"(libs.square.leakcanary)
"instrumentationImplementation"(libs.androidx.fragment.testing) {
exclude(group = "androidx.test", module = "core")
}
testImplementation(testLibs.junit.junit)
testImplementation(testLibs.assertk)
testImplementation(testLibs.androidx.test.core)
testImplementation(testLibs.robolectric.robolectric) {
exclude(group = "com.google.protobuf", module = "protobuf-java")
}
testImplementation(testLibs.bouncycastle.bcprov.jdk15on) {
version {
strictly("1.70")
}
}
testImplementation(testLibs.bouncycastle.bcpkix.jdk15on) {
version {
strictly("1.70")
}
}
testImplementation(testLibs.conscrypt.openjdk.uber)
testImplementation(testLibs.mockk)
testImplementation(testFixtures(project(":libsignal-service")))
testImplementation(testLibs.espresso.core)
testImplementation(testLibs.kotlinx.coroutines.test)
testImplementation(libs.androidx.compose.ui.test.junit4)
"perfImplementation"(libs.androidx.compose.ui.test.manifest)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
androidTestImplementation(testLibs.androidx.test.ext.junit)
androidTestImplementation(testLibs.espresso.core)
androidTestImplementation(testLibs.androidx.test.core)
androidTestImplementation(testLibs.androidx.test.core.ktx)
androidTestImplementation(testLibs.androidx.test.ext.junit.ktx)
androidTestImplementation(testLibs.assertk)
androidTestImplementation(testLibs.mockk.android)
androidTestImplementation(testLibs.diff.utils)
androidTestUtil(testLibs.androidx.test.orchestrator)
}
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 getCurrentGitTag(): String? {
assertIsGitRepo()
val output = providers.exec {
commandLine("git", "tag", "--points-at", "HEAD")
}.standardOutput.asText.get().trim()
return if (output.isNotEmpty()) {
val tags = output.split("\n").toList()
tags.firstOrNull { it.contains("nightly") } ?: tags[0]
} else {
null
}
}
tasks.withType<Test>().configureEach {
testLogging {
events("failed")
exceptionFormat = TestExceptionFormat.FULL
showCauses = true
showExceptions = true
showStackTraces = true
}
}
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 {
val mapKey = file("${project.rootDir}/maps.key")
return if (mapKey.exists()) {
mapKey.readLines()[0]
} else {
"AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"
}
}
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")
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() }
}

11
app/jni/Android.mk Normal file
View file

@ -0,0 +1,11 @@
JNI_DIR := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := native-utils
LOCAL_C_INCLUDES := $(JNI_DIR)/utils/
LOCAL_CFLAGS += -Wall
LOCAL_SRC_FILES := $(JNI_DIR)/utils/org_thoughtcrime_securesms_util_FileUtils.cpp
include $(BUILD_SHARED_LIBRARY)

6
app/jni/Application.mk Normal file
View file

@ -0,0 +1,6 @@
# Built with NDK 19.2.5345600
APP_ABI := armeabi-v7a x86 arm64-v8a x86_64
APP_PLATFORM := android-19
APP_STL := c++_static
APP_CPPFLAGS += -fexceptions
APP_OPTIM := debug

View file

@ -0,0 +1,45 @@
#include "org_thoughtcrime_securesms_util_FileUtils.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/memfd.h>
#include <syscall.h>
jint JNICALL Java_org_thoughtcrime_securesms_util_FileUtils_getFileDescriptorOwner
(JNIEnv *env, jclass clazz, jobject fileDescriptor)
{
jclass fdClass = env->GetObjectClass(fileDescriptor);
if (fdClass == NULL) {
return -1;
}
jfieldID fdFieldId = env->GetFieldID(fdClass, "descriptor", "I");
if (fdFieldId == NULL) {
return -1;
}
int fd = env->GetIntField(fileDescriptor, fdFieldId);
struct stat stat_struct;
if (fstat(fd, &stat_struct) != 0) {
return -1;
}
return stat_struct.st_uid;
}
JNIEXPORT jint JNICALL Java_org_thoughtcrime_securesms_util_FileUtils_createMemoryFileDescriptor
(JNIEnv *env, jclass clazz, jstring jname)
{
const char *name = env->GetStringUTFChars(jname, NULL);
int fd = syscall(SYS_memfd_create, name, MFD_CLOEXEC);
env->ReleaseStringUTFChars(jname, name);
return fd;
}

View file

@ -0,0 +1,29 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class org_thoughtcrime_securesms_util_FileUtils */
#ifndef _Included_org_thoughtcrime_securesms_util_FileUtils
#define _Included_org_thoughtcrime_securesms_util_FileUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: org_thoughtcrime_securesms_util_FileUtils
* Method: getFileDescriptorOwner
* Signature: (Ljava/io/FileDescriptor;)I
*/
JNIEXPORT jint JNICALL Java_org_thoughtcrime_securesms_util_FileUtils_getFileDescriptorOwner
(JNIEnv *, jclass, jobject);
/*
* Class: org_thoughtcrime_securesms_util_FileUtils
* Method: createMemoryFileDescriptor
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_org_thoughtcrime_securesms_util_FileUtils_createMemoryFileDescriptor
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif

38022
app/lint-baseline.xml Normal file

File diff suppressed because one or more lines are too long

50
app/lint.xml Normal file
View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<!-- Wont pass lint or qa with a STOPSHIP in a comment -->
<issue id="StopShip" severity="fatal" />
<!-- L10N errors -->
<!-- This is a runtime crash so we don't want to ship with this. -->
<issue id="StringFormatMatches" severity="error" />
<!-- L10N warnings -->
<issue id="MissingTranslation" severity="ignore" />
<issue id="MissingQuantity" severity="warning" />
<issue id="MissingDefaultResource" severity="error">
<ignore path="*/res/values-*/strings.xml" /> <!-- Ignore for non-English, excludeNonTranslatables task will remove these -->
</issue>
<issue id="ExtraTranslation" severity="warning" />
<issue id="ImpliedQuantity" severity="warning" />
<issue id="TypographyDashes" severity="error" >
<ignore path="*/res/values-*/strings.xml" /> <!-- Ignore for non-English -->
</issue>
<issue id="CanvasSize" severity="error" />
<issue id="HardcodedText" severity="error" />
<issue id="VectorRaster" severity="error" />
<issue id="ButtonOrder" severity="error" />
<issue id="ExtraTranslation" severity="warning" />
<issue id="UnspecifiedImmutableFlag" severity="error" />
<!-- Custom lints -->
<issue id="LogNotSignal" severity="error" />
<issue id="LogNotAppSignal" severity="error" />
<issue id="LogTagInlined" severity="error" />
<issue id="AlertDialogBuilderUsage" severity="warning" />
<issue id="RestrictedApi" severity="error">
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/VideoCapture.java" />
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/CameraXModule.java" />
<ignore path="*/org/thoughtcrime/securesms/conversation/*.java" />
<ignore path="*/org/thoughtcrime/securesms/lock/v2/CreateKbsPinViewModel.java" />
<ignore path="*/org/thoughtcrime/securesms/jobs/StickerPackDownloadJob.java" />
</issue>
<issue id="OptionalUsedAsFieldOrParameterType" severity="ignore" />
<issue id="SameParameterValue" severity="ignore" />
<!-- Disables check for digital asset linking in manifest. It's not needed, since we are not using CredentialManager for passkey support. -->
<issue id="CredManMissingDal" severity="ignore" />
</lint>

View file

@ -0,0 +1,13 @@
# https://code.google.com/p/android/issues/detail?id=78377
-keepnames class !android.support.v7.internal.view.menu.**, ** { *; }
-keep public class android.support.v7.widget.** { *; }
-keep public class android.support.v7.internal.widget.** { *; }
-keep public class * extends android.support.v4.view.ActionProvider {
public <init>(android.content.Context);
}
-keepattributes *Annotation*
-keep public class * extends android.support.design.widget.CoordinatorLayout.Behavior { *; }
-keep public class * extends android.support.design.widget.ViewOffsetBehavior { *; }

View file

@ -0,0 +1,11 @@
-keepattributes Exceptions
-dontskipnonpubliclibraryclassmembers
-dontwarn android.test.**
-dontwarn com.android.support.test.**
-dontwarn sun.reflect.**
-dontwarn sun.misc.**
-dontwarn assertk.**
-dontwarn com.squareup.**
-dontobfuscate

View file

@ -0,0 +1 @@
-dontwarn ezvcard.io.html.HCardPage

View file

@ -0,0 +1 @@
-dontwarn com.google.firebase.analytics.connector.AnalyticsConnector

View file

@ -0,0 +1,6 @@
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.AppGlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}

View file

@ -0,0 +1,19 @@
## Google Play Services 4.3.23 specific rules ##
## https://developer.android.com/google/play-services/setup.html#Proguard ##
-keep class * extends java.util.ListResourceBundle {
protected Object[][] getContents();
}
-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {
public static final *** NULL;
}
-keepnames @com.google.android.gms.common.annotation.KeepName class *
-keepclassmembernames class * {
@com.google.android.gms.common.annotation.KeepName *;
}
-keepnames class * implements android.os.Parcelable {
public static final ** CREATOR;
}

View file

@ -0,0 +1,12 @@
# Proguard configuration for Jackson 2.x (fasterxml package instead of codehaus package)
-keepattributes *Annotation*,EnclosingMethod,Signature
-keepnames class com.fasterxml.jackson.** {
*;
}
-keepnames interface com.fasterxml.jackson.** {
*;
}
-dontwarn com.fasterxml.jackson.databind.**
-keep class org.codehaus.** { *; }

View file

@ -0,0 +1,3 @@
-dontwarn android.net.ConnectivityManager
-dontwarn android.net.ConnectivityManager$NetworkCallback
-dontwarn org.webrtc.NetworkMonitorAutoDetect$ConnectivityManagerDelegate

View file

@ -0,0 +1,2 @@
# MobileCoin
-keep class com.mobilecoin.** { *; }

View file

@ -0,0 +1,3 @@
-dontwarn okio.**
-dontwarn javax.annotation.Nullable
-dontwarn javax.annotation.ParametersAreNonnullByDefault

View file

@ -0,0 +1,4 @@
-dontwarn retrofit.**
-keep class retrofit.** { *; }
-keepattributes Signature
-keepattributes Exceptions

View file

@ -0,0 +1,2 @@
-dontwarn java.lang.invoke.*
-dontwarn **$$Lambda$*

View file

@ -0,0 +1 @@
-dontwarn com.squareup.picasso.**

View file

@ -0,0 +1 @@
-keep class me.leolin.shortcutbadger.** {*;}

View file

@ -0,0 +1,8 @@
-keep class org.sqlite.** { *; }
-keep class org.sqlite.database.** { *; }
-keep class net.sqlcipher.** { *; }
-dontwarn net.sqlcipher.**
-keep class net.zetetic.** { *; }
-dontwarn net.zetetic.**

View file

@ -0,0 +1,6 @@
# OkHttp
-keepattributes Signature
-keepattributes *Annotation*
-keep class com.squareup.okhttp.** { *; }
-keep interface com.squareup.okhttp.** { *; }
-dontwarn com.squareup.okhttp.**

View file

@ -0,0 +1,5 @@
# Okio
-keep class sun.misc.Unsafe { *; }
-dontwarn java.nio.file.*
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn okio.**

28
app/proguard/proguard.cfg Normal file
View file

@ -0,0 +1,28 @@
-dontoptimize
-dontobfuscate
-keepattributes SourceFile,LineNumberTable
-keep class org.whispersystems.** { *; }
-keep class org.signal.libsignal.net.** { *; }
-keep class org.signal.libsignal.protocol.** { *; }
-keep class org.signal.libsignal.usernames.** { *; }
-keep class org.thoughtcrime.securesms.** { *; }
-keep class org.signal.donations.json.** { *; }
-keepclassmembers class ** {
public void onEvent*(**);
}
# Protobuf lite
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
-keep class androidx.window.** { *; }
-keepclassmembers class * extends androidx.constraintlayout.motion.widget.Key {
public <init>();
}
# AGP generated dont warns
-dontwarn com.android.org.conscrypt.SSLParametersImpl
-dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl
-dontwarn org.slf4j.impl.StaticLoggerBinder
-dontwarn sun.net.spi.nameservice.NameService
-dontwarn sun.net.spi.nameservice.NameServiceDescriptor

View file

@ -0,0 +1,29 @@
{
"data": [
{
"name": "Ottttooooooooo Ocataaaaaaaavius",
"number": "+1 (555) 555-5555",
"label": "Mobile"
},
{
"name": "Victor Von Doom Phd",
"number": "+1 (555) 123-4567",
"label": "Home"
},
{
"name": "Flash Thompson",
"number": "+1 (555) 435-1261",
"label": "Work"
},
{
"name": "Dr. Curtis Connors",
"number": "+1 (555) 992-1567",
"label": "Mobile"
},
{
"name": "Billy Russo",
"number": "+1 (555) 234-1516",
"label": "Mobile"
}
]
}

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