mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-16 07:57:38 +00:00
Compare commits
257 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5537039e46 | ||
|
|
0a40432ed4 | ||
|
|
73e46053f0 | ||
|
|
a835e5d143 | ||
|
|
073d5dfe8c | ||
|
|
bfba60b6b6 | ||
|
|
84d9e1d28e | ||
|
|
a14c61c370 | ||
|
|
0db4630f58 | ||
|
|
6d2e51def6 | ||
|
|
091eb0aa2b | ||
|
|
59f05e0815 | ||
|
|
a513e93d18 | ||
|
|
91f6cff4df | ||
|
|
ec3ec969eb | ||
|
|
54d0df9a05 | ||
|
|
320669c54e | ||
|
|
bf491c25f7 | ||
|
|
1e153e129c | ||
|
|
b546d661ba | ||
|
|
6a1a657451 | ||
|
|
ece087eaae | ||
|
|
a04590b658 | ||
|
|
eb6a14e686 | ||
|
|
c7bb0eadc2 | ||
|
|
d70fe8f2cd | ||
|
|
0fd8f73cca | ||
|
|
b9fc36be5a | ||
|
|
4de27482bb | ||
|
|
ba347301cf | ||
|
|
296a113c65 | ||
|
|
43fe789807 | ||
|
|
98dfd5bfbf | ||
|
|
f387785a46 | ||
|
|
7b3d8d01ae | ||
|
|
1712442560 | ||
|
|
e4ddedcc48 | ||
|
|
14503b952a | ||
|
|
5cb3e1cd02 | ||
|
|
7959343661 | ||
|
|
52062679d4 | ||
|
|
9e8350e8c2 | ||
|
|
495c91ba86 | ||
|
|
92b9fda6c7 | ||
|
|
fecfd7cd78 | ||
|
|
6cd6073bc7 | ||
|
|
f149c0adb9 | ||
|
|
3708cc5583 | ||
|
|
4dd8e81db7 | ||
|
|
06b414f4ef | ||
|
|
d471647e12 | ||
|
|
dd3bad858d | ||
|
|
0fe6538ce4 | ||
|
|
1e2f7f0775 | ||
|
|
055b4691d7 | ||
|
|
ebdfa88882 | ||
|
|
d79c4775b6 | ||
|
|
a13599ae2a | ||
|
|
96b2051400 | ||
|
|
ad6d1a2e8d | ||
|
|
eada1e96ee | ||
|
|
91fbc236ce | ||
|
|
202f20893c | ||
|
|
f1d204b834 | ||
|
|
73e19209ff | ||
|
|
835bf3998f | ||
|
|
d83ef56ab1 | ||
|
|
a84a9c5381 | ||
|
|
c6f29fc950 | ||
|
|
4d9dc42868 | ||
|
|
32b66643c5 | ||
|
|
3850c9c89d | ||
|
|
60ae883df6 | ||
|
|
a7e3bdc892 | ||
|
|
9b60bd9a4b | ||
|
|
e9d98b7d39 | ||
|
|
cb0e7ade14 | ||
|
|
268f5c807d | ||
|
|
f6003023bf | ||
|
|
3f160f256a | ||
|
|
9846517075 | ||
|
|
0f1cc03dc0 | ||
|
|
0e5031ab45 | ||
|
|
0e4926b5ec | ||
|
|
a25e7c6d3e | ||
|
|
4081ac2a83 | ||
|
|
98a528f595 | ||
|
|
680325b5ee | ||
|
|
16668574a9 | ||
|
|
0d8f6de4c1 | ||
|
|
4c0a98d526 | ||
|
|
10f78d5daa | ||
|
|
3ce5a7da67 | ||
|
|
4d47b9c594 | ||
|
|
9f6eb142d2 | ||
|
|
0e08b4ee26 | ||
|
|
9b85907918 | ||
|
|
6463dca2c6 | ||
|
|
498b7fee69 | ||
|
|
3478e13d38 | ||
|
|
5f0d37739a | ||
|
|
c5b4f44ab8 | ||
|
|
819c9f61dc | ||
|
|
4f167feaf5 | ||
|
|
de558bc87c | ||
|
|
4a5a65ff6c | ||
|
|
c56e63d62f | ||
|
|
8cd9a3cabe | ||
|
|
3a8c324c12 | ||
|
|
ff882edeae | ||
|
|
fb0aa55cbb | ||
|
|
51015dc898 | ||
|
|
4af40e7861 | ||
|
|
24fcc0c3b0 | ||
|
|
993fc24dd3 | ||
|
|
fddc6bcd5f | ||
|
|
558051086e | ||
|
|
2c187bc55d | ||
|
|
e947979169 | ||
|
|
08f1ddb212 | ||
|
|
4c318d8d82 | ||
|
|
3e6ebfabb0 | ||
|
|
55f4692d99 | ||
|
|
ebe82cf3e6 | ||
|
|
21a8434e4d | ||
|
|
4990778a97 | ||
|
|
303e5c7996 | ||
|
|
599caee229 | ||
|
|
e6f28c6cdd | ||
|
|
fd3b0ee375 | ||
|
|
bd11ed9f17 | ||
|
|
a6a185004d | ||
|
|
3cc556d803 | ||
|
|
c3f9984346 | ||
|
|
10df4ee0d1 | ||
|
|
c03a183904 | ||
|
|
a2893fbec7 | ||
|
|
19cbace33d | ||
|
|
8a78481cca | ||
|
|
e1fd254d15 | ||
|
|
019219f1e1 | ||
|
|
ad3c04cb52 | ||
|
|
61f9dc7498 | ||
|
|
4deb16a37a | ||
|
|
4129151bd2 | ||
|
|
10cf431537 | ||
|
|
011dd2d973 | ||
|
|
c85c4c5020 | ||
|
|
5f1439df00 | ||
|
|
e76bec63a3 | ||
|
|
fc2b67aa0f | ||
|
|
bcd0360dd0 | ||
|
|
04bf2cd0c2 | ||
|
|
aba51da932 | ||
|
|
f8520d83be | ||
|
|
69003dfbe2 | ||
|
|
380b377ed8 | ||
|
|
4c5db983e3 | ||
|
|
48c887ac03 | ||
|
|
f207a82d2f | ||
|
|
56f6888d49 | ||
|
|
66ece479f6 | ||
|
|
c1cc2b064c | ||
|
|
98980b8192 | ||
|
|
79ec76f11f | ||
|
|
45a1c5c369 | ||
|
|
2dc41f319c | ||
|
|
2cdb1b8300 | ||
|
|
e846b4e20a | ||
|
|
961057f620 | ||
|
|
e686a09ce4 | ||
|
|
fc8cf2957f | ||
|
|
0bef37bfc1 | ||
|
|
1618141342 | ||
|
|
d7fb05f596 | ||
|
|
2eb15cc8e3 | ||
|
|
424a0233c2 | ||
|
|
40cf87307a | ||
|
|
643206b946 | ||
|
|
cc95041519 | ||
|
|
45b498f62f | ||
|
|
9e6d78ba5f | ||
|
|
95eba78d9c | ||
|
|
5d9f00b268 | ||
|
|
6a01388e82 | ||
|
|
2ef6f78d39 | ||
|
|
a754c39599 | ||
|
|
14622cd06c | ||
|
|
3132cd1198 | ||
|
|
94c35d86e2 | ||
|
|
3c2c6d782a | ||
|
|
1764b21214 | ||
|
|
260e572071 | ||
|
|
54251a27a8 | ||
|
|
88a8430c31 | ||
|
|
678b653873 | ||
|
|
21592ca5c0 | ||
|
|
1bca2f06bd | ||
|
|
9f166105a6 | ||
|
|
ea08b59e6b | ||
|
|
9aca0af22c | ||
|
|
591d8c3d1a | ||
|
|
22b73494a7 | ||
|
|
9bb80077c6 | ||
|
|
646f41663f | ||
|
|
63cca2de66 | ||
|
|
16361ac489 | ||
|
|
e8f39e8f71 | ||
|
|
7945b3c971 | ||
|
|
e5d196c642 | ||
|
|
979f87db78 | ||
|
|
b70b4fac91 | ||
|
|
031d7b9cb0 | ||
|
|
c68859c606 | ||
|
|
23804046c6 | ||
|
|
7b13550086 | ||
|
|
7949996c5c | ||
|
|
b190f9495a | ||
|
|
b4c0635a63 | ||
|
|
21bd8a308b | ||
|
|
800405fc3e | ||
|
|
bf18db354c | ||
|
|
e0b89bedd4 | ||
|
|
504b7ad5b3 | ||
|
|
0558808370 | ||
|
|
cff3840c51 | ||
|
|
a46fc96ff1 | ||
|
|
77be721f5a | ||
|
|
023b181917 | ||
|
|
311ef0d65b | ||
|
|
74314e08ac | ||
|
|
81df9fcddb | ||
|
|
ff64c2a911 | ||
|
|
8a9605ade8 | ||
|
|
7a449a971f | ||
|
|
258951dea8 | ||
|
|
cdff0a61f2 | ||
|
|
2200af9c31 | ||
|
|
dfb913cb98 | ||
|
|
9ee10512fb | ||
|
|
81c10a1eae | ||
|
|
3e8b5ca91d | ||
|
|
ba0b0cdefa | ||
|
|
f00ee0a226 | ||
|
|
bd4a69eddc | ||
|
|
8c95b37826 | ||
|
|
133d3145d1 | ||
|
|
4a0db31103 | ||
|
|
ce85bb1575 | ||
|
|
eee4ff3f87 | ||
|
|
f6356c9720 | ||
|
|
42d2d415d6 | ||
|
|
683247bf98 | ||
|
|
d7404cf32f | ||
|
|
ec1f771364 | ||
|
|
95ac9628fb | ||
|
|
ba68d795af |
3
.github/workflows/android.yml
vendored
3
.github/workflows/android.yml
vendored
@@ -8,6 +8,9 @@ on:
|
||||
- '4.**'
|
||||
- '5.**'
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
|
||||
3
.github/workflows/docker.yml
vendored
3
.github/workflows/docker.yml
vendored
@@ -4,6 +4,9 @@ on:
|
||||
schedule:
|
||||
- cron: '0 5 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import com.android.build.api.dsl.ManagedVirtualDevice
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'com.google.protobuf'
|
||||
@@ -11,7 +13,7 @@ apply from: 'static-ips.gradle'
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "https://raw.github.com/signalapp/maven/master/sqlcipher/release/"
|
||||
url "https://raw.githubusercontent.com/signalapp/maven/master/sqlcipher/release/"
|
||||
content {
|
||||
includeGroupByRegex "org\\.signal.*"
|
||||
}
|
||||
@@ -50,8 +52,8 @@ ktlint {
|
||||
version = "0.43.2"
|
||||
}
|
||||
|
||||
def canonicalVersionCode = 1167
|
||||
def canonicalVersionName = "6.3.2"
|
||||
def canonicalVersionCode = 1188
|
||||
def canonicalVersionName = "6.7.2"
|
||||
|
||||
def postFixSize = 100
|
||||
def abiPostFix = ['universal' : 0,
|
||||
@@ -93,10 +95,6 @@ android {
|
||||
freeCompilerArgs = ["-Xallow-result-return-type"]
|
||||
}
|
||||
|
||||
dexOptions {
|
||||
javaMaxHeapSize "4g"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
if (keystores.debug != null) {
|
||||
debug {
|
||||
@@ -114,6 +112,17 @@ android {
|
||||
unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
|
||||
managedDevices {
|
||||
devices {
|
||||
pixel3api30 (ManagedVirtualDevice) {
|
||||
device = "Pixel 3"
|
||||
apiLevel = 30
|
||||
systemImageSource = "google-atd"
|
||||
require64Bit = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
@@ -165,7 +174,7 @@ android {
|
||||
multiDexEnabled true
|
||||
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
project.ext.set("archivesBaseName", "Signal");
|
||||
project.ext.set("archivesBaseName", "Signal")
|
||||
|
||||
manifestPlaceholders = [mapsKey:"AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"]
|
||||
|
||||
@@ -180,6 +189,7 @@ android {
|
||||
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_SFU_URL", "\"https://sfu.voip.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_STAGING_SFU_URL", "\"https://sfu.staging.voip.signal.org\""
|
||||
buildConfigField "String[]", "SIGNAL_SFU_INTERNAL_NAMES", "new String[]{\"Test\", \"Staging\", \"Development\"}"
|
||||
buildConfigField "String[]", "SIGNAL_SFU_INTERNAL_URLS", "new String[]{\"https://sfu.test.voip.signal.org\", \"https://sfu.staging.voip.signal.org\", \"https://sfu.staging.test.voip.signal.org\"}"
|
||||
buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\""
|
||||
@@ -574,7 +584,7 @@ def getLastCommitTimestamp() {
|
||||
}
|
||||
|
||||
new ByteArrayOutputStream().withStream { os ->
|
||||
def result = exec {
|
||||
exec {
|
||||
executable = 'git'
|
||||
args = ['log', '-1', '--pretty=format:%ct']
|
||||
standardOutput = os
|
||||
@@ -586,20 +596,20 @@ def getLastCommitTimestamp() {
|
||||
|
||||
def getGitHash() {
|
||||
if (!(new File('.git').exists())) {
|
||||
return "abcd1234"
|
||||
throw new IllegalStateException("Must be a git repository to guarantee reproducible builds! (git hash is part of APK)")
|
||||
}
|
||||
|
||||
def stdout = new ByteArrayOutputStream()
|
||||
exec {
|
||||
commandLine 'git', 'rev-parse', '--short', 'HEAD'
|
||||
commandLine 'git', 'rev-parse', 'HEAD'
|
||||
standardOutput = stdout
|
||||
}
|
||||
return stdout.toString().trim()
|
||||
return stdout.toString().trim().substring(0, 12)
|
||||
}
|
||||
|
||||
def getCurrentGitTag() {
|
||||
if (!(new File('.git').exists())) {
|
||||
return ''
|
||||
throw new IllegalStateException("Must be a git repository to guarantee reproducible builds! (git hash is part of APK)")
|
||||
}
|
||||
|
||||
def stdout = new ByteArrayOutputStream()
|
||||
@@ -633,13 +643,13 @@ def loadKeystoreProperties(filename) {
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
def keystoreProperties = new Properties()
|
||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
return keystoreProperties;
|
||||
return keystoreProperties
|
||||
} else {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
def getDateSuffix() {
|
||||
static def getDateSuffix() {
|
||||
def date = new Date()
|
||||
def formattedDate = date.format('yyyy-MM-dd-HH:mm')
|
||||
return formattedDate
|
||||
|
||||
@@ -2,9 +2,11 @@ package org.thoughtcrime.securesms.components.settings.app.changenumber
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.FlakyTest
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
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
|
||||
@@ -166,6 +168,8 @@ class ChangeNumberViewModelTest {
|
||||
* and apply the pending state after confirming the change on the server.
|
||||
*/
|
||||
@Test
|
||||
@FlakyTest
|
||||
@Ignore("Test sometimes requires manual intervention to continue.")
|
||||
fun testChangeNumber_givenNetworkFailedApiCallEnRouteToClient() {
|
||||
// GIVEN
|
||||
val aci = Recipient.self().requireServiceId()
|
||||
|
||||
@@ -9,11 +9,8 @@ import org.junit.runner.RunWith
|
||||
import org.signal.core.util.ThreadUtil
|
||||
import org.thoughtcrime.securesms.attachments.PointerAttachment
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
||||
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.releasechannel.ReleaseChannel
|
||||
@@ -75,7 +72,7 @@ class ConversationItemPreviewer {
|
||||
attachments = PointerAttachment.forPointers(Optional.of(attachments)),
|
||||
)
|
||||
|
||||
SignalDatabase.mms.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
|
||||
SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
|
||||
|
||||
ThreadUtil.sleep(1)
|
||||
}
|
||||
@@ -94,7 +91,7 @@ class ConversationItemPreviewer {
|
||||
attachments = PointerAttachment.forPointers(Optional.of(attachments)),
|
||||
)
|
||||
|
||||
val insert = SignalDatabase.mms.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
|
||||
val insert = SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
|
||||
|
||||
SignalDatabase.attachments.getAttachmentsForMessage(insert.messageId).forEachIndexed { index, attachment ->
|
||||
// if (index != 1) {
|
||||
@@ -112,29 +109,16 @@ class ConversationItemPreviewer {
|
||||
attachment()
|
||||
}
|
||||
|
||||
val message = OutgoingMediaMessage(
|
||||
other,
|
||||
body,
|
||||
PointerAttachment.forPointers(Optional.of(attachments)),
|
||||
System.currentTimeMillis(),
|
||||
-1,
|
||||
0,
|
||||
false,
|
||||
ThreadDatabase.DistributionTypes.DEFAULT,
|
||||
StoryType.NONE,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptySet(),
|
||||
emptySet(),
|
||||
null
|
||||
val message = OutgoingMessage(
|
||||
recipient = other,
|
||||
body = body,
|
||||
attachments = PointerAttachment.forPointers(Optional.of(attachments)),
|
||||
timestamp = System.currentTimeMillis(),
|
||||
isSecure = true
|
||||
)
|
||||
|
||||
val insert = SignalDatabase.mms.insertMessageOutbox(
|
||||
OutgoingSecureMediaMessage(message),
|
||||
val insert = SignalDatabase.messages.insertMessageOutbox(
|
||||
message,
|
||||
SignalDatabase.threads.getOrCreateThreadIdFor(other),
|
||||
false,
|
||||
null
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase
|
||||
import org.thoughtcrime.securesms.database.IdentityTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
|
||||
@@ -32,7 +32,7 @@ class SafetyNumberChangeDialogPreviewer {
|
||||
|
||||
SignalDatabase.recipients.setProfileName(other.id, ProfileName.fromParts("Super really long name like omg", "But seriously it's long like really really long"))
|
||||
|
||||
harness.setVerified(other, IdentityDatabase.VerifiedStatus.VERIFIED)
|
||||
harness.setVerified(other, IdentityTable.VerifiedStatus.VERIFIED)
|
||||
harness.changeIdentityKey(other)
|
||||
|
||||
val scenario: ActivityScenario<ConversationActivity> = harness.launchActivity { putExtra("recipient_id", other.id.serialize()) }
|
||||
@@ -52,7 +52,7 @@ class SafetyNumberChangeDialogPreviewer {
|
||||
othersRecipients.forEach { other ->
|
||||
SignalDatabase.recipients.setProfileName(other.id, ProfileName.fromParts("My", "Name"))
|
||||
|
||||
harness.setVerified(other, IdentityDatabase.VerifiedStatus.DEFAULT)
|
||||
harness.setVerified(other, IdentityTable.VerifiedStatus.DEFAULT)
|
||||
harness.changeIdentityKey(other)
|
||||
|
||||
SignalDatabase.distributionLists.addMemberToList(DistributionListId.MY_STORY, DistributionListPrivacyMode.ONLY_WITH, other.id)
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import java.util.Optional
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AttachmentDatabaseTest {
|
||||
class AttachmentTableTest {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
@@ -39,7 +39,7 @@ class AttachmentDatabaseTest {
|
||||
val blob = BlobProvider.getInstance().forData(byteArrayOf(1, 2, 3, 4, 5)).createForSingleSessionInMemory()
|
||||
val highQualityProperties = createHighQualityTransformProperties()
|
||||
val highQualityImage = createAttachment(1, blob, highQualityProperties)
|
||||
val lowQualityImage = createAttachment(1, blob, AttachmentDatabase.TransformProperties.empty())
|
||||
val lowQualityImage = createAttachment(1, blob, AttachmentTable.TransformProperties.empty())
|
||||
val attachment = SignalDatabase.attachments.insertAttachmentForPreUpload(highQualityImage)
|
||||
val attachment2 = SignalDatabase.attachments.insertAttachmentForPreUpload(lowQualityImage)
|
||||
|
||||
@@ -55,8 +55,8 @@ class AttachmentDatabaseTest {
|
||||
false
|
||||
)
|
||||
|
||||
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentDatabase.DATA)
|
||||
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentDatabase.DATA)
|
||||
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA)
|
||||
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA)
|
||||
|
||||
assertNotEquals(attachment1Info, attachment2Info)
|
||||
}
|
||||
@@ -81,13 +81,13 @@ class AttachmentDatabaseTest {
|
||||
true
|
||||
)
|
||||
|
||||
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentDatabase.DATA)
|
||||
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentDatabase.DATA)
|
||||
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA)
|
||||
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA)
|
||||
|
||||
assertNotEquals(attachment1Info, attachment2Info)
|
||||
}
|
||||
|
||||
private fun createAttachment(id: Long, uri: Uri, transformProperties: AttachmentDatabase.TransformProperties): UriAttachment {
|
||||
private fun createAttachment(id: Long, uri: Uri, transformProperties: AttachmentTable.TransformProperties): UriAttachment {
|
||||
return UriAttachmentBuilder.build(
|
||||
id,
|
||||
uri = uri,
|
||||
@@ -96,8 +96,8 @@ class AttachmentDatabaseTest {
|
||||
)
|
||||
}
|
||||
|
||||
private fun createHighQualityTransformProperties(): AttachmentDatabase.TransformProperties {
|
||||
return AttachmentDatabase.TransformProperties.forSentMediaQuality(Optional.empty(), SentMediaQuality.HIGH)
|
||||
private fun createHighQualityTransformProperties(): AttachmentTable.TransformProperties {
|
||||
return AttachmentTable.TransformProperties.forSentMediaQuality(Optional.empty(), SentMediaQuality.HIGH)
|
||||
}
|
||||
|
||||
private fun createMediaStream(byteArray: ByteArray): MediaStream {
|
||||
@@ -10,9 +10,9 @@ import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.api.push.ACI
|
||||
import java.util.UUID
|
||||
|
||||
class DistributionListDatabaseTest {
|
||||
class DistributionListTablesTest {
|
||||
|
||||
private lateinit var distributionDatabase: DistributionListDatabase
|
||||
private lateinit var distributionDatabase: DistributionListTables
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
@@ -17,8 +17,8 @@ import java.util.UUID
|
||||
|
||||
@Suppress("ClassName")
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MmsDatabaseTest_gifts {
|
||||
private lateinit var mms: MmsDatabase
|
||||
class MessageTableTest_gifts {
|
||||
private lateinit var mms: MessageTable
|
||||
|
||||
private val localAci = ACI.from(UUID.randomUUID())
|
||||
private val localPni = PNI.from(UUID.randomUUID())
|
||||
@@ -27,7 +27,7 @@ class MmsDatabaseTest_gifts {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mms = SignalDatabase.mms
|
||||
mms = SignalDatabase.messages
|
||||
|
||||
mms.deleteAllThreads()
|
||||
|
||||
@@ -4,8 +4,7 @@ import org.thoughtcrime.securesms.database.model.ParentStoryId
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
||||
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import java.util.Optional
|
||||
|
||||
@@ -21,36 +20,28 @@ object MmsHelper {
|
||||
subscriptionId: Int = -1,
|
||||
expiresIn: Long = 0,
|
||||
viewOnce: Boolean = false,
|
||||
distributionType: Int = ThreadDatabase.DistributionTypes.DEFAULT,
|
||||
threadId: Long = 1,
|
||||
distributionType: Int = ThreadTable.DistributionTypes.DEFAULT,
|
||||
threadId: Long = SignalDatabase.threads.getOrCreateThreadIdFor(recipient, distributionType),
|
||||
storyType: StoryType = StoryType.NONE,
|
||||
parentStoryId: ParentStoryId? = null,
|
||||
isStoryReaction: Boolean = false,
|
||||
giftBadge: GiftBadge? = null,
|
||||
secure: Boolean = true
|
||||
): Long {
|
||||
val message = OutgoingMediaMessage(
|
||||
recipient,
|
||||
body,
|
||||
emptyList(),
|
||||
sentTimeMillis,
|
||||
subscriptionId,
|
||||
expiresIn,
|
||||
viewOnce,
|
||||
distributionType,
|
||||
storyType,
|
||||
parentStoryId,
|
||||
isStoryReaction,
|
||||
null,
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptySet(),
|
||||
emptySet(),
|
||||
giftBadge
|
||||
).let {
|
||||
if (secure) OutgoingSecureMediaMessage(it) else it
|
||||
}
|
||||
val message = OutgoingMessage(
|
||||
recipient = recipient,
|
||||
body = body,
|
||||
timestamp = sentTimeMillis,
|
||||
subscriptionId = subscriptionId,
|
||||
expiresIn = expiresIn,
|
||||
viewOnce = viewOnce,
|
||||
distributionType = distributionType,
|
||||
storyType = storyType,
|
||||
parentStoryId = parentStoryId,
|
||||
isStoryReaction = isStoryReaction,
|
||||
giftBadge = giftBadge,
|
||||
isSecure = secure
|
||||
)
|
||||
|
||||
return insert(
|
||||
message = message,
|
||||
@@ -59,16 +50,16 @@ object MmsHelper {
|
||||
}
|
||||
|
||||
fun insert(
|
||||
message: OutgoingMediaMessage,
|
||||
message: OutgoingMessage,
|
||||
threadId: Long
|
||||
): Long {
|
||||
return SignalDatabase.mms.insertMessageOutbox(message, threadId, false, GroupReceiptDatabase.STATUS_UNKNOWN, null)
|
||||
return SignalDatabase.messages.insertMessageOutbox(message, threadId, false, GroupReceiptTable.STATUS_UNKNOWN, null)
|
||||
}
|
||||
|
||||
fun insert(
|
||||
message: IncomingMediaMessage,
|
||||
threadId: Long
|
||||
): Optional<MessageDatabase.InsertResult> {
|
||||
return SignalDatabase.mms.insertSecureDecryptedMessageInbox(message, threadId)
|
||||
): Optional<MessageTable.InsertResult> {
|
||||
return SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, threadId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ import java.util.concurrent.TimeUnit
|
||||
|
||||
@Suppress("ClassName")
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MmsDatabaseTest_stories {
|
||||
class MmsTableTest_stories {
|
||||
|
||||
private lateinit var mms: MmsDatabase
|
||||
private lateinit var mms: MessageTable
|
||||
|
||||
private val localAci = ACI.from(UUID.randomUUID())
|
||||
private val localPni = PNI.from(UUID.randomUUID())
|
||||
@@ -37,7 +37,7 @@ class MmsDatabaseTest_stories {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mms = SignalDatabase.mms
|
||||
mms = SignalDatabase.messages
|
||||
|
||||
mms.deleteAllThreads()
|
||||
|
||||
@@ -106,14 +106,14 @@ class MmsDatabaseTest_stories {
|
||||
-1L
|
||||
).get().messageId
|
||||
|
||||
val messageBeforeMark = SignalDatabase.mms.getMessageRecord(messageId)
|
||||
val messageBeforeMark = SignalDatabase.messages.getMessageRecord(messageId)
|
||||
assertFalse(messageBeforeMark.incomingStoryViewedAtTimestamp > 0)
|
||||
|
||||
// WHEN
|
||||
SignalDatabase.mms.setIncomingMessageViewed(messageId)
|
||||
SignalDatabase.messages.setIncomingMessageViewed(messageId)
|
||||
|
||||
// THEN
|
||||
val messageAfterMark = SignalDatabase.mms.getMessageRecord(messageId)
|
||||
val messageAfterMark = SignalDatabase.messages.getMessageRecord(messageId)
|
||||
assertTrue(messageAfterMark.incomingStoryViewedAtTimestamp > 0)
|
||||
}
|
||||
|
||||
@@ -136,12 +136,12 @@ class MmsDatabaseTest_stories {
|
||||
|
||||
val randomizedOrderedIds = messageIds.shuffled()
|
||||
randomizedOrderedIds.forEach {
|
||||
SignalDatabase.mms.setIncomingMessageViewed(it)
|
||||
SignalDatabase.messages.setIncomingMessageViewed(it)
|
||||
Thread.sleep(5)
|
||||
}
|
||||
|
||||
// WHEN
|
||||
val result = SignalDatabase.mms.getOrderedStoryRecipientsAndIds(false)
|
||||
val result = SignalDatabase.messages.getOrderedStoryRecipientsAndIds(false)
|
||||
val resultOrderedIds = result.map { it.messageId }
|
||||
|
||||
// THEN
|
||||
@@ -183,7 +183,7 @@ class MmsDatabaseTest_stories {
|
||||
val interspersedIds: List<Long> = (0 until 10).map {
|
||||
Thread.sleep(5)
|
||||
if (it % 2 == 0) {
|
||||
SignalDatabase.mms.setIncomingMessageViewed(viewedIds[it / 2])
|
||||
SignalDatabase.messages.setIncomingMessageViewed(viewedIds[it / 2])
|
||||
viewedIds[it / 2]
|
||||
} else {
|
||||
MmsHelper.insert(
|
||||
@@ -195,7 +195,7 @@ class MmsDatabaseTest_stories {
|
||||
}
|
||||
}
|
||||
|
||||
val result = SignalDatabase.mms.getOrderedStoryRecipientsAndIds(false)
|
||||
val result = SignalDatabase.messages.getOrderedStoryRecipientsAndIds(false)
|
||||
val resultOrderedIds = result.map { it.messageId }
|
||||
|
||||
assertEquals(unviewedIds.reversed() + interspersedIds.reversed(), resultOrderedIds)
|
||||
@@ -237,8 +237,7 @@ class MmsDatabaseTest_stories {
|
||||
MmsHelper.insert(
|
||||
recipient = myStory,
|
||||
sentTimeMillis = 200,
|
||||
storyType = StoryType.STORY_WITH_REPLIES,
|
||||
threadId = -1L
|
||||
storyType = StoryType.STORY_WITH_REPLIES
|
||||
)
|
||||
|
||||
// WHEN
|
||||
@@ -296,8 +295,7 @@ class MmsDatabaseTest_stories {
|
||||
val groupStoryId = MmsHelper.insert(
|
||||
recipient = myStory,
|
||||
sentTimeMillis = 200,
|
||||
storyType = StoryType.STORY_WITH_REPLIES,
|
||||
threadId = -1L
|
||||
storyType = StoryType.STORY_WITH_REPLIES
|
||||
)
|
||||
|
||||
MmsHelper.insert(
|
||||
@@ -355,7 +353,7 @@ class MmsDatabaseTest_stories {
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val oldestTimestamp = SignalDatabase.mms.getOldestStorySendTimestamp(false)
|
||||
val oldestTimestamp = SignalDatabase.messages.getOldestStorySendTimestamp(false)
|
||||
|
||||
// THEN
|
||||
assertNull(oldestTimestamp)
|
||||
@@ -374,7 +372,7 @@ class MmsDatabaseTest_stories {
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val oldestTimestamp = SignalDatabase.mms.getOldestStorySendTimestamp(true)
|
||||
val oldestTimestamp = SignalDatabase.messages.getOldestStorySendTimestamp(true)
|
||||
|
||||
// THEN
|
||||
assertEquals(expected, oldestTimestamp)
|
||||
@@ -13,7 +13,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class RecipientDatabaseTest {
|
||||
class RecipientTableTest {
|
||||
|
||||
@get:Rule
|
||||
val harness = SignalActivityRule()
|
||||
@@ -38,7 +38,7 @@ class RecipientDatabaseTest {
|
||||
val results: MutableList<RecipientId> = SignalDatabase.recipients.getSignalContacts(false)?.use {
|
||||
val ids = mutableListOf<RecipientId>()
|
||||
while (it.moveToNext()) {
|
||||
ids.add(RecipientId.from(CursorUtil.requireLong(it, RecipientDatabase.ID)))
|
||||
ids.add(RecipientId.from(CursorUtil.requireLong(it, RecipientTable.ID)))
|
||||
}
|
||||
|
||||
ids
|
||||
@@ -79,7 +79,7 @@ class RecipientDatabaseTest {
|
||||
val results: MutableList<RecipientId> = SignalDatabase.recipients.getNonGroupContacts(false)?.use {
|
||||
val ids = mutableListOf<RecipientId>()
|
||||
while (it.moveToNext()) {
|
||||
ids.add(RecipientId.from(CursorUtil.requireLong(it, RecipientDatabase.ID)))
|
||||
ids.add(RecipientId.from(CursorUtil.requireLong(it, RecipientTable.ID)))
|
||||
}
|
||||
|
||||
ids
|
||||
@@ -109,7 +109,7 @@ class RecipientDatabaseTest {
|
||||
val results: MutableList<RecipientId> = SignalDatabase.recipients.getSignalContacts(false)?.use {
|
||||
val ids = mutableListOf<RecipientId>()
|
||||
while (it.moveToNext()) {
|
||||
ids.add(RecipientId.from(CursorUtil.requireLong(it, RecipientDatabase.ID)))
|
||||
ids.add(RecipientId.from(CursorUtil.requireLong(it, RecipientTable.ID)))
|
||||
}
|
||||
|
||||
ids
|
||||
@@ -150,7 +150,7 @@ class RecipientDatabaseTest {
|
||||
val results: MutableList<RecipientId> = SignalDatabase.recipients.getNonGroupContacts(false)?.use {
|
||||
val ids = mutableListOf<RecipientId>()
|
||||
while (it.moveToNext()) {
|
||||
ids.add(RecipientId.from(CursorUtil.requireLong(it, RecipientDatabase.ID)))
|
||||
ids.add(RecipientId.from(CursorUtil.requireLong(it, RecipientTable.ID)))
|
||||
}
|
||||
|
||||
ids
|
||||
@@ -39,7 +39,7 @@ import java.util.Optional
|
||||
import java.util.UUID
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class RecipientDatabaseTest_getAndPossiblyMerge {
|
||||
class RecipientTableTest_getAndPossiblyMerge {
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
@@ -393,13 +393,13 @@ class RecipientDatabaseTest_getAndPossiblyMerge {
|
||||
val recipientIdE164: RecipientId = SignalDatabase.recipients.getOrInsertFromE164(E164_A)
|
||||
val recipientIdAciB: RecipientId = SignalDatabase.recipients.getOrInsertFromServiceId(ACI_B)
|
||||
|
||||
val smsId1: Long = SignalDatabase.sms.insertMessageInbox(smsMessage(sender = recipientIdAci, time = 0, body = "0")).get().messageId
|
||||
val smsId2: Long = SignalDatabase.sms.insertMessageInbox(smsMessage(sender = recipientIdE164, time = 1, body = "1")).get().messageId
|
||||
val smsId3: Long = SignalDatabase.sms.insertMessageInbox(smsMessage(sender = recipientIdAci, time = 2, body = "2")).get().messageId
|
||||
val smsId1: Long = SignalDatabase.messages.insertMessageInbox(smsMessage(sender = recipientIdAci, time = 0, body = "0")).get().messageId
|
||||
val smsId2: Long = SignalDatabase.messages.insertMessageInbox(smsMessage(sender = recipientIdE164, time = 1, body = "1")).get().messageId
|
||||
val smsId3: Long = SignalDatabase.messages.insertMessageInbox(smsMessage(sender = recipientIdAci, time = 2, body = "2")).get().messageId
|
||||
|
||||
val mmsId1: Long = SignalDatabase.mms.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdAci, time = 3, body = "3"), -1).get().messageId
|
||||
val mmsId2: Long = SignalDatabase.mms.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdE164, time = 4, body = "4"), -1).get().messageId
|
||||
val mmsId3: Long = SignalDatabase.mms.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdAci, time = 5, body = "5"), -1).get().messageId
|
||||
val mmsId1: Long = SignalDatabase.messages.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdAci, time = 3, body = "3"), -1).get().messageId
|
||||
val mmsId2: Long = SignalDatabase.messages.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdE164, time = 4, body = "4"), -1).get().messageId
|
||||
val mmsId3: Long = SignalDatabase.messages.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdAci, time = 5, body = "5"), -1).get().messageId
|
||||
|
||||
val threadIdAci: Long = SignalDatabase.threads.getThreadIdFor(recipientIdAci)!!
|
||||
val threadIdE164: Long = SignalDatabase.threads.getThreadIdFor(recipientIdE164)!!
|
||||
@@ -413,13 +413,13 @@ class RecipientDatabaseTest_getAndPossiblyMerge {
|
||||
val identityKeyAci: IdentityKey = identityKey(1)
|
||||
val identityKeyE164: IdentityKey = identityKey(2)
|
||||
|
||||
SignalDatabase.identities.saveIdentity(ACI_A.toString(), recipientIdAci, identityKeyAci, IdentityDatabase.VerifiedStatus.VERIFIED, false, 0, false)
|
||||
SignalDatabase.identities.saveIdentity(E164_A, recipientIdE164, identityKeyE164, IdentityDatabase.VerifiedStatus.VERIFIED, false, 0, false)
|
||||
SignalDatabase.identities.saveIdentity(ACI_A.toString(), recipientIdAci, identityKeyAci, IdentityTable.VerifiedStatus.VERIFIED, false, 0, false)
|
||||
SignalDatabase.identities.saveIdentity(E164_A, recipientIdE164, identityKeyE164, IdentityTable.VerifiedStatus.VERIFIED, false, 0, false)
|
||||
|
||||
SignalDatabase.sessions.store(ACI_SELF, SignalProtocolAddress(ACI_A.toString(), 1), SessionRecord())
|
||||
|
||||
SignalDatabase.reactions.addReaction(MessageId(smsId1, false), ReactionRecord("a", recipientIdAci, 1, 1))
|
||||
SignalDatabase.reactions.addReaction(MessageId(mmsId1, true), ReactionRecord("b", recipientIdE164, 1, 1))
|
||||
SignalDatabase.reactions.addReaction(MessageId(smsId1), ReactionRecord("a", recipientIdAci, 1, 1))
|
||||
SignalDatabase.reactions.addReaction(MessageId(mmsId1), ReactionRecord("b", recipientIdE164, 1, 1))
|
||||
|
||||
val profile1: NotificationProfile = notificationProfile(name = "Test")
|
||||
val profile2: NotificationProfile = notificationProfile(name = "Test2")
|
||||
@@ -450,9 +450,9 @@ class RecipientDatabaseTest_getAndPossiblyMerge {
|
||||
Assert.assertNull(SignalDatabase.threads.getThreadRecord(threadIdE164))
|
||||
|
||||
// SMS validation
|
||||
val sms1: MessageRecord = SignalDatabase.sms.getMessageRecord(smsId1)!!
|
||||
val sms2: MessageRecord = SignalDatabase.sms.getMessageRecord(smsId2)!!
|
||||
val sms3: MessageRecord = SignalDatabase.sms.getMessageRecord(smsId3)!!
|
||||
val sms1: MessageRecord = SignalDatabase.messages.getMessageRecord(smsId1)!!
|
||||
val sms2: MessageRecord = SignalDatabase.messages.getMessageRecord(smsId2)!!
|
||||
val sms3: MessageRecord = SignalDatabase.messages.getMessageRecord(smsId3)!!
|
||||
|
||||
assertEquals(retrievedId, sms1.recipient.id)
|
||||
assertEquals(retrievedId, sms2.recipient.id)
|
||||
@@ -463,9 +463,9 @@ class RecipientDatabaseTest_getAndPossiblyMerge {
|
||||
assertEquals(retrievedThreadId, sms3.threadId)
|
||||
|
||||
// MMS validation
|
||||
val mms1: MessageRecord = SignalDatabase.mms.getMessageRecord(mmsId1)!!
|
||||
val mms2: MessageRecord = SignalDatabase.mms.getMessageRecord(mmsId2)!!
|
||||
val mms3: MessageRecord = SignalDatabase.mms.getMessageRecord(mmsId3)!!
|
||||
val mms1: MessageRecord = SignalDatabase.messages.getMessageRecord(mmsId1)!!
|
||||
val mms2: MessageRecord = SignalDatabase.messages.getMessageRecord(mmsId2)!!
|
||||
val mms3: MessageRecord = SignalDatabase.messages.getMessageRecord(mmsId3)!!
|
||||
|
||||
assertEquals(retrievedId, mms1.recipient.id)
|
||||
assertEquals(retrievedId, mms2.recipient.id)
|
||||
@@ -485,7 +485,7 @@ class RecipientDatabaseTest_getAndPossiblyMerge {
|
||||
assertEquals(retrievedThreadId, mention2.threadId)
|
||||
|
||||
// Group receipt validation
|
||||
val groupReceipts: List<GroupReceiptDatabase.GroupReceiptInfo> = SignalDatabase.groupReceipts.getGroupReceiptInfo(mmsId1)
|
||||
val groupReceipts: List<GroupReceiptTable.GroupReceiptInfo> = SignalDatabase.groupReceipts.getGroupReceiptInfo(mmsId1)
|
||||
assertEquals(retrievedId, groupReceipts[0].recipientId)
|
||||
assertEquals(retrievedId, groupReceipts[1].recipientId)
|
||||
|
||||
@@ -497,8 +497,8 @@ class RecipientDatabaseTest_getAndPossiblyMerge {
|
||||
Assert.assertNotNull(SignalDatabase.sessions.load(ACI_SELF, SignalProtocolAddress(ACI_A.toString(), 1)))
|
||||
|
||||
// Reaction validation
|
||||
val reactionsSms: List<ReactionRecord> = SignalDatabase.reactions.getReactions(MessageId(smsId1, false))
|
||||
val reactionsMms: List<ReactionRecord> = SignalDatabase.reactions.getReactions(MessageId(mmsId1, true))
|
||||
val reactionsSms: List<ReactionRecord> = SignalDatabase.reactions.getReactions(MessageId(smsId1))
|
||||
val reactionsMms: List<ReactionRecord> = SignalDatabase.reactions.getReactions(MessageId(mmsId1))
|
||||
|
||||
assertEquals(1, reactionsSms.size)
|
||||
assertEquals(ReactionRecord("a", recipientIdAci, 1, 1), reactionsSms[0])
|
||||
@@ -539,11 +539,11 @@ class RecipientDatabaseTest_getAndPossiblyMerge {
|
||||
}
|
||||
|
||||
private fun getMention(messageId: Long): MentionModel {
|
||||
SignalDatabase.rawDatabase.rawQuery("SELECT * FROM ${MentionDatabase.TABLE_NAME} WHERE ${MentionDatabase.MESSAGE_ID} = $messageId").use { cursor ->
|
||||
SignalDatabase.rawDatabase.rawQuery("SELECT * FROM ${MentionTable.TABLE_NAME} WHERE ${MentionTable.MESSAGE_ID} = $messageId").use { cursor ->
|
||||
cursor.moveToFirst()
|
||||
return MentionModel(
|
||||
recipientId = RecipientId.from(CursorUtil.requireLong(cursor, MentionDatabase.RECIPIENT_ID)),
|
||||
threadId = CursorUtil.requireLong(cursor, MentionDatabase.THREAD_ID)
|
||||
recipientId = RecipientId.from(CursorUtil.requireLong(cursor, MentionTable.RECIPIENT_ID)),
|
||||
threadId = CursorUtil.requireLong(cursor, MentionTable.THREAD_ID)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -624,7 +624,7 @@ class RecipientDatabaseTest_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.sms.insertMessageInbox(IncomingEncryptedMessage(IncomingTextMessage(id, 1, 0, 0, 0, "", Optional.empty(), 0, false, ""), ""))
|
||||
SignalDatabase.messages.insertMessageInbox(IncomingEncryptedMessage(IncomingTextMessage(id, 1, 0, 0, 0, "", Optional.empty(), 0, false, ""), ""))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,19 +660,19 @@ class RecipientDatabaseTest_getAndPossiblyMerge {
|
||||
fun expectDeleted(id: RecipientId) {
|
||||
SignalDatabase.rawDatabase
|
||||
.select("1")
|
||||
.from(RecipientDatabase.TABLE_NAME)
|
||||
.where("${RecipientDatabase.ID} = ?", id)
|
||||
.from(RecipientTable.TABLE_NAME)
|
||||
.where("${RecipientTable.ID} = ?", id)
|
||||
.run()
|
||||
.use { !it.moveToFirst() }
|
||||
}
|
||||
|
||||
fun expectChangeNumberEvent() {
|
||||
assertEquals(1, SignalDatabase.sms.getChangeNumberMessageCount(outputRecipientId))
|
||||
assertEquals(1, SignalDatabase.messages.getChangeNumberMessageCount(outputRecipientId))
|
||||
changeNumberExpected = true
|
||||
}
|
||||
|
||||
fun expectNoChangeNumberEvent() {
|
||||
assertEquals(0, SignalDatabase.sms.getChangeNumberMessageCount(outputRecipientId))
|
||||
assertEquals(0, SignalDatabase.messages.getChangeNumberMessageCount(outputRecipientId))
|
||||
changeNumberExpected = false
|
||||
}
|
||||
|
||||
@@ -681,13 +681,13 @@ class RecipientDatabaseTest_getAndPossiblyMerge {
|
||||
val pniString: String? = pni?.toString()
|
||||
|
||||
val id: Long = SignalDatabase.rawDatabase.insert(
|
||||
RecipientDatabase.TABLE_NAME,
|
||||
RecipientTable.TABLE_NAME,
|
||||
null,
|
||||
contentValuesOf(
|
||||
RecipientDatabase.PHONE to e164,
|
||||
RecipientDatabase.SERVICE_ID to serviceIdString,
|
||||
RecipientDatabase.PNI_COLUMN to pniString,
|
||||
RecipientDatabase.REGISTERED to RecipientDatabase.RegisteredState.REGISTERED.id
|
||||
RecipientTable.PHONE to e164,
|
||||
RecipientTable.SERVICE_ID to serviceIdString,
|
||||
RecipientTable.PNI_COLUMN to pniString,
|
||||
RecipientTable.REGISTERED to RecipientTable.RegisteredState.REGISTERED.id
|
||||
)
|
||||
)
|
||||
|
||||
@@ -18,13 +18,13 @@ import java.lang.IllegalStateException
|
||||
import java.util.UUID
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class RecipientDatabaseTest_processPnpTupleToChangeSet {
|
||||
class RecipientTableTest_processPnpTupleToChangeSet {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val databaseRule = SignalDatabaseRule(deleteAllThreadsOnEachRun = false)
|
||||
|
||||
private lateinit var db: RecipientDatabase
|
||||
private lateinit var db: RecipientTable
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
@@ -711,13 +711,13 @@ class RecipientDatabaseTest_processPnpTupleToChangeSet {
|
||||
|
||||
private fun insert(e164: String?, pni: PNI?, aci: ACI?): RecipientId {
|
||||
val id: Long = SignalDatabase.rawDatabase.insert(
|
||||
RecipientDatabase.TABLE_NAME,
|
||||
RecipientTable.TABLE_NAME,
|
||||
null,
|
||||
contentValuesOf(
|
||||
RecipientDatabase.PHONE to e164,
|
||||
RecipientDatabase.SERVICE_ID to (aci ?: pni)?.toString(),
|
||||
RecipientDatabase.PNI_COLUMN to pni?.toString(),
|
||||
RecipientDatabase.REGISTERED to RecipientDatabase.RegisteredState.REGISTERED.id
|
||||
RecipientTable.PHONE to e164,
|
||||
RecipientTable.SERVICE_ID to (aci ?: pni)?.toString(),
|
||||
RecipientTable.PNI_COLUMN to pni?.toString(),
|
||||
RecipientTable.REGISTERED to RecipientTable.RegisteredState.REGISTERED.id
|
||||
)
|
||||
)
|
||||
|
||||
@@ -726,12 +726,12 @@ class RecipientDatabaseTest_processPnpTupleToChangeSet {
|
||||
|
||||
private fun insertMockSessionFor(account: ServiceId, address: ServiceId) {
|
||||
SignalDatabase.rawDatabase.insert(
|
||||
SessionDatabase.TABLE_NAME, null,
|
||||
SessionTable.TABLE_NAME, null,
|
||||
contentValuesOf(
|
||||
SessionDatabase.ACCOUNT_ID to account.toString(),
|
||||
SessionDatabase.ADDRESS to address.toString(),
|
||||
SessionDatabase.DEVICE to 1,
|
||||
SessionDatabase.RECORD to Util.getSecretBytes(32)
|
||||
SessionTable.ACCOUNT_ID to account.toString(),
|
||||
SessionTable.ADDRESS to address.toString(),
|
||||
SessionTable.DEVICE to 1,
|
||||
SessionTable.RECORD to Util.getSecretBytes(32)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -762,7 +762,7 @@ class RecipientDatabaseTest_processPnpTupleToChangeSet {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that will call insert your recipients, call [RecipientDatabase.processPnpTupleToChangeSet] with your params,
|
||||
* Helper method that will call insert your recipients, call [RecipientTable.processPnpTupleToChangeSet] with your params,
|
||||
* and then verify your output matches what you expect.
|
||||
*
|
||||
* It results the inserted ID's and changeset for additional verification.
|
||||
@@ -31,8 +31,8 @@ import java.util.UUID
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
|
||||
|
||||
private lateinit var recipients: RecipientDatabase
|
||||
private lateinit var sms: SmsDatabase
|
||||
private lateinit var recipients: RecipientTable
|
||||
private lateinit var sms: MessageTable
|
||||
|
||||
private val localAci = ACI.from(UUID.randomUUID())
|
||||
private val localPni = PNI.from(UUID.randomUUID())
|
||||
@@ -45,7 +45,7 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
|
||||
@Before
|
||||
fun setUp() {
|
||||
recipients = SignalDatabase.recipients
|
||||
sms = SignalDatabase.sms
|
||||
sms = SignalDatabase.messages
|
||||
|
||||
SignalStore.account().setAci(localAci)
|
||||
SignalStore.account().setPni(localPni)
|
||||
@@ -163,7 +163,7 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
|
||||
*/
|
||||
@Test
|
||||
fun previousJoinRequestCollapse() {
|
||||
val latestMessage: MessageDatabase.InsertResult = sms.insertMessageInbox(
|
||||
val latestMessage: MessageTable.InsertResult = sms.insertMessageInbox(
|
||||
groupUpdateMessage(
|
||||
sender = alice,
|
||||
groupContext = groupContext(masterKey = masterKey) {
|
||||
@@ -197,7 +197,7 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
|
||||
fun previousJoinThenTextCollapse() {
|
||||
val secondLatestMessage = sms.insertMessageInbox(smsMessage(sender = alice, body = "What up")).get()
|
||||
|
||||
val latestMessage: MessageDatabase.InsertResult = sms.insertMessageInbox(
|
||||
val latestMessage: MessageTable.InsertResult = sms.insertMessageInbox(
|
||||
groupUpdateMessage(
|
||||
sender = alice,
|
||||
groupContext = groupContext(masterKey = masterKey) {
|
||||
@@ -231,7 +231,7 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
|
||||
*/
|
||||
@Test
|
||||
fun previousCollapseAndJoinRequestDoubleCollapse() {
|
||||
val secondLatestMessage: MessageDatabase.InsertResult = sms.insertMessageInbox(
|
||||
val secondLatestMessage: MessageTable.InsertResult = sms.insertMessageInbox(
|
||||
groupUpdateMessage(
|
||||
sender = alice,
|
||||
groupContext = groupContext(masterKey = masterKey) {
|
||||
@@ -243,7 +243,7 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
|
||||
)
|
||||
).get()
|
||||
|
||||
val latestMessage: MessageDatabase.InsertResult = sms.insertMessageInbox(
|
||||
val latestMessage: MessageTable.InsertResult = sms.insertMessageInbox(
|
||||
groupUpdateMessage(
|
||||
sender = alice,
|
||||
groupContext = groupContext(masterKey = masterKey) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -22,7 +23,7 @@ import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import java.util.UUID
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class StorySendsDatabaseTest {
|
||||
class StorySendTableTest {
|
||||
|
||||
private val distributionId1 = DistributionId.from(UUID.randomUUID())
|
||||
private val distributionId2 = DistributionId.from(UUID.randomUUID())
|
||||
@@ -45,7 +46,7 @@ class StorySendsDatabaseTest {
|
||||
private var messageId2: Long = 0
|
||||
private var messageId3: Long = 0
|
||||
|
||||
private lateinit var storySends: StorySendsDatabase
|
||||
private lateinit var storySends: StorySendTable
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
@@ -183,7 +184,7 @@ class StorySendsDatabaseTest {
|
||||
@Test
|
||||
fun getRemoteDeleteRecipients_overlapWithPreviousDeletes() {
|
||||
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
|
||||
SignalDatabase.mms.markAsRemoteDelete(messageId1)
|
||||
SignalDatabase.messages.markAsRemoteDelete(messageId1)
|
||||
|
||||
storySends.insert(messageId2, recipients6to15, 200, true, distributionId2)
|
||||
|
||||
@@ -280,19 +281,20 @@ class StorySendsDatabaseTest {
|
||||
fun givenTwoStoriesAndOneIsRemoteDeleted_whenIGetFullSentStorySyncManifestForStory2_thenIExpectNonNullResult() {
|
||||
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
|
||||
storySends.insert(messageId2, recipients1to10, 200, true, distributionId2)
|
||||
SignalDatabase.mms.markAsRemoteDelete(messageId1)
|
||||
SignalDatabase.messages.markAsRemoteDelete(messageId1)
|
||||
|
||||
val manifest = storySends.getFullSentStorySyncManifest(messageId2, 200)!!
|
||||
|
||||
assertNotNull(manifest)
|
||||
}
|
||||
|
||||
/*
|
||||
@Test
|
||||
fun givenTwoStoriesAndOneIsRemoteDeleted_whenIGetRecipientIdsForManifestUpdate_thenIExpectOnlyRecipientsWithStory2() {
|
||||
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
|
||||
storySends.insert(messageId1, recipients11to20, 200, false, distributionId1)
|
||||
storySends.insert(messageId2, recipients1to10, 200, true, distributionId2)
|
||||
SignalDatabase.mms.markAsRemoteDelete(messageId1)
|
||||
SignalDatabase.messages.markAsRemoteDelete(messageId1)
|
||||
|
||||
val recipientIds = storySends.getRecipientIdsForManifestUpdate(200, messageId1)
|
||||
|
||||
@@ -304,7 +306,7 @@ class StorySendsDatabaseTest {
|
||||
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
|
||||
storySends.insert(messageId2, recipients1to10, 200, true, distributionId2)
|
||||
storySends.insert(messageId2, recipients11to20, 200, true, distributionId2)
|
||||
SignalDatabase.mms.markAsRemoteDelete(messageId1)
|
||||
SignalDatabase.messages.markAsRemoteDelete(messageId1)
|
||||
val recipientIds = storySends.getRecipientIdsForManifestUpdate(200, messageId1)
|
||||
|
||||
val results = storySends.getSentStorySyncManifestForUpdate(200, recipientIds)
|
||||
@@ -317,14 +319,14 @@ class StorySendsDatabaseTest {
|
||||
fun givenTwoStoriesAndTheOneThatAllowedRepliesIsRemoteDeleted_whenIGetPartialSentStorySyncManifest_thenIExpectAllowRepliesToBeTrue() {
|
||||
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
|
||||
storySends.insert(messageId2, recipients1to10, 200, true, distributionId2)
|
||||
SignalDatabase.mms.markAsRemoteDelete(messageId2)
|
||||
SignalDatabase.messages.markAsRemoteDelete(messageId2)
|
||||
val recipientIds = storySends.getRecipientIdsForManifestUpdate(200, messageId1)
|
||||
|
||||
val results = storySends.getSentStorySyncManifestForUpdate(200, recipientIds)
|
||||
|
||||
assertTrue(results.entries.all { it.allowedToReply })
|
||||
}
|
||||
|
||||
*/
|
||||
@Test
|
||||
fun givenEmptyManifest_whenIApplyRemoteManifest_thenNothingChanges() {
|
||||
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
|
||||
@@ -354,8 +356,8 @@ class StorySendsDatabaseTest {
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAManifest_whenIApplyRemoteManifestWithoutOneList_thenIExpectMessageToBeMarkedRemoteDeleted() {
|
||||
@Test(expected = NoSuchMessageException::class)
|
||||
fun givenAManifest_whenIApplyRemoteManifestWithoutOneList_thenIExpectMessageToBeDeleted() {
|
||||
val messageId4 = MmsHelper.insert(
|
||||
recipient = distributionListRecipient1,
|
||||
storyType = StoryType.STORY_WITHOUT_REPLIES,
|
||||
@@ -375,7 +377,8 @@ class StorySendsDatabaseTest {
|
||||
|
||||
storySends.applySentStoryManifest(remote, 200)
|
||||
|
||||
assertTrue(SignalDatabase.mms.getMessageRecord(messageId5).isRemoteDelete)
|
||||
SignalDatabase.messages.getMessageRecord(messageId5)
|
||||
fail("Expected messageId5 to no longer exist.")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -399,7 +402,7 @@ class StorySendsDatabaseTest {
|
||||
|
||||
storySends.applySentStoryManifest(remote, 200)
|
||||
|
||||
assertFalse(SignalDatabase.mms.getMessageRecord(messageId4).isRemoteDelete)
|
||||
assertFalse(SignalDatabase.messages.getMessageRecord(messageId4).isRemoteDelete)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -6,13 +6,14 @@ import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.signal.core.util.CursorUtil
|
||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.testing.SignalDatabaseRule
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import java.util.UUID
|
||||
|
||||
@Suppress("ClassName")
|
||||
class ThreadDatabaseTest_pinned {
|
||||
class ThreadTableTest_pinned {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@@ -33,7 +34,7 @@ class ThreadDatabaseTest_pinned {
|
||||
SignalDatabase.threads.pinConversations(listOf(threadId))
|
||||
|
||||
// WHEN
|
||||
SignalDatabase.mms.deleteMessage(messageId)
|
||||
SignalDatabase.messages.deleteMessage(messageId)
|
||||
|
||||
// THEN
|
||||
val pinned = SignalDatabase.threads.getPinnedThreadIds()
|
||||
@@ -48,10 +49,10 @@ class ThreadDatabaseTest_pinned {
|
||||
SignalDatabase.threads.pinConversations(listOf(threadId))
|
||||
|
||||
// WHEN
|
||||
SignalDatabase.mms.deleteMessage(messageId)
|
||||
SignalDatabase.messages.deleteMessage(messageId)
|
||||
|
||||
// THEN
|
||||
val unarchivedCount = SignalDatabase.threads.getUnarchivedConversationListCount()
|
||||
val unarchivedCount = SignalDatabase.threads.getUnarchivedConversationListCount(ConversationFilter.OFF)
|
||||
assertEquals(1, unarchivedCount)
|
||||
}
|
||||
|
||||
@@ -63,12 +64,12 @@ class ThreadDatabaseTest_pinned {
|
||||
SignalDatabase.threads.pinConversations(listOf(threadId))
|
||||
|
||||
// WHEN
|
||||
SignalDatabase.mms.deleteMessage(messageId)
|
||||
SignalDatabase.messages.deleteMessage(messageId)
|
||||
|
||||
// THEN
|
||||
SignalDatabase.threads.getUnarchivedConversationList(true, 0, 1).use {
|
||||
SignalDatabase.threads.getUnarchivedConversationList(ConversationFilter.OFF, true, 0, 1).use {
|
||||
it.moveToFirst()
|
||||
assertEquals(threadId, CursorUtil.requireLong(it, ThreadDatabase.ID))
|
||||
assertEquals(threadId, CursorUtil.requireLong(it, ThreadTable.ID))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import java.util.UUID
|
||||
|
||||
@Suppress("ClassName")
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ThreadDatabaseTest_recents {
|
||||
class ThreadTableTest_recents {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@@ -40,7 +40,7 @@ class ThreadDatabaseTest_recents {
|
||||
val results: MutableList<RecipientId> = SignalDatabase.threads.getRecentConversationList(10, false, false, false, false, false, false).use { cursor ->
|
||||
val ids = mutableListOf<RecipientId>()
|
||||
while (cursor.moveToNext()) {
|
||||
ids.add(RecipientId.from(CursorUtil.requireLong(cursor, ThreadDatabase.RECIPIENT_ID)))
|
||||
ids.add(RecipientId.from(CursorUtil.requireLong(cursor, ThreadTable.RECIPIENT_ID)))
|
||||
}
|
||||
|
||||
ids
|
||||
@@ -11,7 +11,7 @@ object UriAttachmentBuilder {
|
||||
id: Long,
|
||||
uri: Uri = Uri.parse("content://$id"),
|
||||
contentType: String,
|
||||
transferState: Int = AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
|
||||
transferState: Int = AttachmentTable.TRANSFER_PROGRESS_PENDING,
|
||||
size: Long = 0L,
|
||||
fileName: String = "file$id",
|
||||
voiceNote: Boolean = false,
|
||||
@@ -22,7 +22,7 @@ object UriAttachmentBuilder {
|
||||
stickerLocator: StickerLocator? = null,
|
||||
blurHash: BlurHash? = null,
|
||||
audioHash: AudioHash? = null,
|
||||
transformProperties: AttachmentDatabase.TransformProperties? = null
|
||||
transformProperties: AttachmentTable.TransformProperties? = null
|
||||
): UriAttachment {
|
||||
return UriAttachment(
|
||||
uri,
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.signal.core.util.SqlUtil
|
||||
import org.thoughtcrime.securesms.database.DistributionListDatabase
|
||||
import org.thoughtcrime.securesms.database.DistributionListTables
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||
import org.thoughtcrime.securesms.testing.SignalDatabaseRule
|
||||
@@ -72,9 +72,9 @@ class MyStoryMigrationTest {
|
||||
|
||||
private fun setMyStoryDistributionId(serializedId: String) {
|
||||
SignalDatabase.rawDatabase.update(
|
||||
DistributionListDatabase.LIST_TABLE_NAME,
|
||||
DistributionListTables.LIST_TABLE_NAME,
|
||||
contentValuesOf(
|
||||
DistributionListDatabase.DISTRIBUTION_ID to serializedId
|
||||
DistributionListTables.DISTRIBUTION_ID to serializedId
|
||||
),
|
||||
"_id = ?",
|
||||
SqlUtil.buildArgs(DistributionListId.MY_STORY)
|
||||
@@ -83,7 +83,7 @@ class MyStoryMigrationTest {
|
||||
|
||||
private fun deleteMyStory() {
|
||||
SignalDatabase.rawDatabase.delete(
|
||||
DistributionListDatabase.LIST_TABLE_NAME,
|
||||
DistributionListTables.LIST_TABLE_NAME,
|
||||
"_id = ?",
|
||||
SqlUtil.buildArgs(DistributionListId.MY_STORY)
|
||||
)
|
||||
@@ -91,9 +91,9 @@ class MyStoryMigrationTest {
|
||||
|
||||
private fun assertValidMyStoryExists() {
|
||||
SignalDatabase.rawDatabase.query(
|
||||
DistributionListDatabase.LIST_TABLE_NAME,
|
||||
DistributionListTables.LIST_TABLE_NAME,
|
||||
SqlUtil.COUNT,
|
||||
"_id = ? AND ${DistributionListDatabase.DISTRIBUTION_ID} = ?",
|
||||
"_id = ? AND ${DistributionListTables.DISTRIBUTION_ID} = ?",
|
||||
SqlUtil.buildArgs(DistributionListId.MY_STORY, DistributionId.MY_STORY.toString()),
|
||||
null,
|
||||
null,
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||
import org.signal.storageservice.protos.groups.Member
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember
|
||||
import org.thoughtcrime.securesms.database.MessageDatabase
|
||||
import org.thoughtcrime.securesms.database.MessageTable
|
||||
import org.thoughtcrime.securesms.database.MmsHelper
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||
@@ -30,12 +30,12 @@ class MessageContentProcessor__handleStoryMessageTest : MessageContentProcessorT
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
SignalDatabase.mms.deleteAllThreads()
|
||||
SignalDatabase.messages.deleteAllThreads()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
SignalDatabase.mms.deleteAllThreads()
|
||||
SignalDatabase.messages.deleteAllThreads()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -72,16 +72,16 @@ class MessageContentProcessor__handleStoryMessageTest : MessageContentProcessorT
|
||||
|
||||
runTestWithContent(contentProto = storyContent)
|
||||
|
||||
val replyId = SignalDatabase.mmsSms.getConversation(senderThreadId, 0, 1).use {
|
||||
val replyId = SignalDatabase.messages.getConversation(senderThreadId, 0, 1).use {
|
||||
it.moveToFirst()
|
||||
it.requireLong(MessageDatabase.ID)
|
||||
it.requireLong(MessageTable.ID)
|
||||
}
|
||||
|
||||
val replyRecord = SignalDatabase.mms.getMessageRecord(replyId) as MediaMmsMessageRecord
|
||||
val replyRecord = SignalDatabase.messages.getMessageRecord(replyId) as MediaMmsMessageRecord
|
||||
assertEquals(ParentStoryId.DirectReply(storyMessageId).serialize(), replyRecord.parentStoryId!!.serialize())
|
||||
assertEquals(expectedBody, replyRecord.body)
|
||||
|
||||
SignalDatabase.mms.deleteAllThreads()
|
||||
SignalDatabase.messages.deleteAllThreads()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -137,19 +137,19 @@ class MessageContentProcessor__handleStoryMessageTest : MessageContentProcessorT
|
||||
|
||||
runTestWithContent(storyContent)
|
||||
|
||||
val replyId = SignalDatabase.mms.getStoryReplies(insertResult.get().messageId).use { cursor ->
|
||||
val replyId = SignalDatabase.messages.getStoryReplies(insertResult.get().messageId).use { cursor ->
|
||||
assertEquals(1, cursor.count)
|
||||
cursor.moveToFirst()
|
||||
cursor.requireLong(MessageDatabase.ID)
|
||||
cursor.requireLong(MessageTable.ID)
|
||||
}
|
||||
|
||||
val replyRecord = SignalDatabase.mms.getMessageRecord(replyId) as MediaMmsMessageRecord
|
||||
val replyRecord = SignalDatabase.messages.getMessageRecord(replyId) as MediaMmsMessageRecord
|
||||
assertEquals(ParentStoryId.GroupReply(insertResult.get().messageId).serialize(), replyRecord.parentStoryId?.serialize())
|
||||
assertEquals(threadForGroup, replyRecord.threadId)
|
||||
assertEquals(expectedBody, replyRecord.body)
|
||||
|
||||
SignalDatabase.mms.deleteGroupStoryReplies(insertResult.get().messageId)
|
||||
SignalDatabase.mms.deleteAllThreads()
|
||||
SignalDatabase.messages.deleteGroupStoryReplies(insertResult.get().messageId)
|
||||
SignalDatabase.messages.deleteAllThreads()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,6 +176,6 @@ class MessageContentProcessor__handleStoryMessageTest : MessageContentProcessorT
|
||||
private fun runTestWithContent(contentProto: SignalServiceContentProto) {
|
||||
val content = SignalServiceContent.createFromProto(contentProto)
|
||||
val testSubject = createNormalContentTestSubject()
|
||||
testSubject.doProcess(content = content)
|
||||
testSubject.doProcess(content = content!!)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,11 @@ class MessageContentProcessor__handleTextMessageTest : MessageContentProcessorTe
|
||||
val content = SignalServiceContent.createFromProto(contentProto)
|
||||
|
||||
// WHEN
|
||||
testSubject.doProcess(content = content)
|
||||
testSubject.doProcess(content = content!!)
|
||||
|
||||
// THEN
|
||||
val record = SignalDatabase.sms.getMessageRecord(1)
|
||||
val threadSize = SignalDatabase.mmsSms.getConversationCount(record.threadId)
|
||||
val record = SignalDatabase.messages.getMessageRecord(1)
|
||||
val threadSize = SignalDatabase.messages.getMessageCountForThread(record.threadId)
|
||||
assertEquals(1, threadSize)
|
||||
|
||||
assertTrue(record.isSecure)
|
||||
|
||||
@@ -73,6 +73,7 @@ class UsernameEditFragmentTest {
|
||||
onView(withContentDescription(R.string.load_more_header__loading)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
|
||||
}
|
||||
|
||||
@Ignore("Flakey espresso test.")
|
||||
@Test
|
||||
fun testUsernameCreationOutsideOfRegistration() {
|
||||
val scenario = createScenario()
|
||||
|
||||
@@ -33,7 +33,7 @@ class SafetyNumberBottomSheetRepositoryTest {
|
||||
|
||||
testScheduler.triggerActions()
|
||||
|
||||
result.assertValueAt(1) { map ->
|
||||
result.assertValueAt(0) { map ->
|
||||
assertMatch(map, mapOf(SafetyNumberBucket.ContactsBucket to harness.others))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ object MockProvider {
|
||||
}
|
||||
|
||||
kbsRepository.stub {
|
||||
on { getToken(any() as String) } doReturn Single.just(ServiceResponse.forResult(tokenData, 200, ""))
|
||||
on { getToken(any() as? String) } doReturn Single.just(ServiceResponse.forResult(tokenData, 200, ""))
|
||||
}
|
||||
|
||||
val session: KeyBackupService.RestoreSession = object : KeyBackupService.RestoreSession {
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.signal.libsignal.protocol.SignalProtocolAddress
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase
|
||||
import org.thoughtcrime.securesms.database.IdentityTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
|
||||
@@ -108,7 +108,7 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource()
|
||||
val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i)))
|
||||
SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i"))
|
||||
SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew())
|
||||
SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true, true, true, true, true, true, true))
|
||||
SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true, true, true, true, true, true, true, true))
|
||||
SignalDatabase.recipients.setProfileSharing(recipientId, true)
|
||||
SignalDatabase.recipients.markRegistered(recipientId, aci)
|
||||
ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), IdentityKeyUtil.generateIdentityKeyPair().publicKey)
|
||||
@@ -130,7 +130,7 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource()
|
||||
return ApplicationDependencies.getProtocolStore().aci().identities().getIdentity(SignalProtocolAddress(recipient.requireServiceId().toString(), 0))
|
||||
}
|
||||
|
||||
fun setVerified(recipient: Recipient, status: IdentityDatabase.VerifiedStatus) {
|
||||
ApplicationDependencies.getProtocolStore().aci().identities().setVerified(recipient.id, getIdentity(recipient), IdentityDatabase.VerifiedStatus.VERIFIED)
|
||||
fun setVerified(recipient: Recipient, status: IdentityTable.VerifiedStatus) {
|
||||
ApplicationDependencies.getProtocolStore().aci().identities().setVerified(recipient.id, getIdentity(recipient), IdentityTable.VerifiedStatus.VERIFIED)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class SignalDatabaseRule(
|
||||
|
||||
private fun deleteAllThreads() {
|
||||
if (deleteAllThreadsOnEachRun) {
|
||||
SignalDatabase.mms.deleteAllThreads()
|
||||
SignalDatabase.messages.deleteAllThreads()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,11 +332,6 @@
|
||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize"/>
|
||||
|
||||
<activity android:name=".DatabaseMigrationActivity"
|
||||
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".migrations.ApplicationMigrationActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:launchMode="singleTask"
|
||||
@@ -702,7 +697,6 @@
|
||||
|
||||
<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:name=".service.ApplicationMigrationService"/>
|
||||
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
|
||||
<service android:enabled="true" android:name=".messages.IncomingMessageObserver$ForegroundService"/>
|
||||
<service android:name=".service.webrtc.AndroidCallConnectionService"
|
||||
@@ -888,7 +882,7 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="org.thoughtcrime.securesms.jobs.ForegroundUtil$Receiver" android:exported="false" />
|
||||
<receiver android:name="org.thoughtcrime.securesms.jobs.ForegroundServiceUtil$Receiver" android:exported="false" />
|
||||
|
||||
<receiver android:name=".service.PersistentConnectionBootListener" android:exported="false">
|
||||
<intent-filter>
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import org.thoughtcrime.securesms.stories.Stories;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.whispersystems.signalservice.api.account.AccountAttributes;
|
||||
|
||||
public final class AppCapabilities {
|
||||
|
||||
private AppCapabilities() {
|
||||
}
|
||||
|
||||
private static final boolean UUID_CAPABLE = false;
|
||||
private static final boolean GV2_CAPABLE = true;
|
||||
private static final boolean GV1_MIGRATION = true;
|
||||
private static final boolean ANNOUNCEMENT_GROUPS = true;
|
||||
private static final boolean SENDER_KEY = true;
|
||||
private static final boolean CHANGE_NUMBER = true;
|
||||
private static final boolean STORIES = true;
|
||||
|
||||
/**
|
||||
* @param storageCapable Whether or not the user can use storage service. This is another way of
|
||||
* asking if the user has set a Signal PIN or not.
|
||||
*/
|
||||
public static AccountAttributes.Capabilities getCapabilities(boolean storageCapable) {
|
||||
return new AccountAttributes.Capabilities(UUID_CAPABLE, GV2_CAPABLE, storageCapable, GV1_MIGRATION, SENDER_KEY, ANNOUNCEMENT_GROUPS, CHANGE_NUMBER, STORIES, FeatureFlags.giftBadgeReceiveSupport(), FeatureFlags.phoneNumberPrivacy());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.thoughtcrime.securesms
|
||||
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.whispersystems.signalservice.api.account.AccountAttributes
|
||||
|
||||
object AppCapabilities {
|
||||
/**
|
||||
* @param storageCapable Whether or not the user can use storage service. This is another way of
|
||||
* asking if the user has set a Signal PIN or not.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getCapabilities(storageCapable: Boolean): AccountAttributes.Capabilities {
|
||||
return AccountAttributes.Capabilities(
|
||||
isUuid = false,
|
||||
isGv2 = true,
|
||||
isStorage = storageCapable,
|
||||
isGv1Migration = true,
|
||||
isSenderKey = true,
|
||||
isAnnouncementGroup = true,
|
||||
isChangeNumber = true,
|
||||
isStories = true,
|
||||
isGiftBadges = true,
|
||||
isPnp = FeatureFlags.phoneNumberPrivacy(),
|
||||
paymentActivation = true
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
@@ -35,6 +36,8 @@ import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.AndroidLogger;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.core.util.tracing.Tracer;
|
||||
import org.signal.donations.GooglePayApi;
|
||||
import org.signal.donations.StripeApi;
|
||||
import org.signal.glide.SignalGlideCodecs;
|
||||
import org.signal.libsignal.protocol.logging.SignalProtocolLoggerProvider;
|
||||
import org.signal.ringrtc.CallManager;
|
||||
@@ -89,6 +92,7 @@ import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.util.AppForegroundObserver;
|
||||
import org.thoughtcrime.securesms.util.AppStartup;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.Environment;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
|
||||
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
|
||||
@@ -102,6 +106,8 @@ import java.net.SocketTimeoutException;
|
||||
import java.security.Security;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.reactivex.rxjava3.core.CompletableObserver;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
|
||||
import io.reactivex.rxjava3.exceptions.UndeliverableException;
|
||||
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
|
||||
@@ -155,7 +161,6 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
.addBlocking("rx-init", this::initializeRx)
|
||||
.addBlocking("event-bus", () -> EventBus.builder().logNoSubscriberMessages(false).installDefaultEventBus())
|
||||
.addBlocking("app-dependencies", this::initializeAppDependencies)
|
||||
.addBlocking("notification-channels", () -> NotificationChannels.create(this))
|
||||
.addBlocking("first-launch", this::initializeFirstEverAppLaunch)
|
||||
.addBlocking("app-migrations", this::initializeApplicationMigrations)
|
||||
.addBlocking("ring-rtc", this::initializeRingRtc)
|
||||
@@ -177,6 +182,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
.addBlocking("blob-provider", this::initializeBlobProvider)
|
||||
.addBlocking("feature-flags", FeatureFlags::init)
|
||||
.addBlocking("glide", () -> SignalGlideModule.setRegisterGlideComponents(new SignalGlideComponents()))
|
||||
.addNonBlocking(this::checkIsGooglePayReady)
|
||||
.addNonBlocking(this::cleanAvatarStorage)
|
||||
.addNonBlocking(this::initializeRevealableMessageManager)
|
||||
.addNonBlocking(this::initializePendingRetryReceiptManager)
|
||||
@@ -460,6 +466,18 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
AvatarPickerStorage.cleanOrphans(this);
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private void checkIsGooglePayReady() {
|
||||
GooglePayApi.queryIsReadyToPay(
|
||||
this,
|
||||
new StripeApi.Gateway(Environment.Donations.getStripeConfiguration()),
|
||||
Environment.Donations.getGooglePayConfiguration()
|
||||
).subscribe(
|
||||
/* onComplete = */ () -> SignalStore.donationsValues().setGooglePayReady(true),
|
||||
/* onError = */ t -> SignalStore.donationsValues().setGooglePayReady(false)
|
||||
);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void initializeCleanup() {
|
||||
int deleted = SignalDatabase.attachments().deleteAbandonedPreuploadedAttachments();
|
||||
|
||||
@@ -667,7 +667,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
SelectedContact selectedContact = contact.isUsernameType() ? SelectedContact.forUsername(contact.getRecipientId().orElse(null), contact.getNumber())
|
||||
: SelectedContact.forPhone(contact.getRecipientId().orElse(null), contact.getNumber());
|
||||
|
||||
if (!canSelectSelf && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId(requireContext()))) {
|
||||
if (!canSelectSelf && !selectedContact.hasUsername() && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId(requireContext()))) {
|
||||
Toast.makeText(requireContext(), R.string.ContactSelectionListFragment_you_do_not_need_to_add_yourself_to_the_group, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Parcelable;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.database.SmsMigrator.ProgressDescription;
|
||||
import org.thoughtcrime.securesms.service.ApplicationMigrationService;
|
||||
import org.thoughtcrime.securesms.service.ApplicationMigrationService.ImportState;
|
||||
|
||||
public class DatabaseMigrationActivity extends PassphraseRequiredActivity {
|
||||
|
||||
private final ImportServiceConnection serviceConnection = new ImportServiceConnection();
|
||||
private final ImportStateHandler importStateHandler = new ImportStateHandler();
|
||||
private final BroadcastReceiver completedReceiver = new NullReceiver();
|
||||
|
||||
private LinearLayout promptLayout;
|
||||
private LinearLayout progressLayout;
|
||||
private Button skipButton;
|
||||
private Button importButton;
|
||||
private ProgressBar progress;
|
||||
private TextView progressLabel;
|
||||
|
||||
private ApplicationMigrationService importService;
|
||||
private boolean isVisible = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle, boolean ready) {
|
||||
setContentView(R.layout.database_migration_activity);
|
||||
|
||||
initializeResources();
|
||||
initializeServiceBinding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
isVisible = true;
|
||||
registerForCompletedNotification();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
isVisible = false;
|
||||
unregisterForCompletedNotification();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
shutdownServiceBinding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
|
||||
}
|
||||
|
||||
private void initializeServiceBinding() {
|
||||
Intent intent = new Intent(this, ApplicationMigrationService.class);
|
||||
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
this.promptLayout = (LinearLayout)findViewById(R.id.prompt_layout);
|
||||
this.progressLayout = (LinearLayout)findViewById(R.id.progress_layout);
|
||||
this.skipButton = (Button) findViewById(R.id.skip_button);
|
||||
this.importButton = (Button) findViewById(R.id.import_button);
|
||||
this.progress = (ProgressBar) findViewById(R.id.import_progress);
|
||||
this.progressLabel = (TextView) findViewById(R.id.import_status);
|
||||
|
||||
this.progressLayout.setVisibility(View.GONE);
|
||||
this.promptLayout.setVisibility(View.GONE);
|
||||
|
||||
this.importButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(DatabaseMigrationActivity.this, ApplicationMigrationService.class);
|
||||
intent.setAction(ApplicationMigrationService.MIGRATE_DATABASE);
|
||||
intent.putExtra("master_secret", (Parcelable)getIntent().getParcelableExtra("master_secret"));
|
||||
startService(intent);
|
||||
|
||||
promptLayout.setVisibility(View.GONE);
|
||||
progressLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
this.skipButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
ApplicationMigrationService.setDatabaseImported(DatabaseMigrationActivity.this);
|
||||
handleImportComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void registerForCompletedNotification() {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ApplicationMigrationService.COMPLETED_ACTION);
|
||||
filter.setPriority(1000);
|
||||
|
||||
registerReceiver(completedReceiver, filter);
|
||||
}
|
||||
|
||||
private void unregisterForCompletedNotification() {
|
||||
unregisterReceiver(completedReceiver);
|
||||
}
|
||||
|
||||
private void shutdownServiceBinding() {
|
||||
unbindService(serviceConnection);
|
||||
}
|
||||
|
||||
private void handleStateIdle() {
|
||||
this.promptLayout.setVisibility(View.VISIBLE);
|
||||
this.progressLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void handleStateProgress(ProgressDescription update) {
|
||||
this.promptLayout.setVisibility(View.GONE);
|
||||
this.progressLayout.setVisibility(View.VISIBLE);
|
||||
this.progressLabel.setText(update.primaryComplete + "/" + update.primaryTotal);
|
||||
|
||||
double max = this.progress.getMax();
|
||||
double primaryTotal = update.primaryTotal;
|
||||
double primaryComplete = update.primaryComplete;
|
||||
double secondaryTotal = update.secondaryTotal;
|
||||
double secondaryComplete = update.secondaryComplete;
|
||||
|
||||
this.progress.setProgress((int)Math.round((primaryComplete / primaryTotal) * max));
|
||||
this.progress.setSecondaryProgress((int)Math.round((secondaryComplete / secondaryTotal) * max));
|
||||
}
|
||||
|
||||
private void handleImportComplete() {
|
||||
if (isVisible) {
|
||||
if (getIntent().hasExtra("next_intent")) {
|
||||
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
|
||||
} else {
|
||||
// TODO [greyson] Navigation
|
||||
startActivity(MainActivity.clearTop(this));
|
||||
}
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
private class ImportStateHandler extends Handler {
|
||||
|
||||
public ImportStateHandler() {
|
||||
super(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
switch (message.what) {
|
||||
case ImportState.STATE_IDLE: handleStateIdle(); break;
|
||||
case ImportState.STATE_MIGRATING_IN_PROGRESS: handleStateProgress((ProgressDescription)message.obj); break;
|
||||
case ImportState.STATE_MIGRATING_COMPLETE: handleImportComplete(); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ImportServiceConnection implements ServiceConnection {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||
importService = ((ApplicationMigrationService.ApplicationMigrationBinder)service).getService();
|
||||
importService.setImportStateHandler(importStateHandler);
|
||||
|
||||
ImportState state = importService.getState();
|
||||
importStateHandler.obtainMessage(state.state, state.progress).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
importService.setImportStateHandler(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static class NullReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
abortBroadcast();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -28,10 +28,10 @@ import org.thoughtcrime.securesms.contacts.SelectedContact;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarInviteTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@@ -254,7 +254,7 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
||||
Recipient recipient = Recipient.resolved(recipientId);
|
||||
int subscriptionId = recipient.getDefaultSubscriptionId().orElse(-1);
|
||||
|
||||
MessageSender.send(context, new OutgoingTextMessage(recipient, message, subscriptionId), -1L, true, null, null);
|
||||
MessageSender.send(context, OutgoingMessage.sms(recipient, message, subscriptionId), -1L, true, null, null);
|
||||
|
||||
if (recipient.getContactUri() != null) {
|
||||
SignalDatabase.recipients().setHasSentInvite(recipient.getId());
|
||||
|
||||
@@ -602,7 +602,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
}
|
||||
|
||||
public void handleGroupMemberCountChange(int count) {
|
||||
boolean canRing = count <= FeatureFlags.maxGroupCallRingSize() && FeatureFlags.groupCallRinging();
|
||||
boolean canRing = count <= FeatureFlags.maxGroupCallRingSize();
|
||||
callScreen.enableRingGroup(canRing);
|
||||
ApplicationDependencies.getSignalCallManager().setRingGroup(canRing);
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.audio.AudioHash;
|
||||
import org.thoughtcrime.securesms.blurhash.BlurHash;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable.TransformProperties;
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||
|
||||
public abstract class Attachment {
|
||||
@@ -118,13 +118,13 @@ public abstract class Attachment {
|
||||
}
|
||||
|
||||
public boolean isInProgress() {
|
||||
return transferState != AttachmentDatabase.TRANSFER_PROGRESS_DONE &&
|
||||
transferState != AttachmentDatabase.TRANSFER_PROGRESS_FAILED &&
|
||||
transferState != AttachmentDatabase.TRANSFER_PROGRESS_PERMANENT_FAILURE;
|
||||
return transferState != AttachmentTable.TRANSFER_PROGRESS_DONE &&
|
||||
transferState != AttachmentTable.TRANSFER_PROGRESS_FAILED &&
|
||||
transferState != AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE;
|
||||
}
|
||||
|
||||
public boolean isPermanentlyFailed() {
|
||||
return transferState == AttachmentDatabase.TRANSFER_PROGRESS_PERMANENT_FAILURE;
|
||||
return transferState == AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
|
||||
@@ -6,7 +6,7 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.audio.AudioHash;
|
||||
import org.thoughtcrime.securesms.blurhash.BlurHash;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable.TransformProperties;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import android.net.Uri;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.database.MessageTable;
|
||||
|
||||
public class MmsNotificationAttachment extends Attachment {
|
||||
|
||||
@@ -26,14 +26,14 @@ public class MmsNotificationAttachment extends Attachment {
|
||||
}
|
||||
|
||||
private static int getTransferStateFromStatus(int status) {
|
||||
if (status == MmsDatabase.Status.DOWNLOAD_INITIALIZED ||
|
||||
status == MmsDatabase.Status.DOWNLOAD_NO_CONNECTIVITY)
|
||||
if (status == MessageTable.MmsStatus.DOWNLOAD_INITIALIZED ||
|
||||
status == MessageTable.MmsStatus.DOWNLOAD_NO_CONNECTIVITY)
|
||||
{
|
||||
return AttachmentDatabase.TRANSFER_PROGRESS_PENDING;
|
||||
} else if (status == MmsDatabase.Status.DOWNLOAD_CONNECTING) {
|
||||
return AttachmentDatabase.TRANSFER_PROGRESS_STARTED;
|
||||
return AttachmentTable.TRANSFER_PROGRESS_PENDING;
|
||||
} else if (status == MessageTable.MmsStatus.DOWNLOAD_CONNECTING) {
|
||||
return AttachmentTable.TRANSFER_PROGRESS_STARTED;
|
||||
} else {
|
||||
return AttachmentDatabase.TRANSFER_PROGRESS_FAILED;
|
||||
return AttachmentTable.TRANSFER_PROGRESS_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.blurhash.BlurHash;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
@@ -68,7 +68,7 @@ public class PointerAttachment extends Attachment {
|
||||
return results;
|
||||
}
|
||||
|
||||
public static List<Attachment> forPointers(List<SignalServiceDataMessage.Quote.QuotedAttachment> pointers) {
|
||||
public static List<Attachment> forPointers(@Nullable List<SignalServiceDataMessage.Quote.QuotedAttachment> pointers) {
|
||||
List<Attachment> results = new LinkedList<>();
|
||||
|
||||
if (pointers != null) {
|
||||
@@ -102,7 +102,7 @@ public class PointerAttachment extends Attachment {
|
||||
}
|
||||
|
||||
return Optional.of(new PointerAttachment(pointer.get().getContentType(),
|
||||
AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
|
||||
AttachmentTable.TRANSFER_PROGRESS_PENDING,
|
||||
pointer.get().asPointer().getSize().orElse(0),
|
||||
pointer.get().asPointer().getFileName().orElse(null),
|
||||
pointer.get().asPointer().getCdnNumber(),
|
||||
@@ -126,7 +126,7 @@ public class PointerAttachment extends Attachment {
|
||||
SignalServiceAttachment thumbnail = pointer.getThumbnail();
|
||||
|
||||
return Optional.of(new PointerAttachment(pointer.getContentType(),
|
||||
AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
|
||||
AttachmentTable.TRANSFER_PROGRESS_PENDING,
|
||||
thumbnail != null ? thumbnail.asPointer().getSize().orElse(0) : 0,
|
||||
pointer.getFileName(),
|
||||
thumbnail != null ? thumbnail.asPointer().getCdnNumber() : 0,
|
||||
|
||||
@@ -5,7 +5,7 @@ import android.net.Uri;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
|
||||
/**
|
||||
* An attachment that represents where an attachment used to be. Useful when you need to know that
|
||||
@@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
public class TombstoneAttachment extends Attachment {
|
||||
|
||||
public TombstoneAttachment(@NonNull String contentType, boolean quote) {
|
||||
super(contentType, AttachmentDatabase.TRANSFER_PROGRESS_DONE, 0, null, 0, 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
|
||||
|
||||
@@ -7,9 +7,11 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.audio.AudioHash;
|
||||
import org.thoughtcrime.securesms.blurhash.BlurHash;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable.TransformProperties;
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class UriAttachment extends Attachment {
|
||||
|
||||
private final @NonNull Uri dataUri;
|
||||
@@ -51,7 +53,7 @@ public class UriAttachment extends Attachment {
|
||||
@Nullable TransformProperties 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 = dataUri;
|
||||
this.dataUri = Objects.requireNonNull(dataUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -51,6 +51,7 @@ private class AudioRecorderFocusManager26(context: Context, changeListener: OnAu
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private class AudioRecorderFocusManagerLegacy(context: Context, val changeListener: OnAudioFocusChangeListener) : AudioRecorderFocusManager(context) {
|
||||
override fun requestAudioFocus(): Int {
|
||||
return audioManager.requestAudioFocus(changeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.AudioWaveFormData;
|
||||
import org.thoughtcrime.securesms.media.DecryptableUriMediaInput;
|
||||
@@ -100,7 +100,7 @@ public final class AudioWaveForm {
|
||||
|
||||
if (attachment instanceof DatabaseAttachment) {
|
||||
try {
|
||||
AttachmentDatabase attachmentDatabase = SignalDatabase.attachments();
|
||||
AttachmentTable attachmentDatabase = SignalDatabase.attachments();
|
||||
DatabaseAttachment dbAttachment = (DatabaseAttachment) attachment;
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ class TextAvatarDrawable(
|
||||
setBounds(0, 0, size, size)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun draw(canvas: Canvas) {
|
||||
val width = bounds.width()
|
||||
val textSize = Avatars.getTextSizeForLength(context, avatar.text, width * 0.8f, width * 0.45f)
|
||||
|
||||
@@ -1,30 +1,27 @@
|
||||
package org.thoughtcrime.securesms.backup
|
||||
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptTable
|
||||
import org.thoughtcrime.securesms.database.MessageTable
|
||||
|
||||
/**
|
||||
* Queries used by backup exporter to estimate total counts for various complicated tables.
|
||||
*/
|
||||
object BackupCountQueries {
|
||||
|
||||
const val mmsCount: String = "SELECT COUNT(*) FROM ${MmsDatabase.TABLE_NAME} WHERE ${MmsDatabase.EXPIRES_IN} <= 0 AND ${MmsDatabase.VIEW_ONCE} <= 0"
|
||||
|
||||
const val smsCount: String = "SELECT COUNT(*) FROM ${SmsDatabase.TABLE_NAME} WHERE ${SmsDatabase.EXPIRES_IN} <= 0"
|
||||
const val mmsCount: String = "SELECT COUNT(*) FROM ${MessageTable.TABLE_NAME} WHERE ${MessageTable.EXPIRES_IN} <= 0 AND ${MessageTable.VIEW_ONCE} <= 0"
|
||||
|
||||
@get:JvmStatic
|
||||
val groupReceiptCount: String = """
|
||||
SELECT COUNT(*) FROM ${GroupReceiptDatabase.TABLE_NAME}
|
||||
INNER JOIN ${MmsDatabase.TABLE_NAME} ON ${GroupReceiptDatabase.TABLE_NAME}.${GroupReceiptDatabase.MMS_ID} = ${MmsDatabase.TABLE_NAME}.${MmsDatabase.ID}
|
||||
WHERE ${MmsDatabase.TABLE_NAME}.${MmsDatabase.EXPIRES_IN} <= 0 AND ${MmsDatabase.TABLE_NAME}.${MmsDatabase.VIEW_ONCE} <= 0
|
||||
SELECT COUNT(*) FROM ${GroupReceiptTable.TABLE_NAME}
|
||||
INNER JOIN ${MessageTable.TABLE_NAME} ON ${GroupReceiptTable.TABLE_NAME}.${GroupReceiptTable.MMS_ID} = ${MessageTable.TABLE_NAME}.${MessageTable.ID}
|
||||
WHERE ${MessageTable.TABLE_NAME}.${MessageTable.EXPIRES_IN} <= 0 AND ${MessageTable.TABLE_NAME}.${MessageTable.VIEW_ONCE} <= 0
|
||||
""".trimIndent()
|
||||
|
||||
@get:JvmStatic
|
||||
val attachmentCount: String = """
|
||||
SELECT COUNT(*) FROM ${AttachmentDatabase.TABLE_NAME}
|
||||
INNER JOIN ${MmsDatabase.TABLE_NAME} ON ${AttachmentDatabase.TABLE_NAME}.${AttachmentDatabase.MMS_ID} = ${MmsDatabase.TABLE_NAME}.${MmsDatabase.ID}
|
||||
WHERE ${MmsDatabase.TABLE_NAME}.${MmsDatabase.EXPIRES_IN} <= 0 AND ${MmsDatabase.TABLE_NAME}.${MmsDatabase.VIEW_ONCE} <= 0
|
||||
SELECT COUNT(*) FROM ${AttachmentTable.TABLE_NAME}
|
||||
INNER JOIN ${MessageTable.TABLE_NAME} ON ${AttachmentTable.TABLE_NAME}.${AttachmentTable.MMS_ID} = ${MessageTable.TABLE_NAME}.${MessageTable.ID}
|
||||
WHERE ${MessageTable.TABLE_NAME}.${MessageTable.EXPIRES_IN} <= 0 AND ${MessageTable.TABLE_NAME}.${MessageTable.VIEW_ONCE} <= 0
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public enum BackupFileIOError {
|
||||
|
||||
public void postNotification(@NonNull Context context) {
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, -1, AppSettingsActivity.backups(context), PendingIntentFlags.mutable());
|
||||
Notification backupFailedNotification = new NotificationCompat.Builder(context, NotificationChannels.FAILURES)
|
||||
Notification backupFailedNotification = new NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES)
|
||||
.setSmallIcon(R.drawable.ic_signal_backup)
|
||||
.setContentTitle(context.getString(titleId))
|
||||
.setContentText(context.getString(messageId))
|
||||
|
||||
@@ -15,6 +15,7 @@ object BackupVerifier {
|
||||
private val TAG = Log.tag(BackupVerifier::class.java)
|
||||
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun verifyFile(cipherStream: InputStream, passphrase: String, expectedCount: Long): Boolean {
|
||||
val inputStream = BackupRecordInputStream(cipherStream, passphrase)
|
||||
|
||||
@@ -47,7 +48,7 @@ object BackupVerifier {
|
||||
try {
|
||||
inputStream.readAttachmentTo(NullOutputStream, attachment.length)
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Bad attachment: ${attachment.attachmentId}", e)
|
||||
Log.w(TAG, "Bad attachment id: ${attachment.attachmentId} len: ${attachment.length}", e)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -58,7 +59,7 @@ object BackupVerifier {
|
||||
try {
|
||||
inputStream.readAttachmentTo(NullOutputStream, sticker.length)
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Bad sticker: ${sticker.rowId}", e)
|
||||
Log.w(TAG, "Bad sticker id: ${sticker.rowId} len: ${sticker.length}", e)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -68,7 +69,7 @@ object BackupVerifier {
|
||||
try {
|
||||
inputStream.readAttachmentTo(NullOutputStream, avatar.length)
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Bad sticker: ${avatar.recipientId}", e)
|
||||
Log.w(TAG, "Bad avatar id: ${avatar.recipientId} len: ${avatar.length}", e)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -29,23 +29,21 @@ import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.EmojiSearchDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.database.EmojiSearchTable;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptTable;
|
||||
import org.thoughtcrime.securesms.database.KeyValueDatabase;
|
||||
import org.thoughtcrime.securesms.database.MentionDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
|
||||
import org.thoughtcrime.securesms.database.PendingRetryReceiptDatabase;
|
||||
import org.thoughtcrime.securesms.database.ReactionDatabase;
|
||||
import org.thoughtcrime.securesms.database.SearchDatabase;
|
||||
import org.thoughtcrime.securesms.database.SenderKeyDatabase;
|
||||
import org.thoughtcrime.securesms.database.SenderKeySharedDatabase;
|
||||
import org.thoughtcrime.securesms.database.SessionDatabase;
|
||||
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
||||
import org.thoughtcrime.securesms.database.MentionTable;
|
||||
import org.thoughtcrime.securesms.database.MessageTable;
|
||||
import org.thoughtcrime.securesms.database.OneTimePreKeyTable;
|
||||
import org.thoughtcrime.securesms.database.PendingRetryReceiptTable;
|
||||
import org.thoughtcrime.securesms.database.ReactionTable;
|
||||
import org.thoughtcrime.securesms.database.SearchTable;
|
||||
import org.thoughtcrime.securesms.database.SenderKeyTable;
|
||||
import org.thoughtcrime.securesms.database.SenderKeySharedTable;
|
||||
import org.thoughtcrime.securesms.database.SessionTable;
|
||||
import org.thoughtcrime.securesms.database.SignedPreKeyTable;
|
||||
import org.thoughtcrime.securesms.database.StickerTable;
|
||||
import org.thoughtcrime.securesms.database.model.AvatarPickerDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
@@ -96,15 +94,14 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
* but we will not export the actual contents.
|
||||
*/
|
||||
private static final Set<String> TABLE_CONTENT_BLOCKLIST = SetUtil.newHashSet(
|
||||
SignedPreKeyDatabase.TABLE_NAME,
|
||||
OneTimePreKeyDatabase.TABLE_NAME,
|
||||
SessionDatabase.TABLE_NAME,
|
||||
SearchDatabase.SMS_FTS_TABLE_NAME,
|
||||
SearchDatabase.MMS_FTS_TABLE_NAME,
|
||||
EmojiSearchDatabase.TABLE_NAME,
|
||||
SenderKeyDatabase.TABLE_NAME,
|
||||
SenderKeySharedDatabase.TABLE_NAME,
|
||||
PendingRetryReceiptDatabase.TABLE_NAME,
|
||||
SignedPreKeyTable.TABLE_NAME,
|
||||
OneTimePreKeyTable.TABLE_NAME,
|
||||
SessionTable.TABLE_NAME,
|
||||
SearchTable.MMS_FTS_TABLE_NAME,
|
||||
EmojiSearchTable.TABLE_NAME,
|
||||
SenderKeyTable.TABLE_NAME,
|
||||
SenderKeySharedTable.TABLE_NAME,
|
||||
PendingRetryReceiptTable.TABLE_NAME,
|
||||
AvatarPickerDatabase.TABLE_NAME
|
||||
);
|
||||
|
||||
@@ -172,19 +169,17 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
|
||||
for (String table : tables) {
|
||||
throwIfCanceled(cancellationSignal);
|
||||
if (table.equals(MmsDatabase.TABLE_NAME)) {
|
||||
if (table.equals(MessageTable.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringMmsMessage, null, count, estimatedCount, cancellationSignal);
|
||||
} else if (table.equals(SmsDatabase.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringSmsMessage, null, count, estimatedCount, cancellationSignal);
|
||||
} else if (table.equals(ReactionDatabase.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, new MessageId(CursorUtil.requireLong(cursor, ReactionDatabase.MESSAGE_ID), CursorUtil.requireBoolean(cursor, ReactionDatabase.IS_MMS))), null, count, estimatedCount, cancellationSignal);
|
||||
} else if (table.equals(MentionDatabase.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMmsMessage(input, CursorUtil.requireLong(cursor, MentionDatabase.MESSAGE_ID)), null, count, estimatedCount, cancellationSignal);
|
||||
} else if (table.equals(GroupReceiptDatabase.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMmsMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(GroupReceiptDatabase.MMS_ID))), null, count, estimatedCount, cancellationSignal);
|
||||
} else if (table.equals(AttachmentDatabase.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMmsMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.MMS_ID))), (cursor, innerCount) -> exportAttachment(attachmentSecret, cursor, outputStream, innerCount, estimatedCount), count, estimatedCount, cancellationSignal);
|
||||
} else if (table.equals(StickerDatabase.TABLE_NAME)) {
|
||||
} else if (table.equals(ReactionTable.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, new MessageId(CursorUtil.requireLong(cursor, ReactionTable.MESSAGE_ID))), null, count, estimatedCount, cancellationSignal);
|
||||
} else if (table.equals(MentionTable.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMmsMessage(input, CursorUtil.requireLong(cursor, MentionTable.MESSAGE_ID)), null, count, estimatedCount, cancellationSignal);
|
||||
} else if (table.equals(GroupReceiptTable.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMmsMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(GroupReceiptTable.MMS_ID))), null, count, estimatedCount, cancellationSignal);
|
||||
} else if (table.equals(AttachmentTable.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMmsMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentTable.MMS_ID))), (cursor, innerCount) -> exportAttachment(attachmentSecret, cursor, outputStream, innerCount, estimatedCount), count, estimatedCount, cancellationSignal);
|
||||
} else if (table.equals(StickerTable.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, cursor -> true, (cursor, innerCount) -> exportSticker(attachmentSecret, cursor, outputStream, innerCount, estimatedCount), count, estimatedCount, cancellationSignal);
|
||||
} else if (!TABLE_CONTENT_BLOCKLIST.contains(table)) {
|
||||
count = exportTable(table, input, outputStream, null, null, count, estimatedCount, cancellationSignal);
|
||||
@@ -230,15 +225,13 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
long count = DATABASE_VERSION_RECORD_COUNT + TABLE_RECORD_COUNT_MULTIPLIER * tables.size();
|
||||
|
||||
for (String table : tables) {
|
||||
if (table.equals(MmsDatabase.TABLE_NAME)) {
|
||||
if (table.equals(MessageTable.TABLE_NAME)) {
|
||||
count += getCount(input, BackupCountQueries.mmsCount);
|
||||
} else if (table.equals(SmsDatabase.TABLE_NAME)) {
|
||||
count += getCount(input, BackupCountQueries.smsCount);
|
||||
} else if (table.equals(GroupReceiptDatabase.TABLE_NAME)) {
|
||||
} else if (table.equals(GroupReceiptTable.TABLE_NAME)) {
|
||||
count += getCount(input, BackupCountQueries.getGroupReceiptCount());
|
||||
} else if (table.equals(AttachmentDatabase.TABLE_NAME)) {
|
||||
} else if (table.equals(AttachmentTable.TABLE_NAME)) {
|
||||
count += getCount(input, BackupCountQueries.getAttachmentCount());
|
||||
} else if (table.equals(StickerDatabase.TABLE_NAME)) {
|
||||
} else if (table.equals(StickerTable.TABLE_NAME)) {
|
||||
count += getCount(input, "SELECT COUNT(*) FROM " + table);
|
||||
} else if (!TABLE_CONTENT_BLOCKLIST.contains(table)) {
|
||||
count += getCount(input, "SELECT COUNT(*) FROM " + table);
|
||||
@@ -375,12 +368,10 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
}
|
||||
|
||||
boolean isReservedTable = table.startsWith("sqlite_");
|
||||
boolean isSmsFtsSecretTable = !table.equals(SearchDatabase.SMS_FTS_TABLE_NAME) && table.startsWith(SearchDatabase.SMS_FTS_TABLE_NAME);
|
||||
boolean isMmsFtsSecretTable = !table.equals(SearchDatabase.MMS_FTS_TABLE_NAME) && table.startsWith(SearchDatabase.MMS_FTS_TABLE_NAME);
|
||||
boolean isEmojiFtsSecretTable = !table.equals(EmojiSearchDatabase.TABLE_NAME) && table.startsWith(EmojiSearchDatabase.TABLE_NAME);
|
||||
boolean isMmsFtsSecretTable = !table.equals(SearchTable.MMS_FTS_TABLE_NAME) && table.startsWith(SearchTable.MMS_FTS_TABLE_NAME);
|
||||
boolean isEmojiFtsSecretTable = !table.equals(EmojiSearchTable.TABLE_NAME) && table.startsWith(EmojiSearchTable.TABLE_NAME);
|
||||
|
||||
return !isReservedTable &&
|
||||
!isSmsFtsSecretTable &&
|
||||
!isMmsFtsSecretTable &&
|
||||
!isEmojiFtsSecretTable;
|
||||
}
|
||||
@@ -451,12 +442,12 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
long estimatedCount)
|
||||
throws IOException
|
||||
{
|
||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.ROW_ID));
|
||||
long uniqueId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.UNIQUE_ID));
|
||||
long size = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.SIZE));
|
||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentTable.ROW_ID));
|
||||
long uniqueId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentTable.UNIQUE_ID));
|
||||
long size = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentTable.SIZE));
|
||||
|
||||
String data = cursor.getString(cursor.getColumnIndexOrThrow(AttachmentDatabase.DATA));
|
||||
byte[] random = cursor.getBlob(cursor.getColumnIndexOrThrow(AttachmentDatabase.DATA_RANDOM));
|
||||
String data = cursor.getString(cursor.getColumnIndexOrThrow(AttachmentTable.DATA));
|
||||
byte[] random = cursor.getBlob(cursor.getColumnIndexOrThrow(AttachmentTable.DATA_RANDOM));
|
||||
|
||||
if (!TextUtils.isEmpty(data)) {
|
||||
long fileLength = new File(data).length();
|
||||
@@ -473,7 +464,7 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
try (InputStream inputStream = openAttachmentStream(attachmentSecret, random, data)) {
|
||||
outputStream.write(new AttachmentId(rowId, uniqueId), inputStream, size);
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.w(TAG, "Missing attachment: " + e.getMessage());
|
||||
Log.w(TAG, "Missing attachment", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,18 +478,18 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
long estimatedCount)
|
||||
throws IOException
|
||||
{
|
||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase._ID));
|
||||
long size = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_LENGTH));
|
||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(StickerTable._ID));
|
||||
long size = cursor.getLong(cursor.getColumnIndexOrThrow(StickerTable.FILE_LENGTH));
|
||||
|
||||
String data = cursor.getString(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_PATH));
|
||||
byte[] random = cursor.getBlob(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_RANDOM));
|
||||
String data = cursor.getString(cursor.getColumnIndexOrThrow(StickerTable.FILE_PATH));
|
||||
byte[] random = cursor.getBlob(cursor.getColumnIndexOrThrow(StickerTable.FILE_RANDOM));
|
||||
|
||||
if (!TextUtils.isEmpty(data) && size > 0) {
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
|
||||
try (InputStream inputStream = ModernDecryptingPartInputStream.createFor(attachmentSecret, random, new File(data), 0)) {
|
||||
outputStream.writeSticker(rowId, inputStream, size);
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.w(TAG, "Missing sticker: " + e.getMessage());
|
||||
Log.w(TAG, "Missing sticker", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,7 +507,7 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
result += read;
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.w(TAG, "Missing attachment: " + e.getMessage());
|
||||
Log.w(TAG, "Missing attachment for size calculation", e);
|
||||
return 0;
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to determine stream length", e);
|
||||
@@ -586,42 +577,24 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
}
|
||||
|
||||
private static boolean isNonExpiringMmsMessage(@NonNull Cursor cursor) {
|
||||
return cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.EXPIRES_IN)) <= 0 &&
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.VIEW_ONCE)) <= 0;
|
||||
return cursor.getLong(cursor.getColumnIndexOrThrow(MessageTable.EXPIRES_IN)) <= 0 &&
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(MessageTable.VIEW_ONCE)) <= 0;
|
||||
}
|
||||
|
||||
private static boolean isNonExpiringSmsMessage(@NonNull Cursor cursor) {
|
||||
return cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.EXPIRES_IN)) <= 0;
|
||||
return cursor.getLong(cursor.getColumnIndexOrThrow(MessageTable.EXPIRES_IN)) <= 0;
|
||||
}
|
||||
|
||||
private static boolean isForNonExpiringMessage(@NonNull SQLiteDatabase db, @NonNull MessageId messageId) {
|
||||
if (messageId.isMms()) {
|
||||
return isForNonExpiringMmsMessage(db, messageId.getId());
|
||||
} else {
|
||||
return isForNonExpiringSmsMessage(db, messageId.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isForNonExpiringSmsMessage(@NonNull SQLiteDatabase db, long smsId) {
|
||||
String[] columns = new String[] { SmsDatabase.EXPIRES_IN };
|
||||
String where = SmsDatabase.ID + " = ?";
|
||||
String[] args = new String[] { String.valueOf(smsId) };
|
||||
|
||||
try (Cursor cursor = db.query(SmsDatabase.TABLE_NAME, columns, where, args, null, null, null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return isNonExpiringSmsMessage(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return isForNonExpiringMmsMessage(db, messageId.getId());
|
||||
}
|
||||
|
||||
private static boolean isForNonExpiringMmsMessage(@NonNull SQLiteDatabase db, long mmsId) {
|
||||
String[] columns = new String[] { MmsDatabase.RECIPIENT_ID, MmsDatabase.EXPIRES_IN, MmsDatabase.VIEW_ONCE };
|
||||
String where = MmsDatabase.ID + " = ?";
|
||||
String[] columns = new String[] { MessageTable.RECIPIENT_ID, MessageTable.EXPIRES_IN, MessageTable.VIEW_ONCE };
|
||||
String where = MessageTable.ID + " = ?";
|
||||
String[] args = new String[] { String.valueOf(mmsId) };
|
||||
|
||||
try (Cursor mmsCursor = db.query(MmsDatabase.TABLE_NAME, columns, where, args, null, null, null)) {
|
||||
try (Cursor mmsCursor = db.query(MessageTable.TABLE_NAME, columns, where, args, null, null, null)) {
|
||||
if (mmsCursor != null && mmsCursor.moveToFirst()) {
|
||||
return isNonExpiringMmsMessage(mmsCursor);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import android.annotation.SuppressLint;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import net.zetetic.database.sqlcipher.SQLiteDatabase;
|
||||
|
||||
@@ -24,11 +24,11 @@ import org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement;
|
||||
import org.thoughtcrime.securesms.backup.BackupProtos.Sticker;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.EmojiSearchDatabase;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.database.EmojiSearchTable;
|
||||
import org.thoughtcrime.securesms.database.KeyValueDatabase;
|
||||
import org.thoughtcrime.securesms.database.SearchDatabase;
|
||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
||||
import org.thoughtcrime.securesms.database.SearchTable;
|
||||
import org.thoughtcrime.securesms.database.StickerTable;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
@@ -42,29 +42,24 @@ import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class FullBackupImporter extends FullBackupBase {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = Log.tag(FullBackupImporter.class);
|
||||
|
||||
private static final String[] TABLES_TO_DROP_FIRST = {
|
||||
"distribution_list_member",
|
||||
"distribution_list",
|
||||
"message_send_log_recipients",
|
||||
"msl_recipient",
|
||||
"msl_message",
|
||||
"reaction",
|
||||
"notification_profile_schedule",
|
||||
"notification_profile_allowed_members",
|
||||
"story_sends"
|
||||
};
|
||||
|
||||
public static void importFile(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret,
|
||||
@NonNull SQLiteDatabase db, @NonNull Uri uri, @NonNull String passphrase)
|
||||
throws IOException
|
||||
@@ -132,12 +127,11 @@ public class FullBackupImporter extends FullBackupBase {
|
||||
}
|
||||
|
||||
private static void processStatement(@NonNull SQLiteDatabase db, SqlStatement statement) {
|
||||
boolean isForSmsFtsSecretTable = statement.getStatement().contains(SearchDatabase.SMS_FTS_TABLE_NAME + "_");
|
||||
boolean isForMmsFtsSecretTable = statement.getStatement().contains(SearchDatabase.MMS_FTS_TABLE_NAME + "_");
|
||||
boolean isForEmojiSecretTable = statement.getStatement().contains(EmojiSearchDatabase.TABLE_NAME + "_");
|
||||
boolean isForMmsFtsSecretTable = statement.getStatement().contains(SearchTable.MMS_FTS_TABLE_NAME + "_");
|
||||
boolean isForEmojiSecretTable = statement.getStatement().contains(EmojiSearchTable.TABLE_NAME + "_");
|
||||
boolean isForSqliteSecretTable = statement.getStatement().toLowerCase().startsWith("create table sqlite_");
|
||||
|
||||
if (isForSmsFtsSecretTable || isForMmsFtsSecretTable || isForEmojiSecretTable || isForSqliteSecretTable) {
|
||||
if (isForMmsFtsSecretTable || isForEmojiSecretTable || isForSqliteSecretTable) {
|
||||
Log.i(TAG, "Ignoring import for statement: " + statement.getStatement());
|
||||
return;
|
||||
}
|
||||
@@ -159,7 +153,7 @@ public class FullBackupImporter extends FullBackupBase {
|
||||
private static void processAttachment(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull Attachment attachment, BackupRecordInputStream inputStream)
|
||||
throws IOException
|
||||
{
|
||||
File dataFile = AttachmentDatabase.newFile(context);
|
||||
File dataFile = AttachmentTable.newFile(context);
|
||||
Pair<byte[], OutputStream> output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false);
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
@@ -167,24 +161,24 @@ public class FullBackupImporter extends FullBackupBase {
|
||||
try {
|
||||
inputStream.readAttachmentTo(output.second, attachment.getLength());
|
||||
|
||||
contentValues.put(AttachmentDatabase.DATA, dataFile.getAbsolutePath());
|
||||
contentValues.put(AttachmentDatabase.DATA_RANDOM, output.first);
|
||||
contentValues.put(AttachmentTable.DATA, dataFile.getAbsolutePath());
|
||||
contentValues.put(AttachmentTable.DATA_RANDOM, output.first);
|
||||
} catch (BackupRecordInputStream.BadMacException e) {
|
||||
Log.w(TAG, "Bad MAC for attachment " + attachment.getAttachmentId() + "! Can't restore it.", e);
|
||||
dataFile.delete();
|
||||
contentValues.put(AttachmentDatabase.DATA, (String) null);
|
||||
contentValues.put(AttachmentDatabase.DATA_RANDOM, (String) null);
|
||||
contentValues.put(AttachmentTable.DATA, (String) null);
|
||||
contentValues.put(AttachmentTable.DATA_RANDOM, (String) null);
|
||||
}
|
||||
|
||||
db.update(AttachmentDatabase.TABLE_NAME, contentValues,
|
||||
AttachmentDatabase.ROW_ID + " = ? AND " + AttachmentDatabase.UNIQUE_ID + " = ?",
|
||||
db.update(AttachmentTable.TABLE_NAME, contentValues,
|
||||
AttachmentTable.ROW_ID + " = ? AND " + AttachmentTable.UNIQUE_ID + " = ?",
|
||||
new String[] {String.valueOf(attachment.getRowId()), String.valueOf(attachment.getAttachmentId())});
|
||||
}
|
||||
|
||||
private static void processSticker(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull Sticker sticker, BackupRecordInputStream inputStream)
|
||||
throws IOException
|
||||
{
|
||||
File stickerDirectory = context.getDir(StickerDatabase.DIRECTORY, Context.MODE_PRIVATE);
|
||||
File stickerDirectory = context.getDir(StickerTable.DIRECTORY, Context.MODE_PRIVATE);
|
||||
File dataFile = File.createTempFile("sticker", ".mms", stickerDirectory);
|
||||
|
||||
Pair<byte[], OutputStream> output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false);
|
||||
@@ -192,12 +186,12 @@ public class FullBackupImporter extends FullBackupBase {
|
||||
inputStream.readAttachmentTo(output.second, sticker.getLength());
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(StickerDatabase.FILE_PATH, dataFile.getAbsolutePath());
|
||||
contentValues.put(StickerDatabase.FILE_LENGTH, sticker.getLength());
|
||||
contentValues.put(StickerDatabase.FILE_RANDOM, output.first);
|
||||
contentValues.put(StickerTable.FILE_PATH, dataFile.getAbsolutePath());
|
||||
contentValues.put(StickerTable.FILE_LENGTH, sticker.getLength());
|
||||
contentValues.put(StickerTable.FILE_RANDOM, output.first);
|
||||
|
||||
db.update(StickerDatabase.TABLE_NAME, contentValues,
|
||||
StickerDatabase._ID + " = ?",
|
||||
db.update(StickerTable.TABLE_NAME, contentValues,
|
||||
StickerTable._ID + " = ?",
|
||||
new String[] {String.valueOf(sticker.getRowId())});
|
||||
}
|
||||
|
||||
@@ -268,21 +262,69 @@ public class FullBackupImporter extends FullBackupBase {
|
||||
}
|
||||
|
||||
private static void dropAllTables(@NonNull SQLiteDatabase db) {
|
||||
for (String name : TABLES_TO_DROP_FIRST) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + name);
|
||||
for (String trigger : SqlUtil.getAllTriggers(db)) {
|
||||
Log.i(TAG, "Dropping trigger: " + trigger);
|
||||
db.execSQL("DROP TRIGGER IF EXISTS " + trigger);
|
||||
}
|
||||
for (String table : getTablesToDropInOrder(db)) {
|
||||
Log.i(TAG, "Dropping table: " + table);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + table);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of tables we should drop, in the order they should be dropped in.
|
||||
* The order is chosen to ensure we won't violate any foreign key constraints when we import them.
|
||||
*/
|
||||
private static List<String> getTablesToDropInOrder(@NonNull SQLiteDatabase input) {
|
||||
List<String> tables = SqlUtil.getAllTables(input)
|
||||
.stream()
|
||||
.filter(table -> !table.startsWith("sqlite_"))
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
Map<String, Set<String>> dependsOn = new LinkedHashMap<>();
|
||||
for (String table : tables) {
|
||||
dependsOn.put(table, SqlUtil.getForeignKeyDependencies(input, table));
|
||||
}
|
||||
|
||||
try (Cursor cursor = db.rawQuery("SELECT name, type FROM sqlite_master", null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
String name = cursor.getString(0);
|
||||
String type = cursor.getString(1);
|
||||
for (String table : tables) {
|
||||
Set<String> dependsOnTable = dependsOn.keySet().stream().filter(t -> dependsOn.get(t).contains(table)).collect(Collectors.toSet());
|
||||
Log.i(TAG, "Tables that depend on " + table + ": " + dependsOnTable);
|
||||
}
|
||||
|
||||
if ("table".equals(type) && !name.startsWith("sqlite_")) {
|
||||
Log.i(TAG, "Dropping table: " + name);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + name);
|
||||
}
|
||||
return computeTableDropOrder(dependsOn);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static List<String> computeTableDropOrder(@NonNull Map<String, Set<String>> dependsOn) {
|
||||
List<String> rootNodes = dependsOn.keySet()
|
||||
.stream()
|
||||
.filter(table -> {
|
||||
boolean nothingDependsOnIt = dependsOn.values().stream().noneMatch(it -> it.contains(table));
|
||||
return nothingDependsOnIt;
|
||||
})
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
LinkedHashSet<String> dropOrder = new LinkedHashSet<>();
|
||||
|
||||
Queue<String> processOrder = new LinkedList<>(rootNodes);
|
||||
|
||||
while (!processOrder.isEmpty()) {
|
||||
String head = processOrder.remove();
|
||||
|
||||
dropOrder.remove(head);
|
||||
dropOrder.add(head);
|
||||
|
||||
Set<String> dependencies = dependsOn.get(head);
|
||||
if (dependencies != null) {
|
||||
processOrder.addAll(dependencies);
|
||||
}
|
||||
}
|
||||
|
||||
return new ArrayList<>(dropOrder);
|
||||
}
|
||||
|
||||
public static class DatabaseDowngradeException extends IOException {
|
||||
|
||||
@@ -6,7 +6,7 @@ import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileContentUpdateJob
|
||||
@@ -40,13 +40,13 @@ class BadgeRepository(context: Context) {
|
||||
): List<Badge> {
|
||||
Log.d(TAG, "[setVisibilityForAllBadgesSync] Setting badge visibility...", true)
|
||||
|
||||
val recipientDatabase: RecipientDatabase = SignalDatabase.recipients
|
||||
val recipientTable: RecipientTable = SignalDatabase.recipients
|
||||
val badges = selfBadges.map { it.copy(visible = displayBadgesOnProfile) }
|
||||
|
||||
Log.d(TAG, "[setVisibilityForAllBadgesSync] Uploading profile...", true)
|
||||
ProfileUtil.uploadProfileWithBadges(context, badges)
|
||||
SignalStore.donationsValues().setDisplayBadgesOnProfile(displayBadgesOnProfile)
|
||||
recipientDatabase.markNeedsSync(Recipient.self().id)
|
||||
recipientTable.markNeedsSync(Recipient.self().id)
|
||||
|
||||
Log.d(TAG, "[setVisibilityForAllBadgesSync] Requesting data change sync...", true)
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
|
||||
@@ -4,11 +4,8 @@ import android.content.Context
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException
|
||||
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
||||
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.Base64
|
||||
import java.lang.Integer.min
|
||||
@@ -32,23 +29,14 @@ object Gifts {
|
||||
giftBadge: GiftBadge,
|
||||
sentTimestamp: Long,
|
||||
expiresIn: Long
|
||||
): OutgoingMediaMessage {
|
||||
return OutgoingSecureMediaMessage(
|
||||
recipient,
|
||||
Base64.encodeBytes(giftBadge.toByteArray()),
|
||||
listOf(),
|
||||
sentTimestamp,
|
||||
ThreadDatabase.DistributionTypes.CONVERSATION,
|
||||
expiresIn,
|
||||
false,
|
||||
StoryType.NONE,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
listOf(),
|
||||
listOf(),
|
||||
listOf(),
|
||||
giftBadge
|
||||
): OutgoingMessage {
|
||||
return OutgoingMessage(
|
||||
recipient = recipient,
|
||||
body = Base64.encodeBytes(giftBadge.toByteArray()),
|
||||
isSecure = true,
|
||||
sentTimeMillis = sentTimestamp,
|
||||
expiresIn = expiresIn,
|
||||
giftBadge = giftBadge
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ class GiftFlowActivity : FragmentWrapperActivity(), DonationPaymentComponent {
|
||||
return NavHostFragment.create(R.navigation.gift_flow)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
googlePayResultPublisher.onNext(DonationPaymentComponent.GooglePayResult(requestCode, resultCode, data))
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.thoughtcrime.securesms.badges.gifts.flow
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.view.KeyEvent
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
@@ -26,8 +25,6 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.donate.Do
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationCheckoutDelegate
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorAction
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayRequest
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorDialogs
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.components.settings.conversation.preferences.RecipientPreference
|
||||
@@ -72,7 +69,6 @@ class GiftFlowConfirmationFragment :
|
||||
private lateinit var emojiKeyboard: MediaKeyboard
|
||||
|
||||
private val lifecycleDisposable = LifecycleDisposable()
|
||||
private var errorDialog: DialogInterface? = null
|
||||
private var donationCheckoutDelegate: DonationCheckoutDelegate? = null
|
||||
private lateinit var processingDonationPaymentDialog: AlertDialog
|
||||
private lateinit var verifyingRecipientDonationPaymentDialog: AlertDialog
|
||||
@@ -87,7 +83,7 @@ class GiftFlowConfirmationFragment :
|
||||
|
||||
keyboardPagerViewModel.setOnlyPage(KeyboardPage.EMOJI)
|
||||
|
||||
donationCheckoutDelegate = DonationCheckoutDelegate(this, this)
|
||||
donationCheckoutDelegate = DonationCheckoutDelegate(this, this, DonationErrorSource.GIFT)
|
||||
|
||||
processingDonationPaymentDialog = MaterialAlertDialogBuilder(requireContext())
|
||||
.setView(R.layout.processing_payment_dialog)
|
||||
@@ -161,7 +157,7 @@ class GiftFlowConfirmationFragment :
|
||||
viewModel.setAdditionalMessage(it)
|
||||
},
|
||||
onEmojiToggleClicked = {
|
||||
if (inputAwareLayout.isKeyboardOpen || (!inputAwareLayout.isKeyboardOpen && !inputAwareLayout.isInputOpen)) {
|
||||
if ((inputAwareLayout.isKeyboardOpen && !emojiKeyboard.isEmojiSearchMode) || (!inputAwareLayout.isKeyboardOpen && !inputAwareLayout.isInputOpen)) {
|
||||
inputAwareLayout.show(it, emojiKeyboard)
|
||||
emojiToggle.setImageResource(R.drawable.ic_keyboard_24)
|
||||
} else {
|
||||
@@ -192,12 +188,6 @@ class GiftFlowConfirmationFragment :
|
||||
}
|
||||
|
||||
lifecycleDisposable.bindTo(viewLifecycleOwner)
|
||||
lifecycleDisposable += DonationError
|
||||
.getErrorsForSource(DonationErrorSource.GIFT)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { donationError ->
|
||||
onPaymentError(donationError)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
@@ -236,24 +226,6 @@ class GiftFlowConfirmationFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private fun onPaymentError(throwable: Throwable?) {
|
||||
Log.w(TAG, "onPaymentError", throwable, true)
|
||||
|
||||
if (errorDialog != null) {
|
||||
Log.i(TAG, "Already displaying an error dialog. Skipping.")
|
||||
return
|
||||
}
|
||||
|
||||
errorDialog = DonationErrorDialogs.show(
|
||||
requireContext(), throwable,
|
||||
object : DonationErrorDialogs.DialogCallback() {
|
||||
override fun onDialogDismissed() {
|
||||
requireActivity().finish()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onToolbarNavigationClicked() {
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
@@ -282,6 +254,10 @@ class GiftFlowConfirmationFragment :
|
||||
findNavController().safeNavigate(GiftFlowConfirmationFragmentDirections.actionGiftFlowConfirmationFragmentToStripePaymentInProgressFragment(DonationProcessorAction.PROCESS_NEW_DONATION, gatewayRequest))
|
||||
}
|
||||
|
||||
override fun navigateToPayPalPaymentInProgress(gatewayRequest: GatewayRequest) {
|
||||
findNavController().safeNavigate(GiftFlowConfirmationFragmentDirections.actionGiftFlowConfirmationFragmentToPaypalPaymentInProgressFragment(DonationProcessorAction.PROCESS_NEW_DONATION, gatewayRequest))
|
||||
}
|
||||
|
||||
override fun navigateToCreditCardForm(gatewayRequest: GatewayRequest) {
|
||||
findNavController().safeNavigate(GiftFlowConfirmationFragmentDirections.actionGiftFlowConfirmationFragmentToCreditCardFragment(gatewayRequest))
|
||||
}
|
||||
@@ -297,4 +273,7 @@ class GiftFlowConfirmationFragment :
|
||||
}
|
||||
|
||||
override fun onProcessorActionProcessed() = Unit
|
||||
override fun onUserCancelledPaymentFlow() {
|
||||
findNavController().popBackStack(R.id.giftFlowConfirmationFragment, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,17 +5,17 @@ import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.thoughtcrime.securesms.badges.Badges
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.getGiftBadgeAmounts
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.getGiftBadges
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.PlatformCurrencyUtil
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse
|
||||
import org.whispersystems.signalservice.internal.push.DonationsConfiguration
|
||||
import java.io.IOException
|
||||
import java.util.Currency
|
||||
import java.util.Locale
|
||||
@@ -29,15 +29,14 @@ class GiftFlowRepository {
|
||||
private val TAG = Log.tag(GiftFlowRepository::class.java)
|
||||
}
|
||||
|
||||
fun getGiftBadge(): Single<Pair<Long, Badge>> {
|
||||
fun getGiftBadge(): Single<Pair<Int, Badge>> {
|
||||
return Single
|
||||
.fromCallable {
|
||||
ApplicationDependencies.getDonationsService()
|
||||
.getGiftBadges(Locale.getDefault())
|
||||
.getDonationsConfiguration(Locale.getDefault())
|
||||
}
|
||||
.flatMap(ServiceResponse<Map<Long, SignalServiceProfile.Badge>>::flattenResult)
|
||||
.map { gifts -> gifts.map { it.key to Badges.fromServiceBadge(it.value) } }
|
||||
.map { it.first() }
|
||||
.flatMap { it.flattenResult() }
|
||||
.map { DonationsConfiguration.GIFT_LEVEL to it.getGiftBadges().first() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
@@ -45,20 +44,17 @@ class GiftFlowRepository {
|
||||
return Single
|
||||
.fromCallable {
|
||||
ApplicationDependencies.getDonationsService()
|
||||
.giftAmount
|
||||
.getDonationsConfiguration(Locale.getDefault())
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.flatMap { it.flattenResult() }
|
||||
.map { result ->
|
||||
result
|
||||
.filter { PlatformCurrencyUtil.getAvailableCurrencyCodes().contains(it.key) }
|
||||
.mapKeys { (code, _) -> Currency.getInstance(code) }
|
||||
.mapValues { (currency, price) -> FiatMoney(price, currency) }
|
||||
}
|
||||
.map { it.getGiftBadgeAmounts() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the given recipient is a supported target for a gift.
|
||||
*
|
||||
* TODO[alex] - this needs to be incorporated into the correct flows.
|
||||
*/
|
||||
fun verifyRecipientIsAllowedToReceiveAGift(badgeRecipient: RecipientId): Completable {
|
||||
return Completable.fromAction {
|
||||
@@ -70,7 +66,7 @@ class GiftFlowRepository {
|
||||
throw DonationError.GiftRecipientVerificationError.SelectedRecipientDoesNotSupportGifts
|
||||
}
|
||||
|
||||
if (recipient.isGroup || recipient.isDistributionList || recipient.registered != RecipientDatabase.RegisteredState.REGISTERED) {
|
||||
if (recipient.isGroup || recipient.isDistributionList || recipient.registered != RecipientTable.RegisteredState.REGISTERED) {
|
||||
Log.w(TAG, "Invalid badge recipient $badgeRecipient. Verification failed.", true)
|
||||
throw DonationError.GiftRecipientVerificationError.SelectedRecipientIsInvalid
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ class GiftFlowViewModel(
|
||||
onSuccess = { (giftLevel, giftBadge) ->
|
||||
store.update {
|
||||
it.copy(
|
||||
giftLevel = giftLevel,
|
||||
giftLevel = giftLevel.toLong(),
|
||||
giftBadge = giftBadge,
|
||||
stage = getLoadState(it, giftBadge = giftBadge)
|
||||
)
|
||||
|
||||
@@ -4,8 +4,8 @@ import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation
|
||||
import org.thoughtcrime.securesms.badges.Badges
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.getBadge
|
||||
import org.thoughtcrime.securesms.database.DatabaseObserver
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
@@ -23,24 +23,24 @@ class ViewGiftRepository {
|
||||
.fromCallable {
|
||||
ApplicationDependencies
|
||||
.getDonationsService()
|
||||
.getGiftBadge(Locale.getDefault(), presentation.receiptLevel)
|
||||
.getDonationsConfiguration(Locale.getDefault())
|
||||
}
|
||||
.flatMap { it.flattenResult() }
|
||||
.map { Badges.fromServiceBadge(it) }
|
||||
.map { it.getBadge(presentation.receiptLevel.toInt()) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun getGiftBadge(messageId: Long): Observable<GiftBadge> {
|
||||
return Observable.create { emitter ->
|
||||
fun refresh() {
|
||||
val record = SignalDatabase.mms.getMessageRecord(messageId)
|
||||
val record = SignalDatabase.messages.getMessageRecord(messageId)
|
||||
val giftBadge: GiftBadge = (record as MmsMessageRecord).giftBadge!!
|
||||
|
||||
emitter.onNext(giftBadge)
|
||||
}
|
||||
|
||||
val messageObserver = DatabaseObserver.MessageObserver {
|
||||
if (it.mms && messageId == it.id) {
|
||||
if (messageId == it.id) {
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -37,10 +38,13 @@ public class BlockedUsersActivity extends PassphraseRequiredActivity implements
|
||||
|
||||
private BlockedUsersViewModel viewModel;
|
||||
|
||||
private final LifecycleDisposable lifecycleDisposable = new LifecycleDisposable();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
||||
super.onCreate(savedInstanceState, ready);
|
||||
|
||||
lifecycleDisposable.bindTo(this);
|
||||
dynamicTheme.onCreate(this);
|
||||
|
||||
setContentView(R.layout.blocked_users_activity);
|
||||
@@ -78,7 +82,11 @@ public class BlockedUsersActivity extends PassphraseRequiredActivity implements
|
||||
.add(R.id.fragment_container, new BlockedUsersFragment())
|
||||
.commit();
|
||||
|
||||
viewModel.getEvents().observe(this, event -> handleEvent(container, event));
|
||||
lifecycleDisposable.add(
|
||||
viewModel
|
||||
.getEvents()
|
||||
.subscribe(event -> handleEvent(container, event))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,12 +15,15 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import org.thoughtcrime.securesms.BlockUnblockDialog;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable;
|
||||
|
||||
public class BlockedUsersFragment extends Fragment {
|
||||
|
||||
private BlockedUsersViewModel viewModel;
|
||||
private Listener listener;
|
||||
|
||||
private final LifecycleDisposable lifecycleDisposable = new LifecycleDisposable();
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
@@ -59,16 +62,19 @@ public class BlockedUsersFragment extends Fragment {
|
||||
}
|
||||
});
|
||||
|
||||
lifecycleDisposable.bindTo(getViewLifecycleOwner());
|
||||
viewModel = new ViewModelProvider(requireActivity()).get(BlockedUsersViewModel.class);
|
||||
viewModel.getRecipients().observe(getViewLifecycleOwner(), list -> {
|
||||
if (list.isEmpty()) {
|
||||
empty.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
empty.setVisibility(View.GONE);
|
||||
}
|
||||
lifecycleDisposable.add(
|
||||
viewModel.getRecipients().subscribe(list -> {
|
||||
if (list.isEmpty()) {
|
||||
empty.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
empty.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
adapter.submitList(list);
|
||||
});
|
||||
adapter.submitList(list);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private void handleRecipientClicked(@NonNull Recipient recipient) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import androidx.core.util.Consumer;
|
||||
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeFailedException;
|
||||
@@ -32,8 +32,8 @@ class BlockedUsersRepository {
|
||||
|
||||
void getBlocked(@NonNull Consumer<List<Recipient>> blockedUsers) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
RecipientDatabase db = SignalDatabase.recipients();
|
||||
try (RecipientDatabase.RecipientReader reader = db.readerForBlocked(db.getBlocked())) {
|
||||
RecipientTable db = SignalDatabase.recipients();
|
||||
try (RecipientTable.RecipientReader reader = db.readerForBlocked(db.getBlocked())) {
|
||||
int count = reader.getCount();
|
||||
if (count == 0) {
|
||||
blockedUsers.accept(Collections.emptyList());
|
||||
|
||||
@@ -2,64 +2,66 @@ package org.thoughtcrime.securesms.blocked;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject;
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject;
|
||||
import io.reactivex.rxjava3.subjects.Subject;
|
||||
|
||||
public class BlockedUsersViewModel extends ViewModel {
|
||||
|
||||
private final BlockedUsersRepository repository;
|
||||
private final MutableLiveData<List<Recipient>> recipients;
|
||||
private final SingleLiveEvent<Event> events = new SingleLiveEvent<>();
|
||||
private final BlockedUsersRepository repository;
|
||||
private final Subject<List<Recipient>> recipients = BehaviorSubject.create();
|
||||
private final Subject<Event> events = PublishSubject.create();
|
||||
|
||||
private BlockedUsersViewModel(@NonNull BlockedUsersRepository repository) {
|
||||
this.repository = repository;
|
||||
this.recipients = new MutableLiveData<>();
|
||||
|
||||
loadRecipients();
|
||||
}
|
||||
|
||||
public LiveData<List<Recipient>> getRecipients() {
|
||||
return recipients;
|
||||
public Observable<List<Recipient>> getRecipients() {
|
||||
return recipients.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
|
||||
public LiveData<Event> getEvents() {
|
||||
return events;
|
||||
public Observable<Event> getEvents() {
|
||||
return events.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
|
||||
void block(@NonNull RecipientId recipientId) {
|
||||
repository.block(recipientId,
|
||||
() -> {
|
||||
loadRecipients();
|
||||
events.postValue(new Event(EventType.BLOCK_SUCCEEDED, Recipient.resolved(recipientId)));
|
||||
events.onNext(new Event(EventType.BLOCK_SUCCEEDED, Recipient.resolved(recipientId)));
|
||||
},
|
||||
() -> events.postValue(new Event(EventType.BLOCK_FAILED, Recipient.resolved(recipientId))));
|
||||
() -> events.onNext(new Event(EventType.BLOCK_FAILED, Recipient.resolved(recipientId))));
|
||||
}
|
||||
|
||||
void createAndBlock(@NonNull String number) {
|
||||
repository.createAndBlock(number, () -> {
|
||||
loadRecipients();
|
||||
events.postValue(new Event(EventType.BLOCK_SUCCEEDED, number));
|
||||
events.onNext(new Event(EventType.BLOCK_SUCCEEDED, number));
|
||||
});
|
||||
}
|
||||
|
||||
void unblock(@NonNull RecipientId recipientId) {
|
||||
repository.unblock(recipientId, () -> {
|
||||
loadRecipients();
|
||||
events.postValue(new Event(EventType.UNBLOCK_SUCCEEDED, Recipient.resolved(recipientId)));
|
||||
events.onNext(new Event(EventType.UNBLOCK_SUCCEEDED, Recipient.resolved(recipientId)));
|
||||
});
|
||||
}
|
||||
|
||||
private void loadRecipients() {
|
||||
repository.getBlocked(recipients::postValue);
|
||||
repository.getBlocked(recipients::onNext);
|
||||
}
|
||||
|
||||
enum EventType {
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
@@ -34,7 +35,7 @@ import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.audio.AudioWaveForm;
|
||||
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.events.PartProgressEvent;
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||
@@ -192,7 +193,7 @@ public final class AudioView extends FrameLayout {
|
||||
if (circleProgress.isSpinning()) circleProgress.stopSpinning();
|
||||
circleProgress.setVisibility(View.GONE);
|
||||
}
|
||||
} else if (showControls && audio.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_STARTED) {
|
||||
} else if (showControls && audio.getTransferState() == AttachmentTable.TRANSFER_PROGRESS_STARTED) {
|
||||
controlToggle.displayQuick(progressAndPlay);
|
||||
seekBar.setEnabled(false);
|
||||
if (circleProgress != null) {
|
||||
@@ -337,7 +338,7 @@ public final class AudioView extends FrameLayout {
|
||||
super.setClickable(clickable);
|
||||
this.playPauseButton.setClickable(clickable);
|
||||
this.seekBar.setClickable(clickable);
|
||||
this.seekBar.setOnTouchListener(clickable ? null : new TouchIgnoringListener());
|
||||
this.seekBar.setOnTouchListener(clickable ? new LongTapAwareTouchListener() : new TouchIgnoringListener());
|
||||
this.downloadButton.setClickable(clickable);
|
||||
}
|
||||
|
||||
@@ -505,6 +506,20 @@ public final class AudioView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private class LongTapAwareTouchListener implements OnTouchListener {
|
||||
private final GestureDetector gestureDetector = new GestureDetector(AudioView.this.getContext(), new GestureDetector.SimpleOnGestureListener() {
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
performLongClick();
|
||||
}
|
||||
});
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
return gestureDetector.onTouchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
private static class TouchIgnoringListener implements OnTouchListener {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
|
||||
@@ -361,9 +361,9 @@ public class ConversationItemFooter extends ConstraintLayout {
|
||||
boolean mms = messageRecord.isMms();
|
||||
|
||||
if (mms) {
|
||||
SignalDatabase.mms().markExpireStarted(id);
|
||||
SignalDatabase.messages().markExpireStarted(id);
|
||||
} else {
|
||||
SignalDatabase.sms().markExpireStarted(id);
|
||||
SignalDatabase.messages().markExpireStarted(id);
|
||||
}
|
||||
|
||||
expirationManager.scheduleDeletion(id, mms, messageRecord.getExpiresIn());
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.events.PartProgressEvent;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||
@@ -97,7 +97,7 @@ public class DocumentView extends FrameLayout {
|
||||
controlToggle.displayQuick(downloadButton);
|
||||
downloadButton.setOnClickListener(new DownloadClickedListener(documentSlide));
|
||||
if (downloadProgress.isSpinning()) downloadProgress.stopSpinning();
|
||||
} else if (showControls && documentSlide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_STARTED) {
|
||||
} else if (showControls && documentSlide.getTransferState() == AttachmentTable.TRANSFER_PROGRESS_STARTED) {
|
||||
controlToggle.displayQuick(downloadProgress);
|
||||
downloadProgress.spin();
|
||||
} else {
|
||||
|
||||
@@ -39,7 +39,7 @@ import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
|
||||
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
|
||||
import org.thoughtcrime.securesms.conversation.ConversationStickerSuggestionAdapter;
|
||||
import org.thoughtcrime.securesms.conversation.VoiceNoteDraftView;
|
||||
import org.thoughtcrime.securesms.database.DraftDatabase;
|
||||
import org.thoughtcrime.securesms.database.DraftTable;
|
||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
||||
import org.thoughtcrime.securesms.keyboard.KeyboardPage;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
@@ -489,6 +489,10 @@ public class InputPanel extends LinearLayout
|
||||
mediaKeyboard.setToMedia();
|
||||
}
|
||||
|
||||
public void setToIme() {
|
||||
mediaKeyboard.setToIme();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onKeyEvent(KeyEvent keyEvent) {
|
||||
composeText.dispatchKeyEvent(keyEvent);
|
||||
@@ -518,7 +522,7 @@ public class InputPanel extends LinearLayout
|
||||
microphoneRecorderView.unlockAction();
|
||||
}
|
||||
|
||||
public void setVoiceNoteDraft(@Nullable DraftDatabase.Draft voiceNoteDraft) {
|
||||
public void setVoiceNoteDraft(@Nullable DraftTable.Draft voiceNoteDraft) {
|
||||
if (voiceNoteDraft != null) {
|
||||
voiceNoteDraftView.setDraft(voiceNoteDraft);
|
||||
voiceNoteDraftView.setVisibility(VISIBLE);
|
||||
@@ -531,7 +535,7 @@ public class InputPanel extends LinearLayout
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable DraftDatabase.Draft getVoiceNoteDraft() {
|
||||
public @Nullable DraftTable.Draft getVoiceNoteDraft() {
|
||||
return voiceNoteDraftView.getDraft();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewAnimationUtils
|
||||
import android.widget.EditText
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.animation.addListener
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
@@ -49,6 +50,10 @@ class Material3SearchToolbar @JvmOverloads constructor(
|
||||
})
|
||||
}
|
||||
|
||||
fun setSearchInputHint(@StringRes hintStringRes: Int) {
|
||||
input.setHint(hintStringRes)
|
||||
}
|
||||
|
||||
fun display(x: Float, y: Float) {
|
||||
if (Build.VERSION.SDK_INT < 21) {
|
||||
visibility = VISIBLE
|
||||
|
||||
@@ -22,7 +22,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.contactshare.Contact;
|
||||
import org.thoughtcrime.securesms.contactshare.ContactUtil;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||
@@ -195,7 +195,7 @@ public class SharedContactView extends LinearLayout implements RecipientForeverO
|
||||
List<Recipient> systemUsers = new ArrayList<>(recipients.size());
|
||||
|
||||
for (LiveRecipient recipient : activeRecipients.values()) {
|
||||
if (recipient.get().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) {
|
||||
if (recipient.get().getRegistered() == RecipientTable.RegisteredState.REGISTERED) {
|
||||
pushUsers.add(recipient.get());
|
||||
} else if (recipient.get().isSystemContact()) {
|
||||
systemUsers.add(recipient.get());
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package org.thoughtcrime.securesms.components
|
||||
|
||||
import android.app.ProgressDialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
|
||||
/**
|
||||
* Wraps a normal progress dialog for showing blocking in-progress UI.
|
||||
*/
|
||||
class SignalProgressDialog private constructor(val progressDialog: ProgressDialog) {
|
||||
|
||||
val isShowing: Boolean
|
||||
get() = progressDialog.isShowing
|
||||
|
||||
fun hide() {
|
||||
progressDialog.hide()
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
progressDialog.dismiss()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun show(
|
||||
context: Context,
|
||||
title: CharSequence? = null,
|
||||
message: CharSequence? = null,
|
||||
indeterminate: Boolean = false,
|
||||
cancelable: Boolean = false,
|
||||
cancelListener: DialogInterface.OnCancelListener? = null
|
||||
): SignalProgressDialog {
|
||||
return SignalProgressDialog(ProgressDialog.show(context, title, message, indeterminate, cancelable, cancelListener))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||
import org.thoughtcrime.securesms.database.MediaDatabase;
|
||||
import org.thoughtcrime.securesms.database.MediaTable;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
@@ -88,9 +88,9 @@ public class ThreadPhotoRailView extends FrameLayout {
|
||||
|
||||
@Override
|
||||
public void onBindItemViewHolder(ThreadPhotoViewHolder viewHolder, @NonNull Cursor cursor) {
|
||||
ThumbnailView imageView = viewHolder.imageView;
|
||||
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(cursor);
|
||||
Slide slide = MediaUtil.getSlideForAttachment(getContext(), mediaRecord.getAttachment());
|
||||
ThumbnailView imageView = viewHolder.imageView;
|
||||
MediaTable.MediaRecord mediaRecord = MediaTable.MediaRecord.from(cursor);
|
||||
Slide slide = MediaUtil.getSlideForAttachment(getContext(), mediaRecord.getAttachment());
|
||||
|
||||
if (slide != null) {
|
||||
imageView.setImageResource(glideRequests, slide, false, false);
|
||||
@@ -118,6 +118,6 @@ public class ThreadPhotoRailView extends FrameLayout {
|
||||
}
|
||||
|
||||
public interface OnItemClickedListener {
|
||||
void onItemClicked(MediaDatabase.MediaRecord mediaRecord);
|
||||
void onItemClicked(MediaTable.MediaRecord mediaRecord);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,20 +21,18 @@ import androidx.annotation.UiThread;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
|
||||
import com.bumptech.glide.RequestBuilder;
|
||||
import com.bumptech.glide.TransitionOptions;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
|
||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
||||
import com.bumptech.glide.load.resource.bitmap.FitCenter;
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
||||
import com.bumptech.glide.request.RequestListener;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.blurhash.BlurHash;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequest;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
@@ -347,7 +345,7 @@ public class ThumbnailView extends FrameLayout {
|
||||
}
|
||||
|
||||
if (slide.getUri() != null && slide.hasPlayOverlay() &&
|
||||
(slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_DONE || isPreview))
|
||||
(slide.getTransferState() == AttachmentTable.TRANSFER_PROGRESS_DONE || isPreview))
|
||||
{
|
||||
this.playOverlay.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
@@ -579,7 +577,7 @@ public class ThumbnailView extends FrameLayout {
|
||||
public void onClick(View view) {
|
||||
boolean validThumbnail = slide != null &&
|
||||
slide.asAttachment().getUri() != null &&
|
||||
slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_DONE;
|
||||
slide.getTransferState() == AttachmentTable.TRANSFER_PROGRESS_DONE;
|
||||
|
||||
boolean permanentFailure = slide != null && slide.asAttachment().isPermanentlyFailed();
|
||||
|
||||
|
||||
@@ -18,10 +18,12 @@ import androidx.annotation.Px;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.google.android.material.shape.MaterialShapeDrawable;
|
||||
import com.google.android.material.shape.ShapeAppearanceModel;
|
||||
|
||||
import org.signal.core.util.DimensionUnit;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
/**
|
||||
* Class for creating simple tooltips to show throughout the app. Utilizes a popup window so you
|
||||
@@ -42,6 +44,8 @@ public class TooltipPopup extends PopupWindow {
|
||||
private final int position;
|
||||
private final int startMargin;
|
||||
|
||||
private final MaterialShapeDrawable shapeableBubbleBackground = new MaterialShapeDrawable();
|
||||
|
||||
public static Builder forTarget(@NonNull View anchor) {
|
||||
return new Builder(anchor);
|
||||
}
|
||||
@@ -85,9 +89,11 @@ public class TooltipPopup extends PopupWindow {
|
||||
if (backgroundTint == 0) {
|
||||
bubble.getBackground().setColorFilter(ContextCompat.getColor(anchor.getContext(), R.color.tooltip_default_color), PorterDuff.Mode.MULTIPLY);
|
||||
arrow.setColorFilter(ContextCompat.getColor(anchor.getContext(), R.color.tooltip_default_color), PorterDuff.Mode.SRC_IN);
|
||||
shapeableBubbleBackground.setTint(ContextCompat.getColor(anchor.getContext(), R.color.tooltip_default_color));
|
||||
} else {
|
||||
bubble.getBackground().setColorFilter(backgroundTint, PorterDuff.Mode.MULTIPLY);
|
||||
arrow.setColorFilter(backgroundTint, PorterDuff.Mode.SRC_IN);
|
||||
shapeableBubbleBackground.setTint(backgroundTint);
|
||||
}
|
||||
|
||||
if (iconGlideModel != null) {
|
||||
@@ -161,6 +167,26 @@ public class TooltipPopup extends PopupWindow {
|
||||
xoffset -= startMargin;
|
||||
}
|
||||
|
||||
View bubble = getContentView().findViewById(R.id.tooltip_bubble);
|
||||
ShapeAppearanceModel.Builder shapeAppearanceModel = ShapeAppearanceModel.builder()
|
||||
.setAllCornerSizes(DimensionUnit.DP.toPixels(18));
|
||||
|
||||
// If the arrow is within the last 20dp of the right hand side, use RIGHT and set corner to 9dp
|
||||
onLayout(() -> {
|
||||
if (arrow.getX() > getContentView().getWidth() / 2f) {
|
||||
arrow.setImageResource(R.drawable.ic_tooltip_arrow_up_right);
|
||||
}
|
||||
|
||||
float arrowEnd = arrow.getX() + arrow.getRight();
|
||||
if (arrowEnd > getContentView().getRight() - DimensionUnit.DP.toPixels(20)) {
|
||||
shapeableBubbleBackground.setShapeAppearanceModel(shapeAppearanceModel.setTopRightCornerSize(DimensionUnit.DP.toPixels(9f)).build());
|
||||
bubble.setBackground(shapeableBubbleBackground);
|
||||
} else if (arrowEnd < DimensionUnit.DP.toPixels(20)) {
|
||||
shapeableBubbleBackground.setShapeAppearanceModel(shapeAppearanceModel.setTopLeftCornerSize(DimensionUnit.DP.toPixels(9f)).build());
|
||||
bubble.setBackground(shapeableBubbleBackground);
|
||||
}
|
||||
});
|
||||
|
||||
showAsDropDown(anchor, xoffset, yoffset);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.events.PartProgressEvent;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
|
||||
@@ -114,17 +114,17 @@ public final class TransferControlView extends FrameLayout {
|
||||
}
|
||||
|
||||
for (Slide slide : slides) {
|
||||
if (slide.asAttachment().getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_DONE) {
|
||||
if (slide.asAttachment().getTransferState() == AttachmentTable.TRANSFER_PROGRESS_DONE) {
|
||||
networkProgress.put(slide.asAttachment(), 1f);
|
||||
}
|
||||
}
|
||||
|
||||
switch (getTransferState(slides)) {
|
||||
case AttachmentDatabase.TRANSFER_PROGRESS_STARTED:
|
||||
case AttachmentTable.TRANSFER_PROGRESS_STARTED:
|
||||
showProgressSpinner(calculateProgress(networkProgress, compresssionProgress));
|
||||
break;
|
||||
case AttachmentDatabase.TRANSFER_PROGRESS_PENDING:
|
||||
case AttachmentDatabase.TRANSFER_PROGRESS_FAILED:
|
||||
case AttachmentTable.TRANSFER_PROGRESS_PENDING:
|
||||
case AttachmentTable.TRANSFER_PROGRESS_FAILED:
|
||||
downloadDetailsText.setText(getDownloadText(this.slides));
|
||||
display(downloadDetails);
|
||||
break;
|
||||
@@ -183,27 +183,27 @@ public final class TransferControlView extends FrameLayout {
|
||||
}
|
||||
|
||||
private int getTransferState(@NonNull List<Slide> slides) {
|
||||
int transferState = AttachmentDatabase.TRANSFER_PROGRESS_DONE;
|
||||
int transferState = AttachmentTable.TRANSFER_PROGRESS_DONE;
|
||||
boolean allFailed = true;
|
||||
|
||||
for (Slide slide : slides) {
|
||||
if (slide.getTransferState() != AttachmentDatabase.TRANSFER_PROGRESS_PERMANENT_FAILURE) {
|
||||
if (slide.getTransferState() != AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE) {
|
||||
allFailed = false;
|
||||
if (slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_PENDING && transferState == AttachmentDatabase.TRANSFER_PROGRESS_DONE) {
|
||||
if (slide.getTransferState() == AttachmentTable.TRANSFER_PROGRESS_PENDING && transferState == AttachmentTable.TRANSFER_PROGRESS_DONE) {
|
||||
transferState = slide.getTransferState();
|
||||
} else {
|
||||
transferState = Math.max(transferState, slide.getTransferState());
|
||||
}
|
||||
}
|
||||
}
|
||||
return allFailed ? AttachmentDatabase.TRANSFER_PROGRESS_PERMANENT_FAILURE : transferState;
|
||||
return allFailed ? AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE : transferState;
|
||||
}
|
||||
|
||||
private String getDownloadText(@NonNull List<Slide> slides) {
|
||||
if (slides.size() == 1) {
|
||||
return slides.get(0).getContentDescription();
|
||||
} else {
|
||||
int downloadCount = Stream.of(slides).reduce(0, (count, slide) -> slide.getTransferState() != AttachmentDatabase.TRANSFER_PROGRESS_DONE ? count + 1 : count);
|
||||
int downloadCount = Stream.of(slides).reduce(0, (count, slide) -> slide.getTransferState() != AttachmentTable.TRANSFER_PROGRESS_DONE ? count + 1 : count);
|
||||
return getContext().getResources().getQuantityString(R.plurals.TransferControlView_n_items, downloadCount, downloadCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,26 @@ public class MediaKeyboard extends FrameLayout implements InputView {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
if (isInitialised && fragmentManager != null && keyboardPagerFragment != null) {
|
||||
fragmentManager.beginTransaction()
|
||||
.replace(R.id.media_keyboard_fragment_container, keyboardPagerFragment, TAG)
|
||||
.commitNowAllowingStateLoss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
if (fragmentManager != null && keyboardPagerFragment != null) {
|
||||
fragmentManager.beginTransaction()
|
||||
.remove(keyboardPagerFragment)
|
||||
.commitNowAllowingStateLoss();
|
||||
}
|
||||
}
|
||||
|
||||
public void setFragmentManager(@NonNull FragmentManager fragmentManager) {
|
||||
this.fragmentManager = fragmentManager;
|
||||
}
|
||||
@@ -152,6 +172,7 @@ public class MediaKeyboard extends FrameLayout implements InputView {
|
||||
|
||||
private void initView() {
|
||||
if (!isInitialised) {
|
||||
Log.d(TAG, "Initialising...");
|
||||
|
||||
LayoutInflater.from(getContext()).inflate(R.layout.media_keyboard, this, true);
|
||||
|
||||
@@ -166,7 +187,7 @@ public class MediaKeyboard extends FrameLayout implements InputView {
|
||||
}
|
||||
|
||||
fragmentManager.beginTransaction()
|
||||
.replace(R.id.media_keyboard_fragment_container, keyboardPagerFragment)
|
||||
.replace(R.id.media_keyboard_fragment_container, keyboardPagerFragment, TAG)
|
||||
.commitNowAllowingStateLoss();
|
||||
|
||||
keyboardState = State.NORMAL;
|
||||
|
||||
@@ -8,7 +8,7 @@ import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityTable;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.signal.core.util.concurrent.SimpleTask;
|
||||
@@ -44,7 +44,7 @@ public class UnverifiedSendDialog extends AlertDialog.Builder implements DialogI
|
||||
for (IdentityRecord identityRecord : untrustedRecords) {
|
||||
ApplicationDependencies.getProtocolStore().aci().identities().setVerified(identityRecord.getRecipientId(),
|
||||
identityRecord.getIdentityKey(),
|
||||
IdentityDatabase.VerifiedStatus.DEFAULT);
|
||||
IdentityTable.VerifiedStatus.DEFAULT);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package org.thoughtcrime.securesms.components.reminder;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
|
||||
import org.thoughtcrime.securesms.DatabaseMigrationActivity;
|
||||
import org.thoughtcrime.securesms.MainActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.service.ApplicationMigrationService;
|
||||
|
||||
public class SystemSmsImportReminder extends Reminder {
|
||||
|
||||
public SystemSmsImportReminder(final Context context) {
|
||||
super(context.getString(R.string.reminder_header_sms_import_title),
|
||||
context.getString(R.string.reminder_header_sms_import_text));
|
||||
|
||||
final OnClickListener okListener = v -> {
|
||||
Intent intent = new Intent(context, ApplicationMigrationService.class);
|
||||
intent.setAction(ApplicationMigrationService.MIGRATE_DATABASE);
|
||||
context.startService(intent);
|
||||
|
||||
// TODO [greyson] Navigation
|
||||
Intent nextIntent = MainActivity.clearTop(context);
|
||||
Intent activityIntent = new Intent(context, DatabaseMigrationActivity.class);
|
||||
activityIntent.putExtra("next_intent", nextIntent);
|
||||
context.startActivity(activityIntent);
|
||||
};
|
||||
final OnClickListener cancelListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
ApplicationMigrationService.setDatabaseImported(context);
|
||||
}
|
||||
};
|
||||
setOkListener(okListener);
|
||||
setDismissListener(cancelListener);
|
||||
}
|
||||
|
||||
public static boolean isEligible(Context context) {
|
||||
return !ApplicationMigrationService.isDatabaseImported(context);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,6 @@ public class UnauthorizedReminder extends Reminder {
|
||||
}
|
||||
|
||||
public static boolean isEligible(Context context) {
|
||||
return TextSecurePreferences.isUnauthorizedRecieved(context);
|
||||
return TextSecurePreferences.isUnauthorizedReceived(context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ class AppSettingsActivity : DSLSettingsActivity(), DonationPaymentComponent {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
googlePayResultPublisher.onNext(DonationPaymentComponent.GooglePayResult(requestCode, resultCode, data))
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.signal.libsignal.protocol.util.KeyHelper
|
||||
import org.signal.libsignal.protocol.util.Medium
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
import org.thoughtcrime.securesms.crypto.PreKeyUtil
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase
|
||||
import org.thoughtcrime.securesms.database.IdentityTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingChangeNumberMetadata
|
||||
import org.thoughtcrime.securesms.database.model.toProtoByteString
|
||||
@@ -237,7 +237,7 @@ class ChangeNumberRepository(
|
||||
pniProtocolStore.identities().saveIdentityWithoutSideEffects(
|
||||
Recipient.self().id,
|
||||
pniProtocolStore.identityKeyPair.publicKey,
|
||||
IdentityDatabase.VerifiedStatus.VERIFIED,
|
||||
IdentityTable.VerifiedStatus.VERIFIED,
|
||||
true,
|
||||
System.currentTimeMillis(),
|
||||
true
|
||||
|
||||
@@ -15,7 +15,6 @@ import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.exporter.flow.SmsExportActivity
|
||||
import org.thoughtcrime.securesms.exporter.flow.SmsExportDialogs
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
@@ -114,16 +113,14 @@ class ChatsSettingsFragment : DSLSettingsFragment(R.string.preferences_chats__ch
|
||||
}
|
||||
)
|
||||
|
||||
if (FeatureFlags.keepMutedChatsArchived() || FeatureFlags.internalUser()) {
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__pref_keep_muted_chats_archived),
|
||||
summary = DSLSettingsText.from(R.string.preferences__muted_chats_that_are_archived_will_remain_archived),
|
||||
isChecked = state.keepMutedChatsArchived,
|
||||
onClick = {
|
||||
viewModel.setKeepMutedChatsArchived(!state.keepMutedChatsArchived)
|
||||
}
|
||||
)
|
||||
}
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__pref_keep_muted_chats_archived),
|
||||
summary = DSLSettingsText.from(R.string.preferences__muted_chats_that_are_archived_will_remain_archived),
|
||||
isChecked = state.keepMutedChatsArchived,
|
||||
onClick = {
|
||||
viewModel.setKeepMutedChatsArchived(!state.keepMutedChatsArchived)
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ package org.thoughtcrime.securesms.components.settings.app.chats.sms
|
||||
import androidx.annotation.WorkerThread
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.thoughtcrime.securesms.database.MessageDatabase
|
||||
import org.thoughtcrime.securesms.database.MessageTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
|
||||
class SmsSettingsRepository(
|
||||
private val smsDatabase: MessageDatabase = SignalDatabase.sms,
|
||||
private val mmsDatabase: MessageDatabase = SignalDatabase.mms
|
||||
private val smsDatabase: MessageTable = SignalDatabase.messages,
|
||||
private val mmsDatabase: MessageTable = SignalDatabase.messages
|
||||
) {
|
||||
fun getSmsExportState(): Single<SmsExportState> {
|
||||
if (!FeatureFlags.smsExporter()) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.JobTracker
|
||||
import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob
|
||||
import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob
|
||||
import org.thoughtcrime.securesms.jobs.PnpInitializeDevicesJob
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
|
||||
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob
|
||||
import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob
|
||||
@@ -43,6 +44,7 @@ import org.thoughtcrime.securesms.megaphone.Megaphones
|
||||
import org.thoughtcrime.securesms.payments.DataExportUtil
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.thoughtcrime.securesms.util.ConversationUtil
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
import java.util.Optional
|
||||
@@ -357,11 +359,11 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
||||
sectionHeaderPref(DSLSettingsText.from("Group call server"))
|
||||
|
||||
radioPref(
|
||||
title = DSLSettingsText.from("Default"),
|
||||
title = DSLSettingsText.from("Production server"),
|
||||
summary = DSLSettingsText.from(BuildConfig.SIGNAL_SFU_URL),
|
||||
isChecked = state.callingServer == BuildConfig.SIGNAL_SFU_URL,
|
||||
onClick = {
|
||||
viewModel.setInternalGroupCallingServer(null)
|
||||
viewModel.setInternalGroupCallingServer(BuildConfig.SIGNAL_SFU_URL)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -430,6 +432,16 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
||||
findNavController().safeNavigate(InternalSettingsFragmentDirections.actionInternalSettingsFragmentToDonorErrorConfigurationFragment())
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from("Clear keep-alive timestamps"),
|
||||
onClick = {
|
||||
SignalStore.donationsValues().subscriptionEndOfPeriodRedemptionStarted = 0L
|
||||
SignalStore.donationsValues().subscriptionEndOfPeriodConversionStarted = 0L
|
||||
SignalStore.donationsValues().setLastEndOfPeriod(0L)
|
||||
Toast.makeText(context, "Cleared", Toast.LENGTH_SHORT)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
dividerPref()
|
||||
@@ -534,6 +546,48 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
||||
findNavController().safeNavigate(InternalSettingsFragmentDirections.actionInternalSettingsFragmentToStoryDialogsLauncherFragment())
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
|
||||
sectionHeaderPref(DSLSettingsText.from("PNP"))
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from("Trigger No-Op Change Number"),
|
||||
summary = DSLSettingsText.from("Mimics the 'Hello world' event"),
|
||||
isEnabled = true,
|
||||
onClick = {
|
||||
SimpleTask.run(viewLifecycleOwner.lifecycle, {
|
||||
ApplicationDependencies.getJobManager().runSynchronously(PnpInitializeDevicesJob(), 10.seconds.inWholeMilliseconds)
|
||||
}, { state ->
|
||||
if (state.isPresent) {
|
||||
Toast.makeText(context, "Job finished with result: ${state.get()}!", Toast.LENGTH_SHORT).show()
|
||||
viewModel.refresh()
|
||||
} else {
|
||||
Toast.makeText(context, "Job timed out after 10 seconds!", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from("Reset 'PNP initialized' state"),
|
||||
summary = DSLSettingsText.from("Current initialized state: ${state.pnpInitialized}"),
|
||||
isEnabled = state.pnpInitialized,
|
||||
onClick = {
|
||||
viewModel.resetPnpInitializedState()
|
||||
}
|
||||
)
|
||||
|
||||
if (FeatureFlags.chatFilters()) {
|
||||
dividerPref()
|
||||
sectionHeaderPref(DSLSettingsText.from("Chat Filters"))
|
||||
clickPref(
|
||||
title = DSLSettingsText.from("Reset pull to refresh tip count"),
|
||||
onClick = {
|
||||
SignalStore.uiHints().resetNeverDisplayPullToRefreshCount()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.components.settings.app.internal
|
||||
|
||||
import android.content.Context
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.thoughtcrime.securesms.database.MessageDatabase
|
||||
import org.thoughtcrime.securesms.database.MessageTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.addStyle
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
|
||||
@@ -38,17 +38,17 @@ class InternalSettingsRepository(context: Context) {
|
||||
val recipientId = SignalStore.releaseChannelValues().releaseChannelRecipientId!!
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(recipientId))
|
||||
|
||||
val insertResult: MessageDatabase.InsertResult? = ReleaseChannel.insertReleaseChannelMessage(
|
||||
val insertResult: MessageTable.InsertResult? = ReleaseChannel.insertReleaseChannelMessage(
|
||||
recipientId = recipientId,
|
||||
body = body,
|
||||
threadId = threadId,
|
||||
messageRanges = bodyRangeList.build(),
|
||||
image = "/static/release-notes/signal.png",
|
||||
imageWidth = 1800,
|
||||
imageHeight = 720
|
||||
media = "/static/release-notes/signal.png",
|
||||
mediaWidth = 1800,
|
||||
mediaHeight = 720
|
||||
)
|
||||
|
||||
SignalDatabase.sms.insertBoostRequestMessage(recipientId, threadId)
|
||||
SignalDatabase.messages.insertBoostRequestMessage(recipientId, threadId)
|
||||
|
||||
if (insertResult != null) {
|
||||
SignalDatabase.attachments.getAttachmentsForMessage(insertResult.messageId)
|
||||
|
||||
@@ -20,5 +20,6 @@ data class InternalSettingsState(
|
||||
val removeSenderKeyMinimium: Boolean,
|
||||
val delayResends: Boolean,
|
||||
val disableStorageService: Boolean,
|
||||
val canClearOnboardingState: Boolean
|
||||
val canClearOnboardingState: Boolean,
|
||||
val pnpInitialized: Boolean
|
||||
)
|
||||
|
||||
@@ -64,6 +64,11 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun resetPnpInitializedState() {
|
||||
SignalStore.misc().setPniInitializedDevices(false)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setUseBuiltInEmoji(enabled: Boolean) {
|
||||
preferenceDataStore.putBoolean(InternalValues.FORCE_BUILT_IN_EMOJI, enabled)
|
||||
refresh()
|
||||
@@ -103,7 +108,7 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito
|
||||
repository.addSampleReleaseNote()
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
fun refresh() {
|
||||
store.update { getState().copy(emojiVersion = it.emojiVersion) }
|
||||
}
|
||||
|
||||
@@ -124,7 +129,8 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito
|
||||
removeSenderKeyMinimium = SignalStore.internalValues().removeSenderKeyMinimum(),
|
||||
delayResends = SignalStore.internalValues().delayResends(),
|
||||
disableStorageService = SignalStore.internalValues().storageServiceDisabled(),
|
||||
canClearOnboardingState = SignalStore.storyValues().hasDownloadedOnboardingStory && Stories.isFeatureEnabled()
|
||||
canClearOnboardingState = SignalStore.storyValues().hasDownloadedOnboardingStory && Stories.isFeatureEnabled(),
|
||||
pnpInitialized = SignalStore.misc().hasPniInitializedDevices()
|
||||
)
|
||||
|
||||
fun onClearOnboardingState() {
|
||||
|
||||
@@ -11,6 +11,9 @@ import org.signal.donations.StripeDeclineCode
|
||||
import org.thoughtcrime.securesms.badges.Badges
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.UnexpectedSubscriptionCancellation
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.getBoostBadges
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.getGiftBadges
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.getSubscriptionLevels
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobs.SubscriptionReceiptRequestResponseJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
@@ -29,28 +32,28 @@ class InternalDonorErrorConfigurationViewModel : ViewModel() {
|
||||
val giftBadges: Single<List<Badge>> = Single
|
||||
.fromCallable {
|
||||
ApplicationDependencies.getDonationsService()
|
||||
.getGiftBadges(Locale.getDefault())
|
||||
.getDonationsConfiguration(Locale.getDefault())
|
||||
}
|
||||
.flatMap { it.flattenResult() }
|
||||
.map { results -> results.values.map { Badges.fromServiceBadge(it) } }
|
||||
.map { it.getGiftBadges() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
|
||||
val boostBadges: Single<List<Badge>> = Single
|
||||
.fromCallable {
|
||||
ApplicationDependencies.getDonationsService()
|
||||
.getBoostBadge(Locale.getDefault())
|
||||
.getDonationsConfiguration(Locale.getDefault())
|
||||
}
|
||||
.flatMap { it.flattenResult() }
|
||||
.map { listOf(Badges.fromServiceBadge(it)) }
|
||||
.map { it.getBoostBadges() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
|
||||
val subscriptionBadges: Single<List<Badge>> = Single
|
||||
.fromCallable {
|
||||
ApplicationDependencies.getDonationsService()
|
||||
.getSubscriptionLevels(Locale.getDefault())
|
||||
.getDonationsConfiguration(Locale.getDefault())
|
||||
}
|
||||
.flatMap { it.flattenResult() }
|
||||
.map { levels -> levels.levels.values.map { Badges.fromServiceBadge(it.badge) } }
|
||||
.map { config -> config.getSubscriptionLevels().values.map { Badges.fromServiceBadge(it.badge) } }
|
||||
.subscribeOn(Schedulers.io())
|
||||
|
||||
disposables += Single.zip(giftBadges, boostBadges, subscriptionBadges) { g, b, s ->
|
||||
|
||||
@@ -104,7 +104,7 @@ class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__
|
||||
summary = DSLSettingsText.from(R.string.preferences__change_sound_and_vibration),
|
||||
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
||||
onClick = {
|
||||
NotificationChannels.openChannelSettings(requireContext(), NotificationChannels.getMessagesChannel(requireContext()), null)
|
||||
NotificationChannels.getInstance().openChannelSettings(requireActivity(), NotificationChannels.getInstance().messagesChannel, null)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
@@ -301,7 +301,7 @@ class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__
|
||||
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
||||
intent.putExtra(
|
||||
Settings.EXTRA_CHANNEL_ID,
|
||||
NotificationChannels.getMessagesChannel(requireContext())
|
||||
NotificationChannels.getInstance().messagesChannel
|
||||
)
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
|
||||
startActivity(intent)
|
||||
|
||||
@@ -16,8 +16,8 @@ class NotificationsSettingsViewModel(private val sharedPreferences: SharedPrefer
|
||||
|
||||
init {
|
||||
if (NotificationChannels.supported()) {
|
||||
SignalStore.settings().messageNotificationSound = NotificationChannels.getMessageRingtone(ApplicationDependencies.getApplication())
|
||||
SignalStore.settings().isMessageVibrateEnabled = NotificationChannels.getMessageVibrate(ApplicationDependencies.getApplication())
|
||||
SignalStore.settings().messageNotificationSound = NotificationChannels.getInstance().messageRingtone
|
||||
SignalStore.settings().isMessageVibrateEnabled = NotificationChannels.getInstance().messageVibrate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,19 +33,19 @@ class NotificationsSettingsViewModel(private val sharedPreferences: SharedPrefer
|
||||
fun setMessageNotificationsSound(sound: Uri?) {
|
||||
val messageSound = sound ?: Uri.EMPTY
|
||||
SignalStore.settings().messageNotificationSound = messageSound
|
||||
NotificationChannels.updateMessageRingtone(ApplicationDependencies.getApplication(), messageSound)
|
||||
NotificationChannels.getInstance().updateMessageRingtone(messageSound)
|
||||
store.update { getState() }
|
||||
}
|
||||
|
||||
fun setMessageNotificationVibration(enabled: Boolean) {
|
||||
SignalStore.settings().isMessageVibrateEnabled = enabled
|
||||
NotificationChannels.updateMessageVibrate(ApplicationDependencies.getApplication(), enabled)
|
||||
NotificationChannels.getInstance().updateMessageVibrate(enabled)
|
||||
store.update { getState() }
|
||||
}
|
||||
|
||||
fun setMessageNotificationLedColor(color: String) {
|
||||
SignalStore.settings().messageLedColor = color
|
||||
NotificationChannels.updateMessagesLedColor(ApplicationDependencies.getApplication(), color)
|
||||
NotificationChannels.getInstance().updateMessagesLedColor(color)
|
||||
store.update { getState() }
|
||||
}
|
||||
|
||||
|
||||
@@ -344,7 +344,7 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
|
||||
if (!ServiceUtil.getKeyguardManager(requireContext()).isKeyguardSecure) {
|
||||
showGoToPhoneSettings()
|
||||
} else if (state.paymentLock) {
|
||||
biometricAuth.authenticate(requireContext(), true) { biometricDeviceLockLauncher?.launch(getString(R.string.BiometricDeviceAuthentication__signal)) }
|
||||
biometricAuth.authenticate(requireContext(), true) { biometricDeviceLockLauncher.launch(getString(R.string.BiometricDeviceAuthentication__signal)) }
|
||||
} else {
|
||||
viewModel.togglePaymentLock(true)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.privacy.advanced
|
||||
|
||||
import android.app.ProgressDialog
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@@ -18,6 +17,7 @@ import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.SignalProgressDialog
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
@@ -48,7 +48,7 @@ class AdvancedPrivacySettingsFragment : DSLSettingsFragment(R.string.preferences
|
||||
}
|
||||
}
|
||||
|
||||
var progressDialog: ProgressDialog? = null
|
||||
var progressDialog: SignalProgressDialog? = null
|
||||
|
||||
val statusIcon: CharSequence by lazy {
|
||||
val unidentifiedDeliveryIcon = requireNotNull(
|
||||
@@ -85,7 +85,7 @@ class AdvancedPrivacySettingsFragment : DSLSettingsFragment(R.string.preferences
|
||||
viewModel.state.observe(viewLifecycleOwner) {
|
||||
if (it.showProgressSpinner) {
|
||||
if (progressDialog?.isShowing == false) {
|
||||
progressDialog = ProgressDialog.show(requireContext(), null, null, true)
|
||||
progressDialog = SignalProgressDialog.show(requireContext(), null, null, true)
|
||||
}
|
||||
} else {
|
||||
progressDialog?.hide()
|
||||
|
||||
@@ -5,11 +5,11 @@ import androidx.annotation.WorkerThread
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadTable
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeException
|
||||
import org.thoughtcrime.securesms.groups.GroupManager
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.sms.MessageSender
|
||||
@@ -39,7 +39,7 @@ class ExpireTimerSettingsRepository(val context: Context) {
|
||||
}
|
||||
} else {
|
||||
SignalDatabase.recipients.setExpireMessages(recipientId, newExpirationTime)
|
||||
val outgoingMessage = OutgoingExpirationUpdateMessage(Recipient.resolved(recipientId), System.currentTimeMillis(), newExpirationTime * 1000L)
|
||||
val outgoingMessage = OutgoingMessage.expirationUpdateMessage(Recipient.resolved(recipientId), System.currentTimeMillis(), newExpirationTime * 1000L)
|
||||
MessageSender.send(context, outgoingMessage, getThreadId(recipientId), false, null, null)
|
||||
consumer.invoke(Result.success(newExpirationTime))
|
||||
}
|
||||
@@ -57,8 +57,8 @@ class ExpireTimerSettingsRepository(val context: Context) {
|
||||
|
||||
@WorkerThread
|
||||
private fun getThreadId(recipientId: RecipientId): Long {
|
||||
val threadDatabase: ThreadDatabase = SignalDatabase.threads
|
||||
val threadTable: ThreadTable = SignalDatabase.threads
|
||||
val recipient: Recipient = Recipient.resolved(recipientId)
|
||||
return threadDatabase.getOrCreateThreadIdFor(recipient)
|
||||
return threadTable.getOrCreateThreadIdFor(recipient)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.subscription
|
||||
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.signal.core.util.money.PlatformCurrencyUtil
|
||||
import org.thoughtcrime.securesms.badges.Badges
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.whispersystems.signalservice.internal.push.DonationsConfiguration
|
||||
import org.whispersystems.signalservice.internal.push.DonationsConfiguration.BOOST_LEVEL
|
||||
import org.whispersystems.signalservice.internal.push.DonationsConfiguration.GIFT_LEVEL
|
||||
import org.whispersystems.signalservice.internal.push.DonationsConfiguration.LevelConfiguration
|
||||
import org.whispersystems.signalservice.internal.push.DonationsConfiguration.SUBSCRIPTION_LEVELS
|
||||
import java.math.BigDecimal
|
||||
import java.util.Currency
|
||||
|
||||
private const val CARD = "CARD"
|
||||
private const val PAYPAL = "PAYPAL"
|
||||
|
||||
/**
|
||||
* Transforms the DonationsConfiguration into a Set<FiatMoney> which has been properly filtered
|
||||
* for available currencies on the platform and based off user device availability.
|
||||
*
|
||||
* CARD - Google Pay & Credit Card
|
||||
* PAYPAL - PayPal
|
||||
*
|
||||
* @param level The subscription level to get amounts for
|
||||
* @param paymentMethodAvailability Predicate object which checks whether different payment methods are availble.
|
||||
*/
|
||||
fun DonationsConfiguration.getSubscriptionAmounts(
|
||||
level: Int,
|
||||
paymentMethodAvailability: PaymentMethodAvailability = DefaultPaymentMethodAvailability
|
||||
): Set<FiatMoney> {
|
||||
require(SUBSCRIPTION_LEVELS.contains(level))
|
||||
|
||||
return getFilteredCurrencies(paymentMethodAvailability).map { (code, config) ->
|
||||
val amount: BigDecimal = config.subscription[level]!!
|
||||
FiatMoney(amount, Currency.getInstance(code.uppercase()))
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently, we only support a single gift badge at level GIFT_LEVEL
|
||||
*/
|
||||
fun DonationsConfiguration.getGiftBadges(): List<Badge> {
|
||||
val configuration = levels[GIFT_LEVEL]
|
||||
return listOfNotNull(configuration?.badge?.let { Badges.fromServiceBadge(it) })
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently, we only support a single gift badge amount per currency
|
||||
*/
|
||||
fun DonationsConfiguration.getGiftBadgeAmounts(paymentMethodAvailability: PaymentMethodAvailability = DefaultPaymentMethodAvailability): Map<Currency, FiatMoney> {
|
||||
return getFilteredCurrencies(paymentMethodAvailability).filter {
|
||||
it.value.oneTime[GIFT_LEVEL]?.isNotEmpty() == true
|
||||
}.mapKeys {
|
||||
Currency.getInstance(it.key.uppercase())
|
||||
}.mapValues {
|
||||
FiatMoney(it.value.oneTime[GIFT_LEVEL]!!.first(), it.key)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently, we only support a single boost badge at level BOOST_LEVEL
|
||||
*/
|
||||
fun DonationsConfiguration.getBoostBadges(): List<Badge> {
|
||||
val configuration = levels[BOOST_LEVEL]
|
||||
return listOfNotNull(configuration?.badge?.let { Badges.fromServiceBadge(it) })
|
||||
}
|
||||
|
||||
fun DonationsConfiguration.getBoostAmounts(paymentMethodAvailability: PaymentMethodAvailability = DefaultPaymentMethodAvailability): Map<Currency, List<FiatMoney>> {
|
||||
return getFilteredCurrencies(paymentMethodAvailability).filter {
|
||||
it.value.oneTime[BOOST_LEVEL]?.isNotEmpty() == true
|
||||
}.mapKeys {
|
||||
Currency.getInstance(it.key.uppercase())
|
||||
}.mapValues { (currency, config) ->
|
||||
config.oneTime[BOOST_LEVEL]!!.map { FiatMoney(it, currency) }
|
||||
}
|
||||
}
|
||||
|
||||
fun DonationsConfiguration.getBadge(level: Int): Badge {
|
||||
require(level == GIFT_LEVEL || level == BOOST_LEVEL || SUBSCRIPTION_LEVELS.contains(level))
|
||||
return Badges.fromServiceBadge(levels[level]!!.badge)
|
||||
}
|
||||
|
||||
fun DonationsConfiguration.getSubscriptionLevels(): Map<Int, LevelConfiguration> {
|
||||
return levels.filterKeys { SUBSCRIPTION_LEVELS.contains(it) }.toSortedMap()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a map describing the minimum donation amounts per currency.
|
||||
* This returns only the currencies available to the user.
|
||||
*/
|
||||
fun DonationsConfiguration.getMinimumDonationAmounts(paymentMethodAvailability: PaymentMethodAvailability = DefaultPaymentMethodAvailability): Map<Currency, FiatMoney> {
|
||||
return getFilteredCurrencies(paymentMethodAvailability)
|
||||
.mapKeys { Currency.getInstance(it.key.uppercase()) }
|
||||
.mapValues { FiatMoney(it.value.minimum, it.key) }
|
||||
}
|
||||
|
||||
fun DonationsConfiguration.getAvailablePaymentMethods(currencyCode: String): Set<String> {
|
||||
return currencies[currencyCode.lowercase()]?.supportedPaymentMethods ?: emptySet()
|
||||
}
|
||||
|
||||
private fun DonationsConfiguration.getFilteredCurrencies(paymentMethodAvailability: PaymentMethodAvailability): Map<String, DonationsConfiguration.CurrencyConfiguration> {
|
||||
val userPaymentMethods = paymentMethodAvailability.toSet()
|
||||
val availableCurrencyCodes = PlatformCurrencyUtil.getAvailableCurrencyCodes()
|
||||
return currencies.filter { (code, config) ->
|
||||
val areAllMethodsAvailable = config.supportedPaymentMethods.any { it in userPaymentMethods }
|
||||
availableCurrencyCodes.contains(code.uppercase()) && areAllMethodsAvailable
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface is available to ease unit testing of the extension methods in
|
||||
* this file. In all normal situations, you can just allow the methods to use the
|
||||
* default value.
|
||||
*/
|
||||
interface PaymentMethodAvailability {
|
||||
fun isPayPalAvailable(): Boolean
|
||||
fun isGooglePayOrCreditCardAvailable(): Boolean
|
||||
|
||||
fun toSet(): Set<String> {
|
||||
val set = mutableSetOf<String>()
|
||||
if (isPayPalAvailable()) {
|
||||
set.add(PAYPAL)
|
||||
}
|
||||
|
||||
if (isGooglePayOrCreditCardAvailable()) {
|
||||
set.add(CARD)
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
}
|
||||
|
||||
private object DefaultPaymentMethodAvailability : PaymentMethodAvailability {
|
||||
override fun isPayPalAvailable(): Boolean = InAppDonations.isPayPalAvailable()
|
||||
override fun isGooglePayOrCreditCardAvailable(): Boolean = InAppDonations.isCreditCardAvailable() || InAppDonations.isGooglePayAvailable()
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.subscription
|
||||
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonateToSignalType
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.LocaleFeatureFlags
|
||||
import org.thoughtcrime.securesms.util.PlayServicesUtil
|
||||
|
||||
/**
|
||||
* Helper object to determine in-app donations availability.
|
||||
@@ -21,6 +22,22 @@ object InAppDonations {
|
||||
return isCreditCardAvailable() || isPayPalAvailable() || isGooglePayAvailable()
|
||||
}
|
||||
|
||||
fun isPaymentSourceAvailable(paymentSourceType: PaymentSourceType, donateToSignalType: DonateToSignalType): Boolean {
|
||||
return when (paymentSourceType) {
|
||||
PaymentSourceType.PayPal -> isPayPalAvailableForDonateToSignalType(donateToSignalType)
|
||||
PaymentSourceType.Stripe.CreditCard -> isCreditCardAvailable()
|
||||
PaymentSourceType.Stripe.GooglePay -> isGooglePayAvailable()
|
||||
PaymentSourceType.Unknown -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun isPayPalAvailableForDonateToSignalType(donateToSignalType: DonateToSignalType): Boolean {
|
||||
return when (donateToSignalType) {
|
||||
DonateToSignalType.ONE_TIME, DonateToSignalType.GIFT -> FeatureFlags.paypalOneTimeDonations()
|
||||
DonateToSignalType.MONTHLY -> FeatureFlags.paypalRecurringDonations()
|
||||
} && !LocaleFeatureFlags.isPayPalDisabled()
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the user is in a region that supports credit cards, based off local phone number.
|
||||
*/
|
||||
@@ -32,21 +49,13 @@ object InAppDonations {
|
||||
* Whether the user is in a region that supports PayPal, based off local phone number.
|
||||
*/
|
||||
fun isPayPalAvailable(): Boolean {
|
||||
return false
|
||||
return (FeatureFlags.paypalOneTimeDonations() || FeatureFlags.paypalRecurringDonations()) && !LocaleFeatureFlags.isPayPalDisabled()
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the user is in a region that supports GooglePay, based off local phone number.
|
||||
* Whether the user is using a device that supports GooglePay, based off Wallet API and phone number.
|
||||
*/
|
||||
private fun isGooglePayAvailable(): Boolean {
|
||||
return isPlayServicesAvailable() && !LocaleFeatureFlags.isGooglePayDisabled()
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether Play Services is available. This will *not* tell you whether a user has Google Pay set up, but is
|
||||
* enough information to determine whether we can display Google Pay as an option.
|
||||
*/
|
||||
private fun isPlayServicesAvailable(): Boolean {
|
||||
return PlayServicesUtil.getPlayServicesStatus(ApplicationDependencies.getApplication()) == PlayServicesUtil.PlayServicesStatus.SUCCESS
|
||||
fun isGooglePayAvailable(): Boolean {
|
||||
return SignalStore.donationsValues().isGooglePayReady && !LocaleFeatureFlags.isGooglePayDisabled()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.thoughtcrime.securesms.badges.Badges
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource
|
||||
@@ -20,15 +19,12 @@ import org.thoughtcrime.securesms.subscription.LevelUpdate
|
||||
import org.thoughtcrime.securesms.subscription.LevelUpdateOperation
|
||||
import org.thoughtcrime.securesms.subscription.Subscriber
|
||||
import org.thoughtcrime.securesms.subscription.Subscription
|
||||
import org.thoughtcrime.securesms.util.PlatformCurrencyUtil
|
||||
import org.whispersystems.signalservice.api.services.DonationsService
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
|
||||
import org.whispersystems.signalservice.api.subscriptions.IdempotencyKey
|
||||
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
|
||||
import org.whispersystems.signalservice.api.subscriptions.SubscriptionLevels
|
||||
import org.whispersystems.signalservice.internal.EmptyResponse
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse
|
||||
import java.util.Currency
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
@@ -52,29 +48,23 @@ class MonthlyDonationRepository(private val donationsService: DonationsService)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSubscriptions(): Single<List<Subscription>> = Single
|
||||
.fromCallable { donationsService.getSubscriptionLevels(Locale.getDefault()) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.flatMap(ServiceResponse<SubscriptionLevels>::flattenResult)
|
||||
.map { subscriptionLevels ->
|
||||
subscriptionLevels.levels.map { (code, level) ->
|
||||
Subscription(
|
||||
id = code,
|
||||
name = level.name,
|
||||
badge = Badges.fromServiceBadge(level.badge),
|
||||
prices = level.currencies.filter {
|
||||
PlatformCurrencyUtil
|
||||
.getAvailableCurrencyCodes()
|
||||
.contains(it.key)
|
||||
}.map { (currencyCode, price) ->
|
||||
FiatMoney(price, Currency.getInstance(currencyCode))
|
||||
}.toSet(),
|
||||
level = code.toInt()
|
||||
)
|
||||
}.sortedBy {
|
||||
it.level
|
||||
fun getSubscriptions(): Single<List<Subscription>> {
|
||||
return Single
|
||||
.fromCallable { donationsService.getDonationsConfiguration(Locale.getDefault()) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.flatMap { it.flattenResult() }
|
||||
.map { config ->
|
||||
config.getSubscriptionLevels().map { (level, levelConfig) ->
|
||||
Subscription(
|
||||
id = level.toString(),
|
||||
level = level,
|
||||
name = levelConfig.name,
|
||||
badge = Badges.fromServiceBadge(levelConfig.badge),
|
||||
prices = config.getSubscriptionAmounts(level)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun syncAccountRecord(): Completable {
|
||||
return Completable.fromAction {
|
||||
|
||||
@@ -5,7 +5,7 @@ import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.thoughtcrime.securesms.badges.Badges
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.boost.Boost
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
|
||||
@@ -17,11 +17,8 @@ import org.thoughtcrime.securesms.jobmanager.JobTracker
|
||||
import org.thoughtcrime.securesms.jobs.BoostReceiptRequestResponseJob
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.PlatformCurrencyUtil
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
|
||||
import org.whispersystems.signalservice.api.services.DonationsService
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse
|
||||
import java.math.BigDecimal
|
||||
import org.whispersystems.signalservice.internal.push.DonationProcessor
|
||||
import java.util.Currency
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.CountDownLatch
|
||||
@@ -31,17 +28,28 @@ class OneTimeDonationRepository(private val donationsService: DonationsService)
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(OneTimeDonationRepository::class.java)
|
||||
|
||||
fun <T : Any> handleCreatePaymentIntentError(throwable: Throwable, badgeRecipient: RecipientId, paymentSourceType: PaymentSourceType): Single<T> {
|
||||
return if (throwable is DonationError) {
|
||||
Single.error(throwable)
|
||||
} else {
|
||||
val recipient = Recipient.resolved(badgeRecipient)
|
||||
val errorSource = if (recipient.isSelf) DonationErrorSource.BOOST else DonationErrorSource.GIFT
|
||||
Single.error(DonationError.getPaymentSetupError(errorSource, throwable, paymentSourceType))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getBoosts(): Single<Map<Currency, List<Boost>>> {
|
||||
return Single.fromCallable { donationsService.boostAmounts }
|
||||
return Single.fromCallable { donationsService.getDonationsConfiguration(Locale.getDefault()) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.flatMap(ServiceResponse<Map<String, List<BigDecimal>>>::flattenResult)
|
||||
.map { result ->
|
||||
result
|
||||
.filter { PlatformCurrencyUtil.getAvailableCurrencyCodes().contains(it.key) }
|
||||
.mapKeys { (code, _) -> Currency.getInstance(code) }
|
||||
.mapValues { (currency, prices) -> prices.map { Boost(FiatMoney(it, currency)) } }
|
||||
.flatMap { it.flattenResult() }
|
||||
.map { config ->
|
||||
config.getBoostAmounts().mapValues { (_, value) ->
|
||||
value.map {
|
||||
Boost(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,11 +57,18 @@ class OneTimeDonationRepository(private val donationsService: DonationsService)
|
||||
return Single
|
||||
.fromCallable {
|
||||
ApplicationDependencies.getDonationsService()
|
||||
.getBoostBadge(Locale.getDefault())
|
||||
.getDonationsConfiguration(Locale.getDefault())
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.flatMap(ServiceResponse<SignalServiceProfile.Badge>::flattenResult)
|
||||
.map(Badges::fromServiceBadge)
|
||||
.flatMap { it.flattenResult() }
|
||||
.map { it.getBoostBadges().first() }
|
||||
}
|
||||
|
||||
fun getMinimumDonationAmounts(): Single<Map<Currency, FiatMoney>> {
|
||||
return Single.fromCallable { donationsService.getDonationsConfiguration(Locale.getDefault()) }
|
||||
.flatMap { it.flattenResult() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.map { it.getMinimumDonationAmounts() }
|
||||
}
|
||||
|
||||
fun waitForOneTimeRedemption(
|
||||
@@ -62,6 +77,7 @@ class OneTimeDonationRepository(private val donationsService: DonationsService)
|
||||
badgeRecipient: RecipientId,
|
||||
additionalMessage: String?,
|
||||
badgeLevel: Long,
|
||||
donationProcessor: DonationProcessor
|
||||
): Completable {
|
||||
val isBoost = badgeRecipient == Recipient.self().id
|
||||
val donationErrorSource: DonationErrorSource = if (isBoost) DonationErrorSource.BOOST else DonationErrorSource.GIFT
|
||||
@@ -81,9 +97,9 @@ class OneTimeDonationRepository(private val donationsService: DonationsService)
|
||||
val countDownLatch = CountDownLatch(1)
|
||||
var finalJobState: JobTracker.JobState? = null
|
||||
val chain = if (isBoost) {
|
||||
BoostReceiptRequestResponseJob.createJobChainForBoost(paymentIntentId)
|
||||
BoostReceiptRequestResponseJob.createJobChainForBoost(paymentIntentId, donationProcessor)
|
||||
} else {
|
||||
BoostReceiptRequestResponseJob.createJobChainForGift(paymentIntentId, badgeRecipient, additionalMessage, badgeLevel)
|
||||
BoostReceiptRequestResponseJob.createJobChainForGift(paymentIntentId, badgeRecipient, additionalMessage, badgeLevel, donationProcessor)
|
||||
}
|
||||
|
||||
chain.enqueue { _, jobState ->
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.subscription
|
||||
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.paypal.PayPalConfirmationResult
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.api.services.DonationsService
|
||||
import org.whispersystems.signalservice.api.subscriptions.PayPalConfirmPaymentIntentResponse
|
||||
import org.whispersystems.signalservice.api.subscriptions.PayPalCreatePaymentIntentResponse
|
||||
import org.whispersystems.signalservice.api.subscriptions.PayPalCreatePaymentMethodResponse
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Repository that deals directly with PayPal API calls. Since we don't interact with the PayPal APIs directly (yet)
|
||||
* we can do everything here in one place.
|
||||
*/
|
||||
class PayPalRepository(private val donationsService: DonationsService) {
|
||||
|
||||
companion object {
|
||||
const val ONE_TIME_RETURN_URL = "https://signaldonations.org/return/onetime"
|
||||
const val MONTHLY_RETURN_URL = "https://signaldonations.org/return/monthly"
|
||||
const val CANCEL_URL = "https://signaldonations.org/cancel"
|
||||
|
||||
private val TAG = Log.tag(PayPalRepository::class.java)
|
||||
}
|
||||
|
||||
fun createOneTimePaymentIntent(
|
||||
amount: FiatMoney,
|
||||
badgeRecipient: RecipientId,
|
||||
badgeLevel: Long
|
||||
): Single<PayPalCreatePaymentIntentResponse> {
|
||||
return Single.fromCallable {
|
||||
donationsService
|
||||
.createPayPalOneTimePaymentIntent(
|
||||
Locale.getDefault(),
|
||||
amount.currency.currencyCode,
|
||||
amount.minimumUnitPrecisionString,
|
||||
badgeLevel,
|
||||
ONE_TIME_RETURN_URL,
|
||||
CANCEL_URL
|
||||
)
|
||||
}
|
||||
.flatMap { it.flattenResult() }
|
||||
.onErrorResumeNext { OneTimeDonationRepository.handleCreatePaymentIntentError(it, badgeRecipient, PaymentSourceType.PayPal) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun confirmOneTimePaymentIntent(
|
||||
amount: FiatMoney,
|
||||
badgeLevel: Long,
|
||||
paypalConfirmationResult: PayPalConfirmationResult
|
||||
): Single<PayPalConfirmPaymentIntentResponse> {
|
||||
return Single.fromCallable {
|
||||
Log.d(TAG, "Confirming one-time payment intent...", true)
|
||||
donationsService
|
||||
.confirmPayPalOneTimePaymentIntent(
|
||||
amount.currency.currencyCode,
|
||||
amount.minimumUnitPrecisionString,
|
||||
badgeLevel,
|
||||
paypalConfirmationResult.payerId,
|
||||
paypalConfirmationResult.paymentId,
|
||||
paypalConfirmationResult.paymentToken
|
||||
)
|
||||
}.flatMap { it.flattenResult() }.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun createPaymentMethod(): Single<PayPalCreatePaymentMethodResponse> {
|
||||
return Single.fromCallable {
|
||||
donationsService.createPayPalPaymentMethod(
|
||||
Locale.getDefault(),
|
||||
SignalStore.donationsValues().requireSubscriber().subscriberId,
|
||||
MONTHLY_RETURN_URL,
|
||||
CANCEL_URL
|
||||
)
|
||||
}.flatMap { it.flattenResult() }.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun setDefaultPaymentMethod(paymentMethodId: String): Completable {
|
||||
return Single.fromCallable {
|
||||
Log.d(TAG, "Setting default payment method...", true)
|
||||
donationsService.setDefaultPayPalPaymentMethod(
|
||||
SignalStore.donationsValues().requireSubscriber().subscriberId,
|
||||
paymentMethodId
|
||||
)
|
||||
}.flatMap { it.flattenResult() }.ignoreElement().doOnComplete {
|
||||
Log.d(TAG, "Set default payment method.", true)
|
||||
Log.d(TAG, "Storing the subscription payment source type locally.", true)
|
||||
SignalStore.donationsValues().setSubscriptionPaymentSourceType(PaymentSourceType.PayPal)
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.signal.donations.GooglePayApi
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.signal.donations.StripeApi
|
||||
import org.signal.donations.StripeIntentAccessor
|
||||
import org.signal.donations.json.StripeIntentStatus
|
||||
@@ -86,12 +87,13 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
|
||||
price: FiatMoney,
|
||||
badgeRecipient: RecipientId,
|
||||
badgeLevel: Long,
|
||||
paymentSourceType: PaymentSourceType
|
||||
): Single<StripeIntentAccessor> {
|
||||
Log.d(TAG, "Creating payment intent for $price...", true)
|
||||
|
||||
return stripeApi.createPaymentIntent(price, badgeLevel)
|
||||
.onErrorResumeNext {
|
||||
handleCreatePaymentIntentError(it, badgeRecipient)
|
||||
OneTimeDonationRepository.handleCreatePaymentIntentError(it, badgeRecipient, paymentSourceType)
|
||||
}
|
||||
.flatMap { result ->
|
||||
val recipient = Recipient.resolved(badgeRecipient)
|
||||
@@ -127,7 +129,7 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
|
||||
Log.d(TAG, "Confirming payment intent...", true)
|
||||
return stripeApi.confirmPaymentIntent(paymentSource, paymentIntent)
|
||||
.onErrorResumeNext {
|
||||
Single.error(DonationError.getPaymentSetupError(donationErrorSource, it))
|
||||
Single.error(DonationError.getPaymentSetupError(donationErrorSource, it, paymentSource.type))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +198,10 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
|
||||
}
|
||||
}
|
||||
|
||||
fun setDefaultPaymentMethod(paymentMethodId: String): Completable {
|
||||
fun setDefaultPaymentMethod(
|
||||
paymentMethodId: String,
|
||||
paymentSourceType: PaymentSourceType
|
||||
): Completable {
|
||||
return Single.fromCallable {
|
||||
Log.d(TAG, "Getting the subscriber...")
|
||||
SignalStore.donationsValues().requireSubscriber()
|
||||
@@ -209,6 +214,8 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
|
||||
}
|
||||
}.flatMap(ServiceResponse<EmptyResponse>::flattenResult).ignoreElement().doOnComplete {
|
||||
Log.d(TAG, "Set default payment method via Signal service!")
|
||||
Log.d(TAG, "Storing the subscription payment source type locally.")
|
||||
SignalStore.donationsValues().setSubscriptionPaymentSourceType(paymentSourceType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +223,7 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
|
||||
Log.d(TAG, "Creating credit card payment source via Stripe api...")
|
||||
return stripeApi.createPaymentSourceFromCardData(cardData).map {
|
||||
when (it) {
|
||||
is StripeApi.CreatePaymentSourceFromCardDataResult.Failure -> throw DonationError.getPaymentSetupError(donationErrorSource, it.reason)
|
||||
is StripeApi.CreatePaymentSourceFromCardDataResult.Failure -> throw DonationError.getPaymentSetupError(donationErrorSource, it.reason, PaymentSourceType.Stripe.CreditCard)
|
||||
is StripeApi.CreatePaymentSourceFromCardDataResult.Success -> it.paymentSource
|
||||
}
|
||||
}
|
||||
@@ -229,15 +236,5 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(StripeRepository::class.java)
|
||||
|
||||
fun <T> handleCreatePaymentIntentError(throwable: Throwable, badgeRecipient: RecipientId): Single<T> {
|
||||
return if (throwable is DonationError) {
|
||||
Single.error(throwable)
|
||||
} else {
|
||||
val recipient = Recipient.resolved(badgeRecipient)
|
||||
val errorSource = if (recipient.isSelf) DonationErrorSource.BOOST else DonationErrorSource.GIFT
|
||||
Single.error(DonationError.getPaymentSetupError(errorSource, throwable))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.text.Spanned
|
||||
import android.text.TextWatcher
|
||||
import android.text.method.DigitsKeyListener
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.appcompat.widget.AppCompatEditText
|
||||
import androidx.core.animation.doOnEnd
|
||||
@@ -26,6 +27,7 @@ import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
import java.lang.Integer.min
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.text.NumberFormat
|
||||
@@ -102,7 +104,9 @@ data class Boost(
|
||||
val currency: Currency,
|
||||
override val isEnabled: Boolean,
|
||||
val onBoostClick: (View, Boost) -> Unit,
|
||||
val minimumAmount: FiatMoney,
|
||||
val isCustomAmountFocused: Boolean,
|
||||
val isCustomAmountTooSmall: Boolean,
|
||||
val onCustomAmountChanged: (String) -> Unit,
|
||||
val onCustomAmountFocusChanged: (Boolean) -> Unit,
|
||||
) : PreferenceModel<SelectionModel>(isEnabled = isEnabled) {
|
||||
@@ -113,7 +117,10 @@ data class Boost(
|
||||
newItem.boosts == boosts &&
|
||||
newItem.selectedBoost == selectedBoost &&
|
||||
newItem.currency == currency &&
|
||||
newItem.isCustomAmountFocused == isCustomAmountFocused
|
||||
newItem.isCustomAmountFocused == isCustomAmountFocused &&
|
||||
newItem.isCustomAmountTooSmall == isCustomAmountTooSmall &&
|
||||
newItem.minimumAmount.amount == minimumAmount.amount &&
|
||||
newItem.minimumAmount.currency == minimumAmount.currency
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +133,7 @@ data class Boost(
|
||||
private val boost5: MaterialButton = itemView.findViewById(R.id.boost_5)
|
||||
private val boost6: MaterialButton = itemView.findViewById(R.id.boost_6)
|
||||
private val custom: AppCompatEditText = itemView.findViewById(R.id.boost_custom)
|
||||
private val error: TextView = itemView.findViewById(R.id.boost_custom_too_small)
|
||||
|
||||
private val boostButtons: List<MaterialButton>
|
||||
get() {
|
||||
@@ -145,6 +153,16 @@ data class Boost(
|
||||
override fun bind(model: SelectionModel) {
|
||||
itemView.isEnabled = model.isEnabled
|
||||
|
||||
error.text = context.getString(
|
||||
R.string.Boost__the_minimum_amount_you_can_donate_is_s,
|
||||
FiatMoneyUtil.format(
|
||||
context.resources, model.minimumAmount,
|
||||
FiatMoneyUtil.formatOptions().trimZerosAfterDecimal()
|
||||
)
|
||||
)
|
||||
|
||||
error.visible = model.isCustomAmountTooSmall
|
||||
|
||||
model.boosts.zip(boostButtons).forEach { (boost, button) ->
|
||||
val isSelected = boost == model.selectedBoost && !model.isCustomAmountFocused
|
||||
button.isSelected = isSelected
|
||||
|
||||
@@ -2,8 +2,10 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.donate
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
import io.reactivex.rxjava3.subjects.Subject
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.FragmentWrapperActivity
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.StripeRepository
|
||||
@@ -18,11 +20,10 @@ class DonateToSignalActivity : FragmentWrapperActivity(), DonationPaymentCompone
|
||||
override val googlePayResultPublisher: Subject<DonationPaymentComponent.GooglePayResult> = PublishSubject.create()
|
||||
|
||||
override fun getFragment(): Fragment {
|
||||
return DonateToSignalFragment().apply {
|
||||
arguments = DonateToSignalFragmentArgs.Builder(DonateToSignalType.ONE_TIME).build().toBundle()
|
||||
}
|
||||
return NavHostFragment.create(R.navigation.donate_to_signal, DonateToSignalFragmentArgs.Builder(DonateToSignalType.ONE_TIME).build().toBundle())
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
googlePayResultPublisher.onNext(DonationPaymentComponent.GooglePayResult(requestCode, resultCode, data))
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.subscription.donate
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -15,7 +14,6 @@ import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.lottie.LottieAnimationView
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import org.signal.core.util.dp
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
@@ -29,8 +27,6 @@ import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.boost.Boost
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayRequest
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorDialogs
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.models.CurrencySelection
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.models.NetworkFailure
|
||||
@@ -78,8 +74,6 @@ class DonateToSignalFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private var errorDialog: DialogInterface? = null
|
||||
|
||||
private val args: DonateToSignalFragmentArgs by navArgs()
|
||||
private val viewModel: DonateToSignalViewModel by viewModels(factoryProducer = {
|
||||
DonateToSignalViewModel.Factory(args.startType)
|
||||
@@ -112,7 +106,7 @@ class DonateToSignalFragment :
|
||||
}
|
||||
|
||||
override fun bindAdapter(adapter: MappingAdapter) {
|
||||
donationCheckoutDelegate = DonationCheckoutDelegate(this, this)
|
||||
donationCheckoutDelegate = DonationCheckoutDelegate(this, this, DonationErrorSource.BOOST, DonationErrorSource.SUBSCRIPTION)
|
||||
|
||||
val recyclerView = this.recyclerView!!
|
||||
recyclerView.overScrollMode = RecyclerView.OVER_SCROLL_IF_CONTENT_SCROLLS
|
||||
@@ -137,19 +131,6 @@ class DonateToSignalFragment :
|
||||
DonationPillToggle.register(adapter)
|
||||
|
||||
disposables.bindTo(viewLifecycleOwner)
|
||||
|
||||
disposables += DonationError.getErrorsForSource(DonationErrorSource.BOOST)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { error ->
|
||||
showErrorDialog(error)
|
||||
}
|
||||
|
||||
disposables += DonationError.getErrorsForSource(DonationErrorSource.SUBSCRIPTION)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { error ->
|
||||
showErrorDialog(error)
|
||||
}
|
||||
|
||||
disposables += viewModel.actions.subscribe { action ->
|
||||
when (action) {
|
||||
is DonateToSignalAction.DisplayCurrencySelectionDialog -> {
|
||||
@@ -261,6 +242,7 @@ class DonateToSignalFragment :
|
||||
when (state.donateToSignalType) {
|
||||
DonateToSignalType.ONE_TIME -> displayOneTimeSelection(state.areFieldsEnabled, state.oneTimeDonationState)
|
||||
DonateToSignalType.MONTHLY -> displayMonthlySelection(state.areFieldsEnabled, state.monthlyDonationState)
|
||||
DonateToSignalType.GIFT -> error("This fragment does not support gifts.")
|
||||
}
|
||||
|
||||
space(20.dp)
|
||||
@@ -329,6 +311,8 @@ class DonateToSignalFragment :
|
||||
selectedBoost = state.selectedBoost,
|
||||
currency = state.customAmount.currency,
|
||||
isCustomAmountFocused = state.isCustomAmountFocused,
|
||||
isCustomAmountTooSmall = state.shouldDisplayCustomAmountTooSmallError,
|
||||
minimumAmount = state.minimumDonationAmountOfSelectedCurrency,
|
||||
isEnabled = areFieldsEnabled,
|
||||
onBoostClick = { view, boost ->
|
||||
startAnimationAboveSelectedBoost(view)
|
||||
@@ -387,23 +371,6 @@ class DonateToSignalFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private fun showErrorDialog(throwable: Throwable) {
|
||||
if (errorDialog != null) {
|
||||
Log.d(TAG, "Already displaying an error dialog. Skipping.", throwable, true)
|
||||
} else {
|
||||
Log.d(TAG, "Displaying donation error dialog.", true)
|
||||
errorDialog = DonationErrorDialogs.show(
|
||||
requireContext(), throwable,
|
||||
object : DonationErrorDialogs.DialogCallback() {
|
||||
override fun onDialogDismissed() {
|
||||
errorDialog = null
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAnimationAboveSelectedBoost(view: View) {
|
||||
val animationView = getAnimationContainer(view)
|
||||
val viewProjection = Projection.relativeToViewRoot(view, null)
|
||||
@@ -437,6 +404,15 @@ class DonateToSignalFragment :
|
||||
findNavController().safeNavigate(DonateToSignalFragmentDirections.actionDonateToSignalFragmentToStripePaymentInProgressFragment(DonationProcessorAction.PROCESS_NEW_DONATION, gatewayRequest))
|
||||
}
|
||||
|
||||
override fun navigateToPayPalPaymentInProgress(gatewayRequest: GatewayRequest) {
|
||||
findNavController().safeNavigate(
|
||||
DonateToSignalFragmentDirections.actionDonateToSignalFragmentToPaypalPaymentInProgressFragment(
|
||||
DonationProcessorAction.PROCESS_NEW_DONATION,
|
||||
gatewayRequest
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun navigateToCreditCardForm(gatewayRequest: GatewayRequest) {
|
||||
findNavController().safeNavigate(DonateToSignalFragmentDirections.actionDonateToSignalFragmentToCreditCardFragment(gatewayRequest))
|
||||
}
|
||||
@@ -448,4 +424,8 @@ class DonateToSignalFragment :
|
||||
override fun onProcessorActionProcessed() {
|
||||
viewModel.refreshActiveSubscription()
|
||||
}
|
||||
|
||||
override fun onUserCancelledPaymentFlow() {
|
||||
findNavController().popBackStack(R.id.donateToSignalFragment, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,9 +81,15 @@ data class DonateToSignalState(
|
||||
val customAmount: FiatMoney = FiatMoney(BigDecimal.ZERO, selectedCurrency),
|
||||
val isCustomAmountFocused: Boolean = false,
|
||||
val donationStage: DonationStage = DonationStage.INIT,
|
||||
val selectableCurrencyCodes: List<String> = emptyList()
|
||||
val selectableCurrencyCodes: List<String> = emptyList(),
|
||||
private val minimumDonationAmounts: Map<Currency, FiatMoney> = emptyMap()
|
||||
) {
|
||||
val isSelectionValid: Boolean = if (isCustomAmountFocused) customAmount.amount > BigDecimal.ZERO else selectedBoost != null
|
||||
val minimumDonationAmountOfSelectedCurrency: FiatMoney = minimumDonationAmounts[selectedCurrency] ?: FiatMoney(BigDecimal.ZERO, selectedCurrency)
|
||||
private val isCustomAmountTooSmall: Boolean = if (isCustomAmountFocused) customAmount.amount < minimumDonationAmountOfSelectedCurrency.amount else false
|
||||
private val isCustomAmountZero: Boolean = customAmount.amount == BigDecimal.ZERO
|
||||
|
||||
val isSelectionValid: Boolean = if (isCustomAmountFocused) !isCustomAmountTooSmall else selectedBoost != null
|
||||
val shouldDisplayCustomAmountTooSmallError: Boolean = isCustomAmountTooSmall && !isCustomAmountZero
|
||||
}
|
||||
|
||||
data class MonthlyDonationState(
|
||||
|
||||
@@ -2,10 +2,19 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.donate
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource
|
||||
|
||||
@Parcelize
|
||||
enum class DonateToSignalType(val requestCode: Short) : Parcelable {
|
||||
ONE_TIME(16141),
|
||||
MONTHLY(16142),
|
||||
GIFT(16143)
|
||||
GIFT(16143);
|
||||
|
||||
fun toErrorSource(): DonationErrorSource {
|
||||
return when (this) {
|
||||
ONE_TIME -> DonationErrorSource.BOOST
|
||||
MONTHLY -> DonationErrorSource.SUBSCRIPTION
|
||||
GIFT -> DonationErrorSource.GIFT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user