Compare commits

..

7 Commits

Author SHA1 Message Date
Cody Henthorne
c3e9f98273 Bump version to 6.22.8 2023-06-09 15:00:23 -04:00
Cody Henthorne
9a3faae731 Update SQLCipher to 4.5.4-S2 2023-06-09 14:59:33 -04:00
Cody Henthorne
82e8cb87c1 Fix bug when processing duplicate text messages within the same batch. 2023-06-09 14:30:06 -04:00
Cody Henthorne
9bdcc9e3d8 Bump version to 6.22.7 2023-06-08 12:07:14 -04:00
Cody Henthorne
daee747338 Bump version to 6.22.6
Previous bump only incremented the version code.
2023-06-08 11:47:14 -04:00
Cody Henthorne
535a163f00 Bump version to 6.22.6 2023-06-08 10:58:12 -04:00
Clark
bd61b91722 Drop failed processed incoming messages. 2023-06-08 10:48:01 -04:00
909 changed files with 28536 additions and 41815 deletions

View File

@@ -1,8 +1,5 @@
root = true
[*.{kt,kts}]
[*.kt]
indent_size = 2
ij_kotlin_allow_trailing_comma_on_call_site = false
ij_kotlin_allow_trailing_comma = false
ktlint_code_style = intellij_idea
twitter_compose_allowed_composition_locals=LocalExtendedColors

View File

@@ -19,11 +19,11 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: set up JDK 17
- name: set up JDK 11
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 17
java-version: 11
cache: gradle
- name: Validate Gradle Wrapper

View File

