Compare commits

...

124 Commits

Author SHA1 Message Date
Cody Henthorne
9359d56880 Bump version to 8.0.3 2026-02-23 11:42:37 -05:00
Cody Henthorne
3214200188 Update baseline profile. 2026-02-23 11:35:58 -05:00
Cody Henthorne
841ab7f983 Update translations and other static files. 2026-02-23 11:06:18 -05:00
Alex Hart
53b3728432 Update handling for early nav. 2026-02-23 11:13:42 -04:00
Alex Hart
cf9f98efc9 Fix bad behavior when rotating device with message details open. 2026-02-23 10:54:21 -04:00
Alex Hart
b5c666a1f4 Bump version to 8.0.2 2026-02-19 13:18:41 -04:00
Alex Hart
b1954a509c Update baseline profile. 2026-02-19 13:16:06 -04:00
Alex Hart
c2c91cfe42 Update translations and other static files. 2026-02-19 12:51:41 -04:00
Greyson Parrelli
cccbec5744 Only set HDR transcoder flags for HDR content. 2026-02-19 10:58:55 -05:00
Alex Hart
4c89b20fad Fix possible captcha race. 2026-02-19 11:14:14 -04:00
Greyson Parrelli
2328fa3e88 Route video GIF attachments to the GENERIC_TRANSCODE queue. 2026-02-19 09:57:23 -05:00
Greyson Parrelli
e19d4624c1 Fix video transcoding crash caused by premature codec API calls.
Move getParameterDescriptor and setParameters calls to after
configure/start, since they require the codec to be in the
Executing state. Always set KEY_COLOR_TRANSFER_REQUEST in the
format before configure as the primary tone-mapping mechanism.
2026-02-19 09:57:06 -05:00
Alex Hart
345f58ed48 Bump version to 8.0.1 2026-02-18 16:26:31 -04:00
Alex Hart
4c14ce3937 Update translations and other static files. 2026-02-18 16:17:34 -04:00
Alex Hart
82684c0169 Bump version to 8.0.0 2026-02-18 16:03:34 -04:00
Alex Hart
2607328255 Update translations and other static files. 2026-02-18 15:52:21 -04:00
Michelle Tang
484ce3a1da Turn on binary service writes. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
85d5f62301 Fix potential archive sync issue. 2026-02-18 15:48:16 -04:00
jeffrey-signal
b0571f8184 Fix edit group member label placeholder text. 2026-02-18 15:48:16 -04:00
Cody Henthorne
b80dd28b40 Defensively prevent gif playback in background. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
e0cf0808cf PIP moves opposite to finger in RTL mode.
Co-authored-by: Alex Hart <alex@signal.org>
2026-02-18 15:48:16 -04:00
Greyson Parrelli
ffdd5b62ae Remove now-unused colorInfo parsing from video transcoding. 2026-02-18 15:48:16 -04:00
jeffrey-signal
3b5376ef8b Fix mixed-directional text input behavior in recipient search field. 2026-02-18 15:48:16 -04:00
jeffrey-signal
cd57fb0d76 Fix member label pill overflow caused by excessive combining marks. 2026-02-18 15:48:16 -04:00
adel-signal
6986acd6f4 Update RingRTC to 2.65.0
Co-authored-by: emir-signal <emir@signal.org>
Co-authored-by: Cody Henthorne <cody@signal.org>
2026-02-18 15:48:16 -04:00
Greyson Parrelli
2bc571ffd3 Possible fix for some transcoding issues. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
a8dddf33f8 Media needs to be tapped on twice for it to load during call.
Fixes #14581
Fixes #14592
2026-02-18 15:48:16 -04:00
Greyson Parrelli
46582a685b Fix weird screen transition when accepting an incoming call.
Co-authored-by: Alex Hart <alex@signal.org>
2026-02-18 15:48:16 -04:00
Greyson Parrelli
ad381783f7 Gifs stop or lower volume of other media playing.
Fixes #14297

Co-authored-by: Alex Hart <alex@signal.org>
2026-02-18 15:48:16 -04:00
jeffrey-signal
b81c1eb65c Fix crash when selecting a group to add a recipient. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
2c4d3b3ee4 Fix deleted attachment still visible under 'Shared media'. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
d1400928ce Slide in country picker vertically in regV5. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
49abece92b Includes quotes of your messages in 'notify for mentions' setting.
Fixes #14595
2026-02-18 15:48:16 -04:00
Greyson Parrelli
b48b1f031e Fix gradle format task. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
9cefe0bc04 Allow importing a backup as part of quickstart. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
ee73b0e229 Allow reading quickstart credentials from disk. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
ab0ce58812 Fix find-by-username text field using wrong background color.
The TextField was not explicitly setting container colors, causing it
to fall back to Material 3 defaults instead of the app's surfaceVariant
color. Added focusedContainerColor, unfocusedContainerColor, and
disabledContainerColor to match the pattern used throughout the rest
of the codebase.
2026-02-18 15:48:16 -04:00
Greyson Parrelli
333a206d36 Fix issue where emoji search bar got weird after tab switching. 2026-02-18 15:48:16 -04:00
Michelle Tang
86bb7666ea Fix unpinning all in note to self. 2026-02-18 15:48:16 -04:00
Cody Henthorne
58b5ebf39d Allow SendDeliveryReceiptJob to run during benchmarking tests. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
47947b85c7 Fix note to self media delete options. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
6910ba6d2e Ignore storageId in Recipient.hasSameContent().
Was potentially causing unnecessary UI churn.
2026-02-18 15:48:16 -04:00
Cody Henthorne
08254edae6 Add incoming group message benchmark tests. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
e67307a961 Add quickstart variant that launches with predefined credentials. 2026-02-18 15:48:16 -04:00
Cody Henthorne
9922621945 Add incoming individual message benchmark tests. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
c7476a2a07 Mute in lobby if accepting large group call. 2026-02-18 15:48:16 -04:00
Alex Hart
ac59528f5c Enable call vanity when joining a video call. 2026-02-18 15:48:16 -04:00
Alex Hart
97c9728c65 Resolve crash in blur container due to multiple measures. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
80d1694e6e Ensure data is updated when refreshing contacts during a search.
Fixes #14112
2026-02-18 15:48:16 -04:00
Greyson Parrelli
28c6e31c7d Removing a recipient does not immediately remove them from the contacts list. 2026-02-18 15:48:16 -04:00
Cody Henthorne
8836b2a570 Fix media send delayed after backgrounding app. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
786c2b888b Remove beta labeling from Signal Secure Backups. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
c91275c5da Add the country code picker to regV5. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
7b362460e7 Spruce up the welcome and permission screens in regV5. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
a1862c3420 Sticker from Google emoji keyboard renders as image attachment when forwarded. 2026-02-18 15:48:16 -04:00
jeffrey-signal
44ea9ccc59 Fix unique constraint violation when importing groups with duplicate recipient IDs. 2026-02-18 15:48:16 -04:00
andrew-signal
4c9cdf3b8f Refactor RefreshOwnProfileJob to Kotlin. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
4a6d4f197d Add 429 handling to various archive calls. 2026-02-18 15:48:16 -04:00
Greyson Parrelli
ae04749336 Add better logging for ovesized messages. 2026-02-18 15:48:16 -04:00
Cody Henthorne
caa743aba2 Fix foreground service start in background crash by starting service sooner for incoming group calls. 2026-02-18 15:48:16 -04:00
Alex Hart
a4469a4285 Fix issue with initial audio output setting.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2026-02-18 15:48:08 -04:00
Alex Hart
2771b31aab Clear stale user-selected audio device when it disconnects.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2026-02-18 15:48:06 -04:00
Greyson Parrelli
5c418a4260 Add RRP support to regV5. 2026-02-18 15:48:06 -04:00
Alex Hart
17dbdf3b74 Bump version to 7.74.3 2026-02-18 11:38:55 -04:00
Alex Hart
8315ae47c4 Update translations and other static files. 2026-02-18 11:31:14 -04:00
Michelle Tang
50b59805ca Add more binary service reads. 2026-02-18 10:13:41 -05:00
Alex Hart
58bc387d0b Bump version to 7.74.2 2026-02-17 23:18:40 -04:00
Alex Hart
d2dd6790a0 Update translations and other static files. 2026-02-17 23:16:23 -04:00
jeffrey-signal
78c7795b89 Fix sender name/label overflowing beyond message bubble media width. 2026-02-17 22:43:06 -04:00
jeffrey-signal
73c25f3476 Ensure consistent margins below sender name/label. 2026-02-17 18:17:36 -05:00
jeffrey-signal
6f9f89734d Fix sender name/label overflowing beyond message bubble media width. 2026-02-17 16:10:33 -05:00
Greyson Parrelli
da68bee742 Fix issue where you can't download media in calls. 2026-02-17 11:44:28 -05:00
jeffrey-signal
e5f1f9394d Fix sender name alignment in conversations. 2026-02-17 11:37:07 -05:00
Michelle Tang
d9d6f8c97e Bump version to 7.74.1 2026-02-16 16:33:31 -05:00
Michelle Tang
a35a141411 Update translations and other static files. 2026-02-16 16:18:38 -05:00
Michelle Tang
1e0c7b1120 Fix recipient call crash. 2026-02-16 16:09:39 -05:00
Michelle Tang
9b9734f82a Bump version to 7.74.0 2026-02-11 18:24:13 -05:00
Michelle Tang
fe437b5234 Update translations and other static files. 2026-02-11 18:14:12 -05:00
jeffrey-signal
59bb505a3e Support member labels in backups. 2026-02-11 18:08:57 -05:00
jeffrey-signal
28d8d62cbd Rotate receive member labels flag. 2026-02-11 18:08:57 -05:00
jeffrey-signal
cb05608422 Show member labels in quotes. 2026-02-11 18:08:57 -05:00
Michelle Tang
e384a37fab Fix beta label width. 2026-02-11 18:08:57 -05:00
Michelle Tang
9a04cd9e3b Move education sheet to safety numbers screen. 2026-02-11 18:08:57 -05:00
Cody Henthorne
dd396eb75a Fix group updates from others showing before messages made before the update. 2026-02-11 18:08:57 -05:00
Greyson Parrelli
611b52780e Fix sticker grid sometimes getting stuck on 2 columns. 2026-02-11 18:08:57 -05:00
Michelle Tang
20a05220ea Adjust strings. 2026-02-11 18:08:57 -05:00
Greyson Parrelli
38e8f24c20 Fix gif and stickers buttons disappearing after exiting edit. 2026-02-11 18:08:57 -05:00
Alex Hart
58d2c92102 Move the rest of the permissions classes. 2026-02-11 18:08:57 -05:00
Greyson Parrelli
f90ba45940 Make pinned chat limit remote configurable. 2026-02-11 18:08:57 -05:00
Tom Ritter
1ecf42bfd3 Fix the message edited label not always appearing.
Closes signalapp/Signal-Android#14582
2026-02-11 18:08:57 -05:00
Greyson Parrelli
ed56c21e5b Fix bug where image wasn't showing after send. 2026-02-11 18:08:57 -05:00
Greyson Parrelli
a0c55baf39 Show whose safety number changed in group chat summaries. 2026-02-10 11:34:24 -05:00
Greyson Parrelli
3b3ef0d545 Allow some context menu actions in search. 2026-02-10 11:30:05 -05:00
jeffrey-signal
78e7f99344 Add group member labels to conversation items. 2026-02-10 10:35:54 -05:00
jeffrey-signal
d709d67f54 Fix position of clearable text field remaining characters count. 2026-02-10 10:35:54 -05:00
jeffrey-signal
6d30fd11a7 Add description to edit member label screen. 2026-02-10 10:35:54 -05:00
Cody Henthorne
4a39c7950f Split performance benchmark and baseline profile into separate modules. 2026-02-10 10:35:54 -05:00
Greyson Parrelli
3dd5ad2a8a Fix video scrubber contrast with HDR video.
Co-authored-by: Milan Stevanovic <milan@signal.org>
2026-02-10 10:35:54 -05:00
jeffrey-signal
d7b7727aa6 Show member labels in conversation settings. 2026-02-10 10:35:54 -05:00
Cody Henthorne
0199cd24ef Remove proto files from APK. 2026-02-10 10:35:54 -05:00
jeffrey-signal
8513e8c4f9 Fix donation learn more sheet icon colors. 2026-02-10 10:35:54 -05:00
Greyson Parrelli
5d2d9017f1 Fix for some HDR transcoding issues.
Co-authored-by: Milan Stevanovic <milan@signal.org>
2026-02-10 10:35:54 -05:00
Alex Hart
879e8f98bd Ensure lint configuration is applied to all modules. 2026-02-10 10:35:54 -05:00
Greyson Parrelli
2c6524f6c0 Include battery info debuglog. 2026-02-10 10:35:54 -05:00
Greyson Parrelli
c9bd81d332 Show linked deviceId's for internal users. 2026-02-10 10:35:54 -05:00
Greyson Parrelli
2d29b02cea Add new sticker packs.
- My Daily Life 2
- Rocky Talk
- Cozy Season
- Chug the Mouse
- Croco's Feelings as blessed sticker packs
2026-02-10 10:35:54 -05:00
andrew-signal
c0a279fcc5 Bump to libsignal v0.87.1 2026-02-10 10:35:54 -05:00
Greyson Parrelli
1a612fab0b Update video demo app to minSdk 26. 2026-02-10 10:35:54 -05:00
Michelle Tang
63e821634a Adjust strings. 2026-02-10 10:35:54 -05:00
Alex Hart
62d951b438 Move additional fragments to core UI. 2026-02-10 10:35:54 -05:00
Greyson Parrelli
8d749c404f Refactor and improve video demo app. 2026-02-10 10:35:54 -05:00
jeffrey-signal
d5b2f4fdd3 Display member label on recipient details sheet. 2026-02-10 10:35:54 -05:00
Alex Hart
fae4ca91bd Ensure spans are accounted for when measuring text. 2026-02-10 10:35:54 -05:00
Alex Hart
6e92ff5096 Move fragments to core UI. 2026-02-10 10:35:54 -05:00
jeffrey-signal
61522cd682 Fix GroupTable.memberLabel() to return null for blank labels. 2026-02-10 10:35:54 -05:00
jeffrey-signal
74dbd0814a Add comment to clarify intentional fall-through in DeviceTransferSetUpFragment switch statement.
Resolves signalapp/Signal-Android##14437
2026-02-10 10:35:54 -05:00
Alex Hart
05751a5b79 Remove app module SignalTheme. 2026-02-10 10:35:54 -05:00
Alex Hart
7741844055 Add audio-indicator slot to use for single-remote-participant calls. 2026-02-10 10:35:54 -05:00
Cody Henthorne
09c07f0707 Fix benchmark tests. 2026-02-10 10:35:54 -05:00
Alex Hart
c565db812e Fix back button intermittently not working in conversation view.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2026-02-10 10:35:54 -05:00
Greyson Parrelli
00b72c9263 Clean up system for blessed sticker packs. 2026-02-10 10:35:54 -05:00
Alex Hart
5c415139fd Reshape entry point for V3 media screens. 2026-02-10 10:35:54 -05:00
jeffrey-signal
6d944c0f8c Add tests for MemberLabelViewModel. 2026-02-10 10:35:53 -05:00
Michelle Tang
6744a79325 Avoid showing KT for note to self. 2026-02-10 10:35:53 -05:00
1044 changed files with 115105 additions and 42180 deletions

