diff --git a/.editorconfig b/.editorconfig
index 0139377bf9..6479173ef8 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -4,6 +4,17 @@ root = true
indent_size = 2
ktlint_standard_trailing-comma-on-call-site = disable
ktlint_standard_trailing-comma-on-declaration-site = disable
-ktlink_standard_spacing-between-declarations-with-annotations = disable
+ktlint_standard_spacing-between-declarations-with-annotations = disable
ktlint_code_style = intellij_idea
-ktlint_standard_class-naming = disabled
\ No newline at end of file
+ktlint_standard_class-naming = disabled
+
+# below rules disabled during ktlint version migration because they were preexisting but should be corrected and re-enabled ASAP
+ktlint_function_naming_ignore_when_annotated_with = Composable
+ktlint_standard_property-naming = disabled
+ktlint_standard_enum-wrapping = disabled
+ktlint_standard_multiline-if-else = disabled
+ktlint_standard_backing-property-naming = disabled
+ktlint_standard_statement-wrapping = disabled
+internal:ktlint-suppression = disabled
+ktlint_standard_unnecessary-parentheses-before-trailing-lambda = disabled
+ktlint_standard_value-parameter-comment = disabled
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 2a5a8a1a5c..172098c107 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -49,6 +49,10 @@
+
+
+
+
@@ -213,6 +217,10 @@
+
+
+
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 15406e8935..05e1d66a22 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -18,11 +18,11 @@ apply {
from("fix-profm.gradle")
}
-val canonicalVersionCode = 1421
-val canonicalVersionName = "7.8.1"
-val mollyRevision = 2
-
-val postFixSize = 100
+val canonicalVersionCode = 1436
+val canonicalVersionName = "7.11.4"
+val mollyRevision = 1
+val currentHotfixVersion = 1
+val maxHotfixVersions = 100
val sourceVersionNameWithRevision = "${canonicalVersionName}-${mollyRevision}"
@@ -85,6 +85,8 @@ android {
useLibrary("org.apache.http.legacy")
testBuildType = "instrumentation"
+ android.bundle.language.enableSplit = false
+
kotlinOptions {
jvmTarget = signalKotlinJvmTarget
}
@@ -142,15 +144,15 @@ android {
}
composeOptions {
- kotlinCompilerExtensionVersion = "1.4.4"
+ kotlinCompilerExtensionVersion = "1.5.4"
}
- if (mollyRevision < 0 || mollyRevision >= postFixSize) {
- throw GradleException("Molly revision $mollyRevision out of range")
+ if (mollyRevision < 0 || currentHotfixVersion < 0 || (mollyRevision + currentHotfixVersion) >= maxHotfixVersions) {
+ throw GradleException("Molly revision $mollyRevision or Hotfix version $currentHotfixVersion out of range")
}
defaultConfig {
- versionCode = canonicalVersionCode * postFixSize + mollyRevision
+ versionCode = (canonicalVersionCode * maxHotfixVersions) + mollyRevision + currentHotfixVersion
versionName = if (ciEnabled) getCommitTag() else sourceVersionNameWithRevision
minSdk = signalMinSdkVersion
@@ -198,11 +200,12 @@ android {
buildConfigField("String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/registration/generate.html\"")
buildConfigField("String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/challenge/generate.html\"")
buildConfigField("org.signal.libsignal.net.Network.Environment", "LIBSIGNAL_NET_ENV", "org.signal.libsignal.net.Network.Environment.PRODUCTION")
+ buildConfigField("int", "LIBSIGNAL_LOG_LEVEL", "org.signal.libsignal.protocol.logging.SignalProtocolLogger.INFO")
// MOLLY: Rely on the built-in variables FLAVOR and BUILD_TYPE instead of BUILD_*_TYPE
buildConfigField("String", "BADGE_STATIC_ROOT", "\"https://updates2.signal.org/static/badges/\"")
+ buildConfigField("String", "STRIPE_PUBLISHABLE_KEY", "\"pk_live_6cmGZopuTsV8novGgJJW9JpC00vLIgtQ1D\"")
buildConfigField("boolean", "TRACING_ENABLED", "false")
- buildConfigField("boolean", "MESSAGE_BACKUP_RESTORE_ENABLED", "false")
ndk {
//noinspection ChromeOsAbiSupport
@@ -211,12 +214,6 @@ android {
resourceConfigurations += listOf()
- bundle {
- language {
- enableSplit = false
- }
- }
-
testInstrumentationRunner = "org.thoughtcrime.securesms.testing.SignalTestRunner"
testInstrumentationRunnerArguments["clearPackageData"] = "true"
}
@@ -342,10 +339,10 @@ android {
buildConfigField("String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/staging/registration/generate.html\"")
buildConfigField("String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/staging/challenge/generate.html\"")
buildConfigField("org.signal.libsignal.net.Network.Environment", "LIBSIGNAL_NET_ENV", "org.signal.libsignal.net.Network.Environment.STAGING")
+ buildConfigField("int", "LIBSIGNAL_LOG_LEVEL", "org.signal.libsignal.protocol.logging.SignalProtocolLogger.DEBUG")
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"Staging\"")
buildConfigField("String", "STRIPE_PUBLISHABLE_KEY", "\"pk_test_sngOd8FnXNkpce9nPXawKrJD00kIDngZkD\"")
- buildConfigField("boolean", "MESSAGE_BACKUP_RESTORE_ENABLED", "true")
}
}
@@ -406,6 +403,7 @@ dependencies {
implementation(project(":video"))
implementation(project(":device-transfer"))
implementation(project(":image-editor"))
+ implementation(project(":donations"))
implementation(project(":contacts"))
implementation(project(":qr"))
implementation(project(":sticky-header-grid"))
@@ -454,6 +452,7 @@ dependencies {
implementation(libs.androidx.profileinstaller)
implementation(libs.androidx.asynclayoutinflater)
implementation(libs.androidx.asynclayoutinflater.appcompat)
+ implementation(libs.androidx.emoji2)
implementation(libs.androidx.webkit)
"gmsImplementation"(libs.firebase.messaging) {
exclude(group = "com.google.firebase", module = "firebase-core")
@@ -462,6 +461,7 @@ dependencies {
}
"gmsImplementation"(libs.google.play.services.maps)
"gmsImplementation"(libs.google.play.services.auth)
+ "fossImplementation"(project(":libfakegms"))
implementation(libs.bundles.media3)
implementation(libs.conscrypt.android)
implementation(libs.signal.aesgcmprovider)
@@ -486,6 +486,7 @@ dependencies {
}
implementation(libs.stream)
implementation(libs.lottie)
+ implementation(libs.lottie.compose)
implementation(libs.signal.android.database.sqlcipher)
implementation(libs.androidx.sqlite)
implementation(libs.google.ez.vcard) {
@@ -496,6 +497,9 @@ dependencies {
implementation(libs.kotlinx.collections.immutable)
implementation(libs.accompanist.permissions)
implementation(libs.kotlin.stdlib.jdk8)
+ implementation(libs.kotlin.reflect)
+ "gmsImplementation"(libs.kotlinx.coroutines.play.services)
+ implementation(libs.jackson.module.kotlin)
implementation(libs.rxjava3.rxandroid)
implementation(libs.rxjava3.rxkotlin)
implementation(libs.rxdogtag)
@@ -506,7 +510,6 @@ dependencies {
implementation(libs.molly.glide.webp.decoder)
implementation(libs.gosimple.nbvcxz)
"fossImplementation"("org.osmdroid:osmdroid-android:6.1.16")
- "fossImplementation"(project(":libfakegms"))
"spinnerImplementation"(project(":spinner"))
diff --git a/app/proguard/proguard.cfg b/app/proguard/proguard.cfg
index 65f2de3c3b..d52eb11852 100644
--- a/app/proguard/proguard.cfg
+++ b/app/proguard/proguard.cfg
@@ -15,6 +15,10 @@
-keep class androidx.window.** { *; }
+-keepclassmembers class * extends androidx.constraintlayout.motion.widget.Key {
+ public ();
+}
+
# AGP generated dont warns
-dontwarn com.android.org.conscrypt.SSLParametersImpl
-dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl
diff --git a/app/src/androidTest/assets/backupTests/account-data.binproto b/app/src/androidTest/assets/backupTests/account-data.binproto
new file mode 100644
index 0000000000..54ebb6e04a
Binary files /dev/null and b/app/src/androidTest/assets/backupTests/account-data.binproto differ
diff --git a/app/src/androidTest/assets/backupTests/registered-blocked-contact.binproto b/app/src/androidTest/assets/backupTests/registered-blocked-contact.binproto
new file mode 100644
index 0000000000..439129a11c
Binary files /dev/null and b/app/src/androidTest/assets/backupTests/registered-blocked-contact.binproto differ
diff --git a/app/src/androidTest/assets/backupTests/story-distribution-list.binproto b/app/src/androidTest/assets/backupTests/story-distribution-list.binproto
new file mode 100644
index 0000000000..369e68ab6b
Binary files /dev/null and b/app/src/androidTest/assets/backupTests/story-distribution-list.binproto differ
diff --git a/app/src/androidTest/assets/backupTests/unregistered-contact.binproto b/app/src/androidTest/assets/backupTests/unregistered-contact.binproto
new file mode 100644
index 0000000000..cf94133160
Binary files /dev/null and b/app/src/androidTest/assets/backupTests/unregistered-contact.binproto differ
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/SignalInstrumentationApplicationContext.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/SignalInstrumentationApplicationContext.kt
index f7164a0eae..362bfd03c3 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/SignalInstrumentationApplicationContext.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/SignalInstrumentationApplicationContext.kt
@@ -6,7 +6,7 @@ import org.signal.core.util.logging.Log
import org.signal.libsignal.protocol.logging.SignalProtocolLoggerProvider
import org.thoughtcrime.securesms.crypto.MasterSecretUtil
import org.thoughtcrime.securesms.database.LogDatabase
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
+import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger
@@ -26,8 +26,8 @@ class SignalInstrumentationApplicationContext : ApplicationContext() {
override fun initializeAppDependencies() {
val default = ApplicationDependencyProvider(this)
- ApplicationDependencies.init(this, InstrumentationApplicationDependencyProvider(this, default))
- ApplicationDependencies.getDeadlockDetector().start()
+ AppDependencies.init(this, InstrumentationApplicationDependencyProvider(this, default))
+ AppDependencies.deadlockDetector.start()
}
override fun initializeLogging(locked: Boolean) {
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/BackupTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/BackupTest.kt
index 77cff4430d..704cd2bac0 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/BackupTest.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/BackupTest.kt
@@ -10,6 +10,7 @@ import android.database.Cursor
import androidx.core.content.contentValuesOf
import net.zetetic.database.sqlcipher.SQLiteDatabase
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.signal.core.util.Hex
import org.signal.core.util.SqlUtil
@@ -24,27 +25,30 @@ import org.signal.core.util.toInt
import org.signal.core.util.withinTransaction
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.backup.v2.database.clearAllDataForBackupRestore
+import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository
import org.thoughtcrime.securesms.database.CallTable
import org.thoughtcrime.securesms.database.EmojiSearchTable
import org.thoughtcrime.securesms.database.MessageTable
import org.thoughtcrime.securesms.database.MessageTypes
import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.SignalDatabase
+import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
+import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
+import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.QuoteModel
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
-import org.thoughtcrime.securesms.subscription.Subscriber
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import java.io.ByteArrayInputStream
+import java.util.Currency
import java.util.UUID
import kotlin.random.Random
@@ -79,18 +83,20 @@ class BackupTest {
@Before
fun setup() {
- SignalStore.account().setE164(SELF_E164)
- SignalStore.account().setAci(SELF_ACI)
- SignalStore.account().setPni(SELF_PNI)
- SignalStore.account().generateAciIdentityKeyIfNecessary()
- SignalStore.account().generatePniIdentityKeyIfNecessary()
+ SignalStore.account.setE164(SELF_E164)
+ SignalStore.account.setAci(SELF_ACI)
+ SignalStore.account.setPni(SELF_PNI)
+ SignalStore.account.generateAciIdentityKeyIfNecessary()
+ SignalStore.account.generatePniIdentityKeyIfNecessary()
}
+ @Ignore("Will likely be removed soon")
@Test
fun emptyDatabase() {
backupTest { }
}
+ @Ignore("Will likely be removed soon")
@Test
fun noteToSelf() {
backupTest {
@@ -102,6 +108,7 @@ class BackupTest {
}
}
+ @Ignore("Will likely be removed soon")
@Test
fun individualChat() {
backupTest {
@@ -118,6 +125,7 @@ class BackupTest {
}
}
+ @Ignore("Will likely be removed soon")
@Test
fun individualRecipients() {
backupTest {
@@ -149,6 +157,7 @@ class BackupTest {
}
}
+ @Ignore("Will likely be removed soon")
@Test
fun individualCallLogs() {
backupTest {
@@ -231,9 +240,10 @@ class BackupTest {
}
}
+ @Ignore("Will likely be removed soon")
@Test
fun accountData() {
- val context = ApplicationDependencies.getApplication()
+ val context = AppDependencies.application
backupTest(validateKeyValue = true) {
val self = Recipient.self()
@@ -241,35 +251,34 @@ class BackupTest {
// TODO note-to-self archived
// TODO note-to-self unread
- SignalStore.account().setAci(SELF_ACI)
- SignalStore.account().setPni(SELF_PNI)
- SignalStore.account().setE164(SELF_E164)
- SignalStore.account().generateAciIdentityKeyIfNecessary()
- SignalStore.account().generatePniIdentityKeyIfNecessary()
+ SignalStore.account.setAci(SELF_ACI)
+ SignalStore.account.setPni(SELF_PNI)
+ SignalStore.account.setE164(SELF_E164)
+ SignalStore.account.generateAciIdentityKeyIfNecessary()
+ SignalStore.account.generatePniIdentityKeyIfNecessary()
SignalDatabase.recipients.setProfileKey(self.id, ProfileKey(Random.nextBytes(32)))
SignalDatabase.recipients.setProfileName(self.id, ProfileName.fromParts("Peter", "Parker"))
SignalDatabase.recipients.setProfileAvatar(self.id, "https://example.com/")
- SignalStore.donationsValues().markUserManuallyCancelled()
- SignalStore.donationsValues().setSubscriber(Subscriber(SubscriberId.generate(), "USD"))
- SignalStore.donationsValues().setDisplayBadgesOnProfile(false)
+ InAppPaymentsRepository.setSubscriber(InAppPaymentSubscriberRecord(SubscriberId.generate(), Currency.getInstance("USD"), InAppPaymentSubscriberRecord.Type.DONATION, false, InAppPaymentData.PaymentMethodType.UNKNOWN))
+ SignalStore.inAppPayments.setDisplayBadgesOnProfile(false)
- SignalStore.phoneNumberPrivacy().phoneNumberDiscoverabilityMode = PhoneNumberPrivacyValues.PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE
- SignalStore.phoneNumberPrivacy().phoneNumberSharingMode = PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY
+ SignalStore.phoneNumberPrivacy.phoneNumberDiscoverabilityMode = PhoneNumberPrivacyValues.PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE
+ SignalStore.phoneNumberPrivacy.phoneNumberSharingMode = PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY
- SignalStore.settings().isLinkPreviewsEnabled = false
- SignalStore.settings().isPreferSystemContactPhotos = true
- SignalStore.settings().universalExpireTimer = 42
- SignalStore.settings().setKeepMutedChatsArchived(true)
+ SignalStore.settings.isLinkPreviewsEnabled = false
+ SignalStore.settings.isPreferSystemContactPhotos = true
+ SignalStore.settings.universalExpireTimer = 42
+ SignalStore.settings.setKeepMutedChatsArchived(true)
- SignalStore.storyValues().viewedReceiptsEnabled = false
- SignalStore.storyValues().userHasViewedOnboardingStory = true
- SignalStore.storyValues().isFeatureDisabled = false
- SignalStore.storyValues().userHasBeenNotifiedAboutStories = true
- SignalStore.storyValues().userHasSeenGroupStoryEducationSheet = true
+ SignalStore.story.viewedReceiptsEnabled = false
+ SignalStore.story.userHasViewedOnboardingStory = true
+ SignalStore.story.isFeatureDisabled = false
+ SignalStore.story.userHasBeenNotifiedAboutStories = true
+ SignalStore.story.userHasSeenGroupStoryEducationSheet = true
- SignalStore.emojiValues().reactions = listOf("a", "b", "c")
+ SignalStore.emoji.reactions = listOf("a", "b", "c")
TextSecurePreferences.setTypingIndicatorsEnabled(context, false)
TextSecurePreferences.setReadReceiptsEnabled(context, false)
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ImportExportTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ImportExportTest.kt
index 689afeeeed..3112c32100 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ImportExportTest.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ImportExportTest.kt
@@ -7,9 +7,9 @@ package org.thoughtcrime.securesms.backup.v2
import android.Manifest
import android.app.UiAutomation
+import android.content.Context
import android.os.Environment
import androidx.test.platform.app.InstrumentationRegistry
-import io.mockk.InternalPlatformDsl.toArray
import okio.ByteString.Companion.toByteString
import org.junit.Assert
import org.junit.Before
@@ -17,6 +17,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestName
import org.signal.core.util.Base64
+import org.signal.core.util.test.getObjectDiff
import org.signal.libsignal.messagebackup.MessageBackup
import org.signal.libsignal.messagebackup.MessageBackupKey
import org.signal.libsignal.zkgroup.profiles.ProfileKey
@@ -28,12 +29,17 @@ import org.thoughtcrime.securesms.backup.v2.proto.Chat
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
import org.thoughtcrime.securesms.backup.v2.proto.ChatUpdateMessage
import org.thoughtcrime.securesms.backup.v2.proto.Contact
+import org.thoughtcrime.securesms.backup.v2.proto.ContactAttachment
+import org.thoughtcrime.securesms.backup.v2.proto.ContactMessage
import org.thoughtcrime.securesms.backup.v2.proto.DistributionList
+import org.thoughtcrime.securesms.backup.v2.proto.DistributionListItem
import org.thoughtcrime.securesms.backup.v2.proto.ExpirationTimerChatUpdate
import org.thoughtcrime.securesms.backup.v2.proto.FilePointer
import org.thoughtcrime.securesms.backup.v2.proto.Frame
+import org.thoughtcrime.securesms.backup.v2.proto.GiftBadge
import org.thoughtcrime.securesms.backup.v2.proto.Group
import org.thoughtcrime.securesms.backup.v2.proto.IndividualCall
+import org.thoughtcrime.securesms.backup.v2.proto.LinkPreview
import org.thoughtcrime.securesms.backup.v2.proto.MessageAttachment
import org.thoughtcrime.securesms.backup.v2.proto.ProfileChangeChatUpdate
import org.thoughtcrime.securesms.backup.v2.proto.Quote
@@ -48,9 +54,12 @@ import org.thoughtcrime.securesms.backup.v2.proto.StandardMessage
import org.thoughtcrime.securesms.backup.v2.proto.StickerPack
import org.thoughtcrime.securesms.backup.v2.proto.Text
import org.thoughtcrime.securesms.backup.v2.proto.ThreadMergeChatUpdate
+import org.thoughtcrime.securesms.backup.v2.stream.BackupExportWriter
import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupReader
import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupWriter
+import org.thoughtcrime.securesms.backup.v2.stream.PlainTextBackupWriter
import org.thoughtcrime.securesms.keyvalue.SignalStore
+import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.kbs.MasterKey
import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.push.ServiceId
@@ -71,6 +80,14 @@ import kotlin.time.Duration.Companion.days
*/
class ImportExportTest {
companion object {
+ /**
+ * Output the frames as a plaintext .binproto for sharing tests
+ *
+ * This only seems to work on API 28 emulators, You can find the generated files
+ * at /sdcard/backup-tests/
+ * */
+ val OUTPUT_FILES = false
+
val SELF_ACI = ServiceId.ACI.from(UUID.fromString("77770000-b477-4f35-a824-d92987a63641"))
val SELF_PNI = ServiceId.PNI.from(UUID.fromString("77771111-b014-41fb-bf73-05cb2ec52910"))
const val SELF_E164 = "+10000000000"
@@ -79,7 +96,17 @@ class ImportExportTest {
val defaultBackupInfo = BackupInfo(version = 1L, backupTimeMs = 123456L)
val selfRecipient = Recipient(id = 1, self = Self())
- val releaseNotes = Recipient(id = 2, releaseNotes = ReleaseNotes())
+ val myStory = Recipient(
+ id = 2,
+ distributionList = DistributionListItem(
+ distributionId = DistributionId.MY_STORY.asUuid().toByteArray().toByteString(),
+ distributionList = DistributionList(
+ name = DistributionId.MY_STORY.toString(),
+ privacyMode = DistributionList.PrivacyMode.ALL
+ )
+ )
+ )
+ val releaseNotes = Recipient(id = 3, releaseNotes = ReleaseNotes())
val standardAccountData = AccountData(
profileKey = SELF_PROFILE_KEY.serialize().toByteString(),
username = "self.01",
@@ -87,9 +114,11 @@ class ImportExportTest {
givenName = "Peter",
familyName = "Parker",
avatarUrlPath = "https://example.com/",
- subscriberId = SubscriberId.generate().bytes.toByteString(),
- subscriberCurrencyCode = "USD",
- subscriptionManuallyCancelled = true,
+ donationSubscriberData = AccountData.SubscriberData(
+ subscriberId = SubscriberId.generate().bytes.toByteString(),
+ currencyCode = "USD",
+ manuallyCancelled = true
+ ),
accountSettings = AccountData.AccountSettings(
readReceipts = true,
sealedSenderIndicators = true,
@@ -111,16 +140,15 @@ class ImportExportTest {
)
)
val alice = Recipient(
- id = 3,
+ id = 4,
contact = Contact(
aci = TestRecipientUtils.nextAci().toByteString(),
pni = TestRecipientUtils.nextPni().toByteString(),
username = "cool.01",
e164 = 141255501234,
blocked = false,
- hidden = false,
- registered = Contact.Registered.REGISTERED,
- unregisteredTimestamp = 0L,
+ visibility = Contact.Visibility.VISIBLE,
+ registered = Contact.Registered(),
profileKey = TestRecipientUtils.generateProfileKey().toByteString(),
profileSharing = true,
profileGivenName = "Alexa",
@@ -130,23 +158,26 @@ class ImportExportTest {
)
/**
- * When using standardFrames you must start recipient ids at 3.
+ * When using standardFrames you must start recipient ids at 4.
*/
- private val standardFrames = arrayOf(defaultBackupInfo, standardAccountData, selfRecipient, releaseNotes)
+ private val standardFrames = arrayOf(defaultBackupInfo, standardAccountData, selfRecipient, myStory, releaseNotes)
}
+ private val context: Context
+ get() = InstrumentationRegistry.getInstrumentation().targetContext
+
@JvmField
@Rule
var testName = TestName()
@Before
fun setup() {
- SignalStore.svr().setMasterKey(MasterKey(MASTER_KEY), "1234")
- SignalStore.account().setE164(SELF_E164)
- SignalStore.account().setAci(SELF_ACI)
- SignalStore.account().setPni(SELF_PNI)
- SignalStore.account().generateAciIdentityKeyIfNecessary()
- SignalStore.account().generatePniIdentityKeyIfNecessary()
+ SignalStore.svr.setMasterKey(MasterKey(MASTER_KEY), "1234")
+ SignalStore.account.setE164(SELF_E164)
+ SignalStore.account.setAci(SELF_ACI)
+ SignalStore.account.setPni(SELF_PNI)
+ SignalStore.account.generateAciIdentityKeyIfNecessary()
+ SignalStore.account.generatePniIdentityKeyIfNecessary()
}
@Test
@@ -158,7 +189,7 @@ class ImportExportTest {
fun largeNumberOfRecipientsAndChats() {
val recipients = ArrayList(5000)
val chats = ArrayList(5000)
- var id = 3L
+ var id = 4L
for (i in 0..5000) {
val recipientId = id++
recipients.add(
@@ -170,9 +201,8 @@ class ImportExportTest {
username = "rec$i.01",
e164 = 14125550000 + i,
blocked = false,
- hidden = false,
- registered = Contact.Registered.REGISTERED,
- unregisteredTimestamp = 0L,
+ visibility = Contact.Visibility.VISIBLE,
+ registered = Contact.Registered(),
profileKey = TestRecipientUtils.generateProfileKey().toByteString(),
profileSharing = true,
profileGivenName = "Test",
@@ -216,7 +246,7 @@ class ImportExportTest {
@Test
fun largeNumberOfMessagesAndChats() {
- val NUM_INDIVIDUAL_RECIPIENTS = 1000
+ val numIndividualRecipients = 1000
val numIndividualMessages = 500
val numGroupMessagesPerPerson = 200
@@ -225,7 +255,7 @@ class ImportExportTest {
val recipients = ArrayList(1010)
val chats = ArrayList(1010)
var id = 3L
- for (i in 0 until NUM_INDIVIDUAL_RECIPIENTS) {
+ for (i in 0 until numIndividualRecipients) {
val recipientId = id++
recipients.add(
Recipient(
@@ -236,9 +266,8 @@ class ImportExportTest {
username = if (random.trueWithProbability(0.2f)) "rec$i.01" else null,
e164 = 14125550000 + i,
blocked = random.trueWithProbability(0.1f),
- hidden = random.trueWithProbability(0.1f),
- registered = Contact.Registered.REGISTERED,
- unregisteredTimestamp = 0L,
+ visibility = if (random.trueWithProbability(0.1f)) Contact.Visibility.HIDDEN else Contact.Visibility.VISIBLE,
+ registered = Contact.Registered(),
profileKey = TestRecipientUtils.generateProfileKey().toByteString(),
profileSharing = random.trueWithProbability(0.9f),
profileGivenName = "Test",
@@ -369,12 +398,12 @@ class ImportExportTest {
}
}
}
- val import = exportFrames(
+
+ exportFrames(
*standardFrames,
*recipients.toArray(),
*chatItems.toArray()
)
- outputFile(import)
}
@Test
@@ -382,16 +411,15 @@ class ImportExportTest {
importExport(
*standardFrames,
Recipient(
- id = 3,
+ id = 4,
contact = Contact(
aci = TestRecipientUtils.nextAci().toByteString(),
pni = TestRecipientUtils.nextPni().toByteString(),
username = "cool.01",
e164 = 141255501234,
blocked = true,
- hidden = true,
- registered = Contact.Registered.REGISTERED,
- unregisteredTimestamp = 0L,
+ visibility = Contact.Visibility.VISIBLE,
+ registered = Contact.Registered(),
profileKey = TestRecipientUtils.generateProfileKey().toByteString(),
profileSharing = true,
profileGivenName = "Alexa",
@@ -400,16 +428,15 @@ class ImportExportTest {
)
),
Recipient(
- id = 4,
+ id = 5,
contact = Contact(
aci = null,
pni = null,
username = null,
e164 = 141255501235,
blocked = true,
- hidden = true,
- registered = Contact.Registered.NOT_REGISTERED,
- unregisteredTimestamp = 1234568927398L,
+ visibility = Contact.Visibility.HIDDEN,
+ notRegistered = Contact.NotRegistered(unregisteredTimestamp = 1234568927398L),
profileKey = TestRecipientUtils.generateProfileKey().toByteString(),
profileSharing = false,
profileGivenName = "Peter",
@@ -425,7 +452,7 @@ class ImportExportTest {
importExport(
*standardFrames,
Recipient(
- id = 3,
+ id = 4,
group = Group(
masterKey = TestRecipientUtils.generateGroupMasterKey().toByteString(),
whitelisted = true,
@@ -440,7 +467,7 @@ class ImportExportTest {
)
),
Recipient(
- id = 4,
+ id = 5,
group = Group(
masterKey = TestRecipientUtils.generateGroupMasterKey().toByteString(),
whitelisted = false,
@@ -462,16 +489,15 @@ class ImportExportTest {
importExport(
*standardFrames,
Recipient(
- id = 3,
+ id = 4,
contact = Contact(
aci = TestRecipientUtils.nextAci().toByteString(),
pni = TestRecipientUtils.nextPni().toByteString(),
username = "cool.01",
e164 = 141255501234,
blocked = true,
- hidden = true,
- registered = Contact.Registered.REGISTERED,
- unregisteredTimestamp = 0L,
+ visibility = Contact.Visibility.HIDDEN,
+ registered = Contact.Registered(),
profileKey = TestRecipientUtils.generateProfileKey().toByteString(),
profileSharing = true,
profileGivenName = "Alexa",
@@ -480,16 +506,15 @@ class ImportExportTest {
)
),
Recipient(
- id = 4,
+ id = 5,
contact = Contact(
aci = null,
pni = null,
username = null,
e164 = 141255501235,
blocked = true,
- hidden = true,
- registered = Contact.Registered.REGISTERED,
- unregisteredTimestamp = 0L,
+ visibility = Contact.Visibility.HIDDEN,
+ registered = Contact.Registered(),
profileKey = TestRecipientUtils.generateProfileKey().toByteString(),
profileSharing = true,
profileGivenName = "Peter",
@@ -498,16 +523,15 @@ class ImportExportTest {
)
),
Recipient(
- id = 5,
+ id = 6,
contact = Contact(
aci = null,
pni = null,
username = null,
e164 = 141255501236,
blocked = true,
- hidden = true,
- registered = Contact.Registered.REGISTERED,
- unregisteredTimestamp = 0L,
+ visibility = Contact.Visibility.HIDDEN,
+ registered = Contact.Registered(),
profileKey = TestRecipientUtils.generateProfileKey().toByteString(),
profileSharing = true,
profileGivenName = "Father",
@@ -516,14 +540,15 @@ class ImportExportTest {
)
),
Recipient(
- id = 6,
- distributionList = DistributionList(
- name = "Kim Family",
+ id = 7,
+ distributionList = DistributionListItem(
distributionId = DistributionId.create().asUuid().toByteArray().toByteString(),
- allowReplies = true,
- deletionTimestamp = 0L,
- privacyMode = DistributionList.PrivacyMode.ONLY_WITH,
- memberRecipientIds = listOf(3, 4, 5)
+ distributionList = DistributionList(
+ name = "Kim Family",
+ allowReplies = true,
+ privacyMode = DistributionList.PrivacyMode.ONLY_WITH,
+ memberRecipientIds = listOf(3, 4, 5)
+ )
)
)
)
@@ -532,16 +557,15 @@ class ImportExportTest {
@Test
fun deletedDistributionList() {
val alexa = Recipient(
- id = 3,
+ id = 4,
contact = Contact(
aci = TestRecipientUtils.nextAci().toByteString(),
pni = TestRecipientUtils.nextPni().toByteString(),
username = "cool.01",
e164 = 141255501234,
blocked = true,
- hidden = true,
- registered = Contact.Registered.REGISTERED,
- unregisteredTimestamp = 0L,
+ visibility = Contact.Visibility.HIDDEN,
+ registered = Contact.Registered(),
profileKey = TestRecipientUtils.generateProfileKey().toByteString(),
profileSharing = true,
profileGivenName = "Alexa",
@@ -554,23 +578,19 @@ class ImportExportTest {
alexa,
Recipient(
id = 6,
- distributionList = DistributionList(
- name = "Deleted list",
+ distributionList = DistributionListItem(
distributionId = DistributionId.create().asUuid().toByteArray().toByteString(),
- allowReplies = true,
- deletionTimestamp = 12345L,
- privacyMode = DistributionList.PrivacyMode.ONLY_WITH,
- memberRecipientIds = listOf(3)
+ deletionTimestamp = 12345L
)
)
)
import(importData)
- val exported = export()
+ val exported = BackupRepository.export()
val expected = exportFrames(
*standardFrames,
alexa
)
- outputFile(importData, expected)
+
compare(expected, exported)
}
@@ -579,16 +599,15 @@ class ImportExportTest {
importExport(
*standardFrames,
Recipient(
- id = 3,
+ id = 4,
contact = Contact(
aci = TestRecipientUtils.nextAci().toByteString(),
pni = TestRecipientUtils.nextPni().toByteString(),
username = "cool.01",
e164 = 141255501234,
blocked = false,
- hidden = false,
- registered = Contact.Registered.REGISTERED,
- unregisteredTimestamp = 0L,
+ visibility = Contact.Visibility.VISIBLE,
+ registered = Contact.Registered(),
profileKey = TestRecipientUtils.generateProfileKey().toByteString(),
profileSharing = true,
profileGivenName = "Alexa",
@@ -597,7 +616,7 @@ class ImportExportTest {
)
),
Recipient(
- id = 4,
+ id = 5,
group = Group(
masterKey = TestRecipientUtils.generateGroupMasterKey().toByteString(),
whitelisted = true,
@@ -607,14 +626,13 @@ class ImportExportTest {
),
Chat(
id = 1,
- recipientId = 3,
+ recipientId = 4,
archived = true,
pinnedOrder = 1,
expirationTimerMs = 1.days.inWholeMilliseconds,
muteUntilMs = System.currentTimeMillis(),
markedUnread = true,
- dontNotifyForMentionsIfMuted = true,
- wallpaper = null
+ dontNotifyForMentionsIfMuted = true
)
)
}
@@ -681,16 +699,15 @@ class ImportExportTest {
importExport(
*standardFrames,
Recipient(
- id = 3,
+ id = 4,
contact = Contact(
aci = startedAci,
pni = TestRecipientUtils.nextPni().toByteString(),
username = "cool.01",
e164 = 141255501234,
blocked = false,
- hidden = false,
- registered = Contact.Registered.REGISTERED,
- unregisteredTimestamp = 0L,
+ visibility = Contact.Visibility.VISIBLE,
+ registered = Contact.Registered(),
profileKey = TestRecipientUtils.generateProfileKey().toByteString(),
profileSharing = true,
profileGivenName = "Alexa",
@@ -699,7 +716,7 @@ class ImportExportTest {
)
),
Recipient(
- id = 4,
+ id = 5,
group = Group(
masterKey = TestRecipientUtils.generateGroupMasterKey().toByteString(),
whitelisted = true,
@@ -709,14 +726,13 @@ class ImportExportTest {
),
Chat(
id = 1,
- recipientId = 3,
+ recipientId = 4,
archived = true,
pinnedOrder = 1,
expirationTimerMs = 1.days.inWholeMilliseconds,
muteUntilMs = System.currentTimeMillis(),
markedUnread = true,
- dontNotifyForMentionsIfMuted = true,
- wallpaper = null
+ dontNotifyForMentionsIfMuted = true
),
*individualCalls.toArray()
)
@@ -994,14 +1010,13 @@ class ImportExportTest {
expirationNotStarted
)
import(importData)
- val exported = export()
+ val exported = BackupRepository.export()
val expected = exportFrames(
*standardFrames,
alice,
chat,
expirationNotStarted
)
- outputFile(importData, expected)
compare(expected, exported)
}
@@ -1051,23 +1066,175 @@ class ImportExportTest {
incrementalMacChunkSize = 0
),
wasDownloaded = false
+ )
+ )
+ )
+ )
+ )
+ }
+
+ @Test
+ fun linkPreviewMessages() {
+ var dateSent = System.currentTimeMillis()
+ val sendStatuses = enumerateSendStatuses(alice.id)
+ val incomingMessageDetails = enumerateIncomingMessageDetails(dateSent + 200)
+ val outgoingMessages = ArrayList()
+ val incomingMessages = ArrayList()
+ for (sendStatus in sendStatuses) {
+ outgoingMessages.add(
+ ChatItem(
+ chatId = 1,
+ authorId = selfRecipient.id,
+ dateSent = dateSent++,
+ expireStartDate = dateSent + 1000,
+ expiresInMs = TimeUnit.DAYS.toMillis(2),
+ sms = false,
+ outgoing = ChatItem.OutgoingMessageDetails(
+ sendStatus = listOf(sendStatus)
+ ),
+ standardMessage = StandardMessage(
+ text = Text(
+ body = "Text only body"
),
- MessageAttachment(
- pointer = FilePointer(
- backupLocator = FilePointer.BackupLocator(
- "digestherebutimlazy",
- cdnNumber = 3,
+ linkPreview = listOf(
+ LinkPreview(
+ url = "https://signal.org/",
+ title = "Signal Messenger: Speak Freely",
+ description = "Say \"hello\" to a different messaging experience. An unexpected focus on privacy, combined with all the features you expect.",
+ date = System.currentTimeMillis(),
+ image = FilePointer(
+ invalidAttachmentLocator = FilePointer.InvalidAttachmentLocator(),
+ contentType = "image/png",
+ width = 100,
+ height = 200,
+ caption = "Love this cool picture! Too bad u cant download it",
+ incrementalMacChunkSize = 0
+ )
+ )
+ )
+ )
+ )
+ )
+ }
+ dateSent++
+ for (incomingDetail in incomingMessageDetails) {
+ incomingMessages.add(
+ ChatItem(
+ chatId = 1,
+ authorId = alice.id,
+ dateSent = dateSent++,
+ expireStartDate = dateSent + 1000,
+ expiresInMs = TimeUnit.DAYS.toMillis(2),
+ sms = false,
+ incoming = incomingDetail,
+ standardMessage = StandardMessage(
+ text = Text(
+ body = "Text only body"
+ ),
+ linkPreview = listOf(
+ LinkPreview(
+ url = "https://signal.org/",
+ title = "Signal Messenger: Speak Freely",
+ description = "Say \"hello\" to a different messaging experience. An unexpected focus on privacy, combined with all the features you expect.",
+ date = System.currentTimeMillis(),
+ image = FilePointer(
+ invalidAttachmentLocator = FilePointer.InvalidAttachmentLocator(),
+ contentType = "image/png",
+ width = 100,
+ height = 200,
+ caption = "Love this cool picture! Too bad u cant download it",
+ incrementalMacChunkSize = 0
+ )
+ )
+ )
+ )
+ )
+ )
+ }
+
+ importExport(
+ *standardFrames,
+ alice,
+ buildChat(alice, 1),
+ *outgoingMessages.toArray(),
+ *incomingMessages.toArray()
+ )
+ }
+
+ @Test
+ fun contactMessageWithAllFields() {
+ importExport(
+ *standardFrames,
+ alice,
+ buildChat(alice, 1),
+ ChatItem(
+ chatId = 1,
+ authorId = selfRecipient.id,
+ dateSent = 150L,
+ sms = false,
+ outgoing = ChatItem.OutgoingMessageDetails(
+ sendStatus = listOf(SendStatus(alice.id, deliveryStatus = SendStatus.Status.READ, lastStatusUpdateTimestamp = -1))
+ ),
+ contactMessage = ContactMessage(
+ contact = listOf(
+ ContactAttachment(
+ name = ContactAttachment.Name(
+ givenName = "Given",
+ familyName = "Family",
+ prefix = "Prefix",
+ suffix = "Suffix",
+ middleName = "Middle",
+ displayName = "Display Name"
+ ),
+ organization = "Organization",
+ email = listOf(
+ ContactAttachment.Email(
+ value_ = "coolemail@gmail.com",
+ label = "Label",
+ type = ContactAttachment.Email.Type.HOME
+ ),
+ ContactAttachment.Email(
+ value_ = "coolemail2@gmail.com",
+ label = "Label2",
+ type = ContactAttachment.Email.Type.MOBILE
+ )
+ ),
+ address = listOf(
+ ContactAttachment.PostalAddress(
+ type = ContactAttachment.PostalAddress.Type.HOME,
+ label = "Label",
+ street = "Street",
+ pobox = "POBOX",
+ neighborhood = "Neighborhood",
+ city = "City",
+ region = "Region",
+ postcode = "15213",
+ country = "United States"
+ )
+ ),
+ number = listOf(
+ ContactAttachment.Phone(
+ value_ = "+14155551234",
+ type = ContactAttachment.Phone.Type.CUSTOM,
+ label = "Label"
+ )
+ ),
+ avatar = FilePointer(
+ attachmentLocator = FilePointer.AttachmentLocator(
+ cdnKey = "coolCdnKey",
+ cdnNumber = 2,
+ uploadTimestamp = System.currentTimeMillis(),
key = (1..32).map { it.toByte() }.toByteArray().toByteString(),
- digest = (1..64).map { it.toByte() }.toByteArray().toByteString(),
- size = 12345
+ size = 12345,
+ digest = (1..32).map { it.toByte() }.toByteArray().toByteString()
),
contentType = "image/png",
+ fileName = "very_cool_picture.png",
width = 100,
height = 200,
- caption = "Love this cool picture! Too bad u cant download it",
+ caption = "Love this cool picture!",
incrementalMacChunkSize = 0
- ),
- wasDownloaded = true
+ )
)
)
)
@@ -1256,6 +1423,76 @@ class ImportExportTest {
)
}
+ @Test
+ fun giftBadgeMessage() {
+ var dateSentStart = 100L
+ importExport(
+ *standardFrames,
+ alice,
+ buildChat(alice, 1),
+ ChatItem(
+ chatId = 1,
+ authorId = alice.id,
+ dateSent = dateSentStart++,
+ incoming = ChatItem.IncomingMessageDetails(
+ dateReceived = dateSentStart,
+ dateServerSent = dateSentStart,
+ read = true,
+ sealedSender = true
+ ),
+ giftBadge = GiftBadge(
+ receiptCredentialPresentation = Util.getSecretBytes(32).toByteString(),
+ state = GiftBadge.State.OPENED
+ )
+ ),
+ ChatItem(
+ chatId = 1,
+ authorId = alice.id,
+ dateSent = dateSentStart++,
+ incoming = ChatItem.IncomingMessageDetails(
+ dateReceived = dateSentStart,
+ dateServerSent = dateSentStart,
+ read = true,
+ sealedSender = true
+ ),
+ giftBadge = GiftBadge(
+ receiptCredentialPresentation = Util.getSecretBytes(32).toByteString(),
+ state = GiftBadge.State.FAILED
+ )
+ ),
+ ChatItem(
+ chatId = 1,
+ authorId = alice.id,
+ dateSent = dateSentStart++,
+ incoming = ChatItem.IncomingMessageDetails(
+ dateReceived = dateSentStart,
+ dateServerSent = dateSentStart,
+ read = true,
+ sealedSender = true
+ ),
+ giftBadge = GiftBadge(
+ receiptCredentialPresentation = Util.getSecretBytes(32).toByteString(),
+ state = GiftBadge.State.REDEEMED
+ )
+ ),
+ ChatItem(
+ chatId = 1,
+ authorId = alice.id,
+ dateSent = dateSentStart++,
+ incoming = ChatItem.IncomingMessageDetails(
+ dateReceived = dateSentStart,
+ dateServerSent = dateSentStart,
+ read = true,
+ sealedSender = true
+ ),
+ giftBadge = GiftBadge(
+ receiptCredentialPresentation = Util.getSecretBytes(32).toByteString(),
+ state = GiftBadge.State.UNOPENED
+ )
+ )
+ )
+ }
+
fun enumerateIncomingMessageDetails(dateSent: Long): List {
val details = mutableListOf()
details.add(
@@ -1361,8 +1598,7 @@ class ImportExportTest {
expirationTimerMs = 0,
muteUntilMs = 0,
markedUnread = false,
- dontNotifyForMentionsIfMuted = false,
- wallpaper = null
+ dontNotifyForMentionsIfMuted = false
)
}
@@ -1371,95 +1607,85 @@ class ImportExportTest {
* any standard frames (e.g. backup header).
*/
private fun exportFrames(vararg objects: Any): ByteArray {
+ outputBinProto(*objects)
val outputStream = ByteArrayOutputStream()
val writer = EncryptedBackupWriter(
- key = SignalStore.svr().getOrCreateMasterKey().deriveBackupKey(),
- aci = SignalStore.account().aci!!,
+ key = SignalStore.svr.getOrCreateMasterKey().deriveBackupKey(),
+ aci = SignalStore.account.aci!!,
outputStream = outputStream,
append = { mac -> outputStream.write(mac) }
)
writer.use {
- for (obj in objects) {
- when (obj) {
- is BackupInfo -> writer.write(obj)
- is AccountData -> writer.write(Frame(account = obj))
- is Recipient -> writer.write(Frame(recipient = obj))
- is Chat -> writer.write(Frame(chat = obj))
- is ChatItem -> writer.write(Frame(chatItem = obj))
- is AdHocCall -> writer.write(Frame(adHocCall = obj))
- is StickerPack -> writer.write(Frame(stickerPack = obj))
- else -> Assert.fail("invalid object $obj")
- }
- }
+ writer.writeFrames(*objects)
}
return outputStream.toByteArray()
}
- /**
- * Exports the passed in frames as a backup and then attempts to
- * import them.
- */
- private fun import(vararg objects: Any) {
- val importData = exportFrames(*objects)
- import(importData)
- }
-
private fun import(importData: ByteArray) {
BackupRepository.import(length = importData.size.toLong(), inputStreamFactory = { ByteArrayInputStream(importData) }, selfData = BackupRepository.SelfData(SELF_ACI, SELF_PNI, SELF_E164, SELF_PROFILE_KEY))
}
- /**
- * Export our current database as a backup.
- */
- private fun export(): ByteArray {
- val exportData = BackupRepository.export()
- return exportData
- }
-
private fun validate(importData: ByteArray): MessageBackup.ValidationResult {
val factory = { ByteArrayInputStream(importData) }
- val masterKey = SignalStore.svr().getOrCreateMasterKey()
+ val masterKey = SignalStore.svr.getOrCreateMasterKey()
val key = MessageBackupKey(masterKey.serialize(), org.signal.libsignal.protocol.ServiceId.Aci.parseFromBinary(SELF_ACI.toByteArray()))
return MessageBackup.validate(key, MessageBackup.Purpose.REMOTE_BACKUP, factory, importData.size.toLong())
}
/**
- * Imports the passed in frames and then exports them.
+ * Given some [Frame]s, this will do the following:
*
- * It will do a comparison to assert that the import and export
- * are equal.
+ * 1. Write the frames using an [EncryptedBackupWriter] and keep the result in memory (A).
+ * 2. Import those frames back into the local database.
+ * 3. Export the state of the local database and keep the result in memory (B).
+ * 4. Assert that (A) and (B) are identical. Or, in other words, assert that importing and exporting again results in the original backup data.
*/
private fun importExport(vararg objects: Any) {
+ val originalBackupData = exportFrames(*objects)
+
+ import(originalBackupData)
+
+ val generatedBackupData = BackupRepository.export()
+ compare(originalBackupData, generatedBackupData)
+ }
+
+ private fun BackupExportWriter.writeFrames(vararg objects: Any) {
+ for (obj in objects) {
+ when (obj) {
+ is BackupInfo -> write(obj)
+ is AccountData -> write(Frame(account = obj))
+ is Recipient -> write(Frame(recipient = obj))
+ is Chat -> write(Frame(chat = obj))
+ is ChatItem -> write(Frame(chatItem = obj))
+ is AdHocCall -> write(Frame(adHocCall = obj))
+ is StickerPack -> write(Frame(stickerPack = obj))
+ else -> Assert.fail("invalid object $obj")
+ }
+ }
+ }
+
+ private fun outputBinProto(vararg objects: Any) {
+ if (!OUTPUT_FILES) return
+
val outputStream = ByteArrayOutputStream()
- val writer = EncryptedBackupWriter(
- key = SignalStore.svr().getOrCreateMasterKey().deriveBackupKey(),
- aci = SignalStore.account().aci!!,
- outputStream = outputStream,
- append = { mac -> outputStream.write(mac) }
+ val plaintextWriter = PlainTextBackupWriter(
+ outputStream = outputStream
)
- writer.use {
- for (obj in objects) {
- when (obj) {
- is BackupInfo -> writer.write(obj)
- is AccountData -> writer.write(Frame(account = obj))
- is Recipient -> writer.write(Frame(recipient = obj))
- is Chat -> writer.write(Frame(chat = obj))
- is ChatItem -> writer.write(Frame(chatItem = obj))
- is AdHocCall -> writer.write(Frame(adHocCall = obj))
- is StickerPack -> writer.write(Frame(stickerPack = obj))
- else -> Assert.fail("invalid object $obj")
- }
- }
+ plaintextWriter.use {
+ it.writeFrames(*objects)
}
- val importData = outputStream.toByteArray()
- outputFile(importData)
- BackupRepository.import(length = importData.size.toLong(), inputStreamFactory = { ByteArrayInputStream(importData) }, selfData = BackupRepository.SelfData(SELF_ACI, SELF_PNI, SELF_E164, SELF_PROFILE_KEY))
- val export = export()
- compare(importData, export)
+ grantPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
+ val dir = File(Environment.getExternalStorageDirectory(), "backup-tests")
+ if (dir.mkdirs() || dir.exists()) {
+ FileOutputStream(File(dir, testName.methodName + ".binproto")).use {
+ it.write(outputStream.toByteArray())
+ it.flush()
+ }
+ }
}
private fun compare(import: ByteArray, export: ByteArray) {
@@ -1498,7 +1724,14 @@ class ImportExportTest {
for (f in framesExported) {
when {
f.account != null -> accountImported.add(f.account!!)
- f.recipient != null -> recipientsExported.add(f.recipient!!)
+ f.recipient != null -> {
+ val frameRecipient = f.recipient!!
+ if (frameRecipient.distributionList != null && frameRecipient.distributionList!!.distributionId == DistributionId.MY_STORY.asUuid().toByteArray().toByteString()) {
+ recipientsExported.add(frameRecipient.copy(distributionList = frameRecipient.distributionList!!.copyWithoutMembers()))
+ } else {
+ recipientsExported.add(f.recipient!!)
+ }
+ }
f.chat != null -> chatsExported.add(f.chat!!)
f.chatItem != null -> chatItemsExported.add(f.chatItem!!)
f.adHocCall != null -> callsExported.add(f.adHocCall!!)
@@ -1513,11 +1746,19 @@ class ImportExportTest {
prettyAssertEquals(stickersImported, stickersExported) { it.packId }
}
- private fun prettyAssertEquals(import: List, export: List) {
+ private fun DistributionListItem.copyWithoutMembers(): DistributionListItem {
+ return this.copy(
+ distributionList = this.distributionList?.copy(
+ memberRecipientIds = emptyList()
+ )
+ )
+ }
+
+ private inline fun prettyAssertEquals(import: List, export: List) {
Assert.assertEquals(import.size, export.size)
import.zip(export).forEach { (a1, a2) ->
if (a1 != a2) {
- Assert.fail("Items do not match: \n $a1 \n $a2")
+ Assert.fail("Items do not match:\n\n-- Pretty diff\n${getObjectDiff(a1, a2)}\n-- Full objects\n$a1\n$a2")
}
}
}
@@ -1526,19 +1767,31 @@ class ImportExportTest {
return nextFloat() < prob
}
- private fun > prettyAssertEquals(import: List, export: List, selector: (T) -> R?) {
+ private inline fun > prettyAssertEquals(import: List, export: List, crossinline selector: (T) -> R?) {
if (import.size != export.size) {
- var msg = StringBuilder()
+ val msg = StringBuilder()
+ msg.append("There's a different number of items in the lists!\n\n")
+
+ msg.append("Imported:\n")
for (i in import) {
msg.append(i)
msg.append("\n")
}
+ if (import.isEmpty()) {
+ msg.append("")
+ }
+ msg.append("\n")
+ msg.append("Exported:\n")
for (i in export) {
msg.append(i)
msg.append("\n")
}
+ if (export.isEmpty()) {
+ msg.append("")
+ }
Assert.fail(msg.toString())
}
+
Assert.assertEquals(import.size, export.size)
val sortedImport = import.sortedBy(selector)
val sortedExport = export.sortedBy(selector)
@@ -1549,9 +1802,9 @@ class ImportExportTest {
private fun readAllFrames(import: ByteArray, selfData: BackupRepository.SelfData): List {
val inputFactory = { ByteArrayInputStream(import) }
val frameReader = EncryptedBackupReader(
- key = SignalStore.svr().getOrCreateMasterKey().deriveBackupKey(),
+ key = SignalStore.svr.getOrCreateMasterKey().deriveBackupKey(),
aci = selfData.aci,
- streamLength = import.size.toLong(),
+ length = import.size.toLong(),
dataStream = inputFactory
)
val frames = ArrayList()
@@ -1562,25 +1815,9 @@ class ImportExportTest {
return frames
}
- private fun outputFile(importBytes: ByteArray, resultBytes: ByteArray? = null) {
- grantPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
- val dir = File(Environment.getExternalStorageDirectory(), "backup-tests")
- if (dir.mkdirs() || dir.exists()) {
- FileOutputStream(File(dir, testName.methodName + ".import")).use {
- it.write(importBytes)
- it.flush()
- }
-
- if (resultBytes != null) {
- FileOutputStream(File(dir, testName.methodName + ".result")).use {
- it.write(resultBytes)
- it.flush()
- }
- }
- }
- }
-
private fun grantPermissions(vararg permissions: String?) {
+ if (!OUTPUT_FILES) return
+
val auto: UiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
for (perm in permissions) {
auto.grantRuntimePermissionAsUser(InstrumentationRegistry.getInstrumentation().targetContext.packageName, perm, android.os.Process.myUserHandle())
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ImportExportTestSuite.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ImportExportTestSuite.kt
new file mode 100644
index 0000000000..3aa19b3105
--- /dev/null
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ImportExportTestSuite.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2024 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.thoughtcrime.securesms.backup.v2
+
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.signal.core.util.Base64
+import org.signal.core.util.StreamUtil
+import org.signal.libsignal.zkgroup.profiles.ProfileKey
+import org.thoughtcrime.securesms.keyvalue.SignalStore
+import org.whispersystems.signalservice.api.kbs.MasterKey
+import org.whispersystems.signalservice.api.push.ServiceId
+import java.io.ByteArrayInputStream
+import java.util.UUID
+import kotlin.random.Random
+
+@RunWith(Parameterized::class)
+class ImportExportTestSuite(private val path: String) {
+ companion object {
+ val SELF_ACI = ServiceId.ACI.from(UUID.fromString("77770000-b477-4f35-a824-d92987a63641"))
+ val SELF_PNI = ServiceId.PNI.from(UUID.fromString("77771111-b014-41fb-bf73-05cb2ec52910"))
+ const val SELF_E164 = "+10000000000"
+ val SELF_PROFILE_KEY = ProfileKey(Random.nextBytes(32))
+ val MASTER_KEY = Base64.decode("sHuBMP4ToZk4tcNU+S8eBUeCt8Am5EZnvuqTBJIR4Do")
+
+ const val TESTS_FOLDER = "backupTests"
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun data(): Collection> {
+ val testFiles = InstrumentationRegistry.getInstrumentation().context.resources.assets.list(TESTS_FOLDER)
+ return testFiles?.map { arrayOf(it) }!!.toList()
+ }
+ }
+
+ @Before
+ fun setup() {
+ SignalStore.svr.setMasterKey(MasterKey(MASTER_KEY), "1234")
+ SignalStore.account.setE164(SELF_E164)
+ SignalStore.account.setAci(SELF_ACI)
+ SignalStore.account.setPni(SELF_PNI)
+ SignalStore.account.generateAciIdentityKeyIfNecessary()
+ SignalStore.account.generatePniIdentityKeyIfNecessary()
+ }
+
+ @Test
+ fun testBinProto() {
+ val binProtoBytes: ByteArray = InstrumentationRegistry.getInstrumentation().context.resources.assets.open("${TESTS_FOLDER}/$path").use {
+ StreamUtil.readFully(it)
+ }
+ import(binProtoBytes)
+ val generatedBackupData = BackupRepository.export()
+ compare(binProtoBytes, generatedBackupData)
+ }
+
+ private fun import(importData: ByteArray) {
+ BackupRepository.import(
+ length = importData.size.toLong(),
+ inputStreamFactory = { ByteArrayInputStream(importData) },
+ selfData = BackupRepository.SelfData(SELF_ACI, SELF_PNI, SELF_E164, SELF_PROFILE_KEY),
+ plaintext = true
+ )
+ }
+
+ // TODO compare with libsignal's library
+ private fun compare(import: ByteArray, export: ByteArray) {
+ }
+}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModelTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModelTest.kt
deleted file mode 100644
index 49e18a29d6..0000000000
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModelTest.kt
+++ /dev/null
@@ -1,393 +0,0 @@
-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
-import org.signal.core.util.ThreadUtil
-import org.signal.libsignal.protocol.state.SignedPreKeyRecord
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
-import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
-import org.thoughtcrime.securesms.keyvalue.SignalStore
-import org.thoughtcrime.securesms.recipients.Recipient
-import org.thoughtcrime.securesms.registration.VerifyAccountRepository
-import org.thoughtcrime.securesms.registration.VerifyResponseProcessor
-import org.thoughtcrime.securesms.testing.Get
-import org.thoughtcrime.securesms.testing.MockProvider
-import org.thoughtcrime.securesms.testing.Post
-import org.thoughtcrime.securesms.testing.Put
-import org.thoughtcrime.securesms.testing.SignalActivityRule
-import org.thoughtcrime.securesms.testing.assertIs
-import org.thoughtcrime.securesms.testing.assertIsNot
-import org.thoughtcrime.securesms.testing.assertIsNotNull
-import org.thoughtcrime.securesms.testing.assertIsNull
-import org.thoughtcrime.securesms.testing.assertIsSize
-import org.thoughtcrime.securesms.testing.connectionFailure
-import org.thoughtcrime.securesms.testing.failure
-import org.thoughtcrime.securesms.testing.parsedRequestBody
-import org.thoughtcrime.securesms.testing.success
-import org.thoughtcrime.securesms.testing.timeout
-import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest
-import org.whispersystems.signalservice.api.push.ServiceId
-import org.whispersystems.signalservice.api.push.ServiceId.PNI
-import org.whispersystems.signalservice.internal.push.MismatchedDevices
-import org.whispersystems.signalservice.internal.push.PreKeyState
-import java.util.UUID
-
-@RunWith(AndroidJUnit4::class)
-class ChangeNumberViewModelTest {
-
- @get:Rule
- val harness = SignalActivityRule()
-
- private lateinit var viewModel: ChangeNumberViewModel
-
- @Before
- fun setUp() {
- ThreadUtil.runOnMainSync {
- viewModel = ChangeNumberViewModel(
- localNumber = harness.self.requireE164(),
- changeNumberRepository = ChangeNumberRepository(),
- savedState = SavedStateHandle(),
- password = SignalStore.account().servicePassword!!,
- verifyAccountRepository = VerifyAccountRepository(harness.application)
- )
-
- viewModel.setNewCountry(1)
- viewModel.setNewNationalNumber("5555550102")
- }
- }
-
- @After
- fun tearDown() {
- InstrumentationApplicationDependencyProvider.clearHandlers()
- }
-
- @Test
- fun testChangeNumber_givenOnlyPrimaryAndNoRegLock() {
- // GIVEN
- val aci = Recipient.self().requireServiceId()
- val newPni = PNI.from(UUID.randomUUID())
- lateinit var changeNumberRequest: ChangePhoneNumberRequest
- lateinit var setPreKeysRequest: PreKeyState
-
- InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
- Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
- Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
- Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
- Put("/v2/accounts/number") { r ->
- changeNumberRequest = r.parsedRequestBody()
- MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
- },
- Put("/v2/keys") { r ->
- setPreKeysRequest = r.parsedRequestBody()
- MockResponse().success()
- },
- Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
- )
-
- // WHEN
- viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
- viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().resultOrThrow
-
- // THEN
- assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
- }
-
- /**
- * If we encounter a server error, this means the server ack our request and rejected it. In this
- * case we know the change *did not* take on the server and can reset to a clean state.
- */
- @Test
- fun testChangeNumber_givenServerFailedApiCall() {
- // GIVEN
- val oldPni = Recipient.self().requirePni()
- val oldE164 = Recipient.self().requireE164()
-
- InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
- Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
- Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
- Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
- Put("/v2/accounts/number") { MockResponse().failure(500) }
- )
-
- // WHEN
- viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
- val processor: VerifyResponseProcessor = viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet()
-
- // THEN
- processor.isServerSentError() assertIs true
- Recipient.self().requireE164() assertIs oldE164
- Recipient.self().requirePni() assertIs oldPni
- SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
- }
-
- /**
- * If we encounter a non-server error like a timeout or bad SSL, we do not know the state of our change
- * number on the server side. We have to do a whoami call to query the server for our details and then
- * respond accordingly.
- *
- * In this case, the whoami is our old details, so we can know the change *did not* take on the server
- * and can reset to a clean state.
- */
- @Test
- fun testChangeNumber_givenNetworkFailedApiCallEnRouteToServer() {
- // GIVEN
- val aci = Recipient.self().requireServiceId()
- val oldPni = Recipient.self().requirePni()
- val oldE164 = Recipient.self().requireE164()
-
- InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
- Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
- Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
- Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
- Put("/v2/accounts/number") { MockResponse().connectionFailure() },
- Get("/v1/accounts/whoami") { MockResponse().success(MockProvider.createWhoAmIResponse(aci, oldPni, oldE164)) }
- )
-
- // WHEN
- viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
- val processor: VerifyResponseProcessor = viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet()
-
- // THEN
- processor.isServerSentError() assertIs false
- Recipient.self().requireE164() assertIs oldE164
- Recipient.self().requirePni() assertIs oldPni
- SignalStore.misc().isChangeNumberLocked assertIs false
- SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
- }
-
- /**
- * If we encounter a non-server error like a timeout or bad SSL, we do not know the state of our change
- * number on the server side. We have to do a whoami call to query the server for our details and then
- * respond accordingly.
- *
- * In this case, the whoami is our new details, so we can know the change *did* take on the server
- * and need to keep the app in a locked state. The test then uses the ChangeNumberLockActivity to unlock
- * 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()
- val oldPni = Recipient.self().requirePni()
- val oldE164 = Recipient.self().requireE164()
- val newPni = PNI.from(UUID.randomUUID())
-
- lateinit var changeNumberRequest: ChangePhoneNumberRequest
- lateinit var setPreKeysRequest: PreKeyState
-
- InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
- Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
- Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
- Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
- Put("/v2/accounts/number") { r ->
- changeNumberRequest = r.parsedRequestBody()
- MockResponse().timeout()
- },
- Get("/v1/accounts/whoami") { MockResponse().success(MockProvider.createWhoAmIResponse(aci, newPni, "+15555550102")) },
- Put("/v2/keys") { r ->
- setPreKeysRequest = r.parsedRequestBody()
- MockResponse().success()
- },
- Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
- )
-
- // WHEN
- viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
- val processor: VerifyResponseProcessor = viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet()
-
- // THEN
- processor.isServerSentError() assertIs false
- Recipient.self().requireE164() assertIs oldE164
- Recipient.self().requirePni() assertIs oldPni
- SignalStore.misc().isChangeNumberLocked assertIs true
- SignalStore.misc().pendingChangeNumberMetadata.assertIsNotNull()
-
- // WHEN AGAIN Processing lock
- val scenario = harness.launchActivity()
- scenario.onActivity {}
- ThreadUtil.sleep(500)
-
- // THEN AGAIN
- assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
- }
-
- @Test
- fun testChangeNumber_givenOnlyPrimaryAndRegistrationLock() {
- // GIVEN
- val aci = Recipient.self().requireServiceId()
- val newPni = PNI.from(UUID.randomUUID())
-
- lateinit var changeNumberRequest: ChangePhoneNumberRequest
- lateinit var setPreKeysRequest: PreKeyState
-
- InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
- Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
- Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
- Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
- Put("/v2/accounts/number") { r ->
- changeNumberRequest = r.parsedRequestBody()
- if (changeNumberRequest.registrationLock.isNullOrEmpty()) {
- MockResponse().failure(423, MockProvider.lockedFailure)
- } else {
- MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
- }
- },
- Put("/v2/keys") { r ->
- setPreKeysRequest = r.parsedRequestBody()
- MockResponse().success()
- },
- Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
- )
-
- // WHEN
- viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
- viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().also { processor ->
- processor.registrationLock() assertIs true
- Recipient.self().requirePni() assertIsNot newPni
- SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
- }
-
- viewModel.verifyCodeAndRegisterAccountWithRegistrationLock("pin").blockingGet().resultOrThrow
-
- // THEN
- assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
- }
-
- @Test
- fun testChangeNumber_givenMismatchedDevicesOnFirstCall() {
- // GIVEN
- val aci = Recipient.self().requireServiceId()
- val newPni = PNI.from(UUID.randomUUID())
- lateinit var changeNumberRequest: ChangePhoneNumberRequest
- lateinit var setPreKeysRequest: PreKeyState
-
- InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
- Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
- Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
- Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
- Put("/v2/accounts/number") { r ->
- changeNumberRequest = r.parsedRequestBody()
- if (changeNumberRequest.deviceMessages.isEmpty()) {
- MockResponse().failure(
- 409,
- MismatchedDevices().apply {
- missingDevices = listOf(2)
- extraDevices = emptyList()
- }
- )
- } else {
- MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
- }
- },
- Get("/v2/keys/$aci/2") {
- MockResponse().success(MockProvider.createPreKeyResponse(deviceId = 2))
- },
- Put("/v2/keys") { r ->
- setPreKeysRequest = r.parsedRequestBody()
- MockResponse().success()
- },
- Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
- )
-
- // WHEN
- viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
- viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().resultOrThrow
-
- // THEN
- assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
- }
-
- @Test
- fun testChangeNumber_givenRegLockAndMismatchedDevicesOnFirstTwoCalls() {
- // GIVEN
- val aci = Recipient.self().requireServiceId()
- val newPni = PNI.from(UUID.randomUUID())
-
- lateinit var changeNumberRequest: ChangePhoneNumberRequest
- lateinit var setPreKeysRequest: PreKeyState
-
- InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
- Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
- Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
- Put("/v2/accounts/number") { r ->
- changeNumberRequest = r.parsedRequestBody()
- if (changeNumberRequest.registrationLock.isNullOrEmpty()) {
- MockResponse().failure(423, MockProvider.lockedFailure)
- } else if (changeNumberRequest.deviceMessages.isEmpty()) {
- MockResponse().failure(
- 409,
- MismatchedDevices().apply {
- missingDevices = listOf(2)
- extraDevices = emptyList()
- }
- )
- } else if (changeNumberRequest.deviceMessages.size == 1) {
- MockResponse().failure(
- 409,
- MismatchedDevices().apply {
- missingDevices = listOf(2, 3)
- extraDevices = emptyList()
- }
- )
- } else {
- MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
- }
- },
- Get("/v2/keys/$aci/2") {
- MockResponse().success(MockProvider.createPreKeyResponse(deviceId = 2))
- },
- Get("/v2/keys/$aci/3") {
- MockResponse().success(MockProvider.createPreKeyResponse(deviceId = 3))
- },
- Put("/v2/keys") { r ->
- setPreKeysRequest = r.parsedRequestBody()
- MockResponse().success()
- },
- Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
- )
-
- // WHEN
- viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
- viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().also { processor ->
- processor.registrationLock() assertIs true
- Recipient.self().requirePni() assertIsNot newPni
- SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
- }
-
- viewModel.verifyCodeAndRegisterAccountWithRegistrationLock("pin").blockingGet().resultOrThrow
-
- // THEN
- assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
- }
-
- private fun assertSuccess(newPni: ServiceId, changeNumberRequest: ChangePhoneNumberRequest, setPreKeysRequest: PreKeyState) {
- val pniProtocolStore = ApplicationDependencies.getProtocolStore().pni()
- val pniMetadataStore = SignalStore.account().pniPreKeys
-
- Recipient.self().requireE164() assertIs "+15555550102"
- Recipient.self().requirePni() assertIs newPni
-
- SignalStore.account().pniRegistrationId assertIs changeNumberRequest.pniRegistrationIds["1"]!!
- SignalStore.account().pniIdentityKey.publicKey assertIs changeNumberRequest.pniIdentityKey
- pniMetadataStore.activeSignedPreKeyId assertIs changeNumberRequest.devicePniSignedPrekeys["1"]!!.keyId
-
- val activeSignedPreKey: SignedPreKeyRecord = pniProtocolStore.loadSignedPreKey(pniMetadataStore.activeSignedPreKeyId)
- activeSignedPreKey.keyPair.publicKey assertIs changeNumberRequest.devicePniSignedPrekeys["1"]!!.publicKey
- activeSignedPreKey.signature assertIs changeNumberRequest.devicePniSignedPrekeys["1"]!!.signature
-
- setPreKeysRequest.signedPreKey.publicKey assertIs activeSignedPreKey.keyPair.publicKey
- setPreKeysRequest.preKeys assertIsSize 100
-
- SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
- }
-}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/ConversationItemPreviewer.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/ConversationItemPreviewer.kt
index 2ea7db24ab..3e8fc8cca5 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/ConversationItemPreviewer.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/ConversationItemPreviewer.kt
@@ -154,7 +154,8 @@ class ConversationItemPreviewer {
false,
Optional.empty(),
Optional.empty(),
- System.currentTimeMillis()
+ System.currentTimeMillis(),
+ null
)
}
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/SafetyNumberChangeDialogPreviewer.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/SafetyNumberChangeDialogPreviewer.kt
index 597d5d5d63..f6d94001f4 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/SafetyNumberChangeDialogPreviewer.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/SafetyNumberChangeDialogPreviewer.kt
@@ -12,7 +12,7 @@ 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
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
+import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
@@ -64,7 +64,7 @@ class SafetyNumberChangeDialogPreviewer {
scenario.onActivity { conversationActivity ->
SafetyNumberBottomSheet
.forIdentityRecordsAndDestinations(
- identityRecords = ApplicationDependencies.getProtocolStore().aci().identities().getIdentityRecords(othersRecipients).identityRecords,
+ identityRecords = AppDependencies.protocolStore.aci().identities().getIdentityRecords(othersRecipients).identityRecords,
destinations = listOf(ContactSearchKey.RecipientSearchKey(myStoryRecipientId, true))
)
.show(conversationActivity.supportFragmentManager)
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt
index a9d4b2009d..c750d999fa 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt
@@ -290,6 +290,8 @@ class V2ConversationItemShapeTest {
override fun onChangeNumberUpdateContact(recipient: Recipient) = Unit
+ override fun onChangeProfileNameUpdateContact(recipient: Recipient) = Unit
+
override fun onCallToAction(action: String) = Unit
override fun onDonateClicked() = Unit
@@ -310,7 +312,7 @@ class V2ConversationItemShapeTest {
override fun goToMediaPreview(parent: ConversationItem?, sharedElement: View?, args: MediaIntentFactory.MediaPreviewArgs?) = Unit
- override fun onEditedIndicatorClicked(messageRecord: MessageRecord) = Unit
+ override fun onEditedIndicatorClicked(conversationMessage: ConversationMessage) = Unit
override fun onShowGroupDescriptionClicked(groupName: String, description: String, shouldLinkifyWebLinks: Boolean) = Unit
@@ -327,5 +329,6 @@ class V2ConversationItemShapeTest {
override fun onMessageRequestAcceptOptionsClicked() = Unit
override fun onItemDoubleClick(item: MultiselectPart) = Unit
+ override fun onPaymentTombstoneClicked() = Unit
}
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest_deduping.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest_deduping.kt
index d5f8015ce2..2940cc0204 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest_deduping.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest_deduping.kt
@@ -16,6 +16,7 @@ import org.signal.core.util.update
import org.thoughtcrime.securesms.attachments.AttachmentId
import org.thoughtcrime.securesms.attachments.Cdn
import org.thoughtcrime.securesms.attachments.PointerAttachment
+import org.thoughtcrime.securesms.backup.v2.BackupRepository.getMediaName
import org.thoughtcrime.securesms.database.AttachmentTable.TransformProperties
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.MediaStream
@@ -25,6 +26,8 @@ import org.thoughtcrime.securesms.mms.SentMediaQuality
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.MediaUtil
+import org.thoughtcrime.securesms.util.Util
+import org.whispersystems.signalservice.api.backup.MediaId
import org.whispersystems.signalservice.api.push.ServiceId
import java.io.File
import java.util.UUID
@@ -47,9 +50,9 @@ class AttachmentTableTest_deduping {
@Before
fun setUp() {
- SignalStore.account().setAci(ServiceId.ACI.from(UUID.randomUUID()))
- SignalStore.account().setPni(ServiceId.PNI.from(UUID.randomUUID()))
- SignalStore.account().setE164("+15558675309")
+ SignalStore.account.setAci(ServiceId.ACI.from(UUID.randomUUID()))
+ SignalStore.account.setPni(ServiceId.PNI.from(UUID.randomUUID()))
+ SignalStore.account.setE164("+15558675309")
SignalDatabase.attachments.deleteAllAttachments()
}
@@ -195,6 +198,8 @@ class AttachmentTableTest_deduping {
assertDataHashEndMatches(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id2, true)
+ assertRemoteFieldsMatch(id1, id2)
+ assertArchiveFieldsMatch(id1, id2)
}
// Mimics sending two files at once. Ensures all fields are kept in sync as we compress and upload.
@@ -220,6 +225,7 @@ class AttachmentTableTest_deduping {
assertDataHashStartMatches(id1, id2)
assertDataHashEndMatches(id1, id2)
assertRemoteFieldsMatch(id1, id2)
+ assertArchiveFieldsMatch(id1, id2)
}
// Re-use the upload when uploaded recently
@@ -234,6 +240,7 @@ class AttachmentTableTest_deduping {
assertDataHashStartMatches(id1, id2)
assertDataHashEndMatches(id1, id2)
assertRemoteFieldsMatch(id1, id2)
+ assertArchiveFieldsMatch(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id2, true)
}
@@ -253,6 +260,7 @@ class AttachmentTableTest_deduping {
assertSkipTransform(id2, true)
assertDoesNotHaveRemoteFields(id2)
+ assertArchiveFieldsMatch(id1, id2)
}
// This isn't so much "desirable behavior" as it is documenting how things work.
@@ -282,6 +290,7 @@ class AttachmentTableTest_deduping {
assertSkipTransform(id1, true)
assertSkipTransform(id1, true)
assertRemoteFieldsMatch(id1, id2)
+ assertArchiveFieldsMatch(id1, id2)
}
// This represents what would happen if you edited a video, sent it, then forwarded it. We should match, skip transform, and skip upload.
@@ -297,6 +306,7 @@ class AttachmentTableTest_deduping {
assertSkipTransform(id1, true)
assertSkipTransform(id1, true)
assertRemoteFieldsMatch(id1, id2)
+ assertArchiveFieldsMatch(id1, id2)
}
// This represents what would happen if you edited a video, sent it, then forwarded it, but *edited the forwarded video*. We should not dedupe.
@@ -327,6 +337,7 @@ class AttachmentTableTest_deduping {
assertSkipTransform(id1, true)
assertSkipTransform(id1, true)
assertRemoteFieldsMatch(id1, id2)
+ assertArchiveFieldsMatch(id1, id2)
}
// This represents what would happen if you sent an image using high quality, then forwarded it using standard quality.
@@ -343,6 +354,7 @@ class AttachmentTableTest_deduping {
assertSkipTransform(id1, true)
assertSkipTransform(id1, true)
assertRemoteFieldsMatch(id1, id2)
+ assertArchiveFieldsMatch(id1, id2)
}
// Make sure that files marked as unhashable are all updated together
@@ -457,6 +469,7 @@ class AttachmentTableTest_deduping {
assertDataHashStartMatches(id1, id2)
assertDataHashEndMatches(id1, id2)
assertRemoteFieldsMatch(id1, id2)
+ assertArchiveFieldsMatch(id1, id2)
}
// Making sure things work for quotes of videos, which have trickier transform properties
@@ -470,6 +483,7 @@ class AttachmentTableTest_deduping {
assertDataFilesAreTheSame(id1, id2)
assertDataHashEndMatches(id1, id2)
assertRemoteFieldsMatch(id1, id2)
+ assertArchiveFieldsMatch(id1, id2)
}
}
@@ -648,6 +662,15 @@ class AttachmentTableTest_deduping {
fun upload(attachmentId: AttachmentId, uploadTimestamp: Long = System.currentTimeMillis()) {
SignalDatabase.attachments.finalizeAttachmentAfterUpload(attachmentId, createPointerAttachment(attachmentId, uploadTimestamp), uploadTimestamp)
+
+ val attachment = SignalDatabase.attachments.getAttachment(attachmentId)!!
+ SignalDatabase.attachments.setArchiveData(
+ attachmentId = attachmentId,
+ archiveCdn = Cdn.CDN_3.cdnNumber,
+ archiveMediaName = attachment.getMediaName().name,
+ archiveThumbnailMediaId = MediaId(Util.getSecretBytes(15)).encode(),
+ archiveMediaId = MediaId(Util.getSecretBytes(15)).encode()
+ )
}
fun delete(attachmentId: AttachmentId) {
@@ -746,6 +769,15 @@ class AttachmentTableTest_deduping {
assertEquals(lhsAttachment.cdn.cdnNumber, rhsAttachment.cdn.cdnNumber)
}
+ fun assertArchiveFieldsMatch(lhs: AttachmentId, rhs: AttachmentId) {
+ val lhsAttachment = SignalDatabase.attachments.getAttachment(lhs)!!
+ val rhsAttachment = SignalDatabase.attachments.getAttachment(rhs)!!
+
+ assertEquals(lhsAttachment.archiveCdn, rhsAttachment.archiveCdn)
+ assertEquals(lhsAttachment.archiveMediaName, rhsAttachment.archiveMediaName)
+ assertEquals(lhsAttachment.archiveMediaId, rhsAttachment.archiveMediaId)
+ }
+
fun assertDoesNotHaveRemoteFields(attachmentId: AttachmentId) {
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId)!!
assertEquals(0, databaseAttachment.uploadTimestamp)
@@ -792,7 +824,8 @@ class AttachmentTableTest_deduping {
uploadTimestamp,
databaseAttachment.caption,
databaseAttachment.stickerLocator,
- databaseAttachment.blurHash
+ databaseAttachment.blurHash,
+ databaseAttachment.uuid
)
}
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/CallTableTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/CallTableTest.kt
index 38c63ce8b5..b02dad66b9 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/CallTableTest.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/CallTableTest.kt
@@ -56,7 +56,7 @@ class CallTableTest {
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
- SignalDatabase.calls.deleteGroupCall(call!!)
+ SignalDatabase.calls.markCallDeletedFromSyncEvent(call!!)
val deletedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val oldestDeletionTimestamp = SignalDatabase.calls.getOldestDeletionTimestamp()
@@ -69,9 +69,10 @@ class CallTableTest {
@Test
fun givenNoPreExistingEvent_whenIDeleteGroupCall_thenIInsertAndMarkCallDeleted() {
val callId = 1L
- SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(
+ SignalDatabase.calls.insertDeletedCallFromSyncEvent(
callId,
groupRecipientId,
+ CallTable.Type.GROUP_CALL,
CallTable.Direction.OUTGOING,
System.currentTimeMillis()
)
@@ -438,11 +439,12 @@ class CallTableTest {
@Test
fun givenADeletedCallEvent_whenIReceiveARingUpdate_thenIIgnoreTheRingUpdate() {
val callId = 1L
- SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(
+ SignalDatabase.calls.insertDeletedCallFromSyncEvent(
callId = callId,
recipientId = groupRecipientId,
direction = CallTable.Direction.INCOMING,
- timestamp = System.currentTimeMillis()
+ timestamp = System.currentTimeMillis(),
+ type = CallTable.Type.GROUP_CALL
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/DatabaseConsistencyTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/DatabaseConsistencyTest.kt
index dbd03dfae7..fc6938e72e 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/DatabaseConsistencyTest.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/DatabaseConsistencyTest.kt
@@ -15,7 +15,7 @@ import org.signal.core.util.getIndexes
import org.signal.core.util.readToList
import org.signal.core.util.requireNonNullString
import org.thoughtcrime.securesms.database.helpers.SignalDatabaseMigrations
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
+import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.testing.SignalActivityRule
/**
@@ -30,7 +30,7 @@ class DatabaseConsistencyTest {
@Test
fun testUpgradeConsistency() {
val currentVersionStatements = SignalDatabase.rawDatabase.getAllCreateStatements()
- val testHelper = InMemoryTestHelper(ApplicationDependencies.getApplication()).also {
+ val testHelper = InMemoryTestHelper(AppDependencies.application).also {
it.onUpgrade(it.writableDatabase, 181, SignalDatabaseMigrations.DATABASE_VERSION)
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/DatabaseObserverTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/DatabaseObserverTest.kt
index c86e5bdb57..f1118138e1 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/DatabaseObserverTest.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/DatabaseObserverTest.kt
@@ -8,7 +8,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.concurrent.SignalExecutors
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
+import org.thoughtcrime.securesms.dependencies.AppDependencies
import java.util.concurrent.CountDownLatch
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
@@ -26,7 +26,7 @@ class DatabaseObserverTest {
@Before
fun setup() {
db = SignalDatabase.instance!!.signalWritableDatabase
- observer = ApplicationDependencies.getDatabaseObserver()
+ observer = AppDependencies.databaseObserver
}
@Test
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/DistributionListTablesTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/DistributionListTablesTest.kt
index df616f9c70..33d21a4f61 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/DistributionListTablesTest.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/DistributionListTablesTest.kt
@@ -25,15 +25,6 @@ class DistributionListTablesTest {
Assert.assertNotNull(id)
}
- @Test
- fun createList_whenNameConflict_failToInsert() {
- val id: DistributionListId? = distributionDatabase.createList("test", recipientList(1, 2, 3))
- Assert.assertNotNull(id)
-
- val id2: DistributionListId? = distributionDatabase.createList("test", recipientList(1, 2, 3))
- Assert.assertNull(id2)
- }
-
@Test
fun getList_returnCorrectList() {
createRecipients(3)
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/GroupTableTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/GroupTableTest.kt
index 0ca921ede5..195ff7d8b3 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/GroupTableTest.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/GroupTableTest.kt
@@ -167,8 +167,8 @@ class GroupTableTest {
@Test
fun givenTwoGroupsWithoutMembers_whenIQueryThem_thenIExpectEach() {
- val g1 = insertPushGroup(listOf())
- val g2 = insertPushGroup(listOf())
+ val g1 = insertPushGroup(members = emptyList())
+ val g2 = insertPushGroup(members = emptyList())
val gr1 = groupTable.getGroup(g1)
val gr2 = groupTable.getGroup(g2)
@@ -195,6 +195,85 @@ class GroupTableTest {
assertEquals(groups[0].id, groupInCommon)
}
+ @Test
+ fun givenTwoGroupsWithANameThatSharesAToken_whenISearchForTheSharedToken_thenIExpectBothGroups() {
+ insertPushGroup("Group Alice")
+ insertPushGroup("Group Bob")
+
+ SignalDatabase.groups.queryGroupsByTitle(
+ inputQuery = "Group",
+ includeInactive = false,
+ excludeV1 = false,
+ excludeMms = false
+ ).use {
+ assertEquals(2, it.cursor?.count)
+
+ val firstGroup = it.getNext()
+ val secondGroup = it.getNext()
+
+ assertEquals("Group Alice", firstGroup?.title)
+ assertEquals("Group Bob", secondGroup?.title)
+ }
+ }
+
+ @Test
+ fun givenTwoGroupsWithANameThatSharesAToken_whenISearchForAnUnsharedToken_thenIExpectOneGroup() {
+ insertPushGroup("Group Alice")
+ insertPushGroup("Group Bob")
+
+ SignalDatabase.groups.queryGroupsByTitle(
+ inputQuery = "Alice",
+ includeInactive = false,
+ excludeV1 = false,
+ excludeMms = false
+ ).use {
+ assertEquals(1, it.cursor?.count)
+
+ val firstGroup = it.getNext()
+
+ assertEquals("Group Alice", firstGroup?.title)
+ }
+ }
+
+ @Test
+ fun givenAGroupWithThreeTokens_whenISearchForTheFirstAndLastToken_thenIExpectThatGroup() {
+ insertPushGroup("Group & Alice")
+
+ SignalDatabase.groups.queryGroupsByTitle(
+ inputQuery = "Group Alice",
+ includeInactive = false,
+ excludeV1 = false,
+ excludeMms = false
+ ).use {
+ assertEquals(1, it.cursor?.count)
+
+ val firstGroup = it.getNext()
+
+ assertEquals("Group & Alice", firstGroup?.title)
+ }
+ }
+
+ @Test
+ fun givenTwoGroupsWithSharedTokens_whenISearchForAnExactMatch_thenIExpectThatGroupFirst() {
+ insertPushGroup("Group Alice Bob")
+ insertPushGroup("Group Bob")
+
+ SignalDatabase.groups.queryGroupsByTitle(
+ inputQuery = "Group Bob",
+ includeInactive = false,
+ excludeV1 = false,
+ excludeMms = false
+ ).use {
+ assertEquals(2, it.cursor?.count)
+
+ val firstGroup = it.getNext()
+ val second = it.getNext()
+
+ assertEquals("Group Bob", firstGroup?.title)
+ assertEquals("Group Alice Bob", second?.title)
+ }
+ }
+
private fun insertThread(groupId: GroupId): Long {
val groupRecipient = SignalDatabase.recipients.getByGroupId(groupId).get()
return SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(groupRecipient))
@@ -214,6 +293,7 @@ class GroupTableTest {
}
private fun insertPushGroup(
+ title: String = "Test Group",
members: List = listOf(
DecryptedMember.Builder()
.aciBytes(harness.self.requireAci().toByteString())
@@ -229,11 +309,12 @@ class GroupTableTest {
): GroupId {
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val decryptedGroupState = DecryptedGroup.Builder()
+ .title(title)
.members(members)
.revision(0)
.build()
- return groupTable.create(groupMasterKey, decryptedGroupState)!!
+ return groupTable.create(groupMasterKey, decryptedGroupState, null)!!
}
private fun insertPushGroupWithSelfAndOthers(others: List): GroupId {
@@ -258,6 +339,6 @@ class GroupTableTest {
.revision(0)
.build()
- return groupTable.create(groupMasterKey, decryptedGroupState)!!
+ return groupTable.create(groupMasterKey, decryptedGroupState, null)!!
}
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/KyberPreKeyTableTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/KyberPreKeyTableTest.kt
index 4736aac117..e52deb27ec 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/KyberPreKeyTableTest.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/KyberPreKeyTableTest.kt
@@ -159,7 +159,7 @@ class KyberPreKeyTableTest {
val count = SignalDatabase.rawDatabase
.update(KyberPreKeyTable.TABLE_NAME)
.values(KyberPreKeyTable.STALE_TIMESTAMP to staleTime)
- .where("${KyberPreKeyTable.ACCOUNT_ID} = ? AND ${KyberPreKeyTable.KEY_ID} = $id", account)
+ .where("${KyberPreKeyTable.ACCOUNT_ID} = ? AND ${KyberPreKeyTable.KEY_ID} = $id", account.toAccountId())
.run()
assertEquals(1, count)
@@ -169,8 +169,15 @@ class KyberPreKeyTableTest {
return SignalDatabase.rawDatabase
.select(KyberPreKeyTable.STALE_TIMESTAMP)
.from(KyberPreKeyTable.TABLE_NAME)
- .where("${KyberPreKeyTable.ACCOUNT_ID} = ? AND ${KyberPreKeyTable.KEY_ID} = $id", account)
+ .where("${KyberPreKeyTable.ACCOUNT_ID} = ? AND ${KyberPreKeyTable.KEY_ID} = $id", account.toAccountId())
.run()
.readToSingleObject { it.requireLongOrNull(KyberPreKeyTable.STALE_TIMESTAMP) }
}
+
+ private fun ServiceId.toAccountId(): String {
+ return when (this) {
+ is ACI -> this.toString()
+ is PNI -> KyberPreKeyTable.PNI_ACCOUNT_ID
+ }
+ }
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/MessageTableTest_gifts.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/MessageTableTest_gifts.kt
index 3fe7d7ed5c..01f4c5cd68 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/MessageTableTest_gifts.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/MessageTableTest_gifts.kt
@@ -30,8 +30,8 @@ class MessageTableTest_gifts {
mms.deleteAllThreads()
- SignalStore.account().setAci(localAci)
- SignalStore.account().setPni(localPni)
+ SignalStore.account.setAci(localAci)
+ SignalStore.account.setPni(localPni)
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())) }
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/MmsTableTest_stories.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/MmsTableTest_stories.kt
index 792aa96b14..23b918a9f8 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/MmsTableTest_stories.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/MmsTableTest_stories.kt
@@ -40,14 +40,14 @@ class MmsTableTest_stories {
mms.deleteAllThreads()
- SignalStore.account().setAci(localAci)
- SignalStore.account().setPni(localPni)
+ SignalStore.account.setAci(localAci)
+ SignalStore.account.setPni(localPni)
myStory = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromDistributionListId(DistributionListId.MY_STORY))
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())) }
releaseChannelRecipient = Recipient.resolved(SignalDatabase.recipients.insertReleaseChannelRecipient())
- SignalStore.releaseChannelValues().setReleaseChannelRecipientId(releaseChannelRecipient.id)
+ SignalStore.releaseChannel.setReleaseChannelRecipientId(releaseChannelRecipient.id)
}
@Test
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/OneTimePreKeyTableTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/OneTimePreKeyTableTest.kt
index beaacf0d34..e28b54b891 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/OneTimePreKeyTableTest.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/OneTimePreKeyTableTest.kt
@@ -120,7 +120,7 @@ class OneTimePreKeyTableTest {
val count = SignalDatabase.rawDatabase
.update(OneTimePreKeyTable.TABLE_NAME)
.values(OneTimePreKeyTable.STALE_TIMESTAMP to staleTime)
- .where("${OneTimePreKeyTable.ACCOUNT_ID} = ? AND ${OneTimePreKeyTable.KEY_ID} = $id", account)
+ .where("${OneTimePreKeyTable.ACCOUNT_ID} = ? AND ${OneTimePreKeyTable.KEY_ID} = $id", account.toAccountId())
.run()
assertEquals(1, count)
@@ -130,8 +130,15 @@ class OneTimePreKeyTableTest {
return SignalDatabase.rawDatabase
.select(OneTimePreKeyTable.STALE_TIMESTAMP)
.from(OneTimePreKeyTable.TABLE_NAME)
- .where("${OneTimePreKeyTable.ACCOUNT_ID} = ? AND ${OneTimePreKeyTable.KEY_ID} = $id", account)
+ .where("${OneTimePreKeyTable.ACCOUNT_ID} = ? AND ${OneTimePreKeyTable.KEY_ID} = $id", account.toAccountId())
.run()
.readToSingleObject { it.requireLongOrNull(OneTimePreKeyTable.STALE_TIMESTAMP) }
}
+
+ private fun ServiceId.toAccountId(): String {
+ return when (this) {
+ is ACI -> this.toString()
+ is PNI -> OneTimePreKeyTable.PNI_ACCOUNT_ID
+ }
+ }
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_applyStorageSyncContactUpdate.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_applyStorageSyncContactUpdate.kt
index a795cfbab3..dfd59bf614 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_applyStorageSyncContactUpdate.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_applyStorageSyncContactUpdate.kt
@@ -9,7 +9,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
+import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.storage.StorageRecordUpdate
import org.thoughtcrime.securesms.storage.StorageSyncModels
@@ -28,7 +28,7 @@ class RecipientTableTest_applyStorageSyncContactUpdate {
@Test
fun insertMessageOnVerifiedToDefault() {
// GIVEN
- val identities = ApplicationDependencies.getProtocolStore().aci().identities()
+ val identities = AppDependencies.protocolStore.aci().identities()
val other = Recipient.resolved(harness.others[0])
MmsHelper.insert(recipient = other)
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_getAndPossiblyMerge.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_getAndPossiblyMerge.kt
index aba11af7cd..624395ecfd 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_getAndPossiblyMerge.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_getAndPossiblyMerge.kt
@@ -34,7 +34,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.ReactionRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent
import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
+import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.IncomingMessage
@@ -53,9 +53,9 @@ class RecipientTableTest_getAndPossiblyMerge {
@Before
fun setup() {
- SignalStore.account().setE164(E164_SELF)
- SignalStore.account().setAci(ACI_SELF)
- SignalStore.account().setPni(PNI_SELF)
+ SignalStore.account.setE164(E164_SELF)
+ SignalStore.account.setAci(ACI_SELF)
+ SignalStore.account.setPni(PNI_SELF)
}
@Test
@@ -1113,8 +1113,8 @@ class RecipientTableTest_getAndPossiblyMerge {
SignalDatabase.rawDatabase.execSQL("DELETE FROM $table")
}
- ApplicationDependencies.getRecipientCache().clear()
- ApplicationDependencies.getRecipientCache().clearSelf()
+ AppDependencies.recipientCache.clear()
+ AppDependencies.recipientCache.clearSelf()
RecipientId.clearCache()
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/SmsDatabaseTest_collapseJoinRequestEventsIfPossible.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/SmsDatabaseTest_collapseJoinRequestEventsIfPossible.kt
index b00aba7111..5b89e19e1d 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/SmsDatabaseTest_collapseJoinRequestEventsIfPossible.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/SmsDatabaseTest_collapseJoinRequestEventsIfPossible.kt
@@ -10,7 +10,9 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.Hex
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
+import org.thoughtcrime.securesms.database.model.GroupsV2UpdateMessageConverter
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
+import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription
import org.thoughtcrime.securesms.database.model.databaseprotos.addMember
import org.thoughtcrime.securesms.database.model.databaseprotos.addRequestingMember
import org.thoughtcrime.securesms.database.model.databaseprotos.deleteRequestingMember
@@ -44,8 +46,8 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
recipients = SignalDatabase.recipients
sms = SignalDatabase.messages
- SignalStore.account().setAci(localAci)
- SignalStore.account().setPni(localPni)
+ SignalStore.account.setAci(localAci)
+ SignalStore.account.setPni(localPni)
alice = recipients.getOrInsertFromServiceId(aliceServiceId)
bob = recipients.getOrInsertFromServiceId(bobServiceId)
@@ -286,11 +288,18 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
private fun groupUpdateMessage(sender: RecipientId, groupContext: DecryptedGroupV2Context): IncomingMessage {
wallClock++
+
+ val updateDescription = GV2UpdateDescription(
+ gv2ChangeDescription = groupContext,
+ groupChangeUpdate = GroupsV2UpdateMessageConverter.translateDecryptedChangeUpdate(SignalStore.account.getServiceIds(), groupContext)
+ )
+
return IncomingMessage.groupUpdate(
from = sender,
timestamp = wallClock,
groupId = groupId,
- groupContext = groupContext,
+ update = updateDescription,
+ isGroupAdd = false,
serverGuid = null
)
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/UriAttachmentBuilder.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/UriAttachmentBuilder.kt
index 828b75fe71..a51693a2ee 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/UriAttachmentBuilder.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/UriAttachmentBuilder.kt
@@ -5,6 +5,7 @@ import org.thoughtcrime.securesms.attachments.UriAttachment
import org.thoughtcrime.securesms.audio.AudioHash
import org.thoughtcrime.securesms.blurhash.BlurHash
import org.thoughtcrime.securesms.stickers.StickerLocator
+import java.util.UUID
object UriAttachmentBuilder {
fun build(
@@ -22,23 +23,28 @@ object UriAttachmentBuilder {
stickerLocator: StickerLocator? = null,
blurHash: BlurHash? = null,
audioHash: AudioHash? = null,
- transformProperties: AttachmentTable.TransformProperties? = null
+ transformProperties: AttachmentTable.TransformProperties? = null,
+ uuid: UUID? = UUID.randomUUID()
): UriAttachment {
return UriAttachment(
- uri,
- contentType,
- transferState,
- size,
- fileName,
- voiceNote,
- borderless,
- videoGif,
- quote,
- caption,
- stickerLocator,
- blurHash,
- audioHash,
- transformProperties
+ dataUri = uri,
+ contentType = contentType,
+ transferState = transferState,
+ size = size,
+ width = 0,
+ height = 0,
+ fileName = fileName,
+ fastPreflightId = null,
+ voiceNote = voiceNote,
+ borderless = borderless,
+ videoGif = videoGif,
+ quote = quote,
+ caption = caption,
+ stickerLocator = stickerLocator,
+ blurHash = blurHash,
+ audioHash = audioHash,
+ transformProperties = transformProperties,
+ uuid = uuid
)
}
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/helpers/migration/FixInAppCurrencyIfAbleTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/helpers/migration/FixInAppCurrencyIfAbleTest.kt
new file mode 100644
index 0000000000..ea471c92ad
--- /dev/null
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/helpers/migration/FixInAppCurrencyIfAbleTest.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2024 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.thoughtcrime.securesms.database.helpers.migration
+
+import android.app.Application
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.signal.core.util.readToSingleObject
+import org.signal.core.util.requireNonNullString
+import org.signal.core.util.select
+import org.signal.core.util.update
+import org.signal.donations.InAppPaymentType
+import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toDecimalValue
+import org.thoughtcrime.securesms.database.InAppPaymentSubscriberTable
+import org.thoughtcrime.securesms.database.InAppPaymentTable
+import org.thoughtcrime.securesms.database.SignalDatabase
+import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
+import org.thoughtcrime.securesms.database.model.databaseprotos.FiatValue
+import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
+import org.thoughtcrime.securesms.testing.SignalDatabaseRule
+import org.thoughtcrime.securesms.testing.assertIs
+import org.whispersystems.signalservice.api.subscriptions.SubscriberId
+import java.math.BigDecimal
+import java.util.Currency
+
+@RunWith(AndroidJUnit4::class)
+class FixInAppCurrencyIfAbleTest {
+
+ @get:Rule
+ val harness = SignalDatabaseRule(deleteAllThreadsOnEachRun = false)
+
+ @Test
+ fun givenNoSubscribers_whenIMigrate_thenIDoNothing() {
+ migrate()
+ }
+
+ @Test
+ fun givenASubscriberButNoPayment_whenIMigrate_thenIDoNothing() {
+ val subscriber = insertSubscriber("USD")
+ clearCurrencyCode(subscriber)
+ migrate()
+
+ getCurrencyCode(subscriber) assertIs ""
+ }
+
+ @Test
+ fun givenASubscriberAndMismatchedPayment_whenIMigrate_thenIDoNothing() {
+ val subscriber = insertSubscriber("USD")
+ val otherSubscriber = insertSubscriber("EUR")
+ insertPayment(otherSubscriber)
+ clearCurrencyCode(subscriber)
+ migrate()
+
+ getCurrencyCode(subscriber) assertIs ""
+ }
+
+ @Test
+ fun givenASubscriberAndPaymentWithNoSubscriber_whenIMigrate_thenDoNothing() {
+ val subscriber = insertSubscriber("USD")
+ insertPayment(null)
+ clearCurrencyCode(subscriber)
+ migrate()
+
+ getCurrencyCode(subscriber) assertIs ""
+ }
+
+ @Test
+ fun givenASubscriberAndMatchingPayment_whenIMigrate_thenUpdateCurrencyCode() {
+ val subscriber = insertSubscriber("USD")
+ insertPayment(subscriber)
+ clearCurrencyCode(subscriber)
+ migrate()
+
+ getCurrencyCode(subscriber) assertIs "USD"
+ }
+
+ @Test
+ fun givenASupercededSubscriber_whenIMigrate_thenIDoNothing() {
+ val oldSubscriber = insertSubscriber("USD")
+ insertPayment(oldSubscriber)
+ clearCurrencyCode(oldSubscriber)
+ insertSubscriber("USD")
+ migrate()
+ }
+
+ private fun migrate() {
+ V236_FixInAppSubscriberCurrencyIfAble.migrate(
+ context = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application,
+ db = SignalDatabase.rawDatabase,
+ oldVersion = 0,
+ newVersion = 0
+ )
+ }
+
+ private fun insertSubscriber(currencyCode: String): InAppPaymentSubscriberRecord {
+ val record = InAppPaymentSubscriberRecord(
+ subscriberId = SubscriberId.generate(),
+ currency = Currency.getInstance(currencyCode),
+ type = InAppPaymentSubscriberRecord.Type.DONATION,
+ requiresCancel = false,
+ paymentMethodType = InAppPaymentData.PaymentMethodType.PAYPAL
+ )
+
+ SignalDatabase.inAppPaymentSubscribers.insertOrReplace(record)
+
+ return record
+ }
+
+ private fun clearCurrencyCode(inAppPaymentSubscriberRecord: InAppPaymentSubscriberRecord) {
+ SignalDatabase.rawDatabase.update(InAppPaymentSubscriberTable.TABLE_NAME)
+ .values(InAppPaymentSubscriberTable.CURRENCY_CODE to "")
+ .where("${InAppPaymentSubscriberTable.SUBSCRIBER_ID} = ?", inAppPaymentSubscriberRecord.subscriberId.serialize())
+ .run()
+ }
+
+ private fun getCurrencyCode(inAppPaymentSubscriberRecord: InAppPaymentSubscriberRecord): String {
+ return SignalDatabase.rawDatabase.select(InAppPaymentSubscriberTable.CURRENCY_CODE)
+ .from(InAppPaymentSubscriberTable.TABLE_NAME)
+ .where("${InAppPaymentSubscriberTable.SUBSCRIBER_ID} = ?", inAppPaymentSubscriberRecord.subscriberId.serialize())
+ .run()
+ .readToSingleObject { it.requireNonNullString(InAppPaymentSubscriberTable.CURRENCY_CODE) }!!
+ }
+
+ private fun insertPayment(inAppPaymentSubscriberRecord: InAppPaymentSubscriberRecord?): InAppPaymentTable.InAppPayment {
+ val id = SignalDatabase.inAppPayments.insert(
+ type = InAppPaymentType.RECURRING_DONATION,
+ state = InAppPaymentTable.State.END,
+ subscriberId = inAppPaymentSubscriberRecord?.subscriberId,
+ endOfPeriod = null,
+ inAppPaymentData = InAppPaymentData(
+ amount = FiatValue(
+ currencyCode = inAppPaymentSubscriberRecord?.currency?.currencyCode ?: "USD",
+ amount = BigDecimal.ONE.toDecimalValue()
+ ),
+ level = 200,
+ paymentMethodType = inAppPaymentSubscriberRecord?.paymentMethodType ?: InAppPaymentData.PaymentMethodType.UNKNOWN
+ )
+ )
+
+ return SignalDatabase.inAppPayments.getById(id)!!
+ }
+}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/dependencies/InstrumentationApplicationDependencyProvider.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/dependencies/InstrumentationApplicationDependencyProvider.kt
index ab4b457930..8297bc67c9 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/dependencies/InstrumentationApplicationDependencyProvider.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/dependencies/InstrumentationApplicationDependencyProvider.kt
@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.dependencies
import android.app.Application
+import io.mockk.spyk
import okhttp3.ConnectionSpec
import okhttp3.Response
import okhttp3.WebSocket
@@ -24,6 +25,9 @@ import org.thoughtcrime.securesms.testing.Get
import org.thoughtcrime.securesms.testing.Verb
import org.thoughtcrime.securesms.testing.runSync
import org.thoughtcrime.securesms.testing.success
+import org.whispersystems.signalservice.api.SignalServiceDataStore
+import org.whispersystems.signalservice.api.SignalServiceMessageSender
+import org.whispersystems.signalservice.api.SignalWebSocket
import org.whispersystems.signalservice.api.push.TrustStore
import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl
import org.whispersystems.signalservice.internal.configuration.SignalCdsiUrl
@@ -37,12 +41,13 @@ import org.whispersystems.signalservice.internal.configuration.SignalSvr2Url
*
* Handles setting up a mock web server for API calls, and provides mockable versions of [SignalServiceNetworkAccess].
*/
-class InstrumentationApplicationDependencyProvider(val application: Application, private val default: ApplicationDependencyProvider) : ApplicationDependencies.Provider by default {
+class InstrumentationApplicationDependencyProvider(val application: Application, private val default: ApplicationDependencyProvider) : AppDependencies.Provider by default {
private val serviceTrustStore: TrustStore
private val uncensoredConfiguration: SignalServiceConfiguration
private val serviceNetworkAccessMock: SignalServiceNetworkAccess
private val recipientCache: LiveRecipientCache
+ private var signalServiceMessageSender: SignalServiceMessageSender? = null
init {
runSync {
@@ -53,18 +58,21 @@ class InstrumentationApplicationDependencyProvider(val application: Application,
Get("/v1/websocket/?login=") {
MockResponse().success().withWebSocketUpgrade(mockIdentifiedWebSocket)
},
- Get("/v1/websocket", { !it.path.contains("login") }) {
+ Get("/v1/websocket", {
+ val path = it.path
+ return@Get path == null || !path.contains("login")
+ }) {
MockResponse().success().withWebSocketUpgrade(object : WebSocketListener() {})
}
)
}
- webServer.setDispatcher(object : Dispatcher() {
+ webServer.dispatcher = object : Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
val handler = handlers.firstOrNull { it.requestPredicate(request) }
return handler?.responseFactory?.invoke(request) ?: MockResponse().setResponseCode(500)
}
- })
+ }
serviceTrustStore = SignalServiceTrustStore(application)
uncensoredConfiguration = SignalServiceConfiguration(
@@ -102,6 +110,17 @@ class InstrumentationApplicationDependencyProvider(val application: Application,
return recipientCache
}
+ override fun provideSignalServiceMessageSender(
+ signalWebSocket: SignalWebSocket,
+ protocolStore: SignalServiceDataStore,
+ signalServiceConfiguration: SignalServiceConfiguration
+ ): SignalServiceMessageSender {
+ if (signalServiceMessageSender == null) {
+ signalServiceMessageSender = spyk(objToCopy = default.provideSignalServiceMessageSender(signalWebSocket, protocolStore, signalServiceConfiguration))
+ }
+ return signalServiceMessageSender!!
+ }
+
class MockWebSocket : WebSocketListener() {
private val TAG = "MockWebSocket"
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJobTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJobTest.kt
index 3a05feabb6..536443a257 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJobTest.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJobTest.kt
@@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.attachments.UriAttachment
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.UriAttachmentBuilder
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
+import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.mms.SentMediaQuality
import org.thoughtcrime.securesms.providers.BlobProvider
@@ -38,7 +38,7 @@ class AttachmentCompressionJobTest {
StreamUtil.readFully(it)
}
- val blob = BlobProvider.getInstance().forData(imageBytes).createForSingleSessionOnDisk(ApplicationDependencies.getApplication())
+ val blob = BlobProvider.getInstance().forData(imageBytes).createForSingleSessionOnDisk(AppDependencies.application)
val firstPreUpload = createAttachment(1, blob, AttachmentTable.TransformProperties.empty())
val firstDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(firstPreUpload)
@@ -51,12 +51,12 @@ class AttachmentCompressionJobTest {
val secondJobLatch = CountDownLatch(1)
val jobThread = Thread {
- firstCompressionJob.setContext(ApplicationDependencies.getApplication())
+ firstCompressionJob.setContext(AppDependencies.application)
firstJobResult = firstCompressionJob.run()
secondJobLatch.await()
- secondCompressionJob!!.setContext(ApplicationDependencies.getApplication())
+ secondCompressionJob!!.setContext(AppDependencies.application)
secondJobResult = secondCompressionJob!!.run()
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageHelper.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageHelper.kt
new file mode 100644
index 0000000000..43fa465044
--- /dev/null
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageHelper.kt
@@ -0,0 +1,290 @@
+/*
+ * Copyright 2024 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.thoughtcrime.securesms.messages
+
+import android.net.Uri
+import io.mockk.every
+import io.mockk.mockkStatic
+import io.mockk.slot
+import io.mockk.unmockkStatic
+import org.thoughtcrime.securesms.attachments.Attachment
+import org.thoughtcrime.securesms.attachments.UriAttachment
+import org.thoughtcrime.securesms.database.AttachmentTable
+import org.thoughtcrime.securesms.database.SignalDatabase
+import org.thoughtcrime.securesms.database.UriAttachmentBuilder
+import org.thoughtcrime.securesms.database.model.GroupsV2UpdateMessageConverter
+import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
+import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription
+import org.thoughtcrime.securesms.jobs.ThreadUpdateJob
+import org.thoughtcrime.securesms.keyvalue.SignalStore
+import org.thoughtcrime.securesms.mms.OutgoingMessage
+import org.thoughtcrime.securesms.providers.BlobProvider
+import org.thoughtcrime.securesms.recipients.Recipient
+import org.thoughtcrime.securesms.recipients.RecipientId
+import org.thoughtcrime.securesms.testing.GroupTestingUtils
+import org.thoughtcrime.securesms.testing.MessageContentFuzzer
+import org.thoughtcrime.securesms.testing.SignalActivityRule
+import org.thoughtcrime.securesms.util.MediaUtil
+import java.util.UUID
+import kotlin.random.Random
+
+/**
+ * Makes inserting messages through the "normal" code paths simpler. Mostly focused on incoming messages.
+ */
+class MessageHelper(private val harness: SignalActivityRule, var startTime: Long = System.currentTimeMillis()) {
+
+ val alice: RecipientId = harness.others[0]
+ val bob: RecipientId = harness.others[1]
+ val group: GroupTestingUtils.TestGroupInfo = harness.group!!
+ val processor: MessageContentProcessor = MessageContentProcessor(harness.context)
+
+ init {
+ val threadIdSlot = slot()
+ mockkStatic(ThreadUpdateJob::class)
+ every { ThreadUpdateJob.enqueue(capture(threadIdSlot)) } answers {
+ SignalDatabase.threads.update(threadIdSlot.captured, false)
+ }
+ }
+
+ fun tearDown() {
+ unmockkStatic(ThreadUpdateJob::class)
+ }
+
+ fun incomingText(sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
+ startTime = nextStartTime()
+
+ val messageData = MessageData(author = sender, timestamp = startTime)
+
+ processor.process(
+ envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
+ content = MessageContentFuzzer.fuzzTextMessage(
+ sentTimestamp = messageData.timestamp,
+ groupContextV2 = if (destination == group.recipientId) group.groupV2Context else null,
+ allowExpireTimeChanges = false
+ ),
+ metadata = MessageContentFuzzer.envelopeMetadata(
+ source = sender,
+ destination = harness.self.id,
+ groupId = if (destination == group.recipientId) group.groupId else null
+ ),
+ serverDeliveredTimestamp = messageData.timestamp + 10
+ )
+
+ return messageData
+ }
+
+ fun outgoingText(conversationId: RecipientId = alice, successfulSend: Boolean = true, updateMessage: ((OutgoingMessage) -> OutgoingMessage)? = null): MessageData {
+ startTime = nextStartTime()
+
+ val messageData = MessageData(author = harness.self.id, timestamp = startTime)
+ val threadRecipient = Recipient.resolved(conversationId)
+
+ val message = OutgoingMessage(
+ threadRecipient = threadRecipient,
+ body = MessageContentFuzzer.string(),
+ sentTimeMillis = messageData.timestamp,
+ isUrgent = true,
+ isSecure = true
+ ).let { updateMessage?.invoke(it) ?: it }
+
+ val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(threadRecipient)
+ val messageId = SignalDatabase.messages.insertMessageOutbox(message, threadId, false, null)
+
+ if (successfulSend) {
+ SignalDatabase.messages.markAsSent(messageId, true)
+ }
+
+ return messageData.copy(messageId = messageId)
+ }
+
+ fun outgoingMessage(conversationId: RecipientId = alice, updateMessage: OutgoingMessage.() -> OutgoingMessage): MessageData {
+ startTime = nextStartTime()
+
+ val messageData = MessageData(author = harness.self.id, timestamp = startTime)
+ val threadRecipient = Recipient.resolved(conversationId)
+
+ val message = OutgoingMessage(
+ threadRecipient = threadRecipient,
+ sentTimeMillis = messageData.timestamp,
+ isUrgent = true,
+ isSecure = true
+ ).apply { updateMessage() }
+
+ val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(threadRecipient)
+ val messageId = SignalDatabase.messages.insertMessageOutbox(message, threadId, false, null)
+
+ return messageData.copy(messageId = messageId)
+ }
+
+ fun outgoingAttachment(data: ByteArray, uuid: UUID? = UUID.randomUUID()): Attachment {
+ val uri: Uri = BlobProvider.getInstance().forData(data).createForSingleSessionInMemory()
+
+ val attachment: UriAttachment = UriAttachmentBuilder.build(
+ id = Random.nextLong(),
+ uri = uri,
+ contentType = MediaUtil.IMAGE_JPEG,
+ transformProperties = AttachmentTable.TransformProperties(),
+ uuid = uuid
+ )
+
+ return attachment
+ }
+
+ fun outgoingGroupChange(): MessageData {
+ startTime = nextStartTime()
+
+ val messageData = MessageData(author = harness.self.id, timestamp = startTime)
+ val groupRecipient = Recipient.resolved(group.recipientId)
+ val decryptedGroupV2Context = DecryptedGroupV2Context(
+ context = group.groupV2Context,
+ groupState = SignalDatabase.groups.getGroup(group.groupId).get().requireV2GroupProperties().decryptedGroup
+ )
+
+ val updateDescription = GV2UpdateDescription.Builder()
+ .gv2ChangeDescription(decryptedGroupV2Context)
+ .groupChangeUpdate(GroupsV2UpdateMessageConverter.translateDecryptedChange(SignalStore.account.getServiceIds(), decryptedGroupV2Context))
+ .build()
+
+ val outgoingMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, updateDescription, startTime)
+
+ val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(groupRecipient)
+ val messageId = SignalDatabase.messages.insertMessageOutbox(outgoingMessage, threadId, false, null)
+ SignalDatabase.messages.markAsSent(messageId, true)
+
+ return messageData.copy(messageId = messageId)
+ }
+
+ fun incomingMedia(sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
+ startTime = nextStartTime()
+
+ val messageData = MessageData(author = sender, timestamp = startTime)
+
+ processor.process(
+ envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
+ content = MessageContentFuzzer.fuzzStickerMediaMessage(
+ sentTimestamp = messageData.timestamp,
+ groupContextV2 = if (destination == group.recipientId) group.groupV2Context else null
+ ),
+ metadata = MessageContentFuzzer.envelopeMetadata(
+ source = sender,
+ destination = harness.self.id,
+ groupId = if (destination == group.recipientId) group.groupId else null
+ ),
+ serverDeliveredTimestamp = messageData.timestamp + 10
+ )
+
+ return messageData
+ }
+
+ fun incomingEditText(targetTimestamp: Long = System.currentTimeMillis(), sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
+ startTime = nextStartTime()
+
+ val messageData = MessageData(author = sender, timestamp = startTime)
+
+ processor.process(
+ envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
+ content = MessageContentFuzzer.editTextMessage(
+ targetTimestamp = targetTimestamp,
+ editedDataMessage = MessageContentFuzzer.fuzzTextMessage(
+ sentTimestamp = messageData.timestamp,
+ groupContextV2 = if (destination == group.recipientId) group.groupV2Context else null
+ ).dataMessage!!
+ ),
+ metadata = MessageContentFuzzer.envelopeMetadata(
+ source = sender,
+ destination = harness.self.id,
+ groupId = if (destination == group.recipientId) group.groupId else null
+ ),
+ serverDeliveredTimestamp = messageData.timestamp + 10
+ )
+
+ return messageData
+ }
+
+ fun syncReadMessage(vararg reads: Pair): MessageData {
+ startTime = nextStartTime()
+ val messageData = MessageData(timestamp = startTime)
+
+ processor.process(
+ envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
+ content = MessageContentFuzzer.syncReadsMessage(reads.toList()),
+ metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, sourceDeviceId = 2),
+ serverDeliveredTimestamp = messageData.timestamp + 10
+ )
+
+ return messageData
+ }
+
+ fun syncDeleteForMeMessage(vararg deletes: MessageContentFuzzer.DeleteForMeSync): MessageData {
+ startTime = nextStartTime()
+ val messageData = MessageData(timestamp = startTime)
+
+ processor.process(
+ envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
+ content = MessageContentFuzzer.syncDeleteForMeMessage(deletes.toList()),
+ metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, sourceDeviceId = 2),
+ serverDeliveredTimestamp = messageData.timestamp + 10
+ )
+
+ return messageData
+ }
+
+ fun syncDeleteForMeConversation(vararg deletes: MessageContentFuzzer.DeleteForMeSync): MessageData {
+ startTime = nextStartTime()
+ val messageData = MessageData(timestamp = startTime)
+
+ processor.process(
+ envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
+ content = MessageContentFuzzer.syncDeleteForMeConversation(deletes.toList()),
+ metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, sourceDeviceId = 2),
+ serverDeliveredTimestamp = messageData.timestamp + 10
+ )
+
+ return messageData
+ }
+
+ fun syncDeleteForMeLocalOnlyConversation(vararg conversations: RecipientId): MessageData {
+ startTime = nextStartTime()
+ val messageData = MessageData(timestamp = startTime)
+
+ processor.process(
+ envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
+ content = MessageContentFuzzer.syncDeleteForMeLocalOnlyConversation(conversations.toList()),
+ metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, sourceDeviceId = 2),
+ serverDeliveredTimestamp = messageData.timestamp + 10
+ )
+
+ return messageData
+ }
+
+ fun syncDeleteForMeAttachment(conversationId: RecipientId, message: Pair, uuid: UUID?, digest: ByteArray?, plainTextHash: String?): MessageData {
+ startTime = nextStartTime()
+ val messageData = MessageData(timestamp = startTime)
+
+ processor.process(
+ envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
+ content = MessageContentFuzzer.syncDeleteForMeAttachment(conversationId, message, uuid, digest, plainTextHash),
+ metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, sourceDeviceId = 2),
+ serverDeliveredTimestamp = messageData.timestamp + 10
+ )
+
+ return messageData
+ }
+
+ /**
+ * Get the next "sentTimestamp" for current + [nextMessageOffset]th message. Useful for early message processing and future message timestamps.
+ */
+ fun nextStartTime(nextMessageOffset: Int = 1): Long {
+ return startTime + 1000 * nextMessageOffset
+ }
+
+ data class MessageData(
+ val author: RecipientId = RecipientId.UNKNOWN,
+ val serverGuid: UUID = UUID.randomUUID(),
+ val timestamp: Long,
+ val messageId: Long = -1L
+ )
+}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageProcessingPerformanceTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageProcessingPerformanceTest.kt
index 07233e48f7..f51d478c35 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageProcessingPerformanceTest.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageProcessingPerformanceTest.kt
@@ -16,7 +16,7 @@ import org.signal.core.util.logging.Log
import org.signal.libsignal.protocol.ecc.Curve
import org.signal.libsignal.protocol.ecc.ECKeyPair
import org.signal.libsignal.zkgroup.profiles.ProfileKey
-import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
+import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.testing.AliceClient
@@ -55,8 +55,8 @@ class MessageProcessingPerformanceTest {
@Before
fun setup() {
- mockkStatic(UnidentifiedAccessUtil::class)
- every { UnidentifiedAccessUtil.getCertificateValidator() } returns FakeClientHelpers.noOpCertificateValidator
+ mockkStatic(SealedSenderAccessUtil::class)
+ every { SealedSenderAccessUtil.getCertificateValidator() } returns FakeClientHelpers.noOpCertificateValidator
mockkObject(MessageContentProcessor)
every { MessageContentProcessor.create(harness.application) } returns TimingMessageContentProcessor(harness.application)
@@ -64,7 +64,7 @@ class MessageProcessingPerformanceTest {
@After
fun after() {
- unmockkStatic(UnidentifiedAccessUtil::class)
+ unmockkStatic(SealedSenderAccessUtil::class)
unmockkStatic(MessageContentProcessor::class)
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/messages/SyncMessageProcessorTest_readSyncs.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/SyncMessageProcessorTest_readSyncs.kt
index 36a752235c..67f0f2dfa4 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/messages/SyncMessageProcessorTest_readSyncs.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/SyncMessageProcessorTest_readSyncs.kt
@@ -6,23 +6,14 @@
package org.thoughtcrime.securesms.messages
import androidx.test.ext.junit.runners.AndroidJUnit4
-import io.mockk.every
-import io.mockk.mockkStatic
-import io.mockk.slot
-import io.mockk.unmockkStatic
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.database.SignalDatabase
-import org.thoughtcrime.securesms.jobs.ThreadUpdateJob
-import org.thoughtcrime.securesms.recipients.RecipientId
-import org.thoughtcrime.securesms.testing.GroupTestingUtils
-import org.thoughtcrime.securesms.testing.MessageContentFuzzer
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
-import java.util.UUID
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
@@ -31,43 +22,28 @@ class SyncMessageProcessorTest_readSyncs {
@get:Rule
val harness = SignalActivityRule(createGroup = true)
- private lateinit var alice: RecipientId
- private lateinit var bob: RecipientId
- private lateinit var group: GroupTestingUtils.TestGroupInfo
- private lateinit var processor: MessageContentProcessor
+ private lateinit var messageHelper: MessageHelper
@Before
fun setUp() {
- alice = harness.others[0]
- bob = harness.others[1]
- group = harness.group!!
-
- processor = MessageContentProcessor(harness.context)
-
- val threadIdSlot = slot()
- mockkStatic(ThreadUpdateJob::class)
- every { ThreadUpdateJob.enqueue(capture(threadIdSlot)) } answers {
- SignalDatabase.threads.update(threadIdSlot.captured, false)
- }
+ messageHelper = MessageHelper(harness)
}
@After
fun tearDown() {
- unmockkStatic(ThreadUpdateJob::class)
+ messageHelper.tearDown()
}
@Test
fun handleSynchronizeReadMessage() {
- val messageHelper = MessageHelper()
-
val message1Timestamp = messageHelper.incomingText().timestamp
val message2Timestamp = messageHelper.incomingText().timestamp
- val threadId = SignalDatabase.threads.getThreadIdFor(alice)!!
+ val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
- messageHelper.syncReadMessage(alice to message1Timestamp, alice to message2Timestamp)
+ messageHelper.syncReadMessage(messageHelper.alice to message1Timestamp, messageHelper.alice to message2Timestamp)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
@@ -75,16 +51,14 @@ class SyncMessageProcessorTest_readSyncs {
@Test
fun handleSynchronizeReadMessageMissingTimestamp() {
- val messageHelper = MessageHelper()
-
messageHelper.incomingText().timestamp
val message2Timestamp = messageHelper.incomingText().timestamp
- val threadId = SignalDatabase.threads.getThreadIdFor(alice)!!
+ val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
- messageHelper.syncReadMessage(alice to message2Timestamp)
+ messageHelper.syncReadMessage(messageHelper.alice to message2Timestamp)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
@@ -92,21 +66,19 @@ class SyncMessageProcessorTest_readSyncs {
@Test
fun handleSynchronizeReadWithEdits() {
- val messageHelper = MessageHelper()
-
val message1Timestamp = messageHelper.incomingText().timestamp
- messageHelper.syncReadMessage(alice to message1Timestamp)
+ messageHelper.syncReadMessage(messageHelper.alice to message1Timestamp)
val editMessage1Timestamp1 = messageHelper.incomingEditText(message1Timestamp).timestamp
val editMessage1Timestamp2 = messageHelper.incomingEditText(editMessage1Timestamp1).timestamp
val message2Timestamp = messageHelper.incomingMedia().timestamp
- val threadId = SignalDatabase.threads.getThreadIdFor(alice)!!
+ val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
- messageHelper.syncReadMessage(alice to message2Timestamp, alice to editMessage1Timestamp1, alice to editMessage1Timestamp2)
+ messageHelper.syncReadMessage(messageHelper.alice to message2Timestamp, messageHelper.alice to editMessage1Timestamp1, messageHelper.alice to editMessage1Timestamp2)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
@@ -114,112 +86,22 @@ class SyncMessageProcessorTest_readSyncs {
@Test
fun handleSynchronizeReadWithEditsInGroup() {
- val messageHelper = MessageHelper()
+ val message1Timestamp = messageHelper.incomingText(sender = messageHelper.alice, destination = messageHelper.group.recipientId).timestamp
- val message1Timestamp = messageHelper.incomingText(sender = alice, destination = group.recipientId).timestamp
+ messageHelper.syncReadMessage(messageHelper.alice to message1Timestamp)
- messageHelper.syncReadMessage(alice to message1Timestamp)
+ val editMessage1Timestamp1 = messageHelper.incomingEditText(targetTimestamp = message1Timestamp, sender = messageHelper.alice, destination = messageHelper.group.recipientId).timestamp
+ val editMessage1Timestamp2 = messageHelper.incomingEditText(targetTimestamp = editMessage1Timestamp1, sender = messageHelper.alice, destination = messageHelper.group.recipientId).timestamp
- val editMessage1Timestamp1 = messageHelper.incomingEditText(targetTimestamp = message1Timestamp, sender = alice, destination = group.recipientId).timestamp
- val editMessage1Timestamp2 = messageHelper.incomingEditText(targetTimestamp = editMessage1Timestamp1, sender = alice, destination = group.recipientId).timestamp
+ val message2Timestamp = messageHelper.incomingMedia(sender = messageHelper.bob, destination = messageHelper.group.recipientId).timestamp
- val message2Timestamp = messageHelper.incomingMedia(sender = bob, destination = group.recipientId).timestamp
-
- val threadId = SignalDatabase.threads.getThreadIdFor(group.recipientId)!!
+ val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.group.recipientId)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
- messageHelper.syncReadMessage(bob to message2Timestamp, alice to editMessage1Timestamp1, alice to editMessage1Timestamp2)
+ messageHelper.syncReadMessage(messageHelper.bob to message2Timestamp, messageHelper.alice to editMessage1Timestamp1, messageHelper.alice to editMessage1Timestamp2)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
}
-
- private inner class MessageHelper(var startTime: Long = System.currentTimeMillis()) {
-
- fun incomingText(sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
- startTime += 1000
-
- val messageData = MessageData(timestamp = startTime)
-
- processor.process(
- envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
- content = MessageContentFuzzer.fuzzTextMessage(
- sentTimestamp = messageData.timestamp,
- groupContextV2 = if (destination == group.recipientId) group.groupV2Context else null
- ),
- metadata = MessageContentFuzzer.envelopeMetadata(
- source = sender,
- destination = harness.self.id,
- groupId = if (destination == group.recipientId) group.groupId else null
- ),
- serverDeliveredTimestamp = messageData.timestamp + 10
- )
-
- return messageData
- }
-
- fun incomingMedia(sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
- startTime += 1000
-
- val messageData = MessageData(timestamp = startTime)
-
- processor.process(
- envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
- content = MessageContentFuzzer.fuzzStickerMediaMessage(
- sentTimestamp = messageData.timestamp,
- groupContextV2 = if (destination == group.recipientId) group.groupV2Context else null
- ),
- metadata = MessageContentFuzzer.envelopeMetadata(
- source = sender,
- destination = harness.self.id,
- groupId = if (destination == group.recipientId) group.groupId else null
- ),
- serverDeliveredTimestamp = messageData.timestamp + 10
- )
-
- return messageData
- }
-
- fun incomingEditText(targetTimestamp: Long = System.currentTimeMillis(), sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
- startTime += 1000
-
- val messageData = MessageData(timestamp = startTime)
-
- processor.process(
- envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
- content = MessageContentFuzzer.editTextMessage(
- targetTimestamp = targetTimestamp,
- editedDataMessage = MessageContentFuzzer.fuzzTextMessage(
- sentTimestamp = messageData.timestamp,
- groupContextV2 = if (destination == group.recipientId) group.groupV2Context else null
- ).dataMessage!!
- ),
- metadata = MessageContentFuzzer.envelopeMetadata(
- source = sender,
- destination = harness.self.id,
- groupId = if (destination == group.recipientId) group.groupId else null
- ),
- serverDeliveredTimestamp = messageData.timestamp + 10
- )
-
- return messageData
- }
-
- fun syncReadMessage(vararg reads: Pair): MessageData {
- startTime += 1000
- val messageData = MessageData(timestamp = startTime)
-
- processor.process(
- envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
- content = MessageContentFuzzer.syncReadsMessage(reads.toList()),
- metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, sourceDeviceId = 2),
- serverDeliveredTimestamp = messageData.timestamp + 10
- )
-
- return messageData
- }
- }
-
- private data class MessageData(val serverGuid: UUID = UUID.randomUUID(), val timestamp: Long)
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/messages/SyncMessageProcessorTest_synchronizeDeleteForMe.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/SyncMessageProcessorTest_synchronizeDeleteForMe.kt
new file mode 100644
index 0000000000..f0a3abccfc
--- /dev/null
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/SyncMessageProcessorTest_synchronizeDeleteForMe.kt
@@ -0,0 +1,703 @@
+/*
+ * Copyright 2024 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.thoughtcrime.securesms.messages
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.hamcrest.Matchers.greaterThan
+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
+import org.signal.core.util.logging.Log
+import org.signal.core.util.update
+import org.signal.core.util.withinTransaction
+import org.thoughtcrime.securesms.attachments.Attachment
+import org.thoughtcrime.securesms.attachments.DatabaseAttachment
+import org.thoughtcrime.securesms.database.AttachmentTable
+import org.thoughtcrime.securesms.database.CallTable
+import org.thoughtcrime.securesms.database.MessageTable
+import org.thoughtcrime.securesms.database.SignalDatabase
+import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent
+import org.thoughtcrime.securesms.recipients.Recipient
+import org.thoughtcrime.securesms.recipients.RecipientId
+import org.thoughtcrime.securesms.testing.MessageContentFuzzer.DeleteForMeSync
+import org.thoughtcrime.securesms.testing.SignalActivityRule
+import org.thoughtcrime.securesms.testing.assert
+import org.thoughtcrime.securesms.testing.assertIs
+import org.thoughtcrime.securesms.testing.assertIsNot
+import org.thoughtcrime.securesms.testing.assertIsNotNull
+import org.thoughtcrime.securesms.testing.assertIsSize
+import org.thoughtcrime.securesms.util.IdentityUtil
+import java.util.UUID
+
+@Suppress("ClassName")
+@RunWith(AndroidJUnit4::class)
+class SyncMessageProcessorTest_synchronizeDeleteForMe {
+
+ companion object {
+ private val TAG = "SyncDeleteForMeTest"
+ }
+
+ @get:Rule
+ val harness = SignalActivityRule(createGroup = true)
+
+ private lateinit var messageHelper: MessageHelper
+
+ @Before
+ fun setUp() {
+ messageHelper = MessageHelper(harness)
+ }
+
+ @After
+ fun tearDown() {
+ messageHelper.tearDown()
+ }
+
+ @Test
+ fun singleMessageDelete() {
+ // GIVEN
+ val message1Timestamp = messageHelper.incomingText().timestamp
+ messageHelper.incomingText()
+
+ val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
+ var messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
+ messageCount assertIs 2
+
+ // WHEN
+ messageHelper.syncDeleteForMeMessage(
+ DeleteForMeSync(conversationId = messageHelper.alice, messageHelper.alice to message1Timestamp)
+ )
+
+ // THEN
+ messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
+ messageCount assertIs 1
+ }
+
+ @Test
+ fun singleOutgoingMessageDelete() {
+ // GIVEN
+ val message1Timestamp = messageHelper.outgoingText().timestamp
+ messageHelper.incomingText()
+
+ val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
+ var messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
+ messageCount assertIs 2
+
+ // WHEN
+ messageHelper.syncDeleteForMeMessage(
+ DeleteForMeSync(conversationId = messageHelper.alice, harness.self.id to message1Timestamp)
+ )
+
+ // THEN
+ messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
+ messageCount assertIs 1
+ }
+
+ @Test
+ fun singleGroupMessageDelete() {
+ // GIVEN
+ val message1Timestamp = messageHelper.incomingText(sender = messageHelper.alice, destination = messageHelper.group.recipientId).timestamp
+ messageHelper.incomingText(sender = messageHelper.alice, destination = messageHelper.group.recipientId)
+ messageHelper.incomingText(sender = messageHelper.bob, destination = messageHelper.group.recipientId)
+
+ val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.group.recipientId)!!
+ var messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
+ messageCount assertIs 3
+
+ // WHEN
+ messageHelper.syncDeleteForMeMessage(
+ DeleteForMeSync(conversationId = messageHelper.group.recipientId, messageHelper.alice to message1Timestamp)
+ )
+
+ // THEN
+ messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
+ messageCount assertIs 2
+ }
+
+ @Test
+ fun multipleGroupMessageDelete() {
+ // GIVEN
+ val message1Timestamp = messageHelper.incomingText(sender = messageHelper.alice, destination = messageHelper.group.recipientId).timestamp
+ messageHelper.incomingText(sender = messageHelper.alice, destination = messageHelper.group.recipientId)
+ val message3Timestamp = messageHelper.incomingText(sender = messageHelper.bob, destination = messageHelper.group.recipientId).timestamp
+
+ val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.group.recipientId)!!
+ var messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
+ messageCount assertIs 3
+
+ // WHEN
+ messageHelper.syncDeleteForMeMessage(
+ DeleteForMeSync(conversationId = messageHelper.group.recipientId, messageHelper.alice to message1Timestamp, messageHelper.bob to message3Timestamp)
+ )
+
+ // THEN
+ messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
+ messageCount assertIs 1
+ }
+
+ @Test
+ fun allMessagesDelete() {
+ // GIVEN
+ val message1Timestamp = messageHelper.incomingText().timestamp
+ val message2Timestamp = messageHelper.incomingText().timestamp
+
+ val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
+ var messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
+ messageCount assertIs 2
+
+ // WHEN
+ messageHelper.syncDeleteForMeMessage(
+ DeleteForMeSync(conversationId = messageHelper.alice, messageHelper.alice to message1Timestamp, messageHelper.alice to message2Timestamp)
+ )
+
+ // THEN
+ messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
+ messageCount assertIs 0
+
+ val threadRecord = SignalDatabase.threads.getThreadRecord(threadId)
+ threadRecord assertIs null
+ }
+
+ @Test
+ fun earlyMessagesDelete() {
+ // GIVEN
+ messageHelper.incomingText().timestamp
+
+ val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
+ var messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
+ messageCount assertIs 1
+
+ // WHEN
+ val nextTextMessageTimestamp = messageHelper.nextStartTime(2)
+ messageHelper.syncDeleteForMeMessage(
+ DeleteForMeSync(conversationId = messageHelper.alice, messageHelper.alice to nextTextMessageTimestamp)
+ )
+ messageHelper.incomingText()
+
+ // THEN
+ messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
+ messageCount assertIs 1
+ }
+
+ @Test
+ fun multipleConversationMessagesDelete() {
+ // GIVEN
+ messageHelper.incomingText(sender = messageHelper.alice)
+ val aliceMessage2 = messageHelper.incomingText(sender = messageHelper.alice).timestamp
+
+ messageHelper.incomingText(sender = messageHelper.bob)
+ val bobMessage2 = messageHelper.incomingText(sender = messageHelper.bob).timestamp
+
+ val aliceThreadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
+ var aliceMessageCount = SignalDatabase.messages.getMessageCountForThread(aliceThreadId)
+ aliceMessageCount assertIs 2
+
+ val bobThreadId = SignalDatabase.threads.getThreadIdFor(messageHelper.bob)!!
+ var bobMessageCount = SignalDatabase.messages.getMessageCountForThread(bobThreadId)
+ bobMessageCount assertIs 2
+
+ // WHEN
+ messageHelper.syncDeleteForMeMessage(
+ DeleteForMeSync(conversationId = messageHelper.alice, messageHelper.alice to aliceMessage2),
+ DeleteForMeSync(conversationId = messageHelper.bob, messageHelper.bob to bobMessage2)
+ )
+
+ // THEN
+ aliceMessageCount = SignalDatabase.messages.getMessageCountForThread(aliceThreadId)
+ aliceMessageCount assertIs 1
+
+ bobMessageCount = SignalDatabase.messages.getMessageCountForThread(bobThreadId)
+ bobMessageCount assertIs 1
+ }
+
+ @Test
+ fun singleConversationDelete() {
+ // GIVEN
+ val messages = mutableListOf()
+
+ for (i in 0 until 10) {
+ messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
+ messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
+ }
+
+ val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
+ SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
+
+ // WHEN
+ messageHelper.syncDeleteForMeConversation(
+ DeleteForMeSync(
+ conversationId = messageHelper.alice,
+ messages = messages.takeLast(5).map { it.recipientId to it.timetamp },
+ isFullDelete = true
+ )
+ )
+
+ // THEN
+ SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 0
+ SignalDatabase.threads.getThreadRecord(threadId) assertIs null
+ }
+
+ @Test
+ fun singleConversationNoRecentsFoundDelete() {
+ // GIVEN
+ val messages = mutableListOf()
+
+ for (i in 0 until 10) {
+ messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
+ messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
+ }
+
+ val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
+ SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
+
+ // WHEN
+ val randomFutureMessages = (1..5).map {
+ messageHelper.alice to messageHelper.nextStartTime(it)
+ }
+
+ messageHelper.syncDeleteForMeConversation(
+ DeleteForMeSync(conversationId = messageHelper.alice, randomFutureMessages, isFullDelete = true)
+ )
+
+ // THEN
+ SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
+ SignalDatabase.threads.getThreadRecord(threadId).assertIsNotNull()
+
+ harness.inMemoryLogger.flush()
+ harness.inMemoryLogger.entries().filter { it.message?.contains("Unable to find most recent received at timestamp") == true }.size assertIs 1
+ }
+
+ @Test
+ fun singleConversationNoRecentsFoundNonExpiringRecentsFoundDelete() {
+ // GIVEN
+ val messages = mutableListOf()
+
+ for (i in 0 until 10) {
+ messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
+ messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
+ }
+
+ val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
+ SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
+
+ // WHEN
+ val nonExpiringMessages = messages.takeLast(5).map { it.recipientId to it.timetamp }
+
+ val randomFutureMessages = (1..5).map {
+ messageHelper.alice to messageHelper.nextStartTime(it)
+ }
+
+ messageHelper.syncDeleteForMeConversation(
+ DeleteForMeSync(conversationId = messageHelper.alice, randomFutureMessages, nonExpiringMessages, true)
+ )
+
+ // THEN
+ SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 0
+ SignalDatabase.threads.getThreadRecord(threadId) assertIs null
+
+ harness.inMemoryLogger.flush()
+ harness.inMemoryLogger.entries().filter { it.message?.contains("Using backup non-expiring messages") == true }.size assertIs 1
+ }
+
+ @Test
+ fun localOnlyRemainingAfterConversationDeleteWithFullDelete() {
+ // GIVEN
+ val messages = mutableListOf()
+
+ Log.v(TAG, "Adding normal messages")
+ for (i in 0 until 10) {
+ messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
+ messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
+ }
+
+ val alice = Recipient.resolved(messageHelper.alice)
+ Log.v(TAG, "Adding identity message")
+ IdentityUtil.markIdentityVerified(harness.context, alice, true, true)
+ Log.v(TAG, "Adding profile message")
+ SignalDatabase.messages.insertProfileNameChangeMessages(alice, "new name", "previous name")
+ Log.v(TAG, "Adding call message")
+ SignalDatabase.calls.insertOneToOneCall(1, System.currentTimeMillis(), alice.id, CallTable.Type.AUDIO_CALL, CallTable.Direction.OUTGOING, CallTable.Event.ACCEPTED)
+
+ val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
+ SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 23
+
+ // WHEN
+ Log.v(TAG, "Processing sync message")
+ messageHelper.syncDeleteForMeConversation(
+ DeleteForMeSync(
+ conversationId = messageHelper.alice,
+ messages = messages.takeLast(5).map { it.recipientId to it.timetamp },
+ isFullDelete = true
+ )
+ )
+
+ // THEN
+ SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 0
+ SignalDatabase.threads.getThreadRecord(threadId) assertIs null
+ }
+
+ @Test
+ fun localOnlyRemainingAfterConversationDeleteWithoutFullDelete() {
+ // GIVEN
+ val messages = mutableListOf()
+
+ for (i in 0 until 10) {
+ messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
+ messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
+ }
+
+ val alice = Recipient.resolved(messageHelper.alice)
+ IdentityUtil.markIdentityVerified(harness.context, alice, true, true)
+ SignalDatabase.messages.insertProfileNameChangeMessages(alice, "new name", "previous name")
+ SignalDatabase.calls.insertOneToOneCall(1, System.currentTimeMillis(), alice.id, CallTable.Type.AUDIO_CALL, CallTable.Direction.OUTGOING, CallTable.Event.ACCEPTED)
+
+ val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
+ SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 23
+
+ // WHEN
+ messageHelper.syncDeleteForMeConversation(
+ DeleteForMeSync(
+ conversationId = messageHelper.alice,
+ messages = messages.takeLast(5).map { it.recipientId to it.timetamp },
+ isFullDelete = false
+ )
+ )
+
+ // THEN
+ SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 3
+ SignalDatabase.threads.getThreadRecord(threadId).assertIsNotNull()
+ }
+
+ @Test
+ fun groupConversationDelete() {
+ // GIVEN
+ val messages = mutableListOf()
+
+ for (i in 0 until 50) {
+ messages += when (i % 3) {
+ 1 -> MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText(sender = messageHelper.alice, destination = messageHelper.group.recipientId).timestamp)
+ 2 -> MessageTable.SyncMessageId(messageHelper.bob, messageHelper.incomingText(sender = messageHelper.bob, destination = messageHelper.group.recipientId).timestamp)
+ else -> MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText(messageHelper.group.recipientId).timestamp)
+ }
+ }
+
+ val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.group.recipientId)!!
+
+ // WHEN
+ messageHelper.syncDeleteForMeConversation(
+ DeleteForMeSync(
+ conversationId = messageHelper.group.recipientId,
+ messages = messages.takeLast(5).map { it.recipientId to it.timetamp },
+ isFullDelete = true
+ )
+ )
+
+ // THEN
+ SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 0
+ SignalDatabase.threads.getThreadRecord(threadId) assertIs null
+ }
+
+ @Test
+ fun multipleConversationDelete() {
+ // GIVEN
+ val allMessages = mapOf>(
+ messageHelper.alice to mutableListOf(),
+ messageHelper.bob to mutableListOf()
+ )
+
+ allMessages.forEach { (conversation, messages) ->
+ for (i in 0 until 10) {
+ messages += MessageTable.SyncMessageId(conversation, messageHelper.incomingText(sender = conversation).timestamp)
+ messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText(conversationId = conversation).timestamp)
+ }
+ }
+
+ val threadIds = allMessages.keys.map { SignalDatabase.threads.getThreadIdFor(it)!! }
+ threadIds.forEach { SignalDatabase.messages.getMessageCountForThread(it) assertIs 20 }
+
+ // WHEN
+ messageHelper.syncDeleteForMeConversation(
+ DeleteForMeSync(conversationId = messageHelper.alice, allMessages[messageHelper.alice]!!.takeLast(5).map { it.recipientId to it.timetamp }, isFullDelete = true),
+ DeleteForMeSync(conversationId = messageHelper.bob, allMessages[messageHelper.bob]!!.takeLast(5).map { it.recipientId to it.timetamp }, isFullDelete = true)
+ )
+
+ // THEN
+ threadIds.forEach {
+ SignalDatabase.messages.getMessageCountForThread(it) assertIs 0
+ SignalDatabase.threads.getThreadRecord(it) assertIs null
+ }
+ }
+
+ @Test
+ fun singleLocalOnlyConversation() {
+ // GIVEN
+ val alice = Recipient.resolved(messageHelper.alice)
+
+ // Insert placeholder message to prevent early thread update deletes
+ val oneToOnePlaceHolderMessage = messageHelper.outgoingText().messageId
+
+ val aliceThreadId = SignalDatabase.threads.getOrCreateThreadIdFor(messageHelper.alice, isGroup = false)
+
+ IdentityUtil.markIdentityVerified(harness.context, alice, true, false)
+ SignalDatabase.calls.insertOneToOneCall(1, System.currentTimeMillis(), alice.id, CallTable.Type.AUDIO_CALL, CallTable.Direction.OUTGOING, CallTable.Event.ACCEPTED)
+ SignalDatabase.messages.insertProfileNameChangeMessages(alice, "new name", "previous name")
+ SignalDatabase.messages.markAsSentFailed(messageHelper.outgoingText().messageId)
+
+ // Cleanup and confirm setup
+ SignalDatabase.messages.deleteMessage(messageId = oneToOnePlaceHolderMessage, threadId = aliceThreadId, notify = false, updateThread = false)
+ SignalDatabase.messages.getMessageCountForThread(aliceThreadId) assert greaterThan(0)
+
+ // WHEN
+ messageHelper.syncDeleteForMeLocalOnlyConversation(messageHelper.alice)
+
+ // THEN
+ SignalDatabase.messages.getMessageCountForThread(aliceThreadId) assertIs 0
+ SignalDatabase.threads.getThreadRecord(aliceThreadId) assertIs null
+ }
+
+ @Ignore("counts are consistent for some reason")
+ @Test
+ fun multipleLocalOnlyConversation() {
+ // GIVEN
+ val alice = Recipient.resolved(messageHelper.alice)
+
+ // Insert placeholder messages in group and alice thread to prevent early thread update deletes
+ val groupPlaceholderMessage = messageHelper.outgoingText(conversationId = messageHelper.group.recipientId).messageId
+ val oneToOnePlaceHolderMessage = messageHelper.outgoingText().messageId
+
+ val aliceThreadId = SignalDatabase.threads.getOrCreateThreadIdFor(messageHelper.alice, isGroup = false)
+ val groupThreadId = SignalDatabase.threads.getOrCreateThreadIdFor(messageHelper.group.recipientId, isGroup = true)
+
+ // Identity changes
+ IdentityUtil.markIdentityVerified(harness.context, alice, true, true)
+ IdentityUtil.markIdentityVerified(harness.context, alice, false, true)
+ IdentityUtil.markIdentityVerified(harness.context, alice, true, false)
+ IdentityUtil.markIdentityVerified(harness.context, alice, false, false)
+
+ IdentityUtil.markIdentityUpdate(harness.context, alice.id)
+
+ // Calls
+ SignalDatabase.calls.insertOneToOneCall(1, System.currentTimeMillis(), alice.id, CallTable.Type.AUDIO_CALL, CallTable.Direction.OUTGOING, CallTable.Event.ACCEPTED)
+ SignalDatabase.calls.insertOneToOneCall(2, System.currentTimeMillis(), alice.id, CallTable.Type.VIDEO_CALL, CallTable.Direction.INCOMING, CallTable.Event.MISSED)
+ SignalDatabase.calls.insertOneToOneCall(3, System.currentTimeMillis(), alice.id, CallTable.Type.AUDIO_CALL, CallTable.Direction.INCOMING, CallTable.Event.MISSED_NOTIFICATION_PROFILE)
+
+ SignalDatabase.calls.insertAcceptedGroupCall(4, messageHelper.group.recipientId, CallTable.Direction.INCOMING, System.currentTimeMillis())
+ SignalDatabase.calls.insertDeclinedGroupCall(5, messageHelper.group.recipientId, System.currentTimeMillis())
+
+ // Detected changes
+ SignalDatabase.messages.insertProfileNameChangeMessages(alice, "new name", "previous name")
+ SignalDatabase.messages.insertLearnedProfileNameChangeMessage(alice, null, "username.42")
+ SignalDatabase.messages.insertNumberChangeMessages(alice.id)
+ SignalDatabase.messages.insertSmsExportMessage(alice.id, SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!)
+ SignalDatabase.messages.insertSessionSwitchoverEvent(alice.id, aliceThreadId, SessionSwitchoverEvent())
+
+ // Sent failed
+ SignalDatabase.messages.markAsSending(messageHelper.outgoingText().messageId)
+ SignalDatabase.messages.markAsSentFailed(messageHelper.outgoingText().messageId)
+ messageHelper.outgoingText().let {
+ SignalDatabase.messages.markAsSending(it.messageId)
+ SignalDatabase.messages.markAsRateLimited(it.messageId)
+ }
+
+ // Group change
+ messageHelper.outgoingGroupChange()
+
+ // Cleanup and confirm setup
+ SignalDatabase.messages.deleteMessage(messageId = oneToOnePlaceHolderMessage, threadId = aliceThreadId, notify = false, updateThread = false)
+ SignalDatabase.messages.deleteMessage(messageId = groupPlaceholderMessage, threadId = aliceThreadId, notify = false, updateThread = false)
+
+ SignalDatabase.rawDatabase.withinTransaction {
+ SignalDatabase.messages.getMessageCountForThread(aliceThreadId) assertIs 16
+ SignalDatabase.messages.getMessageCountForThread(groupThreadId) assertIs 10
+ }
+
+ // WHEN
+ messageHelper.syncDeleteForMeLocalOnlyConversation(messageHelper.alice, messageHelper.group.recipientId)
+
+ // THEN
+ SignalDatabase.messages.getMessageCountForThread(aliceThreadId) assertIs 0
+ SignalDatabase.threads.getThreadRecord(aliceThreadId) assertIs null
+
+ SignalDatabase.messages.getMessageCountForThread(groupThreadId) assertIs 0
+ SignalDatabase.threads.getThreadRecord(groupThreadId) assertIs null
+ }
+
+ @Test
+ fun singleLocalOnlyConversationHasAddressable() {
+ // GIVEN
+ val messages = mutableListOf()
+
+ for (i in 0 until 10) {
+ messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
+ messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
+ }
+
+ val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
+ SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
+
+ // WHEN
+ messageHelper.syncDeleteForMeLocalOnlyConversation(messageHelper.alice)
+
+ // THEN
+ SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
+ SignalDatabase.threads.getThreadRecord(threadId).assertIsNotNull()
+
+ harness.inMemoryLogger.flush()
+ harness.inMemoryLogger.entries().filter { it.message?.contains("Thread is not local only") == true }.size assertIs 1
+ }
+
+ @Test
+ fun singleAttachmentDeletes() {
+ // GIVEN
+ val message1 = messageHelper.outgoingText { message ->
+ message.copy(
+ attachments = listOf(
+ messageHelper.outgoingAttachment(byteArrayOf(1, 2, 3)),
+ messageHelper.outgoingAttachment(byteArrayOf(2, 3, 4), null),
+ messageHelper.outgoingAttachment(byteArrayOf(5, 6, 7), null),
+ messageHelper.outgoingAttachment(byteArrayOf(10, 11, 12))
+ )
+ )
+ }
+
+ var attachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
+ attachments assertIsSize 4
+
+ val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
+ SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 1
+
+ // Has all three
+ SignalDatabase.attachments.finalizeAttachmentAfterUpload(
+ id = attachments[0].attachmentId,
+ attachment = attachments[0].copy(digest = byteArrayOf(attachments[0].attachmentId.id.toByte())),
+ uploadTimestamp = message1.timestamp + 1
+ )
+
+ // Missing uuid and digest
+ SignalDatabase.attachments.finalizeAttachmentAfterUpload(
+ id = attachments[1].attachmentId,
+ attachment = attachments[1],
+ uploadTimestamp = message1.timestamp + 1
+ )
+
+ // Missing uuid and plain text
+ SignalDatabase.attachments.finalizeAttachmentAfterUpload(
+ id = attachments[2].attachmentId,
+ attachment = attachments[2].copy(digest = byteArrayOf(attachments[2].attachmentId.id.toByte())),
+ uploadTimestamp = message1.timestamp + 1
+ )
+ SignalDatabase.rawDatabase.update(AttachmentTable.TABLE_NAME).values(AttachmentTable.DATA_HASH_END to null).where("${AttachmentTable.ID} = ?", attachments[2].attachmentId).run()
+
+ // Different has all three
+ SignalDatabase.attachments.finalizeAttachmentAfterUpload(
+ id = attachments[3].attachmentId,
+ attachment = attachments[3].copy(digest = byteArrayOf(attachments[3].attachmentId.id.toByte())),
+ uploadTimestamp = message1.timestamp + 1
+ )
+
+ attachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
+
+ // WHEN
+ messageHelper.syncDeleteForMeAttachment(
+ conversationId = messageHelper.alice,
+ message = message1.author to message1.timestamp,
+ attachments[0].uuid,
+ attachments[0].remoteDigest,
+ attachments[0].dataHash
+ )
+
+ SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 1
+ var updatedAttachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
+ updatedAttachments assertIsSize 3
+ updatedAttachments.forEach { it.attachmentId assertIsNot attachments[0].attachmentId }
+
+ messageHelper.syncDeleteForMeAttachment(
+ conversationId = messageHelper.alice,
+ message = message1.author to message1.timestamp,
+ attachments[1].uuid,
+ attachments[1].remoteDigest,
+ attachments[1].dataHash
+ )
+
+ SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 1
+ updatedAttachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
+ updatedAttachments assertIsSize 2
+ updatedAttachments.forEach { it.attachmentId assertIsNot attachments[1].attachmentId }
+
+ messageHelper.syncDeleteForMeAttachment(
+ conversationId = messageHelper.alice,
+ message = message1.author to message1.timestamp,
+ attachments[2].uuid,
+ attachments[2].remoteDigest,
+ attachments[2].dataHash
+ )
+
+ SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 1
+ updatedAttachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
+ updatedAttachments assertIsSize 1
+ updatedAttachments.forEach { it.attachmentId assertIsNot attachments[2].attachmentId }
+
+ messageHelper.syncDeleteForMeAttachment(
+ conversationId = messageHelper.alice,
+ message = message1.author to message1.timestamp,
+ attachments[3].uuid,
+ attachments[3].remoteDigest,
+ attachments[3].dataHash
+ )
+
+ SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 0
+ updatedAttachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
+ updatedAttachments assertIsSize 0
+
+ SignalDatabase.threads.getThreadRecord(threadId) assertIs null
+ }
+
+ private fun DatabaseAttachment.copy(
+ uuid: UUID? = this.uuid,
+ digest: ByteArray? = this.remoteDigest
+ ): Attachment {
+ return DatabaseAttachment(
+ attachmentId = this.attachmentId,
+ mmsId = this.mmsId,
+ hasData = this.hasData,
+ hasThumbnail = false,
+ hasArchiveThumbnail = false,
+ contentType = this.contentType,
+ transferProgress = this.transferState,
+ size = this.size,
+ fileName = this.fileName,
+ cdn = this.cdn,
+ location = this.remoteLocation,
+ key = this.remoteKey,
+ digest = digest,
+ incrementalDigest = this.incrementalDigest,
+ incrementalMacChunkSize = this.incrementalMacChunkSize,
+ fastPreflightId = this.fastPreflightId,
+ voiceNote = this.voiceNote,
+ borderless = this.borderless,
+ videoGif = this.videoGif,
+ width = this.width,
+ height = this.height,
+ quote = this.quote,
+ caption = this.caption,
+ stickerLocator = this.stickerLocator,
+ blurHash = this.blurHash,
+ audioHash = this.audioHash,
+ transformProperties = this.transformProperties,
+ displayOrder = this.displayOrder,
+ uploadTimestamp = this.uploadTimestamp,
+ dataHash = this.dataHash,
+ archiveCdn = this.archiveCdn,
+ archiveThumbnailCdn = this.archiveThumbnailCdn,
+ archiveMediaName = this.archiveMediaName,
+ archiveMediaId = this.archiveMediaId,
+ thumbnailRestoreState = this.thumbnailRestoreState,
+ uuid = uuid
+ )
+ }
+}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/migrations/SubscriberIdMigrationJobTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/migrations/SubscriberIdMigrationJobTest.kt
new file mode 100644
index 0000000000..3eed9ac9d7
--- /dev/null
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/migrations/SubscriberIdMigrationJobTest.kt
@@ -0,0 +1,55 @@
+package org.thoughtcrime.securesms.migrations
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.signal.core.util.count
+import org.signal.core.util.readToSingleInt
+import org.signal.donations.PaymentSourceType
+import org.thoughtcrime.securesms.database.InAppPaymentSubscriberTable
+import org.thoughtcrime.securesms.database.SignalDatabase
+import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
+import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
+import org.thoughtcrime.securesms.keyvalue.SignalStore
+import org.thoughtcrime.securesms.testing.assertIs
+import org.thoughtcrime.securesms.testing.assertIsNotNull
+import org.whispersystems.signalservice.api.subscriptions.SubscriberId
+import java.util.Currency
+
+@RunWith(AndroidJUnit4::class)
+class SubscriberIdMigrationJobTest {
+
+ private val testSubject = SubscriberIdMigrationJob()
+
+ @Test
+ fun givenNoSubscriber_whenIRunSubscriberIdMigrationJob_thenIExpectNoDatabaseEntries() {
+ testSubject.run()
+
+ val actual = SignalDatabase.inAppPaymentSubscribers.readableDatabase.count()
+ .from(InAppPaymentSubscriberTable.TABLE_NAME)
+ .run()
+ .readToSingleInt()
+
+ actual assertIs 0
+ }
+
+ @Test
+ fun givenUSDSubscriber_whenIRunSubscriberIdMigrationJob_thenIExpectASingleEntry() {
+ val subscriberId = SubscriberId.generate()
+ SignalStore.inAppPayments.setSubscriberCurrency(Currency.getInstance("USD"), InAppPaymentSubscriberRecord.Type.DONATION)
+ SignalStore.inAppPayments.setSubscriber("USD", subscriberId)
+ SignalStore.inAppPayments.setSubscriptionPaymentSourceType(PaymentSourceType.PayPal)
+ SignalStore.inAppPayments.shouldCancelSubscriptionBeforeNextSubscribeAttempt = true
+
+ testSubject.run()
+
+ val actual = SignalDatabase.inAppPaymentSubscribers.getByCurrencyCode("USD", InAppPaymentSubscriberRecord.Type.DONATION)
+
+ actual.assertIsNotNull()
+ actual!!.subscriberId.bytes assertIs subscriberId.bytes
+ actual.paymentMethodType assertIs InAppPaymentData.PaymentMethodType.PAYPAL
+ actual.requiresCancel assertIs true
+ actual.currency assertIs Currency.getInstance("USD")
+ actual.type assertIs InAppPaymentSubscriberRecord.Type.DONATION
+ }
+}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt
index e33b739bab..c986ef8c55 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt
@@ -24,9 +24,9 @@ class ContactRecordProcessorTest {
@Before
fun setup() {
- SignalStore.account().setE164(E164_SELF)
- SignalStore.account().setAci(ACI_SELF)
- SignalStore.account().setPni(PNI_SELF)
+ SignalStore.account.setE164(E164_SELF)
+ SignalStore.account.setAci(ACI_SELF)
+ SignalStore.account.setPni(PNI_SELF)
}
@Test
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/AliceClient.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/AliceClient.kt
index 04d09283de..b429745306 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/AliceClient.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/AliceClient.kt
@@ -3,8 +3,7 @@ package org.thoughtcrime.securesms.testing
import org.signal.core.util.logging.Log
import org.signal.libsignal.protocol.ecc.ECKeyPair
import org.signal.libsignal.zkgroup.profiles.ProfileKey
-import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
+import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.messages.protocol.BufferedProtocolStore
import org.thoughtcrime.securesms.recipients.Recipient
@@ -30,14 +29,14 @@ class AliceClient(val serviceId: ServiceId, val e164: String, val trustRoot: ECK
uuid = serviceId.rawUuid,
e164 = e164,
deviceId = 1,
- identityKey = SignalStore.account().aciIdentityKey.publicKey.publicKey,
+ identityKey = SignalStore.account.aciIdentityKey.publicKey.publicKey,
expires = 31337
)
fun process(envelope: Envelope, serverDeliveredTimestamp: Long) {
val start = System.currentTimeMillis()
val bufferedStore = BufferedProtocolStore.create()
- ApplicationDependencies.getIncomingMessageObserver()
+ AppDependencies.incomingMessageObserver
.processEnvelope(bufferedStore, envelope, serverDeliveredTimestamp)
?.mapNotNull { it.run() }
?.forEach { it.enqueue() }
@@ -48,9 +47,9 @@ class AliceClient(val serviceId: ServiceId, val e164: String, val trustRoot: ECK
}
fun encrypt(now: Long, destination: Recipient): Envelope {
- return ApplicationDependencies.getSignalServiceMessageSender().getEncryptedMessage(
+ return AppDependencies.signalServiceMessageSender.getEncryptedMessage(
SignalServiceAddress(destination.requireServiceId(), destination.requireE164()),
- FakeClientHelpers.getTargetUnidentifiedAccess(ProfileKeyUtil.getSelfProfileKey(), ProfileKey(destination.profileKey), aliceSenderCertificate),
+ FakeClientHelpers.getSealedSenderAccess(ProfileKey(destination.profileKey), aliceSenderCertificate),
1,
FakeClientHelpers.encryptedTextMessage(now),
false
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/BobClient.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/BobClient.kt
index b79713ff46..3eb4d602be 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/BobClient.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/BobClient.kt
@@ -17,7 +17,7 @@ import org.signal.libsignal.protocol.state.SignedPreKeyRecord
import org.signal.libsignal.protocol.util.KeyHelper
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
-import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
+import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil
import org.thoughtcrime.securesms.database.OneTimePreKeyTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.SignedPreKeyTable
@@ -25,14 +25,13 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testing.FakeClientHelpers.toEnvelope
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore
import org.whispersystems.signalservice.api.SignalSessionLock
+import org.whispersystems.signalservice.api.crypto.SealedSenderAccess
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher
import org.whispersystems.signalservice.api.crypto.SignalSessionBuilder
-import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess
import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.push.Envelope
-import java.util.Optional
import java.util.UUID
import java.util.concurrent.locks.ReentrantLock
@@ -75,12 +74,12 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
}
fun decrypt(envelope: Envelope, serverDeliveredTimestamp: Long) {
- val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, UnidentifiedAccessUtil.getCertificateValidator())
+ val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, SealedSenderAccessUtil.getCertificateValidator())
cipher.decrypt(envelope, serverDeliveredTimestamp)
}
private fun getAliceServiceId(): ServiceId {
- return SignalStore.account().requireAci()
+ return SignalStore.account.requireAci()
}
private fun getAlicePreKeyBundle(): PreKeyBundle {
@@ -103,7 +102,7 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
val selfSignedPreKeyRecord = SignalDatabase.signedPreKeys.get(getAliceServiceId(), selfSignedPreKeyId)!!
return PreKeyBundle(
- SignalStore.account().registrationId,
+ SignalStore.account.registrationId,
1,
selfPreKeyId,
selfPreKeyRecord.keyPair.publicKey,
@@ -115,19 +114,19 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
}
private fun getAliceProtocolAddress(): SignalProtocolAddress {
- return SignalProtocolAddress(SignalStore.account().requireAci().toString(), 1)
+ return SignalProtocolAddress(SignalStore.account.requireAci().toString(), 1)
}
private fun getAlicePublicKey(): IdentityKey {
- return SignalStore.account().aciIdentityKey.publicKey
+ return SignalStore.account.aciIdentityKey.publicKey
}
private fun getAliceProfileKey(): ProfileKey {
return ProfileKeyUtil.getSelfProfileKey()
}
- private fun getAliceUnidentifiedAccess(): Optional {
- return FakeClientHelpers.getTargetUnidentifiedAccess(profileKey, getAliceProfileKey(), senderCertificate)
+ private fun getAliceUnidentifiedAccess(): SealedSenderAccess? {
+ return FakeClientHelpers.getSealedSenderAccess(getAliceProfileKey(), senderCertificate)
}
private class BobSignalServiceAccountDataStore(private val registrationId: Int, private val identityKeyPair: IdentityKeyPair) : SignalServiceAccountDataStore {
@@ -139,10 +138,12 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
override fun isTrustedIdentity(address: SignalProtocolAddress?, identityKey: IdentityKey?, direction: IdentityKeyStore.Direction?): Boolean = true
override fun loadSession(address: SignalProtocolAddress?): SessionRecord = aliceSessionRecord ?: SessionRecord()
override fun saveIdentity(address: SignalProtocolAddress?, identityKey: IdentityKey?): Boolean = false
- override fun storeSession(address: SignalProtocolAddress?, record: SessionRecord?) { aliceSessionRecord = record }
+ override fun storeSession(address: SignalProtocolAddress?, record: SessionRecord?) {
+ aliceSessionRecord = record
+ }
override fun getSubDeviceSessions(name: String?): List = emptyList()
override fun containsSession(address: SignalProtocolAddress?): Boolean = aliceSessionRecord != null
- override fun getIdentity(address: SignalProtocolAddress?): IdentityKey = SignalStore.account().aciIdentityKey.publicKey
+ override fun getIdentity(address: SignalProtocolAddress?): IdentityKey = SignalStore.account.aciIdentityKey.publicKey
override fun loadPreKey(preKeyId: Int): PreKeyRecord = throw UnsupportedOperationException()
override fun storePreKey(preKeyId: Int, record: PreKeyRecord?) = throw UnsupportedOperationException()
override fun containsPreKey(preKeyId: Int): Boolean = throw UnsupportedOperationException()
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/FakeClientHelpers.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/FakeClientHelpers.kt
index d18eb79922..903c530611 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/FakeClientHelpers.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/FakeClientHelpers.kt
@@ -14,8 +14,8 @@ import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith
import org.whispersystems.signalservice.api.crypto.ContentHint
import org.whispersystems.signalservice.api.crypto.EnvelopeContent
+import org.whispersystems.signalservice.api.crypto.SealedSenderAccess
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess
-import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.DataMessage
@@ -46,11 +46,10 @@ object FakeClientHelpers {
}
}
- fun getTargetUnidentifiedAccess(myProfileKey: ProfileKey, theirProfileKey: ProfileKey, senderCertificate: SenderCertificate): Optional {
- val selfUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(myProfileKey)
- val themUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey)
+ fun getSealedSenderAccess(theirProfileKey: ProfileKey, senderCertificate: SenderCertificate): SealedSenderAccess? {
+ val themUnidentifiedAccessKey = UnidentifiedAccess(UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey), senderCertificate.serialized, false)
- return UnidentifiedAccessPair(UnidentifiedAccess(selfUnidentifiedAccessKey, senderCertificate.serialized, false), UnidentifiedAccess(themUnidentifiedAccessKey, senderCertificate.serialized, false)).targetUnidentifiedAccess
+ return SealedSenderAccess.forIndividual(themUnidentifiedAccessKey)
}
fun encryptedTextMessage(now: Long, message: String = "Test body message"): EnvelopeContent {
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/GroupTestingUtils.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/GroupTestingUtils.kt
index 0c0d366194..cecba043d0 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/GroupTestingUtils.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/GroupTestingUtils.kt
@@ -33,7 +33,7 @@ object GroupTestingUtils {
.title(MessageContentFuzzer.string())
.build()
- val groupId = SignalDatabase.groups.create(groupMasterKey, decryptedGroupState)!!
+ val groupId = SignalDatabase.groups.create(groupMasterKey, decryptedGroupState, null)!!
val groupRecipientId = SignalDatabase.recipients.getOrInsertFromGroupId(groupId)
SignalDatabase.recipients.setProfileSharing(groupRecipientId, true)
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/MessageContentFuzzer.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/MessageContentFuzzer.kt
index 38a366315b..35ace0612a 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/MessageContentFuzzer.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/MessageContentFuzzer.kt
@@ -2,12 +2,15 @@ package org.thoughtcrime.securesms.testing
import okio.ByteString
import okio.ByteString.Companion.toByteString
+import org.signal.core.util.Base64
+import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith
import org.thoughtcrime.securesms.messages.TestMessage
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
+import org.whispersystems.signalservice.api.util.UuidUtil
import org.whispersystems.signalservice.internal.push.AttachmentPointer
import org.whispersystems.signalservice.internal.push.BodyRange
import org.whispersystems.signalservice.internal.push.Content
@@ -61,13 +64,13 @@ object MessageContentFuzzer {
* - An expire timer value
* - Bold style body ranges
*/
- fun fuzzTextMessage(sentTimestamp: Long? = null, groupContextV2: GroupContextV2? = null): Content {
+ fun fuzzTextMessage(sentTimestamp: Long? = null, groupContextV2: GroupContextV2? = null, allowExpireTimeChanges: Boolean = true): Content {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
timestamp = sentTimestamp
body = string()
- if (random.nextBoolean()) {
+ if (allowExpireTimeChanges && random.nextBoolean()) {
expireTimer = random.nextInt(0..28.days.inWholeSeconds.toInt())
}
if (random.nextBoolean()) {
@@ -150,6 +153,121 @@ object MessageContentFuzzer {
).build()
}
+ fun syncDeleteForMeMessage(allDeletes: List): Content {
+ return Content
+ .Builder()
+ .syncMessage(
+ SyncMessage(
+ deleteForMe = SyncMessage.DeleteForMe(
+ messageDeletes = allDeletes.map { (conversationId, conversationDeletes) ->
+ val conversation = Recipient.resolved(conversationId)
+ SyncMessage.DeleteForMe.MessageDeletes(
+ conversation = if (conversation.isGroup) {
+ SyncMessage.DeleteForMe.ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
+ } else {
+ SyncMessage.DeleteForMe.ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
+ },
+
+ messages = conversationDeletes.map { (author, timestamp) ->
+ SyncMessage.DeleteForMe.AddressableMessage(
+ authorServiceId = Recipient.resolved(author).requireAci().toString(),
+ sentTimestamp = timestamp
+ )
+ }
+ )
+ }
+ )
+ )
+ ).build()
+ }
+
+ fun syncDeleteForMeConversation(allDeletes: List): Content {
+ return Content
+ .Builder()
+ .syncMessage(
+ SyncMessage(
+ deleteForMe = SyncMessage.DeleteForMe(
+ conversationDeletes = allDeletes.map { delete ->
+ val conversation = Recipient.resolved(delete.conversationId)
+ SyncMessage.DeleteForMe.ConversationDelete(
+ conversation = if (conversation.isGroup) {
+ SyncMessage.DeleteForMe.ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
+ } else {
+ SyncMessage.DeleteForMe.ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
+ },
+
+ mostRecentMessages = delete.messages.map { (author, timestamp) ->
+ SyncMessage.DeleteForMe.AddressableMessage(
+ authorServiceId = Recipient.resolved(author).requireAci().toString(),
+ sentTimestamp = timestamp
+ )
+ },
+
+ mostRecentNonExpiringMessages = delete.nonExpiringMessages.map { (author, timestamp) ->
+ SyncMessage.DeleteForMe.AddressableMessage(
+ authorServiceId = Recipient.resolved(author).requireAci().toString(),
+ sentTimestamp = timestamp
+ )
+ },
+
+ isFullDelete = delete.isFullDelete
+ )
+ }
+ )
+ )
+ ).build()
+ }
+
+ fun syncDeleteForMeLocalOnlyConversation(conversations: List): Content {
+ return Content
+ .Builder()
+ .syncMessage(
+ SyncMessage(
+ deleteForMe = SyncMessage.DeleteForMe(
+ localOnlyConversationDeletes = conversations.map { conversationId ->
+ val conversation = Recipient.resolved(conversationId)
+ SyncMessage.DeleteForMe.LocalOnlyConversationDelete(
+ conversation = if (conversation.isGroup) {
+ SyncMessage.DeleteForMe.ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
+ } else {
+ SyncMessage.DeleteForMe.ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
+ }
+ )
+ }
+ )
+ )
+ ).build()
+ }
+
+ fun syncDeleteForMeAttachment(conversationId: RecipientId, message: Pair, uuid: UUID?, digest: ByteArray?, plainTextHash: String?): Content {
+ val conversation = Recipient.resolved(conversationId)
+
+ return Content
+ .Builder()
+ .syncMessage(
+ SyncMessage(
+ deleteForMe = SyncMessage.DeleteForMe(
+ attachmentDeletes = listOf(
+ SyncMessage.DeleteForMe.AttachmentDelete(
+ conversation = if (conversation.isGroup) {
+ SyncMessage.DeleteForMe.ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
+ } else {
+ SyncMessage.DeleteForMe.ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
+ },
+ targetMessage = SyncMessage.DeleteForMe.AddressableMessage(
+ authorServiceId = Recipient.resolved(message.first).requireAci().toString(),
+ sentTimestamp = message.second
+ ),
+ uuid = uuid?.let { UuidUtil.toByteString(it) },
+ fallbackDigest = digest?.toByteString(),
+ fallbackPlaintextHash = plainTextHash?.let { Base64.decodeOrNull(it)?.toByteString() }
+ )
+ )
+ )
+ )
+ ).build()
+ }
+
/**
* Create a random media message that may be:
* - A text body
@@ -278,7 +396,7 @@ object MessageContentFuzzer {
caption = string(allowNullString = true)
blurHash = string()
uploadTimestamp = random.nextLong()
- cdnNumber = 1
+ cdnNumber = 2
build()
}
@@ -290,4 +408,14 @@ object MessageContentFuzzer {
fun fuzzServerDeliveredTimestamp(envelopeTimestamp: Long): Long {
return envelopeTimestamp + 10
}
+
+ data class DeleteForMeSync(
+ val conversationId: RecipientId,
+ val messages: List>,
+ val nonExpiringMessages: List> = emptyList(),
+ val isFullDelete: Boolean = true,
+ val attachments: List> = emptyList()
+ ) {
+ constructor(conversationId: RecipientId, vararg messages: Pair) : this(conversationId, messages.toList())
+ }
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/MockProvider.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/MockProvider.kt
index 4175150446..daca791f17 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/MockProvider.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/MockProvider.kt
@@ -31,7 +31,7 @@ object MockProvider {
val lockedFailure = PushServiceSocket.RegistrationLockFailure().apply {
svr1Credentials = AuthCredentials.create("username", "password")
- svr2Credentials = null
+ svr2Credentials = AuthCredentials.create("username", "password")
}
val primaryOnlyDeviceList = DeviceInfoList().apply {
@@ -68,7 +68,7 @@ object MockProvider {
}
}
- fun createPreKeyResponse(identity: IdentityKeyPair = SignalStore.account().aciIdentityKey, deviceId: Int): PreKeyResponse {
+ fun createPreKeyResponse(identity: IdentityKeyPair = SignalStore.account.aciIdentityKey, deviceId: Int): PreKeyResponse {
val signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), identity.privateKey)
val oneTimePreKey = PreKeyRecord(SecureRandom().nextInt(Medium.MAX_VALUE), Curve.generateKeyPair())
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/ResponseMocking.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/ResponseMocking.kt
index 13268521da..3d3ca25567 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/ResponseMocking.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/ResponseMocking.kt
@@ -55,5 +55,5 @@ inline fun RecordedRequest.parsedRequestBody(): T {
}
private fun defaultRequestPredicate(verb: String, path: String, predicate: RequestPredicate = { true }): RequestPredicate = { request ->
- request.method == verb && request.path.startsWith("/$path") && predicate(request)
+ request.method == verb && request.path?.startsWith("/$path") == true && predicate(request)
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt
index c827a0f82a..153ee24c1b 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt
@@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.database.IdentityTable
import org.thoughtcrime.securesms.database.SignalDatabase
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
+import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.profiles.ProfileName
@@ -45,7 +45,7 @@ import java.util.UUID
*/
class SignalActivityRule(private val othersCount: Int = 4, private val createGroup: Boolean = false) : ExternalResource() {
- val application: Application = ApplicationDependencies.getApplication()
+ val application: Application = AppDependencies.application
lateinit var context: Context
private set
@@ -87,8 +87,8 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
SecurePreferenceManager.getSecurePreferences(application).edit().putBoolean("pref_prompted_push_registration", true).commit()
// MOLLY: Test runner SignalTestRunner initializes MasterSecret
- SignalStore.account().generateAciIdentityKeyIfNecessary()
- SignalStore.account().generatePniIdentityKeyIfNecessary()
+ SignalStore.account.generateAciIdentityKeyIfNecessary()
+ SignalStore.account.generatePniIdentityKeyIfNecessary()
val registrationRepository = RegistrationRepository(application)
@@ -108,19 +108,19 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
verifyAccountResponse = VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false),
masterKey = null,
pin = null,
- aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().aciPreKeys),
- pniPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().pniPreKeys)
+ aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account.aciIdentityKey, SignalStore.account.aciPreKeys),
+ pniPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account.aciIdentityKey, SignalStore.account.pniPreKeys)
),
false
).blockingGet()
ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow
- SignalStore.svr().optOut()
+ SignalStore.svr.optOut()
RegistrationUtil.maybeMarkRegistrationComplete()
SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson"))
- SignalStore.settings().isMessageNotificationsEnabled = false
+ SignalStore.settings.isMessageNotificationsEnabled = false
return Recipient.self()
}
@@ -138,11 +138,11 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
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))
+ SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, false))
SignalDatabase.recipients.setProfileSharing(recipientId, true)
SignalDatabase.recipients.markRegistered(recipientId, aci)
val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair()
- ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), otherIdentity.publicKey)
+ AppDependencies.protocolStore.aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), otherIdentity.publicKey)
others += recipientId
othersKeys += otherIdentity
}
@@ -155,14 +155,14 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
}
fun changeIdentityKey(recipient: Recipient, identityKey: IdentityKey = IdentityKeyUtil.generateIdentityKeyPair().publicKey) {
- ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(recipient.requireServiceId().toString(), 0), identityKey)
+ AppDependencies.protocolStore.aci().saveIdentity(SignalProtocolAddress(recipient.requireServiceId().toString(), 0), identityKey)
}
fun getIdentity(recipient: Recipient): IdentityKey {
- return ApplicationDependencies.getProtocolStore().aci().identities().getIdentity(SignalProtocolAddress(recipient.requireServiceId().toString(), 0))
+ return AppDependencies.protocolStore.aci().identities().getIdentity(SignalProtocolAddress(recipient.requireServiceId().toString(), 0))
}
fun setVerified(recipient: Recipient, status: IdentityTable.VerifiedStatus) {
- ApplicationDependencies.getProtocolStore().aci().identities().setVerified(recipient.id, getIdentity(recipient), IdentityTable.VerifiedStatus.VERIFIED)
+ AppDependencies.protocolStore.aci().identities().setVerified(recipient.id, getIdentity(recipient), IdentityTable.VerifiedStatus.VERIFIED)
}
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalDatabaseRule.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalDatabaseRule.kt
index a3a0794545..978c346685 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalDatabaseRule.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalDatabaseRule.kt
@@ -26,8 +26,8 @@ class SignalDatabaseRule(
override fun starting(description: Description?) {
deleteAllThreads()
- SignalStore.account().setAci(localAci)
- SignalStore.account().setPni(localPni)
+ SignalStore.account.setAci(localAci)
+ SignalStore.account.setPni(localPni)
}
override fun finished(description: Description?) {
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/TestUtils.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/TestUtils.kt
index 542672cc17..49831b637f 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/TestUtils.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/TestUtils.kt
@@ -1,13 +1,14 @@
package org.thoughtcrime.securesms.testing
import android.database.Cursor
-import android.util.Base64
+import org.hamcrest.Matcher
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.hasSize
import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.not
import org.hamcrest.Matchers.notNullValue
import org.hamcrest.Matchers.nullValue
+import org.signal.core.util.Hex
import org.signal.core.util.logging.Log
import org.signal.core.util.readToList
import org.signal.core.util.select
@@ -56,33 +57,41 @@ infix fun > T.assertIsSize(expected: Int) {
assertThat(this, hasSize(expected))
}
+infix fun T.assert(matcher: Matcher) {
+ assertThat(this, matcher)
+}
+
fun CountDownLatch.awaitFor(duration: Duration) {
if (!await(duration.inWholeMilliseconds, TimeUnit.MILLISECONDS)) {
throw TimeoutException("Latch await took longer than ${duration.inWholeMilliseconds}ms")
}
}
-fun dumpTableToLogs(tag: String = "TestUtils", table: String) {
- dumpTable(table).forEach { Log.d(tag, it.toString()) }
+fun dumpTableToLogs(tag: String = "TestUtils", table: String, columns: Set? = null) {
+ dumpTable(table, columns).forEach { Log.d(tag, it.toString()) }
}
-fun dumpTable(table: String): List>> {
+fun dumpTable(table: String, columns: Set?): List>> {
return SignalDatabase.rawDatabase
.select()
.from(table)
.run()
.readToList { cursor ->
- val map: List> = cursor.columnNames.map { column ->
- val index = cursor.getColumnIndex(column)
- var data: String? = when (cursor.getType(index)) {
- Cursor.FIELD_TYPE_BLOB -> Base64.encodeToString(cursor.getBlob(index), 0)
- else -> cursor.getString(index)
- }
- if (table == MessageTable.TABLE_NAME && column == MessageTable.TYPE) {
- data = MessageTableTestUtils.typeColumnToString(cursor.getLong(index))
- }
+ val map: List> = cursor.columnNames.mapNotNull { column ->
+ if (columns == null || columns.contains(column)) {
+ val index = cursor.getColumnIndex(column)
+ var data: String? = when (cursor.getType(index)) {
+ Cursor.FIELD_TYPE_BLOB -> Hex.toStringCondensed(cursor.getBlob(index))
+ else -> cursor.getString(index)
+ }
+ if (table == MessageTable.TABLE_NAME && column == MessageTable.TYPE) {
+ data = MessageTableTestUtils.typeColumnToString(cursor.getLong(index))
+ }
- column to data
+ column to data
+ } else {
+ null
+ }
}
map
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/util/FeatureFlagsAccessor.java b/app/src/androidTest/java/org/thoughtcrime/securesms/util/FeatureFlagsAccessor.java
deleted file mode 100644
index 437b1f6e77..0000000000
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/util/FeatureFlagsAccessor.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.thoughtcrime.securesms.util;
-
-/**
- * A class that allows us to inject feature flags during tests.
- */
-public final class FeatureFlagsAccessor {
-
- public static void forceValue(String key, Object value) {
- FeatureFlags.FORCED_VALUES.put(key, value);
- }
-}
diff --git a/app/src/benchmark/java/org/signal/benchmark/setup/TestUsers.kt b/app/src/benchmark/java/org/signal/benchmark/setup/TestUsers.kt
deleted file mode 100644
index 829dbd0c86..0000000000
--- a/app/src/benchmark/java/org/signal/benchmark/setup/TestUsers.kt
+++ /dev/null
@@ -1,117 +0,0 @@
-package org.signal.benchmark.setup
-
-import android.app.Application
-import android.content.SharedPreferences
-import android.preference.PreferenceManager
-import org.signal.benchmark.DummyAccountManagerFactory
-import org.signal.core.util.concurrent.safeBlockingGet
-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.SignalDatabase
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
-import org.thoughtcrime.securesms.keyvalue.SignalStore
-import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor
-import org.thoughtcrime.securesms.profiles.ProfileName
-import org.thoughtcrime.securesms.push.AccountManagerFactory
-import org.thoughtcrime.securesms.recipients.Recipient
-import org.thoughtcrime.securesms.recipients.RecipientId
-import org.thoughtcrime.securesms.registration.RegistrationData
-import org.thoughtcrime.securesms.registration.RegistrationRepository
-import org.thoughtcrime.securesms.registration.RegistrationUtil
-import org.thoughtcrime.securesms.registration.VerifyResponse
-import org.thoughtcrime.securesms.util.Util
-import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
-import org.whispersystems.signalservice.api.push.ServiceId.ACI
-import org.whispersystems.signalservice.api.push.SignalServiceAddress
-import org.whispersystems.signalservice.internal.ServiceResponse
-import org.whispersystems.signalservice.internal.ServiceResponseProcessor
-import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
-import java.util.UUID
-
-object TestUsers {
-
- private var generatedOthers: Int = 0
-
- fun setupSelf(): Recipient {
- val application: Application = ApplicationDependencies.getApplication()
- DeviceTransferBlockingInterceptor.getInstance().blockNetwork()
-
- PreferenceManager.getDefaultSharedPreferences(application).edit().putBoolean("pref_prompted_push_registration", true).commit()
- val masterSecret = MasterSecretUtil.generateMasterSecret(application, MasterSecretUtil.UNENCRYPTED_PASSPHRASE)
- MasterSecretUtil.generateAsymmetricMasterSecret(application, masterSecret)
- val preferences: SharedPreferences = application.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0)
- preferences.edit().putBoolean("passphrase_initialized", true).commit()
-
- SignalStore.account().generateAciIdentityKeyIfNecessary()
- SignalStore.account().generatePniIdentityKeyIfNecessary()
-
- val registrationRepository = RegistrationRepository(application)
- val registrationData = RegistrationData(
- code = "123123",
- e164 = "+15555550101",
- password = Util.getSecret(18),
- registrationId = registrationRepository.registrationId,
- profileKey = registrationRepository.getProfileKey("+15555550101"),
- fcmToken = "fcm-token",
- pniRegistrationId = registrationRepository.pniRegistrationId,
- recoveryPassword = "asdfasdfasdfasdf"
- )
-
- val verifyResponse = VerifyResponse(
- VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false),
- masterKey = null,
- pin = null,
- aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().aciPreKeys),
- pniPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().pniPreKeys)
- )
-
- AccountManagerFactory.setInstance(DummyAccountManagerFactory())
-
- val response: ServiceResponse = registrationRepository.registerAccount(
- registrationData,
- verifyResponse,
- false
- ).safeBlockingGet()
-
- ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow
-
- SignalStore.svr().optOut()
- RegistrationUtil.maybeMarkRegistrationComplete()
- SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson"))
-
- return Recipient.self()
- }
-
- fun setupTestRecipient(): RecipientId {
- return setupTestRecipients(1).first()
- }
-
- fun setupTestRecipients(othersCount: Int): List {
- val others = mutableListOf()
- synchronized(this) {
- if (generatedOthers + othersCount !in 0 until 1000) {
- throw IllegalArgumentException("$othersCount must be between 0 and 1000")
- }
-
- for (i in generatedOthers until generatedOthers + othersCount) {
- val aci = ACI.from(UUID.randomUUID())
- 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))
- SignalDatabase.recipients.setProfileSharing(recipientId, true)
- SignalDatabase.recipients.markRegistered(recipientId, aci)
- val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair()
- ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), otherIdentity.publicKey)
-
- others += recipientId
- }
-
- generatedOthers += othersCount
- }
-
- return others
- }
-}
diff --git a/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/ConversationElementGenerator.kt b/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/ConversationElementGenerator.kt
index 1b358b84cd..47997cc23b 100644
--- a/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/ConversationElementGenerator.kt
+++ b/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/ConversationElementGenerator.kt
@@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.conversation.v2.data.OutgoingTextOnly
import org.thoughtcrime.securesms.database.MessageTypes
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.database.model.StoryType
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
+import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.mms.SlideDeck
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
@@ -123,7 +123,7 @@ class ConversationElementGenerator {
)
val conversationMessage = ConversationMessageFactory.createWithUnresolvedData(
- ApplicationDependencies.getApplication(),
+ AppDependencies.application,
record,
Recipient.UNKNOWN
)
diff --git a/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/InternalConversationTestFragment.kt b/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/InternalConversationTestFragment.kt
index 3dfdd94d5e..929d9de17b 100644
--- a/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/InternalConversationTestFragment.kt
+++ b/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/InternalConversationTestFragment.kt
@@ -231,6 +231,10 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
+ override fun onChangeProfileNameUpdateContact(recipient: Recipient) {
+ Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
+ }
+
override fun onCallToAction(action: String) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
@@ -272,7 +276,7 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
- override fun onEditedIndicatorClicked(messageRecord: MessageRecord) {
+ override fun onEditedIndicatorClicked(conversationMessage: ConversationMessage) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
@@ -296,6 +300,10 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
+ override fun onPaymentTombstoneClicked() {
+ Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
+ }
+
override fun onShowSafetyTips(forGroup: Boolean) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9f491e6e98..d32a766b25 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -719,13 +719,6 @@
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
-
-
-
-
-
-
-
diff --git a/app/src/main/baseline-prof.txt b/app/src/main/baseline-prof.txt
index cdd8c68769..59c47ff109 100644
--- a/app/src/main/baseline-prof.txt
+++ b/app/src/main/baseline-prof.txt
@@ -1,38 +1,313 @@
+HPLandroidx/appcompat/widget/AppCompatTextView;->setCompoundDrawablesWithIntrinsicBounds(IIII)V
HPLandroidx/appcompat/widget/SearchView;->(Landroid/content/Context;Landroid/util/AttributeSet;I)V
HPLandroidx/core/view/ViewGroupKt$descendants$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
-HPLandroidx/customview/poolingcontainer/PoolingContainer;->callPoolingContainerOnRelease(Landroid/view/View;)V
+HPLandroidx/core/view/ViewGroupKt$iterator$1;->next()Landroid/view/View;
+HPLandroidx/core/view/ViewGroupKt$iterator$1;->next()Ljava/lang/Object;
+HPLandroidx/customview/poolingcontainer/PoolingContainerListenerHolder;->()V
HPLandroidx/customview/poolingcontainer/PoolingContainerListenerHolder;->onRelease()V
HPLandroidx/fragment/app/FragmentManager;->saveAllStateInternal()Landroid/os/Bundle;
HPLandroidx/fragment/app/FragmentStateManager;->saveState()Landroid/os/Bundle;
-HPLandroidx/recyclerview/widget/AsyncListDiffer$1$1;->areItemsTheSame(II)Z
+HPLandroidx/recyclerview/widget/BatchingListUpdateCallback;->onChanged(IILjava/lang/Object;)V
+HPLandroidx/recyclerview/widget/ConcatAdapter;->findRelativeAdapterPositionIn(Landroidx/recyclerview/widget/RecyclerView$Adapter;Landroidx/recyclerview/widget/RecyclerView$ViewHolder;I)I
+HPLandroidx/recyclerview/widget/ConcatAdapter;->getItemViewType(I)I
+HPLandroidx/recyclerview/widget/ConcatAdapterController;->findWrapperAndLocalPosition(I)Landroidx/recyclerview/widget/ConcatAdapterController$WrapperAndLocalPosition;
+HPLandroidx/recyclerview/widget/ConcatAdapterController;->getItemViewType(I)I
+HPLandroidx/recyclerview/widget/ConcatAdapterController;->getLocalAdapterPosition(Landroidx/recyclerview/widget/RecyclerView$Adapter;Landroidx/recyclerview/widget/RecyclerView$ViewHolder;I)I
+HPLandroidx/recyclerview/widget/ConcatAdapterController;->onBindViewHolder(Landroidx/recyclerview/widget/RecyclerView$ViewHolder;I)V
+HPLandroidx/recyclerview/widget/ConcatAdapterController;->releaseWrapperAndLocalPosition(Landroidx/recyclerview/widget/ConcatAdapterController$WrapperAndLocalPosition;)V
HPLandroidx/recyclerview/widget/DiffUtil;->backward(Landroidx/recyclerview/widget/DiffUtil$Range;Landroidx/recyclerview/widget/DiffUtil$Callback;Landroidx/recyclerview/widget/DiffUtil$CenteredArray;Landroidx/recyclerview/widget/DiffUtil$CenteredArray;I)Landroidx/recyclerview/widget/DiffUtil$Snake;
+HPLandroidx/recyclerview/widget/ListAdapter;->getCurrentList()Ljava/util/List;
+HPLandroidx/recyclerview/widget/NestedAdapterWrapper;->getItemViewType(I)I
+HPLandroidx/recyclerview/widget/RecyclerView;->getChildViewHolder(Landroid/view/View;)Landroidx/recyclerview/widget/RecyclerView$ViewHolder;
+HPLandroidx/recyclerview/widget/RecyclerView;->viewRangeUpdate(IILjava/lang/Object;)V
HPLandroidx/recyclerview/widget/ViewInfoStore;->addToPreLayout(Landroidx/recyclerview/widget/RecyclerView$ViewHolder;Landroidx/recyclerview/widget/RecyclerView$ItemAnimator$ItemHolderInfo;)V
+HPLandroidx/recyclerview/widget/ViewInfoStore;->isDisappearing(Landroidx/recyclerview/widget/RecyclerView$ViewHolder;)Z
HPLandroidx/recyclerview/widget/ViewInfoStore;->popFromLayoutStep(Landroidx/recyclerview/widget/RecyclerView$ViewHolder;I)Landroidx/recyclerview/widget/RecyclerView$ItemAnimator$ItemHolderInfo;
+HPLandroidx/recyclerview/widget/ViewTypeStorage$IsolatedViewTypeStorage$WrapperViewTypeLookup;->localToGlobal(I)I
HPLandroidx/savedstate/SavedStateRegistry;->performSave(Landroid/os/Bundle;)V
-HPLcom/annimon/stream/iterator/LazyIterator;->hasNext()Z
+HPLcom/annimon/stream/operator/ObjFilter;->hasNext()Z
+HPLcom/google/android/material/animation/ArgbEvaluatorCompat;->evaluate(FLjava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;
+HPLcom/google/common/collect/Sets;->intersection(Ljava/util/Set;Ljava/util/Set;)Lcom/google/common/collect/Sets$SetView;
+HPLio/reactivex/rxjava3/internal/operators/flowable/FlowableObserveOn$BaseObserveOnSubscriber;->cancel()V
HPLio/reactivex/rxjava3/internal/operators/observable/ObservableFlatMapMaybe$FlatMapMaybeObserver;->innerSuccess(Lio/reactivex/rxjava3/internal/operators/observable/ObservableFlatMapMaybe$FlatMapMaybeObserver$InnerObserver;Ljava/lang/Object;)V
HPLio/reactivex/rxjava3/internal/operators/observable/ObservableFlatMapMaybe$FlatMapMaybeObserver;->onNext(Ljava/lang/Object;)V
-HPLj$/util/Optional;->ofNullable(Ljava/lang/Object;)Lj$/util/Optional;
-HPLokio/SegmentPool;->firstRef()Ljava/util/concurrent/atomic/AtomicReference;
-HPLorg/signal/core/util/concurrent/DeadlockDetector;->hasPotentialLock([Ljava/lang/StackTraceElement;)Z
-HPLorg/signal/core/util/tracing/Tracer;->start(Ljava/lang/String;JLjava/lang/String;Ljava/lang/String;)V
-HPLorg/signal/core/util/tracing/TrackEvent$Builder;->track_uuid(Ljava/lang/Long;)Lorg/signal/core/util/tracing/TrackEvent$Builder;
-HPLorg/thoughtcrime/securesms/attachments/PointerAttachment$Companion;->forPointers(Lj$/util/Optional;)Ljava/util/List;
+HPLio/reactivex/rxjava3/internal/operators/observable/ObservableReplay$BoundedReplayBuffer;->removeFirst()V
+HPLj$/time/Instant;->atZone(Lj$/time/ZoneId;)Lj$/time/ZonedDateTime;
+HPLj$/time/LocalDateTime;->U()Lj$/time/LocalDate;
+HPLj$/time/ZonedDateTime;->(Lj$/time/LocalDateTime;Lj$/time/ZoneId;Lj$/time/ZoneOffset;)V
+HPLj$/time/ZonedDateTime;->M(JILj$/time/ZoneId;)Lj$/time/ZonedDateTime;
+HPLj$/time/ZonedDateTime;->N(Lj$/time/Instant;Lj$/time/ZoneId;)Lj$/time/ZonedDateTime;
+HPLj$/time/ZonedDateTime;->toLocalDate()Lj$/time/LocalDate;
+HPLj$/util/S;->r(Lj$/util/function/Consumer;)Z
+HPLkotlin/collections/CollectionsKt___CollectionsKt;->sortedWith(Ljava/lang/Iterable;Ljava/util/Comparator;)Ljava/util/List;
+HPLkotlin/sequences/SequencesKt__SequenceBuilderKt;->sequence(Lkotlin/jvm/functions/Function2;)Lkotlin/sequences/Sequence;
+HPLkotlin/sequences/SequencesKt___SequencesJvmKt$filterIsInstance$1;->invoke(Ljava/lang/Object;)Ljava/lang/Boolean;
+HPLkotlin/sequences/SequencesKt___SequencesJvmKt$filterIsInstance$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HPLokio/OutputStreamSink;->write(Lokio/Buffer;J)V
+HPLorg/conscrypt/ConscryptEngineSocket$SSLInputStream;->readFromSocket()I
+HPLorg/signal/core/util/OptionalExtensionsKt;->toOptional(Ljava/lang/Object;)Lj$/util/Optional;
+HPLorg/signal/core/util/concurrent/SettableFuture;->(Ljava/lang/Object;)V
+HPLorg/signal/core/util/concurrent/SignalExecutors;->lambda$newCachedBoundedExecutor$1(Ljava/lang/Runnable;Ljava/util/concurrent/ThreadPoolExecutor;)V
+HPLorg/signal/libsignal/protocol/ecc/ECPublicKey;->equals(Ljava/lang/Object;)Z
+HPLorg/signal/paging/FixedSizePagingController;->lambda$onDataItemChanged$2(Ljava/lang/Object;)V
+HPLorg/thoughtcrime/securesms/WebRtcCallActivity$$ExternalSyntheticBackport8;->m([Ljava/lang/Object;)Ljava/util/List;
+HPLorg/thoughtcrime/securesms/badges/gifts/OpenableGiftItemDecoration$onDrawOver$2;->invoke(Lorg/thoughtcrime/securesms/badges/gifts/OpenableGift;)Ljava/lang/Boolean;
+HPLorg/thoughtcrime/securesms/badges/gifts/OpenableGiftItemDecoration$onDrawOver$4;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HPLorg/thoughtcrime/securesms/badges/gifts/OpenableGiftItemDecoration$onDrawOver$4;->invoke(Lorg/thoughtcrime/securesms/badges/gifts/OpenableGift;)Ljava/lang/Boolean;
+HPLorg/thoughtcrime/securesms/badges/gifts/OpenableGiftItemDecoration$onDrawOver$notAnimated$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HPLorg/thoughtcrime/securesms/badges/gifts/OpenableGiftItemDecoration$onDrawOver$notAnimated$1;->invoke(Lorg/thoughtcrime/securesms/badges/gifts/OpenableGift;)Ljava/lang/Boolean;
+HPLorg/thoughtcrime/securesms/components/AlbumThumbnailView;->applyCorners()V
+HPLorg/thoughtcrime/securesms/components/AlbumThumbnailView;->applyCornersForSizeClass2()V
+HPLorg/thoughtcrime/securesms/components/AlbumThumbnailView;->getCells()[Lorg/thoughtcrime/securesms/components/ThumbnailView;
+HPLorg/thoughtcrime/securesms/components/AlbumThumbnailView;->setCellBackgroundColor(I)V
+HPLorg/thoughtcrime/securesms/components/AlbumThumbnailView;->setRadii(IIII)V
+HPLorg/thoughtcrime/securesms/components/AlbumThumbnailView;->setRelativeRadii(Lorg/thoughtcrime/securesms/components/ThumbnailView;IIII)V
+HPLorg/thoughtcrime/securesms/components/AlbumThumbnailView;->setSlide(Lcom/bumptech/glide/RequestManager;Lorg/thoughtcrime/securesms/mms/Slide;IZ)V
+HPLorg/thoughtcrime/securesms/components/AlbumThumbnailView;->setSlides(Lcom/bumptech/glide/RequestManager;Ljava/util/List;Z)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemFooter;->presentDate(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Ljava/util/Locale;Lorg/thoughtcrime/securesms/conversation/ConversationItemDisplayMode;)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemFooter;->presentDeliveryStatus(Lorg/thoughtcrime/securesms/database/model/MessageRecord;)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnail;->(Landroid/content/Context;Landroid/util/AttributeSet;)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnail;->dispatchDraw(Landroid/graphics/Canvas;)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnail;->setCancelTransferClickListener(Lorg/thoughtcrime/securesms/mms/SlidesClickedListener;)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnail;->setClickable(Z)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnail;->setConversationColor(I)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnail;->setCorners(IIII)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnail;->setFocusable(Z)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnail;->setImageResource(Lcom/bumptech/glide/RequestManager;Ljava/util/List;ZZ)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnail;->setMaximumThumbnailHeight(I)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnail;->setMinimumThumbnailWidth(I)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnail;->setOnLongClickListener(Landroid/view/View$OnLongClickListener;)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnail;->setPlayVideoClickListener(Lorg/thoughtcrime/securesms/mms/SlideClickListener;)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnail;->setStartTransferClickListener(Lorg/thoughtcrime/securesms/mms/SlidesClickedListener;)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnail;->setThumbnailBounds([I)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnail;->setThumbnailClickListener(Lorg/thoughtcrime/securesms/mms/SlideClickListener;)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnail;->showThumbnailView()V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$AlbumViewState;->(ZZLorg/thoughtcrime/securesms/mms/SlideClickListener;Lorg/thoughtcrime/securesms/mms/SlidesClickedListener;Lorg/thoughtcrime/securesms/mms/SlidesClickedListener;Lorg/thoughtcrime/securesms/mms/SlideClickListener;Landroid/view/View$OnLongClickListener;IIIIII)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$AlbumViewState;->applyState(Lorg/thoughtcrime/securesms/util/views/Stub;)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$AlbumViewState;->copy$default(Lorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$AlbumViewState;ZZLorg/thoughtcrime/securesms/mms/SlideClickListener;Lorg/thoughtcrime/securesms/mms/SlidesClickedListener;Lorg/thoughtcrime/securesms/mms/SlidesClickedListener;Lorg/thoughtcrime/securesms/mms/SlideClickListener;Landroid/view/View$OnLongClickListener;IIIIIIILjava/lang/Object;)Lorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$AlbumViewState;
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$AlbumViewState;->copy(ZZLorg/thoughtcrime/securesms/mms/SlideClickListener;Lorg/thoughtcrime/securesms/mms/SlidesClickedListener;Lorg/thoughtcrime/securesms/mms/SlidesClickedListener;Lorg/thoughtcrime/securesms/mms/SlideClickListener;Landroid/view/View$OnLongClickListener;IIIIII)Lorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$AlbumViewState;
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$ThumbnailViewState;->(FZZLorg/thoughtcrime/securesms/mms/SlideClickListener;Lorg/thoughtcrime/securesms/mms/SlidesClickedListener;Lorg/thoughtcrime/securesms/mms/SlidesClickedListener;Lorg/thoughtcrime/securesms/mms/SlideClickListener;Landroid/view/View$OnLongClickListener;IIIIIIIII)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$ThumbnailViewState;->applyState(Lorg/thoughtcrime/securesms/util/views/Stub;)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$ThumbnailViewState;->copy$default(Lorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$ThumbnailViewState;FZZLorg/thoughtcrime/securesms/mms/SlideClickListener;Lorg/thoughtcrime/securesms/mms/SlidesClickedListener;Lorg/thoughtcrime/securesms/mms/SlidesClickedListener;Lorg/thoughtcrime/securesms/mms/SlideClickListener;Landroid/view/View$OnLongClickListener;IIIIIIIIIILjava/lang/Object;)Lorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$ThumbnailViewState;
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$ThumbnailViewState;->copy(FZZLorg/thoughtcrime/securesms/mms/SlideClickListener;Lorg/thoughtcrime/securesms/mms/SlidesClickedListener;Lorg/thoughtcrime/securesms/mms/SlidesClickedListener;Lorg/thoughtcrime/securesms/mms/SlideClickListener;Landroid/view/View$OnLongClickListener;IIIIIIIII)Lorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$ThumbnailViewState;
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnailState;->(Lorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$ThumbnailViewState;Lorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$AlbumViewState;)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnailState;->applyState(Lorg/thoughtcrime/securesms/util/views/Stub;Lorg/thoughtcrime/securesms/util/views/Stub;)V
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnailState;->copy$default(Lorg/thoughtcrime/securesms/components/ConversationItemThumbnailState;Lorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$ThumbnailViewState;Lorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$AlbumViewState;ILjava/lang/Object;)Lorg/thoughtcrime/securesms/components/ConversationItemThumbnailState;
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnailState;->copy(Lorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$ThumbnailViewState;Lorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$AlbumViewState;)Lorg/thoughtcrime/securesms/components/ConversationItemThumbnailState;
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnailState;->getAlbumViewState()Lorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$AlbumViewState;
+HPLorg/thoughtcrime/securesms/components/ConversationItemThumbnailState;->getThumbnailViewState()Lorg/thoughtcrime/securesms/components/ConversationItemThumbnailState$ThumbnailViewState;
+HPLorg/thoughtcrime/securesms/components/CornerMask;->mask(Landroid/graphics/Canvas;)V
+HPLorg/thoughtcrime/securesms/components/Outliner;->draw(Landroid/graphics/Canvas;IIII)V
+HPLorg/thoughtcrime/securesms/components/QuoteView;->dismiss()V
+HPLorg/thoughtcrime/securesms/components/ThumbnailView;->(Landroid/content/Context;Landroid/util/AttributeSet;I)V
+HPLorg/thoughtcrime/securesms/components/ThumbnailView;->hasSameContents(Lorg/thoughtcrime/securesms/mms/Slide;Lorg/thoughtcrime/securesms/mms/Slide;)Z
+HPLorg/thoughtcrime/securesms/components/ThumbnailView;->onMeasure(II)V
+HPLorg/thoughtcrime/securesms/components/ThumbnailView;->setBounds(IIII)V
+HPLorg/thoughtcrime/securesms/components/ThumbnailView;->setCancelTransferClickListener(Lorg/thoughtcrime/securesms/mms/SlidesClickedListener;)V
+HPLorg/thoughtcrime/securesms/components/ThumbnailView;->setClickable(Z)V
+HPLorg/thoughtcrime/securesms/components/ThumbnailView;->setFocusable(Z)V
+HPLorg/thoughtcrime/securesms/components/ThumbnailView;->setImageResource(Lcom/bumptech/glide/RequestManager;Lorg/thoughtcrime/securesms/mms/Slide;ZZII)Lorg/signal/core/util/concurrent/ListenableFuture;
+HPLorg/thoughtcrime/securesms/components/ThumbnailView;->setRadii(IIII)V
+HPLorg/thoughtcrime/securesms/components/ThumbnailView;->setThumbnailClickListener(Lorg/thoughtcrime/securesms/mms/SlideClickListener;)V
+HPLorg/thoughtcrime/securesms/components/emoji/EmojiTextView;->getLongestLineWidth(Ljava/lang/CharSequence;)F
+HPLorg/thoughtcrime/securesms/components/emoji/EmojiTextView;->setOverflowText(Ljava/lang/CharSequence;)V
+HPLorg/thoughtcrime/securesms/components/emoji/EmojiTextView;->setTextSize(IF)V
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView$Companion;->getTransferState(Ljava/util/List;)I
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView$Progress;->toString()Ljava/lang/String;
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView$setClickable$1;->(Z)V
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView$setClickable$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView$setClickable$1;->invoke(Lorg/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState;)Lorg/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState;
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView$setFocusable$1;->(Z)V
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView$setFocusable$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView$setFocusable$1;->invoke(Lorg/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState;)Lorg/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState;
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView$setSlides$2;->invoke(Lorg/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState;)Lorg/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState;
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView;->(Landroid/content/Context;Landroid/util/AttributeSet;I)V
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView;->deriveMode(Lorg/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState;)Lorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView$Mode;
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView;->isUpdateToExistingSet(Lorg/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState;Ljava/util/List;)Z
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView;->setCancelClickListener(Landroid/view/View$OnClickListener;)V
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView;->setClickable(Z)V
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView;->setFocusable(Z)V
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView;->setShowSecondaryText(Z)V
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView;->setSlides(Ljava/util/List;)V
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView;->setTransferClickListener(Landroid/view/View$OnClickListener;)V
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView;->setVisible(Z)V
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlView;->updateState(Lkotlin/jvm/functions/Function1;)V
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState;->(ZZZLjava/util/List;Landroid/view/View$OnClickListener;Landroid/view/View$OnClickListener;Landroid/view/View$OnClickListener;ZLjava/util/Map;Ljava/util/Map;ZZ)V
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState;->copy$default(Lorg/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState;ZZZLjava/util/List;Landroid/view/View$OnClickListener;Landroid/view/View$OnClickListener;Landroid/view/View$OnClickListener;ZLjava/util/Map;Ljava/util/Map;ZZILjava/lang/Object;)Lorg/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState;
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState;->copy(ZZZLjava/util/List;Landroid/view/View$OnClickListener;Landroid/view/View$OnClickListener;Landroid/view/View$OnClickListener;ZLjava/util/Map;Ljava/util/Map;ZZ)Lorg/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState;
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState;->equals(Ljava/lang/Object;)Z
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState;->toString()Ljava/lang/String;
+HPLorg/thoughtcrime/securesms/components/transfercontrols/TransferProgressView;->(Landroid/content/Context;Landroid/util/AttributeSet;II)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationHeaderView;->setTitle(Lorg/thoughtcrime/securesms/recipients/Recipient;Ljava/lang/Runnable;)Ljava/lang/String;
+HPLorg/thoughtcrime/securesms/conversation/ConversationHeaderView;->updateOutlineVisibility()V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->bind(Landroidx/lifecycle/LifecycleOwner;Lorg/thoughtcrime/securesms/conversation/ConversationMessage;Lj$/util/Optional;Lj$/util/Optional;Lcom/bumptech/glide/RequestManager;Ljava/util/Locale;Ljava/util/Set;Lorg/thoughtcrime/securesms/recipients/Recipient;Ljava/lang/String;ZZZZLorg/thoughtcrime/securesms/conversation/colors/Colorizer;Lorg/thoughtcrime/securesms/conversation/ConversationItemDisplayMode;)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->forceFooter(Lorg/thoughtcrime/securesms/database/model/MessageRecord;)Z
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->getActiveFooter(Lorg/thoughtcrime/securesms/database/model/MessageRecord;)Lorg/thoughtcrime/securesms/components/ConversationItemFooter;
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->getColorizerProjections(Landroid/view/ViewGroup;)Lorg/thoughtcrime/securesms/util/ProjectionList;
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->getGiftId()J
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->getOpenableGiftProjection(Z)Lorg/thoughtcrime/securesms/util/Projection;
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->getSnapshotProjections(Landroid/view/ViewGroup;ZZ)Lorg/thoughtcrime/securesms/util/ProjectionList;
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->hasExtraText(Lorg/thoughtcrime/securesms/database/model/MessageRecord;)Z
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->hasNoBubble(Lorg/thoughtcrime/securesms/database/model/MessageRecord;)Z
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->isCaptionlessMms(Lorg/thoughtcrime/securesms/database/model/MessageRecord;)Z
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->isEndOfMessageCluster(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Lj$/util/Optional;Z)Z
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->isFooterVisible(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Lj$/util/Optional;Z)Z
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->isStartOfMessageCluster(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Lj$/util/Optional;Z)Z
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->isWithinClusteringTime(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Lorg/thoughtcrime/securesms/database/model/MessageRecord;)Z
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->onMeasure(II)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->readDimen(I)I
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->setAuthor(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Lj$/util/Optional;Lj$/util/Optional;ZZ)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->setBodyText(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Ljava/lang/String;Z)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->setBubbleState(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Lorg/thoughtcrime/securesms/recipients/Recipient;ZLorg/thoughtcrime/securesms/conversation/colors/Colorizer;)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->setContactPhoto(Lorg/thoughtcrime/securesms/recipients/Recipient;)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->setFooter(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Lj$/util/Optional;Ljava/util/Locale;ZZ)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->setGroupAuthorColor(Lorg/thoughtcrime/securesms/database/model/MessageRecord;ZLorg/thoughtcrime/securesms/conversation/colors/Colorizer;)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->setGutterSizes(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Z)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->setHasBeenQuoted(Lorg/thoughtcrime/securesms/conversation/ConversationMessage;)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->setInteractionState(Lorg/thoughtcrime/securesms/conversation/ConversationMessage;Z)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->setMediaAttributes(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Lj$/util/Optional;Lj$/util/Optional;ZZZZ)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->setMessageShape(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Lj$/util/Optional;Lj$/util/Optional;Z)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->setMessageSpacing(Landroid/content/Context;Lorg/thoughtcrime/securesms/database/model/MessageRecord;Lj$/util/Optional;Lj$/util/Optional;Z)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->setQuote(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Lj$/util/Optional;Lj$/util/Optional;Z)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->setReactions(Lorg/thoughtcrime/securesms/database/model/MessageRecord;)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->setStatusIcons(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Z)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->setThumbnailCorners(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Lj$/util/Optional;Lj$/util/Optional;Z)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->shouldInterceptClicks(Lorg/thoughtcrime/securesms/database/model/MessageRecord;)Z
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->showProjectionArea()V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItem;->unbind()V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItemBodyBubble;->onDrawForeground(Landroid/graphics/Canvas;)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItemBodyBubble;->setQuoteViewProjection(Lorg/thoughtcrime/securesms/util/Projection;)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationItemBodyBubble;->setVideoPlayerProjection(Lorg/thoughtcrime/securesms/util/Projection;)V
+HPLorg/thoughtcrime/securesms/conversation/ConversationMessage;->getConversationTimestamp()J
+HPLorg/thoughtcrime/securesms/conversation/MarkReadHelper;->getLatestTimestamp(Lorg/thoughtcrime/securesms/conversation/ConversationAdapterBridge;Landroidx/recyclerview/widget/LinearLayoutManager;)Lj$/util/Optional;
+HPLorg/thoughtcrime/securesms/conversation/colors/Colorizer;->getIncomingBodyTextColor(Landroid/content/Context;Z)I
+HPLorg/thoughtcrime/securesms/conversation/colors/Colorizer;->getIncomingGroupSenderColor(Landroid/content/Context;Lorg/thoughtcrime/securesms/recipients/Recipient;)I
+HPLorg/thoughtcrime/securesms/conversation/colors/RecyclerViewColorizer$itemDecoration$1;->getItemOffsets(Landroid/graphics/Rect;Landroid/view/View;Landroidx/recyclerview/widget/RecyclerView;Landroidx/recyclerview/widget/RecyclerView$State;)V
HPLorg/thoughtcrime/securesms/conversation/mutiselect/ConversationItemAnimator;->animateChange(Landroidx/recyclerview/widget/RecyclerView$ViewHolder;Landroidx/recyclerview/widget/RecyclerView$ViewHolder;Landroidx/recyclerview/widget/RecyclerView$ItemAnimator$ItemHolderInfo;Landroidx/recyclerview/widget/RecyclerView$ItemAnimator$ItemHolderInfo;)Z
HPLorg/thoughtcrime/securesms/conversation/mutiselect/ConversationItemAnimator;->animatePersistence(Landroidx/recyclerview/widget/RecyclerView$ViewHolder;Landroidx/recyclerview/widget/RecyclerView$ItemAnimator$ItemHolderInfo;Landroidx/recyclerview/widget/RecyclerView$ItemAnimator$ItemHolderInfo;)Z
+HPLorg/thoughtcrime/securesms/conversation/mutiselect/ConversationItemAnimator;->animateSlide(Landroidx/recyclerview/widget/RecyclerView$ViewHolder;Landroidx/recyclerview/widget/RecyclerView$ItemAnimator$ItemHolderInfo;Landroidx/recyclerview/widget/RecyclerView$ItemAnimator$ItemHolderInfo;)Z
+HPLorg/thoughtcrime/securesms/conversation/mutiselect/ConversationItemAnimator;->onAnimationFinished(Landroidx/recyclerview/widget/RecyclerView$ViewHolder;)V
+HPLorg/thoughtcrime/securesms/conversation/mutiselect/MultiselectCollection$Single;->toSet()Ljava/util/Set;
+HPLorg/thoughtcrime/securesms/conversation/mutiselect/MultiselectItemDecoration;->getCurrentSelection(Landroidx/recyclerview/widget/RecyclerView;)Ljava/util/Set;
+HPLorg/thoughtcrime/securesms/conversation/mutiselect/MultiselectItemDecoration;->getDifferenceForPart(Ljava/util/Set;Lorg/thoughtcrime/securesms/conversation/mutiselect/MultiselectPart;)Lorg/thoughtcrime/securesms/conversation/mutiselect/MultiselectItemDecoration$Difference;
+HPLorg/thoughtcrime/securesms/conversation/mutiselect/MultiselectItemDecoration;->getItemOffsets(Landroid/graphics/Rect;Landroid/view/View;Landroidx/recyclerview/widget/RecyclerView;Landroidx/recyclerview/widget/RecyclerView$State;)V
+HPLorg/thoughtcrime/securesms/conversation/mutiselect/MultiselectItemDecoration;->resolveMultiselectable(Landroidx/recyclerview/widget/RecyclerView;Landroid/view/View;)Lorg/thoughtcrime/securesms/conversation/mutiselect/Multiselectable;
+HPLorg/thoughtcrime/securesms/conversation/mutiselect/MultiselectItemDecoration;->updateChildOffsets(Landroidx/recyclerview/widget/RecyclerView;Landroid/view/View;)V
+HPLorg/thoughtcrime/securesms/conversation/mutiselect/MultiselectItemDecoration;->updateMultiselectPartAnimator(Ljava/util/Set;Lorg/thoughtcrime/securesms/conversation/mutiselect/MultiselectPart;)V
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2$ConversationViewHolder;->bindPayloadsIfAvailable()Z
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2$ConversationViewHolder;->getBindable()Lorg/thoughtcrime/securesms/BindableConversationItem;
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2$ConversationViewHolder;->getColorizerProjections(Landroid/view/ViewGroup;)Lorg/thoughtcrime/securesms/util/ProjectionList;
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2$ConversationViewHolder;->getNextMessage()Lj$/util/Optional;
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2$ConversationViewHolder;->getPreviousMessage()Lj$/util/Optional;
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2$IncomingMediaViewHolder;->bind(Lorg/thoughtcrime/securesms/conversation/v2/data/IncomingMedia;)V
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2$ThreadHeaderViewHolder;->bind(Lorg/thoughtcrime/securesms/conversation/v2/data/ThreadHeader;)V
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2;->getChatColorsData()Lorg/thoughtcrime/securesms/conversation/v2/items/ChatColorsDrawable$ChatColorsData;
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2;->getConversationMessage(I)Lorg/thoughtcrime/securesms/conversation/ConversationMessage;
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationFragment$ScrollListener;->onScrolled(Landroidx/recyclerview/widget/RecyclerView;II)V
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationFragment$ThreadHeaderMarginDecoration;->getItemOffsets(Landroid/graphics/Rect;Landroid/view/View;Landroidx/recyclerview/widget/RecyclerView;Landroidx/recyclerview/widget/RecyclerView$State;)V
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationFragment$initializeConversationThreadUi$2;->get()Ljava/lang/Object;
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationFragment$initializeConversationThreadUi$6;->invoke()Ljava/lang/Boolean;
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationFragment$initializeConversationThreadUi$7;->invoke()Ljava/lang/Boolean;
HPLorg/thoughtcrime/securesms/conversation/v2/ConversationFragment$initializeConversationThreadUi$8;->invoke()Ljava/lang/Boolean;
HPLorg/thoughtcrime/securesms/conversation/v2/ConversationFragment;->doAfterFirstRender()V
-HPLorg/thoughtcrime/securesms/conversation/v2/ConversationRepository;->getReminder$lambda$10(Lorg/thoughtcrime/securesms/conversation/v2/ConversationRepository;Lorg/thoughtcrime/securesms/database/model/GroupRecord;)Lj$/util/Optional;
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationItemDecorations;->getHeader(Landroidx/recyclerview/widget/RecyclerView;Lorg/thoughtcrime/securesms/conversation/v2/data/ConversationMessageElement;)Lorg/thoughtcrime/securesms/conversation/v2/ConversationItemDecorations$DateHeaderViewHolder;
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationItemDecorations;->getItemOffsets(Landroid/graphics/Rect;Landroid/view/View;Landroidx/recyclerview/widget/RecyclerView;Landroidx/recyclerview/widget/RecyclerView$State;)V
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationItemDecorations;->hasHeader(I)Z
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationItemDecorations;->isFirstUnread(I)Z
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationItemDecorations;->timestamp(Lorg/thoughtcrime/securesms/conversation/v2/data/ConversationMessageElement;)J
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationItemDecorations;->toEpochDay(Lorg/thoughtcrime/securesms/conversation/v2/data/ConversationMessageElement;)J
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationRepository;->getReminder$lambda$12(Lorg/thoughtcrime/securesms/conversation/v2/ConversationRepository;Lorg/thoughtcrime/securesms/database/model/GroupRecord;)Lj$/util/Optional;
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationRepository;->getRequestReviewState$lambda$17(Lorg/thoughtcrime/securesms/database/model/GroupRecord;Lorg/thoughtcrime/securesms/messagerequests/MessageRequestState;Lorg/thoughtcrime/securesms/recipients/Recipient;)Lorg/thoughtcrime/securesms/conversation/v2/RequestReviewState;
HPLorg/thoughtcrime/securesms/conversation/v2/ConversationRepository;->getRequestReviewState(Lorg/thoughtcrime/securesms/recipients/Recipient;Lorg/thoughtcrime/securesms/database/model/GroupRecord;Lorg/thoughtcrime/securesms/messagerequests/MessageRequestState;)Lio/reactivex/rxjava3/core/Single;
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationViewModel$13;->apply(Lj$/util/Optional;)Lio/reactivex/rxjava3/core/MaybeSource;
HPLorg/thoughtcrime/securesms/conversation/v2/ConversationViewModel$canShowAsBubble$1;->apply(Lorg/thoughtcrime/securesms/recipients/Recipient;)Ljava/lang/Boolean;
HPLorg/thoughtcrime/securesms/conversation/v2/ConversationViewModel$getRequestReviewState$1;->apply(Lorg/thoughtcrime/securesms/conversation/v2/InputReadyState;)Lio/reactivex/rxjava3/core/SingleSource;
+HPLorg/thoughtcrime/securesms/conversation/v2/ConversationViewModel;->getChatColorsSnapshot()Lorg/thoughtcrime/securesms/conversation/v2/items/ChatColorsDrawable$ChatColorsData;
HPLorg/thoughtcrime/securesms/conversation/v2/IdentityRecordsState;->equals(Ljava/lang/Object;)Z
HPLorg/thoughtcrime/securesms/conversation/v2/InputReadyState;->equals(Ljava/lang/Object;)Z
+HPLorg/thoughtcrime/securesms/conversation/v2/items/ChatColorsDrawable$ChatColorsItemDecoration$onDraw$$inlined$filterIsInstance$1;->invoke(Ljava/lang/Object;)Ljava/lang/Boolean;
+HPLorg/thoughtcrime/securesms/conversation/v2/items/ChatColorsDrawable$ChatColorsItemDecoration$onDraw$1;->invoke(Landroid/view/View;)Landroidx/recyclerview/widget/RecyclerView$ViewHolder;
+HPLorg/thoughtcrime/securesms/conversation/v2/items/ChatColorsDrawable$ChatColorsItemDecoration$onDraw$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HPLorg/thoughtcrime/securesms/conversation/v2/items/ChatColorsDrawable;->draw(Landroid/graphics/Canvas;)V
+HPLorg/thoughtcrime/securesms/conversation/v2/items/ChatColorsDrawable;->getChatColors()Lorg/thoughtcrime/securesms/conversation/colors/ChatColors;
+HPLorg/thoughtcrime/securesms/conversation/v2/items/ChatColorsDrawable;->getOutline(Landroid/graphics/Outline;)V
+HPLorg/thoughtcrime/securesms/conversation/v2/items/ChatColorsDrawable;->isSolidColor()Z
+HPLorg/thoughtcrime/securesms/conversation/v2/items/ChatColorsDrawable;->setCorners([F)V
+HPLorg/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemLayout;->onMeasure(II)V
+HPLorg/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShape;->isEndOfMessageCluster(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Lorg/thoughtcrime/securesms/database/model/MessageRecord;)Z
+HPLorg/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShape;->setMessageShape(Lorg/thoughtcrime/securesms/database/model/MessageRecord;ZI)Lorg/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShape$MessageShape;
+HPLorg/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder$footerDrawable$1;->invoke()Lorg/thoughtcrime/securesms/conversation/v2/items/ChatColorsDrawable$ChatColorsData;
+HPLorg/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder;->(Lorg/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyBindingBridge;Lorg/thoughtcrime/securesms/conversation/v2/items/V2ConversationContext;Lorg/thoughtcrime/securesms/conversation/v2/items/V2FooterPositionDelegate;)V
+HPLorg/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder;->bind(Lorg/thoughtcrime/securesms/util/adapter/mapping/MappingModel;)V
+HPLorg/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder;->getColorizerProjections(Landroid/view/ViewGroup;)Lorg/thoughtcrime/securesms/util/ProjectionList;
+HPLorg/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder;->invalidateBodyBubbleDrawable(Landroid/view/ViewGroup;)V
+HPLorg/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder;->invalidateChatColorsDrawable(Landroid/view/ViewGroup;)V
+HPLorg/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder;->linkifyMessageBody(Landroid/text/Spannable;)V
+HPLorg/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder;->presentBody()V
+HPLorg/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder;->presentSender()V
+HPLorg/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTheme;->getBodyTextColor(Lorg/thoughtcrime/securesms/conversation/ConversationMessage;)I
+HPLorg/thoughtcrime/securesms/conversation/v2/items/V2FooterPositionDelegate;->onPostMeasure()Z
+HPLorg/thoughtcrime/securesms/conversationlist/model/Conversation;->equals(Ljava/lang/Object;)Z
+HPLorg/thoughtcrime/securesms/database/DatabaseObserver;->lambda$unregisterObserver$18(Lorg/thoughtcrime/securesms/database/DatabaseObserver$Observer;)V
+HPLorg/thoughtcrime/securesms/database/model/DisplayRecord;->isFailed()Z
HPLorg/thoughtcrime/securesms/database/model/IdentityRecord;->equals(Ljava/lang/Object;)Z
+HPLorg/thoughtcrime/securesms/database/model/MessageRecord;->isBundleKeyExchange()Z
+HPLorg/thoughtcrime/securesms/database/model/MessageRecord;->isRateLimited()Z
+HPLorg/thoughtcrime/securesms/database/model/MessageRecord;->isSecure()Z
+HPLorg/thoughtcrime/securesms/database/model/MmsMessageRecord;->isMediaPending()Z
+HPLorg/thoughtcrime/securesms/database/model/ThreadRecord;->equals(Ljava/lang/Object;)Z
+HPLorg/thoughtcrime/securesms/fonts/SignalSymbols$CustomTypefaceSpan;->update(Landroid/text/TextPaint;)V
+HPLorg/thoughtcrime/securesms/fonts/SignalSymbols;->getSpannedString(Landroid/content/Context;Lorg/thoughtcrime/securesms/fonts/SignalSymbols$Weight;Lorg/thoughtcrime/securesms/fonts/SignalSymbols$Glyph;)Ljava/lang/CharSequence;
+HPLorg/thoughtcrime/securesms/giph/mp4/GiphyMp4ItemDecoration$onDraw$1;->invoke(Landroid/view/View;)Landroidx/recyclerview/widget/RecyclerView$ViewHolder;
+HPLorg/thoughtcrime/securesms/giph/mp4/GiphyMp4ItemDecoration$onDraw$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HPLorg/thoughtcrime/securesms/giph/mp4/GiphyMp4PlaybackController$RangeComparator;->compare(Ljava/lang/Integer;Ljava/lang/Integer;)I
+HPLorg/thoughtcrime/securesms/giph/mp4/GiphyMp4ProjectionRecycler;->getCurrentHolder(I)Lorg/thoughtcrime/securesms/giph/mp4/GiphyMp4ProjectionPlayerHolder;
+HPLorg/thoughtcrime/securesms/giph/mp4/GiphyMp4ProjectionRecycler;->updateVideoDisplayPositionAndSize(Landroidx/recyclerview/widget/RecyclerView;Lorg/thoughtcrime/securesms/giph/mp4/GiphyMp4Playable;)V
+HPLorg/thoughtcrime/securesms/keyvalue/SettingsValues;->getMessageFontSize()I
+HPLorg/thoughtcrime/securesms/keyvalue/WallpaperValues;->hasWallpaperSet()Z
HPLorg/thoughtcrime/securesms/mms/Slide;->equals(Ljava/lang/Object;)Z
+HPLorg/thoughtcrime/securesms/mms/Slide;->getTransferState()I
+HPLorg/thoughtcrime/securesms/mms/Slide;->hashCode()I
+HPLorg/thoughtcrime/securesms/mms/Slide;->isInProgress()Z
+HPLorg/thoughtcrime/securesms/mms/Slide;->isPendingDownload()Z
+HPLorg/thoughtcrime/securesms/mms/SlideDeck;->getThumbnailSlides()Ljava/util/List;
HPLorg/thoughtcrime/securesms/profiles/ProfileName;->equals(Ljava/lang/Object;)Z
+HPLorg/thoughtcrime/securesms/reactions/ReactionsConversationView;->clear()V
+HPLorg/thoughtcrime/securesms/recipients/LiveRecipient$$ExternalSyntheticLambda5;->(Lorg/thoughtcrime/securesms/recipients/LiveRecipient;Lorg/thoughtcrime/securesms/recipients/RecipientForeverObserver;)V
+HPLorg/thoughtcrime/securesms/recipients/LiveRecipient;->removeForeverObserver(Lorg/thoughtcrime/securesms/recipients/RecipientForeverObserver;)V
+HPLorg/thoughtcrime/securesms/recipients/Recipient$combinedAboutAndEmoji$2;->invoke()Ljava/lang/String;
+HPLorg/thoughtcrime/securesms/recipients/Recipient;->getHasWallpaper()Z
HPLorg/thoughtcrime/securesms/recipients/Recipient;->hasSameContent(Lorg/thoughtcrime/securesms/recipients/Recipient;)Z
HPLorg/thoughtcrime/securesms/util/BubbleUtil;->canBubble(Landroid/content/Context;Lorg/thoughtcrime/securesms/recipients/Recipient;Ljava/lang/Long;)Z
+HPLorg/thoughtcrime/securesms/util/DateUtils;->getSameDayDateFormat()Ljava/text/SimpleDateFormat;
+HPLorg/thoughtcrime/securesms/util/DateUtils;->isSameDay(JJ)Z
+HPLorg/thoughtcrime/securesms/util/JavaTimeExtensionsKt;->toLocalDate$default(JLj$/time/ZoneId;ILjava/lang/Object;)Lj$/time/LocalDate;
+HPLorg/thoughtcrime/securesms/util/JavaTimeExtensionsKt;->toLocalDate(JLj$/time/ZoneId;)Lj$/time/LocalDate;
+HPLorg/thoughtcrime/securesms/util/Material3OnScrollHelper;->updateActiveState$lambda$7$lambda$6(Lorg/thoughtcrime/securesms/util/Material3OnScrollHelper;IIIILandroid/animation/ValueAnimator;)V
+HPLorg/thoughtcrime/securesms/util/MediaUtil;->isInstantVideoSupported(Lorg/thoughtcrime/securesms/mms/Slide;)Z
+HPLorg/thoughtcrime/securesms/util/MediaUtil;->isVideoType(Ljava/lang/String;)Z
+HPLorg/thoughtcrime/securesms/util/MessageRecordUtil;->hasNoBubble(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Landroid/content/Context;)Z
+HPLorg/thoughtcrime/securesms/util/MessageRecordUtil;->hasOnlyThumbnail(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Landroid/content/Context;)Z
+HPLorg/thoughtcrime/securesms/util/MessageRecordUtil;->isBorderless(Lorg/thoughtcrime/securesms/database/model/MessageRecord;Landroid/content/Context;)Z
+HPLorg/thoughtcrime/securesms/util/MessageRecordUtil;->isEditMessage(Lorg/thoughtcrime/securesms/database/model/MessageRecord;)Z
+HPLorg/thoughtcrime/securesms/util/Projection$Corners;->(FFFF)V
+HPLorg/thoughtcrime/securesms/util/Projection$Corners;->toRadii()[F
+HPLorg/thoughtcrime/securesms/util/ProjectionList;->close()V
+HPLorg/thoughtcrime/securesms/util/TextSecurePreferences;->getMessageBodyTextSize(Landroid/content/Context;)I
+HPLorg/thoughtcrime/securesms/util/ViewExtensionsKt;->drawAsTopItemDecoration(Landroid/view/View;Landroid/graphics/Canvas;Landroid/view/View;Landroid/view/View;I)V
+HPLorg/thoughtcrime/securesms/util/ViewExtensionsKt;->layoutIn(Landroid/view/View;Landroid/view/View;)V
+HPLorg/thoughtcrime/securesms/util/ViewUtil;->isRtl(Landroid/view/View;)Z
+HPLorg/thoughtcrime/securesms/util/ViewUtil;->setPaddingBottom(Landroid/view/View;I)V
+HPLorg/thoughtcrime/securesms/util/ViewUtil;->setPaddingEnd(Landroid/view/View;I)V
+HPLorg/thoughtcrime/securesms/util/ViewUtil;->setPaddingStart(Landroid/view/View;I)V
+HPLorg/thoughtcrime/securesms/util/ViewUtil;->setPaddingTop(Landroid/view/View;I)V
+HPLorg/thoughtcrime/securesms/util/ViewUtil;->setTopMargin(Landroid/view/View;IZ)V
+HPLorg/thoughtcrime/securesms/util/ViewUtil;->updateLayoutParams(Landroid/view/View;II)V
+HPLorg/thoughtcrime/securesms/util/adapter/mapping/MappingViewHolder;->setPayload(Ljava/util/List;)V
+HPLorg/thoughtcrime/securesms/util/adapter/mapping/PagingMappingAdapter;->getItem(I)Lorg/thoughtcrime/securesms/util/adapter/mapping/MappingModel;
+HPLorg/thoughtcrime/securesms/util/adapter/mapping/PagingMappingAdapter;->getItemViewType(I)I
+HPLorg/thoughtcrime/securesms/util/views/NullableStub;->get()Ljava/lang/Object;
+HPLorg/thoughtcrime/securesms/util/views/NullableStub;->require()Ljava/lang/Object;
HSPLandroid/support/v4/media/MediaBrowserCompat$MediaBrowserImplApi21$$ExternalSyntheticThrowCCEIfNotNull0;->m(Ljava/lang/Object;)V
HSPLandroid/support/v4/media/session/IMediaSession$Stub;->()V
HSPLandroid/support/v4/media/session/MediaControllerCompat$MediaControllerImplApi21;->(Landroid/content/Context;Landroid/support/v4/media/session/MediaSessionCompat$Token;)V
@@ -63,7 +338,6 @@ HSPLandroid/support/v4/media/session/MediaSessionCompat$MediaSessionImplApi22;->
HSPLandroid/support/v4/media/session/MediaSessionCompat$MediaSessionImplApi22;->setRatingType(I)V
HSPLandroid/support/v4/media/session/MediaSessionCompat$MediaSessionImplApi28;->(Landroid/content/Context;Ljava/lang/String;Landroidx/versionedparcelable/VersionedParcelable;Landroid/os/Bundle;)V
HSPLandroid/support/v4/media/session/MediaSessionCompat$MediaSessionImplApi29$$ExternalSyntheticApiModelOutline0;->m(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/media/session/MediaSession;
-HSPLandroid/support/v4/media/session/MediaSessionCompat$MediaSessionImplApi29$$ExternalSyntheticApiModelOutline1;->m()V
HSPLandroid/support/v4/media/session/MediaSessionCompat$MediaSessionImplApi29;->(Landroid/content/Context;Ljava/lang/String;Landroidx/versionedparcelable/VersionedParcelable;Landroid/os/Bundle;)V
HSPLandroid/support/v4/media/session/MediaSessionCompat$MediaSessionImplApi29;->createFwkMediaSession(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/media/session/MediaSession;
HSPLandroid/support/v4/media/session/MediaSessionCompat$Token$1;->()V
@@ -93,7 +367,7 @@ HSPLandroid/support/v4/media/session/PlaybackStateCompat$Api21Impl;->setActiveQu
HSPLandroid/support/v4/media/session/PlaybackStateCompat$Api21Impl;->setBufferedPosition(Landroid/media/session/PlaybackState$Builder;J)V
HSPLandroid/support/v4/media/session/PlaybackStateCompat$Api21Impl;->setErrorMessage(Landroid/media/session/PlaybackState$Builder;Ljava/lang/CharSequence;)V
HSPLandroid/support/v4/media/session/PlaybackStateCompat$Api21Impl;->setState(Landroid/media/session/PlaybackState$Builder;IJFJ)V
-HSPLandroid/support/v4/media/session/PlaybackStateCompat$Api22Impl$$ExternalSyntheticApiModelOutline1;->m(Landroid/media/session/PlaybackState$Builder;Landroid/os/Bundle;)Landroid/media/session/PlaybackState$Builder;
+HSPLandroid/support/v4/media/session/PlaybackStateCompat$Api22Impl$$ExternalSyntheticApiModelOutline0;->m(Landroid/media/session/PlaybackState$Builder;Landroid/os/Bundle;)Landroid/media/session/PlaybackState$Builder;
HSPLandroid/support/v4/media/session/PlaybackStateCompat$Api22Impl;->setExtras(Landroid/media/session/PlaybackState$Builder;Landroid/os/Bundle;)V
HSPLandroid/support/v4/media/session/PlaybackStateCompat$Builder;->()V
HSPLandroid/support/v4/media/session/PlaybackStateCompat$Builder;->build()Landroid/support/v4/media/session/PlaybackStateCompat;
@@ -124,7 +398,7 @@ HSPLandroidx/activity/ComponentActivity$6;->onStateChanged(Landroidx/lifecycle/L
HSPLandroidx/activity/ComponentActivity$Api19Impl;->cancelPendingInputEvents(Landroid/view/View;)V
HSPLandroidx/activity/ComponentActivity$ReportFullyDrawnExecutorApi16Impl;->(Landroidx/activity/ComponentActivity;)V
HSPLandroidx/activity/ComponentActivity$ReportFullyDrawnExecutorApi16Impl;->activityDestroyed()V
-HSPLandroidx/activity/ComponentActivity;->$r8$lambda$h2i_RK2mddCIbAsGubaI4eL8_cU(Landroidx/activity/ComponentActivity;Landroid/content/Context;)V
+HSPLandroidx/activity/ComponentActivity;->$r8$lambda$278Cq622rQ9X9WaDkyOm2xm3rFw(Landroidx/activity/ComponentActivity;Landroid/content/Context;)V
HSPLandroidx/activity/ComponentActivity;->()V
HSPLandroidx/activity/ComponentActivity;->addMenuProvider(Landroidx/core/view/MenuProvider;)V
HSPLandroidx/activity/ComponentActivity;->addOnConfigurationChangedListener(Landroidx/core/util/Consumer;)V
@@ -262,8 +536,8 @@ HSPLandroidx/appcompat/app/AppCompatDelegateImpl$5;->(Landroidx/appcompat/
HSPLandroidx/appcompat/app/AppCompatDelegateImpl$5;->onAttachedFromWindow()V
HSPLandroidx/appcompat/app/AppCompatDelegateImpl$5;->onDetachedFromWindow()V
HSPLandroidx/appcompat/app/AppCompatDelegateImpl$Api17Impl;->createConfigurationContext(Landroid/content/Context;Landroid/content/res/Configuration;)Landroid/content/Context;
-HSPLandroidx/appcompat/app/AppCompatDelegateImpl$Api24Impl$$ExternalSyntheticApiModelOutline1;->m(Landroid/content/res/Configuration;)Landroid/os/LocaleList;
-HSPLandroidx/appcompat/app/AppCompatDelegateImpl$Api24Impl$$ExternalSyntheticApiModelOutline2;->m(Landroid/os/LocaleList;)Ljava/lang/String;
+HSPLandroidx/appcompat/app/AppCompatDelegateImpl$Api24Impl$$ExternalSyntheticApiModelOutline0;->m(Landroid/content/res/Configuration;)Landroid/os/LocaleList;
+HSPLandroidx/appcompat/app/AppCompatDelegateImpl$Api24Impl$$ExternalSyntheticApiModelOutline5;->m(Landroid/os/LocaleList;)Ljava/lang/String;
HSPLandroidx/appcompat/app/AppCompatDelegateImpl$Api24Impl;->getLocales(Landroid/content/res/Configuration;)Landroidx/core/os/LocaleListCompat;
HSPLandroidx/appcompat/app/AppCompatDelegateImpl$AppCompatWindowCallback;->(Landroidx/appcompat/app/AppCompatDelegateImpl;Landroid/view/Window$Callback;)V
HSPLandroidx/appcompat/app/AppCompatDelegateImpl$AppCompatWindowCallback;->bypassOnContentChanged(Landroid/view/Window$Callback;)V
@@ -563,7 +837,6 @@ HSPLandroidx/appcompat/widget/AppCompatEditText;->drawableStateChanged()V
HSPLandroidx/appcompat/widget/AppCompatEditText;->getText()Landroid/text/Editable;
HSPLandroidx/appcompat/widget/AppCompatEditText;->getText()Ljava/lang/CharSequence;
HSPLandroidx/appcompat/widget/AppCompatEditText;->initEmojiKeyListener(Landroidx/appcompat/widget/AppCompatEmojiEditTextHelper;)V
-HSPLandroidx/appcompat/widget/AppCompatEditText;->onCreateInputConnection(Landroid/view/inputmethod/EditorInfo;)Landroid/view/inputmethod/InputConnection;
HSPLandroidx/appcompat/widget/AppCompatEditText;->setBackgroundDrawable(Landroid/graphics/drawable/Drawable;)V
HSPLandroidx/appcompat/widget/AppCompatEditText;->setCompoundDrawables(Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;)V
HSPLandroidx/appcompat/widget/AppCompatEditText;->setCustomSelectionActionModeCallback(Landroid/view/ActionMode$Callback;)V
@@ -572,13 +845,11 @@ HSPLandroidx/appcompat/widget/AppCompatEmojiEditTextHelper;->(Landroid/wid
HSPLandroidx/appcompat/widget/AppCompatEmojiEditTextHelper;->getKeyListener(Landroid/text/method/KeyListener;)Landroid/text/method/KeyListener;
HSPLandroidx/appcompat/widget/AppCompatEmojiEditTextHelper;->isEmojiCapableKeyListener(Landroid/text/method/KeyListener;)Z
HSPLandroidx/appcompat/widget/AppCompatEmojiEditTextHelper;->loadFromAttributes(Landroid/util/AttributeSet;I)V
-HSPLandroidx/appcompat/widget/AppCompatEmojiEditTextHelper;->onCreateInputConnection(Landroid/view/inputmethod/InputConnection;Landroid/view/inputmethod/EditorInfo;)Landroid/view/inputmethod/InputConnection;
HSPLandroidx/appcompat/widget/AppCompatEmojiEditTextHelper;->setEnabled(Z)V
HSPLandroidx/appcompat/widget/AppCompatEmojiTextHelper;->(Landroid/widget/TextView;)V
HSPLandroidx/appcompat/widget/AppCompatEmojiTextHelper;->getFilters([Landroid/text/InputFilter;)[Landroid/text/InputFilter;
HSPLandroidx/appcompat/widget/AppCompatEmojiTextHelper;->loadFromAttributes(Landroid/util/AttributeSet;I)V
HSPLandroidx/appcompat/widget/AppCompatEmojiTextHelper;->setEnabled(Z)V
-HSPLandroidx/appcompat/widget/AppCompatHintHelper;->onCreateInputConnection(Landroid/view/inputmethod/InputConnection;Landroid/view/inputmethod/EditorInfo;Landroid/view/View;)Landroid/view/inputmethod/InputConnection;
HSPLandroidx/appcompat/widget/AppCompatImageButton;->(Landroid/content/Context;Landroid/util/AttributeSet;)V
HSPLandroidx/appcompat/widget/AppCompatImageButton;->(Landroid/content/Context;Landroid/util/AttributeSet;I)V
HSPLandroidx/appcompat/widget/AppCompatImageButton;->drawableStateChanged()V
@@ -615,7 +886,6 @@ HSPLandroidx/appcompat/widget/AppCompatSeekBarHelper;->drawableStateChanged()V
HSPLandroidx/appcompat/widget/AppCompatSeekBarHelper;->jumpDrawablesToCurrentState()V
HSPLandroidx/appcompat/widget/AppCompatSeekBarHelper;->loadFromAttributes(Landroid/util/AttributeSet;I)V
HSPLandroidx/appcompat/widget/AppCompatSeekBarHelper;->setTickMark(Landroid/graphics/drawable/Drawable;)V
-HSPLandroidx/appcompat/widget/AppCompatTextClassifierHelper$Api26Impl$$ExternalSyntheticApiModelOutline0;->m(Landroid/content/Context;Ljava/lang/Class;)Ljava/lang/Object;
HSPLandroidx/appcompat/widget/AppCompatTextClassifierHelper;->(Landroid/widget/TextView;)V
HSPLandroidx/appcompat/widget/AppCompatTextHelper$1;->(Landroidx/appcompat/widget/AppCompatTextHelper;IILjava/lang/ref/WeakReference;)V
HSPLandroidx/appcompat/widget/AppCompatTextHelper$1;->onFontRetrievalFailed(I)V
@@ -626,7 +896,6 @@ HSPLandroidx/appcompat/widget/AppCompatTextHelper;->applyCompoundDrawablesTints(
HSPLandroidx/appcompat/widget/AppCompatTextHelper;->onLayout(ZIIII)V
HSPLandroidx/appcompat/widget/AppCompatTextHelper;->onSetCompoundDrawables()V
HSPLandroidx/appcompat/widget/AppCompatTextHelper;->onSetTextAppearance(Landroid/content/Context;I)V
-HSPLandroidx/appcompat/widget/AppCompatTextHelper;->populateSurroundingTextIfNeeded(Landroid/widget/TextView;Landroid/view/inputmethod/InputConnection;Landroid/view/inputmethod/EditorInfo;)V
HSPLandroidx/appcompat/widget/AppCompatTextHelper;->setCompoundDrawables(Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;)V
HSPLandroidx/appcompat/widget/AppCompatTextHelper;->updateTypefaceAndStyle(Landroid/content/Context;Landroidx/appcompat/widget/TintTypedArray;)V
HSPLandroidx/appcompat/widget/AppCompatTextView;->(Landroid/content/Context;)V
@@ -645,7 +914,6 @@ HSPLandroidx/appcompat/widget/AppCompatTextView;->setCompoundDrawables(Landroid/
HSPLandroidx/appcompat/widget/AppCompatTextView;->setCompoundDrawablesRelative(Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;)V
HSPLandroidx/appcompat/widget/AppCompatTextView;->setCompoundDrawablesRelativeWithIntrinsicBounds(IIII)V
HSPLandroidx/appcompat/widget/AppCompatTextView;->setCompoundDrawablesRelativeWithIntrinsicBounds(Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;)V
-HSPLandroidx/appcompat/widget/AppCompatTextView;->setCompoundDrawablesWithIntrinsicBounds(IIII)V
HSPLandroidx/appcompat/widget/AppCompatTextView;->setCompoundDrawablesWithIntrinsicBounds(Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;)V
HSPLandroidx/appcompat/widget/AppCompatTextView;->setEmojiCompatEnabled(Z)V
HSPLandroidx/appcompat/widget/AppCompatTextView;->setFilters([Landroid/text/InputFilter;)V
@@ -689,7 +957,6 @@ HSPLandroidx/appcompat/widget/LinearLayoutCompat;->checkLayoutParams(Landroid/vi
HSPLandroidx/appcompat/widget/LinearLayoutCompat;->drawDividersHorizontal(Landroid/graphics/Canvas;)V
HSPLandroidx/appcompat/widget/LinearLayoutCompat;->generateLayoutParams(Landroid/util/AttributeSet;)Landroid/view/ViewGroup$LayoutParams;
HSPLandroidx/appcompat/widget/LinearLayoutCompat;->generateLayoutParams(Landroid/util/AttributeSet;)Landroidx/appcompat/widget/LinearLayoutCompat$LayoutParams;
-HSPLandroidx/appcompat/widget/LinearLayoutCompat;->getBaseline()I
HSPLandroidx/appcompat/widget/LinearLayoutCompat;->getChildrenSkipCount(Landroid/view/View;I)I
HSPLandroidx/appcompat/widget/LinearLayoutCompat;->getLocationOffset(Landroid/view/View;)I
HSPLandroidx/appcompat/widget/LinearLayoutCompat;->getNextLocationOffset(Landroid/view/View;)I
@@ -766,8 +1033,8 @@ HSPLandroidx/appcompat/widget/TintTypedArray;->obtainStyledAttributes(Landroid/c
HSPLandroidx/appcompat/widget/TintTypedArray;->obtainStyledAttributes(Landroid/content/Context;Landroid/util/AttributeSet;[I)Landroidx/appcompat/widget/TintTypedArray;
HSPLandroidx/appcompat/widget/TintTypedArray;->obtainStyledAttributes(Landroid/content/Context;Landroid/util/AttributeSet;[III)Landroidx/appcompat/widget/TintTypedArray;
HSPLandroidx/appcompat/widget/TintTypedArray;->recycle()V
-HSPLandroidx/appcompat/widget/Toolbar$$ExternalSyntheticLambda0;->(Landroidx/appcompat/widget/Toolbar;)V
-HSPLandroidx/appcompat/widget/Toolbar$$ExternalSyntheticLambda0;->run()V
+HSPLandroidx/appcompat/widget/Toolbar$$ExternalSyntheticLambda1;->(Landroidx/appcompat/widget/Toolbar;)V
+HSPLandroidx/appcompat/widget/Toolbar$$ExternalSyntheticLambda1;->run()V
HSPLandroidx/appcompat/widget/Toolbar$1;->(Landroidx/appcompat/widget/Toolbar;)V
HSPLandroidx/appcompat/widget/Toolbar$2;->(Landroidx/appcompat/widget/Toolbar;)V
HSPLandroidx/appcompat/widget/Toolbar$3;->(Landroidx/appcompat/widget/Toolbar;)V
@@ -852,7 +1119,6 @@ HSPLandroidx/appcompat/widget/ToolbarWidgetWrapper;->setTitleInt(Ljava/lang/Char
HSPLandroidx/appcompat/widget/ToolbarWidgetWrapper;->setWindowCallback(Landroid/view/Window$Callback;)V
HSPLandroidx/appcompat/widget/ToolbarWidgetWrapper;->setWindowTitle(Ljava/lang/CharSequence;)V
HSPLandroidx/appcompat/widget/ToolbarWidgetWrapper;->updateHomeAccessibility()V
-HSPLandroidx/appcompat/widget/TooltipCompat$Api26Impl$$ExternalSyntheticApiModelOutline0;->m(Landroid/view/View;Ljava/lang/CharSequence;)V
HSPLandroidx/appcompat/widget/TooltipCompat$Api26Impl;->setTooltipText(Landroid/view/View;Ljava/lang/CharSequence;)V
HSPLandroidx/appcompat/widget/TooltipCompat;->setTooltipText(Landroid/view/View;Ljava/lang/CharSequence;)V
HSPLandroidx/appcompat/widget/VectorEnabledTintResources;->()V
@@ -872,7 +1138,6 @@ HSPLandroidx/arch/core/executor/ArchTaskExecutor;->getInstance()Landroidx/arch/c
HSPLandroidx/arch/core/executor/ArchTaskExecutor;->isMainThread()Z
HSPLandroidx/arch/core/executor/ArchTaskExecutor;->postToMainThread(Ljava/lang/Runnable;)V
HSPLandroidx/arch/core/executor/DefaultTaskExecutor$1;->(Landroidx/arch/core/executor/DefaultTaskExecutor;)V
-HSPLandroidx/arch/core/executor/DefaultTaskExecutor$Api28Impl$$ExternalSyntheticApiModelOutline0;->m(Landroid/os/Looper;)Landroid/os/Handler;
HSPLandroidx/arch/core/executor/DefaultTaskExecutor$Api28Impl;->createAsync(Landroid/os/Looper;)Landroid/os/Handler;
HSPLandroidx/arch/core/executor/DefaultTaskExecutor;->()V
HSPLandroidx/arch/core/executor/DefaultTaskExecutor;->createAsync(Landroid/os/Looper;)Landroid/os/Handler;
@@ -938,8 +1203,42 @@ HSPLandroidx/asynclayoutinflater/view/AsyncLayoutInflater;->(Landroid/cont
HSPLandroidx/asynclayoutinflater/view/AsyncLayoutInflater;->inflate(ILandroid/view/ViewGroup;Landroidx/asynclayoutinflater/view/AsyncLayoutInflater$OnInflateFinishedListener;)V
HSPLandroidx/asynclayoutinflater/view/AsyncLayoutInflater;->inflateInternal(ILandroid/view/ViewGroup;Landroidx/asynclayoutinflater/view/AsyncLayoutInflater$OnInflateFinishedListener;Landroid/view/LayoutInflater;Ljava/util/concurrent/Executor;)V
HSPLandroidx/asynclayoutinflater/view/AsyncLayoutInflater;->triggerCallbacks(Landroidx/asynclayoutinflater/view/AsyncLayoutInflater$InflateRequest;Landroidx/asynclayoutinflater/view/AsyncLayoutInflater$InflateThread;)V
-HSPLandroidx/camera/camera2/internal/compat/params/OutputConfigurationCompatApi24Impl$OutputConfigurationParamsApi24$$ExternalSyntheticBackport1;->m(J)I
+HSPLandroidx/camera/camera2/internal/compat/params/OutputConfigurationCompatApi24Impl$OutputConfigurationParamsApi24$$ExternalSyntheticBackport0;->m(J)I
HSPLandroidx/camera/view/PreviewView$1$$ExternalSyntheticBackportWithForwarding0;->m(Ljava/util/concurrent/atomic/AtomicReference;Ljava/lang/Object;Ljava/lang/Object;)Z
+HSPLandroidx/cardview/R$styleable;->()V
+HSPLandroidx/cardview/widget/CardView$1;->(Landroidx/cardview/widget/CardView;)V
+HSPLandroidx/cardview/widget/CardView$1;->getCardBackground()Landroid/graphics/drawable/Drawable;
+HSPLandroidx/cardview/widget/CardView$1;->getCardView()Landroid/view/View;
+HSPLandroidx/cardview/widget/CardView$1;->getPreventCornerOverlap()Z
+HSPLandroidx/cardview/widget/CardView$1;->getUseCompatPadding()Z
+HSPLandroidx/cardview/widget/CardView$1;->setCardBackground(Landroid/graphics/drawable/Drawable;)V
+HSPLandroidx/cardview/widget/CardView$1;->setShadowPadding(IIII)V
+HSPLandroidx/cardview/widget/CardView;->()V
+HSPLandroidx/cardview/widget/CardView;->(Landroid/content/Context;Landroid/util/AttributeSet;I)V
+HSPLandroidx/cardview/widget/CardView;->access$001(Landroidx/cardview/widget/CardView;IIII)V
+HSPLandroidx/cardview/widget/CardView;->getCardBackgroundColor()Landroid/content/res/ColorStateList;
+HSPLandroidx/cardview/widget/CardView;->getCardElevation()F
+HSPLandroidx/cardview/widget/CardView;->getContentPaddingBottom()I
+HSPLandroidx/cardview/widget/CardView;->getContentPaddingLeft()I
+HSPLandroidx/cardview/widget/CardView;->getContentPaddingRight()I
+HSPLandroidx/cardview/widget/CardView;->getContentPaddingTop()I
+HSPLandroidx/cardview/widget/CardView;->getPreventCornerOverlap()Z
+HSPLandroidx/cardview/widget/CardView;->getUseCompatPadding()Z
+HSPLandroidx/cardview/widget/CardView;->onMeasure(II)V
+HSPLandroidx/cardview/widget/CardView;->setContentPadding(IIII)V
+HSPLandroidx/cardview/widget/CardViewApi21Impl;->()V
+HSPLandroidx/cardview/widget/CardViewApi21Impl;->getBackgroundColor(Landroidx/cardview/widget/CardViewDelegate;)Landroid/content/res/ColorStateList;
+HSPLandroidx/cardview/widget/CardViewApi21Impl;->getCardBackground(Landroidx/cardview/widget/CardViewDelegate;)Landroidx/cardview/widget/RoundRectDrawable;
+HSPLandroidx/cardview/widget/CardViewApi21Impl;->getElevation(Landroidx/cardview/widget/CardViewDelegate;)F
+HSPLandroidx/cardview/widget/CardViewApi21Impl;->initStatic()V
+HSPLandroidx/cardview/widget/CardViewApi21Impl;->initialize(Landroidx/cardview/widget/CardViewDelegate;Landroid/content/Context;Landroid/content/res/ColorStateList;FFF)V
+HSPLandroidx/cardview/widget/CardViewApi21Impl;->setMaxElevation(Landroidx/cardview/widget/CardViewDelegate;F)V
+HSPLandroidx/cardview/widget/CardViewApi21Impl;->updatePadding(Landroidx/cardview/widget/CardViewDelegate;)V
+HSPLandroidx/cardview/widget/RoundRectDrawable;->(Landroid/content/res/ColorStateList;F)V
+HSPLandroidx/cardview/widget/RoundRectDrawable;->getColor()Landroid/content/res/ColorStateList;
+HSPLandroidx/cardview/widget/RoundRectDrawable;->setBackground(Landroid/content/res/ColorStateList;)V
+HSPLandroidx/cardview/widget/RoundRectDrawable;->setPadding(FZZ)V
+HSPLandroidx/cardview/widget/RoundRectDrawable;->updateBounds(Landroid/graphics/Rect;)V
HSPLandroidx/collection/ArrayMap$EntrySet;->(Landroidx/collection/ArrayMap;)V
HSPLandroidx/collection/ArrayMap$EntrySet;->iterator()Ljava/util/Iterator;
HSPLandroidx/collection/ArrayMap$MapIterator;->(Landroidx/collection/ArrayMap;)V
@@ -1035,16 +1334,12 @@ HSPLandroidx/collection/SparseArrayKt$valueIterator$1;->(Landroidx/collect
HSPLandroidx/collection/SparseArrayKt$valueIterator$1;->hasNext()Z
HSPLandroidx/collection/SparseArrayKt$valueIterator$1;->next()Ljava/lang/Object;
HSPLandroidx/collection/SparseArrayKt;->valueIterator(Landroidx/collection/SparseArrayCompat;)Ljava/util/Iterator;
-HSPLandroidx/compose/ui/autofill/AndroidAutofill$$ExternalSyntheticApiModelOutline1;->m(Landroid/view/View;I)V
HSPLandroidx/compose/ui/graphics/AndroidImageBitmap_androidKt$$ExternalSyntheticApiModelOutline0;->m()Landroid/graphics/Bitmap$Config;
HSPLandroidx/compose/ui/graphics/AndroidImageBitmap_androidKt$$ExternalSyntheticApiModelOutline1;->m()Landroid/graphics/Bitmap$Config;
HSPLandroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticApiModelOutline0;->m(Landroid/content/res/Configuration;)I
-HSPLandroidx/compose/ui/platform/coreshims/SoftwareKeyboardControllerCompat$Impl30$$ExternalSyntheticApiModelOutline1;->m()I
-HSPLandroidx/compose/ui/text/intl/AndroidLocaleDelegateAPI24$$ExternalSyntheticApiModelOutline0;->m()Landroid/os/LocaleList;
+HSPLandroidx/compose/ui/platform/coreshims/SoftwareKeyboardControllerCompat$Impl30$$ExternalSyntheticApiModelOutline3;->m()I
HSPLandroidx/compose/ui/text/intl/AndroidLocaleDelegateAPI24$$ExternalSyntheticApiModelOutline1;->m(Landroid/os/LocaleList;)I
HSPLandroidx/compose/ui/text/intl/AndroidLocaleDelegateAPI24$$ExternalSyntheticApiModelOutline2;->m(Landroid/os/LocaleList;I)Ljava/util/Locale;
-HSPLandroidx/compose/ui/text/platform/extensions/LocaleListHelperMethods$$ExternalSyntheticApiModelOutline0;->m([Ljava/util/Locale;)Landroid/os/LocaleList;
-HSPLandroidx/compose/ui/text/platform/extensions/LocaleListHelperMethods$$ExternalSyntheticApiModelOutline2;->m()V
HSPLandroidx/concurrent/futures/AbstractResolvableFuture$SafeAtomicHelper$$ExternalSyntheticBackportWithForwarding0;->m(Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z
HSPLandroidx/constraintlayout/core/ArrayLinkedVariables;->()V
HSPLandroidx/constraintlayout/core/ArrayLinkedVariables;->(Landroidx/constraintlayout/core/ArrayRow;Landroidx/constraintlayout/core/Cache;)V
@@ -1056,6 +1351,8 @@ HSPLandroidx/constraintlayout/core/ArrayLinkedVariables;->getCurrentSize()I
HSPLandroidx/constraintlayout/core/ArrayLinkedVariables;->getVariable(I)Landroidx/constraintlayout/core/SolverVariable;
HSPLandroidx/constraintlayout/core/ArrayLinkedVariables;->getVariableValue(I)F
HSPLandroidx/constraintlayout/core/ArrayLinkedVariables;->invert()V
+HSPLandroidx/constraintlayout/core/ArrayLinkedVariables;->remove(Landroidx/constraintlayout/core/SolverVariable;Z)F
+HSPLandroidx/constraintlayout/core/ArrayLinkedVariables;->use(Landroidx/constraintlayout/core/ArrayRow;Z)F
HSPLandroidx/constraintlayout/core/ArrayRow;->(Landroidx/constraintlayout/core/Cache;)V
HSPLandroidx/constraintlayout/core/ArrayRow;->addError(Landroidx/constraintlayout/core/LinearSystem;I)Landroidx/constraintlayout/core/ArrayRow;
HSPLandroidx/constraintlayout/core/ArrayRow;->addSingleError(Landroidx/constraintlayout/core/SolverVariable;I)Landroidx/constraintlayout/core/ArrayRow;
@@ -1075,7 +1372,6 @@ HSPLandroidx/constraintlayout/core/ArrayRow;->isNew(Landroidx/constraintlayout/c
HSPLandroidx/constraintlayout/core/ArrayRow;->pivot(Landroidx/constraintlayout/core/SolverVariable;)V
HSPLandroidx/constraintlayout/core/ArrayRow;->reset()V
HSPLandroidx/constraintlayout/core/ArrayRow;->updateFromFinalVariable(Landroidx/constraintlayout/core/LinearSystem;Landroidx/constraintlayout/core/SolverVariable;Z)V
-HSPLandroidx/constraintlayout/core/ArrayRow;->updateFromSystem(Landroidx/constraintlayout/core/LinearSystem;)V
HSPLandroidx/constraintlayout/core/Cache;->()V
HSPLandroidx/constraintlayout/core/LinearSystem;->()V
HSPLandroidx/constraintlayout/core/LinearSystem;->()V
@@ -1086,10 +1382,11 @@ HSPLandroidx/constraintlayout/core/LinearSystem;->addGreaterBarrier(Landroidx/co
HSPLandroidx/constraintlayout/core/LinearSystem;->addGreaterThan(Landroidx/constraintlayout/core/SolverVariable;Landroidx/constraintlayout/core/SolverVariable;II)V
HSPLandroidx/constraintlayout/core/LinearSystem;->addLowerBarrier(Landroidx/constraintlayout/core/SolverVariable;Landroidx/constraintlayout/core/SolverVariable;IZ)V
HSPLandroidx/constraintlayout/core/LinearSystem;->addLowerThan(Landroidx/constraintlayout/core/SolverVariable;Landroidx/constraintlayout/core/SolverVariable;II)V
+HSPLandroidx/constraintlayout/core/LinearSystem;->addRow(Landroidx/constraintlayout/core/ArrayRow;)V
HSPLandroidx/constraintlayout/core/LinearSystem;->addSingleError(Landroidx/constraintlayout/core/ArrayRow;II)V
HSPLandroidx/constraintlayout/core/LinearSystem;->computeValues()V
-HSPLandroidx/constraintlayout/core/LinearSystem;->createErrorVariable(ILjava/lang/String;)Landroidx/constraintlayout/core/SolverVariable;
HSPLandroidx/constraintlayout/core/LinearSystem;->createObjectVariable(Ljava/lang/Object;)Landroidx/constraintlayout/core/SolverVariable;
+HSPLandroidx/constraintlayout/core/LinearSystem;->createRow()Landroidx/constraintlayout/core/ArrayRow;
HSPLandroidx/constraintlayout/core/LinearSystem;->createSlackVariable()Landroidx/constraintlayout/core/SolverVariable;
HSPLandroidx/constraintlayout/core/LinearSystem;->enforceBFS(Landroidx/constraintlayout/core/LinearSystem$Row;)I
HSPLandroidx/constraintlayout/core/LinearSystem;->getCache()Landroidx/constraintlayout/core/Cache;
@@ -1173,6 +1470,7 @@ HSPLandroidx/constraintlayout/core/widgets/ConstraintWidget;->()V
HSPLandroidx/constraintlayout/core/widgets/ConstraintWidget;->()V
HSPLandroidx/constraintlayout/core/widgets/ConstraintWidget;->addAnchors()V
HSPLandroidx/constraintlayout/core/widgets/ConstraintWidget;->addFirst()Z
+HSPLandroidx/constraintlayout/core/widgets/ConstraintWidget;->applyConstraints(Landroidx/constraintlayout/core/LinearSystem;ZZZZLandroidx/constraintlayout/core/SolverVariable;Landroidx/constraintlayout/core/SolverVariable;Landroidx/constraintlayout/core/widgets/ConstraintWidget$DimensionBehaviour;ZLandroidx/constraintlayout/core/widgets/ConstraintAnchor;Landroidx/constraintlayout/core/widgets/ConstraintAnchor;IIIIFZZZZZIIIIFZ)V
HSPLandroidx/constraintlayout/core/widgets/ConstraintWidget;->createObjectVariables(Landroidx/constraintlayout/core/LinearSystem;)V
HSPLandroidx/constraintlayout/core/widgets/ConstraintWidget;->getAnchor(Landroidx/constraintlayout/core/widgets/ConstraintAnchor$Type;)Landroidx/constraintlayout/core/widgets/ConstraintAnchor;
HSPLandroidx/constraintlayout/core/widgets/ConstraintWidget;->getBaselineDistance()I
@@ -1215,6 +1513,7 @@ HSPLandroidx/constraintlayout/core/widgets/ConstraintWidget;->isResolvedVertical
HSPLandroidx/constraintlayout/core/widgets/ConstraintWidget;->isVerticalSolvingPassDone()Z
HSPLandroidx/constraintlayout/core/widgets/ConstraintWidget;->markHorizontalSolvingPassDone()V
HSPLandroidx/constraintlayout/core/widgets/ConstraintWidget;->markVerticalSolvingPassDone()V
+HSPLandroidx/constraintlayout/core/widgets/ConstraintWidget;->reset()V
HSPLandroidx/constraintlayout/core/widgets/ConstraintWidget;->resetFinalResolution()V
HSPLandroidx/constraintlayout/core/widgets/ConstraintWidget;->resetSolverVariables(Landroidx/constraintlayout/core/Cache;)V
HSPLandroidx/constraintlayout/core/widgets/ConstraintWidget;->setBaselineDistance(I)V
@@ -1272,7 +1571,6 @@ HSPLandroidx/constraintlayout/core/widgets/ConstraintWidgetContainer;->invalidat
HSPLandroidx/constraintlayout/core/widgets/ConstraintWidgetContainer;->isHeightMeasuredTooSmall()Z
HSPLandroidx/constraintlayout/core/widgets/ConstraintWidgetContainer;->isRtl()Z
HSPLandroidx/constraintlayout/core/widgets/ConstraintWidgetContainer;->isWidthMeasuredTooSmall()Z
-HSPLandroidx/constraintlayout/core/widgets/ConstraintWidgetContainer;->layout()V
HSPLandroidx/constraintlayout/core/widgets/ConstraintWidgetContainer;->measure(IIIIIIIII)J
HSPLandroidx/constraintlayout/core/widgets/ConstraintWidgetContainer;->measure(ILandroidx/constraintlayout/core/widgets/ConstraintWidget;Landroidx/constraintlayout/core/widgets/analyzer/BasicMeasure$Measurer;Landroidx/constraintlayout/core/widgets/analyzer/BasicMeasure$Measure;I)Z
HSPLandroidx/constraintlayout/core/widgets/ConstraintWidgetContainer;->optimizeFor(I)Z
@@ -1323,6 +1621,7 @@ HSPLandroidx/constraintlayout/core/widgets/analyzer/DependencyGraph;->invalidate
HSPLandroidx/constraintlayout/core/widgets/analyzer/DependencyGraph;->setMeasurer(Landroidx/constraintlayout/core/widgets/analyzer/BasicMeasure$Measurer;)V
HSPLandroidx/constraintlayout/core/widgets/analyzer/Direct;->()V
HSPLandroidx/constraintlayout/core/widgets/analyzer/Direct;->canMeasure(ILandroidx/constraintlayout/core/widgets/ConstraintWidget;)Z
+HSPLandroidx/constraintlayout/core/widgets/analyzer/Direct;->horizontalSolvingPass(ILandroidx/constraintlayout/core/widgets/ConstraintWidget;Landroidx/constraintlayout/core/widgets/analyzer/BasicMeasure$Measurer;Z)V
HSPLandroidx/constraintlayout/core/widgets/analyzer/Direct;->solveBarrier(ILandroidx/constraintlayout/core/widgets/Barrier;Landroidx/constraintlayout/core/widgets/analyzer/BasicMeasure$Measurer;IZ)V
HSPLandroidx/constraintlayout/core/widgets/analyzer/Direct;->solveHorizontalCenterConstraints(ILandroidx/constraintlayout/core/widgets/analyzer/BasicMeasure$Measurer;Landroidx/constraintlayout/core/widgets/ConstraintWidget;Z)V
HSPLandroidx/constraintlayout/core/widgets/analyzer/Direct;->solveHorizontalMatchConstraint(ILandroidx/constraintlayout/core/widgets/ConstraintWidget;Landroidx/constraintlayout/core/widgets/analyzer/BasicMeasure$Measurer;Landroidx/constraintlayout/core/widgets/ConstraintWidget;Z)V
@@ -1352,7 +1651,7 @@ HSPLandroidx/constraintlayout/widget/ConstraintHelper;->updatePreLayout(Landroid
HSPLandroidx/constraintlayout/widget/ConstraintHelper;->validateParams()V
HSPLandroidx/constraintlayout/widget/ConstraintLayout$1;->()V
HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams$Table;->()V
-HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;->(Landroid/content/Context;Landroid/util/AttributeSet;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;->resolveLayoutDirection(I)V
HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;->validate()V
HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;->(Landroidx/constraintlayout/widget/ConstraintLayout;Landroidx/constraintlayout/widget/ConstraintLayout;)V
HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;->captureLayoutInfo(IIIIII)V
@@ -1409,6 +1708,7 @@ HSPLandroidx/constraintlayout/widget/Guideline;->setGuidelineBegin(I)V
HSPLandroidx/constraintlayout/widget/Guideline;->setGuidelineEnd(I)V
HSPLandroidx/constraintlayout/widget/R$styleable;->()V
HSPLandroidx/coordinatorlayout/R$styleable;->()V
+HSPLandroidx/coordinatorlayout/widget/CoordinatorLayout$$ExternalSyntheticApiModelOutline0;->m(Landroidx/coordinatorlayout/widget/CoordinatorLayout;Landroid/content/Context;[ILandroid/util/AttributeSet;Landroid/content/res/TypedArray;II)V
HSPLandroidx/coordinatorlayout/widget/CoordinatorLayout$Behavior;->(Landroid/content/Context;Landroid/util/AttributeSet;)V
HSPLandroidx/coordinatorlayout/widget/CoordinatorLayout$Behavior;->blocksInteractionBelow(Landroidx/coordinatorlayout/widget/CoordinatorLayout;Landroid/view/View;)Z
HSPLandroidx/coordinatorlayout/widget/CoordinatorLayout$Behavior;->getScrimOpacity(Landroidx/coordinatorlayout/widget/CoordinatorLayout;Landroid/view/View;)F
@@ -1506,10 +1806,7 @@ HSPLandroidx/coordinatorlayout/widget/ViewGroupUtils;->offsetDescendantMatrix(La
HSPLandroidx/coordinatorlayout/widget/ViewGroupUtils;->offsetDescendantRect(Landroid/view/ViewGroup;Landroid/view/View;Landroid/graphics/Rect;)V
HSPLandroidx/core/R$styleable;->()V
HSPLandroidx/core/app/ActivityCompat$Api21Impl;->postponeEnterTransition(Landroid/app/Activity;)V
-HSPLandroidx/core/app/ActivityCompat$Api21Impl;->startPostponedEnterTransition(Landroid/app/Activity;)V
HSPLandroidx/core/app/ActivityCompat;->postponeEnterTransition(Landroid/app/Activity;)V
-HSPLandroidx/core/app/ActivityCompat;->startPostponedEnterTransition(Landroid/app/Activity;)V
-HSPLandroidx/core/app/AppOpsManagerCompat$Api23Impl$$ExternalSyntheticApiModelOutline1;->m(Ljava/lang/String;)Ljava/lang/String;
HSPLandroidx/core/app/AppOpsManagerCompat$Api23Impl;->permissionToOp(Ljava/lang/String;)Ljava/lang/String;
HSPLandroidx/core/app/AppOpsManagerCompat;->permissionToOp(Ljava/lang/String;)Ljava/lang/String;
HSPLandroidx/core/app/ComponentActivity;->()V
@@ -1522,19 +1819,15 @@ HSPLandroidx/core/app/CoreComponentFactory;->instantiateProvider(Ljava/lang/Clas
HSPLandroidx/core/app/CoreComponentFactory;->instantiateService(Ljava/lang/ClassLoader;Ljava/lang/String;Landroid/content/Intent;)Landroid/app/Service;
HSPLandroidx/core/app/NavUtils;->getParentActivityName(Landroid/app/Activity;)Ljava/lang/String;
HSPLandroidx/core/app/NavUtils;->getParentActivityName(Landroid/content/Context;Landroid/content/ComponentName;)Ljava/lang/String;
-HSPLandroidx/core/app/NotificationManagerCompat$Api24Impl$$ExternalSyntheticApiModelOutline1;->m(Landroid/app/NotificationManager;)Z
HSPLandroidx/core/app/NotificationManagerCompat;->()V
HSPLandroidx/core/app/NotificationManagerCompat;->(Landroid/content/Context;)V
HSPLandroidx/core/app/NotificationManagerCompat;->cancel(I)V
HSPLandroidx/core/app/NotificationManagerCompat;->cancel(Ljava/lang/String;I)V
HSPLandroidx/core/app/NotificationManagerCompat;->from(Landroid/content/Context;)Landroidx/core/app/NotificationManagerCompat;
-HSPLandroidx/core/content/ContentValuesKt;->contentValuesOf([Lkotlin/Pair;)Landroid/content/ContentValues;
HSPLandroidx/core/content/ContextCompat$Api21Impl;->getDrawable(Landroid/content/Context;I)Landroid/graphics/drawable/Drawable;
-HSPLandroidx/core/content/ContextCompat$Api23Impl$$ExternalSyntheticApiModelOutline1;->m(Landroid/content/Context;I)I
HSPLandroidx/core/content/ContextCompat$Api23Impl;->getColor(Landroid/content/Context;I)I
HSPLandroidx/core/content/ContextCompat$Api23Impl;->getSystemService(Landroid/content/Context;Ljava/lang/Class;)Ljava/lang/Object;
HSPLandroidx/core/content/ContextCompat$Api26Impl;->registerReceiver(Landroid/content/Context;Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;Ljava/lang/String;Landroid/os/Handler;I)Landroid/content/Intent;
-HSPLandroidx/core/content/ContextCompat$Api28Impl$$ExternalSyntheticApiModelOutline0;->m(Landroid/content/Context;)Ljava/util/concurrent/Executor;
HSPLandroidx/core/content/ContextCompat$Api28Impl;->getMainExecutor(Landroid/content/Context;)Ljava/util/concurrent/Executor;
HSPLandroidx/core/content/ContextCompat;->()V
HSPLandroidx/core/content/ContextCompat;->checkSelfPermission(Landroid/content/Context;Ljava/lang/String;)I
@@ -1562,7 +1855,6 @@ HSPLandroidx/core/content/res/ColorStateListInflaterCompat;->modulateColorAlpha(
HSPLandroidx/core/content/res/ColorStateListInflaterCompat;->obtainAttributes(Landroid/content/res/Resources;Landroid/content/res/Resources$Theme;Landroid/util/AttributeSet;[I)Landroid/content/res/TypedArray;
HSPLandroidx/core/content/res/GrowingArrayUtils;->append([III)[I
HSPLandroidx/core/content/res/GrowingArrayUtils;->append([Ljava/lang/Object;ILjava/lang/Object;)[Ljava/lang/Object;
-HSPLandroidx/core/content/res/ResourcesCompat$Api23Impl$$ExternalSyntheticApiModelOutline0;->m(Landroid/content/res/Resources;ILandroid/content/res/Resources$Theme;)Landroid/content/res/ColorStateList;
HSPLandroidx/core/content/res/ResourcesCompat$Api23Impl;->getColorStateList(Landroid/content/res/Resources;ILandroid/content/res/Resources$Theme;)Landroid/content/res/ColorStateList;
HSPLandroidx/core/content/res/ResourcesCompat$ColorStateListCacheEntry;->(Landroid/content/res/ColorStateList;Landroid/content/res/Configuration;Landroid/content/res/Resources$Theme;)V
HSPLandroidx/core/content/res/ResourcesCompat$ColorStateListCacheKey;->(Landroid/content/res/Resources;Landroid/content/res/Resources$Theme;)V
@@ -1570,7 +1862,7 @@ HSPLandroidx/core/content/res/ResourcesCompat$ColorStateListCacheKey;->equals(Lj
HSPLandroidx/core/content/res/ResourcesCompat$ColorStateListCacheKey;->hashCode()I
HSPLandroidx/core/content/res/ResourcesCompat$FontCallback$$ExternalSyntheticLambda1;->(Landroidx/core/content/res/ResourcesCompat$FontCallback;I)V
HSPLandroidx/core/content/res/ResourcesCompat$FontCallback$$ExternalSyntheticLambda1;->run()V
-HSPLandroidx/core/content/res/ResourcesCompat$FontCallback;->$r8$lambda$3yv7dDJDSSH8XKTxkNkwYH6nK6w(Landroidx/core/content/res/ResourcesCompat$FontCallback;I)V
+HSPLandroidx/core/content/res/ResourcesCompat$FontCallback;->$r8$lambda$iq3V68-a5RqNx7LeKJ2odF7JKlo(Landroidx/core/content/res/ResourcesCompat$FontCallback;I)V
HSPLandroidx/core/content/res/ResourcesCompat$FontCallback;->()V
HSPLandroidx/core/content/res/ResourcesCompat$FontCallback;->callbackFailAsync(ILandroid/os/Handler;)V
HSPLandroidx/core/content/res/ResourcesCompat$FontCallback;->getHandler(Landroid/os/Handler;)Landroid/os/Handler;
@@ -1604,8 +1896,6 @@ HSPLandroidx/core/graphics/drawable/DrawableCompat$Api21Impl;->setHotspotBounds(
HSPLandroidx/core/graphics/drawable/DrawableCompat$Api21Impl;->setTint(Landroid/graphics/drawable/Drawable;I)V
HSPLandroidx/core/graphics/drawable/DrawableCompat$Api21Impl;->setTintList(Landroid/graphics/drawable/Drawable;Landroid/content/res/ColorStateList;)V
HSPLandroidx/core/graphics/drawable/DrawableCompat$Api21Impl;->setTintMode(Landroid/graphics/drawable/Drawable;Landroid/graphics/PorterDuff$Mode;)V
-HSPLandroidx/core/graphics/drawable/DrawableCompat$Api23Impl$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/drawable/Drawable;I)Z
-HSPLandroidx/core/graphics/drawable/DrawableCompat$Api23Impl$$ExternalSyntheticApiModelOutline1;->m(Landroid/graphics/drawable/Drawable;)I
HSPLandroidx/core/graphics/drawable/DrawableCompat$Api23Impl;->getLayoutDirection(Landroid/graphics/drawable/Drawable;)I
HSPLandroidx/core/graphics/drawable/DrawableCompat$Api23Impl;->setLayoutDirection(Landroid/graphics/drawable/Drawable;I)Z
HSPLandroidx/core/graphics/drawable/DrawableCompat;->getLayoutDirection(Landroid/graphics/drawable/Drawable;)I
@@ -1621,7 +1911,6 @@ HSPLandroidx/core/math/MathUtils;->clamp(III)I
HSPLandroidx/core/os/CancellationSignal;->()V
HSPLandroidx/core/os/CancellationSignal;->setOnCancelListener(Landroidx/core/os/CancellationSignal$OnCancelListener;)V
HSPLandroidx/core/os/CancellationSignal;->waitForCancelFinishedLocked()V
-HSPLandroidx/core/os/ConfigurationCompat$Api24Impl$$ExternalSyntheticApiModelOutline0;->m(Ljava/lang/Object;)Landroid/os/LocaleList;
HSPLandroidx/core/os/ConfigurationCompat$Api24Impl;->getLocales(Landroid/content/res/Configuration;)Landroid/os/LocaleList;
HSPLandroidx/core/os/ConfigurationCompat;->getLocales(Landroid/content/res/Configuration;)Landroidx/core/os/LocaleListCompat;
HSPLandroidx/core/os/LocaleListCompat$Api21Impl;->()V
@@ -1638,6 +1927,7 @@ HSPLandroidx/core/os/LocaleListCompat;->getFirstMatch([Ljava/lang/String;)Ljava/
HSPLandroidx/core/os/LocaleListCompat;->size()I
HSPLandroidx/core/os/LocaleListCompat;->wrap(Landroid/os/LocaleList;)Landroidx/core/os/LocaleListCompat;
HSPLandroidx/core/os/LocaleListPlatformWrapper$$ExternalSyntheticApiModelOutline0;->m(Landroid/os/LocaleList;[Ljava/lang/String;)Ljava/util/Locale;
+HSPLandroidx/core/os/LocaleListPlatformWrapper$$ExternalSyntheticApiModelOutline4;->m(Ljava/lang/Object;)Landroid/os/LocaleList;
HSPLandroidx/core/os/LocaleListPlatformWrapper;->(Ljava/lang/Object;)V
HSPLandroidx/core/os/LocaleListPlatformWrapper;->get(I)Ljava/util/Locale;
HSPLandroidx/core/os/LocaleListPlatformWrapper;->getFirstMatch([Ljava/lang/String;)Ljava/util/Locale;
@@ -1647,13 +1937,8 @@ HSPLandroidx/core/os/TraceCompat$Api18Impl;->endSection()V
HSPLandroidx/core/os/TraceCompat;->()V
HSPLandroidx/core/os/TraceCompat;->beginSection(Ljava/lang/String;)V
HSPLandroidx/core/os/TraceCompat;->endSection()V
-HSPLandroidx/core/os/UserManagerCompat$Api24Impl$$ExternalSyntheticApiModelOutline0;->m(Landroid/os/UserManager;)Z
HSPLandroidx/core/os/UserManagerCompat$Api24Impl;->isUserUnlocked(Landroid/content/Context;)Z
HSPLandroidx/core/os/UserManagerCompat;->isUserUnlocked(Landroid/content/Context;)Z
-HSPLandroidx/core/text/util/LinkifyCompat$$ExternalSyntheticLambda0;->()V
-HSPLandroidx/core/text/util/LinkifyCompat;->()V
-HSPLandroidx/core/text/util/LinkifyCompat;->addLinks(Landroid/text/Spannable;I)Z
-HSPLandroidx/core/text/util/LinkifyCompat;->shouldAddLinksFallbackToFramework()Z
HSPLandroidx/core/util/ObjectsCompat$Api19Impl;->equals(Ljava/lang/Object;Ljava/lang/Object;)Z
HSPLandroidx/core/util/ObjectsCompat$Api19Impl;->hash([Ljava/lang/Object;)I
HSPLandroidx/core/util/ObjectsCompat;->equals(Ljava/lang/Object;Ljava/lang/Object;)Z
@@ -1723,7 +2008,6 @@ HSPLandroidx/core/view/NestedScrollingChildHelper;->isNestedScrollingEnabled()Z
HSPLandroidx/core/view/NestedScrollingChildHelper;->setNestedScrollingEnabled(Z)V
HSPLandroidx/core/view/NestedScrollingChildHelper;->setNestedScrollingParentForType(ILandroid/view/ViewParent;)V
HSPLandroidx/core/view/NestedScrollingChildHelper;->startNestedScroll(II)Z
-HSPLandroidx/core/view/NestedScrollingChildHelper;->stopNestedScroll()V
HSPLandroidx/core/view/NestedScrollingChildHelper;->stopNestedScroll(I)V
HSPLandroidx/core/view/NestedScrollingParentHelper;->(Landroid/view/ViewGroup;)V
HSPLandroidx/core/view/NestedScrollingParentHelper;->onNestedScrollAccepted(Landroid/view/View;Landroid/view/View;II)V
@@ -1778,23 +2062,14 @@ HSPLandroidx/core/view/ViewCompat$Api21Impl;->getElevation(Landroid/view/View;)F
HSPLandroidx/core/view/ViewCompat$Api21Impl;->getZ(Landroid/view/View;)F
HSPLandroidx/core/view/ViewCompat$Api21Impl;->setBackgroundTintList(Landroid/view/View;Landroid/content/res/ColorStateList;)V
HSPLandroidx/core/view/ViewCompat$Api21Impl;->setOnApplyWindowInsetsListener(Landroid/view/View;Landroidx/core/view/OnApplyWindowInsetsListener;)V
-HSPLandroidx/core/view/ViewCompat$Api23Impl$$ExternalSyntheticApiModelOutline1;->m(Landroid/view/View;)Landroid/view/WindowInsets;
HSPLandroidx/core/view/ViewCompat$Api23Impl;->getRootWindowInsets(Landroid/view/View;)Landroidx/core/view/WindowInsetsCompat;
-HSPLandroidx/core/view/ViewCompat$Api26Impl$$ExternalSyntheticApiModelOutline0;->m(Landroid/view/View;)I
HSPLandroidx/core/view/ViewCompat$Api26Impl;->getImportantForAutofill(Landroid/view/View;)I
HSPLandroidx/core/view/ViewCompat$Api26Impl;->setImportantForAutofill(Landroid/view/View;I)V
-HSPLandroidx/core/view/ViewCompat$Api28Impl$$ExternalSyntheticApiModelOutline0;->m(Landroid/view/View;)Z
-HSPLandroidx/core/view/ViewCompat$Api28Impl$$ExternalSyntheticApiModelOutline10;->m(Landroid/view/View;)Ljava/lang/CharSequence;
-HSPLandroidx/core/view/ViewCompat$Api28Impl$$ExternalSyntheticApiModelOutline2;->m(Landroid/view/View;Landroid/view/View$OnUnhandledKeyEventListener;)V
-HSPLandroidx/core/view/ViewCompat$Api28Impl$$ExternalSyntheticApiModelOutline4;->m(Landroid/view/View;)Z
HSPLandroidx/core/view/ViewCompat$Api28Impl;->getAccessibilityPaneTitle(Landroid/view/View;)Ljava/lang/CharSequence;
HSPLandroidx/core/view/ViewCompat$Api28Impl;->isAccessibilityHeading(Landroid/view/View;)Z
HSPLandroidx/core/view/ViewCompat$Api28Impl;->isScreenReaderFocusable(Landroid/view/View;)Z
-HSPLandroidx/core/view/ViewCompat$Api29Impl$$ExternalSyntheticApiModelOutline2;->m(Landroid/view/View;)Landroid/view/View$AccessibilityDelegate;
-HSPLandroidx/core/view/ViewCompat$Api29Impl$$ExternalSyntheticApiModelOutline3;->m(Landroid/view/View;Landroid/content/Context;[ILandroid/util/AttributeSet;Landroid/content/res/TypedArray;II)V
HSPLandroidx/core/view/ViewCompat$Api29Impl;->getAccessibilityDelegate(Landroid/view/View;)Landroid/view/View$AccessibilityDelegate;
HSPLandroidx/core/view/ViewCompat$Api29Impl;->saveAttributeDataForStyleable(Landroid/view/View;Landroid/content/Context;[ILandroid/util/AttributeSet;Landroid/content/res/TypedArray;II)V
-HSPLandroidx/core/view/ViewCompat$Api30Impl$$ExternalSyntheticApiModelOutline2;->m(Landroid/view/View;)Ljava/lang/CharSequence;
HSPLandroidx/core/view/ViewCompat$Api30Impl;->getStateDescription(Landroid/view/View;)Ljava/lang/CharSequence;
HSPLandroidx/core/view/ViewCompat;->()V
HSPLandroidx/core/view/ViewCompat;->accessibilityHeadingProperty()Landroidx/core/view/ViewCompat$AccessibilityViewProperty;
@@ -1849,11 +2124,8 @@ HSPLandroidx/core/view/ViewCompat;->setOnApplyWindowInsetsListener(Landroid/view
HSPLandroidx/core/view/ViewCompat;->setPaddingRelative(Landroid/view/View;IIII)V
HSPLandroidx/core/view/ViewCompat;->setWindowInsetsAnimationCallback(Landroid/view/View;Landroidx/core/view/WindowInsetsAnimationCompat$Callback;)V
HSPLandroidx/core/view/ViewCompat;->stateDescriptionProperty()Landroidx/core/view/ViewCompat$AccessibilityViewProperty;
-HSPLandroidx/core/view/ViewConfigurationCompat$Api26Impl$$ExternalSyntheticApiModelOutline0;->m(Landroid/view/ViewConfiguration;)F
-HSPLandroidx/core/view/ViewConfigurationCompat$Api26Impl$$ExternalSyntheticApiModelOutline1;->m(Landroid/view/ViewConfiguration;)F
HSPLandroidx/core/view/ViewConfigurationCompat$Api26Impl;->getScaledHorizontalScrollFactor(Landroid/view/ViewConfiguration;)F
HSPLandroidx/core/view/ViewConfigurationCompat$Api26Impl;->getScaledVerticalScrollFactor(Landroid/view/ViewConfiguration;)F
-HSPLandroidx/core/view/ViewConfigurationCompat$Api28Impl$$ExternalSyntheticApiModelOutline0;->m(Landroid/view/ViewConfiguration;)Z
HSPLandroidx/core/view/ViewConfigurationCompat$Api28Impl;->shouldShowMenuShortcutsWhenKeyboardPresent(Landroid/view/ViewConfiguration;)Z
HSPLandroidx/core/view/ViewConfigurationCompat;->()V
HSPLandroidx/core/view/ViewConfigurationCompat;->getScaledHorizontalScrollFactor(Landroid/view/ViewConfiguration;Landroid/content/Context;)F
@@ -1863,28 +2135,24 @@ HSPLandroidx/core/view/ViewGroupKt$children$1;->(Landroid/view/ViewGroup;)
HSPLandroidx/core/view/ViewGroupKt$children$1;->iterator()Ljava/util/Iterator;
HSPLandroidx/core/view/ViewGroupKt$iterator$1;->(Landroid/view/ViewGroup;)V
HSPLandroidx/core/view/ViewGroupKt$iterator$1;->hasNext()Z
-HSPLandroidx/core/view/ViewGroupKt$iterator$1;->next()Landroid/view/View;
-HSPLandroidx/core/view/ViewGroupKt$iterator$1;->next()Ljava/lang/Object;
HSPLandroidx/core/view/ViewGroupKt;->getChildren(Landroid/view/ViewGroup;)Lkotlin/sequences/Sequence;
HSPLandroidx/core/view/ViewGroupKt;->iterator(Landroid/view/ViewGroup;)Ljava/util/Iterator;
HSPLandroidx/core/view/ViewKt$doOnPreDraw$1;->(Lkotlin/jvm/functions/Function1;Landroid/view/View;)V
HSPLandroidx/core/view/ViewKt$doOnPreDraw$1;->run()V
HSPLandroidx/core/view/ViewKt;->doOnPreDraw(Landroid/view/View;Lkotlin/jvm/functions/Function1;)Landroidx/core/view/OneShotPreDrawListener;
-HSPLandroidx/core/view/ViewKt;->isVisible(Landroid/view/View;)Z
HSPLandroidx/core/view/ViewParentCompat;->onNestedScrollAccepted(Landroid/view/ViewParent;Landroid/view/View;Landroid/view/View;II)V
HSPLandroidx/core/view/ViewParentCompat;->onStartNestedScroll(Landroid/view/ViewParent;Landroid/view/View;Landroid/view/View;II)Z
HSPLandroidx/core/view/ViewParentCompat;->onStopNestedScroll(Landroid/view/ViewParent;Landroid/view/View;I)V
HSPLandroidx/core/view/WindowInsetsAnimationCompat$Callback;->(I)V
HSPLandroidx/core/view/WindowInsetsAnimationCompat$Callback;->getDispatchMode()I
-HSPLandroidx/core/view/WindowInsetsAnimationCompat$Impl30$$ExternalSyntheticApiModelOutline2;->m(Landroid/view/View;Landroid/view/WindowInsetsAnimation$Callback;)V
+HSPLandroidx/core/view/WindowInsetsAnimationCompat$Impl30$$ExternalSyntheticApiModelOutline4;->m(Landroid/view/View;Landroid/view/WindowInsetsAnimation$Callback;)V
HSPLandroidx/core/view/WindowInsetsAnimationCompat$Impl30$ProxyCallback;->(Landroidx/core/view/WindowInsetsAnimationCompat$Callback;)V
HSPLandroidx/core/view/WindowInsetsAnimationCompat$Impl30;->setCallback(Landroid/view/View;Landroidx/core/view/WindowInsetsAnimationCompat$Callback;)V
HSPLandroidx/core/view/WindowInsetsAnimationCompat;->setCallback(Landroid/view/View;Landroidx/core/view/WindowInsetsAnimationCompat$Callback;)V
HSPLandroidx/core/view/WindowInsetsCompat$Builder;->()V
HSPLandroidx/core/view/WindowInsetsCompat$Builder;->build()Landroidx/core/view/WindowInsetsCompat;
-HSPLandroidx/core/view/WindowInsetsCompat$BuilderImpl29$$ExternalSyntheticApiModelOutline5;->m(Landroid/view/WindowInsets$Builder;)Landroid/view/WindowInsets;
-HSPLandroidx/core/view/WindowInsetsCompat$BuilderImpl29$$ExternalSyntheticApiModelOutline6;->m()Landroid/view/WindowInsets$Builder;
-HSPLandroidx/core/view/WindowInsetsCompat$BuilderImpl29$$ExternalSyntheticApiModelOutline8;->m()V
+HSPLandroidx/core/view/WindowInsetsCompat$BuilderImpl29$$ExternalSyntheticApiModelOutline3;->m(Landroid/view/WindowInsets$Builder;)Landroid/view/WindowInsets;
+HSPLandroidx/core/view/WindowInsetsCompat$BuilderImpl29$$ExternalSyntheticApiModelOutline7;->m()Landroid/view/WindowInsets$Builder;
HSPLandroidx/core/view/WindowInsetsCompat$BuilderImpl29;->()V
HSPLandroidx/core/view/WindowInsetsCompat$BuilderImpl29;->build()Landroidx/core/view/WindowInsetsCompat;
HSPLandroidx/core/view/WindowInsetsCompat$BuilderImpl30;->()V
@@ -1904,8 +2172,8 @@ HSPLandroidx/core/view/WindowInsetsCompat$Impl28$$ExternalSyntheticApiModelOutli
HSPLandroidx/core/view/WindowInsetsCompat$Impl28;->(Landroidx/core/view/WindowInsetsCompat;Landroid/view/WindowInsets;)V
HSPLandroidx/core/view/WindowInsetsCompat$Impl28;->consumeDisplayCutout()Landroidx/core/view/WindowInsetsCompat;
HSPLandroidx/core/view/WindowInsetsCompat$Impl29;->(Landroidx/core/view/WindowInsetsCompat;Landroid/view/WindowInsets;)V
-HSPLandroidx/core/view/WindowInsetsCompat$Impl30$$ExternalSyntheticApiModelOutline1;->m()Landroid/view/WindowInsets;
-HSPLandroidx/core/view/WindowInsetsCompat$Impl30$$ExternalSyntheticApiModelOutline3;->m(Landroid/view/WindowInsets;I)Landroid/graphics/Insets;
+HSPLandroidx/core/view/WindowInsetsCompat$Impl30$$ExternalSyntheticApiModelOutline1;->m(Landroid/view/WindowInsets;I)Landroid/graphics/Insets;
+HSPLandroidx/core/view/WindowInsetsCompat$Impl30$$ExternalSyntheticApiModelOutline2;->m()Landroid/view/WindowInsets;
HSPLandroidx/core/view/WindowInsetsCompat$Impl30;->()V
HSPLandroidx/core/view/WindowInsetsCompat$Impl30;->(Landroidx/core/view/WindowInsetsCompat;Landroid/view/WindowInsets;)V
HSPLandroidx/core/view/WindowInsetsCompat$Impl30;->copyRootViewBounds(Landroid/view/View;)V
@@ -1935,12 +2203,11 @@ HSPLandroidx/core/view/WindowInsetsCompat;->setRootWindowInsets(Landroidx/core/v
HSPLandroidx/core/view/WindowInsetsCompat;->toWindowInsets()Landroid/view/WindowInsets;
HSPLandroidx/core/view/WindowInsetsCompat;->toWindowInsetsCompat(Landroid/view/WindowInsets;)Landroidx/core/view/WindowInsetsCompat;
HSPLandroidx/core/view/WindowInsetsCompat;->toWindowInsetsCompat(Landroid/view/WindowInsets;Landroid/view/View;)Landroidx/core/view/WindowInsetsCompat;
-HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$$ExternalSyntheticApiModelOutline11;->m(Landroid/view/accessibility/AccessibilityNodeInfo;Z)V
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$$ExternalSyntheticApiModelOutline3;->m(Landroid/view/accessibility/AccessibilityNodeInfo;Z)V
-HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$$ExternalSyntheticApiModelOutline6;->m(Landroid/view/accessibility/AccessibilityNodeInfo;Ljava/lang/CharSequence;)V
+HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$$ExternalSyntheticApiModelOutline4;->m(Landroid/view/accessibility/AccessibilityNodeInfo;Ljava/lang/CharSequence;)V
+HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$$ExternalSyntheticApiModelOutline5;->m(Landroid/view/accessibility/AccessibilityNodeInfo;Z)V
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat$$ExternalSyntheticApiModelOutline0;->m()Landroid/view/accessibility/AccessibilityNodeInfo$AccessibilityAction;
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat$$ExternalSyntheticApiModelOutline11;->m()Landroid/view/accessibility/AccessibilityNodeInfo$AccessibilityAction;
-HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat$$ExternalSyntheticApiModelOutline13;->m()Landroid/view/accessibility/AccessibilityNodeInfo$AccessibilityAction;
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat$$ExternalSyntheticApiModelOutline14;->m()Landroid/view/accessibility/AccessibilityNodeInfo$AccessibilityAction;
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat$$ExternalSyntheticApiModelOutline15;->m()Landroid/view/accessibility/AccessibilityNodeInfo$AccessibilityAction;
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat$$ExternalSyntheticApiModelOutline16;->m()Landroid/view/accessibility/AccessibilityNodeInfo$AccessibilityAction;
@@ -1949,6 +2216,7 @@ HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityAc
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat$$ExternalSyntheticApiModelOutline19;->m()Landroid/view/accessibility/AccessibilityNodeInfo$AccessibilityAction;
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat$$ExternalSyntheticApiModelOutline1;->m()Landroid/view/accessibility/AccessibilityNodeInfo$AccessibilityAction;
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat$$ExternalSyntheticApiModelOutline20;->m()Landroid/view/accessibility/AccessibilityNodeInfo$AccessibilityAction;
+HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat$$ExternalSyntheticApiModelOutline21;->m()Landroid/view/accessibility/AccessibilityNodeInfo$AccessibilityAction;
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat$$ExternalSyntheticApiModelOutline2;->m()Landroid/view/accessibility/AccessibilityNodeInfo$AccessibilityAction;
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat$$ExternalSyntheticApiModelOutline3;->m()Landroid/view/accessibility/AccessibilityNodeInfo$AccessibilityAction;
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat$$ExternalSyntheticApiModelOutline4;->m()Landroid/view/accessibility/AccessibilityNodeInfo$AccessibilityAction;
@@ -1961,7 +2229,6 @@ HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityAc
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat;->(Ljava/lang/Object;ILjava/lang/CharSequence;Landroidx/core/view/accessibility/AccessibilityViewCommand;Ljava/lang/Class;)V
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat;->createReplacementAction(Ljava/lang/CharSequence;Landroidx/core/view/accessibility/AccessibilityViewCommand;)Landroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat;
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat;->getId()I
-HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$Api30Impl$$ExternalSyntheticApiModelOutline0;->m(Landroid/view/accessibility/AccessibilityNodeInfo;Ljava/lang/CharSequence;)V
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$Api30Impl;->setStateDescription(Landroid/view/accessibility/AccessibilityNodeInfo;Ljava/lang/CharSequence;)V
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$CollectionInfoCompat;->(Ljava/lang/Object;)V
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat$CollectionInfoCompat;->obtain(IIZI)Landroidx/core/view/accessibility/AccessibilityNodeInfoCompat$CollectionInfoCompat;
@@ -1981,17 +2248,11 @@ HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat;->unwrap()Landr
HSPLandroidx/core/view/accessibility/AccessibilityNodeInfoCompat;->wrap(Landroid/view/accessibility/AccessibilityNodeInfo;)Landroidx/core/view/accessibility/AccessibilityNodeInfoCompat;
HSPLandroidx/core/view/animation/PathInterpolatorCompat$Api21Impl;->createPathInterpolator(FFFF)Landroid/view/animation/Interpolator;
HSPLandroidx/core/view/animation/PathInterpolatorCompat;->create(FFFF)Landroid/view/animation/Interpolator;
-HSPLandroidx/core/view/inputmethod/EditorInfoCompat$$ExternalSyntheticApiModelOutline0;->m(Landroid/view/inputmethod/EditorInfo;[Ljava/lang/String;)V
-HSPLandroidx/core/view/inputmethod/EditorInfoCompat;->()V
-HSPLandroidx/core/view/inputmethod/EditorInfoCompat;->setContentMimeTypes(Landroid/view/inputmethod/EditorInfo;[Ljava/lang/String;)V
-HSPLandroidx/core/view/inputmethod/InputConnectionCompat$1;->(Landroid/view/inputmethod/InputConnection;ZLandroidx/core/view/inputmethod/InputConnectionCompat$OnCommitContentListener;)V
-HSPLandroidx/core/view/inputmethod/InputConnectionCompat;->createWrapper(Landroid/view/inputmethod/InputConnection;Landroid/view/inputmethod/EditorInfo;Landroidx/core/view/inputmethod/InputConnectionCompat$OnCommitContentListener;)Landroid/view/inputmethod/InputConnection;
HSPLandroidx/core/widget/ImageViewCompat$Api21Impl;->setImageTintList(Landroid/widget/ImageView;Landroid/content/res/ColorStateList;)V
HSPLandroidx/core/widget/ImageViewCompat;->setImageTintList(Landroid/widget/ImageView;Landroid/content/res/ColorStateList;)V
HSPLandroidx/core/widget/TextViewCompat$Api16Impl;->getMaxLines(Landroid/widget/TextView;)I
HSPLandroidx/core/widget/TextViewCompat$Api17Impl;->getCompoundDrawablesRelative(Landroid/widget/TextView;)[Landroid/graphics/drawable/Drawable;
HSPLandroidx/core/widget/TextViewCompat$Api17Impl;->setCompoundDrawablesRelative(Landroid/widget/TextView;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;)V
-HSPLandroidx/core/widget/TextViewCompat$Api23Impl$$ExternalSyntheticApiModelOutline5;->m(Landroid/widget/TextView;Landroid/content/res/ColorStateList;)V
HSPLandroidx/core/widget/TextViewCompat$Api23Impl;->setCompoundDrawableTintList(Landroid/widget/TextView;Landroid/content/res/ColorStateList;)V
HSPLandroidx/core/widget/TextViewCompat;->getCompoundDrawablesRelative(Landroid/widget/TextView;)[Landroid/graphics/drawable/Drawable;
HSPLandroidx/core/widget/TextViewCompat;->getMaxLines(Landroid/widget/TextView;)I
@@ -2001,7 +2262,6 @@ HSPLandroidx/core/widget/TextViewCompat;->setLineHeight(Landroid/widget/TextView
HSPLandroidx/core/widget/TextViewCompat;->wrapCustomSelectionActionModeCallback(Landroid/widget/TextView;Landroid/view/ActionMode$Callback;)Landroid/view/ActionMode$Callback;
HSPLandroidx/core/widget/TextViewOnReceiveContentListener;->()V
HSPLandroidx/customview/poolingcontainer/PoolingContainer;->()V
-HSPLandroidx/customview/poolingcontainer/PoolingContainer;->callPoolingContainerOnReleaseForChildren(Landroid/view/ViewGroup;)V
HSPLandroidx/customview/poolingcontainer/PoolingContainer;->setPoolingContainer(Landroid/view/View;Z)V
HSPLandroidx/customview/widget/ExploreByTouchHelper$1;->()V
HSPLandroidx/customview/widget/ExploreByTouchHelper$2;->()V
@@ -2009,8 +2269,9 @@ HSPLandroidx/customview/widget/ExploreByTouchHelper;->()V
HSPLandroidx/customview/widget/ExploreByTouchHelper;->(Landroid/view/View;)V
HSPLandroidx/emoji2/text/ConcurrencyHelpers$$ExternalSyntheticLambda0;->(Ljava/lang/String;)V
HSPLandroidx/emoji2/text/ConcurrencyHelpers$$ExternalSyntheticLambda0;->newThread(Ljava/lang/Runnable;)Ljava/lang/Thread;
+HSPLandroidx/emoji2/text/ConcurrencyHelpers$Handler28Impl$$ExternalSyntheticApiModelOutline0;->m(Landroid/os/Looper;)Landroid/os/Handler;
HSPLandroidx/emoji2/text/ConcurrencyHelpers$Handler28Impl;->createAsync(Landroid/os/Looper;)Landroid/os/Handler;
-HSPLandroidx/emoji2/text/ConcurrencyHelpers;->$r8$lambda$rm7NN8F9tEuy2Vr8i0fl8_hnx_A(Ljava/lang/String;Ljava/lang/Runnable;)Ljava/lang/Thread;
+HSPLandroidx/emoji2/text/ConcurrencyHelpers;->$r8$lambda$hDWNjMgTS47ccxPkL8ebwFGVHg4(Ljava/lang/String;Ljava/lang/Runnable;)Ljava/lang/Thread;
HSPLandroidx/emoji2/text/ConcurrencyHelpers;->createBackgroundPriorityExecutor(Ljava/lang/String;)Ljava/util/concurrent/ThreadPoolExecutor;
HSPLandroidx/emoji2/text/ConcurrencyHelpers;->lambda$createBackgroundPriorityExecutor$0(Ljava/lang/String;Ljava/lang/Runnable;)Ljava/lang/Thread;
HSPLandroidx/emoji2/text/ConcurrencyHelpers;->mainHandlerAsync()Landroid/os/Handler;
@@ -2048,7 +2309,6 @@ HSPLandroidx/emoji2/text/EmojiCompat;->isInitialized()Z
HSPLandroidx/emoji2/text/EmojiCompat;->load()V
HSPLandroidx/emoji2/text/EmojiCompat;->loadMetadata()V
HSPLandroidx/emoji2/text/EmojiCompat;->onMetadataLoadFailed(Ljava/lang/Throwable;)V
-HSPLandroidx/emoji2/text/EmojiCompat;->updateEditorInfo(Landroid/view/inputmethod/EditorInfo;)V
HSPLandroidx/emoji2/text/EmojiCompatInitializer$1;->(Landroidx/emoji2/text/EmojiCompatInitializer;Landroidx/lifecycle/Lifecycle;)V
HSPLandroidx/emoji2/text/EmojiCompatInitializer$1;->onCreate(Landroidx/lifecycle/LifecycleOwner;)V
HSPLandroidx/emoji2/text/EmojiCompatInitializer$1;->onResume(Landroidx/lifecycle/LifecycleOwner;)V
@@ -2056,7 +2316,7 @@ HSPLandroidx/emoji2/text/EmojiCompatInitializer$1;->onStart(Landroidx/lifecycle/
HSPLandroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultConfig;->(Landroid/content/Context;)V
HSPLandroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader$$ExternalSyntheticLambda0;->(Landroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader;Landroidx/emoji2/text/EmojiCompat$MetadataRepoLoaderCallback;Ljava/util/concurrent/ThreadPoolExecutor;)V
HSPLandroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader$$ExternalSyntheticLambda0;->run()V
-HSPLandroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader;->$r8$lambda$2V1iWTiAwNxOBlVvz73bbuEdzIw(Landroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader;Landroidx/emoji2/text/EmojiCompat$MetadataRepoLoaderCallback;Ljava/util/concurrent/ThreadPoolExecutor;)V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader;->$r8$lambda$55Ivg7v_3m1eazHc6HmoU-ZdULY(Landroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader;Landroidx/emoji2/text/EmojiCompat$MetadataRepoLoaderCallback;Ljava/util/concurrent/ThreadPoolExecutor;)V
HSPLandroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader;->(Landroid/content/Context;)V
HSPLandroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader;->doLoad(Landroidx/emoji2/text/EmojiCompat$MetadataRepoLoaderCallback;Ljava/util/concurrent/ThreadPoolExecutor;)V
HSPLandroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader;->lambda$load$0(Landroidx/emoji2/text/EmojiCompat$MetadataRepoLoaderCallback;Ljava/util/concurrent/ThreadPoolExecutor;)V
@@ -2084,21 +2344,15 @@ HSPLandroidx/emoji2/text/SpannableBuilder;->removeSpan(Ljava/lang/Object;)V
HSPLandroidx/emoji2/text/SpannableBuilder;->setSpan(Ljava/lang/Object;III)V
HSPLandroidx/emoji2/viewsintegration/EmojiEditTextHelper$HelperInternal19;->(Landroid/widget/EditText;Z)V
HSPLandroidx/emoji2/viewsintegration/EmojiEditTextHelper$HelperInternal19;->getKeyListener(Landroid/text/method/KeyListener;)Landroid/text/method/KeyListener;
-HSPLandroidx/emoji2/viewsintegration/EmojiEditTextHelper$HelperInternal19;->onCreateInputConnection(Landroid/view/inputmethod/InputConnection;Landroid/view/inputmethod/EditorInfo;)Landroid/view/inputmethod/InputConnection;
HSPLandroidx/emoji2/viewsintegration/EmojiEditTextHelper$HelperInternal19;->setEnabled(Z)V
HSPLandroidx/emoji2/viewsintegration/EmojiEditTextHelper$HelperInternal;->()V
HSPLandroidx/emoji2/viewsintegration/EmojiEditTextHelper;->(Landroid/widget/EditText;Z)V
HSPLandroidx/emoji2/viewsintegration/EmojiEditTextHelper;->getKeyListener(Landroid/text/method/KeyListener;)Landroid/text/method/KeyListener;
-HSPLandroidx/emoji2/viewsintegration/EmojiEditTextHelper;->onCreateInputConnection(Landroid/view/inputmethod/InputConnection;Landroid/view/inputmethod/EditorInfo;)Landroid/view/inputmethod/InputConnection;
HSPLandroidx/emoji2/viewsintegration/EmojiEditTextHelper;->setEnabled(Z)V
HSPLandroidx/emoji2/viewsintegration/EmojiEditableFactory;->()V
HSPLandroidx/emoji2/viewsintegration/EmojiEditableFactory;->()V
HSPLandroidx/emoji2/viewsintegration/EmojiEditableFactory;->getInstance()Landroid/text/Editable$Factory;
HSPLandroidx/emoji2/viewsintegration/EmojiEditableFactory;->newEditable(Ljava/lang/CharSequence;)Landroid/text/Editable;
-HSPLandroidx/emoji2/viewsintegration/EmojiInputConnection$EmojiCompatDeleteHelper;->()V
-HSPLandroidx/emoji2/viewsintegration/EmojiInputConnection$EmojiCompatDeleteHelper;->updateEditorInfoAttrs(Landroid/view/inputmethod/EditorInfo;)V
-HSPLandroidx/emoji2/viewsintegration/EmojiInputConnection;->(Landroid/widget/TextView;Landroid/view/inputmethod/InputConnection;Landroid/view/inputmethod/EditorInfo;)V
-HSPLandroidx/emoji2/viewsintegration/EmojiInputConnection;->(Landroid/widget/TextView;Landroid/view/inputmethod/InputConnection;Landroid/view/inputmethod/EditorInfo;Landroidx/emoji2/viewsintegration/EmojiInputConnection$EmojiCompatDeleteHelper;)V
HSPLandroidx/emoji2/viewsintegration/EmojiInputFilter;->(Landroid/widget/TextView;)V
HSPLandroidx/emoji2/viewsintegration/EmojiInputFilter;->filter(Ljava/lang/CharSequence;IILandroid/text/Spanned;II)Ljava/lang/CharSequence;
HSPLandroidx/emoji2/viewsintegration/EmojiKeyListener$EmojiCompatHandleKeyDownHelper;->()V
@@ -2165,7 +2419,7 @@ HSPLandroidx/fragment/app/Fragment$9;->onPreAttached()V
HSPLandroidx/fragment/app/Fragment$AnimationInfo;->()V
HSPLandroidx/fragment/app/Fragment$OnPreAttachedListener;->()V
HSPLandroidx/fragment/app/Fragment$OnPreAttachedListener;->(Landroidx/fragment/app/Fragment$1;)V
-HSPLandroidx/fragment/app/Fragment;->$r8$lambda$Cl7MxTaA6NVZ8I5KAGBxRTLl1sc(Landroidx/fragment/app/Fragment;)V
+HSPLandroidx/fragment/app/Fragment;->$r8$lambda$fb2iz_9i-0AeQEy2DZBRxlwOv0g(Landroidx/fragment/app/Fragment;)V
HSPLandroidx/fragment/app/Fragment;->()V
HSPLandroidx/fragment/app/Fragment;->()V
HSPLandroidx/fragment/app/Fragment;->(I)V
@@ -2276,7 +2530,7 @@ HSPLandroidx/fragment/app/FragmentActivity$HostCallbacks;->removeOnConfiguration
HSPLandroidx/fragment/app/FragmentActivity$HostCallbacks;->removeOnMultiWindowModeChangedListener(Landroidx/core/util/Consumer;)V
HSPLandroidx/fragment/app/FragmentActivity$HostCallbacks;->removeOnPictureInPictureModeChangedListener(Landroidx/core/util/Consumer;)V
HSPLandroidx/fragment/app/FragmentActivity$HostCallbacks;->removeOnTrimMemoryListener(Landroidx/core/util/Consumer;)V
-HSPLandroidx/fragment/app/FragmentActivity;->$r8$lambda$euPNEtWNfVdMY89Jt5kWt_WEHqw(Landroidx/fragment/app/FragmentActivity;Landroid/content/Context;)V
+HSPLandroidx/fragment/app/FragmentActivity;->$r8$lambda$4ROHaS4O6f2WoPhKvhOMRg_7Bzo(Landroidx/fragment/app/FragmentActivity;Landroid/content/Context;)V
HSPLandroidx/fragment/app/FragmentActivity;->()V
HSPLandroidx/fragment/app/FragmentActivity;->dispatchFragmentsOnCreateView(Landroid/view/View;Ljava/lang/String;Landroid/content/Context;Landroid/util/AttributeSet;)Landroid/view/View;
HSPLandroidx/fragment/app/FragmentActivity;->getSupportFragmentManager()Landroidx/fragment/app/FragmentManager;
@@ -2297,7 +2551,6 @@ HSPLandroidx/fragment/app/FragmentActivity;->onStart()V
HSPLandroidx/fragment/app/FragmentActivity;->onStateNotSaved()V
HSPLandroidx/fragment/app/FragmentActivity;->onStop()V
HSPLandroidx/fragment/app/FragmentActivity;->supportPostponeEnterTransition()V
-HSPLandroidx/fragment/app/FragmentActivity;->supportStartPostponedEnterTransition()V
HSPLandroidx/fragment/app/FragmentContainer;->()V
HSPLandroidx/fragment/app/FragmentContainer;->instantiate(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroidx/fragment/app/Fragment;
HSPLandroidx/fragment/app/FragmentContainerView;->(Landroid/content/Context;)V
@@ -2542,8 +2795,8 @@ HSPLandroidx/fragment/app/SpecialEffectsController$Operation;->getLifecycleImpac
HSPLandroidx/fragment/app/SpecialEffectsController$Operation;->isCanceled()Z
HSPLandroidx/fragment/app/SpecialEffectsController$Operation;->mergeWith(Landroidx/fragment/app/SpecialEffectsController$Operation$State;Landroidx/fragment/app/SpecialEffectsController$Operation$LifecycleImpact;)V
HSPLandroidx/fragment/app/SpecialEffectsController$WhenMappings;->()V
-HSPLandroidx/fragment/app/SpecialEffectsController;->$r8$lambda$HzA9s4aFoOsiJ_WkKfUvVoTfNJY(Landroidx/fragment/app/SpecialEffectsController;Landroidx/fragment/app/SpecialEffectsController$FragmentStateManagerOperation;)V
-HSPLandroidx/fragment/app/SpecialEffectsController;->$r8$lambda$PcxTk79tvtTaJMirbpJm6o9rlVo(Landroidx/fragment/app/SpecialEffectsController;Landroidx/fragment/app/SpecialEffectsController$FragmentStateManagerOperation;)V
+HSPLandroidx/fragment/app/SpecialEffectsController;->$r8$lambda$TeCmoVW0hctjg0BNHLP6mPbAs5U(Landroidx/fragment/app/SpecialEffectsController;Landroidx/fragment/app/SpecialEffectsController$FragmentStateManagerOperation;)V
+HSPLandroidx/fragment/app/SpecialEffectsController;->$r8$lambda$ahdwWRJghvCwX2xG1GsepYfILrY(Landroidx/fragment/app/SpecialEffectsController;Landroidx/fragment/app/SpecialEffectsController$FragmentStateManagerOperation;)V
HSPLandroidx/fragment/app/SpecialEffectsController;->()V
HSPLandroidx/fragment/app/SpecialEffectsController;->(Landroid/view/ViewGroup;)V
HSPLandroidx/fragment/app/SpecialEffectsController;->enqueue$lambda$4$lambda$2(Landroidx/fragment/app/SpecialEffectsController;Landroidx/fragment/app/SpecialEffectsController$FragmentStateManagerOperation;)V
@@ -2702,7 +2955,6 @@ HSPLandroidx/lifecycle/ProcessLifecycleInitializer;->create(Landroid/content/Con
HSPLandroidx/lifecycle/ProcessLifecycleInitializer;->create(Landroid/content/Context;)Ljava/lang/Object;
HSPLandroidx/lifecycle/ProcessLifecycleInitializer;->dependencies()Ljava/util/List;
HSPLandroidx/lifecycle/ProcessLifecycleOwner$$ExternalSyntheticLambda0;->(Landroidx/lifecycle/ProcessLifecycleOwner;)V
-HSPLandroidx/lifecycle/ProcessLifecycleOwner$Api29Impl$$ExternalSyntheticApiModelOutline0;->m(Landroid/app/Activity;Landroid/app/Application$ActivityLifecycleCallbacks;)V
HSPLandroidx/lifecycle/ProcessLifecycleOwner$Api29Impl;->()V
HSPLandroidx/lifecycle/ProcessLifecycleOwner$Api29Impl;->()V
HSPLandroidx/lifecycle/ProcessLifecycleOwner$Api29Impl;->registerActivityLifecycleCallbacks(Landroid/app/Activity;Landroid/app/Application$ActivityLifecycleCallbacks;)V
@@ -2736,6 +2988,7 @@ HSPLandroidx/lifecycle/ReportFragment$Companion;->()V
HSPLandroidx/lifecycle/ReportFragment$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
HSPLandroidx/lifecycle/ReportFragment$Companion;->dispatch$lifecycle_runtime_release(Landroid/app/Activity;Landroidx/lifecycle/Lifecycle$Event;)V
HSPLandroidx/lifecycle/ReportFragment$Companion;->injectIfNeededIn(Landroid/app/Activity;)V
+HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks$Companion$$ExternalSyntheticApiModelOutline0;->m(Landroid/app/Activity;Landroid/app/Application$ActivityLifecycleCallbacks;)V
HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks$Companion;->()V
HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks$Companion;->registerIn(Landroid/app/Activity;)V
@@ -2903,7 +3156,6 @@ HSPLandroidx/media/MediaSessionManagerImplApi21;->(Landroid/content/Contex
HSPLandroidx/media/MediaSessionManagerImplApi21;->hasMediaControlPermission(Landroidx/media/MediaSessionManager$RemoteUserInfoImpl;)Z
HSPLandroidx/media/MediaSessionManagerImplApi21;->isTrustedForMediaControl(Landroidx/media/MediaSessionManager$RemoteUserInfoImpl;)Z
HSPLandroidx/media/MediaSessionManagerImplApi28$RemoteUserInfoImplApi28$$ExternalSyntheticApiModelOutline0;->m(Ljava/lang/String;II)Landroid/media/session/MediaSessionManager$RemoteUserInfo;
-HSPLandroidx/media/MediaSessionManagerImplApi28$RemoteUserInfoImplApi28$$ExternalSyntheticApiModelOutline1;->m()V
HSPLandroidx/media/MediaSessionManagerImplApi28$RemoteUserInfoImplApi28;->(Ljava/lang/String;II)V
HSPLandroidx/media/MediaSessionManagerImplApi28;->(Landroid/content/Context;)V
HSPLandroidx/media/MediaSessionManagerImplApi28;->isTrustedForMediaControl(Landroidx/media/MediaSessionManager$RemoteUserInfoImpl;)Z
@@ -3250,8 +3502,8 @@ HSPLandroidx/media3/common/util/ListenerSet$ListenerHolder;->(Ljava/lang/O
HSPLandroidx/media3/common/util/ListenerSet$ListenerHolder;->equals(Ljava/lang/Object;)Z
HSPLandroidx/media3/common/util/ListenerSet$ListenerHolder;->invoke(ILandroidx/media3/common/util/ListenerSet$Event;)V
HSPLandroidx/media3/common/util/ListenerSet$ListenerHolder;->iterationFinished(Landroidx/media3/common/util/ListenerSet$IterationFinishedEvent;)V
-HSPLandroidx/media3/common/util/ListenerSet;->$r8$lambda$AlaP-gu7Lfe4GahLPmVnd_l2pLA(Ljava/util/concurrent/CopyOnWriteArraySet;ILandroidx/media3/common/util/ListenerSet$Event;)V
-HSPLandroidx/media3/common/util/ListenerSet;->$r8$lambda$bio3pd12v5B_9b5UeFaPn9XBQ90(Landroidx/media3/common/util/ListenerSet;Landroid/os/Message;)Z
+HSPLandroidx/media3/common/util/ListenerSet;->$r8$lambda$Maws-DUsVhcg5uFQgolq5mcQJiM(Ljava/util/concurrent/CopyOnWriteArraySet;ILandroidx/media3/common/util/ListenerSet$Event;)V
+HSPLandroidx/media3/common/util/ListenerSet;->$r8$lambda$rFcF5Pkb99AL585p5-2u78YfNkY(Landroidx/media3/common/util/ListenerSet;Landroid/os/Message;)Z
HSPLandroidx/media3/common/util/ListenerSet;->(Landroid/os/Looper;Landroidx/media3/common/util/Clock;Landroidx/media3/common/util/ListenerSet$IterationFinishedEvent;)V
HSPLandroidx/media3/common/util/ListenerSet;->(Ljava/util/concurrent/CopyOnWriteArraySet;Landroid/os/Looper;Landroidx/media3/common/util/Clock;Landroidx/media3/common/util/ListenerSet$IterationFinishedEvent;Z)V
HSPLandroidx/media3/common/util/ListenerSet;->add(Ljava/lang/Object;)V
@@ -3272,11 +3524,11 @@ HSPLandroidx/media3/common/util/Log;->getThrowableString(Ljava/lang/Throwable;)L
HSPLandroidx/media3/common/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)V
HSPLandroidx/media3/common/util/NetworkTypeObserver$$ExternalSyntheticLambda0;->(Landroidx/media3/common/util/NetworkTypeObserver;Landroidx/media3/common/util/NetworkTypeObserver$Listener;)V
HSPLandroidx/media3/common/util/NetworkTypeObserver$$ExternalSyntheticLambda0;->run()V
-HSPLandroidx/media3/common/util/NetworkTypeObserver$Api31$$ExternalSyntheticApiModelOutline0;->m(Landroid/telephony/TelephonyManager;Ljava/util/concurrent/Executor;Landroid/telephony/TelephonyCallback;)V
+HSPLandroidx/media3/common/util/NetworkTypeObserver$Api31$$ExternalSyntheticApiModelOutline1;->m(Landroid/telephony/TelephonyManager;Ljava/util/concurrent/Executor;Landroid/telephony/TelephonyCallback;)V
HSPLandroidx/media3/common/util/NetworkTypeObserver$Receiver;->(Landroidx/media3/common/util/NetworkTypeObserver;)V
HSPLandroidx/media3/common/util/NetworkTypeObserver$Receiver;->(Landroidx/media3/common/util/NetworkTypeObserver;Landroidx/media3/common/util/NetworkTypeObserver$1;)V
HSPLandroidx/media3/common/util/NetworkTypeObserver$Receiver;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V
-HSPLandroidx/media3/common/util/NetworkTypeObserver;->$r8$lambda$DoEVJeYUKRUR6jupXFdxPNaPJ8k(Landroidx/media3/common/util/NetworkTypeObserver;Landroidx/media3/common/util/NetworkTypeObserver$Listener;)V
+HSPLandroidx/media3/common/util/NetworkTypeObserver;->$r8$lambda$op5b6rwTRtpp14FG2_2m__BFiFs(Landroidx/media3/common/util/NetworkTypeObserver;Landroidx/media3/common/util/NetworkTypeObserver$Listener;)V
HSPLandroidx/media3/common/util/NetworkTypeObserver;->(Landroid/content/Context;)V
HSPLandroidx/media3/common/util/NetworkTypeObserver;->access$100(Landroid/content/Context;)I
HSPLandroidx/media3/common/util/NetworkTypeObserver;->access$200(Landroidx/media3/common/util/NetworkTypeObserver;I)V
@@ -3336,7 +3588,7 @@ HSPLandroidx/media3/common/util/Util;->shouldShowPlayButton(Landroidx/media3/com
HSPLandroidx/media3/common/util/Util;->usToMs(J)J
HSPLandroidx/media3/datasource/DataSourceBitmapLoader$$ExternalSyntheticLambda0;->()V
HSPLandroidx/media3/datasource/DataSourceBitmapLoader$$ExternalSyntheticLambda0;->get()Ljava/lang/Object;
-HSPLandroidx/media3/datasource/DataSourceBitmapLoader;->$r8$lambda$5oU0w6Wyi1QAQIwNfmTcRBDVUHo()Lcom/google/common/util/concurrent/ListeningExecutorService;
+HSPLandroidx/media3/datasource/DataSourceBitmapLoader;->$r8$lambda$ctPCnFN_Nvj3nel8GNv7G1INN2M()Lcom/google/common/util/concurrent/ListeningExecutorService;
HSPLandroidx/media3/datasource/DataSourceBitmapLoader;->()V
HSPLandroidx/media3/datasource/DataSourceBitmapLoader;->(Landroid/content/Context;)V
HSPLandroidx/media3/datasource/DataSourceBitmapLoader;->(Lcom/google/common/util/concurrent/ListeningExecutorService;Landroidx/media3/datasource/DataSource$Factory;)V
@@ -3347,7 +3599,6 @@ HSPLandroidx/media3/datasource/DefaultHttpDataSource$Factory;->()V
HSPLandroidx/media3/datasource/HttpDataSource$RequestProperties;->()V
HSPLandroidx/media3/decoder/Buffer;->()V
HSPLandroidx/media3/decoder/CryptoInfo$PatternHolderV24$$ExternalSyntheticApiModelOutline0;->m(II)Landroid/media/MediaCodec$CryptoInfo$Pattern;
-HSPLandroidx/media3/decoder/CryptoInfo$PatternHolderV24$$ExternalSyntheticApiModelOutline1;->m()V
HSPLandroidx/media3/decoder/CryptoInfo$PatternHolderV24;->(Landroid/media/MediaCodec$CryptoInfo;)V
HSPLandroidx/media3/decoder/CryptoInfo$PatternHolderV24;->(Landroid/media/MediaCodec$CryptoInfo;Landroidx/media3/decoder/CryptoInfo$1;)V
HSPLandroidx/media3/decoder/CryptoInfo;->()V
@@ -3397,11 +3648,11 @@ HSPLandroidx/media3/exoplayer/DefaultRenderersFactory;->createRenderers(Landroid
HSPLandroidx/media3/exoplayer/DefaultRenderersFactory;->getCodecAdapterFactory()Landroidx/media3/exoplayer/mediacodec/MediaCodecAdapter$Factory;
HSPLandroidx/media3/exoplayer/ExoPlayer$Builder$$ExternalSyntheticLambda0;->(Landroidx/media3/exoplayer/LoadControl;)V
HSPLandroidx/media3/exoplayer/ExoPlayer$Builder$$ExternalSyntheticLambda0;->get()Ljava/lang/Object;
-HSPLandroidx/media3/exoplayer/ExoPlayer$Builder$$ExternalSyntheticLambda1;->(Landroid/content/Context;)V
+HSPLandroidx/media3/exoplayer/ExoPlayer$Builder$$ExternalSyntheticLambda1;->(Landroidx/media3/exoplayer/source/MediaSource$Factory;)V
+HSPLandroidx/media3/exoplayer/ExoPlayer$Builder$$ExternalSyntheticLambda1;->get()Ljava/lang/Object;
HSPLandroidx/media3/exoplayer/ExoPlayer$Builder$$ExternalSyntheticLambda2;->(Landroid/content/Context;)V
-HSPLandroidx/media3/exoplayer/ExoPlayer$Builder$$ExternalSyntheticLambda3;->(Landroidx/media3/exoplayer/RenderersFactory;)V
-HSPLandroidx/media3/exoplayer/ExoPlayer$Builder$$ExternalSyntheticLambda3;->get()Ljava/lang/Object;
-HSPLandroidx/media3/exoplayer/ExoPlayer$Builder$$ExternalSyntheticLambda4;->(Landroidx/media3/exoplayer/source/MediaSource$Factory;)V
+HSPLandroidx/media3/exoplayer/ExoPlayer$Builder$$ExternalSyntheticLambda3;->(Landroid/content/Context;)V
+HSPLandroidx/media3/exoplayer/ExoPlayer$Builder$$ExternalSyntheticLambda4;->(Landroidx/media3/exoplayer/RenderersFactory;)V
HSPLandroidx/media3/exoplayer/ExoPlayer$Builder$$ExternalSyntheticLambda4;->get()Ljava/lang/Object;
HSPLandroidx/media3/exoplayer/ExoPlayer$Builder$$ExternalSyntheticLambda5;->(Landroid/content/Context;)V
HSPLandroidx/media3/exoplayer/ExoPlayer$Builder$$ExternalSyntheticLambda5;->get()Ljava/lang/Object;
@@ -3410,11 +3661,11 @@ HSPLandroidx/media3/exoplayer/ExoPlayer$Builder$$ExternalSyntheticLambda7;->get()Ljava/lang/Object;
HSPLandroidx/media3/exoplayer/ExoPlayer$Builder$$ExternalSyntheticLambda8;->()V
HSPLandroidx/media3/exoplayer/ExoPlayer$Builder$$ExternalSyntheticLambda8;->apply(Ljava/lang/Object;)Ljava/lang/Object;
-HSPLandroidx/media3/exoplayer/ExoPlayer$Builder;->$r8$lambda$E_UuRuiJ7XuGPeWDLUXM247HnO4(Landroidx/media3/exoplayer/LoadControl;)Landroidx/media3/exoplayer/LoadControl;
-HSPLandroidx/media3/exoplayer/ExoPlayer$Builder;->$r8$lambda$FplZJNis2C-ck82WaYRNg8j54tA(Landroid/content/Context;)Landroidx/media3/exoplayer/upstream/BandwidthMeter;
-HSPLandroidx/media3/exoplayer/ExoPlayer$Builder;->$r8$lambda$MbwKRuXmGQh3ZURCZek3Rj0CAIc(Landroidx/media3/exoplayer/RenderersFactory;)Landroidx/media3/exoplayer/RenderersFactory;
-HSPLandroidx/media3/exoplayer/ExoPlayer$Builder;->$r8$lambda$PFgrmzXy8dtRca-zfooecXIZLiM(Landroidx/media3/exoplayer/source/MediaSource$Factory;)Landroidx/media3/exoplayer/source/MediaSource$Factory;
-HSPLandroidx/media3/exoplayer/ExoPlayer$Builder;->$r8$lambda$PLv_OFGgiO4MKR7PBORW2kAfOy8(Landroid/content/Context;)Landroidx/media3/exoplayer/trackselection/TrackSelector;
+HSPLandroidx/media3/exoplayer/ExoPlayer$Builder;->$r8$lambda$1S4JTYE6v7ASEpYX-zQPTquCFzM(Landroidx/media3/exoplayer/RenderersFactory;)Landroidx/media3/exoplayer/RenderersFactory;
+HSPLandroidx/media3/exoplayer/ExoPlayer$Builder;->$r8$lambda$HekyiBTTjarrX819XM5j8sFRos8(Landroidx/media3/exoplayer/LoadControl;)Landroidx/media3/exoplayer/LoadControl;
+HSPLandroidx/media3/exoplayer/ExoPlayer$Builder;->$r8$lambda$HqY2oZARF3vhlKA0gKaeZ2l_Oa8(Landroidx/media3/exoplayer/source/MediaSource$Factory;)Landroidx/media3/exoplayer/source/MediaSource$Factory;
+HSPLandroidx/media3/exoplayer/ExoPlayer$Builder;->$r8$lambda$WPXsxQz0xLwRE7OHWiDerxu51BU(Landroid/content/Context;)Landroidx/media3/exoplayer/upstream/BandwidthMeter;
+HSPLandroidx/media3/exoplayer/ExoPlayer$Builder;->$r8$lambda$xUvto8h1vMLVIth0_snkoRcg_Fw(Landroid/content/Context;)Landroidx/media3/exoplayer/trackselection/TrackSelector;
HSPLandroidx/media3/exoplayer/ExoPlayer$Builder;->(Landroid/content/Context;)V
HSPLandroidx/media3/exoplayer/ExoPlayer$Builder;->(Landroid/content/Context;Lcom/google/common/base/Supplier;Lcom/google/common/base/Supplier;)V
HSPLandroidx/media3/exoplayer/ExoPlayer$Builder;->(Landroid/content/Context;Lcom/google/common/base/Supplier;Lcom/google/common/base/Supplier;Lcom/google/common/base/Supplier;Lcom/google/common/base/Supplier;Lcom/google/common/base/Supplier;Lcom/google/common/base/Function;)V
@@ -3429,20 +3680,20 @@ HSPLandroidx/media3/exoplayer/ExoPlayer$Builder;->setLoadControl(Landroidx/media
HSPLandroidx/media3/exoplayer/ExoPlayer$Builder;->setMediaSourceFactory(Landroidx/media3/exoplayer/source/MediaSource$Factory;)Landroidx/media3/exoplayer/ExoPlayer$Builder;
HSPLandroidx/media3/exoplayer/ExoPlayer$Builder;->setRenderersFactory(Landroidx/media3/exoplayer/RenderersFactory;)Landroidx/media3/exoplayer/ExoPlayer$Builder;
HSPLandroidx/media3/exoplayer/ExoPlayerImpl$$ExternalSyntheticApiModelOutline0;->m(Landroid/media/AudioManager;I)[Landroid/media/AudioDeviceInfo;
-HSPLandroidx/media3/exoplayer/ExoPlayerImpl$$ExternalSyntheticLambda16;->(Landroidx/media3/common/AudioAttributes;)V
-HSPLandroidx/media3/exoplayer/ExoPlayerImpl$$ExternalSyntheticLambda16;->invoke(Ljava/lang/Object;)V
-HSPLandroidx/media3/exoplayer/ExoPlayerImpl$$ExternalSyntheticLambda18;->(Landroidx/media3/exoplayer/ExoPlayerImpl;)V
-HSPLandroidx/media3/exoplayer/ExoPlayerImpl$$ExternalSyntheticLambda18;->invoke(Ljava/lang/Object;Landroidx/media3/common/FlagSet;)V
-HSPLandroidx/media3/exoplayer/ExoPlayerImpl$$ExternalSyntheticLambda19;->(Landroidx/media3/exoplayer/ExoPlayerImpl;)V
-HSPLandroidx/media3/exoplayer/ExoPlayerImpl$Api23$$ExternalSyntheticApiModelOutline0;->m(Landroid/media/AudioDeviceInfo;)I
+HSPLandroidx/media3/exoplayer/ExoPlayerImpl$$ExternalSyntheticLambda7;->(Landroidx/media3/exoplayer/ExoPlayerImpl;)V
+HSPLandroidx/media3/exoplayer/ExoPlayerImpl$$ExternalSyntheticLambda7;->invoke(Ljava/lang/Object;Landroidx/media3/common/FlagSet;)V
+HSPLandroidx/media3/exoplayer/ExoPlayerImpl$$ExternalSyntheticLambda8;->(Landroidx/media3/exoplayer/ExoPlayerImpl;)V
+HSPLandroidx/media3/exoplayer/ExoPlayerImpl$$ExternalSyntheticLambda9;->(Landroidx/media3/common/AudioAttributes;)V
+HSPLandroidx/media3/exoplayer/ExoPlayerImpl$$ExternalSyntheticLambda9;->invoke(Ljava/lang/Object;)V
+HSPLandroidx/media3/exoplayer/ExoPlayerImpl$Api23$$ExternalSyntheticApiModelOutline1;->m(Landroid/media/AudioDeviceInfo;)I
HSPLandroidx/media3/exoplayer/ExoPlayerImpl$Api31$$ExternalSyntheticApiModelOutline0;->m()Landroid/media/metrics/LogSessionId;
HSPLandroidx/media3/exoplayer/ExoPlayerImpl$Api31;->registerMediaMetricsListener(Landroid/content/Context;Landroidx/media3/exoplayer/ExoPlayerImpl;Z)Landroidx/media3/exoplayer/analytics/PlayerId;
HSPLandroidx/media3/exoplayer/ExoPlayerImpl$ComponentListener;->(Landroidx/media3/exoplayer/ExoPlayerImpl;)V
HSPLandroidx/media3/exoplayer/ExoPlayerImpl$ComponentListener;->(Landroidx/media3/exoplayer/ExoPlayerImpl;Landroidx/media3/exoplayer/ExoPlayerImpl$1;)V
HSPLandroidx/media3/exoplayer/ExoPlayerImpl$FrameMetadataListener;->()V
HSPLandroidx/media3/exoplayer/ExoPlayerImpl$FrameMetadataListener;->(Landroidx/media3/exoplayer/ExoPlayerImpl$1;)V
-HSPLandroidx/media3/exoplayer/ExoPlayerImpl;->$r8$lambda$JtzxNRDnWahnXIvwxzcFksVgOkU(Landroidx/media3/exoplayer/ExoPlayerImpl;Landroidx/media3/common/Player$Listener;Landroidx/media3/common/FlagSet;)V
-HSPLandroidx/media3/exoplayer/ExoPlayerImpl;->$r8$lambda$Kk09jzs0OKewoiSfI3wTI0zS6O4(Landroidx/media3/common/AudioAttributes;Landroidx/media3/common/Player$Listener;)V
+HSPLandroidx/media3/exoplayer/ExoPlayerImpl;->$r8$lambda$SA7gbpEMwf_Ft8S0LNQ1m_I4CcI(Landroidx/media3/exoplayer/ExoPlayerImpl;Landroidx/media3/common/Player$Listener;Landroidx/media3/common/FlagSet;)V
+HSPLandroidx/media3/exoplayer/ExoPlayerImpl;->$r8$lambda$j8fsQ-0x1CDXust9j-5G2ehEs6Q(Landroidx/media3/common/AudioAttributes;Landroidx/media3/common/Player$Listener;)V
HSPLandroidx/media3/exoplayer/ExoPlayerImpl;->()V
HSPLandroidx/media3/exoplayer/ExoPlayerImpl;->(Landroidx/media3/exoplayer/ExoPlayer$Builder;Landroidx/media3/common/Player;)V
HSPLandroidx/media3/exoplayer/ExoPlayerImpl;->addAnalyticsListener(Landroidx/media3/exoplayer/analytics/AnalyticsListener;)V
@@ -3540,20 +3791,20 @@ HSPLandroidx/media3/exoplayer/analytics/AnalyticsListener$Events;->contains(I)Z
HSPLandroidx/media3/exoplayer/analytics/AnalyticsListener$Events;->get(I)I
HSPLandroidx/media3/exoplayer/analytics/AnalyticsListener$Events;->getEventTime(I)Landroidx/media3/exoplayer/analytics/AnalyticsListener$EventTime;
HSPLandroidx/media3/exoplayer/analytics/AnalyticsListener$Events;->size()I
-HSPLandroidx/media3/exoplayer/analytics/DefaultAnalyticsCollector$$ExternalSyntheticLambda13;->