@@ -1,82 +0,0 @@
name: APK Diff
on:
pull_request:
permissions:
contents: read # to fetch code (actions/checkout)
pull-requests: write # to comment on PR
jobs:
assemble-base:
if: ${{ github.repository != 'signalapp/Signal-Android' }}
runs-on: ubuntu-latest-8-cores
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.base.sha }}
- name: set up JDK 17
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 17
cache: gradle
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- name: Cache base apk
id: cache-base
uses: actions/cache@v3
with:
path: diffuse-base.apk
key: diffuse-${{ github.event.pull_request.base.sha }}
- name: Build with Gradle
if: steps.cache-base.outputs.cache-hit != 'true'
run: ./gradlew assemblePlayProdRelease --parallel
- name: Copy base apk
if: steps.cache-base.outputs.cache-hit != 'true'
run: mv app/build/outputs/apk/playProd/release/*arm64*.apk diffuse-base.apk
- uses: actions/checkout@v3
with:
clean: 'false'
- name: Build with Gradle
run: ./gradlew assemblePlayProdRelease --parallel
- name: Copy PR apk
run: mv app/build/outputs/apk/playProd/release/*arm64*.apk diffuse-new.apk
- id: diffuse
uses: usefulness/diffuse-action@v1
with:
old-file-path: diffuse-base.apk
new-file-path: diffuse-new.apk
- uses: peter-evans/find-comment@v2
id: find-comment
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: Diffuse output
- uses: peter-evans/create-or-update-comment@v3
with:
body: |
Diffuse output:
${{ steps.diffuse.outputs.diff-gh-comment }}
edit-mode: replace
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/upload-artifact@v3
with:
name: diffuse-output
path: ${{ steps.diffuse.outputs.diff-file }}

View File

@@ -212,12 +212,5 @@
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -42,18 +42,19 @@ wire {
}
ktlint {
version = "0.49.1"
// Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507
version = "0.47.1"
}
def canonicalVersionCode = 1293
def canonicalVersionName = "6.26.4"
def canonicalVersionCode = 1272
def canonicalVersionName = "6.22.8"
def postFixSize = 100
def abiPostFix = ['universal' : 5,
'armeabi-v7a' : 6,
'arm64-v8a' : 7,
'x86' : 8,
'x86_64' : 9]
def abiPostFix = ['universal' : 15,
'armeabi-v7a' : 16,
'arm64-v8a' : 17,
'x86' : 18,
'x86_64' : 19]
def keystores = [ 'debug' : loadKeystoreProperties('keystore.debug.properties') ]
@@ -158,7 +159,7 @@ android {
}
composeOptions {
kotlinCompilerExtensionVersion = '1.4.4'
kotlinCompilerExtensionVersion = '1.3.2'
}
defaultConfig {
@@ -184,7 +185,7 @@ android {
buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.signal.org\""
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\""
buildConfigField "String", "SIGNAL_SVR2_URL", "\"https://svr2.signal.org\""
buildConfigField "String", "SIGNAL_SVR2_URL", "\"https://svr2.staging.signal.org\""
buildConfigField "String", "SIGNAL_SFU_URL", "\"https://sfu.voip.signal.org\""
buildConfigField "String", "SIGNAL_STAGING_SFU_URL", "\"https://sfu.staging.voip.signal.org\""
buildConfigField "String[]", "SIGNAL_SFU_INTERNAL_NAMES", "new String[]{\"Test\", \"Staging\", \"Development\"}"
@@ -198,11 +199,9 @@ android {
buildConfigField "String[]", "SIGNAL_KBS_IPS", kbs_ips
buildConfigField "String[]", "SIGNAL_SFU_IPS", sfu_ips
buildConfigField "String[]", "SIGNAL_CONTENT_PROXY_IPS", content_proxy_ips
buildConfigField "String[]", "SIGNAL_CDSI_IPS", cdsi_ips
buildConfigField "String[]", "SIGNAL_SVR2_IPS", svr2_ips
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
buildConfigField "String", "CDSI_MRENCLAVE", "\"0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57\""
buildConfigField "String", "SVR2_MRENCLAVE", "\"6ee1042f9e20f880326686dd4ba50c25359f01e9f733eeba4382bca001d45094\""
buildConfigField "String", "SVR2_MRENCLAVE", "\"dc9fd472a5a9c871a3c7f76f1af60aa9c1f314abf2e8d1e0c4ba25c8aaa2848c\""
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"e18376436159cda3ad7a45d9320e382e4a497f26b0dca34d8eab0bd0139483b5\", " +
"\"3a485adb56e2058ef7737764c738c4069dd62bc457637eafb6bbce1ce29ddb89\", " +
"\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
@@ -229,8 +228,8 @@ android {
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
resourceConfigurations += []
resConfigs autoResConfig()
splits {
abi {
@@ -379,8 +378,6 @@ android {
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2-staging.signal.org\""
buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.staging.signal.org\""
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
buildConfigField "String", "SIGNAL_SVR2_URL", "\"https://svr2.staging.signal.org\""
buildConfigField "String", "SVR2_MRENCLAVE", "\"a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95\""
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"39963b736823d5780be96ab174869a9499d56d66497aa8f9b2244f777ebc366b\", " +
"\"9dbc6855c198e04f21b5cc35df839fdcd51b53658454dfa3f817afefaffc95ef\", " +
"\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
@@ -448,14 +445,6 @@ android {
variant.setIgnore(true)
}
}
android.buildTypes.each {
if (it.name != 'release') {
sourceSets.findByName(it.name).java.srcDirs += "$projectDir/src/debug/java"
} else {
sourceSets.findByName(it.name).java.srcDirs += "$projectDir/src/release/java"
}
}
}
dependencies {
@@ -479,7 +468,6 @@ dependencies {
implementation libs.androidx.gridlayout
implementation libs.androidx.exifinterface
implementation libs.androidx.compose.rxjava3
implementation libs.androidx.compose.runtime.livedata
implementation libs.androidx.constraintlayout
implementation libs.androidx.multidex
implementation libs.androidx.navigation.fragment.ktx
@@ -541,11 +529,13 @@ dependencies {
implementation libs.leolin.shortcutbadger
implementation libs.emilsjolander.stickylistheaders
implementation libs.jpardogo.materialtabstrip
implementation libs.apache.httpclient.android
implementation libs.glide.glide
implementation libs.roundedimageview
implementation libs.materialish.progress
implementation libs.greenrobot.eventbus
implementation libs.waitingdots
implementation libs.google.zxing.android.integration
implementation libs.google.zxing.core
implementation libs.google.flexbox
@@ -588,8 +578,9 @@ dependencies {
exclude group: 'com.google.protobuf', module: 'protobuf-java'
}
testImplementation testLibs.robolectric.shadows.multidex
testImplementation (testLibs.bouncycastle.bcprov.jdk15on) { version { strictly "1.70" } } // Used by roboelectric
testImplementation (testLibs.bouncycastle.bcpkix.jdk15on) { version { strictly "1.70" } } // Used by roboelectric
testImplementation (testLibs.bouncycastle.bcprov.jdk15on) {
force = true
}
testImplementation testLibs.hamcrest.hamcrest
testImplementation testLibs.mockk

View File

@@ -11,11 +11,4 @@
# Protobuf lite
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
-keep class androidx.window.** { *; }
# AGP generated dont warns
-dontwarn com.android.org.conscrypt.SSLParametersImpl
-dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl
-dontwarn org.slf4j.impl.StaticLoggerBinder
-dontwarn sun.net.spi.nameservice.NameService
-dontwarn sun.net.spi.nameservice.NameServiceDescriptor
-keep class androidx.window.** { *; }

View File

@@ -10,11 +10,13 @@ import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.signal.core.util.ThreadUtil
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.pin.KbsRepository
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.registration.VerifyAccountRepository
import org.thoughtcrime.securesms.registration.VerifyResponseProcessor
@@ -46,17 +48,20 @@ class ChangeNumberViewModelTest {
val harness = SignalActivityRule()
private lateinit var viewModel: ChangeNumberViewModel
private lateinit var kbsRepository: KbsRepository
@Before
fun setUp() {
ApplicationDependencies.getSignalServiceAccountManager().setSoTimeoutMillis(1000)
kbsRepository = mock()
ThreadUtil.runOnMainSync {
viewModel = ChangeNumberViewModel(
localNumber = harness.self.requireE164(),
changeNumberRepository = ChangeNumberRepository(),
savedState = SavedStateHandle(),
password = SignalStore.account().servicePassword!!,
verifyAccountRepository = VerifyAccountRepository(harness.application)
verifyAccountRepository = VerifyAccountRepository(harness.application),
kbsRepository = kbsRepository
)
viewModel.setNewCountry(1)
@@ -230,7 +235,7 @@ class ChangeNumberViewModelTest {
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
MockProvider.mockGetRegistrationLockStringFlow()
MockProvider.mockGetRegistrationLockStringFlow(kbsRepository)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
@@ -318,7 +323,7 @@ class ChangeNumberViewModelTest {
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
MockProvider.mockGetRegistrationLockStringFlow()
MockProvider.mockGetRegistrationLockStringFlow(kbsRepository)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },

View File

@@ -142,7 +142,6 @@ class ConversationItemPreviewer {
1024,
1024,
Optional.empty(),
Optional.empty(),
Optional.of("/not-there.jpg"),
false,
false,

View File

@@ -1,328 +0,0 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.conversation.v2.items
import android.net.Uri
import android.view.View
import androidx.lifecycle.Observer
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.signal.ringrtc.CallLinkRootKey
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState
import org.thoughtcrime.securesms.contactshare.Contact
import org.thoughtcrime.securesms.conversation.ConversationAdapter
import org.thoughtcrime.securesms.conversation.ConversationItem
import org.thoughtcrime.securesms.conversation.ConversationItemDisplayMode
import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.colors.Colorizer
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
import org.thoughtcrime.securesms.database.FakeMessageRecords
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
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.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stickers.StickerLocator
import org.thoughtcrime.securesms.testing.SignalActivityRule
import kotlin.time.Duration.Companion.minutes
class V2ConversationItemShapeTest {
@get:Rule
val harness = SignalActivityRule(othersCount = 10)
@Test
fun givenNextAndPreviousMessageDoNotExist_whenISetMessageShape_thenIExpectSingle() {
val testSubject = V2ConversationItemShape(FakeConversationContext())
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
@Test
fun givenPreviousWithinTimeoutAndNoNext_whenISetMessageShape_thenIExpectEnd() {
val now = System.currentTimeMillis()
val prev = now - 2.minutes.inWholeMilliseconds
val testSubject = V2ConversationItemShape(
FakeConversationContext(
previousMessage = getMessageRecord(prev)
)
)
val expected = V2ConversationItemShape.MessageShape.END
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
@Test
fun givenNextWithinTimeoutAndNoPrevious_whenISetMessageShape_thenIExpectStart() {
val now = System.currentTimeMillis()
val prev = now - 2.minutes.inWholeMilliseconds
val testSubject = V2ConversationItemShape(
FakeConversationContext(
nextMessage = getMessageRecord(now)
)
)
val expected = V2ConversationItemShape.MessageShape.START
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(prev),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
@Test
fun givenPreviousAndNextWithinTimeout_whenISetMessageShape_thenIExpectMiddle() {
val now = System.currentTimeMillis()
val prev = now - 2.minutes.inWholeMilliseconds
val next = now + 2.minutes.inWholeMilliseconds
val testSubject = V2ConversationItemShape(
FakeConversationContext(
previousMessage = getMessageRecord(prev),
nextMessage = getMessageRecord(next)
)
)
val expected = V2ConversationItemShape.MessageShape.MIDDLE
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
@Test
fun givenPreviousOutsideTimeoutAndNoNext_whenISetMessageShape_thenIExpectSingle() {
val now = System.currentTimeMillis()
val prev = now - 4.minutes.inWholeMilliseconds
val testSubject = V2ConversationItemShape(
FakeConversationContext(
previousMessage = getMessageRecord(prev)
)
)
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
@Test
fun givenNextOutsideTimeoutAndNoPrevious_whenISetMessageShape_thenIExpectSingle() {
val now = System.currentTimeMillis()
val prev = now - 4.minutes.inWholeMilliseconds
val testSubject = V2ConversationItemShape(
FakeConversationContext(
nextMessage = getMessageRecord(now)
)
)
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(prev),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
@Test
fun givenPreviousAndNextOutsideTimeout_whenISetMessageShape_thenIExpectSingle() {
val now = System.currentTimeMillis()
val prev = now - 4.minutes.inWholeMilliseconds
val next = now + 4.minutes.inWholeMilliseconds
val testSubject = V2ConversationItemShape(
FakeConversationContext(
previousMessage = getMessageRecord(prev),
nextMessage = getMessageRecord(next)
)
)
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
private fun getMessageRecord(
timestamp: Long = System.currentTimeMillis()
): MessageRecord {
return FakeMessageRecords.buildMediaMmsMessageRecord(
dateReceived = timestamp,
dateSent = timestamp,
dateServer = timestamp
)
}
private class FakeConversationContext(
private val hasWallpaper: Boolean = false,
private val previousMessage: MessageRecord? = null,
private val nextMessage: MessageRecord? = null
) : V2ConversationContext {
private val colorizer = Colorizer()
override val displayMode: ConversationItemDisplayMode = ConversationItemDisplayMode.STANDARD
override val clickListener: ConversationAdapter.ItemClickListener = FakeConversationItemClickListener
override val selectedItems: Set<MultiselectPart> = emptySet()
override val isMessageRequestAccepted: Boolean = true
override val searchQuery: String? = null
override fun onStartExpirationTimeout(messageRecord: MessageRecord) = Unit
override fun hasWallpaper(): Boolean = hasWallpaper
override fun getColorizer(): Colorizer = colorizer
override fun getNextMessage(adapterPosition: Int): MessageRecord? = nextMessage
override fun getPreviousMessage(adapterPosition: Int): MessageRecord? = previousMessage
}
private object FakeConversationItemClickListener : ConversationAdapter.ItemClickListener {
override fun onQuoteClicked(messageRecord: MmsMessageRecord?) = Unit
override fun onLinkPreviewClicked(linkPreview: LinkPreview) = Unit
override fun onQuotedIndicatorClicked(messageRecord: MessageRecord) = Unit
override fun onMoreTextClicked(conversationRecipientId: RecipientId, messageId: Long, isMms: Boolean) = Unit
override fun onStickerClicked(stickerLocator: StickerLocator) = Unit
override fun onViewOnceMessageClicked(messageRecord: MmsMessageRecord) = Unit
override fun onSharedContactDetailsClicked(contact: Contact, avatarTransitionView: View) = Unit
override fun onAddToContactsClicked(contact: Contact) = Unit
override fun onMessageSharedContactClicked(choices: MutableList<Recipient>) = Unit
override fun onInviteSharedContactClicked(choices: MutableList<Recipient>) = Unit
override fun onReactionClicked(multiselectPart: MultiselectPart, messageId: Long, isMms: Boolean) = Unit
override fun onGroupMemberClicked(recipientId: RecipientId, groupId: GroupId) = Unit
override fun onMessageWithErrorClicked(messageRecord: MessageRecord) = Unit
override fun onMessageWithRecaptchaNeededClicked(messageRecord: MessageRecord) = Unit
override fun onIncomingIdentityMismatchClicked(recipientId: RecipientId) = Unit
override fun onRegisterVoiceNoteCallbacks(onPlaybackStartObserver: Observer<VoiceNotePlaybackState>) = Unit
override fun onUnregisterVoiceNoteCallbacks(onPlaybackStartObserver: Observer<VoiceNotePlaybackState>) = Unit
override fun onVoiceNotePause(uri: Uri) = Unit
override fun onVoiceNotePlay(uri: Uri, messageId: Long, position: Double) = Unit
override fun onVoiceNoteSeekTo(uri: Uri, position: Double) = Unit
override fun onVoiceNotePlaybackSpeedChanged(uri: Uri, speed: Float) = Unit
override fun onGroupMigrationLearnMoreClicked(membershipChange: GroupMigrationMembershipChange) = Unit
override fun onChatSessionRefreshLearnMoreClicked() = Unit
override fun onBadDecryptLearnMoreClicked(author: RecipientId) = Unit
override fun onSafetyNumberLearnMoreClicked(recipient: Recipient) = Unit
override fun onJoinGroupCallClicked() = Unit
override fun onInviteFriendsToGroupClicked(groupId: GroupId.V2) = Unit
override fun onEnableCallNotificationsClicked() = Unit
override fun onPlayInlineContent(conversationMessage: ConversationMessage?) = Unit
override fun onInMemoryMessageClicked(messageRecord: InMemoryMessageRecord) = Unit
override fun onViewGroupDescriptionChange(groupId: GroupId?, description: String, isMessageRequestAccepted: Boolean) = Unit
override fun onChangeNumberUpdateContact(recipient: Recipient) = Unit
override fun onCallToAction(action: String) = Unit
override fun onDonateClicked() = Unit
override fun onBlockJoinRequest(recipient: Recipient) = Unit
override fun onRecipientNameClicked(target: RecipientId) = Unit
override fun onInviteToSignalClicked() = Unit
override fun onActivatePaymentsClicked() = Unit
override fun onSendPaymentClicked(recipientId: RecipientId) = Unit
override fun onScheduledIndicatorClicked(view: View, conversationMessage: ConversationMessage) = Unit
override fun onUrlClicked(url: String): Boolean = false
override fun onViewGiftBadgeClicked(messageRecord: MessageRecord) = Unit
override fun onGiftBadgeRevealed(messageRecord: MessageRecord) = Unit
override fun goToMediaPreview(parent: ConversationItem?, sharedElement: View?, args: MediaIntentFactory.MediaPreviewArgs?) = Unit
override fun onEditedIndicatorClicked(messageRecord: MessageRecord) = Unit
override fun onShowGroupDescriptionClicked(groupName: String, description: String, shouldLinkifyWebLinks: Boolean) = Unit
override fun onJoinCallLink(callLinkRootKey: CallLinkRootKey) = Unit
override fun onItemClick(item: MultiselectPart?) = Unit
override fun onItemLongClick(itemView: View?, item: MultiselectPart?) = Unit
}
}

View File

@@ -6,7 +6,6 @@ import androidx.test.filters.FlakyTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.attachments.UriAttachment
@@ -65,7 +64,6 @@ class AttachmentTableTest {
}
@FlakyTest
@Ignore("test is flaky")
@Test
fun givenIdenticalAttachmentsInsertedForPreUpload_whenIUpdateAttachmentDataAndSpecifyOnlyModifyThisAttachment_thenIExpectDifferentFileInfos() {
val blob = BlobProvider.getInstance().forData(byteArrayOf(1, 2, 3, 4, 5)).createForSingleSessionInMemory()

View File

@@ -10,17 +10,13 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.signal.ringrtc.CallId
import org.signal.ringrtc.CallManager
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.SignalActivityRule
@RunWith(AndroidJUnit4::class)
class CallTableTest {
@get:Rule
val harness = SignalActivityRule(createGroup = true)
private val groupRecipientId: RecipientId
get() = harness.group!!.recipientId
val harness = SignalActivityRule()
@Test
fun givenACall_whenISetTimestamp_thenIExpectUpdatedTimestamp() {
@@ -28,13 +24,13 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
groupRecipientId,
harness.others[0],
CallTable.Direction.INCOMING,
now
)
SignalDatabase.calls.setTimestamp(callId, groupRecipientId, -1L)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
SignalDatabase.calls.setTimestamp(callId, harness.others[0], -1L)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(-1L, call?.timestamp)
@@ -49,15 +45,15 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
groupRecipientId,
harness.others[0],
CallTable.Direction.INCOMING,
now
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
SignalDatabase.calls.deleteGroupCall(call!!)
val deletedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val deletedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
val oldestDeletionTimestamp = SignalDatabase.calls.getOldestDeletionTimestamp()
assertEquals(CallTable.Event.DELETE, deletedCall?.event)
@@ -70,12 +66,12 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(
callId,
groupRecipientId,
harness.others[0],
CallTable.Direction.OUTGOING,
System.currentTimeMillis()
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
val oldestDeletionTimestamp = SignalDatabase.calls.getOldestDeletionTimestamp()
@@ -90,12 +86,12 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
groupRecipientId,
harness.others[0],
CallTable.Direction.OUTGOING,
System.currentTimeMillis()
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.OUTGOING_RING, call?.event)
assertEquals(harness.self.id, call?.ringerRecipient)
@@ -107,12 +103,12 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
groupRecipientId,
harness.others[0],
CallTable.Direction.INCOMING,
System.currentTimeMillis()
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.JOINED, call?.event)
assertNull(call?.ringerRecipient)
@@ -124,13 +120,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = groupRecipientId,
groupRecipientId = harness.others[0],
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.RINGING, call?.event)
@@ -138,7 +134,7 @@ class CallTableTest {
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@@ -147,13 +143,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = groupRecipientId,
groupRecipientId = harness.others[0],
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
@@ -161,7 +157,7 @@ class CallTableTest {
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@@ -170,13 +166,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = groupRecipientId,
groupRecipientId = harness.others[0],
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
@@ -184,7 +180,7 @@ class CallTableTest {
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@@ -193,7 +189,7 @@ class CallTableTest {
val era = "aaa"
val callId = CallId.fromEra(era).longValue()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = groupRecipientId,
groupRecipientId = harness.others[0],
sender = harness.others[1],
timestamp = System.currentTimeMillis(),
peekGroupCallEraId = "aaa",
@@ -201,7 +197,7 @@ class CallTableTest {
isCallFull = false
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
@@ -209,7 +205,7 @@ class CallTableTest {
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertEquals(CallTable.Event.JOINED, acceptedCall?.event)
}
@@ -218,7 +214,7 @@ class CallTableTest {
val era = "aaa"
val callId = CallId.fromEra(era).longValue()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = groupRecipientId,
groupRecipientId = harness.others[0],
sender = harness.others[1],
timestamp = System.currentTimeMillis(),
peekGroupCallEraId = "aaa",
@@ -226,7 +222,7 @@ class CallTableTest {
isCallFull = false
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
}
@@ -237,7 +233,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = groupRecipientId,
groupRecipientId = harness.others[0],
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -245,13 +241,13 @@ class CallTableTest {
isCallFull = false
)
SignalDatabase.calls.getCallById(callId, groupRecipientId).let {
SignalDatabase.calls.getCallById(callId, harness.others[0]).let {
assertNotNull(it)
assertEquals(now, it?.timestamp)
}
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = groupRecipientId,
groupRecipientId = harness.others[0],
sender = harness.others[1],
timestamp = 1L,
peekGroupCallEraId = "aaa",
@@ -259,7 +255,7 @@ class CallTableTest {
isCallFull = false
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
assertEquals(1L, call?.timestamp)
@@ -270,20 +266,20 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(
callId = callId,
recipientId = groupRecipientId,
recipientId = harness.others[0],
direction = CallTable.Direction.INCOMING,
timestamp = System.currentTimeMillis()
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = groupRecipientId,
groupRecipientId = harness.others[0],
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.DELETE, call?.event)
}
@@ -294,7 +290,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = groupRecipientId,
groupRecipientId = harness.others[0],
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -304,13 +300,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.RINGING, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -323,20 +319,20 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
groupRecipientId,
harness.others[0],
CallTable.Direction.INCOMING,
now
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -348,7 +344,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = groupRecipientId,
groupRecipientId = harness.others[0],
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -358,13 +354,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -376,7 +372,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = groupRecipientId,
groupRecipientId = harness.others[0],
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -386,7 +382,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
@@ -394,13 +390,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -413,20 +409,20 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
groupRecipientId,
harness.others[0],
CallTable.Direction.INCOMING,
now
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_LOCALLY
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
}
@@ -438,20 +434,20 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
groupRecipientId,
harness.others[0],
CallTable.Direction.INCOMING,
now
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
}
@@ -462,7 +458,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = groupRecipientId,
groupRecipientId = harness.others[0],
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -472,7 +468,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
@@ -480,13 +476,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_LOCALLY
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
}
@@ -497,7 +493,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = groupRecipientId,
groupRecipientId = harness.others[0],
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -507,7 +503,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
@@ -515,13 +511,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
}
@@ -532,7 +528,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = groupRecipientId,
groupRecipientId = harness.others[0],
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -542,13 +538,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
}
@@ -560,7 +556,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
@@ -568,13 +564,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
}
@@ -586,7 +582,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.EXPIRED_REQUEST
@@ -594,13 +590,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
}
@@ -612,20 +608,20 @@ class CallTableTest {
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
groupRecipientId,
harness.others[0],
CallTable.Direction.OUTGOING,
System.currentTimeMillis()
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.OUTGOING_RING, call?.event)
}
@@ -635,13 +631,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.RINGING, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -653,13 +649,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -671,13 +667,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.CANCELLED_BY_RINGER
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -689,13 +685,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_LOCALLY
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertNotNull(call?.messageId)
@@ -706,13 +702,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertNotNull(call?.messageId)
@@ -723,13 +719,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
assertNotNull(call?.messageId)
@@ -740,13 +736,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
groupRecipientId,
harness.others[0],
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
assertNotNull(call?.messageId)

View File

@@ -82,7 +82,7 @@ class MessageTableTest_gifts {
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2,
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
)
@@ -102,7 +102,7 @@ class MessageTableTest_gifts {
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2,
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
)
@@ -121,13 +121,13 @@ class MessageTableTest_gifts {
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2,
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
)
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 3,
sentTimeMillis = 1,
giftBadge = null
)
@@ -146,13 +146,13 @@ class MessageTableTest_gifts {
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2,
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
)
val messageId3 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 3,
sentTimeMillis = 1,
giftBadge = null
)
@@ -171,13 +171,13 @@ class MessageTableTest_gifts {
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2,
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
)
val messageId3 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 3,
sentTimeMillis = 1,
giftBadge = null
)

View File

@@ -253,7 +253,8 @@ class MmsTableTest_stories {
val groupStoryId = MmsHelper.insert(
recipient = myStory,
sentTimeMillis = 200,
storyType = StoryType.STORY_WITH_REPLIES
storyType = StoryType.STORY_WITH_REPLIES,
threadId = -1L
)
// WHEN
@@ -318,7 +319,8 @@ class MmsTableTest_stories {
val groupStoryId = MmsHelper.insert(
recipient = myStory,
sentTimeMillis = 200,
storyType = StoryType.STORY_WITH_REPLIES
storyType = StoryType.STORY_WITH_REPLIES,
threadId = -1L
)
MmsHelper.insert(
@@ -329,7 +331,7 @@ class MmsTableTest_stories {
receivedTimeMillis = 202,
parentStoryId = ParentStoryId.GroupReply(groupStoryId)
),
SignalDatabase.threads.getOrCreateThreadIdFor(myStory, ThreadTable.DistributionTypes.DEFAULT)
-1
)
// WHEN

View File

@@ -1,60 +0,0 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.storage.StorageRecordUpdate
import org.thoughtcrime.securesms.storage.StorageSyncModels
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.MessageTableTestUtils
import org.whispersystems.signalservice.api.storage.SignalContactRecord
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class RecipientTableTest_applyStorageSyncContactUpdate {
@get:Rule
val harness = SignalActivityRule()
@Test
fun insertMessageOnVerifiedToDefault() {
// GIVEN
val identities = ApplicationDependencies.getProtocolStore().aci().identities()
val other = Recipient.resolved(harness.others[0])
MmsHelper.insert(recipient = other)
identities.setVerified(other.id, harness.othersKeys[0].publicKey, IdentityTable.VerifiedStatus.VERIFIED)
val oldRecord: SignalContactRecord = StorageSyncModels.localToRemoteRecord(SignalDatabase.recipients.getRecordForSync(harness.others[0])!!).contact.get()
val newProto = oldRecord
.toProto()
.toBuilder()
.setIdentityState(ContactRecord.IdentityState.DEFAULT)
.build()
val newRecord = SignalContactRecord(oldRecord.id, newProto)
val update = StorageRecordUpdate<SignalContactRecord>(oldRecord, newRecord)
// WHEN
val oldVerifiedStatus: IdentityTable.VerifiedStatus = identities.getIdentityRecord(other.id).get().verifiedStatus
SignalDatabase.recipients.applyStorageSyncContactUpdate(update)
val newVerifiedStatus: IdentityTable.VerifiedStatus = identities.getIdentityRecord(other.id).get().verifiedStatus
// THEN
oldVerifiedStatus assertIs IdentityTable.VerifiedStatus.VERIFIED
newVerifiedStatus assertIs IdentityTable.VerifiedStatus.DEFAULT
val messages = MessageTableTestUtils.getMessages(SignalDatabase.threads.getThreadIdFor(other.id)!!)
messages.first().isIdentityDefault assertIs true
}
}

View File

@@ -47,7 +47,6 @@ import org.whispersystems.signalservice.api.push.ServiceId
import java.util.Optional
import java.util.UUID
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class RecipientTableTest_getAndPossiblyMerge {
@@ -858,7 +857,6 @@ class RecipientTableTest_getAndPossiblyMerge {
}
ApplicationDependencies.getRecipientCache().clear()
ApplicationDependencies.getRecipientCache().clearSelf()
RecipientId.clearCache()
}
@@ -874,7 +872,7 @@ class RecipientTableTest_getAndPossiblyMerge {
if (createThread) {
// Create a thread and throw a dummy message in it so it doesn't get automatically deleted
SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(id))
SignalDatabase.messages.insertMessageInbox(IncomingEncryptedMessage(IncomingTextMessage(id, 1, (Math.random() * Long.MAX_VALUE).toLong(), 0, 0, "", Optional.empty(), 0, false, ""), ""))
SignalDatabase.messages.insertMessageInbox(IncomingEncryptedMessage(IncomingTextMessage(id, 1, 0, 0, 0, "", Optional.empty(), 0, false, ""), ""))
}
if (pniSession) {

View File

@@ -12,14 +12,12 @@ import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.push.ServiceId
import java.util.UUID
@@ -27,9 +25,6 @@ import java.util.UUID
@RunWith(AndroidJUnit4::class)
class StorySendTableTest {
@get:Rule
val harness = SignalActivityRule(othersCount = 0, createGroup = false)
private val distributionId1 = DistributionId.from(UUID.randomUUID())
private val distributionId2 = DistributionId.from(UUID.randomUUID())
private val distributionId3 = DistributionId.from(UUID.randomUUID())

View File

@@ -33,7 +33,6 @@ import org.whispersystems.signalservice.internal.configuration.SignalKeyBackupSe
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl
import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl
import org.whispersystems.signalservice.internal.configuration.SignalSvr2Url
import java.security.KeyStore
import java.util.Optional
@@ -75,20 +74,20 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
serviceTrustStore = SignalServiceTrustStore(application)
uncensoredConfiguration = SignalServiceConfiguration(
signalServiceUrls = arrayOf(SignalServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalCdnUrlMap = mapOf(
arrayOf(SignalServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
mapOf(
0 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
2 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT))
),
signalKeyBackupServiceUrls = arrayOf(SignalKeyBackupServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalStorageUrls = arrayOf(SignalStorageUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalCdsiUrls = arrayOf(SignalCdsiUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalSvr2Urls = arrayOf(SignalSvr2Url(baseUrl, serviceTrustStore, "localhost", ConnectionSpec.CLEARTEXT)),
networkInterceptors = emptyList(),
dns = Optional.of(SignalServiceNetworkAccess.DNS),
signalProxy = Optional.empty(),
zkGroupServerPublicParams = Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS),
genericServerPublicParams = Base64.decode(BuildConfig.GENERIC_SERVER_PUBLIC_PARAMS)
arrayOf(SignalKeyBackupServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
arrayOf(SignalStorageUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
arrayOf(SignalCdsiUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
emptyArray(),
emptyList(),
Optional.of(SignalServiceNetworkAccess.DNS),
Optional.empty(),
Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS),
Base64.decode(BuildConfig.GENERIC_SERVER_PUBLIC_PARAMS)
)
serviceNetworkAccessMock = mock {

View File

@@ -0,0 +1,212 @@
package org.thoughtcrime.securesms.jobs
import androidx.test.ext.junit.runners.AndroidJUnit4
import okhttp3.mockwebserver.MockResponse
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.libsignal.protocol.ecc.Curve
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testing.Get
import org.thoughtcrime.securesms.testing.Put
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.testing.assertIsNot
import org.thoughtcrime.securesms.testing.parsedRequestBody
import org.thoughtcrime.securesms.testing.success
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity
import org.whispersystems.signalservice.internal.push.PreKeyState
import org.whispersystems.signalservice.internal.push.PreKeyStatus
@RunWith(AndroidJUnit4::class)
class PreKeysSyncJobTest {
@get:Rule
val harness = SignalActivityRule()
private val aciPreKeyMeta: PreKeyMetadataStore
get() = SignalStore.account().aciPreKeys
private val pniPreKeyMeta: PreKeyMetadataStore
get() = SignalStore.account().pniPreKeys
private lateinit var job: PreKeysSyncJob
@Before
fun setUp() {
job = PreKeysSyncJob()
}
@After
fun tearDown() {
InstrumentationApplicationDependencyProvider.clearHandlers()
}
/**
* Create signed prekeys for both identities when both do not have registered prekeys according
* to our local state.
*/
@Test
fun runWithoutRegisteredKeysForBothIdentities() {
// GIVEN
aciPreKeyMeta.isSignedPreKeyRegistered = false
pniPreKeyMeta.isSignedPreKeyRegistered = false
lateinit var aciSignedPreKey: SignedPreKeyEntity
lateinit var pniSignedPreKey: SignedPreKeyEntity
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Put("/v2/keys/signed?identity=aci") { r ->
aciSignedPreKey = r.parsedRequestBody()
MockResponse().success()
},
Put("/v2/keys/signed?identity=pni") { r ->
pniSignedPreKey = r.parsedRequestBody()
MockResponse().success()
}
)
// WHEN
val result: Job.Result = job.run()
// THEN
result.isSuccess assertIs true
aciPreKeyMeta.isSignedPreKeyRegistered assertIs true
pniPreKeyMeta.isSignedPreKeyRegistered assertIs true
val aciVerifySignatureResult = Curve.verifySignature(
ApplicationDependencies.getProtocolStore().aci().identityKeyPair.publicKey.publicKey,
aciSignedPreKey.publicKey.serialize(),
aciSignedPreKey.signature
)
aciVerifySignatureResult assertIs true
val pniVerifySignatureResult = Curve.verifySignature(
ApplicationDependencies.getProtocolStore().pni().identityKeyPair.publicKey.publicKey,
pniSignedPreKey.publicKey.serialize(),
pniSignedPreKey.signature
)
pniVerifySignatureResult assertIs true
}
/**
* With 100 prekeys registered for each identity, do nothing.
*/
@Test
fun runWithRegisteredKeysForBothIdentities() {
// GIVEN
val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId
val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(100)) },
Get("/v2/keys?identity=pni") { MockResponse().success(PreKeyStatus(100)) }
)
// WHEN
val result: Job.Result = job.run()
// THEN
result.isSuccess assertIs true
aciPreKeyMeta.activeSignedPreKeyId assertIs currentAciKeyId
pniPreKeyMeta.activeSignedPreKeyId assertIs currentPniKeyId
}
/**
* With 100 prekeys registered for ACI, but no PNI prekeys registered according to local state,
* do nothing for ACI but create PNI prekeys and update local state.
*/
@Test
fun runWithRegisteredKeysForAciIdentityOnly() {
// GIVEN
pniPreKeyMeta.isSignedPreKeyRegistered = false
val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId
val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(100)) },
Put("/v2/keys/signed?identity=pni") { MockResponse().success() }
)
// WHEN
val result: Job.Result = job.run()
// THEN
result.isSuccess assertIs true
pniPreKeyMeta.isSignedPreKeyRegistered assertIs true
aciPreKeyMeta.activeSignedPreKeyId assertIs currentAciKeyId
pniPreKeyMeta.activeSignedPreKeyId assertIsNot currentPniKeyId
}
/**
* With <10 prekeys registered for each identity, upload new.
*/
@Test
fun runWithLowNumberOfRegisteredKeysForBothIdentities() {
// GIVEN
val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId
val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId
val currentNextAciPreKeyId = aciPreKeyMeta.nextOneTimePreKeyId
val currentNextPniPreKeyId = pniPreKeyMeta.nextOneTimePreKeyId
lateinit var aciPreKeyStateRequest: PreKeyState
lateinit var pniPreKeyStateRequest: PreKeyState
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(5)) },
Get("/v2/keys?identity=pni") { MockResponse().success(PreKeyStatus(5)) },
Put("/v2/keys/?identity=aci") { r ->
aciPreKeyStateRequest = r.parsedRequestBody()
MockResponse().success()
},
Put("/v2/keys/?identity=pni") { r ->
pniPreKeyStateRequest = r.parsedRequestBody()
MockResponse().success()
}
)
// WHEN
val result: Job.Result = job.run()
// THEN
result.isSuccess assertIs true
aciPreKeyMeta.activeSignedPreKeyId assertIsNot currentAciKeyId
pniPreKeyMeta.activeSignedPreKeyId assertIsNot currentPniKeyId
aciPreKeyMeta.nextOneTimePreKeyId assertIsNot currentNextAciPreKeyId
pniPreKeyMeta.nextOneTimePreKeyId assertIsNot currentNextPniPreKeyId
ApplicationDependencies.getProtocolStore().aci().identityKeyPair.publicKey.let { aciIdentityKey ->
aciPreKeyStateRequest.identityKey assertIs aciIdentityKey
val verifySignatureResult = Curve.verifySignature(
aciIdentityKey.publicKey,
aciPreKeyStateRequest.signedPreKey.publicKey.serialize(),
aciPreKeyStateRequest.signedPreKey.signature
)
verifySignatureResult assertIs true
}
ApplicationDependencies.getProtocolStore().pni().identityKeyPair.publicKey.let { pniIdentityKey ->
pniPreKeyStateRequest.identityKey assertIs pniIdentityKey
val verifySignatureResult = Curve.verifySignature(
pniIdentityKey.publicKey,
pniPreKeyStateRequest.signedPreKey.publicKey.serialize(),
pniPreKeyStateRequest.signedPreKey.signature
)
verifySignatureResult assertIs true
}
}
}