View File

@@ -1,17 +1,7 @@
@file:Suppress("UnstableApiUsage")
import com.android.build.api.dsl.ManagedVirtualDevice
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ValueSource
import org.gradle.api.provider.ValueSourceParameters
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import java.io.File
import java.util.Properties
plugins {
@@ -20,6 +10,7 @@ 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")
@@ -29,8 +20,8 @@ plugins {
apply(from = "static-ips.gradle.kts")
val canonicalVersionCode = 1647
val canonicalVersionName = "7.73.2"
val canonicalVersionCode = 1655
val canonicalVersionName = "8.0.3"
val currentHotfixVersion = 0
val maxHotfixVersions = 100
@@ -49,6 +40,14 @@ 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 selectableVariants = listOf(
"nightlyBackupRelease",
"nightlyBackupSpinner",
@@ -60,6 +59,8 @@ val selectableVariants = listOf(
"playProdSpinner",
"playProdCanary",
"playProdPerf",
"playProdMocked",
"playProdNonMinifiedMocked",
"playProdBenchmark",
"playProdInstrumentation",
"playProdRelease",
@@ -69,6 +70,8 @@ val selectableVariants = listOf(
"playStagingPerf",
"playStagingInstrumentation",
"playStagingRelease",
"playProdQuickstart",
"playStagingQuickstart",
"websiteProdSpinner",
"websiteProdRelease",
"githubProdSpinner",
@@ -176,7 +179,8 @@ android {
"META-INF/LICENSE-notice.md",
"META-INF/proguard/androidx-annotations.pro",
"**/*.dylib",
"**/*.dll"
"**/*.dll",
"**/*.proto"
)
}
}
@@ -255,7 +259,7 @@ 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", "true")
buildConfigField("boolean", "USE_STRING_ID", "false")
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
@@ -354,8 +358,25 @@ 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") {
@@ -365,6 +386,14 @@ 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 {
@@ -431,7 +460,6 @@ android {
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\"")
@@ -456,6 +484,7 @@ android {
ignoreWarnings = true
quiet = true
disable += "LintError"
lintConfig = rootProject.file("lint.xml")
}
androidComponents {
@@ -493,6 +522,24 @@ android {
}
}
}
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 }
}
}
val releaseDir = "$projectDir/src/release/java"
@@ -505,6 +552,18 @@ android {
}
}
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) {
@@ -514,6 +573,20 @@ android {
}
}
baselineProfile {
warnings {
disabledVariants = false
}
mergeIntoMain = true
variants.create("mocked") {
from(project(":baseline-profile"))
}
dexLayoutOptimization = false
}
dependencies {
lintChecks(project(":lintchecks"))
ktlintRuleset(libs.ktlint.twitter.compose)
@@ -537,6 +610,7 @@ dependencies {
implementation(project(":core:models"))
implementation(project(":core:models-jvm"))
implementation(project(":feature:camera"))
implementation(project(":feature:registration"))
implementation(libs.androidx.fragment.ktx)
implementation(libs.androidx.appcompat) {
@@ -791,3 +865,38 @@ abstract class PropertiesFileValueSource : ValueSource<Properties?, PropertiesFi
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)
}
}

View File