View File

@@ -12,7 +12,6 @@ import org.signal.libsignal.usernames.Username
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testing.Delete
import org.thoughtcrime.securesms.testing.Get
import org.thoughtcrime.securesms.testing.Put
import org.thoughtcrime.securesms.testing.SignalActivityRule
@@ -37,11 +36,6 @@ class RefreshOwnProfileJob__checkUsernameIsInSyncTest {
@Test
fun givenNoLocalUsername_whenICheckUsernameIsInSync_thenIExpectNoFailures() {
// GIVEN
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Delete("/v1/accounts/username_hash") { MockResponse().success() }
)
// WHEN
RefreshOwnProfileJob.checkUsernameIsInSync()
}

View File

@@ -22,10 +22,10 @@ public final class PinHashing_hashPin_Test {
@Test
public void argon2_hashed_pin_password() throws IOException {
String pin = "password";
byte[] salt = Hex.fromStringCondensed("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
byte[] backupId = Hex.fromStringCondensed("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"));
PinHash hashedPin = PinHashUtil.hashPin(pin, salt);
PinHash hashedPin = PinHashUtil.hashPin(pin, new byte[]{});
KbsData kbsData = PinHashUtil.createNewKbsData(hashedPin, masterKey);
assertArrayEquals(hashedPin.accessKey(), kbsData.getKbsAccessKey());
@@ -40,10 +40,10 @@ public final class PinHashing_hashPin_Test {
@Test
public void argon2_hashed_pin_another_password() throws IOException {
String pin = "anotherpassword";
byte[] salt = Hex.fromStringCondensed("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f");
byte[] backupId = Hex.fromStringCondensed("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f");
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("88a787415a2ecd79da0d1016a82a27c5c695c9a19b88b0aa1d35683280aa9a67"));
PinHash hashedPin = PinHashUtil.hashPin(pin, salt);
PinHash hashedPin = PinHashUtil.hashPin(pin, new byte[]{});
KbsData kbsData = PinHashUtil.createNewKbsData(hashedPin, masterKey);
assertArrayEquals(hashedPin.accessKey(), kbsData.getKbsAccessKey());
@@ -58,10 +58,10 @@ public final class PinHashing_hashPin_Test {
@Test
public void argon2_hashed_pin_password_with_spaces_diacritics_and_non_arabic_numerals() throws IOException {
String pin = " Pass६örd ";
byte[] salt = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8");
byte[] backupId = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8");
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("9571f3fde1e58588ba49bcf82be1b301ca3859a6f59076f79a8f47181ef952bf"));
PinHash hashedPin = PinHashUtil.hashPin(pin, salt);
PinHash hashedPin = PinHashUtil.hashPin(pin, new byte[]{});
KbsData kbsData = PinHashUtil.createNewKbsData(hashedPin, masterKey);
assertArrayEquals(hashedPin.accessKey(), kbsData.getKbsAccessKey());
@@ -78,10 +78,10 @@ public final class PinHashing_hashPin_Test {
@Test
public void argon2_hashed_pin_password_with_just_non_arabic_numerals() throws IOException {
String pin = " ६१८ ";
byte[] salt = Hex.fromStringCondensed("717dc111a98423a57196512606822fca646c653facd037c10728f14ba0be2ab3");
byte[] backupId = Hex.fromStringCondensed("717dc111a98423a57196512606822fca646c653facd037c10728f14ba0be2ab3");
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("0432d735b32f66d0e3a70d4f9cc821a8529521a4937d26b987715d8eff4e4c54"));
PinHash hashedPin = PinHashUtil.hashPin(pin, salt);
PinHash hashedPin = PinHashUtil.hashPin(pin, new byte[]{});
KbsData kbsData = PinHashUtil.createNewKbsData(hashedPin, masterKey);
assertArrayEquals(hashedPin.accessKey(), kbsData.getKbsAccessKey());

View File

@@ -0,0 +1,313 @@
package org.thoughtcrime.securesms.messages
import android.database.Cursor
import android.util.Base64
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.ThreadUtil
import org.signal.core.util.readToList
import org.signal.core.util.select
import org.signal.core.util.withinTransaction
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.database.MessageTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.ThreadTable
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testing.Entry
import org.thoughtcrime.securesms.testing.InMemoryLogger
import org.thoughtcrime.securesms.testing.MessageContentFuzzer
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.MessageTableTestUtils
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.api.messages.SignalServiceContent
import org.whispersystems.signalservice.api.messages.SignalServiceMetadata
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer
import org.whispersystems.signalservice.internal.serialize.SignalServiceMetadataProtobufSerializer
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
import java.util.Optional
@RunWith(AndroidJUnit4::class)
class MessageContentProcessorTestV2 {
companion object {
private val TAGS = listOf(MessageContentProcessor.TAG, MessageContentProcessorV2.TAG, AttachmentTable.TAG)
private val GENERALIZE_TAG = mapOf(
MessageContentProcessor.TAG to "MCP",
MessageContentProcessorV2.TAG to "MCP",
AttachmentTable.TAG to AttachmentTable.TAG
)
private val IGNORE_MESSAGE_COLUMNS = listOf(
MessageTable.DATE_RECEIVED,
MessageTable.NOTIFIED_TIMESTAMP,
MessageTable.REACTIONS_LAST_SEEN,
MessageTable.NOTIFIED
)
private val IGNORE_ATTACHMENT_COLUMNS = listOf(
AttachmentTable.UNIQUE_ID,
AttachmentTable.TRANSFER_FILE
)
}
@get:Rule
val harness = SignalActivityRule()
private lateinit var processorV1: MessageContentProcessor
private lateinit var processorV2: MessageContentProcessorV2
private lateinit var testResult: TestResults
private var envelopeTimestamp: Long = 0
@Before
fun setup() {
processorV1 = MessageContentProcessor(harness.context)
processorV2 = MessageContentProcessorV2(harness.context)
envelopeTimestamp = System.currentTimeMillis()
testResult = TestResults()
}
@Test
fun textMessage() {
var start = envelopeTimestamp
val messages: List<TestMessage> = (0 until 100).map {
start += 200
TestMessage(
envelope = MessageContentFuzzer.envelope(start),
content = MessageContentFuzzer.fuzzTextMessage(),
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
)
}
testResult.runV2(messages)
testResult.runV1(messages)
testResult.assert()
}
@Test
fun mediaMessage() {
var start = envelopeTimestamp
val textMessages: List<TestMessage> = (0 until 10).map {
start += 200
TestMessage(
envelope = MessageContentFuzzer.envelope(start),
content = MessageContentFuzzer.fuzzTextMessage(),
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
)
}
val firstBatchMediaMessages: List<TestMessage> = (0 until 10).map {
start += 200
TestMessage(
envelope = MessageContentFuzzer.envelope(start),
content = MessageContentFuzzer.fuzzMediaMessageWithBody(textMessages),
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
)
}
val secondBatchNoContentMediaMessages: List<TestMessage> = (0 until 10).map {
start += 200
TestMessage(
envelope = MessageContentFuzzer.envelope(start),
content = MessageContentFuzzer.fuzzMediaMessageNoContent(textMessages + firstBatchMediaMessages),
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
)
}
val thirdBatchNoTextMediaMessagesMessages: List<TestMessage> = (0 until 10).map {
start += 200
TestMessage(
envelope = MessageContentFuzzer.envelope(start),
content = MessageContentFuzzer.fuzzMediaMessageNoText(textMessages + firstBatchMediaMessages),
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
)
}
testResult.runV2(textMessages + firstBatchMediaMessages + secondBatchNoContentMediaMessages + thirdBatchNoTextMediaMessagesMessages)
testResult.runV1(textMessages + firstBatchMediaMessages + secondBatchNoContentMediaMessages + thirdBatchNoTextMediaMessagesMessages)
testResult.assert()
}
private inner class TestResults {
private lateinit var v1Logs: List<Entry>
private lateinit var v1Messages: List<List<Pair<String, String?>>>
private lateinit var v1Attachments: List<List<Pair<String, String?>>>
private lateinit var v2Logs: List<Entry>
private lateinit var v2Messages: List<List<Pair<String, String?>>>
private lateinit var v2Attachments: List<List<Pair<String, String?>>>
fun runV1(messages: List<TestMessage>) {
messages.forEach { (envelope, content, metadata, serverDeliveredTimestamp) ->
if (content.hasDataMessage()) {
processorV1.process(
MessageContentProcessor.MessageState.DECRYPTED_OK,
toSignalServiceContent(envelope, content, metadata, serverDeliveredTimestamp),
null,
envelope.timestamp,
-1
)
ThreadUtil.sleep(1)
}
}
v1Logs = harness.inMemoryLogger.logs()
harness.inMemoryLogger.clear()
v1Messages = dumpMessages()
v1Attachments = dumpAttachments()
}
fun runV2(messages: List<TestMessage>) {
messages.forEach { (envelope, content, metadata, serverDeliveredTimestamp) ->
if (content.hasDataMessage()) {
processorV2.process(
envelope,
content,
metadata,
serverDeliveredTimestamp,
false
)
ThreadUtil.sleep(1)
}
}
v2Logs = harness.inMemoryLogger.logs()
harness.inMemoryLogger.clear()
v2Messages = dumpMessages()
v2Attachments = dumpAttachments()
cleanup()
}
fun cleanup() {
SignalDatabase.rawDatabase.withinTransaction { db ->
SignalDatabase.threads.deleteAllConversations()
db.execSQL("DELETE FROM sqlite_sequence WHERE name = '${MessageTable.TABLE_NAME}'")
db.execSQL("DELETE FROM sqlite_sequence WHERE name = '${ThreadTable.TABLE_NAME}'")
db.execSQL("DELETE FROM sqlite_sequence WHERE name = '${AttachmentTable.TABLE_NAME}'")
}
}
fun assert() {
v2Logs.zip(v1Logs)
.forEach { (v2, v1) ->
GENERALIZE_TAG[v2.tag]!!.assertIs(GENERALIZE_TAG[v1.tag]!!)
if (v2.tag != AttachmentTable.TAG) {
if (v2.message?.startsWith("[") == true && v1.message?.startsWith("[") == false) {
v2.message!!.substring(v2.message!!.indexOf(']') + 2).assertIs(v1.message)
} else {
v2.message.assertIs(v1.message)
}
} else {
if (v2.message?.startsWith("Inserted attachment at ID: AttachmentId::") == true) {
v2.message!!
.substring(0, v2.message!!.indexOf(','))
.assertIs(
v1.message!!
.substring(0, v1.message!!.indexOf(','))
)
} else {
v2.message.assertIs(v1.message)
}
}
v2.throwable.assertIs(v1.throwable)
}
v2Messages.zip(v1Messages)
.forEach { (v2, v1) ->
v2.assertIs(v1)
}
v2Attachments.zip(v1Attachments)
.forEach { (v2, v1) ->
v2.assertIs(v1)
}
}
private fun InMemoryLogger.logs(): List<Entry> {
return entries()
.filter { TAGS.contains(it.tag) }
}
private fun dumpMessages(): List<List<Pair<String, String?>>> {
return dumpTable(MessageTable.TABLE_NAME)
.map { row ->
val newRow = row.toMutableList()
newRow.removeIf { IGNORE_MESSAGE_COLUMNS.contains(it.first) }
newRow
}
}
private fun dumpAttachments(): List<List<Pair<String, String?>>> {
return dumpTable(AttachmentTable.TABLE_NAME)
.map { row ->
val newRow = row.toMutableList()
newRow.removeIf { IGNORE_ATTACHMENT_COLUMNS.contains(it.first) }
newRow
}
}
private fun dumpTable(table: String): List<List<Pair<String, String?>>> {
return SignalDatabase.rawDatabase
.select()
.from(table)
.run()
.readToList { cursor ->
val map: List<Pair<String, String?>> = cursor.columnNames.map { column ->
val index = cursor.getColumnIndex(column)
var data: String? = when (cursor.getType(index)) {
Cursor.FIELD_TYPE_BLOB -> Base64.encodeToString(cursor.getBlob(index), 0)
else -> cursor.getString(index)
}
if (table == MessageTable.TABLE_NAME && column == MessageTable.TYPE) {
data = MessageTableTestUtils.typeColumnToString(cursor.getLong(index))
}
column to data
}
map
}
}
}
private fun toSignalServiceContent(envelope: SignalServiceProtos.Envelope, content: SignalServiceProtos.Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long): SignalServiceContent {
val localAddress = SignalServiceAddress(metadata.destinationServiceId, Optional.ofNullable(SignalStore.account().e164))
val signalServiceMetadata = SignalServiceMetadata(
SignalServiceAddress(metadata.sourceServiceId, Optional.ofNullable(metadata.sourceE164)),
metadata.sourceDeviceId,
envelope.timestamp,
envelope.serverTimestamp,
serverDeliveredTimestamp,
metadata.sealedSender,
envelope.serverGuid,
Optional.ofNullable(metadata.groupId),
metadata.destinationServiceId.toString()
)
val contentProto = SignalServiceContentProto.newBuilder()
.setLocalAddress(SignalServiceAddressProtobufSerializer.toProtobuf(localAddress))
.setMetadata(SignalServiceMetadataProtobufSerializer.toProtobuf(signalServiceMetadata))
.setContent(content)
.build()
return SignalServiceContent.createFromProto(contentProto)!!
}
}

View File

@@ -9,7 +9,6 @@ import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,7 +37,7 @@ import android.util.Log as AndroidLog
/**
* Sends N messages from Bob to Alice to track performance of Alice's processing of messages.
*/
@Ignore("Ignore test in normal testing as it's a performance test with no assertions")
// @Ignore("Ignore test in normal testing as it's a performance test with no assertions")
@RunWith(AndroidJUnit4::class)
class MessageProcessingPerformanceTest {

View File

@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.messages
import android.content.Context
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.testing.LogPredicate
import org.thoughtcrime.securesms.util.SignalLocalMetrics
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
@@ -19,9 +18,9 @@ class TimingMessageContentProcessorV2(context: Context) : MessageContentProcesso
fun endTag(timestamp: Long) = "$timestamp end"
}
override fun process(envelope: SignalServiceProtos.Envelope, content: SignalServiceProtos.Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long, processingEarlyContent: Boolean, localMetric: SignalLocalMetrics.MessageReceive?) {
override fun process(envelope: SignalServiceProtos.Envelope, content: SignalServiceProtos.Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long, processingEarlyContent: Boolean) {
Log.d(TAG, startTag(envelope.timestamp))
super.process(envelope, content, metadata, serverDeliveredTimestamp, processingEarlyContent, localMetric)
super.process(envelope, content, metadata, serverDeliveredTimestamp, processingEarlyContent)
Log.d(TAG, endTag(envelope.timestamp))
}
}

View File

@@ -32,7 +32,6 @@ import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import java.lang.UnsupportedOperationException
import java.util.Optional
import java.util.UUID
import java.util.concurrent.locks.ReentrantLock
@@ -169,10 +168,6 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
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 loadLastResortKyberPreKeys(): List<KyberPreKeyRecord> = throw UnsupportedOperationException()
override fun isMultiDevice(): Boolean = throw UnsupportedOperationException()
}
}

View File

@@ -1,7 +1,9 @@
package org.thoughtcrime.securesms.testing
import org.mockito.kotlin.anyOrNull
import io.reactivex.rxjava3.core.Single
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
import org.signal.core.util.Hex
import org.signal.libsignal.protocol.IdentityKeyPair
@@ -13,13 +15,17 @@ import org.signal.libsignal.svr2.PinHash
import org.thoughtcrime.securesms.crypto.PreKeyUtil
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.pin.KbsRepository
import org.thoughtcrime.securesms.pin.TokenData
import org.thoughtcrime.securesms.test.BuildConfig
import org.whispersystems.signalservice.api.KbsPinData
import org.whispersystems.signalservice.api.KeyBackupService
import org.whispersystems.signalservice.api.SvrPinData
import org.whispersystems.signalservice.api.kbs.MasterKey
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse
import org.whispersystems.signalservice.internal.push.AuthCredentials
import org.whispersystems.signalservice.internal.push.DeviceInfoList
import org.whispersystems.signalservice.internal.push.PreKeyEntity
@@ -40,8 +46,7 @@ object MockProvider {
val senderCertificate = SenderCertificate().apply { certificate = ByteArray(0) }
val lockedFailure = PushServiceSocket.RegistrationLockFailure().apply {
svr1Credentials = AuthCredentials.create("username", "password")
svr2Credentials = null
backupCredentials = AuthCredentials.create("username", "password")
}
val primaryOnlyDeviceList = DeviceInfoList().apply {
@@ -78,15 +83,26 @@ object MockProvider {
}
}
fun mockGetRegistrationLockStringFlow() {
fun mockGetRegistrationLockStringFlow(kbsRepository: KbsRepository) {
val tokenData: TokenData = mock {
on { enclave } doReturn BuildConfig.KBS_ENCLAVE
on { basicAuth } doReturn "basicAuth"
on { triesRemaining } doReturn 10
on { tokenResponse } doReturn TokenResponse()
}
kbsRepository.stub {
on { getToken(any() as? String) } doReturn Single.just(ServiceResponse.forResult(tokenData, 200, ""))
}
val session: KeyBackupService.RestoreSession = object : KeyBackupService.RestoreSession {
override fun hashSalt(): ByteArray = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8")
override fun restorePin(hashedPin: PinHash?): SvrPinData = SvrPinData(MasterKey.createNew(SecureRandom()), null)
override fun restorePin(hashedPin: PinHash?): KbsPinData = KbsPinData(MasterKey.createNew(SecureRandom()), null)
}
val kbsService = ApplicationDependencies.getKeyBackupService(BuildConfig.KBS_ENCLAVE)
kbsService.stub {
on { newRegistrationSession(anyOrNull(), anyOrNull()) } doReturn session
on { newRegistrationSession(any(), any()) } doReturn session
}
}

View File

@@ -22,8 +22,6 @@ class Put(path: String, responseFactory: ResponseFactory) : Verb(defaultRequestP
class Post(path: String, responseFactory: ResponseFactory) : Verb(defaultRequestPredicate("POST", path), responseFactory)
class Delete(path: String, responseFactory: ResponseFactory) : Verb(defaultRequestPredicate("DELETE", path), responseFactory)
fun MockResponse.success(response: Any? = null): MockResponse {
return setResponseCode(200).apply {
if (response != null) {

View File

@@ -29,7 +29,6 @@ import org.thoughtcrime.securesms.registration.RegistrationData
import org.thoughtcrime.securesms.registration.RegistrationRepository
import org.thoughtcrime.securesms.registration.RegistrationUtil
import org.thoughtcrime.securesms.registration.VerifyResponse
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.ACI
@@ -37,6 +36,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.ServiceResponseProcessor
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
import java.lang.IllegalArgumentException
import java.util.UUID
/**
@@ -45,7 +45,7 @@ import java.util.UUID
*
* To use: `@get:Rule val harness = SignalActivityRule()`
*/
class SignalActivityRule(private val othersCount: Int = 4, private val createGroup: Boolean = false) : ExternalResource() {
class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource() {
val application: Application = ApplicationDependencies.getApplication()
@@ -57,9 +57,6 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
private set
lateinit var othersKeys: List<IdentityKeyPair>
var group: GroupTestingUtils.TestGroupInfo? = null
private set
val inMemoryLogger: InMemoryLogger
get() = (application as SignalInstrumentationApplicationContext).inMemoryLogger
@@ -71,15 +68,6 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
others = setupOthers.first
othersKeys = setupOthers.second
if (createGroup && others.size >= 2) {
group = GroupTestingUtils.insertGroup(
revision = 0,
self.asMember(),
others[0].asMember(),
others[1].asMember()
)
}
InstrumentationApplicationDependencyProvider.clearHandlers()
}
@@ -90,9 +78,6 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
val preferences: SharedPreferences = application.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0)
preferences.edit().putBoolean("passphrase_initialized", true).commit()
SignalStore.account().generateAciIdentityKeyIfNecessary()
SignalStore.account().generatePniIdentityKeyIfNecessary()
val registrationRepository = RegistrationRepository(application)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(Put("/v2/keys") { MockResponse().success() })
@@ -107,19 +92,13 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
pniRegistrationId = registrationRepository.pniRegistrationId,
recoveryPassword = "asdfasdfasdfasdf"
),
VerifyResponse(
verifyAccountResponse = VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false),
masterKey = null,
pin = null,
aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().aciPreKeys),
pniPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().pniPreKeys)
),
VerifyResponse(VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false), null, null),
false
).blockingGet()
ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow
SignalStore.svr().optOut()
SignalStore.kbsValues().optOut()
RegistrationUtil.maybeMarkRegistrationComplete()
SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson"))

View File

@@ -1,14 +1,17 @@
package org.signal.benchmark
import android.content.Context
import org.signal.libsignal.protocol.IdentityKey
import org.signal.libsignal.protocol.state.PreKeyRecord
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
import org.thoughtcrime.securesms.BuildConfig
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.push.AccountManagerFactory
import org.thoughtcrime.securesms.util.FeatureFlags
import org.whispersystems.signalservice.api.SignalServiceAccountManager
import org.whispersystems.signalservice.api.account.PreKeyUpload
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceIdType
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
import java.io.IOException
import java.util.Optional
@@ -34,7 +37,7 @@ class DummyAccountManagerFactory : AccountManagerFactory() {
}
@Throws(IOException::class)
override fun setPreKeys(preKeyUpload: PreKeyUpload) {
override fun setPreKeys(serviceIdType: ServiceIdType, identityKey: IdentityKey, signedPreKey: SignedPreKeyRecord, oneTimePreKeys: List<PreKeyRecord>) {
}
}
}

View File

@@ -148,7 +148,6 @@ object TestMessages {
1024,
1024,
Optional.empty(),
Optional.empty(),
Optional.of("/not-there.jpg"),
false,
false,
@@ -170,7 +169,6 @@ object TestMessages {
1024,
1024,
Optional.empty(),
Optional.empty(),
Optional.of("/not-there.aac"),
true,
false,

View File

@@ -28,6 +28,7 @@ import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.ServiceResponseProcessor
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
import java.util.UUID
object TestUsers {
private var generatedOthers: Int = 0
@@ -53,7 +54,7 @@ object TestUsers {
pniRegistrationId = registrationRepository.pniRegistrationId,
recoveryPassword = "asdfasdfasdfasdf"
)
val verifyResponse = VerifyResponse(VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false), null, null, null, null)
val verifyResponse = VerifyResponse(VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false), null, null)
AccountManagerFactory.setInstance(DummyAccountManagerFactory())
val response: ServiceResponse<VerifyResponse> = registrationRepository.registerAccount(
registrationData,
@@ -62,7 +63,7 @@ object TestUsers {
).blockingGet()
ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow
SignalStore.svr().optOut()
SignalStore.kbsValues().optOut()
RegistrationUtil.maybeMarkRegistrationComplete()
SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson"))

View File

@@ -1,85 +0,0 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.springboard
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.fragment.findNavController
import androidx.navigation.navGraphViewModels
import org.signal.core.ui.Rows
import org.signal.core.ui.Scaffolds
import org.signal.core.ui.theme.SignalTheme
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.compose.ComposeFragment
/**
* Configuration fragment for the internal conversation test fragment.
*/
class InternalConversationSpringboardFragment : ComposeFragment() {
private val viewModel: InternalConversationSpringboardViewModel by navGraphViewModels(R.id.app_settings)
@Composable
override fun FragmentContent() {
Content(this::navigateBack, this::launchTestFragment, viewModel.hasWallpaper)
}
private fun navigateBack() {
findNavController().popBackStack()
}
private fun launchTestFragment() {
findNavController().navigate(
InternalConversationSpringboardFragmentDirections
.actionInternalConversationSpringboardFragmentToInternalConversationTestFragment()
)
}
}
@Preview
@Composable
private fun ContentPreview() {
val hasWallpaper = remember { mutableStateOf(false) }
SignalTheme(isDarkMode = true) {
Content(onBackPressed = {}, onLaunchTestFragment = {}, hasWallpaper = hasWallpaper)
}
}
@Composable
private fun Content(
onBackPressed: () -> Unit,
onLaunchTestFragment: () -> Unit,
hasWallpaper: MutableState<Boolean>
) {
Scaffolds.Settings(
title = "Conversation Test Springboard",
onNavigationClick = onBackPressed,
navigationIconPainter = rememberVectorPainter(ImageVector.vectorResource(id = R.drawable.symbol_arrow_left_24))
) {
Column(modifier = Modifier.padding(it)) {
Rows.TextRow(
text = "Launch Conversation Test Fragment",
onClick = onLaunchTestFragment
)
Rows.ToggleRow(
checked = hasWallpaper.value,
text = "Enable Wallpaper",
onCheckChanged = { hasWallpaper.value = it }
)
}
}
}

View File

@@ -1,13 +0,0 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.springboard
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
class InternalConversationSpringboardViewModel : ViewModel() {
val hasWallpaper = mutableStateOf(false)
}

View File

@@ -1,140 +0,0 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.test
import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationMessageFactory
import org.thoughtcrime.securesms.conversation.v2.data.ConversationElementKey
import org.thoughtcrime.securesms.conversation.v2.data.IncomingTextOnly
import org.thoughtcrime.securesms.conversation.v2.data.OutgoingTextOnly
import org.thoughtcrime.securesms.database.MessageTypes
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.mms.SlideDeck
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
import java.security.SecureRandom
import kotlin.time.Duration.Companion.milliseconds
/**
* Generates random conversation messages via the given set of parameters.
*/
class ConversationElementGenerator {
private val mappingModelCache = mutableMapOf<ConversationElementKey, MappingModel<*>>()
private val random = SecureRandom()
private val wordBank = listOf(
"A",
"Test",
"Message",
"To",
"Display",
"Content",
"In",
"Bubbles",
"User",
"Signal",
"The"
)
fun getMappingModel(key: ConversationElementKey): MappingModel<*> {
val cached = mappingModelCache[key]
if (cached != null) {
return cached
}
val messageModel = generateMessage(key)
mappingModelCache[key] = messageModel
return messageModel
}
private fun getIncomingType(): Long {
return MessageTypes.BASE_INBOX_TYPE or MessageTypes.SECURE_MESSAGE_BIT
}
private fun getSentOutgoingType(): Long {
return MessageTypes.BASE_SENT_TYPE or MessageTypes.SECURE_MESSAGE_BIT
}
private fun getSentFailedOutgoingType(): Long {
return MessageTypes.BASE_SENT_FAILED_TYPE or MessageTypes.SECURE_MESSAGE_BIT
}
private fun getPendingOutgoingType(): Long {
return MessageTypes.BASE_OUTBOX_TYPE or MessageTypes.SECURE_MESSAGE_BIT
}
private fun generateMessage(key: ConversationElementKey): MappingModel<*> {
val messageId = key.requireMessageId()
val now = getNow()
val testMessageWordLength = random.nextInt(3) + 1
val testMessage = (0 until testMessageWordLength).map {
wordBank.random()
}.joinToString(" ")
val isIncoming = random.nextBoolean()
val record = MediaMmsMessageRecord(
messageId,
if (isIncoming) Recipient.UNKNOWN else Recipient.self(),
0,
if (isIncoming) Recipient.self() else Recipient.UNKNOWN,
now,
now,
now,
1,
1,
testMessage,
SlideDeck(),
if (isIncoming) getIncomingType() else getPendingOutgoingType(),
emptySet(),
emptySet(),
0,
0,
0,
false,
1,
null,
emptyList(),
emptyList(),
false,
emptyList(),
false,
false,
now,
1,
now,
null,
StoryType.NONE,
null,
null,
null,
null,
-1,
null,
null,
0
)
val conversationMessage = ConversationMessageFactory.createWithUnresolvedData(
ApplicationDependencies.getApplication(),
record,
Recipient.UNKNOWN
)
return if (isIncoming) {
IncomingTextOnly(conversationMessage)
} else {
OutgoingTextOnly(conversationMessage)
}
}
private fun getNow(): Long {
val now = System.currentTimeMillis()
return now - random.nextInt(20.milliseconds.inWholeMilliseconds.toInt()).toLong()
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.test
import org.signal.paging.PagedDataSource
import org.thoughtcrime.securesms.conversation.v2.data.ConversationElementKey
import org.thoughtcrime.securesms.conversation.v2.data.ConversationMessageElement
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
import kotlin.math.min
class InternalConversationTestDataSource(
private val size: Int,
private val generator: ConversationElementGenerator
) : PagedDataSource<ConversationElementKey, MappingModel<*>> {
override fun size(): Int = size
override fun load(start: Int, length: Int, totalSize: Int, cancellationSignal: PagedDataSource.CancellationSignal): MutableList<MappingModel<*>> {
val end = min(start + length, totalSize)
return (start until end).map {
load(ConversationElementKey.forMessage(it.toLong()))!!
}.toMutableList()
}
override fun getKey(data: MappingModel<*>): ConversationElementKey {
check(data is ConversationMessageElement)
return ConversationElementKey.forMessage(data.conversationMessage.messageRecord.id)
}
override fun load(key: ConversationElementKey?): MappingModel<*>? {
return key?.let { generator.getMappingModel(it) }
}
}

View File

@@ -1,300 +0,0 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.test
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.navigation.navGraphViewModels
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.CallLinkRootKey
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.ViewBinderDelegate
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager
import org.thoughtcrime.securesms.components.settings.app.internal.conversation.springboard.InternalConversationSpringboardViewModel
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState
import org.thoughtcrime.securesms.contactshare.Contact
import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener
import org.thoughtcrime.securesms.conversation.ConversationItem
import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette
import org.thoughtcrime.securesms.conversation.colors.Colorizer
import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
import org.thoughtcrime.securesms.conversation.v2.ConversationAdapterV2
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.databinding.ConversationTestFragmentBinding
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.mms.GlideApp
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stickers.StickerLocator
import org.thoughtcrime.securesms.util.doAfterNextLayout
class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fragment) {
companion object {
private val TAG = Log.tag(InternalConversationTestFragment::class.java)
}
private val binding by ViewBinderDelegate(ConversationTestFragmentBinding::bind)
private val viewModel: InternalConversationTestViewModel by viewModels()
private val lifecycleDisposable = LifecycleDisposable()
private val springboardViewModel: InternalConversationSpringboardViewModel by navGraphViewModels(R.id.app_settings)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val adapter = ConversationAdapterV2(
lifecycleOwner = viewLifecycleOwner,
glideRequests = GlideApp.with(this),
clickListener = ClickListener(),
hasWallpaper = springboardViewModel.hasWallpaper.value,
colorizer = Colorizer(),
startExpirationTimeout = {}
)
if (springboardViewModel.hasWallpaper.value) {
binding.root.setBackgroundColor(0xFF32C7E2.toInt())
}
var startTime = 0L
var firstRender = true
lifecycleDisposable.bindTo(viewLifecycleOwner)
adapter.setPagingController(viewModel.controller)
lifecycleDisposable += viewModel.data.observeOn(AndroidSchedulers.mainThread()).subscribeBy {
if (firstRender) {
startTime = System.currentTimeMillis()
}
adapter.submitList(it) {
if (firstRender) {
firstRender = false
binding.root.doAfterNextLayout {
val endTime = System.currentTimeMillis()
Log.d(TAG, "First render in ${endTime - startTime} millis")
}
}
}
}
binding.recycler.layoutManager = SmoothScrollingLinearLayoutManager(requireContext(), true)
binding.recycler.adapter = adapter
RecyclerViewColorizer(binding.recycler).apply {
setChatColors(ChatColorsPalette.Bubbles.default.withId(ChatColors.Id.Auto))
}
}
private inner class ClickListener : ItemClickListener {
override fun onQuoteClicked(messageRecord: MmsMessageRecord?) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onLinkPreviewClicked(linkPreview: LinkPreview) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onQuotedIndicatorClicked(messageRecord: MessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onMoreTextClicked(conversationRecipientId: RecipientId, messageId: Long, isMms: Boolean) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onStickerClicked(stickerLocator: StickerLocator) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onViewOnceMessageClicked(messageRecord: MmsMessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onSharedContactDetailsClicked(contact: Contact, avatarTransitionView: View) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onAddToContactsClicked(contact: Contact) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onMessageSharedContactClicked(choices: MutableList<Recipient>) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onInviteSharedContactClicked(choices: MutableList<Recipient>) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onReactionClicked(multiselectPart: MultiselectPart, messageId: Long, isMms: Boolean) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onGroupMemberClicked(recipientId: RecipientId, groupId: GroupId) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onMessageWithErrorClicked(messageRecord: MessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onMessageWithRecaptchaNeededClicked(messageRecord: MessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onIncomingIdentityMismatchClicked(recipientId: RecipientId) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onRegisterVoiceNoteCallbacks(onPlaybackStartObserver: Observer<VoiceNotePlaybackState>) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onUnregisterVoiceNoteCallbacks(onPlaybackStartObserver: Observer<VoiceNotePlaybackState>) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onVoiceNotePause(uri: Uri) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onVoiceNotePlay(uri: Uri, messageId: Long, position: Double) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onVoiceNoteSeekTo(uri: Uri, position: Double) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onVoiceNotePlaybackSpeedChanged(uri: Uri, speed: Float) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onGroupMigrationLearnMoreClicked(membershipChange: GroupMigrationMembershipChange) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onChatSessionRefreshLearnMoreClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onBadDecryptLearnMoreClicked(author: RecipientId) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onSafetyNumberLearnMoreClicked(recipient: Recipient) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onJoinGroupCallClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onInviteFriendsToGroupClicked(groupId: GroupId.V2) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onEnableCallNotificationsClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onPlayInlineContent(conversationMessage: ConversationMessage?) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onInMemoryMessageClicked(messageRecord: InMemoryMessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onViewGroupDescriptionChange(groupId: GroupId?, description: String, isMessageRequestAccepted: Boolean) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onChangeNumberUpdateContact(recipient: Recipient) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onCallToAction(action: String) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onDonateClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onBlockJoinRequest(recipient: Recipient) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onRecipientNameClicked(target: RecipientId) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onInviteToSignalClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onActivatePaymentsClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onSendPaymentClicked(recipientId: RecipientId) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onScheduledIndicatorClicked(view: View, conversationMessage: ConversationMessage) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onUrlClicked(url: String): Boolean {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
return true
}
override fun onViewGiftBadgeClicked(messageRecord: MessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onGiftBadgeRevealed(messageRecord: MessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun goToMediaPreview(parent: ConversationItem?, sharedElement: View?, args: MediaIntentFactory.MediaPreviewArgs?) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onEditedIndicatorClicked(messageRecord: MessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onShowGroupDescriptionClicked(groupName: String, description: String, shouldLinkifyWebLinks: Boolean) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onJoinCallLink(callLinkRootKey: CallLinkRootKey) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onItemClick(item: MultiselectPart?) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onItemLongClick(itemView: View?, item: MultiselectPart?) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
}
}

View File

@@ -1,27 +0,0 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.test
import androidx.lifecycle.ViewModel
import org.signal.paging.PagedData
import org.signal.paging.PagingConfig
class InternalConversationTestViewModel : ViewModel() {
private val generator = ConversationElementGenerator()
private val dataSource = InternalConversationTestDataSource(
500,
generator
)
private val config = PagingConfig.Builder().setPageSize(25)
.setBufferPages(2)
.build()
private val pagedData = PagedData.createForObservable(dataSource, config)
val controller = pagedData.controller
val data = pagedData.data
}

View File

@@ -17,7 +17,6 @@
<uses-feature android:name="android.hardware.wifi" android:required="false"/>
<uses-feature android:name="android.hardware.portrait" android:required="false"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
<uses-feature android:name="android.hardware.telephony" android:required="false" />
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"/>
@@ -134,21 +133,18 @@
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:taskAffinity=".calling"
android:resizeableActivity="true"
android:launchMode="singleTask"
android:exported="false" />
android:launchMode="singleTask"/>
<activity android:name=".messagerequests.CalleeMustAcceptMessageRequestActivity"
android:theme="@style/TextSecure.DarkNoActionBar"
android:noHistory="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false" />
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".InviteActivity"
android:theme="@style/Signal.Light.NoActionBar.Invite"
android:windowSoftInputMode="stateHidden"
android:parentActivityName=".MainActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false">
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
@@ -157,8 +153,7 @@
<activity android:name=".PromptMmsActivity"
android:label="Configure MMS Settings"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DeviceProvisioningActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
@@ -180,13 +175,11 @@
</activity>
<activity android:name=".preferences.MmsPreferencesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false" />
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".sharing.interstitial.ShareInterstitialActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="adjustResize"
android:exported="false" />
android:windowSoftInputMode="adjustResize" />
<activity android:name=".sharing.v2.ShareActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
@@ -609,13 +602,10 @@
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sgnl" />
<data android:scheme="https" />
<data android:host="signal.link" />
<data android:scheme="https"
android:host="signal.link" />
</intent-filter>
</activity>
@@ -623,8 +613,7 @@
android:windowSoftInputMode="stateUnchanged"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:parentActivityName=".MainActivity"
android:exported="false">
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.MainActivity" />
@@ -634,8 +623,7 @@
android:windowSoftInputMode="stateUnchanged"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:parentActivityName=".MainActivity"
android:exported="false">
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.MainActivity" />
@@ -644,8 +632,7 @@
<activity android:name=".conversation.BubbleConversationActivity"
android:theme="@style/Signal.DayNight"
android:allowEmbedded="true"
android:resizeableActivity="true"
android:exported="false"/>
android:resizeableActivity="true" />
<activity android:name=".conversation.ConversationPopupActivity"
android:windowSoftInputMode="stateVisible"
@@ -653,93 +640,78 @@
android:taskAffinity=""
android:excludeFromRecents="true"
android:theme="@style/TextSecure.LightTheme.Popup"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
<activity android:name=".groups.ui.invitesandrequests.ManagePendingAndRequestingMembersActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="false"/>
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
<activity android:name=".recipients.ui.disappearingmessages.RecipientDisappearingMessagesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="adjustResize"
android:exported="false"/>
android:windowSoftInputMode="adjustResize"/>
<activity android:name=".migrations.ApplicationMigrationActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PassphraseCreateActivity"
<activity android:name=".PassphraseCreateActivity"
android:label="@string/AndroidManifest__create_passphrase"
android:windowSoftInputMode="stateUnchanged"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PassphrasePromptActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightIntroTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".NewConversationActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".calls.links.details.CallLinkDetailsActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".calls.new.NewCallActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PushContactSelectionActivity"
android:label="@string/AndroidManifest__select_contacts"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:label="@string/AndroidManifest__select_contacts"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".giph.ui.GiphyActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".mediasend.v2.MediaSelectionActivity"
android:theme="@style/TextSecure.DarkNoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:launchMode="singleTop"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:exported="false"/>
<activity android:name=".mediasend.v2.MediaSelectionActivity"
android:theme="@style/TextSecure.DarkNoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:launchMode="singleTop"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"/>
<activity android:name=".conversation.mutiselect.forward.MultiselectForwardActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".conversation.mutiselect.forward.MultiselectForwardActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
<activity android:name=".mediasend.v2.stories.StoriesMultiselectForwardActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".mediasend.v2.stories.StoriesMultiselectForwardActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
<activity android:name=".PassphraseChangeActivity"
android:label="@string/AndroidManifest__change_passphrase"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".verify.VerifyIdentityActivity"
android:exported="false"
@@ -761,15 +733,13 @@
android:name=".stories.my.MyStoriesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name=".stories.settings.StorySettingsActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize"
android:exported="false"/>
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" />
<activity
android:name=".stories.viewer.StoryViewerActivity"
@@ -778,99 +748,82 @@
android:theme="@style/TextSecure.DarkNoActionBar.StoryViewer"
android:launchMode="singleTask"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:parentActivityName=".MainActivity"
android:exported="false">
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.MainActivity" />
</activity>
<activity
android:name=".components.settings.app.changenumber.ChangeNumberLockActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".components.settings.app.changenumber.ChangeNumberLockActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".components.settings.conversation.ConversationSettingsActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.ConversationSettings"
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity android:name=".components.settings.conversation.ConversationSettingsActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.ConversationSettings"
android:windowSoftInputMode="stateAlwaysHidden">
</activity>
<activity
android:name=".components.settings.conversation.CallInfoActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity android:name=".components.settings.conversation.CallInfoActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden">
</activity>
<activity
android:name=".badges.gifts.flow.GiftFlowActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity android:name=".badges.gifts.flow.GiftFlowActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden">
</activity>
<activity
android:name=".wallpaper.ChatWallpaperActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity android:name=".wallpaper.ChatWallpaperActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="stateAlwaysHidden">
</activity>
<activity
android:name=".wallpaper.ChatWallpaperPreviewActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity android:name=".wallpaper.ChatWallpaperPreviewActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="stateAlwaysHidden">
</activity>
<activity
android:name=".devicetransfer.olddevice.OldDeviceTransferActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".devicetransfer.olddevice.OldDeviceTransferActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".devicetransfer.olddevice.OldDeviceExitActivity"
android:noHistory="true"
android:excludeFromRecents="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".devicetransfer.olddevice.OldDeviceExitActivity"
android:noHistory="true"
android:excludeFromRecents="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".registration.RegistrationNavigationActivity"
android:launchMode="singleTask"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".revealable.ViewOnceMessageActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.FullScreenMedia"
android:windowSoftInputMode="stateHidden"
android:excludeFromRecents="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".stickers.StickerManagementActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DeviceActivity"
android:screenOrientation="portrait"
android:label="@string/AndroidManifest__linked_devices"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".logsubmit.SubmitDebugLogActivity"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".mediapreview.MediaPreviewV2Activity"
android:label="@string/AndroidManifest__media_preview"
@@ -883,21 +836,19 @@
<activity android:name=".AvatarPreviewActivity"
android:label="@string/AndroidManifest__media_preview"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".avatar.photo.PhotoEditorActivity"
<activity
android:name=".avatar.photo.PhotoEditorActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:label="@string/AndroidManifest__media_preview"
android:theme="@style/TextSecure.DarkNoActionBar"
android:windowSoftInputMode="stateHidden"
android:exported="false"/>
android:windowSoftInputMode="stateHidden" />
<activity android:name=".mediaoverview.MediaOverviewActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DummyActivity"
android:theme="@android:style/Theme.NoDisplay"
@@ -908,8 +859,7 @@
android:alwaysRetainTaskState="false"
android:stateNotNeeded="true"
android:clearTaskOnLaunch="true"
android:finishOnTaskLaunch="true"
android:exported="false"/>
android:finishOnTaskLaunch="true" />
<activity android:name=".PlayServicesProblemActivity"
android:exported="false"
@@ -952,190 +902,136 @@
<activity android:name=".mediasend.AvatarSelectionActivity"
android:theme="@style/TextSecure.DarkNoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".blocked.BlockedUsersActivity"
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".scribbles.ImageEditorStickerSelectActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".profiles.edit.EditProfileActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="stateVisible|adjustResize"
android:exported="false"/>
android:windowSoftInputMode="stateVisible|adjustResize" />
<activity android:name=".profiles.username.AddAUsernameActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateVisible|adjustResize"
android:exported="false"/>
android:windowSoftInputMode="stateVisible|adjustResize" />
<activity android:name=".profiles.manage.ManageProfileActivity"
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateVisible|adjustResize"
android:exported="false"/>
android:windowSoftInputMode="stateVisible|adjustResize" />
<activity
android:name=".payments.preferences.PaymentsActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".payments.preferences.PaymentsActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".lock.v2.CreateSvrPinActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="adjustResize"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".lock.v2.CreateKbsPinActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="adjustResize"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".lock.v2.SvrMigrationActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="adjustResize"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".lock.v2.KbsMigrationActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="adjustResize"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".contacts.TurnOffContactJoinedNotificationsActivity"
android:theme="@style/Theme.AppCompat.Dialog.Alert"
android:exported="false"/>
android:theme="@style/Theme.AppCompat.Dialog.Alert" />
<activity android:name=".contactshare.ContactShareEditActivity"
android:theme="@style/TextSecure.LightTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".contactshare.ContactNameEditActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".contactshare.SharedContactDetailsActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ShortcutLauncherActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".maps.PlacePickerActivity"
android:label="@string/PlacePickerActivity_title"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity
android:name=".maps.PlacePickerActivity"
android:label="@string/PlacePickerActivity_title"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".MainActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
<activity android:name=".pin.PinRestoreActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
<activity android:name=".groups.ui.creategroup.CreateGroupActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="false"/>
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
<activity android:name=".groups.ui.addtogroup.AddToGroupsActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="false"/>
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
<activity android:name=".groups.ui.addmembers.AddMembersActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="false"/>
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
<activity android:name=".groups.ui.creategroup.details.AddGroupDetailsActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="false"/>
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
<activity android:name=".groups.ui.chooseadmin.ChooseNewAdminActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
<activity android:name=".megaphone.ClientDeprecatedActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:launchMode="singleTask"
android:exported="false"/>
android:launchMode="singleTask" />
<activity android:name=".megaphone.SmsExportMegaphoneActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:screenOrientation="portrait"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:launchMode="singleTask"
android:exported="false"/>
android:launchMode="singleTask" />
<activity android:name=".ratelimit.RecaptchaProofActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode" />
<activity android:name=".wallpaper.crop.WallpaperImageSelectionActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/TextSecure.DarkNoActionBar"
android:exported="false"/>
android:theme="@style/TextSecure.DarkNoActionBar" />
<activity android:name=".wallpaper.crop.WallpaperCropActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:screenOrientation="portrait"
android:theme="@style/Theme.Signal.WallpaperCropper"
android:exported="false"/>
android:theme="@style/Theme.Signal.WallpaperCropper" />
<activity android:name=".reactions.edit.EditReactionsActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".exporter.flow.SmsExportActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".components.settings.app.subscription.donate.DonateToSignalActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<service
android:enabled="true"
android:name=".exporter.SignalSmsExportService"
android:foregroundServiceType="dataSync"
android:exported="false"/>
<service
android:enabled="true"
android:name=".service.webrtc.WebRtcCallService"
android:foregroundServiceType="camera|microphone"
android:exported="false"/>
<service
android:enabled="true"
android:exported="false"
android:name=".service.KeyCachingService" />
<service
android:enabled="true"
android:name=".messages.IncomingMessageObserver$ForegroundService"
android:exported="false"/>
<service
android:enabled="true"
android:name=".messages.IncomingMessageObserver$BackgroundService"
android:exported="false"/>
<service
android:name=".service.webrtc.AndroidCallConnectionService"
<service android:enabled="true" android:name=".exporter.SignalSmsExportService" android:foregroundServiceType="dataSync" />
<service android:enabled="true" android:name=".service.webrtc.WebRtcCallService" android:foregroundServiceType="camera|microphone"/>
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
<service android:enabled="true" android:name=".messages.IncomingMessageObserver$ForegroundService"/>
<service android:enabled="true" android:name=".messages.IncomingMessageObserver$BackgroundService"/>
<service android:name=".service.webrtc.AndroidCallConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
android:exported="true">
<intent-filter>
@@ -1143,9 +1039,7 @@
</intent-filter>
</service>
<service
android:name=".components.voice.VoiceNotePlaybackService"
android:exported="true">
<service android:name=".components.voice.VoiceNotePlaybackService" android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
@@ -1185,17 +1079,11 @@
<meta-data android:name="android.provider.CONTACTS_STRUCTURE" android:resource="@xml/contactsformat" />
</service>
<service
android:name=".service.GenericForegroundService"
android:exported="false"/>
<service android:name=".service.GenericForegroundService"/>
<service
android:name=".gcm.FcmFetchBackgroundService"
android:exported="false"/>
<service android:name=".gcm.FcmFetchBackgroundService" />
<service
android:name=".gcm.FcmFetchForegroundService"
android:exported="false"/>
<service android:name=".gcm.FcmFetchForegroundService" />
<service android:name=".gcm.FcmReceiveService" android:exported="true">
<intent-filter>
@@ -1252,38 +1140,19 @@
</intent-filter>
</receiver>
<receiver
android:name=".service.ExpirationListener"
android:exported="false"/>
<receiver android:name=".service.ExpirationListener" />
<receiver
android:name=".service.ExpiringStoriesManager$ExpireStoriesAlarm"
android:exported="false"/>
<receiver android:name=".service.ExpiringStoriesManager$ExpireStoriesAlarm" />
<receiver
android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm"
android:exported="false"/>
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
<receiver
android:name=".service.ScheduledMessageManager$ScheduledMessagesAlarm"
android:exported="false"/>
<receiver android:name=".service.ScheduledMessageManager$ScheduledMessagesAlarm" />
<receiver
android:name=".service.PendingRetryReceiptManager$PendingRetryReceiptAlarm"
android:exported="false"/>
<receiver android:name=".service.PendingRetryReceiptManager$PendingRetryReceiptAlarm" />
<receiver
android:name=".service.TrimThreadsByDateManager$TrimThreadsByDateAlarm"
android:exported="false"/>
<receiver android:name=".service.TrimThreadsByDateManager$TrimThreadsByDateAlarm" />
<receiver
android:name=".payments.backup.phrase.ClearClipboardAlarmReceiver"
android:exported="false"/>
<provider android:name=".providers.AvatarProvider"
android:authorities="${applicationId}.avatar"
android:exported="false"
android:grantUriPermissions="true" />
<receiver android:name=".payments.backup.phrase.ClearClipboardAlarmReceiver" />
<provider android:name=".providers.PartProvider"
android:grantUriPermissions="true"
@@ -1381,14 +1250,12 @@
android:name=".gcm.FcmJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:enabled="@bool/enable_job_service"
android:exported="false"
tools:targetApi="26" />
<service
android:name=".jobmanager.JobSchedulerScheduler$SystemService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:enabled="@bool/enable_job_service"
android:exported="false"
tools:targetApi="26" />
<service

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 91 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,44 +0,0 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.glide.transforms
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
import kotlin.math.max
import kotlin.math.min
object SignalDownsampleStrategy {
/**
* Center outside, but don't up-scale, only downscale. You should be setting centerOutside
* on the target image view to still maintain center outside behavior.
*/
@JvmField
val CENTER_OUTSIDE_NO_UPSCALE: DownsampleStrategy = CenterOutsideNoUpscale()
private class CenterOutsideNoUpscale : DownsampleStrategy() {
override fun getScaleFactor(
sourceWidth: Int,
sourceHeight: Int,
requestedWidth: Int,
requestedHeight: Int
): Float {
val widthPercentage = requestedWidth / sourceWidth.toFloat()
val heightPercentage = requestedHeight / sourceHeight.toFloat()
return min(MAX_SCALE_FACTOR, max(widthPercentage, heightPercentage))
}
override fun getSampleSizeRounding(
sourceWidth: Int,
sourceHeight: Int,
requestedWidth: Int,
requestedHeight: Int
): SampleSizeRounding {
return SampleSizeRounding.QUALITY
}
companion object {
private const val MAX_SCALE_FACTOR = 1f
}
}
}

View File

@@ -30,7 +30,6 @@ import com.google.android.gms.security.ProviderInstaller;
import org.conscrypt.Conscrypt;
import org.greenrobot.eventbus.EventBus;
import org.signal.aesgcmprovider.AesGcmProvider;
import org.signal.core.util.MemoryTracker;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.AndroidLogger;
import org.signal.core.util.logging.Log;
@@ -61,7 +60,7 @@ import org.thoughtcrime.securesms.jobs.PnpInitializeDevicesJob;
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob;
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.jobs.RefreshSvrCredentialsJob;
import org.thoughtcrime.securesms.jobs.RefreshKbsCredentialsJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.jobs.RetrieveRemoteAnnouncementsJob;
import org.thoughtcrime.securesms.jobs.StoryOnboardingDownloadJob;
@@ -199,7 +198,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
.addPostRender(this::initializeExpiringMessageManager)
.addPostRender(() -> SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this)))
.addPostRender(this::initializeTrimThreadsByDateManager)
.addPostRender(RefreshSvrCredentialsJob::enqueueIfNecessary)
.addPostRender(RefreshKbsCredentialsJob::enqueueIfNecessary)
.addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this))
.addPostRender(EmojiSearchIndexDownloadJob::scheduleIfNecessary)
.addPostRender(() -> SignalDatabase.messageLog().trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge()))
@@ -238,7 +237,6 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
KeyCachingService.onAppForegrounded(this);
ApplicationDependencies.getShakeToReport().enable();
checkBuildExpiration();
MemoryTracker.start();
long lastForegroundTime = SignalStore.misc().getLastForegroundTime();
long currentTime = System.currentTimeMillis();
@@ -262,7 +260,6 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
ApplicationDependencies.getFrameRateTracker().stop();
ApplicationDependencies.getShakeToReport().disable();
ApplicationDependencies.getDeadlockDetector().stop();
MemoryTracker.stop();
}
public PersistentLogger getPersistentLogger() {

View File

@@ -8,7 +8,6 @@ import androidx.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.Observer;
import org.signal.ringrtc.CallLinkRootKey;
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.conversation.ConversationItem;
@@ -117,6 +116,5 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
void goToMediaPreview(ConversationItem parent, View sharedElement, MediaIntentFactory.MediaPreviewArgs args);
void onEditedIndicatorClicked(@NonNull MessageRecord messageRecord);
void onShowGroupDescriptionClicked(@NonNull String groupName, @NonNull String description, boolean shouldLinkifyWebLinks);
void onJoinCallLink(@NonNull CallLinkRootKey callLinkRootKey);
}
}

View File

@@ -6,8 +6,6 @@ import android.view.Window;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.signal.core.util.logging.Log;
public class DeviceProvisioningActivity extends PassphraseRequiredActivity {
@@ -22,7 +20,7 @@ public class DeviceProvisioningActivity extends PassphraseRequiredActivity {
@Override
protected void onCreate(Bundle bundle, boolean ready) {
AlertDialog dialog = new MaterialAlertDialogBuilder(this)
AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle(getString(R.string.DeviceProvisioningActivity_link_a_signal_device))
.setMessage(getString(R.string.DeviceProvisioningActivity_it_looks_like_youre_trying_to_link_a_signal_device_using_a_3rd_party_scanner))
.setPositiveButton(R.string.DeviceProvisioningActivity_continue, (dialog1, which) -> {

View File

@@ -5,8 +5,6 @@ import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LiveData;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.thoughtcrime.securesms.groups.LiveGroup;
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
@@ -28,7 +26,7 @@ public final class GroupMembersDialog {
}
public void display() {
AlertDialog dialog = new MaterialAlertDialogBuilder(fragmentActivity)
AlertDialog dialog = new AlertDialog.Builder(fragmentActivity)
.setTitle(R.string.ConversationActivity_group_members)
.setIcon(R.drawable.ic_group_24)
.setCancelable(true)

View File

@@ -21,8 +21,6 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.thoughtcrime.securesms.components.ContactFilterView;
import org.thoughtcrime.securesms.components.ContactFilterView.OnFilterChangedListener;
import org.thoughtcrime.securesms.contacts.ContactSelectionDisplayMode;
@@ -219,7 +217,7 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
private class SmsSendClickListener implements OnClickListener {
@Override
public void onClick(View v) {
new MaterialAlertDialogBuilder(InviteActivity.this)
new AlertDialog.Builder(InviteActivity.this)
.setTitle(getResources().getQuantityString(R.plurals.InviteActivity_send_sms_invites,
contactsFragment.getSelectedContacts().size(),
contactsFragment.getSelectedContacts().size()))

View File

@@ -13,14 +13,11 @@ import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController;
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner;
import org.thoughtcrime.securesms.conversationlist.RelinkDevicesReminderBottomSheetFragment;
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceExitActivity;
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferLockedDialog;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor;
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabRepository;
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel;
import org.thoughtcrime.securesms.util.AppStartup;
@@ -104,7 +101,6 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
handleGroupLinkInIntent(intent);
handleProxyInIntent(intent);
handleSignalMeIntent(intent);
handleCallLinkInIntent(intent);
}
@Override
@@ -118,16 +114,7 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
super.onResume();
dynamicTheme.onResume(this);
if (SignalStore.misc().isOldDeviceTransferLocked()) {
new MaterialAlertDialogBuilder(this)
.setTitle(R.string.OldDeviceTransferLockedDialog__complete_registration_on_your_new_device)
.setMessage(R.string.OldDeviceTransferLockedDialog__your_signal_account_has_been_transferred_to_your_new_device)
.setPositiveButton(R.string.OldDeviceTransferLockedDialog__done, (d, w) -> OldDeviceExitActivity.exit(this))
.setNegativeButton(R.string.OldDeviceTransferLockedDialog__cancel_and_activate_this_device, (d, w) -> {
SignalStore.misc().clearOldDeviceTransferLocked();
DeviceTransferBlockingInterceptor.getInstance().unblockNetwork();
})
.setCancelable(false)
.show();
OldDeviceTransferLockedDialog.show(getSupportFragmentManager());
}
if (SignalStore.misc().getShouldShowLinkedDevicesReminder()) {

View File

@@ -7,27 +7,20 @@ import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import org.signal.core.util.concurrent.LifecycleDisposable;
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
import org.thoughtcrime.securesms.conversation.ConversationIntents;
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
import org.thoughtcrime.securesms.insights.InsightsLauncher;
import org.thoughtcrime.securesms.recipients.RecipientId;
import io.reactivex.rxjava3.disposables.Disposable;
public class MainNavigator {
public static final int REQUEST_CONFIG_CHANGES = 901;
private final MainActivity activity;
private final LifecycleDisposable lifecycleDisposable;
private final MainActivity activity;
public MainNavigator(@NonNull MainActivity activity) {
this.activity = activity;
this.lifecycleDisposable = new LifecycleDisposable();
lifecycleDisposable.bindTo(activity);
this.activity = activity;
}
public static MainNavigator get(@NonNull Activity activity) {
@@ -40,7 +33,7 @@ public class MainNavigator {
/**
* @return True if the back pressed was handled in our own custom way, false if it should be given
* to the system to do the default behavior.
* to the system to do the default behavior.
*/
public boolean onBackPressed() {
Fragment fragment = getFragmentManager().findFragmentById(R.id.fragment_container);
@@ -53,16 +46,13 @@ public class MainNavigator {
}
public void goToConversation(@NonNull RecipientId recipientId, long threadId, int distributionType, int startingPosition) {
Disposable disposable = ConversationIntents.createBuilder(activity, recipientId, threadId)
.map(builder -> builder.withDistributionType(distributionType)
.withStartingPosition(startingPosition)
.build())
.subscribe(intent -> {
activity.startActivity(intent);
activity.overridePendingTransition(R.anim.slide_from_end, R.anim.fade_scale_out);
});
Intent intent = ConversationIntents.createBuilder(activity, recipientId, threadId)
.withDistributionType(distributionType)
.withStartingPosition(startingPosition)
.build();
lifecycleDisposable.add(disposable);
activity.startActivity(intent);
activity.overridePendingTransition(R.anim.slide_from_end, R.anim.fade_scale_out);
}
public void goToAppSettings() {
@@ -89,7 +79,7 @@ public class MainNavigator {
public interface BackHandler {
/**
* @return True if the back pressed was handled in our own custom way, false if it should be given
* to the system to do the default behavior.
* to the system to do the default behavior.
*/
boolean onBackPressed();
}

View File

@@ -36,7 +36,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar;
import org.signal.core.util.DimensionUnit;
import org.signal.core.util.concurrent.LifecycleDisposable;
import org.signal.core.util.concurrent.SimpleTask;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.menu.ActionItem;
@@ -46,25 +45,24 @@ import org.thoughtcrime.securesms.contacts.management.ContactsManagementViewMode
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey;
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
import org.thoughtcrime.securesms.conversation.ConversationIntents;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.signal.core.util.concurrent.LifecycleDisposable;
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import io.reactivex.rxjava3.disposables.Disposable;
/**
* Activity container for starting a new conversation.
*
@@ -123,7 +121,7 @@ public class NewConversationActivity extends ContactSelectionActivity
if (!resolved.isRegistered() || !resolved.hasServiceId()) {
Log.i(TAG, "[onContactSelected] Not registered or no UUID. Doing a directory refresh.");
try {
ContactDiscovery.refresh(this, resolved, false, TimeUnit.SECONDS.toMillis(10));
ContactDiscovery.refresh(this, resolved, false);
resolved = Recipient.resolved(resolved.getId());
} catch (IOException e) {
Log.w(TAG, "[onContactSelected] Failed to refresh directory for new contact.");
@@ -164,18 +162,15 @@ public class NewConversationActivity extends ContactSelectionActivity
}
private void launch(Recipient recipient) {
Disposable disposable = ConversationIntents.createBuilder(this, recipient.getId(), -1L)
.map(builder -> builder
.withDraftText(getIntent().getStringExtra(Intent.EXTRA_TEXT))
.withDataUri(getIntent().getData())
.withDataType(getIntent().getType())
.build())
.subscribe(intent -> {
startActivity(intent);
finish();
});
long existingThread = SignalDatabase.threads().getThreadIdIfExistsFor(recipient.getId());
Intent intent = ConversationIntents.createBuilder(this, recipient.getId(), existingThread)
.withDraftText(getIntent().getStringExtra(Intent.EXTRA_TEXT))
.withDataUri(getIntent().getData())
.withDataType(getIntent().getType())
.build();
disposables.add(disposable);
startActivity(intent);
finish();
}
@Override
@@ -237,8 +232,8 @@ public class NewConversationActivity extends ContactSelectionActivity
@Override
public boolean onLongClick(View anchorView, ContactSearchKey contactSearchKey, RecyclerView recyclerView) {
RecipientId recipientId = contactSearchKey.requireRecipientSearchKey().getRecipientId();
List<ActionItem> actions = generateContextualActionsForRecipient(recipientId);
RecipientId recipientId = contactSearchKey.requireRecipientSearchKey().getRecipientId();
List<ActionItem> actions = generateContextualActionsForRecipient(recipientId);
if (actions.isEmpty()) {
return false;
}
@@ -273,12 +268,7 @@ public class NewConversationActivity extends ContactSelectionActivity
R.drawable.ic_chat_message_24,
getString(R.string.NewConversationActivity__message),
R.color.signal_colorOnSurface,
() -> {
Disposable disposable = ConversationIntents.createBuilder(this, recipient.getId(), -1L)
.subscribe(builder -> startActivity(builder.build()));
disposables.add(disposable);
}
() -> startActivity(ConversationIntents.createBuilder(this, recipient.getId(), -1L).build())
);
}
@@ -379,7 +369,7 @@ public class NewConversationActivity extends ContactSelectionActivity
.show();
}
private void displaySnackbar(@StringRes int message, Object... formatArgs) {
private void displaySnackbar(@StringRes int message, Object ... formatArgs) {
Snackbar.make(findViewById(android.R.id.content), getString(message, formatArgs), Snackbar.LENGTH_SHORT).show();
}
}

View File

@@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferActivity;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity;
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
import org.thoughtcrime.securesms.migrations.ApplicationMigrationActivity;
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
import org.thoughtcrime.securesms.pin.PinRestoreActivity;
@@ -189,11 +189,11 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
}
private boolean userMustCreateSignalPin() {
return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.svr().hasPin() && !SignalStore.svr().lastPinCreateFailed() && !SignalStore.svr().hasOptedOut();
return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().lastPinCreateFailed() && !SignalStore.kbsValues().hasOptedOut();
}
private boolean userHasSkippedOrForgottenPin() {
return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.svr().hasPin() && !SignalStore.svr().hasOptedOut() && SignalStore.svr().isPinForgottenOrSkipped();
return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut() && SignalStore.kbsValues().isPinForgottenOrSkipped();
}
private boolean userMustSetProfileName() {
@@ -234,7 +234,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
intent = getIntent();
}
return getRoutedIntent(CreateSvrPinActivity.class, intent);
return getRoutedIntent(CreateKbsPinActivity.class, intent);
}
private Intent getCreateProfileNameIntent() {

View File

@@ -28,7 +28,6 @@ import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
public class PlayServicesProblemFragment extends DialogFragment {
@@ -38,7 +37,7 @@ public class PlayServicesProblemFragment extends DialogFragment {
Dialog dialog = GoogleApiAvailability.getInstance().getErrorDialog(getActivity(), code, 9111);
if (dialog == null) {
return new MaterialAlertDialogBuilder(requireActivity())
return new AlertDialog.Builder(requireActivity())
.setNegativeButton(android.R.string.ok, null)
.setMessage(R.string.PlayServicesProblemFragment_the_version_of_google_play_services_you_have_installed_is_not_functioning)
.create();

View File

@@ -48,9 +48,9 @@ public class SmsSendtoActivity extends Activity {
Toast.makeText(this, R.string.ConversationActivity_specify_recipient, Toast.LENGTH_LONG).show();
} else {
Recipient recipient = Recipient.external(this, destination.getDestination());
long threadId = SignalDatabase.threads().getOrCreateThreadIdFor(recipient);
long threadId = SignalDatabase.threads().getThreadIdIfExistsFor(recipient.getId());
nextIntent = ConversationIntents.createBuilderSync(this, recipient.getId(), threadId)
nextIntent = ConversationIntents.createBuilder(this, recipient.getId(), threadId)
.withDraftText(destination.getBody())
.build();
}

View File

@@ -55,7 +55,6 @@ import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.IdentityKey;
import org.thoughtcrime.securesms.components.TooltipPopup;
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor;
import org.thoughtcrime.securesms.components.webrtc.CallLinkInfoSheet;
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsListUpdatePopupWindow;
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsState;
import org.thoughtcrime.securesms.components.webrtc.CallStateUpdatePopupWindow;
@@ -72,7 +71,6 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.messagerequests.CalleeMustAcceptMessageRequestActivity;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet;
@@ -106,17 +104,9 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
private static final int STANDARD_DELAY_FINISH = 1000;
private static final int VIBRATE_DURATION = 50;
/**
* ANSWER the call via voice-only.
*/
public static final String ANSWER_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".ANSWER_ACTION";
/**
* ANSWER the call via video.
*/
public static final String ANSWER_VIDEO_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".ANSWER_VIDEO_ACTION";
public static final String DENY_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".DENY_ACTION";
public static final String END_CALL_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".END_CALL_ACTION";
public static final String ANSWER_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".ANSWER_ACTION";
public static final String DENY_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".DENY_ACTION";
public static final String END_CALL_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".END_CALL_ACTION";
public static final String EXTRA_ENABLE_VIDEO_IF_AVAILABLE = WebRtcCallActivity.class.getCanonicalName() + ".ENABLE_VIDEO_IF_AVAILABLE";
public static final String EXTRA_STARTED_FROM_FULLSCREEN = WebRtcCallActivity.class.getCanonicalName() + ".STARTED_FROM_FULLSCREEN";
@@ -169,19 +159,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
initializeViewModel(isLandscapeEnabled);
initializePictureInPictureParams();
logIntent(getIntent());
if (ANSWER_VIDEO_ACTION.equals(getIntent().getAction())) {
enableVideoIfAvailable = true;
} else if (ANSWER_ACTION.equals(getIntent().getAction()) || getIntent().getBooleanExtra(EXTRA_STARTED_FROM_FULLSCREEN, false)) {
enableVideoIfAvailable = false;
} else {
enableVideoIfAvailable = getIntent().getBooleanExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false);
getIntent().removeExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE);
}
processIntent(getIntent());
enableVideoIfAvailable = getIntent().getBooleanExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false);
getIntent().removeExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE);
windowLayoutInfoConsumer = new WindowLayoutInfoConsumer();
windowInfoTrackerCallbackAdapter = new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
@@ -229,7 +211,6 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
public void onNewIntent(Intent intent) {
Log.i(TAG, "onNewIntent(" + intent.getBooleanExtra(EXTRA_STARTED_FROM_FULLSCREEN, false) + ")");
super.onNewIntent(intent);
logIntent(intent);
processIntent(intent);
}
@@ -316,17 +297,9 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
return isSystemPipEnabledAndAvailable() && isInPictureInPictureMode();
}
private void logIntent(@NonNull Intent intent) {
Log.d(TAG, "Intent: Action: " + intent.getAction());
Log.d(TAG, "Intent: EXTRA_STARTED_FROM_FULLSCREEN: " + intent.getBooleanExtra(EXTRA_STARTED_FROM_FULLSCREEN, false));
Log.d(TAG, "Intent: EXTRA_ENABLE_VIDEO_IF_AVAILABLE: " + intent.getBooleanExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false));
}
private void processIntent(@NonNull Intent intent) {
if (ANSWER_ACTION.equals(intent.getAction())) {
handleAnswerWithAudio();
} else if (ANSWER_VIDEO_ACTION.equals(intent.getAction())) {
handleAnswerWithVideo();
} else if (DENY_ACTION.equals(intent.getAction())) {
handleDenyCall();
} else if (END_CALL_ACTION.equals(intent.getAction())) {
@@ -525,20 +498,26 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
}
private void handleAnswerWithVideo() {
Permissions.with(this)
.request(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
.ifNecessary()
.withRationaleDialog(getString(R.string.WebRtcCallActivity_to_answer_the_call_give_signal_access_to_your_microphone_and_camera), R.drawable.ic_mic_solid_24, R.drawable.ic_video_solid_24_tinted)
.withPermanentDenialDialog(getString(R.string.WebRtcCallActivity_signal_requires_microphone_and_camera_permissions_in_order_to_make_or_receive_calls))
.onAllGranted(() -> {
callScreen.setStatus(getString(R.string.RedPhone_answering));
Recipient recipient = viewModel.getRecipient().get();
ApplicationDependencies.getSignalCallManager().acceptCall(true);
if (!recipient.equals(Recipient.UNKNOWN)) {
Permissions.with(this)
.request(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
.ifNecessary()
.withRationaleDialog(getString(R.string.WebRtcCallActivity_to_answer_the_call_from_s_give_signal_access_to_your_microphone, recipient.getDisplayName(this)),
R.drawable.ic_mic_solid_24, R.drawable.ic_video_solid_24_tinted)
.withPermanentDenialDialog(getString(R.string.WebRtcCallActivity_signal_requires_microphone_and_camera_permissions_in_order_to_make_or_receive_calls))
.onAllGranted(() -> {
callScreen.setRecipient(recipient);
callScreen.setStatus(getString(R.string.RedPhone_answering));
handleSetMuteVideo(false);
})
.onAnyDenied(this::handleDenyCall)
.execute();
ApplicationDependencies.getSignalCallManager().acceptCall(true);
handleSetMuteVideo(false);
})
.onAnyDenied(this::handleDenyCall)
.execute();
}
}
private void handleDenyCall() {
@@ -922,13 +901,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
@Override
public void onCallInfoClicked() {
LiveRecipient liveRecipient = viewModel.getRecipient();
if (liveRecipient.get().isCallLink()) {
CallLinkInfoSheet.show(getSupportFragmentManager(), liveRecipient.get().requireCallLinkRoomId());
} else {
CallParticipantsListDialog.show(getSupportFragmentManager());
}
CallParticipantsListDialog.show(getSupportFragmentManager());
}
@Override

View File

@@ -5,7 +5,7 @@ import android.app.backup.BackupDataInput
import android.app.backup.BackupDataOutput
import android.os.ParcelFileDescriptor
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.absbackup.backupables.SvrAuthTokens
import org.thoughtcrime.securesms.absbackup.backupables.KbsAuthTokens
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.FileInputStream
@@ -17,7 +17,7 @@ import java.io.IOException
*/
class SignalBackupAgent : BackupAgent() {
private val items: List<AndroidBackupItem> = listOf(
SvrAuthTokens
KbsAuthTokens
)
override fun onBackup(oldState: ParcelFileDescriptor?, data: BackupDataOutput, newState: ParcelFileDescriptor) {

View File

@@ -3,13 +3,13 @@ package org.thoughtcrime.securesms.absbackup.backupables
import com.google.protobuf.InvalidProtocolBufferException
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.absbackup.AndroidBackupItem
import org.thoughtcrime.securesms.absbackup.protos.SvrAuthToken
import org.thoughtcrime.securesms.absbackup.protos.KbsAuthToken
import org.thoughtcrime.securesms.keyvalue.SignalStore
/**
* This backs up the not-secret KBS Auth tokens, which can be combined with a PIN to prove ownership of a phone number in order to complete the registration process.
*/
object SvrAuthTokens : AndroidBackupItem {
object KbsAuthTokens : AndroidBackupItem {
private const val TAG = "KbsAuthTokens"
override fun getKey(): String {
@@ -17,19 +17,19 @@ object SvrAuthTokens : AndroidBackupItem {
}
override fun getDataForBackup(): ByteArray {
val proto = SvrAuthToken(tokens = SignalStore.svr().authTokenList)
val proto = KbsAuthToken(tokens = SignalStore.kbsValues().kbsAuthTokenList)
return proto.encode()
}
override fun restoreData(data: ByteArray) {
if (SignalStore.svr().authTokenList.isNotEmpty()) {
if (SignalStore.kbsValues().kbsAuthTokenList.isNotEmpty()) {
return
}
try {
val proto = SvrAuthToken.ADAPTER.decode(data)
val proto = KbsAuthToken.ADAPTER.decode(data)
SignalStore.svr().putAuthTokenList(proto.tokens)
SignalStore.kbsValues().putAuthTokenList(proto.tokens)
} catch (e: InvalidProtocolBufferException) {
Log.w(TAG, "Cannot restore KbsAuthToken from backup service.")
}

View File

@@ -35,9 +35,6 @@ public abstract class Attachment {
@Nullable
private final byte[] digest;
@Nullable
private final byte[] incrementalDigest;
@Nullable
private final String fastPreflightId;
@@ -73,7 +70,6 @@ public abstract class Attachment {
@Nullable String key,
@Nullable String relay,
@Nullable byte[] digest,
@Nullable byte[] incrementalDigest,
@Nullable String fastPreflightId,
boolean voiceNote,
boolean borderless,
@@ -97,7 +93,6 @@ public abstract class Attachment {
this.key = key;
this.relay = relay;
this.digest = digest;
this.incrementalDigest = incrementalDigest;
this.fastPreflightId = fastPreflightId;
this.voiceNote = voiceNote;
this.borderless = borderless;
@@ -170,11 +165,6 @@ public abstract class Attachment {
return digest;
}
@Nullable
public byte[] getIncrementalDigest() {
return incrementalDigest;
}
@Nullable
public String getFastPreflightId() {
return fastPreflightId;

View File

@@ -33,7 +33,6 @@ public class DatabaseAttachment extends Attachment {
String key,
String relay,
byte[] digest,
byte[] incrementalDigest,
String fastPreflightId,
boolean voiceNote,
boolean borderless,
@@ -49,7 +48,7 @@ public class DatabaseAttachment extends Attachment {
int displayOrder,
long uploadTimestamp)
{
super(contentType, transferProgress, size, fileName, cdnNumber, location, key, relay, digest, incrementalDigest, fastPreflightId, voiceNote, borderless, videoGif, width, height, quote, uploadTimestamp, caption, stickerLocator, blurHash, audioHash, transformProperties);
super(contentType, transferProgress, size, fileName, cdnNumber, location, key, relay, digest, fastPreflightId, voiceNote, borderless, videoGif, width, height, quote, uploadTimestamp, caption, stickerLocator, blurHash, audioHash, transformProperties);
this.attachmentId = attachmentId;
this.hasData = hasData;
this.hasThumbnail = hasThumbnail;

View File

@@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.database.MessageTable;
public class MmsNotificationAttachment extends Attachment {
public MmsNotificationAttachment(int status, long size) {
super("application/mms", getTransferStateFromStatus(status), size, null, 0, null, null, null, null, null, null, false, false, false, 0, 0, false, 0, null, null, null, null, null);
super("application/mms", getTransferStateFromStatus(status), size, null, 0, null, null, null, null, null, false, false, false, 0, 0, false, 0, null, null, null, null, null);
}
@Nullable

View File

@@ -30,7 +30,6 @@ public class PointerAttachment extends Attachment {
@Nullable String key,
@Nullable String relay,
@Nullable byte[] digest,
@Nullable byte[] incrementalDigest,
@Nullable String fastPreflightId,
boolean voiceNote,
boolean borderless,
@@ -42,7 +41,7 @@ public class PointerAttachment extends Attachment {
@Nullable StickerLocator stickerLocator,
@Nullable BlurHash blurHash)
{
super(contentType, transferState, size, fileName, cdnNumber, location, key, relay, digest, incrementalDigest, fastPreflightId, voiceNote, borderless, videoGif, width, height, false, uploadTimestamp, caption, stickerLocator, blurHash, null, null);
super(contentType, transferState, size, fileName, cdnNumber, location, key, relay, digest, fastPreflightId, voiceNote, borderless, videoGif, width, height, false, uploadTimestamp, caption, stickerLocator, blurHash, null, null);
}
@Nullable
@@ -113,7 +112,6 @@ public class PointerAttachment extends Attachment {
pointer.get().asPointer().getRemoteId().toString(),
encodedKey, null,
pointer.get().asPointer().getDigest().orElse(null),
pointer.get().asPointer().getincrementalDigest().orElse(null),
fastPreflightId,
pointer.get().asPointer().getVoiceNote(),
pointer.get().asPointer().isBorderless(),
@@ -139,7 +137,6 @@ public class PointerAttachment extends Attachment {
thumbnail != null && thumbnail.asPointer().getKey() != null ? Base64.encodeBytes(thumbnail.asPointer().getKey()) : null,
null,
thumbnail != null ? thumbnail.asPointer().getDigest().orElse(null) : null,
thumbnail != null ? thumbnail.asPointer().getincrementalDigest().orElse(null) : null,
null,
false,
false,
@@ -169,7 +166,6 @@ public class PointerAttachment extends Attachment {
thumbnail != null && thumbnail.asPointer().getKey() != null ? Base64.encodeBytes(thumbnail.asPointer().getKey()) : null,
null,
thumbnail != null ? thumbnail.asPointer().getDigest().orElse(null) : null,
thumbnail != null ? thumbnail.asPointer().getincrementalDigest().orElse(null) : null,
null,
false,
false,

View File

@@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.database.AttachmentTable;
public class TombstoneAttachment extends Attachment {
public TombstoneAttachment(@NonNull String contentType, boolean quote) {
super(contentType, AttachmentTable.TRANSFER_PROGRESS_DONE, 0, null, 0, null, null, null, null, null, null, false, false, false, 0, 0, quote, 0, null, null, null, null, null);
super(contentType, AttachmentTable.TRANSFER_PROGRESS_DONE, 0, null, 0, null, null, null, null, null, false, false, false, 0, 0, quote, 0, null, null, null, null, null);
}
@Override

View File

@@ -52,7 +52,7 @@ public class UriAttachment extends Attachment {
@Nullable AudioHash audioHash,
@Nullable TransformProperties transformProperties)
{
super(contentType, transferState, size, fileName, 0, null, null, null, null, null, fastPreflightId, voiceNote, borderless, videoGif, width, height, quote, 0, caption, stickerLocator, blurHash, audioHash, transformProperties);
super(contentType, transferState, size, fileName, 0, null, null, null, null, fastPreflightId, voiceNote, borderless, videoGif, width, height, quote, 0, caption, stickerLocator, blurHash, audioHash, transformProperties);
this.dataUri = Objects.requireNonNull(dataUri);
}

View File

@@ -48,14 +48,14 @@ public class AudioRecorder {
if (this.uiHandler != null) {
onAudioFocusChangeListener = focusChange -> {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
Log.i(TAG, "Audio focus change " + focusChange + " stopping recording via UI handler.");
Log.i(TAG, "Audio focus change " + focusChange + " stopping recording");
this.uiHandler.onRecordCanceled(false);
}
};
} else {
onAudioFocusChangeListener = focusChange -> {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
Log.i(TAG, "Audio focus change " + focusChange + " stopping recording.");
Log.i(TAG, "Audio focus change " + focusChange + " stopping recording");
stopRecording();
}
};

View File

@@ -25,14 +25,14 @@ sealed interface BluetoothVoiceNoteUtil {
fun destroy()
companion object {
fun create(context: Context, listener: (Boolean) -> Unit, bluetoothPermissionDeniedHandler: () -> Unit): BluetoothVoiceNoteUtil {
fun create(context: Context, listener: () -> Unit, bluetoothPermissionDeniedHandler: () -> Unit): BluetoothVoiceNoteUtil {
return if (Build.VERSION.SDK_INT >= 31) BluetoothVoiceNoteUtil31(listener) else BluetoothVoiceNoteUtilLegacy(context, listener, bluetoothPermissionDeniedHandler)
}
}
}
@RequiresApi(31)
private class BluetoothVoiceNoteUtil31(val listener: (Boolean) -> Unit) : BluetoothVoiceNoteUtil {
private class BluetoothVoiceNoteUtil31(val listener: () -> Unit) : BluetoothVoiceNoteUtil {
override fun connectBluetoothScoConnection() {
val audioManager = ApplicationDependencies.getAndroidCallAudioManager()
val device: AudioDeviceInfo? = audioManager.connectedBluetoothDevice
@@ -40,15 +40,13 @@ private class BluetoothVoiceNoteUtil31(val listener: (Boolean) -> Unit) : Blueto
val result: Boolean = audioManager.setCommunicationDevice(device)
if (result) {
Log.d(TAG, "Successfully set Bluetooth device as active communication device.")
listener(true)
} else {
Log.d(TAG, "Found Bluetooth device but failed to set it as active communication device.")
listener(false)
}
} else {
Log.d(TAG, "Could not find Bluetooth device in list of communications devices, falling back to current input.")
listener(false)
}
listener()
}
override fun disconnectBluetoothScoConnection() {
@@ -66,23 +64,15 @@ private class BluetoothVoiceNoteUtil31(val listener: (Boolean) -> Unit) : Blueto
* @param listener This will be executed on the main thread after the Bluetooth connection connects, or if it doesn't.
* @param bluetoothPermissionDeniedHandler called when we detect the Bluetooth permission has been denied to our app.
*/
private class BluetoothVoiceNoteUtilLegacy(val context: Context, val listener: (Boolean) -> Unit, val bluetoothPermissionDeniedHandler: () -> Unit) : BluetoothVoiceNoteUtil {
private class BluetoothVoiceNoteUtilLegacy(val context: Context, val listener: () -> Unit, val bluetoothPermissionDeniedHandler: () -> Unit) : BluetoothVoiceNoteUtil {
private val commandAndControlThread: HandlerThread = SignalExecutors.getAndStartHandlerThread("voice-note-audio", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD)
private val uiThreadHandler = Handler(context.mainLooper)
private val audioHandler: SignalAudioHandler = SignalAudioHandler(commandAndControlThread.looper)
private val deviceUpdatedListener: AudioDeviceUpdatedListener = object : AudioDeviceUpdatedListener {
override fun onAudioDeviceUpdated() {
when (signalBluetoothManager.state) {
SignalBluetoothManager.State.CONNECTED -> {
Log.d(TAG, "Bluetooth SCO connected. Starting voice note recording on UI thread.")
uiThreadHandler.post { listener(true) }
}
SignalBluetoothManager.State.ERROR,
SignalBluetoothManager.State.PERMISSION_DENIED -> {
Log.w(TAG, "Unable to complete Bluetooth connection due to ${signalBluetoothManager.state}. Starting voice note recording anyway on UI thread.")
uiThreadHandler.post { listener(false) }
}
else -> Log.d(TAG, "Current Bluetooth connection state: ${signalBluetoothManager.state}.")
if (signalBluetoothManager.state == SignalBluetoothManager.State.CONNECTED) {
Log.d(TAG, "Bluetooth SCO connected. Starting voice note recording on UI thread.")
uiThreadHandler.post { listener() }
}
}
}
@@ -115,7 +105,7 @@ private class BluetoothVoiceNoteUtilLegacy(val context: Context, val listener: (
bluetoothPermissionDeniedHandler()
hasWarnedAboutBluetooth = true
}
listener(false)
listener()
}
}
}

View File

@@ -140,7 +140,7 @@ class AvatarPickerFragment : Fragment(R.layout.avatar_picker_fragment) {
viewModel.onAvatarEditCompleted(vector)
}
setFragmentResultListener(PhotoEditorFragment.REQUEST_KEY_EDIT) { _, _ ->
setFragmentResultListener(PhotoEditorFragment.REQUEST_KEY_EDIT) { _, bundle ->
}
photoEditorLauncher = registerForActivityResult(PhotoEditorActivity.Contract()) { photo ->
@@ -155,7 +155,6 @@ class AvatarPickerFragment : Fragment(R.layout.avatar_picker_fragment) {
ViewUtil.hideKeyboard(requireContext(), requireView())
}
@Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_SELECT_IMAGE && resultCode == Activity.RESULT_OK && data != null) {
val media: Media = requireNotNull(data.getParcelableExtraCompat(AvatarSelectionActivity.EXTRA_MEDIA, Media::class.java))
@@ -195,7 +194,7 @@ class AvatarPickerFragment : Fragment(R.layout.avatar_picker_fragment) {
return true
}
private fun openEditor(avatar: Avatar) {
fun openEditor(avatar: Avatar) {
when (avatar) {
is Avatar.Photo -> openPhotoEditor(avatar)
is Avatar.Resource -> throw UnsupportedOperationException()
@@ -251,7 +250,6 @@ class AvatarPickerFragment : Fragment(R.layout.avatar_picker_fragment) {
.execute()
}
@Deprecated("Deprecated in Java")
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults)
}

View File

@@ -1,9 +1,9 @@
package org.thoughtcrime.securesms.avatar.text
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.distinctUntilChanged
import org.thoughtcrime.securesms.avatar.Avatar
import org.thoughtcrime.securesms.avatar.Avatars
import org.thoughtcrime.securesms.util.livedata.Store
@@ -12,7 +12,7 @@ class TextAvatarCreationViewModel(initialText: Avatar.Text) : ViewModel() {
private val store = Store(TextAvatarCreationState(initialText))
val state: LiveData<TextAvatarCreationState> = store.stateLiveData.distinctUntilChanged()
val state: LiveData<TextAvatarCreationState> = Transformations.distinctUntilChanged(store.stateLiveData)
fun setColor(colorPair: Avatars.ColorPair) {
store.update { it.copy(currentAvatar = it.currentAvatar.copy(color = colorPair)) }

View File

@@ -41,7 +41,7 @@ class AvatarView @JvmOverloads constructor(
}
storyRing.visible = true
storyRing.setBackgroundResource(if (hasUnreadStory) R.drawable.avatar_story_ring_active else R.drawable.avatar_story_ring_inactive)
storyRing.isActivated = hasUnreadStory
avatar.scaleX = storyRingScale
avatar.scaleY = storyRingScale

View File

@@ -45,7 +45,7 @@ public class BackupDialog {
@NonNull Runnable onBackupsEnabled)
{
String[] password = BackupUtil.generateBackupPassphrase();
AlertDialog dialog = new MaterialAlertDialogBuilder(context)
AlertDialog dialog = new AlertDialog.Builder(context)
.setTitle(R.string.BackupDialog_enable_local_backups)
.setView(backupDirectorySelectionIntent != null ? R.layout.backup_enable_dialog_v29 : R.layout.backup_enable_dialog)
.setPositiveButton(R.string.BackupDialog_enable_backups, null)

View File

@@ -1,245 +0,0 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup;
import androidx.annotation.NonNull;
import org.signal.core.util.Conversions;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.kdf.HKDF;
import org.signal.libsignal.protocol.util.ByteUtil;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.backup.proto.Attachment;
import org.thoughtcrime.securesms.backup.proto.Avatar;
import org.thoughtcrime.securesms.backup.proto.BackupFrame;
import org.thoughtcrime.securesms.backup.proto.DatabaseVersion;
import org.thoughtcrime.securesms.backup.proto.Header;
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 java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
class BackupFrameOutputStream extends FullBackupBase.BackupStream {
private static final String TAG = Log.tag(BackupFrameOutputStream.class);
private final OutputStream outputStream;
private final Cipher cipher;
private final Mac mac;
private final byte[] cipherKey;
private final byte[] iv;
private int counter;
private int frames;
BackupFrameOutputStream(@NonNull OutputStream output, @NonNull String passphrase) throws IOException {
try {
byte[] salt = Util.getSecretBytes(32);
byte[] key = getBackupKey(passphrase, salt);
byte[] derived = HKDF.deriveSecrets(key, "Backup Export".getBytes(), 64);
byte[][] split = ByteUtil.split(derived, 32, 32);
this.cipherKey = split[0];
byte[] macKey = split[1];
this.cipher = Cipher.getInstance("AES/CTR/NoPadding");
this.mac = Mac.getInstance("HmacSHA256");
this.outputStream = output;
this.iv = Util.getSecretBytes(16);
this.counter = Conversions.byteArrayToInt(iv);
mac.init(new SecretKeySpec(macKey, "HmacSHA256"));
byte[] header = new BackupFrame.Builder().header_(new Header.Builder()
.iv(new okio.ByteString(iv))
.salt(new okio.ByteString(salt))
.version(BackupVersions.CURRENT_VERSION)
.build())
.build()
.encode();
outputStream.write(Conversions.intToByteArray(header.length));
outputStream.write(header);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
throw new AssertionError(e);
}
}
public void write(SharedPreference preference) throws IOException {
write(outputStream, new BackupFrame.Builder().preference(preference).build());
}
public void write(KeyValue keyValue) throws IOException {
write(outputStream, new BackupFrame.Builder().keyValue(keyValue).build());
}
public void write(SqlStatement statement) throws IOException {
write(outputStream, new BackupFrame.Builder().statement(statement).build());
}
public void write(@NonNull String avatarName, @NonNull InputStream in, long size) throws IOException {
try {
write(outputStream, new BackupFrame.Builder()
.avatar(new Avatar.Builder()
.recipientId(avatarName)
.length(Util.toIntExact(size))
.build())
.build());
} catch (ArithmeticException e) {
Log.w(TAG, "Unable to write avatar to backup", e);
throw new FullBackupExporter.InvalidBackupStreamException();
}
if (writeStream(in) != size) {
throw new IOException("Size mismatch!");
}
}
public void write(@NonNull AttachmentId attachmentId, @NonNull InputStream in, long size) throws IOException {
try {
write(outputStream, new BackupFrame.Builder()
.attachment(new Attachment.Builder()
.rowId(attachmentId.getRowId())
.attachmentId(attachmentId.getUniqueId())
.length(Util.toIntExact(size))
.build())
.build());
} catch (ArithmeticException e) {
Log.w(TAG, "Unable to write " + attachmentId + " to backup", e);
throw new FullBackupExporter.InvalidBackupStreamException();
}
if (writeStream(in) != size) {
throw new IOException("Size mismatch!");
}
}
public void writeSticker(long rowId, @NonNull InputStream in, long size) throws IOException {
try {
write(outputStream, new BackupFrame.Builder()
.sticker(new Sticker.Builder()
.rowId(rowId)
.length(Util.toIntExact(size))
.build())
.build());
} catch (ArithmeticException e) {
Log.w(TAG, "Unable to write sticker to backup", e);
throw new FullBackupExporter.InvalidBackupStreamException();
}
if (writeStream(in) != size) {
throw new IOException("Size mismatch!");
}
}
void writeDatabaseVersion(int version) throws IOException {
write(outputStream, new BackupFrame.Builder()
.version(new DatabaseVersion.Builder().version(version).build())
.build());
}
void writeEnd() throws IOException {
write(outputStream, new BackupFrame.Builder().end(true).build());
}
/**
* @return The amount of data written from the provided InputStream.
*/
private long writeStream(@NonNull InputStream inputStream) throws IOException {
try {
Conversions.intToByteArray(iv, 0, counter++);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv));
mac.update(iv);
byte[] buffer = new byte[8192];
long total = 0;
int read;
while ((read = inputStream.read(buffer)) != -1) {
byte[] ciphertext = cipher.update(buffer, 0, read);
if (ciphertext != null) {
outputStream.write(ciphertext);
mac.update(ciphertext);
}
total += read;
}
byte[] remainder = cipher.doFinal();
outputStream.write(remainder);
mac.update(remainder);
byte[] attachmentDigest = mac.doFinal();
outputStream.write(attachmentDigest, 0, 10);
return total;
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e);
}
}
private void write(@NonNull OutputStream out, @NonNull BackupFrame frame) throws IOException {
try {
Conversions.intToByteArray(iv, 0, counter++);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv));
byte[] encodedFrame = frame.encode();
// this assumes a stream cipher
byte[] length = Conversions.intToByteArray(encodedFrame.length + 10);
if (BackupVersions.isFrameLengthEncrypted(BackupVersions.CURRENT_VERSION)) {
byte[] encryptedLength = cipher.update(length);
if (encryptedLength.length != length.length) {
throw new IOException("Stream cipher assumption has been violated!");
}
mac.update(encryptedLength);
length = encryptedLength;
}
byte[] frameCiphertext = cipher.doFinal(frame.encode());
if (frameCiphertext.length != encodedFrame.length) {
throw new IOException("Stream cipher assumption has been violated!");
}
byte[] frameMac = mac.doFinal(frameCiphertext);
out.write(length);
out.write(frameCiphertext);
out.write(frameMac, 0, 10);
frames++;
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e);
}
}
public void close() throws IOException {
outputStream.flush();
outputStream.close();
}
public int getFrames() {
return frames;
}
}

View File

@@ -27,7 +27,6 @@ import javax.crypto.spec.SecretKeySpec;
class BackupRecordInputStream extends FullBackupBase.BackupStream {
private final int version;
private final InputStream in;
private final Cipher cipher;
private final Mac mac;
@@ -56,21 +55,12 @@ class BackupRecordInputStream extends FullBackupBase.BackupStream {
Header header = frame.header_;
if (header.iv == null) {
throw new IOException("Missing IV!");
}
this.iv = header.iv.toByteArray();
if (iv.length != 16) {
throw new IOException("Invalid IV length!");
}
this.version = header.version != null ? header.version : 0;
if (!BackupVersions.isCompatible(version)) {
throw new IOException("Invalid backup version: " + version);
}
byte[] key = getBackupKey(passphrase, header.salt != null ? header.salt.toByteArray() : null);
byte[] derived = HKDF.deriveSecrets(key, "Backup Export".getBytes(), 64);
byte[][] split = ByteUtil.split(derived, 32, 32);
@@ -145,23 +135,7 @@ class BackupRecordInputStream extends FullBackupBase.BackupStream {
byte[] length = new byte[4];
StreamUtil.readFully(in, length);
Conversions.intToByteArray(iv, 0, counter++);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv));
int frameLength;
if (BackupVersions.isFrameLengthEncrypted(version)) {
mac.update(length);
// this depends upon cipher being a stream cipher mode in order to get back the length without needing a full AES block-size input
byte[] decryptedLength = cipher.update(length);
if (decryptedLength.length != length.length) {
throw new IOException("Cipher was not a stream cipher!");
}
frameLength = Conversions.byteArrayToInt(decryptedLength);
} else {
frameLength = Conversions.byteArrayToInt(length);
}
byte[] frame = new byte[frameLength];
byte[] frame = new byte[Conversions.byteArrayToInt(length)];
StreamUtil.readFully(in, frame);
byte[] theirMac = new byte[10];
@@ -174,6 +148,9 @@ class BackupRecordInputStream extends FullBackupBase.BackupStream {
throw new IOException("Bad MAC");
}
Conversions.intToByteArray(iv, 0, counter++);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv));
byte[] plaintext = cipher.doFinal(frame, 0, frame.length - 10);
return BackupFrame.ADAPTER.decode(plaintext);

View File

@@ -42,20 +42,12 @@ object BackupVerifier {
frame = inputStream.readFrame()
}
if (frame.end == true) {
count++
}
}
if (cancellationSignal.isCanceled) {
throw FullBackupExporter.BackupCanceledException()
}
if (count != expectedCount) {
Log.e(TAG, "Incorrect number of frames expected $expectedCount but only $count")
return false
}
return true
}

View File

@@ -1,21 +0,0 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup
object BackupVersions {
const val CURRENT_VERSION = 1
const val MINIMUM_VERSION = 0
@JvmStatic
fun isCompatible(version: Int): Boolean {
return version in MINIMUM_VERSION..CURRENT_VERSION
}
@JvmStatic
fun isFrameLengthEncrypted(version: Int): Boolean {
return version >= 1
}
}

View File

@@ -16,15 +16,24 @@ import com.annimon.stream.function.Predicate;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.greenrobot.eventbus.EventBus;
import org.signal.core.util.Conversions;
import org.signal.core.util.CursorUtil;
import org.signal.core.util.SetUtil;
import org.signal.core.util.SqlUtil;
import org.signal.core.util.Stopwatch;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.kdf.HKDF;
import org.signal.libsignal.protocol.util.ByteUtil;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.backup.proto.Attachment;
import org.thoughtcrime.securesms.backup.proto.Avatar;
import org.thoughtcrime.securesms.backup.proto.BackupFrame;
import org.thoughtcrime.securesms.backup.proto.DatabaseVersion;
import org.thoughtcrime.securesms.backup.proto.Header;
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.crypto.AttachmentSecret;
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
@@ -50,6 +59,7 @@ import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import java.io.File;
import java.io.FileNotFoundException;
@@ -57,6 +67,9 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
@@ -67,6 +80,14 @@ import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import okio.ByteString;
public class FullBackupExporter extends FullBackupBase {
@@ -207,7 +228,7 @@ public class FullBackupExporter extends FullBackupBase {
outputStream.close();
}
}
return new BackupEvent(BackupEvent.Type.FINISHED, outputStream.getFrames(), estimatedCountOutside);
return new BackupEvent(BackupEvent.Type.FINISHED, ++count, estimatedCountOutside);
}
private static long calculateCount(@NonNull Context context, @NonNull SQLiteDatabase input, List<String> tables) {
@@ -606,6 +627,187 @@ public class FullBackupExporter extends FullBackupBase {
return false;
}
private static class BackupFrameOutputStream extends BackupStream {
private final OutputStream outputStream;
private final Cipher cipher;
private final Mac mac;
private final byte[] cipherKey;
private final byte[] iv;
private int counter;
private BackupFrameOutputStream(@NonNull OutputStream output, @NonNull String passphrase) throws IOException {
try {
byte[] salt = Util.getSecretBytes(32);
byte[] key = getBackupKey(passphrase, salt);
byte[] derived = HKDF.deriveSecrets(key, "Backup Export".getBytes(), 64);
byte[][] split = ByteUtil.split(derived, 32, 32);
this.cipherKey = split[0];
byte[] macKey = split[1];
this.cipher = Cipher.getInstance("AES/CTR/NoPadding");
this.mac = Mac.getInstance("HmacSHA256");
this.outputStream = output;
this.iv = Util.getSecretBytes(16);
this.counter = Conversions.byteArrayToInt(iv);
mac.init(new SecretKeySpec(macKey, "HmacSHA256"));
byte[] header = new BackupFrame.Builder().header_(new Header.Builder()
.iv(new okio.ByteString(iv))
.salt(new okio.ByteString(salt))
.build())
.build()
.encode();
outputStream.write(Conversions.intToByteArray(header.length));
outputStream.write(header);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
throw new AssertionError(e);
}
}
public void write(SharedPreference preference) throws IOException {
write(outputStream, new BackupFrame.Builder().preference(preference).build());
}
public void write(KeyValue keyValue) throws IOException {
write(outputStream, new BackupFrame.Builder().keyValue(keyValue).build());
}
public void write(SqlStatement statement) throws IOException {
write(outputStream, new BackupFrame.Builder().statement(statement).build());
}
public void write(@NonNull String avatarName, @NonNull InputStream in, long size) throws IOException {
try {
write(outputStream, new BackupFrame.Builder()
.avatar(new Avatar.Builder()
.recipientId(avatarName)
.length(Util.toIntExact(size))
.build())
.build());
} catch (ArithmeticException e) {
Log.w(TAG, "Unable to write avatar to backup", e);
throw new InvalidBackupStreamException();
}
if (writeStream(in) != size) {
throw new IOException("Size mismatch!");
}
}
public void write(@NonNull AttachmentId attachmentId, @NonNull InputStream in, long size) throws IOException {
try {
write(outputStream, new BackupFrame.Builder()
.attachment(new Attachment.Builder()
.rowId(attachmentId.getRowId())
.attachmentId(attachmentId.getUniqueId())
.length(Util.toIntExact(size))
.build())
.build());
} catch (ArithmeticException e) {
Log.w(TAG, "Unable to write " + attachmentId + " to backup", e);
throw new InvalidBackupStreamException();
}
if (writeStream(in) != size) {
throw new IOException("Size mismatch!");
}
}
public void writeSticker(long rowId, @NonNull InputStream in, long size) throws IOException {
try {
write(outputStream, new BackupFrame.Builder()
.sticker(new Sticker.Builder()
.rowId(rowId)
.length(Util.toIntExact(size))
.build())
.build());
} catch (ArithmeticException e) {
Log.w(TAG, "Unable to write sticker to backup", e);
throw new InvalidBackupStreamException();
}
if (writeStream(in) != size) {
throw new IOException("Size mismatch!");
}
}
void writeDatabaseVersion(int version) throws IOException {
write(outputStream, new BackupFrame.Builder()
.version(new DatabaseVersion.Builder().version(version).build())
.build());
}
void writeEnd() throws IOException {
write(outputStream, new BackupFrame.Builder().end(true).build());
}
/**
* @return The amount of data written from the provided InputStream.
*/
private long writeStream(@NonNull InputStream inputStream) throws IOException {
try {
Conversions.intToByteArray(iv, 0, counter++);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv));
mac.update(iv);
byte[] buffer = new byte[8192];
long total = 0;
int read;
while ((read = inputStream.read(buffer)) != -1) {
byte[] ciphertext = cipher.update(buffer, 0, read);
if (ciphertext != null) {
outputStream.write(ciphertext);
mac.update(ciphertext);
}
total += read;
}
byte[] remainder = cipher.doFinal();
outputStream.write(remainder);
mac.update(remainder);
byte[] attachmentDigest = mac.doFinal();
outputStream.write(attachmentDigest, 0, 10);
return total;
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e);
}
}
private void write(@NonNull OutputStream out, @NonNull BackupFrame frame) throws IOException {
try {
Conversions.intToByteArray(iv, 0, counter++);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv));
byte[] frameCiphertext = cipher.doFinal(frame.encode());
byte[] frameMac = mac.doFinal(frameCiphertext);
byte[] length = Conversions.intToByteArray(frameCiphertext.length + 10);
out.write(length);
out.write(frameCiphertext);
out.write(frameMac, 0, 10);
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e);
}
}
public void close() throws IOException {
outputStream.flush();
outputStream.close();
}
}
public interface PostProcessor {
int postProcess(@NonNull Cursor cursor, int count) throws IOException;
}

View File

@@ -264,14 +264,12 @@ class GiftFlowConfirmationFragment :
override fun onPaymentComplete(gatewayRequest: GatewayRequest) {
val mainActivityIntent = MainActivity.clearTop(requireContext())
lifecycleDisposable += ConversationIntents
val conversationIntent = ConversationIntents
.createBuilder(requireContext(), viewModel.snapshot.recipient!!.id, -1L)
.subscribe { conversationIntent ->
requireActivity().startActivities(
arrayOf(mainActivityIntent, conversationIntent.withGiftBadge(viewModel.snapshot.giftBadge!!).build())
)
}
.withGiftBadge(viewModel.snapshot.giftBadge!!)
.build()
requireActivity().startActivities(arrayOf(mainActivityIntent, conversationIntent))
}
override fun onProcessorActionProcessed() = Unit

View File

@@ -23,6 +23,6 @@ data class GiftFlowState(
RECIPIENT_VERIFICATION,
TOKEN_REQUEST,
PAYMENT_PIPELINE,
FAILURE
FAILURE;
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.calls.links
import android.content.Context
import android.util.AttributeSet
import androidx.annotation.ColorRes
import androidx.appcompat.widget.LinearLayoutCompat
import androidx.core.content.ContextCompat
import com.google.android.material.button.MaterialButton
import org.thoughtcrime.securesms.R
class CallLinkJoinButton @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : LinearLayoutCompat(context, attrs) {
init {
orientation = VERTICAL
inflate(context, R.layout.call_link_join_button, this)
}
private val joinButton: MaterialButton = findViewById(R.id.join_button)
fun setTextColor(@ColorRes textColorResId: Int) {
joinButton.setTextColor(ContextCompat.getColor(context, textColorResId))
}
fun setJoinClickListener(onClickListener: OnClickListener) {
joinButton.setOnClickListener(onClickListener)
}
}

View File

@@ -6,8 +6,8 @@
package org.thoughtcrime.securesms.calls.links
import io.reactivex.rxjava3.core.Observable
import org.signal.core.util.Hex
import org.signal.core.util.logging.Log
import org.signal.ringrtc.CallException
import org.signal.ringrtc.CallLinkRootKey
import org.thoughtcrime.securesms.database.CallLinkTable
import org.thoughtcrime.securesms.database.DatabaseObserver
@@ -21,12 +21,10 @@ import java.net.URLDecoder
*/
object CallLinks {
private const val ROOT_KEY = "key"
private const val HTTPS_LINK_PREFIX = "https://signal.link/call/#key="
private const val SNGL_LINK_PREFIX = "sgnl://signal.link/#key="
private val TAG = Log.tag(CallLinks::class.java)
fun url(linkKeyBytes: ByteArray) = "$HTTPS_LINK_PREFIX${CallLinkRootKey(linkKeyBytes)}"
fun url(linkKeyBytes: ByteArray) = "https://signal.link/call/#key=${Hex.dump(linkKeyBytes)}"
fun watchCallLink(roomId: CallLinkRoomId): Observable<CallLinkTable.CallLink> {
return Observable.create { emitter ->
@@ -51,23 +49,8 @@ object CallLinks {
}
}
@JvmStatic
fun isCallLink(url: String): Boolean {
if (!url.startsWith(HTTPS_LINK_PREFIX) && !url.startsWith(SNGL_LINK_PREFIX)) {
Log.w(TAG, "Invalid url prefix.")
return false
}
return url.split("#").last().startsWith("key=")
}
@JvmStatic
fun parseUrl(url: String): CallLinkRootKey? {
if (!url.startsWith(HTTPS_LINK_PREFIX) && !url.startsWith(SNGL_LINK_PREFIX)) {
Log.w(TAG, "Invalid url prefix.")
return null
}
val parts = url.split("#")
if (parts.size != 2) {
Log.w(TAG, "Invalid fragment delimiter count in url.")
@@ -94,11 +77,7 @@ object CallLinks {
return null
}
return try {
CallLinkRootKey(key)
} catch (e: CallException) {
Log.w(TAG, "Invalid root key found in fragment query string.")
null
}
// TODO Parse the key into a byte array
return null
}
}

View File

@@ -37,6 +37,7 @@ import org.signal.core.ui.Buttons
import org.signal.core.ui.theme.SignalTheme
import org.signal.ringrtc.CallLinkRootKey
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.conversation.colors.AvatarColorPair
import org.thoughtcrime.securesms.database.CallLinkTable
import org.thoughtcrime.securesms.recipients.RecipientId
@@ -48,6 +49,7 @@ import java.time.Instant
@Preview
@Composable
private fun SignalCallRowPreview() {
val avatarColor = remember { AvatarColor.random() }
val callLink = remember {
val credentials = CallLinkCredentials.generate()
CallLinkTable.CallLink(
@@ -59,7 +61,8 @@ private fun SignalCallRowPreview() {
restrictions = org.signal.ringrtc.CallLinkState.Restrictions.NONE,
expiration = Instant.MAX,
revoked = false
)
),
avatarColor = avatarColor
)
}
SignalTheme(false) {
@@ -73,7 +76,7 @@ private fun SignalCallRowPreview() {
@Composable
fun SignalCallRow(
callLink: CallLinkTable.CallLink,
onJoinClicked: (() -> Unit)?,
onJoinClicked: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
@@ -119,15 +122,13 @@ fun SignalCallRow(
)
}
if (onJoinClicked != null) {
Spacer(modifier = Modifier.width(10.dp))
Spacer(modifier = Modifier.width(10.dp))
Buttons.Small(
onClick = onJoinClicked,
modifier = Modifier.align(CenterVertically)
) {
Text(text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__join))
}
Buttons.Small(
onClick = onJoinClicked,
modifier = Modifier.align(CenterVertically)
) {
Text(text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__join))
}
}
}

View File

@@ -34,7 +34,6 @@ import androidx.core.app.ShareCompat
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import io.reactivex.rxjava3.kotlin.subscribeBy
import org.signal.core.ui.BottomSheets
import org.signal.core.ui.Buttons
import org.signal.core.ui.Dividers
import org.signal.core.ui.Rows
@@ -87,7 +86,7 @@ class CreateCallLinkBottomSheetDialogFragment : ComposeBottomSheetDialogFragment
) {
val callLink: CallLinkTable.CallLink by viewModel.callLink
BottomSheets.Handle(modifier = Modifier.align(Alignment.CenterHorizontally))
Handle(modifier = Modifier.align(Alignment.CenterHorizontally))
Spacer(modifier = Modifier.height(20.dp))
@@ -109,7 +108,7 @@ class CreateCallLinkBottomSheetDialogFragment : ComposeBottomSheetDialogFragment
Rows.TextRow(
text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__add_call_name),
onClick = this@CreateCallLinkBottomSheetDialogFragment::onAddACallNameClicked
modifier = Modifier.clickable(onClick = this@CreateCallLinkBottomSheetDialogFragment::onAddACallNameClicked)
)
Rows.ToggleRow(
@@ -124,19 +123,19 @@ class CreateCallLinkBottomSheetDialogFragment : ComposeBottomSheetDialogFragment
Rows.TextRow(
text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__share_link_via_signal),
icon = ImageVector.vectorResource(id = R.drawable.symbol_forward_24),
onClick = this@CreateCallLinkBottomSheetDialogFragment::onShareViaSignalClicked
modifier = Modifier.clickable(onClick = this@CreateCallLinkBottomSheetDialogFragment::onShareViaSignalClicked)
)
Rows.TextRow(
text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__copy_link),
icon = ImageVector.vectorResource(id = R.drawable.symbol_copy_android_24),
onClick = this@CreateCallLinkBottomSheetDialogFragment::onCopyLinkClicked
modifier = Modifier.clickable(onClick = this@CreateCallLinkBottomSheetDialogFragment::onCopyLinkClicked)
)
Rows.TextRow(
text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__share_link),
icon = ImageVector.vectorResource(id = R.drawable.symbol_share_android_24),
onClick = this@CreateCallLinkBottomSheetDialogFragment::onShareLinkClicked
modifier = Modifier.clickable(onClick = this@CreateCallLinkBottomSheetDialogFragment::onShareLinkClicked)
)
Buttons.MediumTonal(

View File

@@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.calls.links.create
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.database.CallLinkTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
@@ -23,7 +24,7 @@ import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkManager
class CreateCallLinkRepository(
private val callLinkManager: SignalCallLinkManager = ApplicationDependencies.getSignalCallManager().callLinkManager
) {
fun ensureCallLinkCreated(credentials: CallLinkCredentials): Single<EnsureCallLinkCreatedResult> {
fun ensureCallLinkCreated(credentials: CallLinkCredentials, avatarColor: AvatarColor): Single<EnsureCallLinkCreatedResult> {
val callLinkRecipientId = Single.fromCallable {
SignalDatabase.recipients.getByCallLinkRoomId(credentials.roomId)
}
@@ -40,7 +41,8 @@ class CreateCallLinkRepository(
recipientId = RecipientId.UNKNOWN,
roomId = credentials.roomId,
credentials = credentials,
state = it.state
state = it.state,
avatarColor = avatarColor
)
)

View File

@@ -16,6 +16,7 @@ import io.reactivex.rxjava3.kotlin.subscribeBy
import org.signal.ringrtc.CallLinkState.Restrictions
import org.thoughtcrime.securesms.calls.links.CallLinks
import org.thoughtcrime.securesms.calls.links.UpdateCallLinkRepository
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.database.CallLinkTable
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials
@@ -28,6 +29,7 @@ class CreateCallLinkViewModel(
private val mutationRepository: UpdateCallLinkRepository = UpdateCallLinkRepository()
) : ViewModel() {
private val credentials = CallLinkCredentials.generate()
private val avatarColor = AvatarColor.random()
private val _callLink: MutableState<CallLinkTable.CallLink> = mutableStateOf(
CallLinkTable.CallLink(
recipientId = RecipientId.UNKNOWN,
@@ -38,7 +40,8 @@ class CreateCallLinkViewModel(
restrictions = Restrictions.NONE,
revoked = false,
expiration = Instant.MAX
)
),
avatarColor = avatarColor
)
)
@@ -60,7 +63,7 @@ class CreateCallLinkViewModel(
}
fun commitCallLink(): Single<EnsureCallLinkCreatedResult> {
return repository.ensureCallLinkCreated(credentials)
return repository.ensureCallLinkCreated(credentials, avatarColor)
}
fun setApproveAllMembers(approveAllMembers: Boolean): Single<UpdateCallLinkResult> {

View File

@@ -10,6 +10,7 @@ import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
@@ -199,7 +200,8 @@ private fun CallLinkDetailsPreview() {
revoked = false,
restrictions = Restrictions.NONE,
expiration = Instant.MAX
)
),
avatarColor = avatarColor
)
}
@@ -246,7 +248,7 @@ private fun CallLinkDetails(
Rows.TextRow(
text = stringResource(id = R.string.CallLinkDetailsFragment__add_call_name),
onClick = callback::onEditNameClicked
modifier = Modifier.clickable(onClick = callback::onEditNameClicked)
)
Rows.ToggleRow(
@@ -260,14 +262,14 @@ private fun CallLinkDetails(
Rows.TextRow(
text = stringResource(id = R.string.CallLinkDetailsFragment__share_link),
icon = ImageVector.vectorResource(id = R.drawable.symbol_link_24),
onClick = callback::onShareClicked
modifier = Modifier.clickable(onClick = callback::onShareClicked)
)
Rows.TextRow(
text = stringResource(id = R.string.CallLinkDetailsFragment__delete_call_link),
icon = ImageVector.vectorResource(id = R.drawable.symbol_trash_24),
foregroundTint = MaterialTheme.colorScheme.error,
onClick = callback::onDeleteClicked
modifier = Modifier.clickable(onClick = callback::onDeleteClicked)
)
}

View File

@@ -5,15 +5,17 @@
package org.thoughtcrime.securesms.calls.links.details
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.kotlin.subscribeBy
import io.reactivex.rxjava3.schedulers.Schedulers
import org.signal.core.util.concurrent.MaybeCompat
import org.signal.core.util.orNull
import org.thoughtcrime.securesms.database.CallLinkTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
import org.thoughtcrime.securesms.service.webrtc.links.ReadCallLinkResult
import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkManager
@@ -22,7 +24,7 @@ class CallLinkDetailsRepository(
private val callLinkManager: SignalCallLinkManager = ApplicationDependencies.getSignalCallManager().callLinkManager
) {
fun refreshCallLinkState(callLinkRoomId: CallLinkRoomId): Disposable {
return MaybeCompat.fromCallable { SignalDatabase.callLinks.getCallLinkByRoomId(callLinkRoomId) }
return Maybe.fromCallable<CallLinkTable.CallLink> { SignalDatabase.callLinks.getCallLinkByRoomId(callLinkRoomId) }
.flatMapSingle { callLinkManager.readCallLink(it.credentials!!) }
.subscribeOn(Schedulers.io())
.subscribeBy { result ->
@@ -34,7 +36,7 @@ class CallLinkDetailsRepository(
}
fun watchCallLinkRecipient(callLinkRoomId: CallLinkRoomId): Observable<Recipient> {
return MaybeCompat.fromCallable { SignalDatabase.recipients.getByCallLinkRoomId(callLinkRoomId).orNull() }
return Maybe.fromCallable<RecipientId> { SignalDatabase.recipients.getByCallLinkRoomId(callLinkRoomId).orNull() }
.flatMapObservable { Recipient.observable(it) }
.distinctUntilChanged { a, b -> a.hasSameContent(b) }
.subscribeOn(Schedulers.io())

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