@@ -18,7 +18,6 @@ import com.bumptech.glide.RequestManager
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.signal.ringrtc.CallLinkEpoch
import org.signal.ringrtc.CallLinkRootKey
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState
import org.thoughtcrime.securesms.contactshare.Contact
@@ -329,7 +328,7 @@ class V2ConversationItemShapeTest {
override fun onShowGroupDescriptionClicked(groupName: String, description: String, shouldLinkifyWebLinks: Boolean) = Unit
override fun onJoinCallLink(callLinkRootKey: CallLinkRootKey, callLinkEpoch: CallLinkEpoch?) = Unit
override fun onJoinCallLink(callLinkRootKey: CallLinkRootKey) = Unit
override fun onItemClick(item: MultiselectPart?) = Unit

View File

@@ -6,9 +6,9 @@
package org.thoughtcrime.securesms.database
import org.signal.core.util.Base64
import org.signal.core.util.Util
import org.thoughtcrime.securesms.attachments.AttachmentId
import org.thoughtcrime.securesms.attachments.Cdn
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.attachment.AttachmentUploadResult
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
import kotlin.random.Random

View File

@@ -14,6 +14,7 @@ import org.junit.runner.RunWith
import org.signal.core.models.ServiceId
import org.signal.core.models.media.TransformProperties
import org.signal.core.util.Base64
import org.signal.core.util.Util
import org.signal.core.util.readFully
import org.signal.core.util.stream.LimitedInputStream
import org.signal.core.util.update
@@ -28,7 +29,6 @@ import org.thoughtcrime.securesms.mms.SentMediaQuality
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream
import java.io.File
import java.util.UUID

View File

@@ -81,8 +81,7 @@ class CallLinkTableTest {
roomId = CallLinkRoomId.fromBytes(roomId),
credentials = CallLinkCredentials(
linkKeyBytes = roomId,
adminPassBytes = null,
epochBytes = null
adminPassBytes = null
),
state = SignalCallLinkState(),
deletionTimestamp = 0L

View File

@@ -18,6 +18,7 @@ import org.signal.core.models.ServiceId.ACI
import org.signal.core.models.ServiceId.PNI
import org.signal.core.util.Base64
import org.signal.core.util.SqlUtil
import org.signal.core.util.Util
import org.signal.core.util.exists
import org.signal.core.util.orNull
import org.signal.core.util.readToSingleBoolean
@@ -43,7 +44,6 @@ import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.Util
import java.util.Optional
import java.util.UUID

View File

@@ -21,6 +21,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.Base64
import org.signal.core.util.Util
import org.signal.core.util.logging.Log
import org.signal.core.util.update
import org.signal.core.util.withinTransaction
@@ -36,7 +37,6 @@ import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.MessageContentFuzzer.DeleteForMeSync
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.util.IdentityUtil
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.attachment.AttachmentUploadResult
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
import java.util.UUID

View File

@@ -17,11 +17,13 @@ import kotlin.random.Random
* Helper methods for creating groups for message processing tests et al.
*/
object GroupTestingUtils {
fun member(aci: ACI, revision: Int = 0, role: Member.Role = Member.Role.ADMINISTRATOR): DecryptedMember {
fun member(aci: ACI, revision: Int = 0, role: Member.Role = Member.Role.ADMINISTRATOR, labelEmoji: String = "", labelString: String = ""): DecryptedMember {
return DecryptedMember.Builder()
.aciBytes(aci.toByteString())
.joinedAtRevision(revision)
.role(role)
.labelEmoji(labelEmoji)
.labelString(labelString)
.build()
}

View File

@@ -11,6 +11,7 @@ import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.runBlocking
import org.junit.rules.ExternalResource
import org.signal.core.models.ServiceId.ACI
import org.signal.core.util.Util
import org.signal.libsignal.protocol.IdentityKey
import org.signal.libsignal.protocol.IdentityKeyPair
import org.signal.libsignal.protocol.SignalProtocolAddress
@@ -32,7 +33,6 @@ import org.thoughtcrime.securesms.registration.data.RegistrationData
import org.thoughtcrime.securesms.registration.data.RegistrationRepository
import org.thoughtcrime.securesms.registration.util.RegistrationUtil
import org.thoughtcrime.securesms.testing.GroupTestingUtils.asMember
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import java.util.UUID

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<profileable android:shell="true" />
<activity android:name="org.signal.benchmark.BenchmarkSetupActivity"
android:launchMode="singleTask"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden"
android:exported="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
</application>
</manifest>

View File

@@ -0,0 +1,167 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms
import android.app.Application
import org.signal.libsignal.net.Network
import org.thoughtcrime.securesms.database.JobDatabase
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.JobManager
import org.thoughtcrime.securesms.jobmanager.JobMigrator
import org.thoughtcrime.securesms.jobmanager.impl.FactoryJobPredicate
import org.thoughtcrime.securesms.jobs.AccountConsistencyWorkerJob
import org.thoughtcrime.securesms.jobs.ArchiveBackupIdReservationJob
import org.thoughtcrime.securesms.jobs.AttachmentCompressionJob
import org.thoughtcrime.securesms.jobs.AttachmentUploadJob
import org.thoughtcrime.securesms.jobs.CreateReleaseChannelJob
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob
import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob
import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob
import org.thoughtcrime.securesms.jobs.FastJobStorage
import org.thoughtcrime.securesms.jobs.FontDownloaderJob
import org.thoughtcrime.securesms.jobs.GroupCallUpdateSendJob
import org.thoughtcrime.securesms.jobs.GroupRingCleanupJob
import org.thoughtcrime.securesms.jobs.GroupV2UpdateSelfProfileKeyJob
import org.thoughtcrime.securesms.jobs.IndividualSendJob
import org.thoughtcrime.securesms.jobs.JobManagerFactories
import org.thoughtcrime.securesms.jobs.LinkedDeviceInactiveCheckJob
import org.thoughtcrime.securesms.jobs.MarkerJob
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob
import org.thoughtcrime.securesms.jobs.PostRegistrationBackupRedemptionJob
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob
import org.thoughtcrime.securesms.jobs.ProfileUploadJob
import org.thoughtcrime.securesms.jobs.PushGroupSendJob
import org.thoughtcrime.securesms.jobs.PushProcessMessageJob
import org.thoughtcrime.securesms.jobs.ReactionSendJob
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
import org.thoughtcrime.securesms.jobs.RetrieveRemoteAnnouncementsJob
import org.thoughtcrime.securesms.jobs.RotateCertificateJob
import org.thoughtcrime.securesms.jobs.SendDeliveryReceiptJob
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob
import org.thoughtcrime.securesms.jobs.StorageSyncJob
import org.thoughtcrime.securesms.jobs.StoryOnboardingDownloadJob
import org.thoughtcrime.securesms.jobs.TypingSendJob
import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.util.UptimeSleepTimer
import org.whispersystems.signalservice.api.websocket.SignalWebSocket
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
import org.whispersystems.signalservice.internal.websocket.BenchmarkWebSocketConnection
import java.util.function.Supplier
import kotlin.time.Duration.Companion.seconds
class BenchmarkApplicationContext : ApplicationContext() {
override fun initializeAppDependencies() {
AppDependencies.init(this, BenchmarkDependencyProvider(this, ApplicationDependencyProvider(this)))
DeviceTransferBlockingInterceptor.getInstance().blockNetwork()
}
override fun onForeground() = Unit
class BenchmarkDependencyProvider(val application: Application, private val default: ApplicationDependencyProvider) : AppDependencies.Provider by default {
override fun provideAuthWebSocket(
signalServiceConfigurationSupplier: Supplier<SignalServiceConfiguration>,
libSignalNetworkSupplier: Supplier<Network>
): SignalWebSocket.AuthenticatedWebSocket {
return SignalWebSocket.AuthenticatedWebSocket(
connectionFactory = { BenchmarkWebSocketConnection.createAuthInstance() },
canConnect = { true },
sleepTimer = UptimeSleepTimer(),
disconnectTimeoutMs = 15.seconds.inWholeMilliseconds
)
}
override fun provideUnauthWebSocket(
signalServiceConfigurationSupplier: Supplier<SignalServiceConfiguration>,
libSignalNetworkSupplier: Supplier<Network>
): SignalWebSocket.UnauthenticatedWebSocket {
return SignalWebSocket.UnauthenticatedWebSocket(
connectionFactory = { BenchmarkWebSocketConnection.createUnauthInstance() },
canConnect = { true },
sleepTimer = UptimeSleepTimer(),
disconnectTimeoutMs = 15.seconds.inWholeMilliseconds
)
}
override fun provideJobManager(): JobManager {
val config = JobManager.Configuration.Builder()
.setJobFactories(filterJobFactories(JobManagerFactories.getJobFactories(application)))
.setConstraintFactories(JobManagerFactories.getConstraintFactories(application))
.setConstraintObservers(JobManagerFactories.getConstraintObservers(application))
.setJobStorage(FastJobStorage(JobDatabase.getInstance(application)))
.setJobMigrator(JobMigrator(TextSecurePreferences.getJobManagerVersion(application), JobManager.CURRENT_VERSION, JobManagerFactories.getJobMigrations(application)))
.addReservedJobRunner(FactoryJobPredicate(PushProcessMessageJob.KEY, MarkerJob.KEY))
.addReservedJobRunner(FactoryJobPredicate(AttachmentUploadJob.KEY, AttachmentCompressionJob.KEY))
.addReservedJobRunner(
FactoryJobPredicate(
IndividualSendJob.KEY,
PushGroupSendJob.KEY,
ReactionSendJob.KEY,
TypingSendJob.KEY,
GroupCallUpdateSendJob.KEY,
SendDeliveryReceiptJob.KEY
)
)
.build()
return JobManager(application, config)
}
private fun filterJobFactories(jobFactories: Map<String, Job.Factory<*>>): Map<String, Job.Factory<*>> {
val blockedJobs = setOf(
AccountConsistencyWorkerJob.KEY,
ArchiveBackupIdReservationJob.KEY,
CreateReleaseChannelJob.KEY,
DirectoryRefreshJob.KEY,
DownloadLatestEmojiDataJob.KEY,
EmojiSearchIndexDownloadJob.KEY,
FontDownloaderJob.KEY,
GroupRingCleanupJob.KEY,
GroupV2UpdateSelfProfileKeyJob.KEY,
LinkedDeviceInactiveCheckJob.KEY,
MultiDeviceProfileKeyUpdateJob.KEY,
PostRegistrationBackupRedemptionJob.KEY,
PreKeysSyncJob.KEY,
ProfileUploadJob.KEY,
RefreshAttributesJob.KEY,
RetrieveRemoteAnnouncementsJob.KEY,
RotateCertificateJob.KEY,
StickerPackDownloadJob.KEY,
StorageSyncJob.KEY,
StoryOnboardingDownloadJob.KEY
)
return jobFactories.mapValues {
if (it.key in blockedJobs) {
NoOpJob.Factory()
} else {
it.value
}
}
}
}
private class NoOpJob(parameters: Parameters) : Job(parameters) {
companion object {
const val KEY = "NoOpJob"
}
override fun serialize(): ByteArray? = null
override fun getFactoryKey(): String = KEY
override fun run(): Result = Result.success()
override fun onFailure() = Unit
class Factory : Job.Factory<NoOpJob> {
override fun create(parameters: Parameters, serializedData: ByteArray?): NoOpJob {
return NoOpJob(parameters)
}
}
}
}

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:name="${applicationClass}"
tools:replace="name">
<profileable android:shell="true" />
<activity
android:name="org.signal.benchmark.BenchmarkSetupActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="true"
android:launchMode="singleTask"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden" />
<receiver
android:name="org.signal.benchmark.BenchmarkCommandReceiver"
android:exported="true">
<intent-filter>
<action android:name="org.signal.benchmark.action.COMMAND" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.benchmark
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.signal.benchmark.setup.Generator
import org.signal.benchmark.setup.Harness
import org.signal.core.util.ThreadUtil
import org.signal.core.util.logging.Log
import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.websocket.BenchmarkWebSocketConnection
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage
import kotlin.random.Random
/**
* A BroadcastReceiver that accepts commands sent from the benchmark app to perform
* background operations on the client.
*/
class BenchmarkCommandReceiver : BroadcastReceiver() {
companion object {
private val TAG = Log.tag(BenchmarkCommandReceiver::class)
const val ACTION_COMMAND = "org.signal.benchmark.action.COMMAND"
const val EXTRA_COMMAND = "command"
}
override fun onReceive(context: Context, intent: Intent) {
if (intent.action != ACTION_COMMAND) {
Log.w(TAG, "Ignoring unknown action: ${intent.action}")
return
}
val command = intent.getStringExtra(EXTRA_COMMAND)
Log.i(TAG, "Received command: $command")
when (command) {
"individual-send" -> handlePrepareIndividualSend()
"group-send" -> handlePrepareGroupSend()
"release-messages" -> {
BenchmarkWebSocketConnection.authInstance.startWholeBatchTrace = true
BenchmarkWebSocketConnection.authInstance.releaseMessages()
}
else -> Log.w(TAG, "Unknown command: $command")
}
}
private fun handlePrepareIndividualSend() {
val client = Harness.otherClients[0]
// Send message from Bob to Self
val encryptedEnvelope = client.encrypt(Generator.encryptedTextMessage(System.currentTimeMillis()))
runBlocking {
launch(Dispatchers.IO) {
BenchmarkWebSocketConnection.authInstance.run {
Log.i(TAG, "Sending initial message form Bob to establish session.")
addPendingMessages(listOf(encryptedEnvelope.toWebSocketPayload()))
releaseMessages()
// Sleep briefly to let the message be processed.
ThreadUtil.sleep(100)
}
}
}
// Have Bob generate N messages that will be received by Alice
val messageCount = 100
val envelopes = client.generateInboundEnvelopes(messageCount)
val messages = envelopes.map { e -> e.toWebSocketPayload() }
BenchmarkWebSocketConnection.authInstance.addPendingMessages(messages)
BenchmarkWebSocketConnection.authInstance.addQueueEmptyMessage()
}
private fun handlePrepareGroupSend() {
val clients = Harness.otherClients.take(5)
// Send message from others to Self in the group
val encryptedEnvelopes = clients.map { it.encrypt(Generator.encryptedTextMessage(System.currentTimeMillis(), groupMasterKey = Harness.groupMasterKey)) }
runBlocking {
launch(Dispatchers.IO) {
BenchmarkWebSocketConnection.authInstance.run {
Log.i(TAG, "Sending initial group messages from client to establish sessions.")
addPendingMessages(encryptedEnvelopes.map { it.toWebSocketPayload() })
releaseMessages()
// Sleep briefly to let the messages be processed.
ThreadUtil.sleep(1000)
}
}
}
// Have clients generate N group messages that will be received by Alice
clients.forEach { client ->
val messageCount = 100
val envelopes = client.generateInboundGroupEnvelopes(messageCount, Harness.groupMasterKey)
val messages = envelopes.map { e -> e.toWebSocketPayload() }
BenchmarkWebSocketConnection.authInstance.addPendingMessages(messages)
}
BenchmarkWebSocketConnection.authInstance.addQueueEmptyMessage()
}
private fun Envelope.toWebSocketPayload(): WebSocketRequestMessage {
return WebSocketRequestMessage(
verb = "PUT",
path = "/api/v1/message",
id = Random.nextLong(),
headers = listOf("X-Signal-Timestamp: ${this.timestamp}"),
body = this.encodeByteString()
)
}
}

View File

@@ -15,6 +15,8 @@ class BenchmarkSetupActivity : BaseActivity() {
when (intent.extras!!.getString("setup-type")) {
"cold-start" -> setupColdStart()
"conversation-open" -> setupConversationOpen()
"message-send" -> setupMessageSend()
"group-message-send" -> setupGroupMessageSend()
}
val textView: TextView = TextView(this).apply {
@@ -32,6 +34,8 @@ class BenchmarkSetupActivity : BaseActivity() {
TestMessages.insertIncomingImageMessage(other = recipient, attachmentCount = 1)
TestMessages.insertIncomingImageMessage(other = recipient, attachmentCount = 2, body = "Album")
TestMessages.insertIncomingImageMessage(other = recipient, body = "Test", attachmentCount = 1, failed = true)
TestMessages.insertIncomingTextMessage(other = recipient, body = "Signal message")
TestMessages.insertIncomingTextMessage(other = recipient, body = "Test")
SignalDatabase.messages.setAllMessagesRead()
@@ -54,4 +58,14 @@ class BenchmarkSetupActivity : BaseActivity() {
SignalDatabase.threads.update(SignalDatabase.threads.getOrCreateThreadIdFor(recipient = recipient), true)
}
}
private fun setupMessageSend() {
TestUsers.setupSelf()
TestUsers.setupTestClients(1)
}
private fun setupGroupMessageSend() {
TestUsers.setupSelf()
TestUsers.setupGroup()
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.benchmark.setup
import okio.ByteString.Companion.toByteString
import org.signal.core.models.ServiceId
import org.signal.core.util.Base64
import org.signal.core.util.toByteArray
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith
import org.whispersystems.signalservice.api.crypto.ContentHint
import org.whispersystems.signalservice.api.crypto.EnvelopeContent
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.DataMessage
import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.push.GroupContextV2
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage
import java.util.Optional
import java.util.UUID
object Generator {
fun encryptedTextMessage(
now: Long,
message: String = "Test message",
groupMasterKey: GroupMasterKey? = null
): EnvelopeContent {
val content = Content.Builder().apply {
dataMessage(
DataMessage.Builder().buildWith {
body = message
timestamp = now
if (groupMasterKey != null) {
groupV2 = GroupContextV2.Builder().buildWith {
masterKey = groupMasterKey.serialize().toByteString()
revision = 1
}
}
}
)
}
return EnvelopeContent.encrypted(content.build(), ContentHint.RESENDABLE, Optional.empty())
}
fun OutgoingPushMessage.toEnvelope(timestamp: Long, destination: ServiceId): Envelope {
val serverGuid = UUID.randomUUID()
return Envelope.Builder()
.type(Envelope.Type.fromValue(this.type))
.sourceDevice(1)
.timestamp(timestamp)
.serverTimestamp(timestamp + 1)
.destinationServiceId(destination.toString())
.destinationServiceIdBinary(destination.toByteString())
.serverGuid(serverGuid.toString())
.serverGuidBinary(serverGuid.toByteArray().toByteString())
.content(Base64.decode(this.content).toByteString())
.urgent(true)
.story(false)
.build()
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.benchmark.setup
import org.signal.core.models.ServiceId.ACI
import org.signal.core.util.Base64
import org.signal.core.util.Hex
import org.signal.core.util.UuidUtil
import org.signal.libsignal.metadata.certificate.SenderCertificate
import org.signal.libsignal.metadata.certificate.ServerCertificate
import org.signal.libsignal.protocol.IdentityKeyPair
import org.signal.libsignal.protocol.ecc.ECKeyPair
import org.signal.libsignal.protocol.ecc.ECPrivateKey
import org.signal.libsignal.protocol.ecc.ECPublicKey
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import java.util.Optional
import java.util.UUID
import kotlin.random.Random
import kotlin.time.Duration
object Harness {
const val SELF_E164 = "+15555559999"
val SELF_ACI = ACI.from(UuidUtil.parseOrThrow("d81b9a54-0ec9-43aa-a73f-7e99280ad53e"))
private val OTHERS_IDENTITY_KEY = IdentityKeyPair(Base64.decode("CiEFbAw403SCGPB+tjqfk+jrH7r9ma1P2hcujqydHRYVzzISIGiWYdWYBBdBzDdF06wgEm+HKcc6ETuWB7Jnvk7Wjw1u"))
private val OTHERS_PROFILE_KEY = ProfileKey(Base64.decode("aJJ/A7GBCSnU9HJ1DdMWcKMMeXQKRUguTlAbtlfo/ik"))
val groupMasterKey = GroupMasterKey(Hex.fromStringCondensed("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
val trustRoot = ECKeyPair(
ECPublicKey(Base64.decode("BVT/2gHqbrG1xzuIypLIOjFgMtihrMld1/5TGADL6Dhv")),
ECPrivateKey(Base64.decode("2B1zU7JQdPol/XWiom4pQXrSrHFeO8jzZ1u7wfrtY3o"))
)
val otherClients: List<OtherClient> by lazy {
val random = Random(4242)
buildList {
(0 until 1000).forEach { i ->
val aci = ACI.from(UUID(random.nextLong(), random.nextLong()))
val e164 = "+1555555%04d".format(i)
val identityKey = OTHERS_IDENTITY_KEY
val profileKey = OTHERS_PROFILE_KEY
add(OtherClient(aci, e164, identityKey, profileKey))
}
}
}
fun createCertificateFor(uuid: UUID, e164: String?, deviceId: Int, identityKey: ECPublicKey, expires: Duration): SenderCertificate {
val serverKey: ECKeyPair = ECKeyPair.generate()
val serverCertificate = ServerCertificate(trustRoot.privateKey, 1, serverKey.publicKey)
return serverCertificate.issue(serverKey.privateKey, uuid.toString(), Optional.ofNullable(e164), deviceId, identityKey, expires.inWholeMilliseconds)
}
}

View File

@@ -0,0 +1,189 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.benchmark.setup
import org.signal.benchmark.setup.Generator.toEnvelope
import org.signal.core.models.ServiceId
import org.signal.libsignal.protocol.IdentityKey
import org.signal.libsignal.protocol.IdentityKeyPair
import org.signal.libsignal.protocol.SessionBuilder
import org.signal.libsignal.protocol.SignalProtocolAddress
import org.signal.libsignal.protocol.ecc.ECPublicKey
import org.signal.libsignal.protocol.groups.state.SenderKeyRecord
import org.signal.libsignal.protocol.state.IdentityKeyStore
import org.signal.libsignal.protocol.state.IdentityKeyStore.IdentityChange
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
import org.signal.libsignal.protocol.state.PreKeyBundle
import org.signal.libsignal.protocol.state.PreKeyRecord
import org.signal.libsignal.protocol.state.SessionRecord
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
import org.signal.libsignal.protocol.util.KeyHelper
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore
import org.whispersystems.signalservice.api.SignalSessionLock
import org.whispersystems.signalservice.api.crypto.EnvelopeContent
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher
import org.whispersystems.signalservice.api.crypto.SignalSessionBuilder
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess
import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.push.Envelope
import java.util.UUID
import java.util.concurrent.locks.ReentrantLock
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.milliseconds
/**
* This is a "fake" client that can start a session with the running app's user, referred to as Alice in this
* code.
*/
class OtherClient(val serviceId: ServiceId, val e164: String, val identityKeyPair: IdentityKeyPair, val profileKey: ProfileKey) {
private val serviceAddress = SignalServiceAddress(serviceId, e164)
private val registrationId = KeyHelper.generateRegistrationId(false)
private val aciStore = BobSignalServiceAccountDataStore(registrationId, identityKeyPair)
private val senderCertificate = Harness.createCertificateFor(serviceId.rawUuid, e164, 1, identityKeyPair.publicKey.publicKey, System.currentTimeMillis().milliseconds + 30.days)
private val sessionLock = object : SignalSessionLock {
private val lock = ReentrantLock()
override fun acquire(): SignalSessionLock.Lock {
lock.lock()
return SignalSessionLock.Lock { lock.unlock() }
}
}
/** Inspired by SignalServiceMessageSender#getEncryptedMessage */
fun encrypt(envelopeContent: EnvelopeContent): Envelope {
val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, null)
if (!aciStore.containsSession(getAliceProtocolAddress())) {
val sessionBuilder = SignalSessionBuilder(sessionLock, SessionBuilder(aciStore, getAliceProtocolAddress()))
sessionBuilder.process(getAlicePreKeyBundle())
}
return cipher.encrypt(getAliceProtocolAddress(), getAliceUnidentifiedAccess(), envelopeContent)
.toEnvelope(envelopeContent.content.get().dataMessage!!.timestamp!!, getAliceServiceId())
}
fun generateInboundEnvelopes(count: Int): List<Envelope> {
val envelopes = ArrayList<Envelope>(count)
var now = System.currentTimeMillis()
for (i in 0 until count) {
envelopes += encrypt(Generator.encryptedTextMessage(now))
now += 3
}
return envelopes
}
fun generateInboundGroupEnvelopes(count: Int, groupMasterKey: GroupMasterKey): List<Envelope> {
val envelopes = ArrayList<Envelope>(count)
var now = System.currentTimeMillis()
for (i in 0 until count) {
envelopes += encrypt(Generator.encryptedTextMessage(now, groupMasterKey = groupMasterKey))
now += 3
}
return envelopes
}
private fun getAliceServiceId(): ServiceId {
return SignalStore.account.requireAci()
}
private fun getAlicePreKeyBundle(): PreKeyBundle {
val aliceSignedPreKeyRecord = SignalDatabase.signedPreKeys.getAll(getAliceServiceId()).first()
val aliceSignedKyberPreKeyRecord = SignalDatabase.kyberPreKeys.getAllLastResort(getAliceServiceId()).first().record
return PreKeyBundle(
registrationId = SignalStore.account.registrationId,
deviceId = 1,
preKeyId = PreKeyBundle.NULL_PRE_KEY_ID,
preKeyPublic = null,
signedPreKeyId = aliceSignedPreKeyRecord.id,
signedPreKeyPublic = aliceSignedPreKeyRecord.keyPair.publicKey,
signedPreKeySignature = aliceSignedPreKeyRecord.signature,
identityKey = getAlicePublicKey(),
kyberPreKeyId = aliceSignedKyberPreKeyRecord.id,
kyberPreKeyPublic = aliceSignedKyberPreKeyRecord.keyPair.publicKey,
kyberPreKeySignature = aliceSignedKyberPreKeyRecord.signature
)
}
private fun getAliceProtocolAddress(): SignalProtocolAddress {
return SignalProtocolAddress(SignalStore.account.requireAci().toString(), 1)
}
private fun getAlicePublicKey(): IdentityKey {
return SignalStore.account.aciIdentityKey.publicKey
}
private fun getAliceProfileKey(): ProfileKey {
return ProfileKeyUtil.getSelfProfileKey()
}
private fun getAliceUnidentifiedAccess(): SealedSenderAccess? {
val theirProfileKey = getAliceProfileKey()
val themUnidentifiedAccessKey = UnidentifiedAccess(UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey), senderCertificate.serialized, false)
return SealedSenderAccess.forIndividual(themUnidentifiedAccessKey)
}
private class BobSignalServiceAccountDataStore(private val registrationId: Int, private val identityKeyPair: IdentityKeyPair) : SignalServiceAccountDataStore {
private var aliceSessionRecord: SessionRecord? = null
override fun getIdentityKeyPair(): IdentityKeyPair = identityKeyPair
override fun getLocalRegistrationId(): Int = registrationId
override fun isTrustedIdentity(address: SignalProtocolAddress?, identityKey: IdentityKey?, direction: IdentityKeyStore.Direction?): Boolean = true
override fun loadSession(address: SignalProtocolAddress?): SessionRecord = aliceSessionRecord ?: SessionRecord()
override fun saveIdentity(address: SignalProtocolAddress?, identityKey: IdentityKey?): IdentityChange = IdentityChange.NEW_OR_UNCHANGED
override fun storeSession(address: SignalProtocolAddress?, record: SessionRecord?) {
aliceSessionRecord = record
}
override fun getSubDeviceSessions(name: String?): List<Int> = emptyList()
override fun containsSession(address: SignalProtocolAddress?): Boolean = aliceSessionRecord != null
override fun getIdentity(address: SignalProtocolAddress?): IdentityKey = SignalStore.account.aciIdentityKey.publicKey
override fun loadPreKey(preKeyId: Int): PreKeyRecord = throw UnsupportedOperationException()
override fun storePreKey(preKeyId: Int, record: PreKeyRecord?) = throw UnsupportedOperationException()
override fun containsPreKey(preKeyId: Int): Boolean = throw UnsupportedOperationException()
override fun removePreKey(preKeyId: Int) = throw UnsupportedOperationException()
override fun loadExistingSessions(addresses: MutableList<SignalProtocolAddress>?): MutableList<SessionRecord> = throw UnsupportedOperationException()
override fun deleteSession(address: SignalProtocolAddress?) = throw UnsupportedOperationException()
override fun deleteAllSessions(name: String?) = throw UnsupportedOperationException()
override fun loadSignedPreKey(signedPreKeyId: Int): SignedPreKeyRecord = throw UnsupportedOperationException()
override fun loadSignedPreKeys(): MutableList<SignedPreKeyRecord> = throw UnsupportedOperationException()
override fun storeSignedPreKey(signedPreKeyId: Int, record: SignedPreKeyRecord?) = throw UnsupportedOperationException()
override fun containsSignedPreKey(signedPreKeyId: Int): Boolean = throw UnsupportedOperationException()
override fun removeSignedPreKey(signedPreKeyId: Int) = throw UnsupportedOperationException()
override fun loadKyberPreKey(kyberPreKeyId: Int): KyberPreKeyRecord = throw UnsupportedOperationException()
override fun loadKyberPreKeys(): MutableList<KyberPreKeyRecord> = throw UnsupportedOperationException()
override fun storeKyberPreKey(kyberPreKeyId: Int, record: KyberPreKeyRecord?) = throw UnsupportedOperationException()
override fun containsKyberPreKey(kyberPreKeyId: Int): Boolean = throw UnsupportedOperationException()
override fun markKyberPreKeyUsed(kyberPreKeyId: Int, signedPreKeyId: Int, baseKey: ECPublicKey) = throw UnsupportedOperationException()
override fun deleteAllStaleOneTimeEcPreKeys(threshold: Long, minCount: Int) = throw UnsupportedOperationException()
override fun markAllOneTimeEcPreKeysStaleIfNecessary(staleTime: Long) = throw UnsupportedOperationException()
override fun storeSenderKey(sender: SignalProtocolAddress?, distributionId: UUID?, record: SenderKeyRecord?) = throw UnsupportedOperationException()
override fun loadSenderKey(sender: SignalProtocolAddress?, distributionId: UUID?): SenderKeyRecord = throw UnsupportedOperationException()
override fun archiveSession(address: SignalProtocolAddress?) = throw UnsupportedOperationException()
override fun getAllAddressesWithActiveSessions(addressNames: MutableList<String>?): MutableMap<SignalProtocolAddress, SessionRecord> = throw UnsupportedOperationException()
override fun getSenderKeySharedWith(distributionId: DistributionId?): MutableSet<SignalProtocolAddress> = throw UnsupportedOperationException()
override fun markSenderKeySharedWith(distributionId: DistributionId?, addresses: MutableCollection<SignalProtocolAddress>?) = throw UnsupportedOperationException()
override fun clearSenderKeySharedWith(addresses: MutableCollection<SignalProtocolAddress>?) = throw UnsupportedOperationException()
override fun storeLastResortKyberPreKey(kyberPreKeyId: Int, kyberPreKeyRecord: KyberPreKeyRecord) = throw UnsupportedOperationException()
override fun removeKyberPreKey(kyberPreKeyId: Int) = throw UnsupportedOperationException()
override fun markAllOneTimeKyberPreKeysStaleIfNecessary(staleTime: Long) = throw UnsupportedOperationException()
override fun deleteAllStaleOneTimeKyberPreKeys(threshold: Long, minCount: Int) = throw UnsupportedOperationException()
override fun loadLastResortKyberPreKeys(): List<KyberPreKeyRecord> = throw UnsupportedOperationException()
override fun isMultiDevice(): Boolean = throw UnsupportedOperationException()
}
}

View File

@@ -96,6 +96,7 @@ object TestMessages {
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
body = body,
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
serverTimeMillis = timestamp ?: System.currentTimeMillis(),
receivedTimeMillis = timestamp ?: System.currentTimeMillis(),

View File

@@ -4,14 +4,24 @@ import android.app.Application
import android.content.SharedPreferences
import android.preference.PreferenceManager
import kotlinx.coroutines.runBlocking
import okio.ByteString
import org.signal.core.models.ServiceId.ACI
import org.signal.core.util.Util
import org.signal.libsignal.protocol.IdentityKeyPair
import org.signal.libsignal.protocol.SignalProtocolAddress
import org.signal.storageservice.storage.protos.groups.AccessControl
import org.signal.storageservice.storage.protos.groups.Member
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup
import org.signal.storageservice.storage.protos.groups.local.DecryptedMember
import org.signal.storageservice.storage.protos.groups.local.DecryptedTimer
import org.signal.storageservice.storage.protos.groups.local.EnabledState
import org.thoughtcrime.securesms.crypto.MasterSecretUtil
import org.thoughtcrime.securesms.crypto.PreKeyUtil
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.databaseprotos.RestoreDecisionState
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.CertificateType
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.keyvalue.Skipped
import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor
@@ -23,15 +33,16 @@ import org.thoughtcrime.securesms.registration.data.LocalRegistrationMetadataUti
import org.thoughtcrime.securesms.registration.data.RegistrationData
import org.thoughtcrime.securesms.registration.data.RegistrationRepository
import org.thoughtcrime.securesms.registration.util.RegistrationUtil
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import java.util.UUID
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.milliseconds
object TestUsers {
private var generatedOthers: Int = 0
private val TEST_E164 = "+15555550101"
private var generatedOthers: Int = 1
fun setupSelf(): Recipient {
val application: Application = AppDependencies.application
@@ -49,19 +60,19 @@ object TestUsers {
runBlocking {
val registrationData = RegistrationData(
code = "123123",
e164 = TEST_E164,
e164 = Harness.SELF_E164,
password = Util.getSecret(18),
registrationId = RegistrationRepository.getRegistrationId(),
profileKey = RegistrationRepository.getProfileKey(TEST_E164),
profileKey = RegistrationRepository.getProfileKey(Harness.SELF_E164),
fcmToken = null,
pniRegistrationId = RegistrationRepository.getPniRegistrationId(),
recoveryPassword = "asdfasdfasdfasdf"
)
val remoteResult = AccountRegistrationResult(
uuid = UUID.randomUUID().toString(),
uuid = Harness.SELF_ACI.toString(),
pni = UUID.randomUUID().toString(),
storageCapable = false,
number = TEST_E164,
number = Harness.SELF_E164,
masterKey = null,
pin = null,
aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account.aciIdentityKey, SignalStore.account.aciPreKeys),
@@ -76,6 +87,31 @@ object TestUsers {
SignalStore.registration.restoreDecisionState = RestoreDecisionState.Skipped
RegistrationUtil.maybeMarkRegistrationComplete()
SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson"))
TextSecurePreferences.setPromptedOptimizeDoze(application, true)
TextSecurePreferences.setRatingEnabled(application, false)
PreKeyUtil.generateAndStoreSignedPreKey(AppDependencies.protocolStore.aci(), SignalStore.account.aciPreKeys)
PreKeyUtil.generateAndStoreOneTimeEcPreKeys(AppDependencies.protocolStore.aci(), SignalStore.account.aciPreKeys)
PreKeyUtil.generateAndStoreOneTimeKyberPreKeys(AppDependencies.protocolStore.aci(), SignalStore.account.aciPreKeys)
val aliceSenderCertificate = Harness.createCertificateFor(
uuid = Harness.SELF_ACI.rawUuid,
e164 = Harness.SELF_E164,
deviceId = 1,
identityKey = SignalStore.account.aciIdentityKey.publicKey.publicKey,
expires = System.currentTimeMillis().milliseconds + 30.days
)
val aliceSenderCertificate2 = Harness.createCertificateFor(
uuid = Harness.SELF_ACI.rawUuid,
e164 = null,
deviceId = 1,
identityKey = SignalStore.account.aciIdentityKey.publicKey.publicKey,
expires = System.currentTimeMillis().milliseconds + 30.days
)
SignalStore.certificate.setUnidentifiedAccessCertificate(CertificateType.ACI_AND_E164, aliceSenderCertificate.serialized)
SignalStore.certificate.setUnidentifiedAccessCertificate(CertificateType.ACI_ONLY, aliceSenderCertificate2.serialized)
return Recipient.self()
}
@@ -110,4 +146,71 @@ object TestUsers {
return others
}
fun setupTestClients(othersCount: Int): List<RecipientId> {
val others = mutableListOf<RecipientId>()
synchronized(this) {
for (i in 0 until othersCount) {
val otherClient = Harness.otherClients[i]
val recipientId = RecipientId.from(SignalServiceAddress(otherClient.serviceId, otherClient.e164))
SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i"))
SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, otherClient.profileKey)
SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true))
SignalDatabase.recipients.setProfileSharing(recipientId, true)
SignalDatabase.recipients.markRegistered(recipientId, otherClient.serviceId)
AppDependencies.protocolStore.aci().saveIdentity(SignalProtocolAddress(otherClient.serviceId.toString(), 1), otherClient.identityKeyPair.publicKey)
others += recipientId
}
generatedOthers += othersCount
}
return others
}
fun setupGroup() {
val members = setupTestClients(5)
val self = Recipient.self()
val fullMembers = buildList {
add(member(aci = self.requireAci()))
addAll(members.map { member(aci = Recipient.resolved(it).requireAci()) })
}
val group = DecryptedGroup(
title = "Title",
avatar = "",
disappearingMessagesTimer = DecryptedTimer(),
accessControl = AccessControl(),
revision = 1,
members = fullMembers,
pendingMembers = emptyList(),
requestingMembers = emptyList(),
inviteLinkPassword = ByteString.EMPTY,
description = "Description",
isAnnouncementGroup = EnabledState.DISABLED,
bannedMembers = emptyList(),
isPlaceholderGroup = false
)
val groupId = SignalDatabase.groups.create(
groupMasterKey = Harness.groupMasterKey,
groupState = group,
groupSendEndorsements = null
)
SignalDatabase.recipients.setProfileSharing(Recipient.externalGroupExact(groupId!!).id, true)
}
private fun member(aci: ACI, role: Member.Role = Member.Role.DEFAULT, joinedAt: Int = 0, labelEmoji: String = "", labelString: String = ""): DecryptedMember {
return DecryptedMember(
role = role,
aciBytes = aci.toByteString(),
joinedAtRevision = joinedAt,
labelEmoji = labelEmoji,
labelString = labelString
)
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.internal.websocket
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.subjects.BehaviorSubject
import org.thoughtcrime.securesms.util.JsonUtils
import org.thoughtcrime.securesms.util.SignalTrace
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState
import org.whispersystems.signalservice.internal.push.SendMessageResponse
import java.net.SocketException
import java.util.LinkedList
import java.util.Optional
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
/**
* A [WebSocketConnection] that provides a way to add "incoming" WebSocket payloads
* and have client code pull them off the "wire" as they would a normal socket.
*
* Add messages with [addPendingMessages] and then can release them to the requestor via
* [releaseMessages].
*/
class BenchmarkWebSocketConnection : WebSocketConnection {
companion object {
lateinit var authInstance: BenchmarkWebSocketConnection
private set
@Synchronized
fun createAuthInstance(): WebSocketConnection {
authInstance = BenchmarkWebSocketConnection()
return authInstance
}
lateinit var unauthInstance: BenchmarkWebSocketConnection
private set
@Synchronized
fun createUnauthInstance(): WebSocketConnection {
unauthInstance = BenchmarkWebSocketConnection()
return unauthInstance
}
}
override val name: String = "bench-${System.identityHashCode(this)}"
private val state = BehaviorSubject.create<WebSocketConnectionState>()
private val incomingRequests = LinkedList<WebSocketRequestMessage>()
private val incomingSemaphore = Semaphore(0)
var startWholeBatchTrace = false
@Volatile
private var isShutdown = false
override fun connect(): Observable<WebSocketConnectionState> {
state.onNext(WebSocketConnectionState.CONNECTED)
return state
}
override fun isDead(): Boolean {
return false
}
override fun disconnect() {
state.onNext(WebSocketConnectionState.DISCONNECTED)
// Signal shutdown
isShutdown = true
val queuedThreads = incomingSemaphore.queueLength
if (queuedThreads > 0) {
incomingSemaphore.release(queuedThreads)
}
}
override fun readRequest(timeoutMillis: Long): WebSocketRequestMessage {
if (incomingSemaphore.tryAcquire(1, 10, TimeUnit.SECONDS)) {
// Check if we were woken up due to shutdown
if (isShutdown) {
throw SocketException("WebSocket connection closed")
}
return getNextRequest()
}
throw TimeoutException("Timeout exceeded")
}
override fun readRequestIfAvailable(): Optional<WebSocketRequestMessage> {
return if (incomingSemaphore.tryAcquire()) {
Optional.of(getNextRequest())
} else {
Optional.empty()
}
}
private fun getNextRequest(): WebSocketRequestMessage {
if (startWholeBatchTrace) {
startWholeBatchTrace = false
SignalTrace.beginSection("IncomingMessageObserver#totalProcessing")
}
return incomingRequests.removeFirst()
}
override fun sendResponse(response: WebSocketResponseMessage) = Unit
fun addPendingMessages(messages: List<WebSocketRequestMessage>) {
incomingRequests.addAll(messages)
}
fun releaseMessages() {
incomingSemaphore.release(incomingRequests.size)
}
override fun sendRequest(
request: WebSocketRequestMessage,
timeoutSeconds: Long
): Single<WebsocketResponse> {
if (request.verb != null && request.path != null) {
if (request.verb == "PUT" && request.path!!.startsWith("/v1/messages/")) {
return Single.just(WebsocketResponse(200, SendMessageResponse().toJson(), emptyList<String>(), true))
}
}
return Single.error(okio.IOException("fake timeout"))
}
override fun sendKeepAlive() = Unit
fun addQueueEmptyMessage() {
addPendingMessages(
listOf(
WebSocketRequestMessage(
verb = "PUT",
path = "/api/v1/queue/empty"
)
)
)
}
}
private fun Any.toJson(): String {
return JsonUtils.toJson(this)
}

View File

@@ -14,13 +14,13 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.navigation.fragment.findNavController
import androidx.navigation.navGraphViewModels
import org.signal.core.ui.compose.ComposeFragment
import org.signal.core.ui.compose.NightPreview
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.Rows
import org.signal.core.ui.compose.Scaffolds
import org.signal.core.ui.compose.SignalIcons
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.compose.ComposeFragment
/**
* Configuration fragment for the internal conversation test fragment.

View File

@@ -18,7 +18,6 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.kotlin.subscribeBy
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.logging.Log
import org.signal.ringrtc.CallLinkEpoch
import org.signal.ringrtc.CallLinkRootKey
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.ViewBinderDelegate
@@ -295,7 +294,7 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onJoinCallLink(callLinkRootKey: CallLinkRootKey, callLinkEpoch: CallLinkEpoch?) {
override fun onJoinCallLink(callLinkRootKey: CallLinkRootKey) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}

View File

@@ -475,7 +475,7 @@
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing" />
<activity
android:name=".mediasend.v3.MediaSendV3Activity"
android:name="org.signal.mediasend.MediaSendActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:exported="false"
android:launchMode="singleTop"
@@ -1446,7 +1446,7 @@
android:exported="false" />
<receiver
android:name=".payments.backup.phrase.ClearClipboardAlarmReceiver"
android:name="org.signal.core.util.ClearClipboardAlarmReceiver"
android:exported="false" />
<receiver

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
import org.thoughtcrime.securesms.migrations.QuoteThumbnailBackfillMigrationJob;
import org.thoughtcrime.securesms.stickers.BlessedPacks;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.signal.core.util.Util;
/**
* Rule of thumb: if there's something you want to do on the first app launch that involves
@@ -41,11 +41,7 @@ public final class AppInitialization {
TextSecurePreferences.setTypingIndicatorsEnabled(context, true);
AppDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
SignalStore.onFirstEverAppLaunch();
AppDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
AppDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
AppDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.DAY_BY_DAY.getPackId(), BlessedPacks.DAY_BY_DAY.getPackKey(), false));
AppDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
AppDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
AppDependencies.getJobManager().addAll(BlessedPacks.getFirstInstallJobs());
}
public static void onPostBackupRestore(@NonNull Context context) {
@@ -59,11 +55,7 @@ public final class AppInitialization {
SignalStore.notificationProfile().setHasSeenTooltip(true);
TextSecurePreferences.onPostBackupRestore(context);
SignalStore.settings().setPassphraseDisabled(true);
AppDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
AppDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
AppDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.DAY_BY_DAY.getPackId(), BlessedPacks.DAY_BY_DAY.getPackKey(), false));
AppDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
AppDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
AppDependencies.getJobManager().addAll(BlessedPacks.getFirstInstallJobs());
EmojiSearchIndexDownloadJob.scheduleImmediately();
DeleteAbandonedAttachmentsJob.enqueue();
@@ -87,10 +79,6 @@ public final class AppInitialization {
SignalStore.settings().setPassphraseDisabled(true);
AppDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
SignalStore.onFirstEverAppLaunch();
AppDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
AppDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
AppDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.DAY_BY_DAY.getPackId(), BlessedPacks.DAY_BY_DAY.getPackKey(), false));
AppDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
AppDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
AppDependencies.getJobManager().addAll(BlessedPacks.getFirstInstallJobs());
}
}

View File

@@ -104,12 +104,13 @@ import org.thoughtcrime.securesms.service.webrtc.AndroidTelecomUtil;
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.AppForegroundObserver;
import org.thoughtcrime.securesms.util.AppStartup;
import org.thoughtcrime.securesms.util.DeviceProperties;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.RemoteConfig;
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.signal.core.util.Util;
import org.thoughtcrime.securesms.util.VersionTracker;
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
import org.whispersystems.signalservice.api.websocket.SignalWebSocket;
@@ -241,7 +242,7 @@ public class ApplicationContext extends Application implements AppForegroundObse
@Override
public void onForeground() {
long startTime = System.currentTimeMillis();
Log.i(TAG, "App is now visible.");
Log.i(TAG, "App is now visible. Battery: " + DeviceProperties.getBatteryLevel(this) + "% (charging: " + DeviceProperties.isCharging(this) + ")");
AppDependencies.getFrameRateTracker().start();
AppDependencies.getMegaphoneRepository().onAppForegrounded();

View File

@@ -11,7 +11,6 @@ import androidx.lifecycle.Observer;
import com.bumptech.glide.RequestManager;
import org.signal.ringrtc.CallLinkEpoch;
import org.signal.ringrtc.CallLinkRootKey;
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
import org.thoughtcrime.securesms.contactshare.Contact;
@@ -30,8 +29,8 @@ import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
import org.thoughtcrime.securesms.polls.PollRecord;
import org.thoughtcrime.securesms.polls.PollOption;
import org.thoughtcrime.securesms.polls.PollRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.stickers.StickerLocator;
@@ -136,7 +135,7 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
void goToMediaPreview(ConversationItem parent, View sharedElement, MediaIntentFactory.MediaPreviewArgs args);
void onEditedIndicatorClicked(@NonNull ConversationMessage conversationMessage);
void onShowGroupDescriptionClicked(@NonNull String groupName, @NonNull String description, boolean shouldLinkifyWebLinks);
void onJoinCallLink(@NonNull CallLinkRootKey callLinkRootKey, @Nullable CallLinkEpoch callLinkEpoch);
void onJoinCallLink(@NonNull CallLinkRootKey callLinkRootKey);
void onShowSafetyTips(boolean forGroup);
void onReportSpamLearnMoreClicked();
void onMessageRequestAcceptOptionsClicked();

View File

@@ -18,6 +18,7 @@ package org.thoughtcrime.securesms;
import android.Manifest;
import org.signal.core.ui.logging.LoggingFragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Rect;
@@ -71,7 +72,7 @@ import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.groups.SelectionLimits;
import org.thoughtcrime.securesms.groups.ui.GroupLimitDialog;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.signal.core.ui.permissions.Permissions;
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository;
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository.UsernameAciFetchResult;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -341,6 +342,7 @@ public final class ContactSelectionListFragment extends LoggingFragment {
.map(r -> new ContactSearchKey.RecipientSearchKey(r, false))
.collect(java.util.stream.Collectors.toSet()),
selectionLimit,
isMulti,
new ContactSearchAdapter.DisplayOptions(
isMulti,
ContactSearchAdapter.DisplaySecondaryInformation.ALWAYS,
@@ -557,6 +559,7 @@ public final class ContactSelectionListFragment extends LoggingFragment {
public void onDataRefreshed() {
this.resetPositionOnCommit = true;
swipeRefresh.setRefreshing(false);
contactSearchMediator.refresh();
}
public boolean hasQueryFilter() {
@@ -573,6 +576,7 @@ public final class ContactSelectionListFragment extends LoggingFragment {
public void reset() {
contactSearchMediator.clearSelection();
contactSearchMediator.refresh();
fastScroller.setVisibility(View.GONE);
headerActionView.setVisibility(View.GONE);
}

View File

@@ -22,9 +22,9 @@ import androidx.fragment.app.setFragmentResult
import org.signal.core.ui.BottomSheetUtil
import org.signal.core.ui.compose.BottomSheets
import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment
import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
/**
* Education sheet shown before authentication explaining that users should use their device credentials

View File

@@ -28,11 +28,11 @@ import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import org.signal.core.ui.compose.ComposeFragment
import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.Scaffolds
import org.signal.core.ui.compose.SignalIcons
import org.thoughtcrime.securesms.compose.ComposeFragment
/**
* Fragment when inviting someone to use Signal

View File

@@ -85,6 +85,10 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.signal.core.ui.BottomSheetUtil
import org.signal.core.ui.compose.Snackbars
import org.signal.core.ui.compose.theme.SignalTheme
import org.signal.core.ui.isSplitPane
import org.signal.core.ui.permissions.Permissions
import org.signal.core.util.Util
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.getSerializableCompat
import org.signal.core.util.logging.Log
@@ -93,7 +97,6 @@ import org.signal.mediasend.MediaSendActivityContract
import org.thoughtcrime.securesms.backup.v2.ArchiveRestoreProgress
import org.thoughtcrime.securesms.backup.v2.ui.verify.VerifyBackupKeyActivity
import org.thoughtcrime.securesms.calls.YouAreAlreadyInACallSnackbar.show
import org.thoughtcrime.securesms.calls.links.details.CallLinkDetailsActivity
import org.thoughtcrime.securesms.calls.log.CallLogFilter
import org.thoughtcrime.securesms.calls.log.CallLogFragment
import org.thoughtcrime.securesms.calls.new.NewCallActivity
@@ -113,7 +116,6 @@ import org.thoughtcrime.securesms.components.snackbars.SnackbarHostKey
import org.thoughtcrime.securesms.components.snackbars.SnackbarState
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner
import org.thoughtcrime.securesms.compose.SignalTheme
import org.thoughtcrime.securesms.conversation.ConversationIntents
import org.thoughtcrime.securesms.conversation.NewConversationActivity
import org.thoughtcrime.securesms.conversation.v2.MotionEventRelay
@@ -137,6 +139,7 @@ import org.thoughtcrime.securesms.main.MainContentLayoutData
import org.thoughtcrime.securesms.main.MainMegaphoneState
import org.thoughtcrime.securesms.main.MainNavigationBar
import org.thoughtcrime.securesms.main.MainNavigationDetailLocation
import org.thoughtcrime.securesms.main.MainNavigationDetailLocationEffect
import org.thoughtcrime.securesms.main.MainNavigationListLocation
import org.thoughtcrime.securesms.main.MainNavigationRail
import org.thoughtcrime.securesms.main.MainNavigationViewModel
@@ -153,11 +156,9 @@ import org.thoughtcrime.securesms.main.chatNavGraphBuilder
import org.thoughtcrime.securesms.main.navigateToDetailLocation
import org.thoughtcrime.securesms.main.rememberDetailNavHostController
import org.thoughtcrime.securesms.main.rememberFocusRequester
import org.thoughtcrime.securesms.main.rememberMainNavigationDetailLocation
import org.thoughtcrime.securesms.main.storiesNavGraphBuilder
import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity
import org.thoughtcrime.securesms.mediasend.v3.MediaSendV3ActivityContract
import org.thoughtcrime.securesms.megaphone.Megaphone
import org.thoughtcrime.securesms.megaphone.MegaphoneActionController
import org.thoughtcrime.securesms.megaphone.Megaphones
@@ -165,7 +166,6 @@ import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor
import org.thoughtcrime.securesms.notifications.VitalsViewModel
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfiles
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.profiles.manage.UsernameEditFragment
import org.thoughtcrime.securesms.service.BackupMediaRestoreService
import org.thoughtcrime.securesms.service.KeyCachingService
@@ -181,14 +181,12 @@ import org.thoughtcrime.securesms.util.DynamicTheme
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
import org.thoughtcrime.securesms.util.SplashScreenUtil
import org.thoughtcrime.securesms.util.TopToastPopup
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.viewModel
import org.thoughtcrime.securesms.window.AppPaneDragHandle
import org.thoughtcrime.securesms.window.AppScaffold
import org.thoughtcrime.securesms.window.AppScaffoldAnimationStateFactory
import org.thoughtcrime.securesms.window.AppScaffoldNavigator
import org.thoughtcrime.securesms.window.NavigationType
import org.thoughtcrime.securesms.window.isSplitPane
import org.thoughtcrime.securesms.window.rememberThreePaneScaffoldNavigatorDelegate
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState
import org.signal.core.ui.R as CoreUiR
@@ -280,7 +278,7 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
super.onCreate(savedInstanceState, ready)
navigator = MainNavigator(this, mainNavigationViewModel)
mediaActivityLauncher = registerForActivityResult(MediaSendV3ActivityContract()) { }
mediaActivityLauncher = registerForActivityResult(MediaSendActivityContract()) { }
AppForegroundObserver.addListener(object : AppForegroundObserver.Listener {
override fun onForeground() {
@@ -448,7 +446,7 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
val chatNavGraphState = ChatNavGraphState.remember(windowSizeClass)
val mutableInteractionSource = remember { MutableInteractionSource() }
val mainNavigationDetailLocation by rememberMainNavigationDetailLocation(mainNavigationViewModel, chatNavGraphState::writeGraphicsLayerToBitmap)
MainNavigationDetailLocationEffect(mainNavigationViewModel, chatNavGraphState::writeGraphicsLayerToBitmap)
val chatsNavHostController = rememberDetailNavHostController(
onRequestFocus = rememberFocusRequester(
@@ -478,25 +476,33 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
storiesNavGraphBuilder()
}
LaunchedEffect(mainNavigationDetailLocation) {
mainNavigationViewModel.clearEarlyDetailLocation()
when (mainNavigationDetailLocation) {
is MainNavigationDetailLocation.Empty -> {
when (mainNavigationState.currentListLocation) {
MainNavigationListLocation.CHATS, MainNavigationListLocation.ARCHIVE -> chatsNavHostController
MainNavigationListLocation.CALLS -> callsNavHostController
MainNavigationListLocation.STORIES -> storiesNavHostController
}.navigateToDetailLocation(mainNavigationDetailLocation)
}
LaunchedEffect(Unit) {
suspend fun navigateToLocation(location: MainNavigationDetailLocation) {
when (location) {
is MainNavigationDetailLocation.Empty -> {
when (mainNavigationState.currentListLocation) {
MainNavigationListLocation.CHATS, MainNavigationListLocation.ARCHIVE -> chatsNavHostController
MainNavigationListLocation.CALLS -> callsNavHostController
MainNavigationListLocation.STORIES -> storiesNavHostController
}.navigateToDetailLocation(location)
}
is MainNavigationDetailLocation.Chats -> {
chatNavGraphState.writeGraphicsLayerToBitmap()
chatsNavHostController.navigateToDetailLocation(mainNavigationDetailLocation)
}
is MainNavigationDetailLocation.Chats -> {
if (location is MainNavigationDetailLocation.Chats.Conversation) {
chatNavGraphState.writeGraphicsLayerToBitmap()
}
chatsNavHostController.navigateToDetailLocation(location)
}
is MainNavigationDetailLocation.Calls -> callsNavHostController.navigateToDetailLocation(mainNavigationDetailLocation)
is MainNavigationDetailLocation.Stories -> storiesNavHostController.navigateToDetailLocation(mainNavigationDetailLocation)
is MainNavigationDetailLocation.Calls -> callsNavHostController.navigateToDetailLocation(location)
is MainNavigationDetailLocation.Stories -> storiesNavHostController.navigateToDetailLocation(location)
}
}
mainNavigationViewModel.earlyNavigationDetailLocationRequested?.let { navigateToLocation(it) }
mainNavigationViewModel.clearEarlyDetailLocation()
mainNavigationViewModel.detailLocation.collect { navigateToLocation(it) }
}
val scope = rememberCoroutineScope()
@@ -753,27 +759,7 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
val coroutine = rememberCoroutineScope()
return remember(scaffoldNavigator, coroutine) {
mainNavigationViewModel.wrapNavigator(coroutine, scaffoldNavigator) { detailLocation ->
when (detailLocation) {
is MainNavigationDetailLocation.Chats.Conversation -> {
startActivity(
ConversationIntents.createBuilderSync(this, detailLocation.conversationArgs.recipientId, detailLocation.conversationArgs.threadId)
.withArgs(detailLocation.conversationArgs)
.build()
)
}
is MainNavigationDetailLocation.Calls.CallLinks.CallLinkDetails -> {
startActivity(CallLinkDetailsActivity.createIntent(this, detailLocation.callLinkRoomId))
}
is MainNavigationDetailLocation.Calls.CallLinks.EditCallLinkName -> {
error("Unexpected subroute EditCallLinkName.")
}
MainNavigationDetailLocation.Empty -> Unit
}
}
mainNavigationViewModel.wrapNavigator(coroutine, scaffoldNavigator)
}
}
@@ -782,7 +768,7 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
CompositionLocalProvider(LocalSnackbarStateConsumerRegistry provides mainNavigationViewModel.snackbarRegistry) {
SignalTheme(isDarkMode = DynamicTheme.isDarkTheme(this)) {
SignalTheme {
val backgroundColor = if (!windowSizeClass.isSplitPane()) {
MaterialTheme.colorScheme.surface
} else {

View File

@@ -4,6 +4,8 @@ import android.content.Context;
import androidx.annotation.NonNull;
import org.signal.core.ui.logging.LoggingFragment;
public class MainFragment extends LoggingFragment {
@Override

View File

@@ -9,9 +9,9 @@ import android.content.Context
import android.text.TextUtils
import okio.ByteString.Companion.toByteString
import org.signal.core.util.Base64
import org.signal.core.util.Util
import org.signal.core.util.toByteArray
import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
import org.whispersystems.signalservice.internal.push.AttachmentPointer
import java.io.IOException

View File

@@ -13,6 +13,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import io.reactivex.rxjava3.core.Completable
import kotlinx.coroutines.rx3.rxCompletable
import kotlinx.coroutines.withContext
import org.signal.core.ui.permissions.Permissions
import org.signal.core.ui.util.StorageUtil
import org.signal.core.ui.view.AlertDialogResult
import org.signal.core.ui.view.awaitResult
import org.signal.core.util.concurrent.SignalDispatchers
@@ -24,11 +26,9 @@ import org.thoughtcrime.securesms.components.ProgressCardDialogFragmentArgs
import org.thoughtcrime.securesms.database.MediaTable
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.util.SaveAttachmentUtil
import org.thoughtcrime.securesms.util.SaveAttachmentUtil.SaveAttachment
import org.thoughtcrime.securesms.util.SaveAttachmentUtil.SaveAttachmentsResult
import org.thoughtcrime.securesms.util.StorageUtil
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

View File

@@ -12,7 +12,7 @@ import android.os.Process;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.util.Util;
import org.signal.core.util.Util;
import java.io.IOException;
import java.io.OutputStream;

View File

@@ -17,6 +17,7 @@ import androidx.fragment.app.viewModels
import androidx.navigation.Navigation
import androidx.recyclerview.widget.RecyclerView
import org.signal.core.models.media.Media
import org.signal.core.ui.permissions.Permissions
import org.signal.core.util.ThreadUtil
import org.signal.core.util.getParcelableExtraCompat
import org.thoughtcrime.securesms.R
@@ -30,7 +31,6 @@ import org.thoughtcrime.securesms.components.ButtonStripItemView
import org.thoughtcrime.securesms.components.recyclerview.GridDividerDecoration
import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity
import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.navigation.safeNavigate

View File

@@ -32,7 +32,7 @@ import org.thoughtcrime.securesms.restore.restorelocalbackup.PassphraseAsYouType
import org.thoughtcrime.securesms.service.LocalBackupListener;
import org.thoughtcrime.securesms.util.BackupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.signal.core.util.Util;
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
public class BackupDialog {

View File

@@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.backup.proto.KeyValue;
import org.thoughtcrime.securesms.backup.proto.SharedPreference;
import org.thoughtcrime.securesms.backup.proto.SqlStatement;
import org.thoughtcrime.securesms.backup.proto.Sticker;
import org.thoughtcrime.securesms.util.Util;
import org.signal.core.util.Util;
import java.io.IOException;
import java.io.InputStream;

View File

@@ -41,7 +41,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.BackupUtil;
import org.thoughtcrime.securesms.util.Util;
import org.signal.core.util.Util;
import java.io.ByteArrayOutputStream;
import java.io.File;

View File

@@ -10,12 +10,12 @@ import org.signal.core.models.backup.MediaName
import org.signal.core.util.Base64
import org.signal.core.util.Base64.decodeBase64
import org.signal.core.util.Base64.decodeBase64OrThrow
import org.signal.core.util.Util
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
import org.thoughtcrime.securesms.attachments.InvalidAttachmentException
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.RemoteConfig
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
import java.io.IOException

View File

@@ -40,7 +40,6 @@ class CallLinkArchiveExporter(private val cursor: Cursor) : Iterator<ArchiveReci
id = callLink.recipientId.toLong(),
callLink = CallLink(
rootKey = callLink.credentials!!.linkKeyBytes.toByteString(),
epoch = callLink.credentials.epochBytes?.takeIf { it.size == 4 }?.toByteString(),
adminKey = callLink.credentials.adminPassBytes?.toByteString()?.nullIfEmpty(),
name = callLink.state.name,
expirationMs = expirationTime.takeIf { it != Long.MAX_VALUE }?.clampToValidBackupRange() ?: 0,

View File

@@ -123,7 +123,13 @@ private fun Member.Role.toRemote(): Group.Member.Role {
}
private fun DecryptedMember.toRemote(): Group.Member {
return Group.Member(userId = aciBytes, role = role.toRemote(), joinedAtVersion = joinedAtRevision)
return Group.Member(
userId = aciBytes,
role = role.toRemote(),
joinedAtVersion = joinedAtRevision,
labelEmoji = labelEmoji,
labelString = labelString
)
}
private fun DecryptedPendingMember.toRemote(): Group.MemberPendingProfileKey {

View File

@@ -42,11 +42,7 @@ object CallLinkArchiveImporter {
CallLinkTable.CallLink(
recipientId = RecipientId.UNKNOWN,
roomId = CallLinkRoomId.fromCallLinkRootKey(rootKey),
credentials = CallLinkCredentials(
callLink.rootKey.toByteArray(),
callLink.epoch?.toByteArray(),
callLink.adminKey?.toByteArray()
),
credentials = CallLinkCredentials(callLink.rootKey.toByteArray(), callLink.adminKey?.toByteArray()),
state = SignalCallLinkState(
name = callLink.name,
restrictions = callLink.restrictions.toLocal(),

View File

@@ -116,7 +116,13 @@ private fun Group.Member.Role.toLocal(): Member.Role {
}
private fun Group.Member.toLocal(): DecryptedMember {
return DecryptedMember(aciBytes = userId, role = role.toLocal(), joinedAtRevision = joinedAtVersion)
return DecryptedMember(
aciBytes = userId,
role = role.toLocal(),
joinedAtRevision = joinedAtVersion,
labelEmoji = labelEmoji,
labelString = labelString
)
}
private fun Group.MemberPendingAdminApproval.toLocal(): DecryptedRequestingMember {

View File

@@ -11,6 +11,7 @@ import org.signal.core.models.backup.BackupId
import org.signal.core.models.backup.MediaName
import org.signal.core.util.Stopwatch
import org.signal.core.util.StreamUtil
import org.signal.core.util.Util
import org.signal.core.util.logging.Log
import org.signal.core.util.readFully
import org.thoughtcrime.securesms.attachments.AttachmentId
@@ -20,7 +21,6 @@ import org.thoughtcrime.securesms.backup.v2.local.proto.FilesFrame
import org.thoughtcrime.securesms.backup.v2.local.proto.Metadata
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.crypto.AttachmentCipherOutputStream
import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream

View File

@@ -7,13 +7,13 @@ package org.thoughtcrime.securesms.backup.v2.stream
import org.signal.core.models.ServiceId.ACI
import org.signal.core.models.backup.MessageBackupKey
import org.signal.core.util.Util
import org.signal.core.util.stream.MacOutputStream
import org.signal.core.util.writeVarInt32
import org.signal.libsignal.messagebackup.BackupForwardSecrecyToken
import org.thoughtcrime.securesms.backup.v2.proto.BackupInfo
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupReader.Companion.createForSignalBackup
import org.thoughtcrime.securesms.util.Util
import java.io.IOException
import java.io.OutputStream
import javax.crypto.Cipher

View File

@@ -37,6 +37,7 @@ import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.parcelize.Parcelize
import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment
import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.theme.SignalTheme
@@ -45,7 +46,6 @@ import org.thoughtcrime.securesms.backup.v2.BackupRepository
import org.thoughtcrime.securesms.billing.launchManageBackupsSubscription
import org.thoughtcrime.securesms.components.contactsupport.ContactSupportDialogFragment
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
import org.thoughtcrime.securesms.jobs.BackupMessagesJob
import org.thoughtcrime.securesms.keyvalue.protos.BackupDownloadNotifierState
import org.thoughtcrime.securesms.util.CommunicationActions

View File

@@ -27,11 +27,11 @@ import androidx.core.os.bundleOf
import androidx.fragment.app.setFragmentResult
import org.signal.core.ui.compose.BottomSheets
import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment
import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.app.backups.BackupStateObserver
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
import org.thoughtcrime.securesms.jobs.BackupMessagesJob
import org.signal.core.ui.R as CoreUiR

View File

@@ -11,12 +11,12 @@ import androidx.compose.ui.res.stringResource
import androidx.core.os.BundleCompat
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import org.signal.core.ui.compose.ComposeDialogFragment
import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Dialogs
import org.signal.core.ui.compose.Previews
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.backup.v2.BackupRepository
import org.thoughtcrime.securesms.compose.ComposeDialogFragment
/**
* Displays a "last chance" dialog to the user to begin a media restore.

View File

@@ -12,11 +12,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment
import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
import org.thoughtcrime.securesms.jobs.BackupMessagesJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import kotlin.time.Duration

View File

@@ -19,12 +19,12 @@ import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withLink
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment
import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.backup.v2.BackupRepository
import org.thoughtcrime.securesms.components.contactsupport.ContactSupportDialogFragment
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
import org.thoughtcrime.securesms.util.CommunicationActions
class NoRemoteStorageSpaceAvailableBottomSheet : ComposeBottomSheetDialogFragment() {

View File

@@ -33,8 +33,6 @@ import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.Scaffolds
import org.signal.core.ui.compose.SignalIcons
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.compose.BetaHeader
import org.thoughtcrime.securesms.components.compose.TextWithBetaLabel
import org.signal.core.ui.R as CoreUiR
/**
@@ -65,10 +63,6 @@ fun MessageBackupsEducationScreen(
.fillMaxWidth()
.weight(1f)
) {
item {
BetaHeader()
}
item {
Image(
painter = painterResource(id = R.drawable.image_signal_backups),
@@ -80,9 +74,9 @@ fun MessageBackupsEducationScreen(
}
item {
TextWithBetaLabel(
Text(
text = stringResource(id = R.string.RemoteBackupsSettingsFragment__signal_backups),
textStyle = MaterialTheme.typography.headlineMedium,
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(top = 15.dp)
)
}

View File

@@ -29,21 +29,21 @@ import com.google.android.gms.common.GoogleApiAvailability
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.rx3.asFlowable
import org.signal.core.ui.compose.ComposeFragment
import org.signal.core.ui.compose.Dialogs
import org.signal.core.util.Util
import org.signal.core.util.concurrent.SignalDispatchers
import org.signal.core.util.getSerializableCompat
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.backup.DeletionState
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.InAppPaymentCheckoutDelegate
import org.thoughtcrime.securesms.compose.ComposeFragment
import org.thoughtcrime.securesms.compose.Nav
import org.thoughtcrime.securesms.database.InAppPaymentTable
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.PlayStoreUtil
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.storage.AndroidCredentialRepository
import org.thoughtcrime.securesms.util.viewModel

View File

@@ -44,8 +44,8 @@ import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.Scaffolds
import org.signal.core.ui.compose.SignalIcons
import org.signal.core.ui.compose.horizontalGutters
import org.signal.core.ui.compose.theme.SignalTheme
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.compose.SignalTheme
import org.signal.core.ui.R as CoreUiR
enum class MessageBackupsKeyEducationScreenMode {

View File

@@ -57,12 +57,12 @@ import org.signal.core.ui.compose.SignalIcons
import org.signal.core.ui.compose.Snackbars
import org.signal.core.ui.compose.horizontalGutters
import org.signal.core.ui.compose.theme.SignalTheme
import org.signal.core.util.Util
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.app.backups.remote.BackupKeyCredentialManagerHandler
import org.thoughtcrime.securesms.components.settings.app.backups.remote.BackupKeySaveState
import org.thoughtcrime.securesms.fonts.MonoTypeface
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.storage.AndroidCredentialRepository
import org.thoughtcrime.securesms.util.storage.CredentialManagerError
import org.thoughtcrime.securesms.util.storage.CredentialManagerResult

View File

@@ -2,8 +2,8 @@ package org.thoughtcrime.securesms.backup.v2.ui.verify
import android.app.Activity.RESULT_OK
import androidx.compose.runtime.Composable
import org.signal.core.ui.compose.ComposeFragment
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsKeyVerifyScreen
import org.thoughtcrime.securesms.compose.ComposeFragment
import org.thoughtcrime.securesms.keyvalue.SignalStore
/**

View File

@@ -5,9 +5,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.signal.core.ui.compose.ComposeFragment
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsKeyRecordMode
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsKeyRecordScreen
import org.thoughtcrime.securesms.compose.ComposeFragment
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.viewModel

View File

@@ -24,11 +24,11 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.theme.SignalTheme
import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.backup.v2.ui.subscription.EnterKeyScreen
import org.thoughtcrime.securesms.components.compose.rememberBiometricsAuthentication
import org.thoughtcrime.securesms.compose.SignalTheme
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.CommunicationActions
import kotlin.random.Random

View File

@@ -32,17 +32,17 @@ import androidx.fragment.app.FragmentManager
import org.signal.core.ui.BottomSheetUtil
import org.signal.core.ui.compose.BottomSheets
import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.SignalIcons
import org.signal.core.ui.compose.Texts
import org.signal.core.ui.compose.theme.SignalTheme
import org.signal.core.util.getParcelableCompat
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
import org.thoughtcrime.securesms.components.settings.app.subscription.BadgeImage112
import org.thoughtcrime.securesms.components.settings.app.subscription.manage.ManageDonationsFragment
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
import org.thoughtcrime.securesms.compose.SignalTheme
import org.thoughtcrime.securesms.database.InAppPaymentTable
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.CommunicationActions

View File

@@ -12,12 +12,12 @@ import androidx.fragment.app.viewModels
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayoutMediator
import org.signal.core.ui.BottomSheetUtil
import org.signal.core.ui.FixedRoundedCornerBottomSheetDialogFragment
import org.signal.core.util.getParcelableCompat
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.badges.BadgeRepository
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.badges.models.LargeBadge
import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialogFragment
import org.thoughtcrime.securesms.components.ViewBinderDelegate
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppDonations

View File

@@ -15,8 +15,8 @@ import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.flow.Flow
import org.signal.core.ui.compose.theme.SignalTheme
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.compose.SignalTheme
/**
* A class that can be instantiated with a list of [Flow]s that produce [Banner]s, then applied to a [ComposeView], typically within a [Fragment].

View File

@@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews
import org.signal.core.util.Util
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.banner.Banner
import org.thoughtcrime.securesms.banner.ui.compose.Action
@@ -22,7 +23,6 @@ import org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner
import org.thoughtcrime.securesms.banner.ui.compose.Importance
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.PlayStoreUtil
import org.thoughtcrime.securesms.util.Util
import kotlin.time.Duration.Companion.milliseconds
/**

View File

@@ -23,6 +23,7 @@ import com.google.android.gms.common.GoogleApiAvailability
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.rx3.asFlowable
import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment
import org.signal.core.ui.compose.Dialogs
import org.signal.core.util.concurrent.SignalDispatchers
import org.thoughtcrime.securesms.R
@@ -31,7 +32,6 @@ import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsFlowVi
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsStage
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.InAppPaymentCheckoutDelegate
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
import org.thoughtcrime.securesms.database.InAppPaymentTable
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.util.viewModel

View File

@@ -8,7 +8,6 @@ package org.thoughtcrime.securesms.calls.links
import io.reactivex.rxjava3.core.Observable
import org.signal.core.util.logging.Log
import org.signal.ringrtc.CallException
import org.signal.ringrtc.CallLinkEpoch
import org.signal.ringrtc.CallLinkRootKey
import org.thoughtcrime.securesms.database.CallLinkTable
import org.thoughtcrime.securesms.database.DatabaseObserver
@@ -23,7 +22,6 @@ import java.net.URLDecoder
*/
object CallLinks {
private const val ROOT_KEY = "key"
private const val EPOCH = "epoch"
private const val LEGACY_HTTPS_LINK_PREFIX = "https://signal.link/call#key="
private const val LEGACY_SGNL_LINK_PREFIX = "sgnl://signal.link/call#key="
private const val HTTPS_LINK_PREFIX = "https://signal.link/call/#key="
@@ -31,13 +29,7 @@ object CallLinks {
private val TAG = Log.tag(CallLinks::class.java)
fun url(rootKeyBytes: ByteArray, epochBytes: ByteArray?): String {
return if (epochBytes == null) {
"$HTTPS_LINK_PREFIX${CallLinkRootKey(rootKeyBytes)}"
} else {
"$HTTPS_LINK_PREFIX${CallLinkRootKey(rootKeyBytes)}&epoch=${CallLinkEpoch.fromBytes(epochBytes)}"
}
}
fun url(rootKeyBytes: ByteArray): String = "$HTTPS_LINK_PREFIX${CallLinkRootKey(rootKeyBytes)}"
fun watchCallLink(roomId: CallLinkRoomId): Observable<CallLinkTable.CallLink> {
return Observable.create { emitter ->
@@ -78,13 +70,8 @@ object CallLinks {
return url.split("#").last().startsWith("key=")
}
data class CallLinkParseResult(
val rootKey: CallLinkRootKey,
val epoch: CallLinkEpoch?
)
@JvmStatic
fun parseUrl(url: String): CallLinkParseResult? {
fun parseUrl(url: String): CallLinkRootKey? {
if (!isPrefixedCallLink(url)) {
Log.w(TAG, "Invalid url prefix.")
return null
@@ -132,13 +119,9 @@ object CallLinks {
}
return try {
val epoch = fragmentQuery[EPOCH]?.let { s -> CallLinkEpoch(s) }
CallLinkParseResult(
rootKey = CallLinkRootKey(key),
epoch = epoch
)
return CallLinkRootKey(key)
} catch (e: CallException) {
Log.w(TAG, "Invalid root key or epoch found in fragment query string.")
Log.w(TAG, "Invalid root key found in fragment query string.")
null
}
}

View File

@@ -40,14 +40,14 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.launch
import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.ComposeDialogFragment
import org.signal.core.ui.compose.Scaffolds
import org.signal.core.ui.compose.SignalIcons
import org.signal.core.ui.isSplitPane
import org.signal.core.util.BreakIteratorCompat
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.calls.links.details.CallLinkDetailsViewModel
import org.thoughtcrime.securesms.compose.ComposeDialogFragment
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
import org.thoughtcrime.securesms.window.isSplitPane
class EditCallLinkNameDialogFragment : ComposeDialogFragment() {

View File

@@ -53,7 +53,7 @@ import org.signal.core.ui.R as CoreUiR
@Composable
private fun SignalCallRowPreview() {
val callLink = remember {
val credentials = CallLinkCredentials(byteArrayOf(1, 2, 3, 4), byteArrayOf(0, 1, 2, 3), byteArrayOf(5, 6, 7, 8))
val credentials = CallLinkCredentials(byteArrayOf(1, 2, 3, 4), byteArrayOf(5, 6, 7, 8))
CallLinkTable.CallLink(
recipientId = RecipientId.UNKNOWN,
roomId = CallLinkRoomId.fromBytes(byteArrayOf(1, 3, 5, 7)),
@@ -97,7 +97,7 @@ fun SignalCallRow(
"https://signal.call.example.com"
} else {
remember(callLink.credentials) {
callLink.credentials?.let { CallLinks.url(it.linkKeyBytes, it.epochBytes) } ?: ""
callLink.credentials?.let { CallLinks.url(it.linkKeyBytes) } ?: ""
}
}

View File

@@ -37,11 +37,13 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.reactivex.rxjava3.kotlin.subscribeBy
import org.signal.core.ui.compose.BottomSheets
import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment
import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Dividers
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.Rows
import org.signal.core.ui.compose.SignalIcons
import org.signal.core.util.Util
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.logging.Log
import org.signal.ringrtc.CallLinkState
@@ -50,7 +52,6 @@ import org.thoughtcrime.securesms.calls.YouAreAlreadyInACallSnackbar.YouAreAlrea
import org.thoughtcrime.securesms.calls.links.CallLinks
import org.thoughtcrime.securesms.calls.links.EditCallLinkNameDialogFragment
import org.thoughtcrime.securesms.calls.links.SignalCallRow
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
import org.thoughtcrime.securesms.database.CallLinkTable
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
@@ -59,7 +60,6 @@ import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkState
import org.thoughtcrime.securesms.service.webrtc.links.UpdateCallLinkResult
import org.thoughtcrime.securesms.sharing.v2.ShareActivity
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.Util
import java.time.Instant
import org.signal.core.ui.R as CoreUiR
@@ -162,7 +162,7 @@ class CreateCallLinkBottomSheetDialogFragment : ComposeBottomSheetDialogFragment
startActivity(
ShareActivity.sendSimpleText(
requireContext(),
getString(R.string.CreateCallLink__use_this_link_to_join_a_signal_call, CallLinks.url(viewModel.linkKeyBytes, viewModel.epochBytes))
getString(R.string.CreateCallLink__use_this_link_to_join_a_signal_call, CallLinks.url(viewModel.linkKeyBytes))
)
)
}
@@ -176,7 +176,7 @@ class CreateCallLinkBottomSheetDialogFragment : ComposeBottomSheetDialogFragment
lifecycleDisposable += viewModel.commitCallLink().subscribeBy(onSuccess = {
when (it) {
is EnsureCallLinkCreatedResult.Success -> {
Util.copyToClipboard(requireContext(), CallLinks.url(viewModel.linkKeyBytes, viewModel.epochBytes))
Util.copyToClipboard(requireContext(), CallLinks.url(viewModel.linkKeyBytes))
Toast.makeText(requireContext(), R.string.CreateCallLinkBottomSheetDialogFragment__copied_to_clipboard, Toast.LENGTH_LONG).show()
}
@@ -191,7 +191,7 @@ class CreateCallLinkBottomSheetDialogFragment : ComposeBottomSheetDialogFragment
is EnsureCallLinkCreatedResult.Success -> {
val mimeType = Intent.normalizeMimeType("text/plain")
val shareIntent = ShareCompat.IntentBuilder(requireContext())
.setText(CallLinks.url(viewModel.linkKeyBytes, viewModel.epochBytes))
.setText(CallLinks.url(viewModel.linkKeyBytes))
.setType(mimeType)
.createChooserIntent()

View File

@@ -52,9 +52,6 @@ class CreateCallLinkViewModel(
val linkKeyBytes: ByteArray
get() = callLink.value.credentials!!.linkKeyBytes
val epochBytes: ByteArray?
get() = callLink.value.credentials!!.epochBytes
private val internalShowAlreadyInACall = MutableStateFlow(false)
val showAlreadyInACall: StateFlow<Boolean> = internalShowAlreadyInACall

View File

@@ -13,9 +13,9 @@ import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.remember
import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentActivity
import org.signal.core.ui.compose.theme.SignalTheme
import org.signal.core.util.getParcelableExtraCompat
import org.thoughtcrime.securesms.calls.links.EditCallLinkNameDialogFragment
import org.thoughtcrime.securesms.compose.SignalTheme
import org.thoughtcrime.securesms.main.MainNavigationDetailLocation
import org.thoughtcrime.securesms.main.MainNavigationListLocation
import org.thoughtcrime.securesms.main.MainNavigationRouter

View File

@@ -37,6 +37,8 @@ import org.signal.core.ui.compose.Rows
import org.signal.core.ui.compose.Scaffolds
import org.signal.core.ui.compose.SignalIcons
import org.signal.core.ui.compose.Snackbars
import org.signal.core.ui.isSplitPane
import org.signal.core.util.Util
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.ringrtc.CallLinkState.Restrictions
import org.thoughtcrime.securesms.R
@@ -53,8 +55,6 @@ import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkState
import org.thoughtcrime.securesms.sharing.v2.ShareActivity
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.window.isSplitPane
import java.time.Instant
@Composable
@@ -119,7 +119,7 @@ class DefaultCallLinkDetailsCallback(
override fun onShareClicked() {
val mimeType = Intent.normalizeMimeType("text/plain")
val shareIntent = ShareCompat.IntentBuilder(activity)
.setText(CallLinks.url(viewModel.rootKeySnapshot, viewModel.epochSnapshot))
.setText(CallLinks.url(viewModel.rootKeySnapshot))
.setType(mimeType)
.createChooserIntent()
@@ -131,7 +131,7 @@ class DefaultCallLinkDetailsCallback(
}
override fun onCopyClicked() {
Util.copyToClipboard(activity, CallLinks.url(viewModel.rootKeySnapshot, viewModel.epochSnapshot))
Util.copyToClipboard(activity, CallLinks.url(viewModel.rootKeySnapshot))
Toast.makeText(activity, R.string.CreateCallLinkBottomSheetDialogFragment__copied_to_clipboard, Toast.LENGTH_LONG).show()
}
@@ -139,7 +139,7 @@ class DefaultCallLinkDetailsCallback(
activity.startActivity(
ShareActivity.sendSimpleText(
activity,
activity.getString(R.string.CreateCallLink__use_this_link_to_join_a_signal_call, CallLinks.url(viewModel.rootKeySnapshot, viewModel.epochSnapshot))
activity.getString(R.string.CreateCallLink__use_this_link_to_join_a_signal_call, CallLinks.url(viewModel.rootKeySnapshot))
)
)
}
@@ -324,7 +324,6 @@ private fun CallLinkDetailsScreenPreview() {
val callLink = remember {
val credentials = CallLinkCredentials(
byteArrayOf(1, 2, 3, 4),
byteArrayOf(0, 1, 2, 3),
byteArrayOf(3, 4, 5, 6)
)
CallLinkTable.CallLink(

View File

@@ -48,9 +48,6 @@ class CallLinkDetailsViewModel(
val rootKeySnapshot: ByteArray
get() = state.value.callLink?.credentials?.linkKeyBytes ?: error("Call link not loaded yet.")
val epochSnapshot: ByteArray?
get() = state.value.callLink?.credentials?.epochBytes
private val recipientSubject = BehaviorSubject.create<Recipient>()
val recipientSnapshot: Recipient?
get() = recipientSubject.value

View File

@@ -23,6 +23,8 @@ import io.reactivex.rxjava3.kotlin.subscribeBy
import kotlinx.coroutines.launch
import org.signal.core.ui.BottomSheetUtil
import org.signal.core.ui.compose.Snackbars
import org.signal.core.ui.getWindowSizeClass
import org.signal.core.ui.isSplitPane
import org.signal.core.util.DimensionUnit
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.concurrent.addTo
@@ -62,8 +64,6 @@ import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.doAfterNextLayout
import org.thoughtcrime.securesms.util.fragments.requireListener
import org.thoughtcrime.securesms.util.visible
import org.thoughtcrime.securesms.window.getWindowSizeClass
import org.thoughtcrime.securesms.window.isSplitPane
import java.util.Objects
import org.signal.core.ui.R as CoreUiR

View File

@@ -36,12 +36,12 @@ import org.signal.core.ui.compose.AllDevicePreviews
import org.signal.core.ui.compose.Dialogs
import org.signal.core.ui.compose.DropdownMenus
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.theme.SignalTheme
import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.calls.new.NewCallUiState.CallType
import org.thoughtcrime.securesms.calls.new.NewCallUiState.UserMessage
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
import org.thoughtcrime.securesms.compose.SignalTheme
import org.thoughtcrime.securesms.recipients.ui.RecipientLookupFailureMessage
import org.thoughtcrime.securesms.recipients.ui.RecipientPicker
import org.thoughtcrime.securesms.recipients.ui.RecipientPickerCallbacks

View File

@@ -15,8 +15,8 @@ import androidx.core.os.bundleOf
import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.setFragmentResultListener
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment
import org.signal.storageservice.protos.calls.quality.SubmitCallQualitySurveyRequest
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity
import org.thoughtcrime.securesms.util.viewModel

View File

@@ -7,8 +7,8 @@ package org.thoughtcrime.securesms.calls.quality
import androidx.compose.runtime.Composable
import androidx.core.os.bundleOf
import org.signal.core.ui.compose.ComposeFullScreenDialogFragment
import org.signal.storageservice.protos.calls.quality.SubmitCallQualitySurveyRequest
import org.thoughtcrime.securesms.compose.ComposeFullScreenDialogFragment
class CallQualityDiagnosticsFragment : ComposeFullScreenDialogFragment() {

View File

@@ -13,7 +13,7 @@ import androidx.compose.runtime.remember
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.setFragmentResult
import org.thoughtcrime.securesms.compose.ComposeFullScreenDialogFragment
import org.signal.core.ui.compose.ComposeFullScreenDialogFragment
/**
* Fragment which allows user to enter additional text to describe a call issue.

View File

@@ -15,7 +15,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.Util;
import org.signal.core.util.Util;
public class ArcProgressBar extends View {

View File

@@ -66,6 +66,7 @@ public class ComposeText extends EmojiEditText {
private MentionRendererDelegate mentionRendererDelegate;
private SpoilerRendererDelegate spoilerRendererDelegate;
private MentionValidatorWatcher mentionValidatorWatcher;
private MessageSendType lastMessageSendType;
@Nullable private InputPanel.MediaListener mediaListener;
@Nullable private CursorPositionChangedListener cursorPositionChangedListener;
@@ -221,6 +222,11 @@ public class ComposeText extends EmojiEditText {
}
public void setMessageSendType(MessageSendType messageSendType) {
if (messageSendType.equals(lastMessageSendType)) {
return;
}
lastMessageSendType = messageSendType;
int imeOptions = (getImeOptions() & ~EditorInfo.IME_MASK_ACTION) | EditorInfo.IME_ACTION_SEND;
int inputType = getInputType();

View File

@@ -33,7 +33,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.signal.core.ui.permissions.Permissions;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.MessageRecordUtil;

View File

@@ -16,6 +16,7 @@ import androidx.core.os.bundleOf
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import org.signal.core.ui.BottomSheetUtil
import org.signal.core.ui.FixedRoundedCornerBottomSheetDialogFragment
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R

View File

@@ -47,7 +47,10 @@ public abstract class FullScreenDialogFragment extends DialogFragment {
@Override
public void onResume() {
super.onResume();
WindowUtil.initializeScreenshotSecurity(requireContext(), requireDialog().getWindow());
if (getShowsDialog()) {
WindowUtil.initializeScreenshotSecurity(requireContext(), requireDialog().getWindow());
}
}
protected void onNavigateUp() {

View File

@@ -219,7 +219,7 @@ public class InputPanel extends ConstraintLayout
@NonNull SlideDeck attachments,
@NonNull QuoteModel.Type quoteType)
{
this.quoteView.setQuote(requestManager, id, author, body, false, attachments, null, quoteType, true);
this.quoteView.setQuote(requestManager, id, author, body, false, attachments, null, quoteType, true, null);
if (listener != null) {
this.quoteView.setOnClickListener(v -> listener.onQuoteClicked(id, author.getId()));
}

View File

@@ -31,7 +31,7 @@ import androidx.appcompat.widget.LinearLayoutCompat;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.Util;
import org.signal.core.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.lang.reflect.Field;

View File

@@ -29,7 +29,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.thoughtcrime.securesms.mms.ImageSlide;
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
import org.thoughtcrime.securesms.util.Util;
import org.signal.core.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.text.DateFormat;
@@ -172,11 +172,11 @@ public class LinkPreviewView extends FrameLayout {
spinner.setVisibility(GONE);
noPreview.setVisibility(GONE);
CallLinks.CallLinkParseResult callLinkParseResult = CallLinks.isCallLink(linkPreview.getUrl()) ? CallLinks.parseUrl(linkPreview.getUrl()) : null;
CallLinkRootKey callLinkRootKey = CallLinks.isCallLink(linkPreview.getUrl()) ? CallLinks.parseUrl(linkPreview.getUrl()) : null;
if (!Util.isEmpty(linkPreview.getTitle())) {
title.setText(linkPreview.getTitle());
title.setVisibility(VISIBLE);
} else if (callLinkParseResult != null) {
} else if (callLinkRootKey != null) {
title.setText(R.string.Recipient_signal_call);
title.setVisibility(VISIBLE);
} else {
@@ -186,7 +186,7 @@ public class LinkPreviewView extends FrameLayout {
if (showDescription && !Util.isEmpty(linkPreview.getDescription())) {
description.setText(linkPreview.getDescription());
description.setVisibility(VISIBLE);
} else if (callLinkParseResult != null) {
} else if (callLinkRootKey != null) {
description.setText(R.string.LinkPreviewView__use_this_link_to_join_a_signal_call);
description.setVisibility(VISIBLE);
} else {
@@ -221,14 +221,14 @@ public class LinkPreviewView extends FrameLayout {
thumbnail.get().setImageResource(requestManager, new ImageSlide(linkPreview.getThumbnail().get()), type == TYPE_CONVERSATION && !scheduleMessageMode, false);
thumbnail.get().showSecondaryText(false);
thumbnail.get().setOutlineEnabled(true);
} else if (callLinkParseResult != null) {
} else if (callLinkRootKey != null) {
thumbnail.setVisibility(VISIBLE);
thumbnailState.applyState(thumbnail);
thumbnail.get().setImageDrawable(
requestManager,
new FallbackAvatarDrawable(
getContext(),
new FallbackAvatar.Resource.CallLink(AvatarColorHash.forCallLink(callLinkParseResult.getRootKey().getKeyBytes()))
new FallbackAvatar.Resource.CallLink(AvatarColorHash.forCallLink(callLinkRootKey.getKeyBytes()))
).circleCrop()
);
thumbnail.get().showSecondaryText(false);
@@ -272,7 +272,7 @@ public class LinkPreviewView extends FrameLayout {
thumbnailState.applyState(thumbnail);
}
private @StringRes static int getLinkPreviewErrorString(@Nullable LinkPreviewRepository.Error customError) {
private @StringRes static int getLinkPreviewErrorString(@Nullable LinkPreviewRepository.Error customError) {
return customError == LinkPreviewRepository.Error.GROUP_LINK_INACTIVE ? R.string.LinkPreviewView_this_group_link_is_not_active
: R.string.LinkPreviewView_no_link_preview_available;
}

View File

@@ -24,7 +24,7 @@ import androidx.core.content.ContextCompat;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.audio.AudioRecordingHandler;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.signal.core.ui.permissions.Permissions;
import org.thoughtcrime.securesms.util.ViewUtil;
public final class MicrophoneRecorderView extends FrameLayout implements View.OnTouchListener {

View File

@@ -13,6 +13,7 @@ import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentManager
import org.signal.core.ui.BottomSheetUtil
import org.signal.core.ui.FixedRoundedCornerBottomSheetDialogFragment
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R

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