Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bdcc9e3d8 | ||
|
|
daee747338 | ||
|
|
535a163f00 | ||
|
|
bd61b91722 |
@@ -1,9 +1,5 @@
|
||||
root = true
|
||||
|
||||
[*.{kt,kts}]
|
||||
[*.kt]
|
||||
indent_size = 2
|
||||
ij_kotlin_allow_trailing_comma_on_call_site = false
|
||||
ij_kotlin_allow_trailing_comma = false
|
||||
ktlint_code_style = intellij_idea
|
||||
twitter_compose_allowed_composition_locals=LocalExtendedColors
|
||||
ktlint_standard_class-naming = disabled
|
||||
twitter_compose_allowed_composition_locals=LocalExtendedColors
|
||||
6
.github/workflows/android.yml
vendored
@@ -18,14 +18,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: set up JDK 17
|
||||
- name: set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 17
|
||||
java-version: 11
|
||||
cache: gradle
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
|
||||
84
.github/workflows/diffuse.yml
vendored
@@ -1,84 +0,0 @@
|
||||
name: APK Diff
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
pull-requests: write # to comment on PR
|
||||
|
||||
jobs:
|
||||
assemble-base:
|
||||
if: ${{ github.repository != 'signalapp/Signal-Android' }}
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
ref: ${{ github.event.pull_request.base.sha }}
|
||||
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 17
|
||||
cache: gradle
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
- name: Cache base apk
|
||||
id: cache-base
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: diffuse-base.apk
|
||||
key: diffuse-${{ github.event.pull_request.base.sha }}
|
||||
|
||||
|
||||
- name: Build with Gradle
|
||||
if: steps.cache-base.outputs.cache-hit != 'true'
|
||||
run: ./gradlew assemblePlayProdRelease --parallel
|
||||
|
||||
- name: Copy base apk
|
||||
if: steps.cache-base.outputs.cache-hit != 'true'
|
||||
run: mv app/build/outputs/apk/playProd/release/*arm64*.apk diffuse-base.apk
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
clean: 'false'
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew assemblePlayProdRelease --parallel
|
||||
|
||||
- name: Copy PR apk
|
||||
run: mv app/build/outputs/apk/playProd/release/*arm64*.apk diffuse-new.apk
|
||||
|
||||
- id: diffuse
|
||||
uses: usefulness/diffuse-action@v1
|
||||
with:
|
||||
old-file-path: diffuse-base.apk
|
||||
new-file-path: diffuse-new.apk
|
||||
|
||||
- uses: peter-evans/find-comment@v2
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body-includes: Diffuse output
|
||||
|
||||
- uses: peter-evans/create-or-update-comment@v3
|
||||
with:
|
||||
body: |
|
||||
Diffuse output:
|
||||
|
||||
${{ steps.diffuse.outputs.diff-gh-comment }}
|
||||
edit-mode: replace
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: diffuse-output
|
||||
path: ${{ steps.diffuse.outputs.diff-file }}
|
||||
3
.gitignore
vendored
@@ -3,7 +3,6 @@ captures/
|
||||
project.properties
|
||||
keystore.debug.properties
|
||||
keystore.staging.properties
|
||||
nightly-url.txt
|
||||
.project
|
||||
.settings
|
||||
bin/
|
||||
@@ -29,4 +28,4 @@ jni/libspeex/.deps/
|
||||
pkcs11.password
|
||||
dev.keystore
|
||||
maps.key
|
||||
local/
|
||||
local/
|
||||
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "libwebp"]
|
||||
path = libwebp
|
||||
url = https://github.com/webmproject/libwebp.git
|
||||
7
.idea/codeStyles/Project.xml
generated
@@ -212,12 +212,5 @@
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
@@ -15,12 +15,6 @@ Truths which we believe to be self-evident:
|
||||
1. **There is no such thing as time.** Protocol ideas that require synchronized clocks are doomed to failure.
|
||||
|
||||
|
||||
## Building
|
||||
|
||||
1. You'll need to get the `libwebp` submodule after checking out the repository with `git submodule init && git submodule update`
|
||||
1. Most things are pretty straightforward, and opening the project in Android Studio should get you most of the way there.
|
||||
1. Depending on your configuration, you'll also likely need to install additional SDK Tool components, namely the versions of NDK and CMake we are currently using in our [Docker](https://github.com/signalapp/Signal-Android/blob/main/reproducible-builds/Dockerfile#L30) configuration.
|
||||
|
||||
## Issues
|
||||
|
||||
### Useful bug reports
|
||||
|
||||
134
app/build.gradle
@@ -3,18 +3,34 @@ import com.android.build.api.dsl.ManagedVirtualDevice
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.protobuf'
|
||||
id 'androidx.navigation.safeargs'
|
||||
id 'org.jlleitschuh.gradle.ktlint'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'app.cash.exhaustive'
|
||||
id 'kotlin-parcelize'
|
||||
id 'com.squareup.wire'
|
||||
id 'android-constants'
|
||||
id 'translations'
|
||||
id 'licenses'
|
||||
}
|
||||
|
||||
apply from: 'static-ips.gradle'
|
||||
|
||||
protobuf {
|
||||
protoc {
|
||||
artifact = 'com.google.protobuf:protoc:3.18.0'
|
||||
}
|
||||
generateProtoTasks {
|
||||
all().each { task ->
|
||||
task.builtins {
|
||||
java {
|
||||
option "lite"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wire {
|
||||
kotlin {
|
||||
javaInterop = true
|
||||
@@ -23,25 +39,22 @@ wire {
|
||||
sourcePath {
|
||||
srcDir 'src/main/protowire'
|
||||
}
|
||||
|
||||
protoPath {
|
||||
srcDir "${project.rootDir}/libsignal-service/src/main/protowire"
|
||||
}
|
||||
}
|
||||
|
||||
ktlint {
|
||||
version = "0.49.1"
|
||||
// Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507
|
||||
version = "0.47.1"
|
||||
}
|
||||
|
||||
def canonicalVersionCode = 1357
|
||||
def canonicalVersionName = "6.40.1"
|
||||
def canonicalVersionCode = 1272
|
||||
def canonicalVersionName = "6.22.7"
|
||||
|
||||
def postFixSize = 100
|
||||
def abiPostFix = ['universal' : 0,
|
||||
'armeabi-v7a' : 1,
|
||||
'arm64-v8a' : 2,
|
||||
'x86' : 3,
|
||||
'x86_64' : 4]
|
||||
def abiPostFix = ['universal' : 10,
|
||||
'armeabi-v7a' : 11,
|
||||
'arm64-v8a' : 12,
|
||||
'x86' : 13,
|
||||
'x86_64' : 14]
|
||||
|
||||
def keystores = [ 'debug' : loadKeystoreProperties('keystore.debug.properties') ]
|
||||
|
||||
@@ -82,7 +95,7 @@ android {
|
||||
testBuildType 'instrumentation'
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = signalKotlinJvmTarget
|
||||
jvmTarget = "11"
|
||||
freeCompilerArgs = ["-Xallow-result-return-type"]
|
||||
}
|
||||
|
||||
@@ -146,7 +159,7 @@ android {
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = '1.4.4'
|
||||
kotlinCompilerExtensionVersion = '1.3.2'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
@@ -169,11 +182,10 @@ android {
|
||||
buildConfigField "String", "STORAGE_URL", "\"https://storage.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_CDN3_URL", "\"https://cdn3.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_SVR2_URL", "\"https://svr2.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_SVR2_URL", "\"https://svr2.staging.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_SFU_URL", "\"https://sfu.voip.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_STAGING_SFU_URL", "\"https://sfu.staging.voip.signal.org\""
|
||||
buildConfigField "String[]", "SIGNAL_SFU_INTERNAL_NAMES", "new String[]{\"Test\", \"Staging\", \"Development\"}"
|
||||
@@ -184,14 +196,18 @@ android {
|
||||
buildConfigField "String[]", "SIGNAL_STORAGE_IPS", storage_ips
|
||||
buildConfigField "String[]", "SIGNAL_CDN_IPS", cdn_ips
|
||||
buildConfigField "String[]", "SIGNAL_CDN2_IPS", cdn2_ips
|
||||
buildConfigField "String[]", "SIGNAL_CDN3_IPS", cdn3_ips
|
||||
buildConfigField "String[]", "SIGNAL_KBS_IPS", kbs_ips
|
||||
buildConfigField "String[]", "SIGNAL_SFU_IPS", sfu_ips
|
||||
buildConfigField "String[]", "SIGNAL_CONTENT_PROXY_IPS", content_proxy_ips
|
||||
buildConfigField "String[]", "SIGNAL_CDSI_IPS", cdsi_ips
|
||||
buildConfigField "String[]", "SIGNAL_SVR2_IPS", svr2_ips
|
||||
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
|
||||
buildConfigField "String", "CDSI_MRENCLAVE", "\"0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57\""
|
||||
buildConfigField "String", "SVR2_MRENCLAVE", "\"6ee1042f9e20f880326686dd4ba50c25359f01e9f733eeba4382bca001d45094\""
|
||||
buildConfigField "String", "SVR2_MRENCLAVE", "\"dc9fd472a5a9c871a3c7f76f1af60aa9c1f314abf2e8d1e0c4ba25c8aaa2848c\""
|
||||
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"e18376436159cda3ad7a45d9320e382e4a497f26b0dca34d8eab0bd0139483b5\", " +
|
||||
"\"3a485adb56e2058ef7737764c738c4069dd62bc457637eafb6bbce1ce29ddb89\", " +
|
||||
"\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
|
||||
buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[] { new org.thoughtcrime.securesms.KbsEnclave(\"0cedba03535b41b67729ce9924185f831d7767928a1d1689acb689bc079c375f\", " +
|
||||
"\"187d2739d22be65e74b65f0055e74d31310e4267e5fac2b1246cc8beba81af39\", " +
|
||||
"\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\") }"
|
||||
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
|
||||
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P\""
|
||||
buildConfigField "String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AByD873dTilmOSG0TjKrvpeaKEsUmIO8Vx9BeMmftwUs9v7ikPwM8P3OHyT0+X3EUMZrSe9VUp26Wai51Q9I8mdk0hX/yo7CeFGJyzoOqn8e/i4Ygbn5HoAyXJx5eXfIbqpc0bIxzju4H/HOQeOpt6h742qii5u/cbwOhFZCsMIbElZTaeU+BWMBQiZHIGHT5IE0qCordQKZ5iPZom0HeFa8Yq0ShuEyAl0WINBiY6xE3H/9WnvzXBbMuuk//eRxXgzO8ieCeK8FwQNxbfXqZm6Ro1cMhCOF3u7xoX83QhpN\""
|
||||
@@ -212,8 +228,8 @@ android {
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
}
|
||||
resourceConfigurations += []
|
||||
|
||||
resConfigs autoResConfig()
|
||||
|
||||
splits {
|
||||
abi {
|
||||
@@ -319,27 +335,26 @@ android {
|
||||
play {
|
||||
dimension 'distribution'
|
||||
isDefault true
|
||||
buildConfigField "boolean", "MANAGES_APP_UPDATES", "false"
|
||||
buildConfigField "String", "APK_UPDATE_MANIFEST_URL", "null"
|
||||
ext.websiteUpdateUrl = "null"
|
||||
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
|
||||
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
|
||||
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"play\""
|
||||
}
|
||||
|
||||
website {
|
||||
dimension 'distribution'
|
||||
buildConfigField "boolean", "MANAGES_APP_UPDATES", "true"
|
||||
buildConfigField "String", "APK_UPDATE_MANIFEST_URL", "\"https://updates.signal.org/android/latest.json\""
|
||||
ext.websiteUpdateUrl = "https://updates.signal.org/android"
|
||||
buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
|
||||
buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\""
|
||||
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"website\""
|
||||
}
|
||||
|
||||
nightly {
|
||||
def apkUpdateManifestUrl = "<unset>"
|
||||
if (file("${project.rootDir}/nightly-url.txt").exists()) {
|
||||
apkUpdateManifestUrl = file("${project.rootDir}/nightly-url.txt").text.trim()
|
||||
}
|
||||
dimension 'distribution'
|
||||
versionNameSuffix "-nightly-untagged-${getDateSuffix()}"
|
||||
buildConfigField "boolean", "MANAGES_APP_UPDATES", "true"
|
||||
buildConfigField "String", "APK_UPDATE_MANIFEST_URL", "\"${apkUpdateManifestUrl}\""
|
||||
ext.websiteUpdateUrl = "null"
|
||||
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
|
||||
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
|
||||
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"nightly\""
|
||||
}
|
||||
|
||||
@@ -361,11 +376,14 @@ android {
|
||||
buildConfigField "String", "STORAGE_URL", "\"https://storage-staging.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn-staging.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2-staging.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_CDN3_URL", "\"https://cdn3-staging.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.staging.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_SVR2_URL", "\"https://svr2.staging.signal.org\""
|
||||
buildConfigField "String", "SVR2_MRENCLAVE", "\"a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95\""
|
||||
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"39963b736823d5780be96ab174869a9499d56d66497aa8f9b2244f777ebc366b\", " +
|
||||
"\"9dbc6855c198e04f21b5cc35df839fdcd51b53658454dfa3f817afefaffc95ef\", " +
|
||||
"\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
|
||||
buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[] { new org.thoughtcrime.securesms.KbsEnclave(\"dd6f66d397d9e8cf6ec6db238e59a7be078dd50e9715427b9c89b409ffe53f99\", " +
|
||||
"\"4200003414528c151e2dccafbc87aa6d3d66a5eb8f8c05979a6e97cb33cd493a\", " +
|
||||
"\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\") }"
|
||||
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
|
||||
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUj\""
|
||||
buildConfigField "String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AHILOIrFPXX9laLbalbA9+L1CXpSbM/bTJXZGZiuyK1JaI6dK5FHHWL6tWxmHKYAZTSYmElmJ5z2A5YcirjO/yfoemE03FItyaf8W1fE4p14hzb5qnrmfXUSiAIVrhaXVwIwSzH6RL/+EO8jFIjJ/YfExfJ8aBl48CKHgu1+A6kWynhttonvWWx6h7924mIzW0Czj2ROuh4LwQyZypex4GuOPW8sgIT21KNZaafgg+KbV7XM1x1tF3XA17B4uGUaDbDw2O+nR1+U5p6qHPzmJ7ggFjSN6Utu+35dS1sS0P9N\""
|
||||
@@ -404,9 +422,6 @@ android {
|
||||
tag = tag.substring(1)
|
||||
}
|
||||
output.versionNameOverride = tag
|
||||
output.outputFileName = output.outputFileName.replace(".apk", "-${output.versionNameOverride}.apk")
|
||||
} else {
|
||||
output.outputFileName = output.outputFileName.replace(".apk", "-${variant.versionName}.apk")
|
||||
}
|
||||
} else {
|
||||
output.outputFileName = output.outputFileName.replace(".apk", "-${variant.versionName}.apk")
|
||||
@@ -430,14 +445,6 @@ android {
|
||||
variant.setIgnore(true)
|
||||
}
|
||||
}
|
||||
|
||||
android.buildTypes.each {
|
||||
if (it.name != 'release') {
|
||||
sourceSets.findByName(it.name).java.srcDirs += "$projectDir/src/debug/java"
|
||||
} else {
|
||||
sourceSets.findByName(it.name).java.srcDirs += "$projectDir/src/release/java"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -461,7 +468,6 @@ dependencies {
|
||||
implementation libs.androidx.gridlayout
|
||||
implementation libs.androidx.exifinterface
|
||||
implementation libs.androidx.compose.rxjava3
|
||||
implementation libs.androidx.compose.runtime.livedata
|
||||
implementation libs.androidx.constraintlayout
|
||||
implementation libs.androidx.multidex
|
||||
implementation libs.androidx.navigation.fragment.ktx
|
||||
@@ -493,7 +499,7 @@ dependencies {
|
||||
implementation libs.google.play.services.maps
|
||||
implementation libs.google.play.services.auth
|
||||
|
||||
implementation libs.bundles.media3
|
||||
implementation libs.bundles.exoplayer
|
||||
|
||||
implementation libs.conscrypt.android
|
||||
implementation libs.signal.aesgcmprovider
|
||||
@@ -511,21 +517,25 @@ dependencies {
|
||||
implementation project(':sms-exporter')
|
||||
implementation project(':sticky-header-grid')
|
||||
implementation project(':photoview')
|
||||
implementation project(':glide-webp')
|
||||
|
||||
implementation libs.libsignal.android
|
||||
implementation libs.google.protobuf.javalite
|
||||
|
||||
implementation libs.mobilecoin
|
||||
implementation(libs.mobilecoin) {
|
||||
exclude group: 'com.google.protobuf'
|
||||
}
|
||||
|
||||
implementation libs.signal.ringrtc
|
||||
|
||||
implementation libs.leolin.shortcutbadger
|
||||
implementation libs.emilsjolander.stickylistheaders
|
||||
implementation libs.jpardogo.materialtabstrip
|
||||
implementation libs.apache.httpclient.android
|
||||
implementation libs.glide.glide
|
||||
implementation libs.roundedimageview
|
||||
implementation libs.materialish.progress
|
||||
implementation libs.greenrobot.eventbus
|
||||
implementation libs.waitingdots
|
||||
implementation libs.google.zxing.android.integration
|
||||
implementation libs.google.zxing.core
|
||||
implementation libs.google.flexbox
|
||||
@@ -568,9 +578,9 @@ dependencies {
|
||||
exclude group: 'com.google.protobuf', module: 'protobuf-java'
|
||||
}
|
||||
testImplementation testLibs.robolectric.shadows.multidex
|
||||
testImplementation (testLibs.bouncycastle.bcprov.jdk15on) { version { strictly "1.70" } } // Used by roboelectric
|
||||
testImplementation (testLibs.bouncycastle.bcpkix.jdk15on) { version { strictly "1.70" } } // Used by roboelectric
|
||||
testImplementation testLibs.conscrypt.openjdk.uber // Used by robolectric
|
||||
testImplementation (testLibs.bouncycastle.bcprov.jdk15on) {
|
||||
force = true
|
||||
}
|
||||
testImplementation testLibs.hamcrest.hamcrest
|
||||
testImplementation testLibs.mockk
|
||||
|
||||
@@ -666,24 +676,6 @@ tasks.withType(Test) {
|
||||
}
|
||||
}
|
||||
|
||||
project.tasks.configureEach { task ->
|
||||
if (task.name.toLowerCase().contains("nightly") && task.name != 'checkNightlyParams') {
|
||||
task.dependsOn checkNightlyParams
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('checkNightlyParams') {
|
||||
doFirst {
|
||||
if (project.gradle.startParameter.taskNames.any { it.toLowerCase().contains("nightly") }) {
|
||||
|
||||
if (!file("${project.rootDir}/nightly-url.txt").exists()) {
|
||||
throw new GradleException("Cannot fine 'nightly-url.txt' for nightly build! It must exist in the root of this project and contain the location of the nightly manifest.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def loadKeystoreProperties(filename) {
|
||||
def keystorePropertiesFile = file("${project.rootDir}/${filename}")
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
-keep class org.whispersystems.** { *; }
|
||||
-keep class org.signal.libsignal.protocol.** { *; }
|
||||
-keep class org.thoughtcrime.securesms.** { *; }
|
||||
-keep class org.signal.donations.json.** { *; }
|
||||
-keepclassmembers class ** {
|
||||
public void onEvent*(**);
|
||||
}
|
||||
@@ -12,11 +11,4 @@
|
||||
# Protobuf lite
|
||||
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
|
||||
|
||||
-keep class androidx.window.** { *; }
|
||||
|
||||
# AGP generated dont warns
|
||||
-dontwarn com.android.org.conscrypt.SSLParametersImpl
|
||||
-dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl
|
||||
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
||||
-dontwarn sun.net.spi.nameservice.NameService
|
||||
-dontwarn sun.net.spi.nameservice.NameServiceDescriptor
|
||||
-keep class androidx.window.** { *; }
|
||||
|
Before Width: | Height: | Size: 115 KiB |
@@ -26,13 +26,15 @@ class SignalInstrumentationApplicationContext : ApplicationContext() {
|
||||
}
|
||||
|
||||
override fun initializeLogging() {
|
||||
Log.initialize({ true }, AndroidLogger(), PersistentLogger(this), inMemoryLogger)
|
||||
persistentLogger = PersistentLogger(this)
|
||||
|
||||
Log.initialize({ true }, AndroidLogger(), persistentLogger, inMemoryLogger)
|
||||
|
||||
SignalProtocolLoggerProvider.setProvider(CustomSignalProtocolLogger())
|
||||
|
||||
SignalExecutors.UNBOUNDED.execute {
|
||||
Log.blockUntilAllWritesFinished()
|
||||
LogDatabase.getInstance(this).logs.trimToSize()
|
||||
LogDatabase.getInstance(this).trimToSize()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,13 @@ import org.junit.Ignore
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.mock
|
||||
import org.signal.core.util.ThreadUtil
|
||||
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.pin.KbsRepository
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.registration.VerifyAccountRepository
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponseProcessor
|
||||
@@ -35,7 +37,6 @@ 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
|
||||
@@ -47,16 +48,20 @@ class ChangeNumberViewModelTest {
|
||||
val harness = SignalActivityRule()
|
||||
|
||||
private lateinit var viewModel: ChangeNumberViewModel
|
||||
private lateinit var kbsRepository: KbsRepository
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
ApplicationDependencies.getSignalServiceAccountManager().setSoTimeoutMillis(1000)
|
||||
kbsRepository = mock()
|
||||
ThreadUtil.runOnMainSync {
|
||||
viewModel = ChangeNumberViewModel(
|
||||
localNumber = harness.self.requireE164(),
|
||||
changeNumberRepository = ChangeNumberRepository(),
|
||||
savedState = SavedStateHandle(),
|
||||
password = SignalStore.account().servicePassword!!,
|
||||
verifyAccountRepository = VerifyAccountRepository(harness.application)
|
||||
verifyAccountRepository = VerifyAccountRepository(harness.application),
|
||||
kbsRepository = kbsRepository
|
||||
)
|
||||
|
||||
viewModel.setNewCountry(1)
|
||||
@@ -73,7 +78,7 @@ class ChangeNumberViewModelTest {
|
||||
fun testChangeNumber_givenOnlyPrimaryAndNoRegLock() {
|
||||
// GIVEN
|
||||
val aci = Recipient.self().requireServiceId()
|
||||
val newPni = PNI.from(UUID.randomUUID())
|
||||
val newPni = ServiceId.from(UUID.randomUUID())
|
||||
lateinit var changeNumberRequest: ChangePhoneNumberRequest
|
||||
lateinit var setPreKeysRequest: PreKeyState
|
||||
|
||||
@@ -180,7 +185,7 @@ class ChangeNumberViewModelTest {
|
||||
val aci = Recipient.self().requireServiceId()
|
||||
val oldPni = Recipient.self().requirePni()
|
||||
val oldE164 = Recipient.self().requireE164()
|
||||
val newPni = PNI.from(UUID.randomUUID())
|
||||
val newPni = ServiceId.from(UUID.randomUUID())
|
||||
|
||||
lateinit var changeNumberRequest: ChangePhoneNumberRequest
|
||||
lateinit var setPreKeysRequest: PreKeyState
|
||||
@@ -225,11 +230,13 @@ class ChangeNumberViewModelTest {
|
||||
fun testChangeNumber_givenOnlyPrimaryAndRegistrationLock() {
|
||||
// GIVEN
|
||||
val aci = Recipient.self().requireServiceId()
|
||||
val newPni = PNI.from(UUID.randomUUID())
|
||||
val newPni = ServiceId.from(UUID.randomUUID())
|
||||
|
||||
lateinit var changeNumberRequest: ChangePhoneNumberRequest
|
||||
lateinit var setPreKeysRequest: PreKeyState
|
||||
|
||||
MockProvider.mockGetRegistrationLockStringFlow(kbsRepository)
|
||||
|
||||
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) },
|
||||
@@ -267,7 +274,7 @@ class ChangeNumberViewModelTest {
|
||||
fun testChangeNumber_givenMismatchedDevicesOnFirstCall() {
|
||||
// GIVEN
|
||||
val aci = Recipient.self().requireServiceId()
|
||||
val newPni = PNI.from(UUID.randomUUID())
|
||||
val newPni = ServiceId.from(UUID.randomUUID())
|
||||
lateinit var changeNumberRequest: ChangePhoneNumberRequest
|
||||
lateinit var setPreKeysRequest: PreKeyState
|
||||
|
||||
@@ -311,11 +318,13 @@ class ChangeNumberViewModelTest {
|
||||
fun testChangeNumber_givenRegLockAndMismatchedDevicesOnFirstTwoCalls() {
|
||||
// GIVEN
|
||||
val aci = Recipient.self().requireServiceId()
|
||||
val newPni = PNI.from(UUID.randomUUID())
|
||||
val newPni = ServiceId.from(UUID.randomUUID())
|
||||
|
||||
lateinit var changeNumberRequest: ChangePhoneNumberRequest
|
||||
lateinit var setPreKeysRequest: PreKeyState
|
||||
|
||||
MockProvider.mockGetRegistrationLockStringFlow(kbsRepository)
|
||||
|
||||
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) },
|
||||
|
||||
@@ -8,10 +8,8 @@ import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.signal.core.util.ThreadUtil
|
||||
import org.thoughtcrime.securesms.attachments.PointerAttachment
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivity
|
||||
import org.thoughtcrime.securesms.database.MessageType
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.mms.IncomingMessage
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
@@ -65,8 +63,7 @@ class ConversationItemPreviewer {
|
||||
attachment()
|
||||
}
|
||||
|
||||
val message = IncomingMessage(
|
||||
type = MessageType.NORMAL,
|
||||
val message = IncomingMediaMessage(
|
||||
from = other.id,
|
||||
body = body,
|
||||
sentTimeMillis = System.currentTimeMillis(),
|
||||
@@ -75,7 +72,7 @@ class ConversationItemPreviewer {
|
||||
attachments = PointerAttachment.forPointers(Optional.of(attachments))
|
||||
)
|
||||
|
||||
SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
|
||||
SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
|
||||
|
||||
ThreadUtil.sleep(1)
|
||||
}
|
||||
@@ -85,8 +82,7 @@ class ConversationItemPreviewer {
|
||||
attachment()
|
||||
}
|
||||
|
||||
val message = IncomingMessage(
|
||||
type = MessageType.NORMAL,
|
||||
val message = IncomingMediaMessage(
|
||||
from = other.id,
|
||||
body = body,
|
||||
sentTimeMillis = System.currentTimeMillis(),
|
||||
@@ -95,7 +91,7 @@ class ConversationItemPreviewer {
|
||||
attachments = PointerAttachment.forPointers(Optional.of(attachments))
|
||||
)
|
||||
|
||||
val insert = SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
|
||||
val insert = SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
|
||||
|
||||
SignalDatabase.attachments.getAttachmentsForMessage(insert.messageId).forEachIndexed { index, attachment ->
|
||||
// if (index != 1) {
|
||||
@@ -146,8 +142,6 @@ class ConversationItemPreviewer {
|
||||
1024,
|
||||
1024,
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
0,
|
||||
Optional.of("/not-there.jpg"),
|
||||
false,
|
||||
false,
|
||||
|
||||
@@ -7,7 +7,6 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivity
|
||||
import org.thoughtcrime.securesms.database.IdentityTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.conversation.v2.items
|
||||
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
import androidx.lifecycle.Observer
|
||||
import io.mockk.mockk
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.signal.ringrtc.CallLinkRootKey
|
||||
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState
|
||||
import org.thoughtcrime.securesms.contactshare.Contact
|
||||
import org.thoughtcrime.securesms.conversation.ConversationAdapter
|
||||
import org.thoughtcrime.securesms.conversation.ConversationItem
|
||||
import org.thoughtcrime.securesms.conversation.ConversationItemDisplayMode
|
||||
import org.thoughtcrime.securesms.conversation.ConversationMessage
|
||||
import org.thoughtcrime.securesms.conversation.colors.Colorizer
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
|
||||
import org.thoughtcrime.securesms.database.FakeMessageRecords
|
||||
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
class V2ConversationItemShapeTest {
|
||||
|
||||
@get:Rule
|
||||
val harness = SignalActivityRule(othersCount = 10)
|
||||
|
||||
@Test
|
||||
fun givenNextAndPreviousMessageDoNotExist_whenISetMessageShape_thenIExpectSingle() {
|
||||
val testSubject = V2ConversationItemShape(FakeConversationContext())
|
||||
|
||||
val expected = V2ConversationItemShape.MessageShape.SINGLE
|
||||
val actual = testSubject.setMessageShape(
|
||||
currentMessage = getMessageRecord(),
|
||||
isGroupThread = false,
|
||||
adapterPosition = 5
|
||||
)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenPreviousWithinTimeoutAndNoNext_whenISetMessageShape_thenIExpectEnd() {
|
||||
val now = System.currentTimeMillis()
|
||||
val prev = now - 2.minutes.inWholeMilliseconds
|
||||
|
||||
val testSubject = V2ConversationItemShape(
|
||||
FakeConversationContext(
|
||||
previousMessage = getMessageRecord(prev)
|
||||
)
|
||||
)
|
||||
|
||||
val expected = V2ConversationItemShape.MessageShape.END
|
||||
val actual = testSubject.setMessageShape(
|
||||
currentMessage = getMessageRecord(now),
|
||||
isGroupThread = false,
|
||||
adapterPosition = 5
|
||||
)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenNextWithinTimeoutAndNoPrevious_whenISetMessageShape_thenIExpectStart() {
|
||||
val now = System.currentTimeMillis()
|
||||
val prev = now - 2.minutes.inWholeMilliseconds
|
||||
|
||||
val testSubject = V2ConversationItemShape(
|
||||
FakeConversationContext(
|
||||
nextMessage = getMessageRecord(now)
|
||||
)
|
||||
)
|
||||
|
||||
val expected = V2ConversationItemShape.MessageShape.START
|
||||
val actual = testSubject.setMessageShape(
|
||||
currentMessage = getMessageRecord(prev),
|
||||
isGroupThread = false,
|
||||
adapterPosition = 5
|
||||
)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenPreviousAndNextWithinTimeout_whenISetMessageShape_thenIExpectMiddle() {
|
||||
val now = System.currentTimeMillis()
|
||||
val prev = now - 2.minutes.inWholeMilliseconds
|
||||
val next = now + 2.minutes.inWholeMilliseconds
|
||||
|
||||
val testSubject = V2ConversationItemShape(
|
||||
FakeConversationContext(
|
||||
previousMessage = getMessageRecord(prev),
|
||||
nextMessage = getMessageRecord(next)
|
||||
)
|
||||
)
|
||||
|
||||
val expected = V2ConversationItemShape.MessageShape.MIDDLE
|
||||
val actual = testSubject.setMessageShape(
|
||||
currentMessage = getMessageRecord(now),
|
||||
isGroupThread = false,
|
||||
adapterPosition = 5
|
||||
)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenPreviousOutsideTimeoutAndNoNext_whenISetMessageShape_thenIExpectSingle() {
|
||||
val now = System.currentTimeMillis()
|
||||
val prev = now - 4.minutes.inWholeMilliseconds
|
||||
|
||||
val testSubject = V2ConversationItemShape(
|
||||
FakeConversationContext(
|
||||
previousMessage = getMessageRecord(prev)
|
||||
)
|
||||
)
|
||||
|
||||
val expected = V2ConversationItemShape.MessageShape.SINGLE
|
||||
val actual = testSubject.setMessageShape(
|
||||
currentMessage = getMessageRecord(now),
|
||||
isGroupThread = false,
|
||||
adapterPosition = 5
|
||||
)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenNextOutsideTimeoutAndNoPrevious_whenISetMessageShape_thenIExpectSingle() {
|
||||
val now = System.currentTimeMillis()
|
||||
val prev = now - 4.minutes.inWholeMilliseconds
|
||||
|
||||
val testSubject = V2ConversationItemShape(
|
||||
FakeConversationContext(
|
||||
nextMessage = getMessageRecord(now)
|
||||
)
|
||||
)
|
||||
|
||||
val expected = V2ConversationItemShape.MessageShape.SINGLE
|
||||
val actual = testSubject.setMessageShape(
|
||||
currentMessage = getMessageRecord(prev),
|
||||
isGroupThread = false,
|
||||
adapterPosition = 5
|
||||
)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenPreviousAndNextOutsideTimeout_whenISetMessageShape_thenIExpectSingle() {
|
||||
val now = System.currentTimeMillis()
|
||||
val prev = now - 4.minutes.inWholeMilliseconds
|
||||
val next = now + 4.minutes.inWholeMilliseconds
|
||||
|
||||
val testSubject = V2ConversationItemShape(
|
||||
FakeConversationContext(
|
||||
previousMessage = getMessageRecord(prev),
|
||||
nextMessage = getMessageRecord(next)
|
||||
)
|
||||
)
|
||||
|
||||
val expected = V2ConversationItemShape.MessageShape.SINGLE
|
||||
val actual = testSubject.setMessageShape(
|
||||
currentMessage = getMessageRecord(now),
|
||||
isGroupThread = false,
|
||||
adapterPosition = 5
|
||||
)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
private fun getMessageRecord(
|
||||
timestamp: Long = System.currentTimeMillis()
|
||||
): MessageRecord {
|
||||
return FakeMessageRecords.buildMediaMmsMessageRecord(
|
||||
dateReceived = timestamp,
|
||||
dateSent = timestamp,
|
||||
dateServer = timestamp
|
||||
)
|
||||
}
|
||||
|
||||
private class FakeConversationContext(
|
||||
private val hasWallpaper: Boolean = false,
|
||||
private val previousMessage: MessageRecord? = null,
|
||||
private val nextMessage: MessageRecord? = null
|
||||
) : V2ConversationContext {
|
||||
|
||||
private val colorizer = Colorizer()
|
||||
|
||||
override val displayMode: ConversationItemDisplayMode = ConversationItemDisplayMode.Standard
|
||||
|
||||
override val clickListener: ConversationAdapter.ItemClickListener = FakeConversationItemClickListener
|
||||
override val selectedItems: Set<MultiselectPart> = emptySet()
|
||||
override val isMessageRequestAccepted: Boolean = true
|
||||
override val searchQuery: String? = null
|
||||
override val glideRequests: GlideRequests = mockk()
|
||||
override val isParentInScroll: Boolean = false
|
||||
|
||||
override fun onStartExpirationTimeout(messageRecord: MessageRecord) = Unit
|
||||
|
||||
override fun hasWallpaper(): Boolean = hasWallpaper
|
||||
|
||||
override fun getColorizer(): Colorizer = colorizer
|
||||
|
||||
override fun getNextMessage(adapterPosition: Int): MessageRecord? = nextMessage
|
||||
|
||||
override fun getPreviousMessage(adapterPosition: Int): MessageRecord? = previousMessage
|
||||
}
|
||||
|
||||
private object FakeConversationItemClickListener : ConversationAdapter.ItemClickListener {
|
||||
override fun onQuoteClicked(messageRecord: MmsMessageRecord?) = Unit
|
||||
|
||||
override fun onLinkPreviewClicked(linkPreview: LinkPreview) = Unit
|
||||
|
||||
override fun onQuotedIndicatorClicked(messageRecord: MessageRecord) = Unit
|
||||
|
||||
override fun onMoreTextClicked(conversationRecipientId: RecipientId, messageId: Long, isMms: Boolean) = Unit
|
||||
|
||||
override fun onStickerClicked(stickerLocator: StickerLocator) = Unit
|
||||
|
||||
override fun onViewOnceMessageClicked(messageRecord: MmsMessageRecord) = Unit
|
||||
|
||||
override fun onSharedContactDetailsClicked(contact: Contact, avatarTransitionView: View) = Unit
|
||||
|
||||
override fun onAddToContactsClicked(contact: Contact) = Unit
|
||||
|
||||
override fun onMessageSharedContactClicked(choices: MutableList<Recipient>) = Unit
|
||||
|
||||
override fun onInviteSharedContactClicked(choices: MutableList<Recipient>) = Unit
|
||||
|
||||
override fun onReactionClicked(multiselectPart: MultiselectPart, messageId: Long, isMms: Boolean) = Unit
|
||||
|
||||
override fun onGroupMemberClicked(recipientId: RecipientId, groupId: GroupId) = Unit
|
||||
|
||||
override fun onMessageWithErrorClicked(messageRecord: MessageRecord) = Unit
|
||||
|
||||
override fun onMessageWithRecaptchaNeededClicked(messageRecord: MessageRecord) = Unit
|
||||
|
||||
override fun onIncomingIdentityMismatchClicked(recipientId: RecipientId) = Unit
|
||||
|
||||
override fun onRegisterVoiceNoteCallbacks(onPlaybackStartObserver: Observer<VoiceNotePlaybackState>) = Unit
|
||||
|
||||
override fun onUnregisterVoiceNoteCallbacks(onPlaybackStartObserver: Observer<VoiceNotePlaybackState>) = Unit
|
||||
|
||||
override fun onVoiceNotePause(uri: Uri) = Unit
|
||||
|
||||
override fun onVoiceNotePlay(uri: Uri, messageId: Long, position: Double) = Unit
|
||||
|
||||
override fun onVoiceNoteSeekTo(uri: Uri, position: Double) = Unit
|
||||
|
||||
override fun onVoiceNotePlaybackSpeedChanged(uri: Uri, speed: Float) = Unit
|
||||
|
||||
override fun onGroupMigrationLearnMoreClicked(membershipChange: GroupMigrationMembershipChange) = Unit
|
||||
|
||||
override fun onChatSessionRefreshLearnMoreClicked() = Unit
|
||||
|
||||
override fun onBadDecryptLearnMoreClicked(author: RecipientId) = Unit
|
||||
|
||||
override fun onSafetyNumberLearnMoreClicked(recipient: Recipient) = Unit
|
||||
|
||||
override fun onJoinGroupCallClicked() = Unit
|
||||
|
||||
override fun onInviteFriendsToGroupClicked(groupId: GroupId.V2) = Unit
|
||||
|
||||
override fun onEnableCallNotificationsClicked() = Unit
|
||||
|
||||
override fun onPlayInlineContent(conversationMessage: ConversationMessage?) = Unit
|
||||
|
||||
override fun onInMemoryMessageClicked(messageRecord: InMemoryMessageRecord) = Unit
|
||||
|
||||
override fun onViewGroupDescriptionChange(groupId: GroupId?, description: String, isMessageRequestAccepted: Boolean) = Unit
|
||||
|
||||
override fun onChangeNumberUpdateContact(recipient: Recipient) = Unit
|
||||
|
||||
override fun onCallToAction(action: String) = Unit
|
||||
|
||||
override fun onDonateClicked() = Unit
|
||||
|
||||
override fun onBlockJoinRequest(recipient: Recipient) = Unit
|
||||
|
||||
override fun onRecipientNameClicked(target: RecipientId) = Unit
|
||||
|
||||
override fun onInviteToSignalClicked() = Unit
|
||||
|
||||
override fun onActivatePaymentsClicked() = Unit
|
||||
|
||||
override fun onSendPaymentClicked(recipientId: RecipientId) = Unit
|
||||
|
||||
override fun onScheduledIndicatorClicked(view: View, conversationMessage: ConversationMessage) = Unit
|
||||
|
||||
override fun onUrlClicked(url: String): Boolean = false
|
||||
|
||||
override fun onViewGiftBadgeClicked(messageRecord: MessageRecord) = Unit
|
||||
|
||||
override fun onGiftBadgeRevealed(messageRecord: MessageRecord) = Unit
|
||||
|
||||
override fun goToMediaPreview(parent: ConversationItem?, sharedElement: View?, args: MediaIntentFactory.MediaPreviewArgs?) = Unit
|
||||
|
||||
override fun onEditedIndicatorClicked(messageRecord: MessageRecord) = Unit
|
||||
|
||||
override fun onShowGroupDescriptionClicked(groupName: String, description: String, shouldLinkifyWebLinks: Boolean) = Unit
|
||||
|
||||
override fun onJoinCallLink(callLinkRootKey: CallLinkRootKey) = Unit
|
||||
|
||||
override fun onItemClick(item: MultiselectPart?) = Unit
|
||||
|
||||
override fun onItemLongClick(itemView: View?, item: MultiselectPart?) = Unit
|
||||
}
|
||||
}
|
||||
@@ -6,16 +6,12 @@ import androidx.test.filters.FlakyTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.UriAttachment
|
||||
import org.thoughtcrime.securesms.mms.MediaStream
|
||||
import org.thoughtcrime.securesms.mms.SentMediaQuality
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.testing.assertIs
|
||||
import org.thoughtcrime.securesms.testing.assertIsNot
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import java.util.Optional
|
||||
|
||||
@@ -68,7 +64,6 @@ class AttachmentTableTest {
|
||||
}
|
||||
|
||||
@FlakyTest
|
||||
@Ignore("test is flaky")
|
||||
@Test
|
||||
fun givenIdenticalAttachmentsInsertedForPreUpload_whenIUpdateAttachmentDataAndSpecifyOnlyModifyThisAttachment_thenIExpectDifferentFileInfos() {
|
||||
val blob = BlobProvider.getInstance().forData(byteArrayOf(1, 2, 3, 4, 5)).createForSingleSessionInMemory()
|
||||
@@ -95,79 +90,6 @@ class AttachmentTableTest {
|
||||
assertNotEquals(attachment1Info, attachment2Info)
|
||||
}
|
||||
|
||||
/**
|
||||
* Given: A previous attachment and two pre-upload attachments with the same data but different transform properties (standard and high).
|
||||
*
|
||||
* When changing content of standard pre-upload attachment to match pre-existing attachment
|
||||
*
|
||||
* Then update standard pre-upload attachment to match previous attachment, do not update high pre-upload attachment, and do
|
||||
* not delete shared pre-upload uri from disk as it is still being used by the high pre-upload attachment.
|
||||
*/
|
||||
@Test
|
||||
fun doNotDeleteDedupedFileIfUsedByAnotherAttachmentWithADifferentTransformProperties() {
|
||||
// GIVEN
|
||||
val uncompressData = byteArrayOf(1, 2, 3, 4, 5)
|
||||
val compressedData = byteArrayOf(1, 2, 3)
|
||||
|
||||
val blobUncompressed = BlobProvider.getInstance().forData(uncompressData).createForSingleSessionInMemory()
|
||||
|
||||
val previousAttachment = createAttachment(1, BlobProvider.getInstance().forData(compressedData).createForSingleSessionInMemory(), AttachmentTable.TransformProperties.empty())
|
||||
val previousDatabaseAttachmentId: AttachmentId = SignalDatabase.attachments.insertAttachmentsForMessage(1, listOf(previousAttachment), emptyList()).values.first()
|
||||
|
||||
val standardQualityPreUpload = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.empty())
|
||||
val standardDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(standardQualityPreUpload)
|
||||
|
||||
val highQualityPreUpload = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.forSentMediaQuality(Optional.empty(), SentMediaQuality.HIGH))
|
||||
val highDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(highQualityPreUpload)
|
||||
|
||||
// WHEN
|
||||
SignalDatabase.attachments.updateAttachmentData(standardDatabaseAttachment, createMediaStream(compressedData), false)
|
||||
|
||||
// THEN
|
||||
val previousInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(previousDatabaseAttachmentId, AttachmentTable.DATA)!!
|
||||
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA)!!
|
||||
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA)!!
|
||||
|
||||
assertNotEquals(standardInfo, highInfo)
|
||||
standardInfo.file assertIs previousInfo.file
|
||||
highInfo.file assertIsNot standardInfo.file
|
||||
highInfo.file.exists() assertIs true
|
||||
}
|
||||
|
||||
/**
|
||||
* Given: Three pre-upload attachments with the same data but different transform properties (1x standard and 2x high).
|
||||
*
|
||||
* When inserting content of high pre-upload attachment.
|
||||
*
|
||||
* Then do not deduplicate with standard pre-upload attachment, but do deduplicate second high insert.
|
||||
*/
|
||||
@Test
|
||||
fun doNotDedupedFileIfUsedByAnotherAttachmentWithADifferentTransformProperties() {
|
||||
// GIVEN
|
||||
val uncompressData = byteArrayOf(1, 2, 3, 4, 5)
|
||||
val blobUncompressed = BlobProvider.getInstance().forData(uncompressData).createForSingleSessionInMemory()
|
||||
|
||||
val standardQualityPreUpload = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.empty())
|
||||
val standardDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(standardQualityPreUpload)
|
||||
|
||||
// WHEN
|
||||
val highQualityPreUpload = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.forSentMediaQuality(Optional.empty(), SentMediaQuality.HIGH))
|
||||
val highDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(highQualityPreUpload)
|
||||
|
||||
val secondHighQualityPreUpload = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.forSentMediaQuality(Optional.empty(), SentMediaQuality.HIGH))
|
||||
val secondHighDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(secondHighQualityPreUpload)
|
||||
|
||||
// THEN
|
||||
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA)!!
|
||||
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA)!!
|
||||
val secondHighInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(secondHighDatabaseAttachment.attachmentId, AttachmentTable.DATA)!!
|
||||
|
||||
highInfo.file assertIsNot standardInfo.file
|
||||
secondHighInfo.file assertIs highInfo.file
|
||||
standardInfo.file.exists() assertIs true
|
||||
highInfo.file.exists() assertIs true
|
||||
}
|
||||
|
||||
private fun createAttachment(id: Long, uri: Uri, transformProperties: AttachmentTable.TransformProperties): UriAttachment {
|
||||
return UriAttachmentBuilder.build(
|
||||
id,
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import junit.framework.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.thoughtcrime.securesms.calls.log.CallLogFilter
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkState
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CallLinkTableTest {
|
||||
|
||||
companion object {
|
||||
private val ROOM_ID_A = byteArrayOf(1, 2, 3, 4)
|
||||
private val ROOM_ID_B = byteArrayOf(2, 2, 3, 4)
|
||||
private const val TIMESTAMP_A = 1000L
|
||||
private const val TIMESTAMP_B = 2000L
|
||||
}
|
||||
|
||||
@get:Rule
|
||||
val harness = SignalActivityRule(createGroup = true)
|
||||
|
||||
@Test
|
||||
fun givenTwoNonAdminCallLinks_whenIDeleteBeforeFirst_thenIExpectNeitherDeleted() {
|
||||
insertTwoNonAdminCallLinksWithEvents()
|
||||
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_A - 500)
|
||||
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
|
||||
assertEquals(2, callEvents.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTwoNonAdminCallLinks_whenIDeleteOnFirst_thenIExpectFirstDeleted() {
|
||||
insertTwoNonAdminCallLinksWithEvents()
|
||||
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_A)
|
||||
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
|
||||
assertEquals(1, callEvents.size)
|
||||
assertEquals(TIMESTAMP_B, callEvents.first().record.timestamp)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTwoNonAdminCallLinks_whenIDeleteAfterFirstAndBeforeSecond_thenIExpectFirstDeleted() {
|
||||
insertTwoNonAdminCallLinksWithEvents()
|
||||
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_B - 500)
|
||||
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
|
||||
assertEquals(1, callEvents.size)
|
||||
assertEquals(TIMESTAMP_B, callEvents.first().record.timestamp)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTwoNonAdminCallLinks_whenIDeleteOnSecond_thenIExpectBothDeleted() {
|
||||
insertTwoNonAdminCallLinksWithEvents()
|
||||
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_B)
|
||||
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
|
||||
assertEquals(0, callEvents.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTwoNonAdminCallLinks_whenIDeleteAfterSecond_thenIExpectBothDeleted() {
|
||||
insertTwoNonAdminCallLinksWithEvents()
|
||||
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_B + 500)
|
||||
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
|
||||
assertEquals(0, callEvents.size)
|
||||
}
|
||||
|
||||
private fun insertTwoNonAdminCallLinksWithEvents() {
|
||||
insertCallLinkWithEvent(ROOM_ID_A, 1000)
|
||||
insertCallLinkWithEvent(ROOM_ID_B, 2000)
|
||||
}
|
||||
|
||||
private fun insertCallLinkWithEvent(roomId: ByteArray, timestamp: Long) {
|
||||
SignalDatabase.callLinks.insertCallLink(
|
||||
CallLinkTable.CallLink(
|
||||
recipientId = RecipientId.UNKNOWN,
|
||||
roomId = CallLinkRoomId.fromBytes(roomId),
|
||||
credentials = CallLinkCredentials(
|
||||
linkKeyBytes = roomId,
|
||||
adminPassBytes = null
|
||||
),
|
||||
state = SignalCallLinkState()
|
||||
)
|
||||
)
|
||||
|
||||
val callLinkRecipient = SignalDatabase.recipients.getByCallLinkRoomId(CallLinkRoomId.fromBytes(roomId)).get()
|
||||
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
1,
|
||||
callLinkRecipient,
|
||||
CallTable.Direction.INCOMING,
|
||||
timestamp
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -10,18 +10,13 @@ import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.signal.ringrtc.CallId
|
||||
import org.signal.ringrtc.CallManager
|
||||
import org.thoughtcrime.securesms.calls.log.CallLogFilter
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CallTableTest {
|
||||
|
||||
@get:Rule
|
||||
val harness = SignalActivityRule(createGroup = true)
|
||||
|
||||
private val groupRecipientId: RecipientId
|
||||
get() = harness.group!!.recipientId
|
||||
val harness = SignalActivityRule()
|
||||
|
||||
@Test
|
||||
fun givenACall_whenISetTimestamp_thenIExpectUpdatedTimestamp() {
|
||||
@@ -29,13 +24,13 @@ class CallTableTest {
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
CallTable.Direction.INCOMING,
|
||||
now
|
||||
)
|
||||
|
||||
SignalDatabase.calls.setTimestamp(callId, groupRecipientId, -1L)
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
SignalDatabase.calls.setTimestamp(callId, harness.others[0], -1L)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(-1L, call?.timestamp)
|
||||
|
||||
@@ -50,15 +45,15 @@ class CallTableTest {
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
CallTable.Direction.INCOMING,
|
||||
now
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
SignalDatabase.calls.deleteGroupCall(call!!)
|
||||
|
||||
val deletedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val deletedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
val oldestDeletionTimestamp = SignalDatabase.calls.getOldestDeletionTimestamp()
|
||||
|
||||
assertEquals(CallTable.Event.DELETE, deletedCall?.event)
|
||||
@@ -71,12 +66,12 @@ class CallTableTest {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
CallTable.Direction.OUTGOING,
|
||||
System.currentTimeMillis()
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
|
||||
val oldestDeletionTimestamp = SignalDatabase.calls.getOldestDeletionTimestamp()
|
||||
@@ -91,12 +86,12 @@ class CallTableTest {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
CallTable.Direction.OUTGOING,
|
||||
System.currentTimeMillis()
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.OUTGOING_RING, call?.event)
|
||||
assertEquals(harness.self.id, call?.ringerRecipient)
|
||||
@@ -108,12 +103,12 @@ class CallTableTest {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
CallTable.Direction.INCOMING,
|
||||
System.currentTimeMillis()
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.JOINED, call?.event)
|
||||
assertNull(call?.ringerRecipient)
|
||||
@@ -125,13 +120,13 @@ class CallTableTest {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
ringId = callId,
|
||||
groupRecipientId = groupRecipientId,
|
||||
groupRecipientId = harness.others[0],
|
||||
ringerRecipient = harness.others[1],
|
||||
dateReceived = System.currentTimeMillis(),
|
||||
ringState = CallManager.RingUpdate.REQUESTED
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.RINGING, call?.event)
|
||||
|
||||
@@ -139,7 +134,7 @@ class CallTableTest {
|
||||
call!!
|
||||
)
|
||||
|
||||
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
|
||||
}
|
||||
|
||||
@@ -148,13 +143,13 @@ class CallTableTest {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
ringId = callId,
|
||||
groupRecipientId = groupRecipientId,
|
||||
groupRecipientId = harness.others[0],
|
||||
ringerRecipient = harness.others[1],
|
||||
dateReceived = System.currentTimeMillis(),
|
||||
ringState = CallManager.RingUpdate.EXPIRED_REQUEST
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||
|
||||
@@ -162,7 +157,7 @@ class CallTableTest {
|
||||
call!!
|
||||
)
|
||||
|
||||
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
|
||||
}
|
||||
|
||||
@@ -171,13 +166,13 @@ class CallTableTest {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
ringId = callId,
|
||||
groupRecipientId = groupRecipientId,
|
||||
groupRecipientId = harness.others[0],
|
||||
ringerRecipient = harness.others[1],
|
||||
dateReceived = System.currentTimeMillis(),
|
||||
ringState = CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.DECLINED, call?.event)
|
||||
|
||||
@@ -185,7 +180,7 @@ class CallTableTest {
|
||||
call!!
|
||||
)
|
||||
|
||||
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
|
||||
}
|
||||
|
||||
@@ -194,7 +189,7 @@ class CallTableTest {
|
||||
val era = "aaa"
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = groupRecipientId,
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = System.currentTimeMillis(),
|
||||
peekGroupCallEraId = "aaa",
|
||||
@@ -202,7 +197,7 @@ class CallTableTest {
|
||||
isCallFull = false
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
|
||||
|
||||
@@ -210,7 +205,7 @@ class CallTableTest {
|
||||
call!!
|
||||
)
|
||||
|
||||
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertEquals(CallTable.Event.JOINED, acceptedCall?.event)
|
||||
}
|
||||
|
||||
@@ -219,7 +214,7 @@ class CallTableTest {
|
||||
val era = "aaa"
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = groupRecipientId,
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = System.currentTimeMillis(),
|
||||
peekGroupCallEraId = "aaa",
|
||||
@@ -227,7 +222,7 @@ class CallTableTest {
|
||||
isCallFull = false
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
|
||||
}
|
||||
@@ -238,7 +233,7 @@ class CallTableTest {
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = groupRecipientId,
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = now,
|
||||
peekGroupCallEraId = "aaa",
|
||||
@@ -246,13 +241,13 @@ class CallTableTest {
|
||||
isCallFull = false
|
||||
)
|
||||
|
||||
SignalDatabase.calls.getCallById(callId, groupRecipientId).let {
|
||||
SignalDatabase.calls.getCallById(callId, harness.others[0]).let {
|
||||
assertNotNull(it)
|
||||
assertEquals(now, it?.timestamp)
|
||||
}
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = groupRecipientId,
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = 1L,
|
||||
peekGroupCallEraId = "aaa",
|
||||
@@ -260,7 +255,7 @@ class CallTableTest {
|
||||
isCallFull = false
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
|
||||
assertEquals(1L, call?.timestamp)
|
||||
@@ -271,20 +266,20 @@ class CallTableTest {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(
|
||||
callId = callId,
|
||||
recipientId = groupRecipientId,
|
||||
recipientId = harness.others[0],
|
||||
direction = CallTable.Direction.INCOMING,
|
||||
timestamp = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
ringId = callId,
|
||||
groupRecipientId = groupRecipientId,
|
||||
groupRecipientId = harness.others[0],
|
||||
ringerRecipient = harness.others[1],
|
||||
dateReceived = System.currentTimeMillis(),
|
||||
ringState = CallManager.RingUpdate.REQUESTED
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.DELETE, call?.event)
|
||||
}
|
||||
@@ -295,7 +290,7 @@ class CallTableTest {
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = groupRecipientId,
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = now,
|
||||
peekGroupCallEraId = "aaa",
|
||||
@@ -305,13 +300,13 @@ class CallTableTest {
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.REQUESTED
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.RINGING, call?.event)
|
||||
assertEquals(harness.others[1], call?.ringerRecipient)
|
||||
@@ -324,20 +319,20 @@ class CallTableTest {
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
CallTable.Direction.INCOMING,
|
||||
now
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.REQUESTED
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.ACCEPTED, call?.event)
|
||||
assertEquals(harness.others[1], call?.ringerRecipient)
|
||||
@@ -349,7 +344,7 @@ class CallTableTest {
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = groupRecipientId,
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = now,
|
||||
peekGroupCallEraId = "aaa",
|
||||
@@ -359,13 +354,13 @@ class CallTableTest {
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.EXPIRED_REQUEST
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||
assertEquals(harness.others[1], call?.ringerRecipient)
|
||||
@@ -377,7 +372,7 @@ class CallTableTest {
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = groupRecipientId,
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = now,
|
||||
peekGroupCallEraId = "aaa",
|
||||
@@ -387,7 +382,7 @@ class CallTableTest {
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.REQUESTED
|
||||
@@ -395,13 +390,13 @@ class CallTableTest {
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.EXPIRED_REQUEST
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||
assertEquals(harness.others[1], call?.ringerRecipient)
|
||||
@@ -414,20 +409,20 @@ class CallTableTest {
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
CallTable.Direction.INCOMING,
|
||||
now
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.BUSY_LOCALLY
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.ACCEPTED, call?.event)
|
||||
}
|
||||
@@ -439,20 +434,20 @@ class CallTableTest {
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
CallTable.Direction.INCOMING,
|
||||
now
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.ACCEPTED, call?.event)
|
||||
}
|
||||
@@ -463,7 +458,7 @@ class CallTableTest {
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = groupRecipientId,
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = now,
|
||||
peekGroupCallEraId = "aaa",
|
||||
@@ -473,7 +468,7 @@ class CallTableTest {
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.REQUESTED
|
||||
@@ -481,13 +476,13 @@ class CallTableTest {
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.BUSY_LOCALLY
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||
}
|
||||
@@ -498,7 +493,7 @@ class CallTableTest {
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = groupRecipientId,
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = now,
|
||||
peekGroupCallEraId = "aaa",
|
||||
@@ -508,7 +503,7 @@ class CallTableTest {
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.REQUESTED
|
||||
@@ -516,13 +511,13 @@ class CallTableTest {
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||
}
|
||||
@@ -533,7 +528,7 @@ class CallTableTest {
|
||||
val callId = CallId.fromEra(era).longValue()
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
|
||||
groupRecipientId = groupRecipientId,
|
||||
groupRecipientId = harness.others[0],
|
||||
sender = harness.others[1],
|
||||
timestamp = now,
|
||||
peekGroupCallEraId = "aaa",
|
||||
@@ -543,13 +538,13 @@ class CallTableTest {
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.ACCEPTED, call?.event)
|
||||
}
|
||||
@@ -561,7 +556,7 @@ class CallTableTest {
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.REQUESTED
|
||||
@@ -569,13 +564,13 @@ class CallTableTest {
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.DECLINED, call?.event)
|
||||
}
|
||||
@@ -587,7 +582,7 @@ class CallTableTest {
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.EXPIRED_REQUEST
|
||||
@@ -595,13 +590,13 @@ class CallTableTest {
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.DECLINED, call?.event)
|
||||
}
|
||||
@@ -613,20 +608,20 @@ class CallTableTest {
|
||||
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
CallTable.Direction.OUTGOING,
|
||||
System.currentTimeMillis()
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.OUTGOING_RING, call?.event)
|
||||
}
|
||||
@@ -636,13 +631,13 @@ class CallTableTest {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.REQUESTED
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.RINGING, call?.event)
|
||||
assertEquals(harness.others[1], call?.ringerRecipient)
|
||||
@@ -654,13 +649,13 @@ class CallTableTest {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.EXPIRED_REQUEST
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||
assertEquals(harness.others[1], call?.ringerRecipient)
|
||||
@@ -672,13 +667,13 @@ class CallTableTest {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.CANCELLED_BY_RINGER
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||
assertEquals(harness.others[1], call?.ringerRecipient)
|
||||
@@ -690,13 +685,13 @@ class CallTableTest {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.BUSY_LOCALLY
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||
assertNotNull(call?.messageId)
|
||||
@@ -707,13 +702,13 @@ class CallTableTest {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.MISSED, call?.event)
|
||||
assertNotNull(call?.messageId)
|
||||
@@ -724,13 +719,13 @@ class CallTableTest {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.ACCEPTED, call?.event)
|
||||
assertNotNull(call?.messageId)
|
||||
@@ -741,85 +736,15 @@ class CallTableTest {
|
||||
val callId = 1L
|
||||
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
|
||||
callId,
|
||||
groupRecipientId,
|
||||
harness.others[0],
|
||||
harness.others[1],
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
|
||||
)
|
||||
|
||||
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
|
||||
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
|
||||
assertNotNull(call)
|
||||
assertEquals(CallTable.Event.DECLINED, call?.event)
|
||||
assertNotNull(call?.messageId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTwoCalls_whenIDeleteBeforeCallB_thenOnlyDeleteCallA() {
|
||||
insertTwoCallEvents()
|
||||
|
||||
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(1500)
|
||||
|
||||
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
|
||||
assertEquals(1, allCallEvents.size)
|
||||
assertEquals(2, allCallEvents.first().record.callId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTwoCalls_whenIDeleteBeforeCallA_thenIDoNotDeleteAnyCalls() {
|
||||
insertTwoCallEvents()
|
||||
|
||||
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(500)
|
||||
|
||||
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
|
||||
assertEquals(2, allCallEvents.size)
|
||||
assertEquals(2, allCallEvents[0].record.callId)
|
||||
assertEquals(1, allCallEvents[1].record.callId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTwoCalls_whenIDeleteOnCallA_thenIOnlyDeleteCallA() {
|
||||
insertTwoCallEvents()
|
||||
|
||||
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(1000)
|
||||
|
||||
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
|
||||
assertEquals(1, allCallEvents.size)
|
||||
assertEquals(2, allCallEvents.first().record.callId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTwoCalls_whenIDeleteOnCallB_thenIDeleteBothCalls() {
|
||||
insertTwoCallEvents()
|
||||
|
||||
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(2000)
|
||||
|
||||
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
|
||||
assertEquals(0, allCallEvents.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenTwoCalls_whenIDeleteAfterCallB_thenIDeleteBothCalls() {
|
||||
insertTwoCallEvents()
|
||||
|
||||
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(2500)
|
||||
|
||||
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
|
||||
assertEquals(0, allCallEvents.size)
|
||||
}
|
||||
|
||||
private fun insertTwoCallEvents() {
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
1,
|
||||
groupRecipientId,
|
||||
CallTable.Direction.INCOMING,
|
||||
1000
|
||||
)
|
||||
|
||||
SignalDatabase.calls.insertAcceptedGroupCall(
|
||||
2,
|
||||
groupRecipientId,
|
||||
CallTable.Direction.OUTGOING,
|
||||
2000
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListRecord
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ACI
|
||||
import java.util.UUID
|
||||
|
||||
class DistributionListTablesTest {
|
||||
|
||||
@@ -293,22 +293,22 @@ class GroupTableTest {
|
||||
|
||||
private fun insertPushGroup(
|
||||
members: List<DecryptedMember> = listOf(
|
||||
DecryptedMember.Builder()
|
||||
.aciBytes(harness.self.requireAci().toByteString())
|
||||
.joinedAtRevision(0)
|
||||
.role(Member.Role.DEFAULT)
|
||||
DecryptedMember.newBuilder()
|
||||
.setUuid(harness.self.requireServiceId().toByteString())
|
||||
.setJoinedAtRevision(0)
|
||||
.setRole(Member.Role.DEFAULT)
|
||||
.build(),
|
||||
DecryptedMember.Builder()
|
||||
.aciBytes(Recipient.resolved(harness.others[0]).requireAci().toByteString())
|
||||
.joinedAtRevision(0)
|
||||
.role(Member.Role.DEFAULT)
|
||||
DecryptedMember.newBuilder()
|
||||
.setUuid(Recipient.resolved(harness.others[0]).requireServiceId().toByteString())
|
||||
.setJoinedAtRevision(0)
|
||||
.setRole(Member.Role.DEFAULT)
|
||||
.build()
|
||||
)
|
||||
): GroupId {
|
||||
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
|
||||
val decryptedGroupState = DecryptedGroup.Builder()
|
||||
.members(members)
|
||||
.revision(0)
|
||||
val decryptedGroupState = DecryptedGroup.newBuilder()
|
||||
.addAllMembers(members)
|
||||
.setRevision(0)
|
||||
.build()
|
||||
|
||||
return groupTable.create(groupMasterKey, decryptedGroupState)!!
|
||||
@@ -317,23 +317,23 @@ class GroupTableTest {
|
||||
private fun insertPushGroupWithSelfAndOthers(others: List<RecipientId>): GroupId {
|
||||
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
|
||||
|
||||
val selfMember: DecryptedMember = DecryptedMember.Builder()
|
||||
.aciBytes(harness.self.requireAci().toByteString())
|
||||
.joinedAtRevision(0)
|
||||
.role(Member.Role.DEFAULT)
|
||||
val selfMember: DecryptedMember = DecryptedMember.newBuilder()
|
||||
.setUuid(harness.self.requireServiceId().toByteString())
|
||||
.setJoinedAtRevision(0)
|
||||
.setRole(Member.Role.DEFAULT)
|
||||
.build()
|
||||
|
||||
val otherMembers: List<DecryptedMember> = others.map { id ->
|
||||
DecryptedMember.Builder()
|
||||
.aciBytes(Recipient.resolved(id).requireAci().toByteString())
|
||||
.joinedAtRevision(0)
|
||||
.role(Member.Role.DEFAULT)
|
||||
DecryptedMember.newBuilder()
|
||||
.setUuid(Recipient.resolved(id).requireServiceId().toByteString())
|
||||
.setJoinedAtRevision(0)
|
||||
.setRole(Member.Role.DEFAULT)
|
||||
.build()
|
||||
}
|
||||
|
||||
val decryptedGroupState = DecryptedGroup.Builder()
|
||||
.members(listOf(selfMember) + otherMembers)
|
||||
.revision(0)
|
||||
val decryptedGroupState = DecryptedGroup.newBuilder()
|
||||
.addAllMembers(listOf(selfMember) + otherMembers)
|
||||
.setRevision(0)
|
||||
.build()
|
||||
|
||||
return groupTable.create(groupMasterKey, decryptedGroupState)!!
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertNotNull
|
||||
import junit.framework.TestCase.assertNull
|
||||
import org.junit.Test
|
||||
import org.signal.core.util.readToSingleObject
|
||||
import org.signal.core.util.requireLongOrNull
|
||||
import org.signal.core.util.select
|
||||
import org.signal.core.util.update
|
||||
import org.signal.libsignal.protocol.ecc.Curve
|
||||
import org.signal.libsignal.protocol.kem.KEMKeyPair
|
||||
import org.signal.libsignal.protocol.kem.KEMKeyType
|
||||
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
import java.util.UUID
|
||||
|
||||
class KyberPreKeyTableTest {
|
||||
|
||||
private val aci: ACI = ACI.from(UUID.randomUUID())
|
||||
private val pni: PNI = PNI.from(UUID.randomUUID())
|
||||
|
||||
@Test
|
||||
fun markAllStaleIfNecessary_onlyUpdatesMatchingAccountAndZeroValues() {
|
||||
insertTestRecord(aci, id = 1)
|
||||
insertTestRecord(aci, id = 2)
|
||||
insertTestRecord(aci, id = 3, staleTime = 42)
|
||||
insertTestRecord(pni, id = 4)
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.kyberPreKeys.markAllStaleIfNecessary(aci, now)
|
||||
|
||||
assertEquals(now, getStaleTime(aci, 1))
|
||||
assertEquals(now, getStaleTime(aci, 2))
|
||||
assertEquals(42L, getStaleTime(aci, 3))
|
||||
assertEquals(0L, getStaleTime(pni, 4))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteAllStaleBefore_deleteOldBeforeThreshold() {
|
||||
insertTestRecord(aci, id = 1, staleTime = 10)
|
||||
insertTestRecord(aci, id = 2, staleTime = 10)
|
||||
insertTestRecord(aci, id = 3, staleTime = 10)
|
||||
insertTestRecord(aci, id = 4, staleTime = 15)
|
||||
insertTestRecord(aci, id = 5, staleTime = 0)
|
||||
|
||||
SignalDatabase.kyberPreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 0)
|
||||
|
||||
assertNull(getStaleTime(aci, 1))
|
||||
assertNull(getStaleTime(aci, 2))
|
||||
assertNull(getStaleTime(aci, 3))
|
||||
assertNotNull(getStaleTime(aci, 4))
|
||||
assertNotNull(getStaleTime(aci, 5))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteAllStaleBefore_neverDeleteStaleOfZero() {
|
||||
insertTestRecord(aci, id = 1, staleTime = 0)
|
||||
insertTestRecord(aci, id = 2, staleTime = 0)
|
||||
insertTestRecord(aci, id = 3, staleTime = 0)
|
||||
insertTestRecord(aci, id = 4, staleTime = 0)
|
||||
insertTestRecord(aci, id = 5, staleTime = 0)
|
||||
|
||||
SignalDatabase.kyberPreKeys.deleteAllStaleBefore(aci, threshold = 10, minCount = 1)
|
||||
|
||||
assertNotNull(getStaleTime(aci, 1))
|
||||
assertNotNull(getStaleTime(aci, 2))
|
||||
assertNotNull(getStaleTime(aci, 3))
|
||||
assertNotNull(getStaleTime(aci, 4))
|
||||
assertNotNull(getStaleTime(aci, 5))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteAllStaleBefore_respectMinCount() {
|
||||
insertTestRecord(aci, id = 1, staleTime = 10)
|
||||
insertTestRecord(aci, id = 2, staleTime = 10)
|
||||
insertTestRecord(aci, id = 3, staleTime = 10)
|
||||
insertTestRecord(aci, id = 4, staleTime = 10)
|
||||
insertTestRecord(aci, id = 5, staleTime = 10)
|
||||
|
||||
SignalDatabase.kyberPreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 3)
|
||||
|
||||
assertNull(getStaleTime(aci, 1))
|
||||
assertNull(getStaleTime(aci, 2))
|
||||
assertNotNull(getStaleTime(aci, 3))
|
||||
assertNotNull(getStaleTime(aci, 4))
|
||||
assertNotNull(getStaleTime(aci, 5))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteAllStaleBefore_respectAccount() {
|
||||
insertTestRecord(aci, id = 1, staleTime = 10)
|
||||
insertTestRecord(aci, id = 2, staleTime = 10)
|
||||
insertTestRecord(aci, id = 3, staleTime = 10)
|
||||
|
||||
insertTestRecord(pni, id = 4, staleTime = 10)
|
||||
insertTestRecord(pni, id = 5, staleTime = 10)
|
||||
|
||||
SignalDatabase.kyberPreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 2)
|
||||
|
||||
assertNull(getStaleTime(aci, 1))
|
||||
assertNotNull(getStaleTime(aci, 2))
|
||||
assertNotNull(getStaleTime(aci, 3))
|
||||
assertNotNull(getStaleTime(pni, 4))
|
||||
assertNotNull(getStaleTime(pni, 5))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteAllStaleBefore_ignoreLastResortForMinCount() {
|
||||
insertTestRecord(aci, id = 1, staleTime = 10)
|
||||
insertTestRecord(aci, id = 2, staleTime = 10)
|
||||
insertTestRecord(aci, id = 3, staleTime = 10)
|
||||
insertTestRecord(aci, id = 4, staleTime = 10)
|
||||
insertTestRecord(aci, id = 5, staleTime = 10, lastResort = true)
|
||||
|
||||
SignalDatabase.kyberPreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 3)
|
||||
|
||||
assertNull(getStaleTime(aci, 1))
|
||||
assertNotNull(getStaleTime(aci, 2))
|
||||
assertNotNull(getStaleTime(aci, 3))
|
||||
assertNotNull(getStaleTime(aci, 4))
|
||||
assertNotNull(getStaleTime(aci, 5))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteAllStaleBefore_neverDeleteLastResort() {
|
||||
insertTestRecord(aci, id = 1, staleTime = 10, lastResort = true)
|
||||
insertTestRecord(aci, id = 2, staleTime = 10, lastResort = true)
|
||||
insertTestRecord(aci, id = 3, staleTime = 10, lastResort = true)
|
||||
|
||||
SignalDatabase.oneTimePreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 0)
|
||||
|
||||
assertNotNull(getStaleTime(aci, 1))
|
||||
assertNotNull(getStaleTime(aci, 2))
|
||||
assertNotNull(getStaleTime(aci, 3))
|
||||
}
|
||||
|
||||
private fun insertTestRecord(account: ServiceId, id: Int, staleTime: Long = 0, lastResort: Boolean = false) {
|
||||
val kemKeyPair = KEMKeyPair.generate(KEMKeyType.KYBER_1024)
|
||||
SignalDatabase.kyberPreKeys.insert(
|
||||
serviceId = account,
|
||||
keyId = id,
|
||||
record = KyberPreKeyRecord(
|
||||
id,
|
||||
System.currentTimeMillis(),
|
||||
kemKeyPair,
|
||||
Curve.generateKeyPair().privateKey.calculateSignature(kemKeyPair.publicKey.serialize())
|
||||
),
|
||||
lastResort = lastResort
|
||||
)
|
||||
|
||||
val count = SignalDatabase.rawDatabase
|
||||
.update(KyberPreKeyTable.TABLE_NAME)
|
||||
.values(KyberPreKeyTable.STALE_TIMESTAMP to staleTime)
|
||||
.where("${KyberPreKeyTable.ACCOUNT_ID} = ? AND ${KyberPreKeyTable.KEY_ID} = $id", account)
|
||||
.run()
|
||||
|
||||
assertEquals(1, count)
|
||||
}
|
||||
|
||||
private fun getStaleTime(account: ServiceId, id: Int): Long? {
|
||||
return SignalDatabase.rawDatabase
|
||||
.select(KyberPreKeyTable.STALE_TIMESTAMP)
|
||||
.from(KyberPreKeyTable.TABLE_NAME)
|
||||
.where("${KyberPreKeyTable.ACCOUNT_ID} = ? AND ${KyberPreKeyTable.KEY_ID} = $id", account)
|
||||
.run()
|
||||
.readToSingleObject { it.requireLongOrNull(KyberPreKeyTable.STALE_TIMESTAMP) }
|
||||
}
|
||||
}
|
||||
@@ -1,288 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import org.junit.Test
|
||||
import org.signal.core.util.forEach
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.requireNonNullString
|
||||
import org.signal.core.util.select
|
||||
import org.signal.core.util.update
|
||||
import org.thoughtcrime.securesms.crash.CrashConfig
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.testing.assertIs
|
||||
|
||||
class LogDatabaseTest {
|
||||
|
||||
private val db: LogDatabase = LogDatabase.getInstance(ApplicationDependencies.getApplication())
|
||||
|
||||
@Test
|
||||
fun crashTable_matchesNamePattern() {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
db.crashes.saveCrash(
|
||||
createdAt = currentTime,
|
||||
name = "TestName",
|
||||
message = "Test Message",
|
||||
stackTrace = "test\nstack\ntrace"
|
||||
)
|
||||
|
||||
val foundMatch = db.crashes.anyMatch(
|
||||
listOf(
|
||||
CrashConfig.CrashPattern(namePattern = "Test")
|
||||
),
|
||||
promptThreshold = currentTime
|
||||
)
|
||||
|
||||
foundMatch assertIs true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun crashTable_matchesMessagePattern() {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
db.crashes.saveCrash(
|
||||
createdAt = currentTime,
|
||||
name = "TestName",
|
||||
message = "Test Message",
|
||||
stackTrace = "test\nstack\ntrace"
|
||||
)
|
||||
|
||||
val foundMatch = db.crashes.anyMatch(
|
||||
listOf(
|
||||
CrashConfig.CrashPattern(messagePattern = "Message")
|
||||
),
|
||||
promptThreshold = currentTime
|
||||
)
|
||||
|
||||
foundMatch assertIs true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun crashTable_matchesStackTracePattern() {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
db.crashes.saveCrash(
|
||||
createdAt = currentTime,
|
||||
name = "TestName",
|
||||
message = "Test Message",
|
||||
stackTrace = "test\nstack\ntrace"
|
||||
)
|
||||
|
||||
val foundMatch = db.crashes.anyMatch(
|
||||
listOf(
|
||||
CrashConfig.CrashPattern(stackTracePattern = "stack")
|
||||
),
|
||||
promptThreshold = currentTime
|
||||
)
|
||||
|
||||
foundMatch assertIs true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun crashTable_matchesNameAndMessagePattern() {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
db.crashes.saveCrash(
|
||||
createdAt = currentTime,
|
||||
name = "TestName",
|
||||
message = "Test Message",
|
||||
stackTrace = "test\nstack\ntrace"
|
||||
)
|
||||
|
||||
val foundMatch = db.crashes.anyMatch(
|
||||
listOf(
|
||||
CrashConfig.CrashPattern(namePattern = "Test", messagePattern = "Message")
|
||||
),
|
||||
promptThreshold = currentTime
|
||||
)
|
||||
|
||||
foundMatch assertIs true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun crashTable_matchesNameAndStackTracePattern() {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
db.crashes.saveCrash(
|
||||
createdAt = currentTime,
|
||||
name = "TestName",
|
||||
message = "Test Message",
|
||||
stackTrace = "test\nstack\ntrace"
|
||||
)
|
||||
|
||||
val foundMatch = db.crashes.anyMatch(
|
||||
listOf(
|
||||
CrashConfig.CrashPattern(namePattern = "Test", stackTracePattern = "stack")
|
||||
),
|
||||
promptThreshold = currentTime
|
||||
)
|
||||
|
||||
foundMatch assertIs true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun crashTable_matchesNameAndMessageAndStackTracePattern() {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
db.crashes.saveCrash(
|
||||
createdAt = currentTime,
|
||||
name = "TestName",
|
||||
message = "Test Message",
|
||||
stackTrace = "test\nstack\ntrace"
|
||||
)
|
||||
|
||||
val foundMatch = db.crashes.anyMatch(
|
||||
listOf(
|
||||
CrashConfig.CrashPattern(namePattern = "Test", messagePattern = "Message", stackTracePattern = "stack")
|
||||
),
|
||||
promptThreshold = currentTime
|
||||
)
|
||||
|
||||
foundMatch assertIs true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun crashTable_doesNotMatchNamePattern() {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
db.crashes.saveCrash(
|
||||
createdAt = currentTime,
|
||||
name = "TestName",
|
||||
message = "Test Message",
|
||||
stackTrace = "test\nstack\ntrace"
|
||||
)
|
||||
|
||||
val foundMatch = db.crashes.anyMatch(
|
||||
listOf(
|
||||
CrashConfig.CrashPattern(namePattern = "Blah")
|
||||
),
|
||||
promptThreshold = currentTime
|
||||
)
|
||||
|
||||
foundMatch assertIs false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun crashTable_matchesNameButNotMessagePattern() {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
db.crashes.saveCrash(
|
||||
createdAt = currentTime,
|
||||
name = "TestName",
|
||||
message = "Test Message",
|
||||
stackTrace = "test\nstack\ntrace"
|
||||
)
|
||||
|
||||
val foundMatch = db.crashes.anyMatch(
|
||||
listOf(
|
||||
CrashConfig.CrashPattern(namePattern = "Test", messagePattern = "Blah")
|
||||
),
|
||||
promptThreshold = currentTime
|
||||
)
|
||||
|
||||
foundMatch assertIs false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun crashTable_matchesNameButNotStackTracePattern() {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
db.crashes.saveCrash(
|
||||
createdAt = currentTime,
|
||||
name = "TestName",
|
||||
message = "Test Message",
|
||||
stackTrace = "test\nstack\ntrace"
|
||||
)
|
||||
|
||||
val foundMatch = db.crashes.anyMatch(
|
||||
listOf(
|
||||
CrashConfig.CrashPattern(namePattern = "Test", stackTracePattern = "Blah")
|
||||
),
|
||||
promptThreshold = currentTime
|
||||
)
|
||||
|
||||
foundMatch assertIs false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun crashTable_matchesNamePatternButPromptedTooRecently() {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
db.crashes.saveCrash(
|
||||
createdAt = currentTime,
|
||||
name = "TestName",
|
||||
message = "Test Message",
|
||||
stackTrace = "test\nstack\ntrace"
|
||||
)
|
||||
|
||||
db.writableDatabase
|
||||
.update(LogDatabase.CrashTable.TABLE_NAME)
|
||||
.values(LogDatabase.CrashTable.LAST_PROMPTED_AT to currentTime)
|
||||
.run()
|
||||
|
||||
val foundMatch = db.crashes.anyMatch(
|
||||
listOf(
|
||||
CrashConfig.CrashPattern(namePattern = "Test")
|
||||
),
|
||||
promptThreshold = currentTime - 100
|
||||
)
|
||||
|
||||
foundMatch assertIs false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun crashTable_noMatches() {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
val foundMatch = db.crashes.anyMatch(
|
||||
listOf(
|
||||
CrashConfig.CrashPattern(namePattern = "Test")
|
||||
),
|
||||
promptThreshold = currentTime - 100
|
||||
)
|
||||
|
||||
foundMatch assertIs false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun crashTable_updatesLastPromptTime() {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
db.crashes.saveCrash(
|
||||
createdAt = currentTime,
|
||||
name = "TestName",
|
||||
message = "Test Message",
|
||||
stackTrace = "test\nstack\ntrace"
|
||||
)
|
||||
|
||||
db.crashes.saveCrash(
|
||||
createdAt = currentTime,
|
||||
name = "XXX",
|
||||
message = "XXX",
|
||||
stackTrace = "XXX"
|
||||
)
|
||||
|
||||
db.crashes.markAsPrompted(
|
||||
listOf(
|
||||
CrashConfig.CrashPattern(namePattern = "Test")
|
||||
),
|
||||
promptedAt = currentTime
|
||||
)
|
||||
|
||||
db.writableDatabase
|
||||
.select(LogDatabase.CrashTable.NAME, LogDatabase.CrashTable.LAST_PROMPTED_AT)
|
||||
.from(LogDatabase.CrashTable.TABLE_NAME)
|
||||
.run()
|
||||
.forEach {
|
||||
if (it.requireNonNullString(LogDatabase.CrashTable.NAME) == "TestName") {
|
||||
it.requireLong(LogDatabase.CrashTable.LAST_PROMPTED_AT) assertIs currentTime
|
||||
} else {
|
||||
it.requireLong(LogDatabase.CrashTable.LAST_PROMPTED_AT) assertIs 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,9 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
import org.whispersystems.signalservice.api.push.ACI
|
||||
import org.whispersystems.signalservice.api.push.PNI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import java.util.UUID
|
||||
|
||||
@Suppress("ClassName")
|
||||
@@ -33,7 +34,7 @@ class MessageTableTest_gifts {
|
||||
SignalStore.account().setAci(localAci)
|
||||
SignalStore.account().setPni(localPni)
|
||||
|
||||
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())) }
|
||||
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -48,7 +49,7 @@ class MessageTableTest_gifts {
|
||||
val messageId = MmsHelper.insert(
|
||||
recipient = Recipient.resolved(recipients[0]),
|
||||
sentTimeMillis = 1,
|
||||
giftBadge = GiftBadge()
|
||||
giftBadge = GiftBadge.getDefaultInstance()
|
||||
)
|
||||
|
||||
val result = mms.setOutgoingGiftsRevealed(listOf(messageId))
|
||||
@@ -62,7 +63,7 @@ class MessageTableTest_gifts {
|
||||
val messageId = MmsHelper.insert(
|
||||
recipient = Recipient.resolved(recipients[0]),
|
||||
sentTimeMillis = 1,
|
||||
giftBadge = GiftBadge()
|
||||
giftBadge = GiftBadge.getDefaultInstance()
|
||||
)
|
||||
mms.setOutgoingGiftsRevealed(listOf(messageId))
|
||||
|
||||
@@ -76,13 +77,13 @@ class MessageTableTest_gifts {
|
||||
val messageId = MmsHelper.insert(
|
||||
recipient = Recipient.resolved(recipients[0]),
|
||||
sentTimeMillis = 1,
|
||||
giftBadge = GiftBadge()
|
||||
giftBadge = GiftBadge.getDefaultInstance()
|
||||
)
|
||||
|
||||
MmsHelper.insert(
|
||||
recipient = Recipient.resolved(recipients[0]),
|
||||
sentTimeMillis = 2,
|
||||
giftBadge = GiftBadge()
|
||||
sentTimeMillis = 1,
|
||||
giftBadge = GiftBadge.getDefaultInstance()
|
||||
)
|
||||
|
||||
val result = mms.setOutgoingGiftsRevealed(listOf(messageId))
|
||||
@@ -96,13 +97,13 @@ class MessageTableTest_gifts {
|
||||
val messageId = MmsHelper.insert(
|
||||
recipient = Recipient.resolved(recipients[0]),
|
||||
sentTimeMillis = 1,
|
||||
giftBadge = GiftBadge()
|
||||
giftBadge = GiftBadge.getDefaultInstance()
|
||||
)
|
||||
|
||||
val messageId2 = MmsHelper.insert(
|
||||
recipient = Recipient.resolved(recipients[0]),
|
||||
sentTimeMillis = 2,
|
||||
giftBadge = GiftBadge()
|
||||
sentTimeMillis = 1,
|
||||
giftBadge = GiftBadge.getDefaultInstance()
|
||||
)
|
||||
|
||||
val result = mms.setOutgoingGiftsRevealed(listOf(messageId, messageId2))
|
||||
@@ -115,18 +116,18 @@ class MessageTableTest_gifts {
|
||||
val messageId = MmsHelper.insert(
|
||||
recipient = Recipient.resolved(recipients[0]),
|
||||
sentTimeMillis = 1,
|
||||
giftBadge = GiftBadge()
|
||||
giftBadge = GiftBadge.getDefaultInstance()
|
||||
)
|
||||
|
||||
val messageId2 = MmsHelper.insert(
|
||||
recipient = Recipient.resolved(recipients[0]),
|
||||
sentTimeMillis = 2,
|
||||
giftBadge = GiftBadge()
|
||||
sentTimeMillis = 1,
|
||||
giftBadge = GiftBadge.getDefaultInstance()
|
||||
)
|
||||
|
||||
MmsHelper.insert(
|
||||
recipient = Recipient.resolved(recipients[0]),
|
||||
sentTimeMillis = 3,
|
||||
sentTimeMillis = 1,
|
||||
giftBadge = null
|
||||
)
|
||||
|
||||
@@ -140,18 +141,18 @@ class MessageTableTest_gifts {
|
||||
val messageId = MmsHelper.insert(
|
||||
recipient = Recipient.resolved(recipients[0]),
|
||||
sentTimeMillis = 1,
|
||||
giftBadge = GiftBadge()
|
||||
giftBadge = GiftBadge.getDefaultInstance()
|
||||
)
|
||||
|
||||
val messageId2 = MmsHelper.insert(
|
||||
recipient = Recipient.resolved(recipients[0]),
|
||||
sentTimeMillis = 2,
|
||||
giftBadge = GiftBadge()
|
||||
sentTimeMillis = 1,
|
||||
giftBadge = GiftBadge.getDefaultInstance()
|
||||
)
|
||||
|
||||
val messageId3 = MmsHelper.insert(
|
||||
recipient = Recipient.resolved(recipients[0]),
|
||||
sentTimeMillis = 3,
|
||||
sentTimeMillis = 1,
|
||||
giftBadge = null
|
||||
)
|
||||
|
||||
@@ -165,18 +166,18 @@ class MessageTableTest_gifts {
|
||||
MmsHelper.insert(
|
||||
recipient = Recipient.resolved(recipients[0]),
|
||||
sentTimeMillis = 1,
|
||||
giftBadge = GiftBadge()
|
||||
giftBadge = GiftBadge.getDefaultInstance()
|
||||
)
|
||||
|
||||
MmsHelper.insert(
|
||||
recipient = Recipient.resolved(recipients[0]),
|
||||
sentTimeMillis = 2,
|
||||
giftBadge = GiftBadge()
|
||||
sentTimeMillis = 1,
|
||||
giftBadge = GiftBadge.getDefaultInstance()
|
||||
)
|
||||
|
||||
val messageId3 = MmsHelper.insert(
|
||||
recipient = Recipient.resolved(recipients[0]),
|
||||
sentTimeMillis = 3,
|
||||
sentTimeMillis = 1,
|
||||
giftBadge = null
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.database
|
||||
import org.thoughtcrime.securesms.database.model.ParentStoryId
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
|
||||
import org.thoughtcrime.securesms.mms.IncomingMessage
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import java.util.Optional
|
||||
@@ -17,6 +17,7 @@ object MmsHelper {
|
||||
recipient: Recipient = Recipient.UNKNOWN,
|
||||
body: String = "body",
|
||||
sentTimeMillis: Long = System.currentTimeMillis(),
|
||||
subscriptionId: Int = -1,
|
||||
expiresIn: Long = 0,
|
||||
viewOnce: Boolean = false,
|
||||
distributionType: Int = ThreadTable.DistributionTypes.DEFAULT,
|
||||
@@ -31,6 +32,7 @@ object MmsHelper {
|
||||
recipient = recipient,
|
||||
body = body,
|
||||
timestamp = sentTimeMillis,
|
||||
subscriptionId = subscriptionId,
|
||||
expiresIn = expiresIn,
|
||||
viewOnce = viewOnce,
|
||||
distributionType = distributionType,
|
||||
@@ -55,9 +57,9 @@ object MmsHelper {
|
||||
}
|
||||
|
||||
fun insert(
|
||||
message: IncomingMessage,
|
||||
message: IncomingMediaMessage,
|
||||
threadId: Long
|
||||
): Optional<MessageTable.InsertResult> {
|
||||
return SignalDatabase.messages.insertMessageInbox(message, threadId)
|
||||
return SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, threadId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,12 @@ import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||
import org.thoughtcrime.securesms.database.model.ParentStoryId
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.mms.IncomingMessage
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
import org.whispersystems.signalservice.api.push.ACI
|
||||
import org.whispersystems.signalservice.api.push.PNI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@@ -44,7 +45,7 @@ class MmsTableTest_stories {
|
||||
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())) }
|
||||
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())) }
|
||||
releaseChannelRecipient = Recipient.resolved(SignalDatabase.recipients.insertReleaseChannelRecipient())
|
||||
|
||||
SignalStore.releaseChannelValues().setReleaseChannelRecipientId(releaseChannelRecipient.id)
|
||||
@@ -73,8 +74,7 @@ class MmsTableTest_stories {
|
||||
)
|
||||
|
||||
MmsHelper.insert(
|
||||
IncomingMessage(
|
||||
type = MessageType.NORMAL,
|
||||
IncomingMediaMessage(
|
||||
from = sender,
|
||||
sentTimeMillis = 2,
|
||||
serverTimeMillis = 2,
|
||||
@@ -96,8 +96,7 @@ class MmsTableTest_stories {
|
||||
// GIVEN
|
||||
val sender = recipients[0]
|
||||
val messageId = MmsHelper.insert(
|
||||
IncomingMessage(
|
||||
type = MessageType.NORMAL,
|
||||
IncomingMediaMessage(
|
||||
from = sender,
|
||||
sentTimeMillis = 2,
|
||||
serverTimeMillis = 2,
|
||||
@@ -124,8 +123,7 @@ class MmsTableTest_stories {
|
||||
// GIVEN
|
||||
val messageIds = recipients.take(5).map {
|
||||
MmsHelper.insert(
|
||||
IncomingMessage(
|
||||
type = MessageType.NORMAL,
|
||||
IncomingMediaMessage(
|
||||
from = it,
|
||||
sentTimeMillis = 2,
|
||||
serverTimeMillis = 2,
|
||||
@@ -157,8 +155,7 @@ class MmsTableTest_stories {
|
||||
val unviewedIds: List<Long> = (0 until 5).map {
|
||||
Thread.sleep(5)
|
||||
MmsHelper.insert(
|
||||
IncomingMessage(
|
||||
type = MessageType.NORMAL,
|
||||
IncomingMediaMessage(
|
||||
from = recipients[it],
|
||||
sentTimeMillis = System.currentTimeMillis(),
|
||||
serverTimeMillis = 2,
|
||||
@@ -172,8 +169,7 @@ class MmsTableTest_stories {
|
||||
val viewedIds: List<Long> = (0 until 5).map {
|
||||
Thread.sleep(5)
|
||||
MmsHelper.insert(
|
||||
IncomingMessage(
|
||||
type = MessageType.NORMAL,
|
||||
IncomingMediaMessage(
|
||||
from = recipients[it],
|
||||
sentTimeMillis = System.currentTimeMillis(),
|
||||
serverTimeMillis = 2,
|
||||
@@ -218,8 +214,7 @@ class MmsTableTest_stories {
|
||||
fun givenNoOutgoingStories_whenICheckIsOutgoingStoryAlreadyInDatabase_thenIExpectFalse() {
|
||||
// GIVEN
|
||||
MmsHelper.insert(
|
||||
IncomingMessage(
|
||||
type = MessageType.NORMAL,
|
||||
IncomingMediaMessage(
|
||||
from = recipients[0],
|
||||
sentTimeMillis = 200,
|
||||
serverTimeMillis = 2,
|
||||
@@ -258,7 +253,8 @@ class MmsTableTest_stories {
|
||||
val groupStoryId = MmsHelper.insert(
|
||||
recipient = myStory,
|
||||
sentTimeMillis = 200,
|
||||
storyType = StoryType.STORY_WITH_REPLIES
|
||||
storyType = StoryType.STORY_WITH_REPLIES,
|
||||
threadId = -1L
|
||||
)
|
||||
|
||||
// WHEN
|
||||
@@ -323,19 +319,19 @@ class MmsTableTest_stories {
|
||||
val groupStoryId = MmsHelper.insert(
|
||||
recipient = myStory,
|
||||
sentTimeMillis = 200,
|
||||
storyType = StoryType.STORY_WITH_REPLIES
|
||||
storyType = StoryType.STORY_WITH_REPLIES,
|
||||
threadId = -1L
|
||||
)
|
||||
|
||||
MmsHelper.insert(
|
||||
IncomingMessage(
|
||||
type = MessageType.NORMAL,
|
||||
IncomingMediaMessage(
|
||||
from = myStory.id,
|
||||
sentTimeMillis = 201,
|
||||
serverTimeMillis = 201,
|
||||
receivedTimeMillis = 202,
|
||||
parentStoryId = ParentStoryId.GroupReply(groupStoryId)
|
||||
),
|
||||
SignalDatabase.threads.getOrCreateThreadIdFor(myStory, ThreadTable.DistributionTypes.DEFAULT)
|
||||
-1
|
||||
)
|
||||
|
||||
// WHEN
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertNotNull
|
||||
import junit.framework.TestCase.assertNull
|
||||
import org.junit.Test
|
||||
import org.signal.core.util.readToSingleObject
|
||||
import org.signal.core.util.requireLongOrNull
|
||||
import org.signal.core.util.select
|
||||
import org.signal.core.util.update
|
||||
import org.signal.libsignal.protocol.ecc.Curve
|
||||
import org.signal.libsignal.protocol.state.PreKeyRecord
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
import java.util.UUID
|
||||
|
||||
class OneTimePreKeyTableTest {
|
||||
|
||||
private val aci: ACI = ACI.from(UUID.randomUUID())
|
||||
private val pni: PNI = PNI.from(UUID.randomUUID())
|
||||
|
||||
@Test
|
||||
fun markAllStaleIfNecessary_onlyUpdatesMatchingAccountAndZeroValues() {
|
||||
insertTestRecord(aci, id = 1)
|
||||
insertTestRecord(aci, id = 2)
|
||||
insertTestRecord(aci, id = 3, staleTime = 42)
|
||||
insertTestRecord(pni, id = 4)
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
SignalDatabase.oneTimePreKeys.markAllStaleIfNecessary(aci, now)
|
||||
|
||||
assertEquals(now, getStaleTime(aci, 1))
|
||||
assertEquals(now, getStaleTime(aci, 2))
|
||||
assertEquals(42L, getStaleTime(aci, 3))
|
||||
assertEquals(0L, getStaleTime(pni, 4))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteAllStaleBefore_deleteOldBeforeThreshold() {
|
||||
insertTestRecord(aci, id = 1, staleTime = 10)
|
||||
insertTestRecord(aci, id = 2, staleTime = 10)
|
||||
insertTestRecord(aci, id = 3, staleTime = 10)
|
||||
insertTestRecord(aci, id = 4, staleTime = 15)
|
||||
insertTestRecord(aci, id = 5, staleTime = 0)
|
||||
|
||||
SignalDatabase.oneTimePreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 0)
|
||||
|
||||
assertNull(getStaleTime(aci, 1))
|
||||
assertNull(getStaleTime(aci, 2))
|
||||
assertNull(getStaleTime(aci, 3))
|
||||
assertNotNull(getStaleTime(aci, 4))
|
||||
assertNotNull(getStaleTime(aci, 5))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteAllStaleBefore_neverDeleteStaleOfZero() {
|
||||
insertTestRecord(aci, id = 1, staleTime = 0)
|
||||
insertTestRecord(aci, id = 2, staleTime = 0)
|
||||
insertTestRecord(aci, id = 3, staleTime = 0)
|
||||
insertTestRecord(aci, id = 4, staleTime = 0)
|
||||
insertTestRecord(aci, id = 5, staleTime = 0)
|
||||
|
||||
SignalDatabase.oneTimePreKeys.deleteAllStaleBefore(aci, threshold = 10, minCount = 0)
|
||||
|
||||
assertNotNull(getStaleTime(aci, 1))
|
||||
assertNotNull(getStaleTime(aci, 2))
|
||||
assertNotNull(getStaleTime(aci, 3))
|
||||
assertNotNull(getStaleTime(aci, 4))
|
||||
assertNotNull(getStaleTime(aci, 5))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteAllStaleBefore_respectMinCount() {
|
||||
insertTestRecord(aci, id = 1, staleTime = 10)
|
||||
insertTestRecord(aci, id = 2, staleTime = 10)
|
||||
insertTestRecord(aci, id = 3, staleTime = 10)
|
||||
insertTestRecord(aci, id = 4, staleTime = 10)
|
||||
insertTestRecord(aci, id = 5, staleTime = 10)
|
||||
|
||||
SignalDatabase.oneTimePreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 3)
|
||||
|
||||
assertNull(getStaleTime(aci, 1))
|
||||
assertNull(getStaleTime(aci, 2))
|
||||
assertNotNull(getStaleTime(aci, 3))
|
||||
assertNotNull(getStaleTime(aci, 4))
|
||||
assertNotNull(getStaleTime(aci, 5))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteAllStaleBefore_respectAccount() {
|
||||
insertTestRecord(aci, id = 1, staleTime = 10)
|
||||
insertTestRecord(aci, id = 2, staleTime = 10)
|
||||
insertTestRecord(aci, id = 3, staleTime = 10)
|
||||
|
||||
insertTestRecord(pni, id = 4, staleTime = 10)
|
||||
insertTestRecord(pni, id = 5, staleTime = 10)
|
||||
|
||||
SignalDatabase.oneTimePreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 2)
|
||||
|
||||
assertNull(getStaleTime(aci, 1))
|
||||
assertNotNull(getStaleTime(aci, 2))
|
||||
assertNotNull(getStaleTime(aci, 3))
|
||||
assertNotNull(getStaleTime(pni, 4))
|
||||
assertNotNull(getStaleTime(pni, 5))
|
||||
}
|
||||
|
||||
private fun insertTestRecord(account: ServiceId, id: Int, staleTime: Long = 0) {
|
||||
SignalDatabase.oneTimePreKeys.insert(
|
||||
serviceId = account,
|
||||
keyId = id,
|
||||
record = PreKeyRecord(id, Curve.generateKeyPair())
|
||||
)
|
||||
|
||||
val count = SignalDatabase.rawDatabase
|
||||
.update(OneTimePreKeyTable.TABLE_NAME)
|
||||
.values(OneTimePreKeyTable.STALE_TIMESTAMP to staleTime)
|
||||
.where("${OneTimePreKeyTable.ACCOUNT_ID} = ? AND ${OneTimePreKeyTable.KEY_ID} = $id", account)
|
||||
.run()
|
||||
|
||||
assertEquals(1, count)
|
||||
}
|
||||
|
||||
private fun getStaleTime(account: ServiceId, id: Int): Long? {
|
||||
return SignalDatabase.rawDatabase
|
||||
.select(OneTimePreKeyTable.STALE_TIMESTAMP)
|
||||
.from(OneTimePreKeyTable.TABLE_NAME)
|
||||
.where("${OneTimePreKeyTable.ACCOUNT_ID} = ? AND ${OneTimePreKeyTable.KEY_ID} = $id", account)
|
||||
.run()
|
||||
.readToSingleObject { it.requireLongOrNull(OneTimePreKeyTable.STALE_TIMESTAMP) }
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.FeatureFlagsAccessor
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
import org.whispersystems.signalservice.api.push.ACI
|
||||
import org.whispersystems.signalservice.api.push.PNI
|
||||
import java.util.UUID
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@@ -24,14 +24,14 @@ class RecipientTableTest {
|
||||
val harness = SignalActivityRule()
|
||||
|
||||
@Test
|
||||
fun givenAHiddenRecipient_whenIQueryAllContacts_thenIExpectHiddenToBeReturned() {
|
||||
fun givenAHiddenRecipient_whenIQueryAllContacts_thenIDoNotExpectHiddenToBeReturned() {
|
||||
val hiddenRecipient = harness.others[0]
|
||||
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
|
||||
SignalDatabase.recipients.markHidden(hiddenRecipient)
|
||||
|
||||
val results = SignalDatabase.recipients.queryAllContacts("Hidden")!!
|
||||
|
||||
assertEquals(1, results.count)
|
||||
assertEquals(0, results.count)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -173,10 +173,10 @@ class RecipientTableTest {
|
||||
|
||||
SignalDatabase.recipients.markUnregistered(mainId)
|
||||
|
||||
val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
|
||||
val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get()
|
||||
|
||||
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
|
||||
val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get()
|
||||
val byPni: RecipientId = SignalDatabase.recipients.getByServiceId(PNI_A).get()
|
||||
|
||||
assertEquals(mainId, byAci)
|
||||
assertEquals(byE164, byPni)
|
||||
@@ -192,10 +192,10 @@ class RecipientTableTest {
|
||||
|
||||
SignalDatabase.recipients.splitForStorageSync(mainRecord.storageId!!)
|
||||
|
||||
val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
|
||||
val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get()
|
||||
|
||||
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
|
||||
val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get()
|
||||
val byPni: RecipientId = SignalDatabase.recipients.getByServiceId(PNI_A).get()
|
||||
|
||||
assertEquals(mainId, byAci)
|
||||
assertEquals(byE164, byPni)
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.storage.StorageRecordUpdate
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncModels
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
import org.thoughtcrime.securesms.testing.assertIs
|
||||
import org.thoughtcrime.securesms.util.MessageTableTestUtils
|
||||
import org.whispersystems.signalservice.api.storage.SignalContactRecord
|
||||
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord
|
||||
|
||||
@Suppress("ClassName")
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class RecipientTableTest_applyStorageSyncContactUpdate {
|
||||
@get:Rule
|
||||
val harness = SignalActivityRule()
|
||||
|
||||
@Test
|
||||
fun insertMessageOnVerifiedToDefault() {
|
||||
// GIVEN
|
||||
val identities = ApplicationDependencies.getProtocolStore().aci().identities()
|
||||
val other = Recipient.resolved(harness.others[0])
|
||||
|
||||
MmsHelper.insert(recipient = other)
|
||||
identities.setVerified(other.id, harness.othersKeys[0].publicKey, IdentityTable.VerifiedStatus.VERIFIED)
|
||||
|
||||
val oldRecord: SignalContactRecord = StorageSyncModels.localToRemoteRecord(SignalDatabase.recipients.getRecordForSync(harness.others[0])!!).contact.get()
|
||||
|
||||
val newProto = oldRecord
|
||||
.toProto()
|
||||
.newBuilder()
|
||||
.identityState(ContactRecord.IdentityState.DEFAULT)
|
||||
.build()
|
||||
val newRecord = SignalContactRecord(oldRecord.id, newProto)
|
||||
|
||||
val update = StorageRecordUpdate<SignalContactRecord>(oldRecord, newRecord)
|
||||
|
||||
// WHEN
|
||||
val oldVerifiedStatus: IdentityTable.VerifiedStatus = identities.getIdentityRecord(other.id).get().verifiedStatus
|
||||
SignalDatabase.recipients.applyStorageSyncContactUpdate(update)
|
||||
val newVerifiedStatus: IdentityTable.VerifiedStatus = identities.getIdentityRecord(other.id).get().verifiedStatus
|
||||
|
||||
// THEN
|
||||
oldVerifiedStatus assertIs IdentityTable.VerifiedStatus.VERIFIED
|
||||
newVerifiedStatus assertIs IdentityTable.VerifiedStatus.DEFAULT
|
||||
|
||||
val messages = MessageTableTestUtils.getMessages(SignalDatabase.threads.getThreadIdFor(other.id)!!)
|
||||
messages.first().isIdentityDefault assertIs true
|
||||
}
|
||||
}
|
||||
@@ -7,17 +7,13 @@ import org.hamcrest.MatcherAssert
|
||||
import org.hamcrest.Matchers
|
||||
import org.junit.Assert
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.SqlUtil
|
||||
import org.signal.core.util.exists
|
||||
import org.signal.core.util.orNull
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.requireNonNullString
|
||||
import org.signal.core.util.select
|
||||
@@ -36,19 +32,21 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.mms.IncomingMessage
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
|
||||
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage
|
||||
import org.thoughtcrime.securesms.util.Base64
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.FeatureFlagsAccessor
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
import org.whispersystems.signalservice.api.push.ACI
|
||||
import org.whispersystems.signalservice.api.push.PNI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
|
||||
@Suppress("ClassName")
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class RecipientTableTest_getAndPossiblyMerge {
|
||||
|
||||
@@ -85,30 +83,6 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
val record = SignalDatabase.recipients.getRecord(id)
|
||||
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
|
||||
}
|
||||
|
||||
test("e164+pni insert") {
|
||||
val id = process(E164_A, PNI_A, null)
|
||||
expect(E164_A, PNI_A, null)
|
||||
|
||||
val record = SignalDatabase.recipients.getRecord(id)
|
||||
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
|
||||
}
|
||||
|
||||
test("e164+aci insert") {
|
||||
val id = process(E164_A, null, ACI_A)
|
||||
expect(E164_A, null, ACI_A)
|
||||
|
||||
val record = SignalDatabase.recipients.getRecord(id)
|
||||
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
|
||||
}
|
||||
|
||||
test("e164+pni+aci insert") {
|
||||
val id = process(E164_A, PNI_A, ACI_A)
|
||||
expect(E164_A, PNI_A, ACI_A)
|
||||
|
||||
val record = SignalDatabase.recipients.getRecord(id)
|
||||
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -118,9 +92,9 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
expect(E164_A, null, null)
|
||||
}
|
||||
|
||||
test("no match, pni-only") {
|
||||
process(null, PNI_A, null)
|
||||
expect(null, PNI_A, null)
|
||||
test("no match, e164 and pni") {
|
||||
process(E164_A, PNI_A, null)
|
||||
expect(E164_A, PNI_A, null)
|
||||
}
|
||||
|
||||
test("no match, aci-only") {
|
||||
@@ -128,11 +102,6 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
expect(null, null, ACI_A)
|
||||
}
|
||||
|
||||
test("no match, e164 and pni") {
|
||||
process(E164_A, PNI_A, null)
|
||||
expect(E164_A, PNI_A, null)
|
||||
}
|
||||
|
||||
test("no match, e164 and aci") {
|
||||
process(E164_A, null, ACI_A)
|
||||
expect(E164_A, null, ACI_A)
|
||||
@@ -142,30 +111,6 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
process(null, null, null)
|
||||
}
|
||||
|
||||
test("pni matches, pni+aci provided, no pni session") {
|
||||
given(E164_A, PNI_A, null)
|
||||
process(null, PNI_A, ACI_A)
|
||||
expect(E164_A, PNI_A, ACI_A)
|
||||
|
||||
expectNoSessionSwitchoverEvent()
|
||||
}
|
||||
|
||||
test("pni matches, pni+aci provided, pni session") {
|
||||
given(E164_A, PNI_A, null, pniSession = true)
|
||||
process(null, PNI_A, ACI_A)
|
||||
expect(E164_A, PNI_A, ACI_A)
|
||||
|
||||
expectSessionSwitchoverEvent(E164_A)
|
||||
}
|
||||
|
||||
test("pni matches, pni+aci provided, pni session, pni-verified") {
|
||||
given(E164_A, PNI_A, null, pniSession = true)
|
||||
process(null, PNI_A, ACI_A, pniVerified = true)
|
||||
expect(E164_A, PNI_A, ACI_A)
|
||||
|
||||
expectNoSessionSwitchoverEvent()
|
||||
}
|
||||
|
||||
test("no match, all fields") {
|
||||
process(E164_A, PNI_A, ACI_A)
|
||||
expect(E164_A, PNI_A, ACI_A)
|
||||
@@ -221,12 +166,6 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
expectSessionSwitchoverEvent(E164_A)
|
||||
}
|
||||
|
||||
test("e164 and pni matches, all provided, new aci, existing pni session, pni-verified") {
|
||||
given(E164_A, PNI_A, null, pniSession = true)
|
||||
process(E164_A, PNI_A, ACI_A, pniVerified = true)
|
||||
expect(E164_A, PNI_A, ACI_A)
|
||||
}
|
||||
|
||||
test("e164 and aci matches, all provided, new pni") {
|
||||
given(E164_A, null, ACI_A)
|
||||
process(E164_A, PNI_A, ACI_A)
|
||||
@@ -369,26 +308,6 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
expectChangeNumberEvent()
|
||||
}
|
||||
|
||||
test("steal, pni is changed") {
|
||||
given(E164_A, PNI_B, ACI_A)
|
||||
given(E164_B, PNI_A, null)
|
||||
|
||||
process(E164_A, PNI_A, null)
|
||||
|
||||
expect(E164_A, PNI_A, ACI_A)
|
||||
expect(E164_B, null, null)
|
||||
}
|
||||
|
||||
test("steal, pni is changed, aci left behind") {
|
||||
given(E164_B, PNI_A, ACI_A)
|
||||
given(E164_A, PNI_B, null)
|
||||
|
||||
process(E164_A, PNI_A, null)
|
||||
|
||||
expect(E164_B, null, ACI_A)
|
||||
expect(E164_A, PNI_A, null)
|
||||
}
|
||||
|
||||
test("steal, e164+pni & e164+pni, no aci provided, no pni session") {
|
||||
given(E164_A, PNI_B, null)
|
||||
given(E164_B, PNI_A, null)
|
||||
@@ -433,7 +352,7 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
expectChangeNumberEvent()
|
||||
}
|
||||
|
||||
test("steal, e164 & pni+e164, no aci provided, pni session exists") {
|
||||
test("steal, e164 & pni+e164, no aci provided") {
|
||||
val id1 = given(E164_A, null, null)
|
||||
val id2 = given(E164_B, PNI_A, null, pniSession = true)
|
||||
|
||||
@@ -446,16 +365,6 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
expectSessionSwitchoverEvent(id2, E164_B)
|
||||
}
|
||||
|
||||
test("steal, e164 & pni+e164, no aci provided, no pni session") {
|
||||
given(E164_A, null, null)
|
||||
given(E164_B, PNI_A, null)
|
||||
|
||||
process(E164_A, PNI_A, null)
|
||||
|
||||
expect(E164_A, PNI_A, null)
|
||||
expect(E164_B, null, null)
|
||||
}
|
||||
|
||||
test("steal, e164+pni+aci & e164+aci, no pni provided, change number") {
|
||||
given(E164_A, PNI_A, ACI_A)
|
||||
given(E164_B, null, ACI_B)
|
||||
@@ -468,76 +377,6 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
expectChangeNumberEvent()
|
||||
}
|
||||
|
||||
test("steal, e164+aci & aci, no pni provided, existing aci session") {
|
||||
given(E164_A, null, ACI_A, aciSession = true)
|
||||
given(null, null, ACI_B)
|
||||
|
||||
process(E164_A, null, ACI_B)
|
||||
|
||||
expect(null, null, ACI_A)
|
||||
expect(E164_A, null, ACI_B)
|
||||
|
||||
expectNoSessionSwitchoverEvent()
|
||||
}
|
||||
|
||||
test("steal, e164+pni+aci & aci, no pni provided, existing aci session") {
|
||||
given(E164_A, PNI_A, ACI_A, aciSession = true)
|
||||
given(null, null, ACI_B)
|
||||
|
||||
process(E164_A, null, ACI_B)
|
||||
|
||||
expect(null, PNI_A, ACI_A)
|
||||
expect(E164_A, null, ACI_B)
|
||||
|
||||
expectNoSessionSwitchoverEvent()
|
||||
}
|
||||
|
||||
test("steal, e164+pni+aci & aci, no pni provided, existing pni session") {
|
||||
given(E164_A, PNI_A, ACI_A, pniSession = true)
|
||||
given(null, null, ACI_B)
|
||||
|
||||
process(E164_A, null, ACI_B)
|
||||
|
||||
expect(null, PNI_A, ACI_A)
|
||||
expect(E164_A, null, ACI_B)
|
||||
|
||||
expectNoSessionSwitchoverEvent()
|
||||
}
|
||||
|
||||
test("steal, e164+pni & aci, no pni provided, no pni session") {
|
||||
given(E164_A, PNI_A, null)
|
||||
given(null, null, ACI_A)
|
||||
|
||||
process(E164_A, null, ACI_A)
|
||||
|
||||
expect(null, PNI_A, null)
|
||||
expect(E164_A, null, ACI_A)
|
||||
}
|
||||
|
||||
test("steal, e164+pni & aci, no pni provided, pni session") {
|
||||
given(E164_A, PNI_A, null, pniSession = true)
|
||||
given(null, null, ACI_A)
|
||||
|
||||
process(E164_A, null, ACI_A)
|
||||
|
||||
expect(null, PNI_A, null)
|
||||
expect(E164_A, null, ACI_A)
|
||||
|
||||
expectNoSessionSwitchoverEvent()
|
||||
}
|
||||
|
||||
test("steal, e164+pni+aci * pni+aci, all provided, aci sessions but not pni sessions, no SSE expected") {
|
||||
given(E164_A, PNI_A, ACI_A, createThread = true, aciSession = true, pniSession = false)
|
||||
given(null, PNI_B, ACI_B, createThread = false, aciSession = true, pniSession = false)
|
||||
|
||||
process(E164_A, PNI_B, ACI_A)
|
||||
|
||||
expect(E164_A, PNI_B, ACI_A)
|
||||
expect(null, null, ACI_B)
|
||||
|
||||
expectNoSessionSwitchoverEvent()
|
||||
}
|
||||
|
||||
test("merge, e164 & pni & aci, all provided") {
|
||||
given(E164_A, null, null)
|
||||
given(null, PNI_A, null)
|
||||
@@ -662,7 +501,7 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
expectThreadMergeEvent(E164_A)
|
||||
}
|
||||
|
||||
test("merge, e164+pni & aci, pni session, thread merge shadows SSE") {
|
||||
test("merge, e164+pni & aci, pni session, thread merge shadows") {
|
||||
given(E164_A, PNI_A, null, pniSession = true)
|
||||
given(null, null, ACI_A)
|
||||
|
||||
@@ -760,18 +599,6 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
expectThreadMergeEvent(E164_A)
|
||||
}
|
||||
|
||||
test("merge, e164 + pni reassigned, aci abandoned") {
|
||||
given(E164_A, PNI_A, ACI_A)
|
||||
given(E164_B, PNI_B, ACI_B)
|
||||
|
||||
process(E164_A, PNI_A, ACI_B)
|
||||
|
||||
expect(null, null, ACI_A)
|
||||
expect(E164_A, PNI_A, ACI_B)
|
||||
|
||||
expectChangeNumberEvent()
|
||||
}
|
||||
|
||||
test("local user, local e164 and aci provided, changeSelf=false, leave e164 alone") {
|
||||
given(E164_SELF, null, ACI_SELF)
|
||||
given(null, null, ACI_A)
|
||||
@@ -793,22 +620,6 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
process(E164_A, null, ACI_SELF, changeSelf = true)
|
||||
expect(E164_A, null, ACI_SELF)
|
||||
}
|
||||
|
||||
test("local user, local e164+aci provided, changeSelf=false, leave pni alone") {
|
||||
given(E164_SELF, PNI_SELF, ACI_SELF)
|
||||
|
||||
process(E164_SELF, PNI_A, ACI_A)
|
||||
|
||||
expect(E164_SELF, PNI_SELF, ACI_SELF)
|
||||
}
|
||||
|
||||
test("local user, local e164+aci provided, changeSelf=false, leave pni alone") {
|
||||
given(E164_SELF, PNI_A, ACI_SELF)
|
||||
|
||||
process(E164_SELF, PNI_SELF, ACI_A)
|
||||
|
||||
expect(E164_SELF, PNI_A, ACI_SELF)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -825,9 +636,9 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
val smsId2: Long = SignalDatabase.messages.insertMessageInbox(smsMessage(sender = recipientIdE164, time = 1, body = "1")).get().messageId
|
||||
val smsId3: Long = SignalDatabase.messages.insertMessageInbox(smsMessage(sender = recipientIdAci, time = 2, body = "2")).get().messageId
|
||||
|
||||
val mmsId1: Long = SignalDatabase.messages.insertMessageInbox(mmsMessage(sender = recipientIdAci, time = 3, body = "3"), -1).get().messageId
|
||||
val mmsId2: Long = SignalDatabase.messages.insertMessageInbox(mmsMessage(sender = recipientIdE164, time = 4, body = "4"), -1).get().messageId
|
||||
val mmsId3: Long = SignalDatabase.messages.insertMessageInbox(mmsMessage(sender = recipientIdAci, time = 5, body = "5"), -1).get().messageId
|
||||
val mmsId1: Long = SignalDatabase.messages.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdAci, time = 3, body = "3"), -1).get().messageId
|
||||
val mmsId2: Long = SignalDatabase.messages.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdE164, time = 4, body = "4"), -1).get().messageId
|
||||
val mmsId3: Long = SignalDatabase.messages.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdAci, time = 5, body = "5"), -1).get().messageId
|
||||
|
||||
val threadIdAci: Long = SignalDatabase.threads.getThreadIdFor(recipientIdAci)!!
|
||||
val threadIdE164: Long = SignalDatabase.threads.getThreadIdFor(recipientIdE164)!!
|
||||
@@ -947,42 +758,18 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
MatcherAssert.assertThat("Distribution list should have updated $recipientIdE164 to $recipientIdAci", updatedList.members, Matchers.containsInAnyOrder(recipientIdAci, recipientIdAciB))
|
||||
}
|
||||
|
||||
private fun smsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingMessage {
|
||||
return IncomingMessage(
|
||||
type = MessageType.NORMAL,
|
||||
from = sender,
|
||||
sentTimeMillis = time,
|
||||
serverTimeMillis = time,
|
||||
receivedTimeMillis = time,
|
||||
body = body,
|
||||
groupId = groupId.orNull(),
|
||||
isUnidentified = true
|
||||
)
|
||||
private fun smsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingTextMessage {
|
||||
return IncomingTextMessage(sender, 1, time, time, time, body, groupId, 0, true, null)
|
||||
}
|
||||
|
||||
private fun mmsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingMessage {
|
||||
return IncomingMessage(
|
||||
type = MessageType.NORMAL,
|
||||
from = sender,
|
||||
groupId = groupId.orNull(),
|
||||
body = body,
|
||||
sentTimeMillis = time,
|
||||
receivedTimeMillis = time,
|
||||
serverTimeMillis = time,
|
||||
isUnidentified = true
|
||||
)
|
||||
private fun mmsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingMediaMessage {
|
||||
return IncomingMediaMessage(sender, groupId, body, time, time, time, emptyList(), 0, 0, false, false, true, Optional.empty(), false, false)
|
||||
}
|
||||
|
||||
private fun identityKey(value: Byte): IdentityKey {
|
||||
val byteArray = ByteArray(32)
|
||||
byteArray[0] = value
|
||||
return identityKey(byteArray)
|
||||
}
|
||||
|
||||
private fun identityKey(value: ByteArray): IdentityKey {
|
||||
val bytes = ByteArray(33)
|
||||
bytes[0] = 0x05
|
||||
value.copyInto(bytes, 1)
|
||||
bytes[1] = value
|
||||
return IdentityKey(bytes)
|
||||
}
|
||||
|
||||
@@ -1070,7 +857,6 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
}
|
||||
|
||||
ApplicationDependencies.getRecipientCache().clear()
|
||||
ApplicationDependencies.getRecipientCache().clearSelf()
|
||||
RecipientId.clearCache()
|
||||
}
|
||||
|
||||
@@ -1079,15 +865,14 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
pni: PNI?,
|
||||
aci: ACI?,
|
||||
createThread: Boolean = true,
|
||||
pniSession: Boolean = false,
|
||||
aciSession: Boolean = false
|
||||
pniSession: Boolean = false
|
||||
): RecipientId {
|
||||
val id = insert(e164, pni, aci)
|
||||
generatedIds += id
|
||||
if (createThread) {
|
||||
// Create a thread and throw a dummy message in it so it doesn't get automatically deleted
|
||||
val result = SignalDatabase.messages.insertMessageInbox(smsMessage(sender = id, time = (Math.random() * 10000000).toLong(), body = "1"))
|
||||
SignalDatabase.threads.markAsActiveEarly(result.get().threadId)
|
||||
SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(id))
|
||||
SignalDatabase.messages.insertMessageInbox(IncomingEncryptedMessage(IncomingTextMessage(id, 1, 0, 0, 0, "", Optional.empty(), 0, false, ""), ""))
|
||||
}
|
||||
|
||||
if (pniSession) {
|
||||
@@ -1098,42 +883,11 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
SignalDatabase.sessions.store(pni, SignalProtocolAddress(pni.toString(), 1), SessionRecord())
|
||||
}
|
||||
|
||||
if (aciSession) {
|
||||
if (aci == null) {
|
||||
throw IllegalArgumentException("aciSession = true but aci is null!")
|
||||
}
|
||||
|
||||
SignalDatabase.sessions.store(aci, SignalProtocolAddress(aci.toString(), 1), SessionRecord())
|
||||
}
|
||||
|
||||
if (aci != null) {
|
||||
SignalDatabase.identities.saveIdentity(
|
||||
addressName = aci.toString(),
|
||||
recipientId = id,
|
||||
identityKey = identityKey(Util.getSecretBytes(32)),
|
||||
verifiedStatus = IdentityTable.VerifiedStatus.DEFAULT,
|
||||
firstUse = true,
|
||||
timestamp = 0,
|
||||
nonBlockingApproval = false
|
||||
)
|
||||
}
|
||||
if (pni != null) {
|
||||
SignalDatabase.identities.saveIdentity(
|
||||
addressName = pni.toString(),
|
||||
recipientId = id,
|
||||
identityKey = identityKey(Util.getSecretBytes(32)),
|
||||
verifiedStatus = IdentityTable.VerifiedStatus.DEFAULT,
|
||||
firstUse = true,
|
||||
timestamp = 0,
|
||||
nonBlockingApproval = false
|
||||
)
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
fun process(e164: String?, pni: PNI?, aci: ACI?, changeSelf: Boolean = false, pniVerified: Boolean = false): RecipientId {
|
||||
outputRecipientId = SignalDatabase.recipients.getAndPossiblyMerge(aci = aci, pni = pni, e164 = e164, pniVerified = pniVerified, changeSelf = changeSelf)
|
||||
outputRecipientId = SignalDatabase.recipients.getAndPossiblyMerge(serviceId = aci ?: pni, pni = pni, e164 = e164, pniVerified = pniVerified, changeSelf = changeSelf)
|
||||
generatedIds += outputRecipientId
|
||||
return outputRecipientId
|
||||
}
|
||||
@@ -1147,15 +901,15 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
val expected = RecipientTuple(
|
||||
e164 = e164,
|
||||
pni = pni,
|
||||
aci = aci
|
||||
serviceId = aci ?: pni
|
||||
)
|
||||
val actual = RecipientTuple(
|
||||
e164 = recipient.e164.orElse(null),
|
||||
pni = recipient.pni.orElse(null),
|
||||
aci = recipient.aci.orElse(null)
|
||||
serviceId = recipient.serviceId.orElse(null)
|
||||
)
|
||||
|
||||
assertEquals("Recipient $id did not match expected result!", expected, actual)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
fun expectDeleted() {
|
||||
@@ -1163,21 +917,21 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
}
|
||||
|
||||
fun expectDeleted(id: RecipientId) {
|
||||
val found = SignalDatabase.rawDatabase
|
||||
.exists(RecipientTable.TABLE_NAME)
|
||||
SignalDatabase.rawDatabase
|
||||
.select("1")
|
||||
.from(RecipientTable.TABLE_NAME)
|
||||
.where("${RecipientTable.ID} = ?", id)
|
||||
.run()
|
||||
|
||||
assertFalse("Expected $id to be deleted, but it's still present!", found)
|
||||
.use { !it.moveToFirst() }
|
||||
}
|
||||
|
||||
fun expectChangeNumberEvent() {
|
||||
assertEquals("Missing change number event!", 1, SignalDatabase.messages.getChangeNumberMessageCount(outputRecipientId))
|
||||
assertEquals(1, SignalDatabase.messages.getChangeNumberMessageCount(outputRecipientId))
|
||||
changeNumberExpected = true
|
||||
}
|
||||
|
||||
fun expectNoChangeNumberEvent() {
|
||||
assertEquals("Unexpected change number event!", 0, SignalDatabase.messages.getChangeNumberMessageCount(outputRecipientId))
|
||||
assertEquals(0, SignalDatabase.messages.getChangeNumberMessageCount(outputRecipientId))
|
||||
changeNumberExpected = false
|
||||
}
|
||||
|
||||
@@ -1187,39 +941,42 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
|
||||
fun expectSessionSwitchoverEvent(recipientId: RecipientId, e164: String) {
|
||||
val event: SessionSwitchoverEvent? = getLatestSessionSwitchoverEvent(recipientId)
|
||||
assertNotNull("Missing session switchover event! Expected one with e164 = $e164", event)
|
||||
assertNotNull(event)
|
||||
assertEquals(e164, event!!.e164)
|
||||
sessionSwitchoverExpected = true
|
||||
}
|
||||
|
||||
fun expectNoSessionSwitchoverEvent() {
|
||||
assertNull("Unexpected session switchover event!", getLatestSessionSwitchoverEvent(outputRecipientId))
|
||||
assertNull(getLatestSessionSwitchoverEvent(outputRecipientId))
|
||||
}
|
||||
|
||||
fun expectThreadMergeEvent(previousE164: String) {
|
||||
val event: ThreadMergeEvent? = getLatestThreadMergeEvent(outputRecipientId)
|
||||
assertNotNull("Missing thread merge event! Expected one with e164 = $previousE164", event)
|
||||
assertEquals("E164 on thread merge event doesn't match!", previousE164, event!!.previousE164)
|
||||
assertNotNull(event)
|
||||
assertEquals(previousE164, event!!.previousE164)
|
||||
threadMergeExpected = true
|
||||
}
|
||||
|
||||
fun expectNoThreadMergeEvent() {
|
||||
assertNull("Unexpected thread merge event!", getLatestThreadMergeEvent(outputRecipientId))
|
||||
assertNull(getLatestThreadMergeEvent(outputRecipientId))
|
||||
}
|
||||
|
||||
private fun insert(e164: String?, pni: PNI?, aci: ACI?): RecipientId {
|
||||
val serviceIdString: String? = (aci ?: pni)?.toString()
|
||||
val pniString: String? = pni?.toString()
|
||||
|
||||
val id: Long = SignalDatabase.rawDatabase.insert(
|
||||
RecipientTable.TABLE_NAME,
|
||||
null,
|
||||
contentValuesOf(
|
||||
RecipientTable.E164 to e164,
|
||||
RecipientTable.ACI_COLUMN to aci?.toString(),
|
||||
RecipientTable.PNI_COLUMN to pni?.toString(),
|
||||
RecipientTable.PHONE to e164,
|
||||
RecipientTable.SERVICE_ID to serviceIdString,
|
||||
RecipientTable.PNI_COLUMN to pniString,
|
||||
RecipientTable.REGISTERED to RecipientTable.RegisteredState.REGISTERED.id
|
||||
)
|
||||
)
|
||||
|
||||
assertTrue("Failed to insert! E164: $e164, ACI: $aci, PNI: $pni", id > 0)
|
||||
assertTrue("Failed to insert! E164: $e164, ServiceId: $serviceIdString, PNI: $pniString", id > 0)
|
||||
|
||||
return RecipientId.from(id)
|
||||
}
|
||||
@@ -1228,14 +985,14 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
data class RecipientTuple(
|
||||
val e164: String?,
|
||||
val pni: PNI?,
|
||||
val aci: ACI?
|
||||
val serviceId: ServiceId?
|
||||
) {
|
||||
|
||||
/**
|
||||
* The intent here is to give nice diffs with the name of the constants rather than the values.
|
||||
*/
|
||||
override fun toString(): String {
|
||||
return "(${e164.e164String()}, ${pni.pniString()}, ${aci.aciString()})"
|
||||
return "(${e164.e164String()}, ${pni.pniString()}, ${serviceId.serviceIdString()})"
|
||||
}
|
||||
|
||||
private fun String?.e164String(): String {
|
||||
@@ -1259,9 +1016,12 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
} ?: "null"
|
||||
}
|
||||
|
||||
private fun ACI?.aciString(): String {
|
||||
private fun ServiceId?.serviceIdString(): String {
|
||||
return this?.let {
|
||||
when (it) {
|
||||
PNI_A -> "PNI_A"
|
||||
PNI_B -> "PNI_B"
|
||||
PNI_SELF -> "PNI_SELF"
|
||||
ACI_A -> "ACI_A"
|
||||
ACI_B -> "ACI_B"
|
||||
ACI_SELF -> "ACI_SELF"
|
||||
@@ -1282,7 +1042,7 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
.use { cursor: Cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
val bytes = Base64.decode(cursor.requireNonNullString(MessageTable.BODY))
|
||||
ThreadMergeEvent.ADAPTER.decode(bytes)
|
||||
ThreadMergeEvent.parseFrom(bytes)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@@ -1300,7 +1060,7 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||
.use { cursor: Cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
val bytes = Base64.decode(cursor.requireNonNullString(MessageTable.BODY))
|
||||
SessionSwitchoverEvent.ADAPTER.decode(bytes)
|
||||
SessionSwitchoverEvent.parseFrom(bytes)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
@@ -18,10 +18,13 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.groupChange
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.groupContext
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.mms.IncomingMessage
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
import org.thoughtcrime.securesms.sms.IncomingGroupUpdateMessage
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage
|
||||
import org.whispersystems.signalservice.api.push.ACI
|
||||
import org.whispersystems.signalservice.api.push.PNI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
|
||||
@Suppress("ClassName", "TestFunctionName")
|
||||
@@ -270,33 +273,18 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
|
||||
assertThat("latest message should be deleted", sms.getMessageRecordOrNull(latestMessage.messageId), nullValue())
|
||||
}
|
||||
|
||||
private fun smsMessage(sender: RecipientId, body: String? = ""): IncomingMessage {
|
||||
private fun smsMessage(sender: RecipientId, body: String? = ""): IncomingTextMessage {
|
||||
wallClock++
|
||||
return IncomingMessage(
|
||||
type = MessageType.NORMAL,
|
||||
from = sender,
|
||||
sentTimeMillis = wallClock,
|
||||
serverTimeMillis = wallClock,
|
||||
receivedTimeMillis = wallClock,
|
||||
body = body,
|
||||
groupId = groupId,
|
||||
isUnidentified = true
|
||||
)
|
||||
return IncomingTextMessage(sender, 1, wallClock, wallClock, wallClock, body, Optional.of(groupId), 0, true, null)
|
||||
}
|
||||
|
||||
private fun groupUpdateMessage(sender: RecipientId, groupContext: DecryptedGroupV2Context): IncomingMessage {
|
||||
wallClock++
|
||||
return IncomingMessage.groupUpdate(
|
||||
from = sender,
|
||||
timestamp = wallClock,
|
||||
groupId = groupId,
|
||||
groupContext = groupContext
|
||||
)
|
||||
private fun groupUpdateMessage(sender: RecipientId, groupContext: DecryptedGroupV2Context): IncomingGroupUpdateMessage {
|
||||
return IncomingGroupUpdateMessage(smsMessage(sender, null), groupContext)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val aliceServiceId: ACI = ACI.from(UUID.fromString("3436efbe-5a76-47fa-a98a-7e72c948a82e"))
|
||||
private val bobServiceId: ACI = ACI.from(UUID.fromString("8de7f691-0b60-4a68-9cd9-ed2f8453f9ed"))
|
||||
private val aliceServiceId: ServiceId = ACI.from(UUID.fromString("3436efbe-5a76-47fa-a98a-7e72c948a82e"))
|
||||
private val bobServiceId: ServiceId = ACI.from(UUID.fromString("8de7f691-0b60-4a68-9cd9-ed2f8453f9ed"))
|
||||
|
||||
private val masterKey = GroupMasterKey(Hex.fromStringCondensed("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
|
||||
private val groupId = GroupId.v2(masterKey)
|
||||
|
||||
@@ -12,24 +12,19 @@ import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
import org.whispersystems.signalservice.api.push.DistributionId
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import java.util.UUID
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class StorySendTableTest {
|
||||
|
||||
@get:Rule
|
||||
val harness = SignalActivityRule(othersCount = 0, createGroup = false)
|
||||
|
||||
private val distributionId1 = DistributionId.from(UUID.randomUUID())
|
||||
private val distributionId2 = DistributionId.from(UUID.randomUUID())
|
||||
private val distributionId3 = DistributionId.from(UUID.randomUUID())
|
||||
@@ -465,7 +460,7 @@ class StorySendTableTest {
|
||||
|
||||
private fun makeRecipients(count: Int): List<RecipientId> {
|
||||
return (1..count).map {
|
||||
SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID()))
|
||||
SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.testing.SignalDatabaseRule
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import java.util.UUID
|
||||
|
||||
@Suppress("ClassName")
|
||||
class ThreadTableTest_active {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val databaseRule = SignalDatabaseRule()
|
||||
|
||||
private lateinit var recipient: Recipient
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenActiveUnarchivedThread_whenIGetUnarchivedConversationList_thenIExpectThread() {
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
|
||||
MmsHelper.insert(recipient = recipient, threadId = threadId)
|
||||
SignalDatabase.threads.update(threadId, false)
|
||||
|
||||
SignalDatabase.threads.getUnarchivedConversationList(
|
||||
ConversationFilter.OFF,
|
||||
false,
|
||||
0,
|
||||
10
|
||||
).use { threads ->
|
||||
assertEquals(1, threads.count)
|
||||
|
||||
val record = ThreadTable.StaticReader(threads, InstrumentationRegistry.getInstrumentation().context).getNext()
|
||||
|
||||
assertNotNull(record)
|
||||
assertEquals(record!!.recipient.id, recipient.id)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenInactiveUnarchivedThread_whenIGetUnarchivedConversationList_thenIExpectNoThread() {
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
|
||||
MmsHelper.insert(recipient = recipient, threadId = threadId)
|
||||
SignalDatabase.threads.update(threadId, false)
|
||||
SignalDatabase.threads.deleteConversation(threadId)
|
||||
|
||||
SignalDatabase.threads.getUnarchivedConversationList(
|
||||
ConversationFilter.OFF,
|
||||
false,
|
||||
0,
|
||||
10
|
||||
).use { threads ->
|
||||
assertEquals(0, threads.count)
|
||||
}
|
||||
|
||||
val threadId2 = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
|
||||
assertEquals(threadId2, threadId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenActiveArchivedThread_whenIGetUnarchivedConversationList_thenIExpectNoThread() {
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
|
||||
MmsHelper.insert(recipient = recipient, threadId = threadId)
|
||||
SignalDatabase.threads.update(threadId, false)
|
||||
SignalDatabase.threads.setArchived(setOf(threadId), true)
|
||||
|
||||
SignalDatabase.threads.getUnarchivedConversationList(
|
||||
ConversationFilter.OFF,
|
||||
false,
|
||||
0,
|
||||
10
|
||||
).use { threads ->
|
||||
assertEquals(0, threads.count)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenActiveArchivedThread_whenIGetArchivedConversationList_thenIExpectThread() {
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
|
||||
MmsHelper.insert(recipient = recipient, threadId = threadId)
|
||||
SignalDatabase.threads.update(threadId, false)
|
||||
SignalDatabase.threads.setArchived(setOf(threadId), true)
|
||||
|
||||
SignalDatabase.threads.getArchivedConversationList(
|
||||
ConversationFilter.OFF,
|
||||
0,
|
||||
10
|
||||
).use { threads ->
|
||||
assertEquals(1, threads.count)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenInactiveArchivedThread_whenIGetArchivedConversationList_thenIExpectNoThread() {
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
|
||||
MmsHelper.insert(recipient = recipient, threadId = threadId)
|
||||
SignalDatabase.threads.update(threadId, false)
|
||||
SignalDatabase.threads.deleteConversation(threadId)
|
||||
SignalDatabase.threads.setArchived(setOf(threadId), true)
|
||||
|
||||
SignalDatabase.threads.getArchivedConversationList(
|
||||
ConversationFilter.OFF,
|
||||
0,
|
||||
10
|
||||
).use { threads ->
|
||||
assertEquals(0, threads.count)
|
||||
}
|
||||
|
||||
val threadId2 = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
|
||||
assertEquals(threadId2, threadId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenActiveArchivedThread_whenIDeactivateThread_thenIExpectNoMessages() {
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
|
||||
MmsHelper.insert(recipient = recipient, threadId = threadId)
|
||||
SignalDatabase.threads.update(threadId, false)
|
||||
|
||||
SignalDatabase.messages.getConversation(threadId).use {
|
||||
assertEquals(1, it.count)
|
||||
}
|
||||
|
||||
SignalDatabase.threads.deleteConversation(threadId)
|
||||
|
||||
SignalDatabase.messages.getConversation(threadId).use {
|
||||
assertEquals(0, it.count)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import org.signal.core.util.CursorUtil
|
||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.testing.SignalDatabaseRule
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import java.util.UUID
|
||||
|
||||
@Suppress("ClassName")
|
||||
@@ -23,7 +23,7 @@ class ThreadTableTest_pinned {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())))
|
||||
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.signal.core.util.CursorUtil
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.testing.SignalDatabaseRule
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import java.util.UUID
|
||||
|
||||
@Suppress("ClassName")
|
||||
@@ -25,7 +25,7 @@ class ThreadTableTest_recents {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())))
|
||||
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -13,9 +13,9 @@ import okio.ByteString
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.BuildConfig
|
||||
import org.thoughtcrime.securesms.KbsEnclave
|
||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess
|
||||
import org.thoughtcrime.securesms.push.SignalServiceTrustStore
|
||||
import org.thoughtcrime.securesms.recipients.LiveRecipientCache
|
||||
@@ -23,25 +23,31 @@ 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.thoughtcrime.securesms.util.Base64
|
||||
import org.whispersystems.signalservice.api.KeyBackupService
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager
|
||||
import org.whispersystems.signalservice.api.push.TrustStore
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalCdsiUrl
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalKeyBackupServiceUrl
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalSvr2Url
|
||||
import java.security.KeyStore
|
||||
import java.util.Optional
|
||||
|
||||
/**
|
||||
* Dependency provider used for instrumentation tests (aka androidTests).
|
||||
*
|
||||
* Handles setting up a mock web server for API calls, and provides mockable versions of [SignalServiceNetworkAccess].
|
||||
* Handles setting up a mock web server for API calls, and provides mockable versions of [SignalServiceNetworkAccess] and
|
||||
* [KeyBackupService].
|
||||
*/
|
||||
class InstrumentationApplicationDependencyProvider(application: Application, default: ApplicationDependencyProvider) : ApplicationDependencies.Provider by default {
|
||||
|
||||
private val serviceTrustStore: TrustStore
|
||||
private val uncensoredConfiguration: SignalServiceConfiguration
|
||||
private val serviceNetworkAccessMock: SignalServiceNetworkAccess
|
||||
private val keyBackupService: KeyBackupService
|
||||
private val recipientCache: LiveRecipientCache
|
||||
|
||||
init {
|
||||
@@ -68,19 +74,20 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
|
||||
|
||||
serviceTrustStore = SignalServiceTrustStore(application)
|
||||
uncensoredConfiguration = SignalServiceConfiguration(
|
||||
signalServiceUrls = arrayOf(SignalServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
|
||||
signalCdnUrlMap = mapOf(
|
||||
arrayOf(SignalServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
|
||||
mapOf(
|
||||
0 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
|
||||
2 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT))
|
||||
),
|
||||
signalStorageUrls = arrayOf(SignalStorageUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
|
||||
signalCdsiUrls = arrayOf(SignalCdsiUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
|
||||
signalSvr2Urls = arrayOf(SignalSvr2Url(baseUrl, serviceTrustStore, "localhost", ConnectionSpec.CLEARTEXT)),
|
||||
networkInterceptors = emptyList(),
|
||||
dns = Optional.of(SignalServiceNetworkAccess.DNS),
|
||||
signalProxy = Optional.empty(),
|
||||
zkGroupServerPublicParams = Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS),
|
||||
genericServerPublicParams = Base64.decode(BuildConfig.GENERIC_SERVER_PUBLIC_PARAMS)
|
||||
arrayOf(SignalKeyBackupServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
|
||||
arrayOf(SignalStorageUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
|
||||
arrayOf(SignalCdsiUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
|
||||
emptyArray(),
|
||||
emptyList(),
|
||||
Optional.of(SignalServiceNetworkAccess.DNS),
|
||||
Optional.empty(),
|
||||
Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS),
|
||||
Base64.decode(BuildConfig.GENERIC_SERVER_PUBLIC_PARAMS)
|
||||
)
|
||||
|
||||
serviceNetworkAccessMock = mock {
|
||||
@@ -89,6 +96,8 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
|
||||
on { uncensoredConfiguration } doReturn uncensoredConfiguration
|
||||
}
|
||||
|
||||
keyBackupService = mock()
|
||||
|
||||
recipientCache = LiveRecipientCache(application) { r -> r.run() }
|
||||
}
|
||||
|
||||
@@ -96,6 +105,10 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
|
||||
return serviceNetworkAccessMock
|
||||
}
|
||||
|
||||
override fun provideKeyBackupService(signalServiceAccountManager: SignalServiceAccountManager, keyStore: KeyStore, enclave: KbsEnclave): KeyBackupService {
|
||||
return keyBackupService
|
||||
}
|
||||
|
||||
override fun provideRecipientCache(): LiveRecipientCache {
|
||||
return recipientCache
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import android.net.Uri
|
||||
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.StreamUtil
|
||||
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.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.mms.SentMediaQuality
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
import org.thoughtcrime.securesms.testing.assertIs
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AttachmentCompressionJobTest {
|
||||
|
||||
@get:Rule
|
||||
val harness = SignalActivityRule()
|
||||
|
||||
@Test
|
||||
fun testCompressionJobsWithDifferentTransformPropertiesCompleteSuccessfully() {
|
||||
val imageBytes: ByteArray = InstrumentationRegistry.getInstrumentation().context.resources.assets.open("images/sample_image.png").use {
|
||||
StreamUtil.readFully(it)
|
||||
}
|
||||
|
||||
val blob = BlobProvider.getInstance().forData(imageBytes).createForSingleSessionOnDisk(ApplicationDependencies.getApplication())
|
||||
|
||||
val firstPreUpload = createAttachment(1, blob, AttachmentTable.TransformProperties.empty())
|
||||
val firstDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(firstPreUpload)
|
||||
|
||||
val firstCompressionJob: AttachmentCompressionJob = AttachmentCompressionJob.fromAttachment(firstDatabaseAttachment, false, -1)
|
||||
|
||||
var secondCompressionJob: AttachmentCompressionJob? = null
|
||||
var firstJobResult: Job.Result? = null
|
||||
var secondJobResult: Job.Result? = null
|
||||
|
||||
val secondJobLatch = CountDownLatch(1)
|
||||
val jobThread = Thread {
|
||||
firstCompressionJob.setContext(ApplicationDependencies.getApplication())
|
||||
firstJobResult = firstCompressionJob.run()
|
||||
|
||||
secondJobLatch.await()
|
||||
|
||||
secondCompressionJob!!.setContext(ApplicationDependencies.getApplication())
|
||||
secondJobResult = secondCompressionJob!!.run()
|
||||
}
|
||||
|
||||
jobThread.start()
|
||||
val secondPreUpload = createAttachment(1, blob, AttachmentTable.TransformProperties.forSentMediaQuality(Optional.empty(), SentMediaQuality.HIGH))
|
||||
val secondDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(secondPreUpload)
|
||||
secondCompressionJob = AttachmentCompressionJob.fromAttachment(secondDatabaseAttachment, false, -1)
|
||||
|
||||
secondJobLatch.countDown()
|
||||
|
||||
jobThread.join()
|
||||
|
||||
firstJobResult!!.isSuccess assertIs true
|
||||
secondJobResult!!.isSuccess assertIs true
|
||||
}
|
||||
|
||||
private fun createAttachment(id: Long, uri: Uri, transformProperties: AttachmentTable.TransformProperties): UriAttachment {
|
||||
return UriAttachmentBuilder.build(
|
||||
id,
|
||||
uri = uri,
|
||||
contentType = MediaUtil.IMAGE_JPEG,
|
||||
transformProperties = transformProperties
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.signal.libsignal.protocol.ecc.Curve
|
||||
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.testing.Get
|
||||
import org.thoughtcrime.securesms.testing.Put
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
import org.thoughtcrime.securesms.testing.assertIs
|
||||
import org.thoughtcrime.securesms.testing.assertIsNot
|
||||
import org.thoughtcrime.securesms.testing.parsedRequestBody
|
||||
import org.thoughtcrime.securesms.testing.success
|
||||
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity
|
||||
import org.whispersystems.signalservice.internal.push.PreKeyState
|
||||
import org.whispersystems.signalservice.internal.push.PreKeyStatus
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PreKeysSyncJobTest {
|
||||
|
||||
@get:Rule
|
||||
val harness = SignalActivityRule()
|
||||
|
||||
private val aciPreKeyMeta: PreKeyMetadataStore
|
||||
get() = SignalStore.account().aciPreKeys
|
||||
|
||||
private val pniPreKeyMeta: PreKeyMetadataStore
|
||||
get() = SignalStore.account().pniPreKeys
|
||||
|
||||
private lateinit var job: PreKeysSyncJob
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
job = PreKeysSyncJob()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
InstrumentationApplicationDependencyProvider.clearHandlers()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create signed prekeys for both identities when both do not have registered prekeys according
|
||||
* to our local state.
|
||||
*/
|
||||
@Test
|
||||
fun runWithoutRegisteredKeysForBothIdentities() {
|
||||
// GIVEN
|
||||
aciPreKeyMeta.isSignedPreKeyRegistered = false
|
||||
pniPreKeyMeta.isSignedPreKeyRegistered = false
|
||||
|
||||
lateinit var aciSignedPreKey: SignedPreKeyEntity
|
||||
lateinit var pniSignedPreKey: SignedPreKeyEntity
|
||||
|
||||
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||
Put("/v2/keys/signed?identity=aci") { r ->
|
||||
aciSignedPreKey = r.parsedRequestBody()
|
||||
MockResponse().success()
|
||||
},
|
||||
Put("/v2/keys/signed?identity=pni") { r ->
|
||||
pniSignedPreKey = r.parsedRequestBody()
|
||||
MockResponse().success()
|
||||
}
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result: Job.Result = job.run()
|
||||
|
||||
// THEN
|
||||
result.isSuccess assertIs true
|
||||
|
||||
aciPreKeyMeta.isSignedPreKeyRegistered assertIs true
|
||||
pniPreKeyMeta.isSignedPreKeyRegistered assertIs true
|
||||
|
||||
val aciVerifySignatureResult = Curve.verifySignature(
|
||||
ApplicationDependencies.getProtocolStore().aci().identityKeyPair.publicKey.publicKey,
|
||||
aciSignedPreKey.publicKey.serialize(),
|
||||
aciSignedPreKey.signature
|
||||
)
|
||||
aciVerifySignatureResult assertIs true
|
||||
|
||||
val pniVerifySignatureResult = Curve.verifySignature(
|
||||
ApplicationDependencies.getProtocolStore().pni().identityKeyPair.publicKey.publicKey,
|
||||
pniSignedPreKey.publicKey.serialize(),
|
||||
pniSignedPreKey.signature
|
||||
)
|
||||
pniVerifySignatureResult assertIs true
|
||||
}
|
||||
|
||||
/**
|
||||
* With 100 prekeys registered for each identity, do nothing.
|
||||
*/
|
||||
@Test
|
||||
fun runWithRegisteredKeysForBothIdentities() {
|
||||
// GIVEN
|
||||
val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId
|
||||
val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId
|
||||
|
||||
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||
Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(100)) },
|
||||
Get("/v2/keys?identity=pni") { MockResponse().success(PreKeyStatus(100)) }
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result: Job.Result = job.run()
|
||||
|
||||
// THEN
|
||||
result.isSuccess assertIs true
|
||||
|
||||
aciPreKeyMeta.activeSignedPreKeyId assertIs currentAciKeyId
|
||||
pniPreKeyMeta.activeSignedPreKeyId assertIs currentPniKeyId
|
||||
}
|
||||
|
||||
/**
|
||||
* With 100 prekeys registered for ACI, but no PNI prekeys registered according to local state,
|
||||
* do nothing for ACI but create PNI prekeys and update local state.
|
||||
*/
|
||||
@Test
|
||||
fun runWithRegisteredKeysForAciIdentityOnly() {
|
||||
// GIVEN
|
||||
pniPreKeyMeta.isSignedPreKeyRegistered = false
|
||||
|
||||
val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId
|
||||
val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId
|
||||
|
||||
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||
Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(100)) },
|
||||
Put("/v2/keys/signed?identity=pni") { MockResponse().success() }
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result: Job.Result = job.run()
|
||||
|
||||
// THEN
|
||||
result.isSuccess assertIs true
|
||||
|
||||
pniPreKeyMeta.isSignedPreKeyRegistered assertIs true
|
||||
aciPreKeyMeta.activeSignedPreKeyId assertIs currentAciKeyId
|
||||
pniPreKeyMeta.activeSignedPreKeyId assertIsNot currentPniKeyId
|
||||
}
|
||||
|
||||
/**
|
||||
* With <10 prekeys registered for each identity, upload new.
|
||||
*/
|
||||
@Test
|
||||
fun runWithLowNumberOfRegisteredKeysForBothIdentities() {
|
||||
// GIVEN
|
||||
val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId
|
||||
val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId
|
||||
|
||||
val currentNextAciPreKeyId = aciPreKeyMeta.nextOneTimePreKeyId
|
||||
val currentNextPniPreKeyId = pniPreKeyMeta.nextOneTimePreKeyId
|
||||
|
||||
lateinit var aciPreKeyStateRequest: PreKeyState
|
||||
lateinit var pniPreKeyStateRequest: PreKeyState
|
||||
|
||||
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||
Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(5)) },
|
||||
Get("/v2/keys?identity=pni") { MockResponse().success(PreKeyStatus(5)) },
|
||||
Put("/v2/keys/?identity=aci") { r ->
|
||||
aciPreKeyStateRequest = r.parsedRequestBody()
|
||||
MockResponse().success()
|
||||
},
|
||||
Put("/v2/keys/?identity=pni") { r ->
|
||||
pniPreKeyStateRequest = r.parsedRequestBody()
|
||||
MockResponse().success()
|
||||
}
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result: Job.Result = job.run()
|
||||
|
||||
// THEN
|
||||
result.isSuccess assertIs true
|
||||
aciPreKeyMeta.activeSignedPreKeyId assertIsNot currentAciKeyId
|
||||
pniPreKeyMeta.activeSignedPreKeyId assertIsNot currentPniKeyId
|
||||
|
||||
aciPreKeyMeta.nextOneTimePreKeyId assertIsNot currentNextAciPreKeyId
|
||||
pniPreKeyMeta.nextOneTimePreKeyId assertIsNot currentNextPniPreKeyId
|
||||
|
||||
ApplicationDependencies.getProtocolStore().aci().identityKeyPair.publicKey.let { aciIdentityKey ->
|
||||
aciPreKeyStateRequest.identityKey assertIs aciIdentityKey
|
||||
|
||||
val verifySignatureResult = Curve.verifySignature(
|
||||
aciIdentityKey.publicKey,
|
||||
aciPreKeyStateRequest.signedPreKey.publicKey.serialize(),
|
||||
aciPreKeyStateRequest.signedPreKey.signature
|
||||
)
|
||||
verifySignatureResult assertIs true
|
||||
}
|
||||
|
||||
ApplicationDependencies.getProtocolStore().pni().identityKeyPair.publicKey.let { pniIdentityKey ->
|
||||
pniPreKeyStateRequest.identityKey assertIs pniIdentityKey
|
||||
|
||||
val verifySignatureResult = Curve.verifySignature(
|
||||
pniIdentityKey.publicKey,
|
||||
pniPreKeyStateRequest.signedPreKey.publicKey.serialize(),
|
||||
pniPreKeyStateRequest.signedPreKey.signature
|
||||
)
|
||||
verifySignatureResult assertIs true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.signal.libsignal.usernames.Username
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.testing.Get
|
||||
import org.thoughtcrime.securesms.testing.Put
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
import org.thoughtcrime.securesms.testing.failure
|
||||
import org.thoughtcrime.securesms.testing.success
|
||||
import org.whispersystems.signalservice.internal.push.ReserveUsernameResponse
|
||||
import org.whispersystems.signalservice.internal.push.WhoAmIResponse
|
||||
import org.whispersystems.util.Base64UrlSafe
|
||||
|
||||
@Suppress("ClassName")
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class RefreshOwnProfileJob__checkUsernameIsInSyncTest {
|
||||
|
||||
@get:Rule
|
||||
val harness = SignalActivityRule()
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
InstrumentationApplicationDependencyProvider.clearHandlers()
|
||||
SignalStore.phoneNumberPrivacy().clearUsernameOutOfSync()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenNoLocalUsername_whenICheckUsernameIsInSync_thenIExpectNoFailures() {
|
||||
// WHEN
|
||||
RefreshOwnProfileJob.checkUsernameIsInSync()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenLocalUsernameDoesNotMatchServerUsername_whenICheckUsernameIsInSync_thenIExpectRetry() {
|
||||
// GIVEN
|
||||
var didReserve = false
|
||||
var didConfirm = false
|
||||
val username = "hello.32"
|
||||
val serverUsername = "hello.3232"
|
||||
SignalDatabase.recipients.setUsername(harness.self.id, username)
|
||||
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||
Get("/v1/accounts/whoami") { r ->
|
||||
MockResponse().success(
|
||||
WhoAmIResponse().apply {
|
||||
usernameHash = Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(serverUsername))
|
||||
}
|
||||
)
|
||||
},
|
||||
Put("/v1/accounts/username_hash/reserve") { r ->
|
||||
didReserve = true
|
||||
MockResponse().success(ReserveUsernameResponse(Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username))))
|
||||
},
|
||||
Put("/v1/accounts/username_hash/confirm") { r ->
|
||||
didConfirm = true
|
||||
MockResponse().success()
|
||||
}
|
||||
)
|
||||
|
||||
// WHEN
|
||||
RefreshOwnProfileJob.checkUsernameIsInSync()
|
||||
|
||||
// THEN
|
||||
assertTrue(didReserve)
|
||||
assertTrue(didConfirm)
|
||||
assertFalse(SignalStore.phoneNumberPrivacy().isUsernameOutOfSync)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenLocalAndNoServer_whenICheckUsernameIsInSync_thenIExpectRetry() {
|
||||
// GIVEN
|
||||
var didReserve = false
|
||||
var didConfirm = false
|
||||
val username = "hello.32"
|
||||
SignalDatabase.recipients.setUsername(harness.self.id, username)
|
||||
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||
Get("/v1/accounts/whoami") { r ->
|
||||
MockResponse().success(WhoAmIResponse())
|
||||
},
|
||||
Put("/v1/accounts/username_hash/reserve") { r ->
|
||||
didReserve = true
|
||||
MockResponse().success(ReserveUsernameResponse(Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username))))
|
||||
},
|
||||
Put("/v1/accounts/username_hash/confirm") { r ->
|
||||
didConfirm = true
|
||||
MockResponse().success()
|
||||
}
|
||||
)
|
||||
|
||||
// WHEN
|
||||
RefreshOwnProfileJob.checkUsernameIsInSync()
|
||||
|
||||
// THEN
|
||||
assertTrue(didReserve)
|
||||
assertTrue(didConfirm)
|
||||
assertFalse(SignalStore.phoneNumberPrivacy().isUsernameOutOfSync)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenLocalAndServerMatch_whenICheckUsernameIsInSync_thenIExpectNoRetry() {
|
||||
// GIVEN
|
||||
var didReserve = false
|
||||
var didConfirm = false
|
||||
val username = "hello.32"
|
||||
SignalDatabase.recipients.setUsername(harness.self.id, username)
|
||||
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||
Get("/v1/accounts/whoami") { r ->
|
||||
MockResponse().success(
|
||||
WhoAmIResponse().apply {
|
||||
usernameHash = Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username))
|
||||
}
|
||||
)
|
||||
},
|
||||
Put("/v1/accounts/username_hash/reserve") { r ->
|
||||
didReserve = true
|
||||
MockResponse().success(ReserveUsernameResponse(Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username))))
|
||||
},
|
||||
Put("/v1/accounts/username_hash/confirm") { r ->
|
||||
didConfirm = true
|
||||
MockResponse().success()
|
||||
}
|
||||
)
|
||||
|
||||
// WHEN
|
||||
RefreshOwnProfileJob.checkUsernameIsInSync()
|
||||
|
||||
// THEN
|
||||
assertFalse(didReserve)
|
||||
assertFalse(didConfirm)
|
||||
assertFalse(SignalStore.phoneNumberPrivacy().isUsernameOutOfSync)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenMismatchAndReservationFails_whenICheckUsernameIsInSync_thenIExpectNoConfirm() {
|
||||
// GIVEN
|
||||
var didReserve = false
|
||||
var didConfirm = false
|
||||
val username = "hello.32"
|
||||
SignalDatabase.recipients.setUsername(harness.self.id, username)
|
||||
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||
Get("/v1/accounts/whoami") { r ->
|
||||
MockResponse().success(
|
||||
WhoAmIResponse().apply {
|
||||
usernameHash = Base64UrlSafe.encodeBytesWithoutPadding(Username.hash("${username}23"))
|
||||
}
|
||||
)
|
||||
},
|
||||
Put("/v1/accounts/username_hash/reserve") { r ->
|
||||
didReserve = true
|
||||
MockResponse().failure(418)
|
||||
},
|
||||
Put("/v1/accounts/username_hash/confirm") { r ->
|
||||
didConfirm = true
|
||||
MockResponse().success()
|
||||
}
|
||||
)
|
||||
|
||||
// WHEN
|
||||
RefreshOwnProfileJob.checkUsernameIsInSync()
|
||||
|
||||
// THEN
|
||||
assertTrue(didReserve)
|
||||
assertFalse(didConfirm)
|
||||
assertTrue(SignalStore.phoneNumberPrivacy().isUsernameOutOfSync)
|
||||
}
|
||||
}
|
||||
@@ -22,10 +22,10 @@ public final class PinHashing_hashPin_Test {
|
||||
@Test
|
||||
public void argon2_hashed_pin_password() throws IOException {
|
||||
String pin = "password";
|
||||
byte[] salt = Hex.fromStringCondensed("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
|
||||
byte[] backupId = Hex.fromStringCondensed("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
|
||||
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"));
|
||||
|
||||
PinHash hashedPin = PinHashUtil.hashPin(pin, salt);
|
||||
PinHash hashedPin = PinHashUtil.hashPin(pin, new byte[]{});
|
||||
KbsData kbsData = PinHashUtil.createNewKbsData(hashedPin, masterKey);
|
||||
|
||||
assertArrayEquals(hashedPin.accessKey(), kbsData.getKbsAccessKey());
|
||||
@@ -40,10 +40,10 @@ public final class PinHashing_hashPin_Test {
|
||||
@Test
|
||||
public void argon2_hashed_pin_another_password() throws IOException {
|
||||
String pin = "anotherpassword";
|
||||
byte[] salt = Hex.fromStringCondensed("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f");
|
||||
byte[] backupId = Hex.fromStringCondensed("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f");
|
||||
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("88a787415a2ecd79da0d1016a82a27c5c695c9a19b88b0aa1d35683280aa9a67"));
|
||||
|
||||
PinHash hashedPin = PinHashUtil.hashPin(pin, salt);
|
||||
PinHash hashedPin = PinHashUtil.hashPin(pin, new byte[]{});
|
||||
KbsData kbsData = PinHashUtil.createNewKbsData(hashedPin, masterKey);
|
||||
|
||||
assertArrayEquals(hashedPin.accessKey(), kbsData.getKbsAccessKey());
|
||||
@@ -58,10 +58,10 @@ public final class PinHashing_hashPin_Test {
|
||||
@Test
|
||||
public void argon2_hashed_pin_password_with_spaces_diacritics_and_non_arabic_numerals() throws IOException {
|
||||
String pin = " Pass६örd ";
|
||||
byte[] salt = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8");
|
||||
byte[] backupId = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8");
|
||||
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("9571f3fde1e58588ba49bcf82be1b301ca3859a6f59076f79a8f47181ef952bf"));
|
||||
|
||||
PinHash hashedPin = PinHashUtil.hashPin(pin, salt);
|
||||
PinHash hashedPin = PinHashUtil.hashPin(pin, new byte[]{});
|
||||
KbsData kbsData = PinHashUtil.createNewKbsData(hashedPin, masterKey);
|
||||
|
||||
assertArrayEquals(hashedPin.accessKey(), kbsData.getKbsAccessKey());
|
||||
@@ -78,10 +78,10 @@ public final class PinHashing_hashPin_Test {
|
||||
@Test
|
||||
public void argon2_hashed_pin_password_with_just_non_arabic_numerals() throws IOException {
|
||||
String pin = " ६१८ ";
|
||||
byte[] salt = Hex.fromStringCondensed("717dc111a98423a57196512606822fca646c653facd037c10728f14ba0be2ab3");
|
||||
byte[] backupId = Hex.fromStringCondensed("717dc111a98423a57196512606822fca646c653facd037c10728f14ba0be2ab3");
|
||||
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("0432d735b32f66d0e3a70d4f9cc821a8529521a4937d26b987715d8eff4e4c54"));
|
||||
|
||||
PinHash hashedPin = PinHashUtil.hashPin(pin, salt);
|
||||
PinHash hashedPin = PinHashUtil.hashPin(pin, new byte[]{});
|
||||
KbsData kbsData = PinHashUtil.createNewKbsData(hashedPin, masterKey);
|
||||
|
||||
assertArrayEquals(hashedPin.accessKey(), kbsData.getKbsAccessKey());
|
||||
|
||||
@@ -22,9 +22,8 @@ import org.thoughtcrime.securesms.testing.MessageContentFuzzer
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
import org.thoughtcrime.securesms.testing.assertIs
|
||||
import org.thoughtcrime.securesms.util.MessageTableTestUtils
|
||||
import org.whispersystems.signalservice.internal.push.Content
|
||||
import org.whispersystems.signalservice.internal.push.EditMessage
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.EditMessage
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@@ -47,13 +46,13 @@ class EditMessageSyncProcessorTest {
|
||||
@get:Rule
|
||||
val harness = SignalActivityRule()
|
||||
|
||||
private lateinit var processorV2: MessageContentProcessor
|
||||
private lateinit var processorV2: MessageContentProcessorV2
|
||||
private lateinit var testResult: TestResults
|
||||
private var envelopeTimestamp: Long = 0
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
processorV2 = MessageContentProcessor(harness.context)
|
||||
processorV2 = MessageContentProcessorV2(harness.context)
|
||||
envelopeTimestamp = System.currentTimeMillis()
|
||||
testResult = TestResults()
|
||||
}
|
||||
@@ -68,17 +67,16 @@ class EditMessageSyncProcessorTest {
|
||||
|
||||
val content = MessageContentFuzzer.fuzzTextMessage()
|
||||
val metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, toRecipient.id)
|
||||
val syncContent = Content.Builder().syncMessage(
|
||||
SyncMessage.Builder().sent(
|
||||
SyncMessage.Sent.Builder()
|
||||
.destinationServiceId(metadata.destinationServiceId.toString())
|
||||
.timestamp(originalTimestamp)
|
||||
.expirationStartTimestamp(originalTimestamp)
|
||||
.message(content.dataMessage)
|
||||
.build()
|
||||
).build()
|
||||
val syncContent = SignalServiceProtos.Content.newBuilder().setSyncMessage(
|
||||
SignalServiceProtos.SyncMessage.newBuilder().setSent(
|
||||
SignalServiceProtos.SyncMessage.Sent.newBuilder()
|
||||
.setDestinationUuid(metadata.destinationServiceId.toString())
|
||||
.setTimestamp(originalTimestamp)
|
||||
.setExpirationStartTimestamp(originalTimestamp)
|
||||
.setMessage(content.dataMessage)
|
||||
)
|
||||
).build()
|
||||
SignalDatabase.recipients.setExpireMessages(toRecipient.id, content.dataMessage?.expireTimer ?: 0)
|
||||
SignalDatabase.recipients.setExpireMessages(toRecipient.id, content.dataMessage.expireTimer)
|
||||
val syncTextMessage = TestMessage(
|
||||
envelope = MessageContentFuzzer.envelope(originalTimestamp),
|
||||
content = syncContent,
|
||||
@@ -88,20 +86,18 @@ class EditMessageSyncProcessorTest {
|
||||
|
||||
val editTimestamp = originalTimestamp + 200
|
||||
val editedContent = MessageContentFuzzer.fuzzTextMessage()
|
||||
val editSyncContent = Content.Builder().syncMessage(
|
||||
SyncMessage.Builder().sent(
|
||||
SyncMessage.Sent.Builder()
|
||||
.destinationServiceId(metadata.destinationServiceId.toString())
|
||||
.timestamp(editTimestamp)
|
||||
.expirationStartTimestamp(editTimestamp)
|
||||
.editMessage(
|
||||
EditMessage.Builder()
|
||||
.dataMessage(editedContent.dataMessage)
|
||||
.targetSentTimestamp(originalTimestamp)
|
||||
.build()
|
||||
val editSyncContent = SignalServiceProtos.Content.newBuilder().setSyncMessage(
|
||||
SignalServiceProtos.SyncMessage.newBuilder().setSent(
|
||||
SignalServiceProtos.SyncMessage.Sent.newBuilder()
|
||||
.setDestinationUuid(metadata.destinationServiceId.toString())
|
||||
.setTimestamp(editTimestamp)
|
||||
.setExpirationStartTimestamp(editTimestamp)
|
||||
.setEditMessage(
|
||||
EditMessage.newBuilder()
|
||||
.setDataMessage(editedContent.dataMessage)
|
||||
.setTargetSentTimestamp(originalTimestamp)
|
||||
)
|
||||
.build()
|
||||
).build()
|
||||
)
|
||||
).build()
|
||||
|
||||
val syncEditMessage = TestMessage(
|
||||
@@ -113,38 +109,38 @@ class EditMessageSyncProcessorTest {
|
||||
|
||||
testResult.runSync(listOf(syncTextMessage, syncEditMessage))
|
||||
|
||||
SignalDatabase.recipients.setExpireMessages(toRecipient.id, (content.dataMessage?.expireTimer ?: 0) / 1000)
|
||||
SignalDatabase.recipients.setExpireMessages(toRecipient.id, content.dataMessage.expireTimer / 1000)
|
||||
val originalTextMessage = OutgoingMessage(
|
||||
threadRecipient = toRecipient,
|
||||
sentTimeMillis = originalTimestamp,
|
||||
body = content.dataMessage?.body ?: "",
|
||||
expiresIn = content.dataMessage?.expireTimer?.seconds?.inWholeMilliseconds ?: 0,
|
||||
body = content.dataMessage.body,
|
||||
expiresIn = content.dataMessage.expireTimer.seconds.inWholeMilliseconds,
|
||||
isUrgent = true,
|
||||
isSecure = true,
|
||||
bodyRanges = content.dataMessage?.bodyRanges.toBodyRangeList()
|
||||
bodyRanges = content.dataMessage.bodyRangesList.toBodyRangeList()
|
||||
)
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(toRecipient)
|
||||
val originalMessageId = SignalDatabase.messages.insertMessageOutbox(originalTextMessage, threadId, false, null)
|
||||
SignalDatabase.messages.markAsSent(originalMessageId, true)
|
||||
if ((content.dataMessage?.expireTimer ?: 0) > 0) {
|
||||
if (content.dataMessage.expireTimer > 0) {
|
||||
SignalDatabase.messages.markExpireStarted(originalMessageId, originalTimestamp)
|
||||
}
|
||||
|
||||
val editMessage = OutgoingMessage(
|
||||
threadRecipient = toRecipient,
|
||||
sentTimeMillis = editTimestamp,
|
||||
body = editedContent.dataMessage?.body ?: "",
|
||||
expiresIn = content.dataMessage?.expireTimer?.seconds?.inWholeMilliseconds ?: 0,
|
||||
body = editedContent.dataMessage.body,
|
||||
expiresIn = content.dataMessage.expireTimer.seconds.inWholeMilliseconds,
|
||||
isUrgent = true,
|
||||
isSecure = true,
|
||||
bodyRanges = editedContent.dataMessage?.bodyRanges.toBodyRangeList(),
|
||||
bodyRanges = editedContent.dataMessage.bodyRangesList.toBodyRangeList(),
|
||||
messageToEdit = originalMessageId
|
||||
)
|
||||
|
||||
val editMessageId = SignalDatabase.messages.insertMessageOutbox(editMessage, threadId, false, null)
|
||||
SignalDatabase.messages.markAsSent(editMessageId, true)
|
||||
|
||||
if ((content.dataMessage?.expireTimer ?: 0) > 0) {
|
||||
if (content.dataMessage.expireTimer > 0) {
|
||||
SignalDatabase.messages.markExpireStarted(editMessageId, originalTimestamp)
|
||||
}
|
||||
testResult.collectLocal()
|
||||
@@ -171,7 +167,7 @@ class EditMessageSyncProcessorTest {
|
||||
|
||||
fun runSync(messages: List<TestMessage>) {
|
||||
messages.forEach { (envelope, content, metadata, serverDeliveredTimestamp) ->
|
||||
if (content.syncMessage != null) {
|
||||
if (content.hasSyncMessage()) {
|
||||
processorV2.process(
|
||||
envelope,
|
||||
content,
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.thoughtcrime.securesms.messages
|
||||
|
||||
import android.app.Application
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import org.junit.Rule
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessor.ExceptionMetadata
|
||||
import org.thoughtcrime.securesms.messages.MessageContentProcessor.MessageState
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
import org.thoughtcrime.securesms.testing.TestProtos
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
|
||||
|
||||
abstract class MessageContentProcessorTest {
|
||||
|
||||
@get:Rule
|
||||
val harness = SignalActivityRule()
|
||||
|
||||
protected fun MessageContentProcessor.doProcess(
|
||||
messageState: MessageState = MessageState.DECRYPTED_OK,
|
||||
content: SignalServiceContent,
|
||||
exceptionMetadata: ExceptionMetadata = ExceptionMetadata("sender", 1),
|
||||
timestamp: Long = 100L,
|
||||
smsMessageId: Long = -1L
|
||||
) {
|
||||
process(messageState, content, exceptionMetadata, timestamp, smsMessageId)
|
||||
}
|
||||
|
||||
protected fun createNormalContentTestSubject(): MessageContentProcessor {
|
||||
val context = ApplicationProvider.getApplicationContext<Application>()
|
||||
|
||||
return MessageContentProcessor.create(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a valid ServiceContentProto with a data message which can be built via
|
||||
* `injectDataMessage`. This function is intended to be built on-top of for more
|
||||
* specific scenario in subclasses.
|
||||
*
|
||||
* Example can be seen in __handleStoryMessageTest
|
||||
*/
|
||||
protected fun createServiceContentWithDataMessage(
|
||||
messageSender: Recipient = Recipient.resolved(harness.others.first()),
|
||||
injectDataMessage: SignalServiceProtos.DataMessage.Builder.() -> Unit
|
||||
): SignalServiceContentProto {
|
||||
return TestProtos.build {
|
||||
serviceContent(
|
||||
localAddress = address(uuid = harness.self.requireServiceId().uuid()).build(),
|
||||
metadata = metadata(
|
||||
address = address(uuid = messageSender.requireServiceId().uuid()).build()
|
||||
).build()
|
||||
).apply {
|
||||
content = content().apply {
|
||||
dataMessage = dataMessage().apply {
|
||||
injectDataMessage()
|
||||
}.build()
|
||||
}.build()
|
||||
}.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
package org.thoughtcrime.securesms.messages
|
||||
|
||||
import android.database.Cursor
|
||||
import android.util.Base64
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.signal.core.util.ThreadUtil
|
||||
import org.signal.core.util.readToList
|
||||
import org.signal.core.util.select
|
||||
import org.signal.core.util.withinTransaction
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
import org.thoughtcrime.securesms.database.MessageTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadTable
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.testing.Entry
|
||||
import org.thoughtcrime.securesms.testing.InMemoryLogger
|
||||
import org.thoughtcrime.securesms.testing.MessageContentFuzzer
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
import org.thoughtcrime.securesms.testing.assertIs
|
||||
import org.thoughtcrime.securesms.util.MessageTableTestUtils
|
||||
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceMetadata
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||
import org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer
|
||||
import org.whispersystems.signalservice.internal.serialize.SignalServiceMetadataProtobufSerializer
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
|
||||
import java.util.Optional
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MessageContentProcessorTestV2 {
|
||||
|
||||
companion object {
|
||||
private val TAGS = listOf(MessageContentProcessor.TAG, MessageContentProcessorV2.TAG, AttachmentTable.TAG)
|
||||
|
||||
private val GENERALIZE_TAG = mapOf(
|
||||
MessageContentProcessor.TAG to "MCP",
|
||||
MessageContentProcessorV2.TAG to "MCP",
|
||||
AttachmentTable.TAG to AttachmentTable.TAG
|
||||
)
|
||||
|
||||
private val IGNORE_MESSAGE_COLUMNS = listOf(
|
||||
MessageTable.DATE_RECEIVED,
|
||||
MessageTable.NOTIFIED_TIMESTAMP,
|
||||
MessageTable.REACTIONS_LAST_SEEN,
|
||||
MessageTable.NOTIFIED
|
||||
)
|
||||
|
||||
private val IGNORE_ATTACHMENT_COLUMNS = listOf(
|
||||
AttachmentTable.UNIQUE_ID,
|
||||
AttachmentTable.TRANSFER_FILE
|
||||
)
|
||||
}
|
||||
|
||||
@get:Rule
|
||||
val harness = SignalActivityRule()
|
||||
|
||||
private lateinit var processorV1: MessageContentProcessor
|
||||
private lateinit var processorV2: MessageContentProcessorV2
|
||||
private lateinit var testResult: TestResults
|
||||
private var envelopeTimestamp: Long = 0
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
processorV1 = MessageContentProcessor(harness.context)
|
||||
processorV2 = MessageContentProcessorV2(harness.context)
|
||||
envelopeTimestamp = System.currentTimeMillis()
|
||||
testResult = TestResults()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun textMessage() {
|
||||
var start = envelopeTimestamp
|
||||
|
||||
val messages: List<TestMessage> = (0 until 100).map {
|
||||
start += 200
|
||||
TestMessage(
|
||||
envelope = MessageContentFuzzer.envelope(start),
|
||||
content = MessageContentFuzzer.fuzzTextMessage(),
|
||||
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
|
||||
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
|
||||
)
|
||||
}
|
||||
|
||||
testResult.runV2(messages)
|
||||
testResult.runV1(messages)
|
||||
|
||||
testResult.assert()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mediaMessage() {
|
||||
var start = envelopeTimestamp
|
||||
|
||||
val textMessages: List<TestMessage> = (0 until 10).map {
|
||||
start += 200
|
||||
TestMessage(
|
||||
envelope = MessageContentFuzzer.envelope(start),
|
||||
content = MessageContentFuzzer.fuzzTextMessage(),
|
||||
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
|
||||
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
|
||||
)
|
||||
}
|
||||
|
||||
val firstBatchMediaMessages: List<TestMessage> = (0 until 10).map {
|
||||
start += 200
|
||||
TestMessage(
|
||||
envelope = MessageContentFuzzer.envelope(start),
|
||||
content = MessageContentFuzzer.fuzzMediaMessageWithBody(textMessages),
|
||||
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
|
||||
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
|
||||
)
|
||||
}
|
||||
|
||||
val secondBatchNoContentMediaMessages: List<TestMessage> = (0 until 10).map {
|
||||
start += 200
|
||||
TestMessage(
|
||||
envelope = MessageContentFuzzer.envelope(start),
|
||||
content = MessageContentFuzzer.fuzzMediaMessageNoContent(textMessages + firstBatchMediaMessages),
|
||||
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
|
||||
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
|
||||
)
|
||||
}
|
||||
|
||||
val thirdBatchNoTextMediaMessagesMessages: List<TestMessage> = (0 until 10).map {
|
||||
start += 200
|
||||
TestMessage(
|
||||
envelope = MessageContentFuzzer.envelope(start),
|
||||
content = MessageContentFuzzer.fuzzMediaMessageNoText(textMessages + firstBatchMediaMessages),
|
||||
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
|
||||
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
|
||||
)
|
||||
}
|
||||
|
||||
testResult.runV2(textMessages + firstBatchMediaMessages + secondBatchNoContentMediaMessages + thirdBatchNoTextMediaMessagesMessages)
|
||||
testResult.runV1(textMessages + firstBatchMediaMessages + secondBatchNoContentMediaMessages + thirdBatchNoTextMediaMessagesMessages)
|
||||
|
||||
testResult.assert()
|
||||
}
|
||||
|
||||
private inner class TestResults {
|
||||
|
||||
private lateinit var v1Logs: List<Entry>
|
||||
private lateinit var v1Messages: List<List<Pair<String, String?>>>
|
||||
private lateinit var v1Attachments: List<List<Pair<String, String?>>>
|
||||
private lateinit var v2Logs: List<Entry>
|
||||
private lateinit var v2Messages: List<List<Pair<String, String?>>>
|
||||
private lateinit var v2Attachments: List<List<Pair<String, String?>>>
|
||||
|
||||
fun runV1(messages: List<TestMessage>) {
|
||||
messages.forEach { (envelope, content, metadata, serverDeliveredTimestamp) ->
|
||||
if (content.hasDataMessage()) {
|
||||
processorV1.process(
|
||||
MessageContentProcessor.MessageState.DECRYPTED_OK,
|
||||
toSignalServiceContent(envelope, content, metadata, serverDeliveredTimestamp),
|
||||
null,
|
||||
envelope.timestamp,
|
||||
-1
|
||||
)
|
||||
ThreadUtil.sleep(1)
|
||||
}
|
||||
}
|
||||
|
||||
v1Logs = harness.inMemoryLogger.logs()
|
||||
harness.inMemoryLogger.clear()
|
||||
|
||||
v1Messages = dumpMessages()
|
||||
v1Attachments = dumpAttachments()
|
||||
}
|
||||
|
||||
fun runV2(messages: List<TestMessage>) {
|
||||
messages.forEach { (envelope, content, metadata, serverDeliveredTimestamp) ->
|
||||
if (content.hasDataMessage()) {
|
||||
processorV2.process(
|
||||
envelope,
|
||||
content,
|
||||
metadata,
|
||||
serverDeliveredTimestamp,
|
||||
false
|
||||
)
|
||||
ThreadUtil.sleep(1)
|
||||
}
|
||||
}
|
||||
|
||||
v2Logs = harness.inMemoryLogger.logs()
|
||||
harness.inMemoryLogger.clear()
|
||||
|
||||
v2Messages = dumpMessages()
|
||||
v2Attachments = dumpAttachments()
|
||||
|
||||
cleanup()
|
||||
}
|
||||
|
||||
fun cleanup() {
|
||||
SignalDatabase.rawDatabase.withinTransaction { db ->
|
||||
SignalDatabase.threads.deleteAllConversations()
|
||||
db.execSQL("DELETE FROM sqlite_sequence WHERE name = '${MessageTable.TABLE_NAME}'")
|
||||
db.execSQL("DELETE FROM sqlite_sequence WHERE name = '${ThreadTable.TABLE_NAME}'")
|
||||
db.execSQL("DELETE FROM sqlite_sequence WHERE name = '${AttachmentTable.TABLE_NAME}'")
|
||||
}
|
||||
}
|
||||
|
||||
fun assert() {
|
||||
v2Logs.zip(v1Logs)
|
||||
.forEach { (v2, v1) ->
|
||||
GENERALIZE_TAG[v2.tag]!!.assertIs(GENERALIZE_TAG[v1.tag]!!)
|
||||
|
||||
if (v2.tag != AttachmentTable.TAG) {
|
||||
if (v2.message?.startsWith("[") == true && v1.message?.startsWith("[") == false) {
|
||||
v2.message!!.substring(v2.message!!.indexOf(']') + 2).assertIs(v1.message)
|
||||
} else {
|
||||
v2.message.assertIs(v1.message)
|
||||
}
|
||||
} else {
|
||||
if (v2.message?.startsWith("Inserted attachment at ID: AttachmentId::") == true) {
|
||||
v2.message!!
|
||||
.substring(0, v2.message!!.indexOf(','))
|
||||
.assertIs(
|
||||
v1.message!!
|
||||
.substring(0, v1.message!!.indexOf(','))
|
||||
)
|
||||
} else {
|
||||
v2.message.assertIs(v1.message)
|
||||
}
|
||||
}
|
||||
v2.throwable.assertIs(v1.throwable)
|
||||
}
|
||||
|
||||
v2Messages.zip(v1Messages)
|
||||
.forEach { (v2, v1) ->
|
||||
v2.assertIs(v1)
|
||||
}
|
||||
|
||||
v2Attachments.zip(v1Attachments)
|
||||
.forEach { (v2, v1) ->
|
||||
v2.assertIs(v1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun InMemoryLogger.logs(): List<Entry> {
|
||||
return entries()
|
||||
.filter { TAGS.contains(it.tag) }
|
||||
}
|
||||
|
||||
private fun dumpMessages(): List<List<Pair<String, String?>>> {
|
||||
return dumpTable(MessageTable.TABLE_NAME)
|
||||
.map { row ->
|
||||
val newRow = row.toMutableList()
|
||||
newRow.removeIf { IGNORE_MESSAGE_COLUMNS.contains(it.first) }
|
||||
newRow
|
||||
}
|
||||
}
|
||||
|
||||
private fun dumpAttachments(): List<List<Pair<String, String?>>> {
|
||||
return dumpTable(AttachmentTable.TABLE_NAME)
|
||||
.map { row ->
|
||||
val newRow = row.toMutableList()
|
||||
newRow.removeIf { IGNORE_ATTACHMENT_COLUMNS.contains(it.first) }
|
||||
newRow
|
||||
}
|
||||
}
|
||||
|
||||
private fun dumpTable(table: String): List<List<Pair<String, String?>>> {
|
||||
return SignalDatabase.rawDatabase
|
||||
.select()
|
||||
.from(table)
|
||||
.run()
|
||||
.readToList { cursor ->
|
||||
val map: List<Pair<String, String?>> = cursor.columnNames.map { column ->
|
||||
val index = cursor.getColumnIndex(column)
|
||||
var data: String? = when (cursor.getType(index)) {
|
||||
Cursor.FIELD_TYPE_BLOB -> Base64.encodeToString(cursor.getBlob(index), 0)
|
||||
else -> cursor.getString(index)
|
||||
}
|
||||
if (table == MessageTable.TABLE_NAME && column == MessageTable.TYPE) {
|
||||
data = MessageTableTestUtils.typeColumnToString(cursor.getLong(index))
|
||||
}
|
||||
|
||||
column to data
|
||||
}
|
||||
map
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toSignalServiceContent(envelope: SignalServiceProtos.Envelope, content: SignalServiceProtos.Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long): SignalServiceContent {
|
||||
val localAddress = SignalServiceAddress(metadata.destinationServiceId, Optional.ofNullable(SignalStore.account().e164))
|
||||
val signalServiceMetadata = SignalServiceMetadata(
|
||||
SignalServiceAddress(metadata.sourceServiceId, Optional.ofNullable(metadata.sourceE164)),
|
||||
metadata.sourceDeviceId,
|
||||
envelope.timestamp,
|
||||
envelope.serverTimestamp,
|
||||
serverDeliveredTimestamp,
|
||||
metadata.sealedSender,
|
||||
envelope.serverGuid,
|
||||
Optional.ofNullable(metadata.groupId),
|
||||
metadata.destinationServiceId.toString()
|
||||
)
|
||||
|
||||
val contentProto = SignalServiceContentProto.newBuilder()
|
||||
.setLocalAddress(SignalServiceAddressProtobufSerializer.toProtobuf(localAddress))
|
||||
.setMetadata(SignalServiceMetadataProtobufSerializer.toProtobuf(signalServiceMetadata))
|
||||
.setContent(content)
|
||||
.build()
|
||||
|
||||
return SignalServiceContent.createFromProto(contentProto)!!
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
package org.thoughtcrime.securesms.messages
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.toProtoByteString
|
||||
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith
|
||||
import org.thoughtcrime.securesms.testing.GroupTestingUtils
|
||||
import org.thoughtcrime.securesms.testing.GroupTestingUtils.asMember
|
||||
@@ -15,22 +15,22 @@ import org.thoughtcrime.securesms.testing.MessageContentFuzzer
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
import org.thoughtcrime.securesms.testing.assertIs
|
||||
import org.thoughtcrime.securesms.util.MessageTableTestUtils
|
||||
import org.whispersystems.signalservice.internal.push.DataMessage
|
||||
import org.whispersystems.signalservice.internal.push.GroupContextV2
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2
|
||||
|
||||
@Suppress("ClassName")
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MessageContentProcessor__recipientStatusTest {
|
||||
class MessageContentProcessorV2__recipientStatusTest {
|
||||
|
||||
@get:Rule
|
||||
val harness = SignalActivityRule()
|
||||
|
||||
private lateinit var processor: MessageContentProcessor
|
||||
private lateinit var processorV2: MessageContentProcessorV2
|
||||
private var envelopeTimestamp: Long = 0
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
processor = MessageContentProcessor(harness.context)
|
||||
processorV2 = MessageContentProcessorV2(harness.context)
|
||||
envelopeTimestamp = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
@@ -41,15 +41,15 @@ class MessageContentProcessor__recipientStatusTest {
|
||||
@Test
|
||||
fun syncGroupSentTextMessageWithRecipientUpdateFollowup() {
|
||||
val (groupId, masterKey, groupRecipientId) = GroupTestingUtils.insertGroup(revision = 0, harness.self.asMember(), harness.others[0].asMember(), harness.others[1].asMember())
|
||||
val groupContextV2 = GroupContextV2.Builder().revision(0).masterKey(masterKey.serialize().toByteString()).build()
|
||||
val groupContextV2 = GroupContextV2.newBuilder().setRevision(0).setMasterKey(masterKey.serialize().toProtoByteString()).build()
|
||||
|
||||
val initialTextMessage = DataMessage.Builder().buildWith {
|
||||
val initialTextMessage = DataMessage.newBuilder().buildWith {
|
||||
body = MessageContentFuzzer.string()
|
||||
groupV2 = groupContextV2
|
||||
timestamp = envelopeTimestamp
|
||||
}
|
||||
|
||||
processor.process(
|
||||
processorV2.process(
|
||||
envelope = MessageContentFuzzer.envelope(envelopeTimestamp),
|
||||
content = MessageContentFuzzer.syncSentTextMessage(initialTextMessage, deliveredTo = listOf(harness.others[0])),
|
||||
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, groupId),
|
||||
@@ -61,7 +61,7 @@ class MessageContentProcessor__recipientStatusTest {
|
||||
val firstMessageId = firstSyncMessages[0].id
|
||||
val firstReceiptInfo = SignalDatabase.groupReceipts.getGroupReceiptInfo(firstMessageId)
|
||||
|
||||
processor.process(
|
||||
processorV2.process(
|
||||
envelope = MessageContentFuzzer.envelope(envelopeTimestamp),
|
||||
content = MessageContentFuzzer.syncSentTextMessage(initialTextMessage, deliveredTo = listOf(harness.others[0], harness.others[1]), recipientUpdate = true),
|
||||
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, groupId),
|
||||
@@ -0,0 +1,181 @@
|
||||
package org.thoughtcrime.securesms.messages
|
||||
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||
import org.signal.storageservice.protos.groups.Member
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember
|
||||
import org.thoughtcrime.securesms.database.MessageTable
|
||||
import org.thoughtcrime.securesms.database.MmsHelper
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.ParentStoryId
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.testing.TestProtos
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||
import org.whispersystems.signalservice.api.push.DistributionId
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
|
||||
import kotlin.random.Random
|
||||
|
||||
@Suppress("ClassName")
|
||||
class MessageContentProcessor__handleStoryMessageTest : MessageContentProcessorTest() {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
SignalDatabase.messages.deleteAllThreads()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
SignalDatabase.messages.deleteAllThreads()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenContentWithADirectStoryReplyWhenIProcessThenIInsertAReplyInTheCorrectThread() {
|
||||
val sender = Recipient.resolved(harness.others.first())
|
||||
val senderThreadId = SignalDatabase.threads.getOrCreateThreadIdFor(sender)
|
||||
val myStory = Recipient.resolved(SignalDatabase.distributionLists.getRecipientId(DistributionListId.MY_STORY)!!)
|
||||
val myStoryThread = SignalDatabase.threads.getOrCreateThreadIdFor(myStory)
|
||||
val expectedSentTime = 200L
|
||||
val storyMessageId = MmsHelper.insert(
|
||||
sentTimeMillis = expectedSentTime,
|
||||
recipient = myStory,
|
||||
storyType = StoryType.STORY_WITH_REPLIES,
|
||||
threadId = myStoryThread
|
||||
)
|
||||
|
||||
SignalDatabase.storySends.insert(
|
||||
messageId = storyMessageId,
|
||||
recipientIds = listOf(sender.id),
|
||||
sentTimestamp = expectedSentTime,
|
||||
allowsReplies = true,
|
||||
distributionId = DistributionId.MY_STORY
|
||||
)
|
||||
|
||||
val expectedBody = "Hello!"
|
||||
|
||||
val storyContent: SignalServiceContentProto = createServiceContentWithStoryContext(
|
||||
messageSender = sender,
|
||||
storyAuthor = harness.self,
|
||||
storySentTimestamp = expectedSentTime
|
||||
) {
|
||||
body = expectedBody
|
||||
}
|
||||
|
||||
runTestWithContent(contentProto = storyContent)
|
||||
|
||||
val replyId = SignalDatabase.messages.getConversation(senderThreadId, 0, 1).use {
|
||||
it.moveToFirst()
|
||||
it.requireLong(MessageTable.ID)
|
||||
}
|
||||
|
||||
val replyRecord = SignalDatabase.messages.getMessageRecord(replyId) as MediaMmsMessageRecord
|
||||
assertEquals(ParentStoryId.DirectReply(storyMessageId).serialize(), replyRecord.parentStoryId!!.serialize())
|
||||
assertEquals(expectedBody, replyRecord.body)
|
||||
|
||||
SignalDatabase.messages.deleteAllThreads()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenContentWithAGroupStoryReplyWhenIProcessThenIInsertAReplyToTheCorrectStory() {
|
||||
val sender = Recipient.resolved(harness.others[0])
|
||||
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
|
||||
val decryptedGroupState = DecryptedGroup.newBuilder()
|
||||
.addAllMembers(
|
||||
listOf(
|
||||
DecryptedMember.newBuilder()
|
||||
.setUuid(harness.self.requireServiceId().toByteString())
|
||||
.setJoinedAtRevision(0)
|
||||
.setRole(Member.Role.DEFAULT)
|
||||
.build(),
|
||||
DecryptedMember.newBuilder()
|
||||
.setUuid(sender.requireServiceId().toByteString())
|
||||
.setJoinedAtRevision(0)
|
||||
.setRole(Member.Role.DEFAULT)
|
||||
.build()
|
||||
)
|
||||
)
|
||||
.setRevision(0)
|
||||
.build()
|
||||
|
||||
val group = SignalDatabase.groups.create(
|
||||
groupMasterKey,
|
||||
decryptedGroupState
|
||||
)
|
||||
|
||||
val groupRecipient = Recipient.externalGroupExact(group!!)
|
||||
val threadForGroup = SignalDatabase.threads.getOrCreateThreadIdFor(groupRecipient)
|
||||
|
||||
val insertResult = MmsHelper.insert(
|
||||
message = IncomingMediaMessage(
|
||||
from = sender.id,
|
||||
sentTimeMillis = 100L,
|
||||
serverTimeMillis = 101L,
|
||||
receivedTimeMillis = 102L,
|
||||
storyType = StoryType.STORY_WITH_REPLIES
|
||||
),
|
||||
threadId = threadForGroup
|
||||
)
|
||||
|
||||
val expectedBody = "Hello, World!"
|
||||
val storyContent: SignalServiceContentProto = createServiceContentWithStoryContext(
|
||||
messageSender = sender,
|
||||
storyAuthor = sender,
|
||||
storySentTimestamp = 100L
|
||||
) {
|
||||
groupV2 = TestProtos.build { groupContextV2(masterKeyBytes = groupMasterKey.serialize()).build() }
|
||||
body = expectedBody
|
||||
}
|
||||
|
||||
runTestWithContent(storyContent)
|
||||
|
||||
val replyId = SignalDatabase.messages.getStoryReplies(insertResult.get().messageId).use { cursor ->
|
||||
assertEquals(1, cursor.count)
|
||||
cursor.moveToFirst()
|
||||
cursor.requireLong(MessageTable.ID)
|
||||
}
|
||||
|
||||
val replyRecord = SignalDatabase.messages.getMessageRecord(replyId) as MediaMmsMessageRecord
|
||||
assertEquals(ParentStoryId.GroupReply(insertResult.get().messageId).serialize(), replyRecord.parentStoryId?.serialize())
|
||||
assertEquals(threadForGroup, replyRecord.threadId)
|
||||
assertEquals(expectedBody, replyRecord.body)
|
||||
|
||||
SignalDatabase.messages.deleteGroupStoryReplies(insertResult.get().messageId)
|
||||
SignalDatabase.messages.deleteAllThreads()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ServiceContent proto with a StoryContext, and then
|
||||
* uses `injectDataMessage` to fill in the data message object.
|
||||
*/
|
||||
private fun createServiceContentWithStoryContext(
|
||||
messageSender: Recipient,
|
||||
storyAuthor: Recipient,
|
||||
storySentTimestamp: Long,
|
||||
injectDataMessage: DataMessage.Builder.() -> Unit
|
||||
): SignalServiceContentProto {
|
||||
return createServiceContentWithDataMessage(messageSender) {
|
||||
storyContext = TestProtos.build {
|
||||
storyContext(
|
||||
sentTimestamp = storySentTimestamp,
|
||||
authorUuid = storyAuthor.requireServiceId().toString()
|
||||
).build()
|
||||
}
|
||||
injectDataMessage()
|
||||
}
|
||||
}
|
||||
|
||||
private fun runTestWithContent(contentProto: SignalServiceContentProto) {
|
||||
val content = SignalServiceContent.createFromProto(contentProto)
|
||||
val testSubject = createNormalContentTestSubject()
|
||||
testSubject.doProcess(content = content!!)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.thoughtcrime.securesms.messages
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
|
||||
|
||||
@Suppress("ClassName")
|
||||
class MessageContentProcessor__handleTextMessageTest : MessageContentProcessorTest() {
|
||||
@Test
|
||||
fun givenContentWithATextMessageWhenIProcessThenIInsertTheTextMessage() {
|
||||
val testSubject: MessageContentProcessor = createNormalContentTestSubject()
|
||||
val expectedBody = "Hello, World!"
|
||||
val contentProto: SignalServiceContentProto = createServiceContentWithDataMessage {
|
||||
body = expectedBody
|
||||
}
|
||||
|
||||
val content = SignalServiceContent.createFromProto(contentProto)
|
||||
|
||||
// WHEN
|
||||
testSubject.doProcess(content = content!!)
|
||||
|
||||
// THEN
|
||||
val record = SignalDatabase.messages.getMessageRecord(1)
|
||||
val threadSize = SignalDatabase.messages.getMessageCountForThread(record.threadId)
|
||||
assertEquals(1, threadSize)
|
||||
|
||||
assertTrue(record.isSecure)
|
||||
assertEquals(expectedBody, record.body)
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,9 @@ import io.mockk.mockkObject
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import okio.ByteString
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -25,9 +25,9 @@ import org.thoughtcrime.securesms.testing.Entry
|
||||
import org.thoughtcrime.securesms.testing.FakeClientHelpers
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
import org.thoughtcrime.securesms.testing.awaitFor
|
||||
import org.whispersystems.signalservice.internal.push.Envelope
|
||||
import org.whispersystems.signalservice.internal.websocket.WebSocketMessage
|
||||
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
|
||||
import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketMessage
|
||||
import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.random.Random
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
@@ -37,7 +37,7 @@ import android.util.Log as AndroidLog
|
||||
/**
|
||||
* Sends N messages from Bob to Alice to track performance of Alice's processing of messages.
|
||||
*/
|
||||
@Ignore("Ignore test in normal testing as it's a performance test with no assertions")
|
||||
// @Ignore("Ignore test in normal testing as it's a performance test with no assertions")
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MessageProcessingPerformanceTest {
|
||||
|
||||
@@ -58,14 +58,14 @@ class MessageProcessingPerformanceTest {
|
||||
mockkStatic(UnidentifiedAccessUtil::class)
|
||||
every { UnidentifiedAccessUtil.getCertificateValidator() } returns FakeClientHelpers.noOpCertificateValidator
|
||||
|
||||
mockkObject(MessageContentProcessor)
|
||||
every { MessageContentProcessor.create(harness.application) } returns TimingMessageContentProcessor(harness.application)
|
||||
mockkObject(MessageContentProcessorV2)
|
||||
every { MessageContentProcessorV2.create(harness.application) } returns TimingMessageContentProcessorV2(harness.application)
|
||||
}
|
||||
|
||||
@After
|
||||
fun after() {
|
||||
unmockkStatic(UnidentifiedAccessUtil::class)
|
||||
unmockkStatic(MessageContentProcessor::class)
|
||||
unmockkStatic(MessageContentProcessorV2::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -92,7 +92,7 @@ class MessageProcessingPerformanceTest {
|
||||
val messageCount = 100
|
||||
val envelopes = generateInboundEnvelopes(bobClient, messageCount)
|
||||
val firstTimestamp = envelopes.first().timestamp
|
||||
val lastTimestamp = envelopes.last().timestamp ?: 0
|
||||
val lastTimestamp = envelopes.last().timestamp
|
||||
|
||||
// Inject the envelopes into the websocket
|
||||
Thread {
|
||||
@@ -106,7 +106,7 @@ class MessageProcessingPerformanceTest {
|
||||
// Wait until they've all been fully decrypted + processed
|
||||
harness
|
||||
.inMemoryLogger
|
||||
.getLockForUntil(TimingMessageContentProcessor.endTagPredicate(lastTimestamp))
|
||||
.getLockForUntil(TimingMessageContentProcessorV2.endTagPredicate(lastTimestamp))
|
||||
.awaitFor(1.minutes)
|
||||
|
||||
harness.inMemoryLogger.flush()
|
||||
@@ -125,7 +125,7 @@ class MessageProcessingPerformanceTest {
|
||||
|
||||
// Calculate MessageContentProcessor
|
||||
|
||||
val takeLast: List<Entry> = entries.filter { it.tag == TimingMessageContentProcessor.TAG }.drop(2)
|
||||
val takeLast: List<Entry> = entries.filter { it.tag == TimingMessageContentProcessorV2.TAG }.drop(2)
|
||||
val iterator = takeLast.iterator()
|
||||
var processCount = 0L
|
||||
var processDuration = 0L
|
||||
@@ -141,7 +141,7 @@ class MessageProcessingPerformanceTest {
|
||||
// Calculate messages per second from "retrieving" first message post session initialization to processing last message
|
||||
|
||||
val start = entries.first { it.message == "Retrieved envelope! $firstTimestamp" }
|
||||
val end = entries.first { it.message == TimingMessageContentProcessor.endTag(lastTimestamp) }
|
||||
val end = entries.first { it.message == TimingMessageContentProcessorV2.endTag(lastTimestamp) }
|
||||
|
||||
val duration = (end.timestamp - start.timestamp).toFloat() / 1000f
|
||||
val messagePerSecond = messageCount.toFloat() / duration
|
||||
@@ -156,7 +156,7 @@ class MessageProcessingPerformanceTest {
|
||||
|
||||
val aliceProcessFirstMessageLatch = harness
|
||||
.inMemoryLogger
|
||||
.getLockForUntil(TimingMessageContentProcessor.endTagPredicate(firstPreKeyMessageTimestamp))
|
||||
.getLockForUntil(TimingMessageContentProcessorV2.endTagPredicate(firstPreKeyMessageTimestamp))
|
||||
|
||||
Thread { aliceClient.process(encryptedEnvelope, System.currentTimeMillis()) }.start()
|
||||
aliceProcessFirstMessageLatch.awaitFor(15.seconds)
|
||||
@@ -178,19 +178,32 @@ class MessageProcessingPerformanceTest {
|
||||
}
|
||||
|
||||
private fun webSocketTombstone(): ByteString {
|
||||
return WebSocketMessage(request = WebSocketRequestMessage(verb = "PUT", path = "/api/v1/queue/empty")).encodeByteString()
|
||||
return WebSocketMessage
|
||||
.newBuilder()
|
||||
.setRequest(
|
||||
WebSocketRequestMessage.newBuilder()
|
||||
.setVerb("PUT")
|
||||
.setPath("/api/v1/queue/empty")
|
||||
)
|
||||
.build()
|
||||
.toByteArray()
|
||||
.toByteString()
|
||||
}
|
||||
|
||||
private fun Envelope.toWebSocketPayload(): ByteString {
|
||||
return WebSocketMessage(
|
||||
type = WebSocketMessage.Type.REQUEST,
|
||||
request = WebSocketRequestMessage(
|
||||
verb = "PUT",
|
||||
path = "/api/v1/message",
|
||||
id = Random(System.currentTimeMillis()).nextLong(),
|
||||
headers = listOf("X-Signal-Timestamp: ${this.timestamp}"),
|
||||
body = this.encodeByteString()
|
||||
return WebSocketMessage
|
||||
.newBuilder()
|
||||
.setType(WebSocketMessage.Type.REQUEST)
|
||||
.setRequest(
|
||||
WebSocketRequestMessage.newBuilder()
|
||||
.setVerb("PUT")
|
||||
.setPath("/api/v1/message")
|
||||
.setId(Random(System.currentTimeMillis()).nextLong())
|
||||
.addHeaders("X-Signal-Timestamp: ${this.timestamp}")
|
||||
.setBody(this.toByteString())
|
||||
)
|
||||
).encodeByteString()
|
||||
.build()
|
||||
.toByteArray()
|
||||
.toByteString()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package org.thoughtcrime.securesms.messages
|
||||
|
||||
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
|
||||
import org.whispersystems.signalservice.internal.push.Content
|
||||
import org.whispersystems.signalservice.internal.push.Envelope
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||
|
||||
data class TestMessage(
|
||||
val envelope: Envelope,
|
||||
val content: Content,
|
||||
val envelope: SignalServiceProtos.Envelope,
|
||||
val content: SignalServiceProtos.Content,
|
||||
val metadata: EnvelopeMetadata,
|
||||
val serverDeliveredTimestamp: Long
|
||||
)
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.thoughtcrime.securesms.messages
|
||||
|
||||
import android.content.Context
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.testing.LogPredicate
|
||||
import org.thoughtcrime.securesms.util.SignalLocalMetrics
|
||||
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
|
||||
import org.whispersystems.signalservice.internal.push.Content
|
||||
import org.whispersystems.signalservice.internal.push.Envelope
|
||||
|
||||
class TimingMessageContentProcessor(context: Context) : MessageContentProcessor(context) {
|
||||
companion object {
|
||||
val TAG = Log.tag(TimingMessageContentProcessor::class.java)
|
||||
|
||||
fun endTagPredicate(timestamp: Long): LogPredicate = { entry ->
|
||||
entry.tag == TAG && entry.message == endTag(timestamp)
|
||||
}
|
||||
|
||||
private fun startTag(timestamp: Long) = "$timestamp start"
|
||||
fun endTag(timestamp: Long) = "$timestamp end"
|
||||
}
|
||||
|
||||
override fun process(envelope: Envelope, content: Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long, processingEarlyContent: Boolean, localMetric: SignalLocalMetrics.MessageReceive?) {
|
||||
Log.d(TAG, startTag(envelope.timestamp!!))
|
||||
super.process(envelope, content, metadata, serverDeliveredTimestamp, processingEarlyContent, localMetric)
|
||||
Log.d(TAG, endTag(envelope.timestamp!!))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.thoughtcrime.securesms.messages
|
||||
|
||||
import android.content.Context
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.testing.LogPredicate
|
||||
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||
|
||||
class TimingMessageContentProcessorV2(context: Context) : MessageContentProcessorV2(context) {
|
||||
companion object {
|
||||
val TAG = Log.tag(TimingMessageContentProcessorV2::class.java)
|
||||
|
||||
fun endTagPredicate(timestamp: Long): LogPredicate = { entry ->
|
||||
entry.tag == TAG && entry.message == endTag(timestamp)
|
||||
}
|
||||
|
||||
private fun startTag(timestamp: Long) = "$timestamp start"
|
||||
fun endTag(timestamp: Long) = "$timestamp end"
|
||||
}
|
||||
|
||||
override fun process(envelope: SignalServiceProtos.Envelope, content: SignalServiceProtos.Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long, processingEarlyContent: Boolean) {
|
||||
Log.d(TAG, startTag(envelope.timestamp))
|
||||
super.process(envelope, content, metadata, serverDeliveredTimestamp, processingEarlyContent)
|
||||
Log.d(TAG, endTag(envelope.timestamp))
|
||||
}
|
||||
}
|
||||
@@ -6,16 +6,16 @@ import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.update
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.Base64
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.FeatureFlagsAccessor
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
import org.whispersystems.signalservice.api.push.ACI
|
||||
import org.whispersystems.signalservice.api.push.PNI
|
||||
import org.whispersystems.signalservice.api.storage.SignalContactRecord
|
||||
import org.whispersystems.signalservice.api.storage.StorageId
|
||||
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord
|
||||
@@ -38,31 +38,26 @@ class ContactRecordProcessorTest {
|
||||
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
|
||||
setStorageId(originalId, STORAGE_ID_A)
|
||||
|
||||
val remote1 = buildRecord(
|
||||
STORAGE_ID_B,
|
||||
ContactRecord(
|
||||
aci = ACI_A.toString(),
|
||||
unregisteredAtTimestamp = 100
|
||||
)
|
||||
)
|
||||
val remote1 = buildRecord(STORAGE_ID_B) {
|
||||
setServiceId(ACI_A.toString())
|
||||
setUnregisteredAtTimestamp(100)
|
||||
}
|
||||
|
||||
val remote2 = buildRecord(
|
||||
STORAGE_ID_C,
|
||||
ContactRecord(
|
||||
pni = PNI_A.toString(),
|
||||
e164 = E164_A
|
||||
)
|
||||
)
|
||||
val remote2 = buildRecord(STORAGE_ID_C) {
|
||||
setServiceId(PNI_A.toString())
|
||||
setServicePni(PNI_A.toString())
|
||||
setServiceE164(E164_A)
|
||||
}
|
||||
|
||||
// WHEN
|
||||
val subject = ContactRecordProcessor()
|
||||
subject.process(listOf(remote1, remote2), StorageSyncHelper.KEY_GENERATOR)
|
||||
|
||||
// THEN
|
||||
val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
|
||||
val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get()
|
||||
|
||||
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
|
||||
val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get()
|
||||
val byPni: RecipientId = SignalDatabase.recipients.getByServiceId(PNI_A).get()
|
||||
|
||||
assertEquals(originalId, byAci)
|
||||
assertEquals(byE164, byPni)
|
||||
@@ -75,29 +70,23 @@ class ContactRecordProcessorTest {
|
||||
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
|
||||
setStorageId(originalId, STORAGE_ID_A)
|
||||
|
||||
val remote1 = buildRecord(
|
||||
STORAGE_ID_B,
|
||||
ContactRecord(
|
||||
aci = ACI_A.toString(),
|
||||
unregisteredAtTimestamp = 0
|
||||
)
|
||||
)
|
||||
val remote1 = buildRecord(STORAGE_ID_B) {
|
||||
setServiceId(ACI_A.toString())
|
||||
setUnregisteredAtTimestamp(0)
|
||||
}
|
||||
|
||||
val remote2 = buildRecord(
|
||||
STORAGE_ID_C,
|
||||
ContactRecord(
|
||||
aci = PNI_A.toString(),
|
||||
pni = PNI_A.toString(),
|
||||
e164 = E164_A
|
||||
)
|
||||
)
|
||||
val remote2 = buildRecord(STORAGE_ID_C) {
|
||||
setServiceId(PNI_A.toString())
|
||||
setServicePni(PNI_A.toString())
|
||||
setServiceE164(E164_A)
|
||||
}
|
||||
|
||||
// WHEN
|
||||
val subject = ContactRecordProcessor()
|
||||
subject.process(listOf(remote1, remote2), StorageSyncHelper.KEY_GENERATOR)
|
||||
|
||||
// THEN
|
||||
val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
|
||||
val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get()
|
||||
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
|
||||
val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get()
|
||||
|
||||
@@ -106,14 +95,14 @@ class ContactRecordProcessorTest {
|
||||
assertEquals(byAci, byE164)
|
||||
}
|
||||
|
||||
private fun buildRecord(id: StorageId, record: ContactRecord): SignalContactRecord {
|
||||
return SignalContactRecord(id, record)
|
||||
private fun buildRecord(id: StorageId, applyParams: ContactRecord.Builder.() -> ContactRecord.Builder): SignalContactRecord {
|
||||
return SignalContactRecord(id, ContactRecord.getDefaultInstance().toBuilder().applyParams().build())
|
||||
}
|
||||
|
||||
private fun setStorageId(recipientId: RecipientId, storageId: StorageId) {
|
||||
SignalDatabase.rawDatabase
|
||||
.update(RecipientTable.TABLE_NAME)
|
||||
.values(RecipientTable.STORAGE_SERVICE_ID to Base64.encodeWithPadding(storageId.raw))
|
||||
.values(RecipientTable.STORAGE_SERVICE_ID to Base64.encodeBytes(storageId.raw))
|
||||
.where("${RecipientTable.ID} = ?", recipientId)
|
||||
.run()
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.testing.FakeClientHelpers.toEnvelope
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.internal.push.Envelope
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
|
||||
|
||||
/**
|
||||
* Welcome to Alice's Client.
|
||||
@@ -27,7 +27,7 @@ class AliceClient(val serviceId: ServiceId, val e164: String, val trustRoot: ECK
|
||||
|
||||
private val aliceSenderCertificate = FakeClientHelpers.createCertificateFor(
|
||||
trustRoot = trustRoot,
|
||||
uuid = serviceId.rawUuid,
|
||||
uuid = serviceId.uuid(),
|
||||
e164 = e164,
|
||||
deviceId = 1,
|
||||
identityKey = SignalStore.account().aciIdentityKey.publicKey.publicKey,
|
||||
|
||||
@@ -31,7 +31,7 @@ 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 org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
@@ -49,7 +49,7 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
|
||||
private val serviceAddress = SignalServiceAddress(serviceId, e164)
|
||||
private val registrationId = KeyHelper.generateRegistrationId(false)
|
||||
private val aciStore = BobSignalServiceAccountDataStore(registrationId, identityKeyPair)
|
||||
private val senderCertificate = FakeClientHelpers.createCertificateFor(trustRoot, serviceId.rawUuid, e164, 1, identityKeyPair.publicKey.publicKey, 31337)
|
||||
private val senderCertificate = FakeClientHelpers.createCertificateFor(trustRoot, serviceId.uuid(), e164, 1, identityKeyPair.publicKey.publicKey, 31337)
|
||||
private val sessionLock = object : SignalSessionLock {
|
||||
private val lock = ReentrantLock()
|
||||
|
||||
@@ -60,7 +60,7 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
|
||||
}
|
||||
|
||||
/** Inspired by SignalServiceMessageSender#getEncryptedMessage */
|
||||
fun encrypt(now: Long): Envelope {
|
||||
fun encrypt(now: Long): SignalServiceProtos.Envelope {
|
||||
val envelopeContent = FakeClientHelpers.encryptedTextMessage(now)
|
||||
|
||||
val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, null)
|
||||
@@ -71,10 +71,10 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
|
||||
}
|
||||
|
||||
return cipher.encrypt(getAliceProtocolAddress(), getAliceUnidentifiedAccess(), envelopeContent)
|
||||
.toEnvelope(envelopeContent.content.get().dataMessage!!.timestamp!!, getAliceServiceId())
|
||||
.toEnvelope(envelopeContent.content.get().dataMessage.timestamp, getAliceServiceId())
|
||||
}
|
||||
|
||||
fun decrypt(envelope: Envelope, serverDeliveredTimestamp: Long) {
|
||||
fun decrypt(envelope: SignalServiceProtos.Envelope, serverDeliveredTimestamp: Long) {
|
||||
val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, UnidentifiedAccessUtil.getCertificateValidator())
|
||||
cipher.decrypt(envelope, serverDeliveredTimestamp)
|
||||
}
|
||||
@@ -143,6 +143,7 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
|
||||
override fun getSubDeviceSessions(name: String?): List<Int> = emptyList()
|
||||
override fun containsSession(address: SignalProtocolAddress?): Boolean = aliceSessionRecord != null
|
||||
override fun getIdentity(address: SignalProtocolAddress?): IdentityKey = SignalStore.account().aciIdentityKey.publicKey
|
||||
|
||||
override fun loadPreKey(preKeyId: Int): PreKeyRecord = throw UnsupportedOperationException()
|
||||
override fun storePreKey(preKeyId: Int, record: PreKeyRecord?) = throw UnsupportedOperationException()
|
||||
override fun containsPreKey(preKeyId: Int): Boolean = throw UnsupportedOperationException()
|
||||
@@ -160,8 +161,6 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
|
||||
override fun storeKyberPreKey(kyberPreKeyId: Int, record: KyberPreKeyRecord?) = throw UnsupportedOperationException()
|
||||
override fun containsKyberPreKey(kyberPreKeyId: Int): Boolean = throw UnsupportedOperationException()
|
||||
override fun markKyberPreKeyUsed(kyberPreKeyId: Int) = throw UnsupportedOperationException()
|
||||
override fun deleteAllStaleOneTimeEcPreKeys(threshold: Long, minCount: Int) = throw UnsupportedOperationException()
|
||||
override fun markAllOneTimeEcPreKeysStaleIfNecessary(staleTime: Long) = throw UnsupportedOperationException()
|
||||
override fun storeSenderKey(sender: SignalProtocolAddress?, distributionId: UUID?, record: SenderKeyRecord?) = throw UnsupportedOperationException()
|
||||
override fun loadSenderKey(sender: SignalProtocolAddress?, distributionId: UUID?): SenderKeyRecord = throw UnsupportedOperationException()
|
||||
override fun archiveSession(address: SignalProtocolAddress?) = throw UnsupportedOperationException()
|
||||
@@ -169,11 +168,6 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
|
||||
override fun getSenderKeySharedWith(distributionId: DistributionId?): MutableSet<SignalProtocolAddress> = throw UnsupportedOperationException()
|
||||
override fun markSenderKeySharedWith(distributionId: DistributionId?, addresses: MutableCollection<SignalProtocolAddress>?) = throw UnsupportedOperationException()
|
||||
override fun clearSenderKeySharedWith(addresses: MutableCollection<SignalProtocolAddress>?) = throw UnsupportedOperationException()
|
||||
override fun storeLastResortKyberPreKey(kyberPreKeyId: Int, kyberPreKeyRecord: KyberPreKeyRecord) = throw UnsupportedOperationException()
|
||||
override fun removeKyberPreKey(kyberPreKeyId: Int) = throw UnsupportedOperationException()
|
||||
override fun markAllOneTimeKyberPreKeysStaleIfNecessary(staleTime: Long) = throw UnsupportedOperationException()
|
||||
override fun deleteAllStaleOneTimeKyberPreKeys(threshold: Long, minCount: Int) = throw UnsupportedOperationException()
|
||||
override fun loadLastResortKyberPreKeys(): List<KyberPreKeyRecord> = throw UnsupportedOperationException()
|
||||
override fun isMultiDevice(): Boolean = throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.thoughtcrime.securesms.testing
|
||||
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.libsignal.internal.Native
|
||||
import org.signal.libsignal.internal.NativeHandleGuard
|
||||
import org.signal.libsignal.metadata.certificate.CertificateValidator
|
||||
@@ -11,16 +9,16 @@ import org.signal.libsignal.protocol.ecc.Curve
|
||||
import org.signal.libsignal.protocol.ecc.ECKeyPair
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey
|
||||
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith
|
||||
import org.thoughtcrime.securesms.database.model.toProtoByteString
|
||||
import org.whispersystems.signalservice.api.crypto.ContentHint
|
||||
import org.whispersystems.signalservice.api.crypto.EnvelopeContent
|
||||
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
|
||||
import org.whispersystems.signalservice.internal.push.Envelope
|
||||
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
|
||||
import org.whispersystems.util.Base64
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
|
||||
@@ -54,9 +52,9 @@ object FakeClientHelpers {
|
||||
}
|
||||
|
||||
fun encryptedTextMessage(now: Long, message: String = "Test body message"): EnvelopeContent {
|
||||
val content = Content.Builder().apply {
|
||||
dataMessage(
|
||||
DataMessage.Builder().buildWith {
|
||||
val content = SignalServiceProtos.Content.newBuilder().apply {
|
||||
setDataMessage(
|
||||
SignalServiceProtos.DataMessage.newBuilder().apply {
|
||||
body = message
|
||||
timestamp = now
|
||||
}
|
||||
@@ -66,16 +64,16 @@ object FakeClientHelpers {
|
||||
}
|
||||
|
||||
fun OutgoingPushMessage.toEnvelope(timestamp: Long, destination: ServiceId): Envelope {
|
||||
return Envelope.Builder()
|
||||
.type(Envelope.Type.fromValue(this.type))
|
||||
.sourceDevice(1)
|
||||
.timestamp(timestamp)
|
||||
.serverTimestamp(timestamp + 1)
|
||||
.destinationServiceId(destination.toString())
|
||||
.serverGuid(UUID.randomUUID().toString())
|
||||
.content(Base64.decode(this.content).toByteString())
|
||||
.urgent(true)
|
||||
.story(false)
|
||||
return Envelope.newBuilder()
|
||||
.setType(Envelope.Type.valueOf(this.type))
|
||||
.setSourceDevice(1)
|
||||
.setTimestamp(timestamp)
|
||||
.setServerTimestamp(timestamp + 1)
|
||||
.setDestinationUuid(destination.toString())
|
||||
.setServerGuid(UUID.randomUUID().toString())
|
||||
.setContent(Base64.decode(this.content).toProtoByteString())
|
||||
.setUrgent(true)
|
||||
.setStory(false)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,27 +8,27 @@ import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* Helper methods for creating groups for message processing tests et al.
|
||||
*/
|
||||
object GroupTestingUtils {
|
||||
fun member(aci: ACI, revision: Int = 0, role: Member.Role = Member.Role.ADMINISTRATOR): DecryptedMember {
|
||||
return DecryptedMember.Builder()
|
||||
.aciBytes(aci.toByteString())
|
||||
.joinedAtRevision(revision)
|
||||
.role(role)
|
||||
fun member(serviceId: ServiceId, revision: Int = 0, role: Member.Role = Member.Role.ADMINISTRATOR): DecryptedMember {
|
||||
return DecryptedMember.newBuilder()
|
||||
.setUuid(serviceId.toByteString())
|
||||
.setJoinedAtRevision(revision)
|
||||
.setRole(role)
|
||||
.build()
|
||||
}
|
||||
|
||||
fun insertGroup(revision: Int = 0, vararg members: DecryptedMember): TestGroupInfo {
|
||||
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
|
||||
val decryptedGroupState = DecryptedGroup.Builder()
|
||||
.members(members.toList())
|
||||
.revision(revision)
|
||||
.title(MessageContentFuzzer.string())
|
||||
val decryptedGroupState = DecryptedGroup.newBuilder()
|
||||
.addAllMembers(members.toList())
|
||||
.setRevision(revision)
|
||||
.setTitle(MessageContentFuzzer.string())
|
||||
.build()
|
||||
|
||||
val groupId = SignalDatabase.groups.create(groupMasterKey, decryptedGroupState)!!
|
||||
@@ -43,7 +43,7 @@ object GroupTestingUtils {
|
||||
}
|
||||
|
||||
fun Recipient.asMember(): DecryptedMember {
|
||||
return member(aci = requireAci())
|
||||
return member(serviceId = requireServiceId())
|
||||
}
|
||||
|
||||
data class TestGroupInfo(val groupId: GroupId.V2, val masterKey: GroupMasterKey, val recipientId: RecipientId)
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
package org.thoughtcrime.securesms.testing
|
||||
|
||||
import okio.ByteString
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import com.google.protobuf.ByteString
|
||||
import org.thoughtcrime.securesms.database.model.toProtoByteString
|
||||
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.internal.push.AttachmentPointer
|
||||
import org.whispersystems.signalservice.internal.push.BodyRange
|
||||
import org.whispersystems.signalservice.internal.push.Content
|
||||
import org.whispersystems.signalservice.internal.push.DataMessage
|
||||
import org.whispersystems.signalservice.internal.push.Envelope
|
||||
import org.whispersystems.signalservice.internal.push.GroupContextV2
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.AttachmentPointer
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage
|
||||
import java.util.UUID
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextInt
|
||||
@@ -34,10 +35,10 @@ object MessageContentFuzzer {
|
||||
* Create an [Envelope].
|
||||
*/
|
||||
fun envelope(timestamp: Long): Envelope {
|
||||
return Envelope.Builder()
|
||||
.timestamp(timestamp)
|
||||
.serverTimestamp(timestamp + 5)
|
||||
.serverGuid(UUID.randomUUID().toString())
|
||||
return Envelope.newBuilder()
|
||||
.setTimestamp(timestamp)
|
||||
.setServerTimestamp(timestamp + 5)
|
||||
.setServerGuidBytes(UuidUtil.toByteString(UUID.randomUUID()))
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -61,22 +62,20 @@ object MessageContentFuzzer {
|
||||
* - Bold style body ranges
|
||||
*/
|
||||
fun fuzzTextMessage(groupContextV2: GroupContextV2? = null): Content {
|
||||
return Content.Builder()
|
||||
.dataMessage(
|
||||
DataMessage.Builder().buildWith {
|
||||
return Content.newBuilder()
|
||||
.setDataMessage(
|
||||
DataMessage.newBuilder().buildWith {
|
||||
body = string()
|
||||
if (random.nextBoolean()) {
|
||||
expireTimer = random.nextInt(0..28.days.inWholeSeconds.toInt())
|
||||
}
|
||||
if (random.nextBoolean()) {
|
||||
bodyRanges(
|
||||
listOf(
|
||||
BodyRange.Builder().buildWith {
|
||||
start = 0
|
||||
length = 1
|
||||
style = BodyRange.Style.BOLD
|
||||
}
|
||||
)
|
||||
addBodyRanges(
|
||||
SignalServiceProtos.BodyRange.newBuilder().buildWith {
|
||||
start = 0
|
||||
length = 1
|
||||
style = SignalServiceProtos.BodyRange.Style.BOLD
|
||||
}
|
||||
)
|
||||
}
|
||||
if (groupContextV2 != null) {
|
||||
@@ -96,17 +95,17 @@ object MessageContentFuzzer {
|
||||
recipientUpdate: Boolean = false
|
||||
): Content {
|
||||
return Content
|
||||
.Builder()
|
||||
.syncMessage(
|
||||
SyncMessage.Builder().buildWith {
|
||||
sent = SyncMessage.Sent.Builder().buildWith {
|
||||
.newBuilder()
|
||||
.setSyncMessage(
|
||||
SyncMessage.newBuilder().buildWith {
|
||||
sent = SyncMessage.Sent.newBuilder().buildWith {
|
||||
timestamp = textMessage.timestamp
|
||||
message = textMessage
|
||||
isRecipientUpdate = recipientUpdate
|
||||
unidentifiedStatus(
|
||||
addAllUnidentifiedStatus(
|
||||
deliveredTo.map {
|
||||
SyncMessage.Sent.UnidentifiedDeliveryStatus.Builder().buildWith {
|
||||
destinationServiceId = Recipient.resolved(it).requireServiceId().toString()
|
||||
SyncMessage.Sent.UnidentifiedDeliveryStatus.newBuilder().buildWith {
|
||||
destinationUuid = Recipient.resolved(it).requireServiceId().toString()
|
||||
unidentified = true
|
||||
}
|
||||
}
|
||||
@@ -124,9 +123,9 @@ object MessageContentFuzzer {
|
||||
* - A message with 0-2 attachment pointers and may contain a text body
|
||||
*/
|
||||
fun fuzzMediaMessageWithBody(quoteAble: List<TestMessage> = emptyList()): Content {
|
||||
return Content.Builder()
|
||||
.dataMessage(
|
||||
DataMessage.Builder().buildWith {
|
||||
return Content.newBuilder()
|
||||
.setDataMessage(
|
||||
DataMessage.newBuilder().buildWith {
|
||||
if (random.nextBoolean()) {
|
||||
body = string()
|
||||
}
|
||||
@@ -134,28 +133,28 @@ object MessageContentFuzzer {
|
||||
if (random.nextBoolean() && quoteAble.isNotEmpty()) {
|
||||
body = string()
|
||||
val quoted = quoteAble.random(random)
|
||||
quote = DataMessage.Quote.Builder().buildWith {
|
||||
quote = DataMessage.Quote.newBuilder().buildWith {
|
||||
id = quoted.envelope.timestamp
|
||||
authorAci = quoted.metadata.sourceServiceId.toString()
|
||||
text = quoted.content.dataMessage?.body
|
||||
attachments(quoted.content.dataMessage?.attachments ?: emptyList())
|
||||
bodyRanges(quoted.content.dataMessage?.bodyRanges ?: emptyList())
|
||||
authorUuid = quoted.metadata.sourceServiceId.toString()
|
||||
text = quoted.content.dataMessage.body
|
||||
addAllAttachments(quoted.content.dataMessage.attachmentsList)
|
||||
addAllBodyRanges(quoted.content.dataMessage.bodyRangesList)
|
||||
type = DataMessage.Quote.Type.NORMAL
|
||||
}
|
||||
}
|
||||
|
||||
if (random.nextFloat() < 0.1 && quoteAble.isNotEmpty()) {
|
||||
val quoted = quoteAble.random(random)
|
||||
quote = DataMessage.Quote.Builder().buildWith {
|
||||
id = random.nextLong(quoted.envelope.timestamp!! - 1000000, quoted.envelope.timestamp!!)
|
||||
authorAci = quoted.metadata.sourceServiceId.toString()
|
||||
text = quoted.content.dataMessage?.body
|
||||
quote = DataMessage.Quote.newBuilder().buildWith {
|
||||
id = random.nextLong(quoted.envelope.timestamp - 1000000, quoted.envelope.timestamp)
|
||||
authorUuid = quoted.metadata.sourceServiceId.toString()
|
||||
text = quoted.content.dataMessage.body
|
||||
}
|
||||
}
|
||||
|
||||
if (random.nextFloat() < 0.25) {
|
||||
val total = random.nextInt(1, 2)
|
||||
attachments((0..total).map { attachmentPointer() })
|
||||
(0..total).forEach { _ -> addAttachments(attachmentPointer()) }
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -167,15 +166,15 @@ object MessageContentFuzzer {
|
||||
* - A reaction to a prior message
|
||||
*/
|
||||
fun fuzzMediaMessageNoContent(previousMessages: List<TestMessage> = emptyList()): Content {
|
||||
return Content.Builder()
|
||||
.dataMessage(
|
||||
DataMessage.Builder().buildWith {
|
||||
return Content.newBuilder()
|
||||
.setDataMessage(
|
||||
DataMessage.newBuilder().buildWith {
|
||||
if (random.nextFloat() < 0.25) {
|
||||
val reactTo = previousMessages.random(random)
|
||||
reaction = DataMessage.Reaction.Builder().buildWith {
|
||||
reaction = DataMessage.Reaction.newBuilder().buildWith {
|
||||
emoji = emojis.random(random)
|
||||
remove = false
|
||||
targetAuthorAci = reactTo.metadata.sourceServiceId.toString()
|
||||
targetAuthorUuid = reactTo.metadata.sourceServiceId.toString()
|
||||
targetSentTimestamp = reactTo.envelope.timestamp
|
||||
}
|
||||
}
|
||||
@@ -188,15 +187,15 @@ object MessageContentFuzzer {
|
||||
* - A sticker
|
||||
*/
|
||||
fun fuzzMediaMessageNoText(previousMessages: List<TestMessage> = emptyList()): Content {
|
||||
return Content.Builder()
|
||||
.dataMessage(
|
||||
DataMessage.Builder().buildWith {
|
||||
return Content.newBuilder()
|
||||
.setDataMessage(
|
||||
DataMessage.newBuilder().buildWith {
|
||||
if (random.nextFloat() < 0.9) {
|
||||
sticker = DataMessage.Sticker.Builder().buildWith {
|
||||
sticker = DataMessage.Sticker.newBuilder().buildWith {
|
||||
packId = byteString(length = 24)
|
||||
packKey = byteString(length = 128)
|
||||
stickerId = random.nextInt()
|
||||
data_ = attachmentPointer()
|
||||
data = attachmentPointer()
|
||||
emoji = emojis.random(random)
|
||||
}
|
||||
}
|
||||
@@ -224,14 +223,14 @@ object MessageContentFuzzer {
|
||||
* Generate a random [ByteString].
|
||||
*/
|
||||
fun byteString(length: Int = 512): ByteString {
|
||||
return random.nextBytes(length).toByteString()
|
||||
return random.nextBytes(length).toProtoByteString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random [AttachmentPointer].
|
||||
*/
|
||||
fun attachmentPointer(): AttachmentPointer {
|
||||
return AttachmentPointer.Builder().run {
|
||||
return AttachmentPointer.newBuilder().run {
|
||||
cdnKey = string()
|
||||
contentType = mediaTypes.random(random)
|
||||
key = byteString()
|
||||
|
||||
@@ -1,15 +1,31 @@
|
||||
package org.thoughtcrime.securesms.testing
|
||||
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.stub
|
||||
import org.signal.core.util.Hex
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair
|
||||
import org.signal.libsignal.protocol.ecc.Curve
|
||||
import org.signal.libsignal.protocol.state.PreKeyRecord
|
||||
import org.signal.libsignal.protocol.util.KeyHelper
|
||||
import org.signal.libsignal.protocol.util.Medium
|
||||
import org.signal.libsignal.svr2.PinHash
|
||||
import org.thoughtcrime.securesms.crypto.PreKeyUtil
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.pin.KbsRepository
|
||||
import org.thoughtcrime.securesms.pin.TokenData
|
||||
import org.thoughtcrime.securesms.test.BuildConfig
|
||||
import org.whispersystems.signalservice.api.KbsPinData
|
||||
import org.whispersystems.signalservice.api.KeyBackupService
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse
|
||||
import org.whispersystems.signalservice.internal.push.AuthCredentials
|
||||
import org.whispersystems.signalservice.internal.push.DeviceInfoList
|
||||
import org.whispersystems.signalservice.internal.push.PreKeyEntity
|
||||
@@ -30,8 +46,7 @@ object MockProvider {
|
||||
val senderCertificate = SenderCertificate().apply { certificate = ByteArray(0) }
|
||||
|
||||
val lockedFailure = PushServiceSocket.RegistrationLockFailure().apply {
|
||||
svr1Credentials = AuthCredentials.create("username", "password")
|
||||
svr2Credentials = null
|
||||
backupCredentials = AuthCredentials.create("username", "password")
|
||||
}
|
||||
|
||||
val primaryOnlyDeviceList = DeviceInfoList().apply {
|
||||
@@ -68,6 +83,29 @@ object MockProvider {
|
||||
}
|
||||
}
|
||||
|
||||
fun mockGetRegistrationLockStringFlow(kbsRepository: KbsRepository) {
|
||||
val tokenData: TokenData = mock {
|
||||
on { enclave } doReturn BuildConfig.KBS_ENCLAVE
|
||||
on { basicAuth } doReturn "basicAuth"
|
||||
on { triesRemaining } doReturn 10
|
||||
on { tokenResponse } doReturn TokenResponse()
|
||||
}
|
||||
|
||||
kbsRepository.stub {
|
||||
on { getToken(any() as? String) } doReturn Single.just(ServiceResponse.forResult(tokenData, 200, ""))
|
||||
}
|
||||
|
||||
val session: KeyBackupService.RestoreSession = object : KeyBackupService.RestoreSession {
|
||||
override fun hashSalt(): ByteArray = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8")
|
||||
override fun restorePin(hashedPin: PinHash?): KbsPinData = KbsPinData(MasterKey.createNew(SecureRandom()), null)
|
||||
}
|
||||
|
||||
val kbsService = ApplicationDependencies.getKeyBackupService(BuildConfig.KBS_ENCLAVE)
|
||||
kbsService.stub {
|
||||
on { newRegistrationSession(any(), any()) } doReturn session
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
|
||||
@@ -22,8 +22,6 @@ class Put(path: String, responseFactory: ResponseFactory) : Verb(defaultRequestP
|
||||
|
||||
class Post(path: String, responseFactory: ResponseFactory) : Verb(defaultRequestPredicate("POST", path), responseFactory)
|
||||
|
||||
class Delete(path: String, responseFactory: ResponseFactory) : Verb(defaultRequestPredicate("DELETE", path), responseFactory)
|
||||
|
||||
fun MockResponse.success(response: Any? = null): MockResponse {
|
||||
return setResponseCode(200).apply {
|
||||
if (response != null) {
|
||||
|
||||
@@ -29,14 +29,14 @@ import org.thoughtcrime.securesms.registration.RegistrationData
|
||||
import org.thoughtcrime.securesms.registration.RegistrationRepository
|
||||
import org.thoughtcrime.securesms.registration.RegistrationUtil
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponse
|
||||
import org.thoughtcrime.securesms.testing.GroupTestingUtils.asMember
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.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.lang.IllegalArgumentException
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
@@ -45,7 +45,7 @@ import java.util.UUID
|
||||
*
|
||||
* To use: `@get:Rule val harness = SignalActivityRule()`
|
||||
*/
|
||||
class SignalActivityRule(private val othersCount: Int = 4, private val createGroup: Boolean = false) : ExternalResource() {
|
||||
class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource() {
|
||||
|
||||
val application: Application = ApplicationDependencies.getApplication()
|
||||
|
||||
@@ -57,9 +57,6 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
|
||||
private set
|
||||
lateinit var othersKeys: List<IdentityKeyPair>
|
||||
|
||||
var group: GroupTestingUtils.TestGroupInfo? = null
|
||||
private set
|
||||
|
||||
val inMemoryLogger: InMemoryLogger
|
||||
get() = (application as SignalInstrumentationApplicationContext).inMemoryLogger
|
||||
|
||||
@@ -71,15 +68,6 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
|
||||
others = setupOthers.first
|
||||
othersKeys = setupOthers.second
|
||||
|
||||
if (createGroup && others.size >= 2) {
|
||||
group = GroupTestingUtils.insertGroup(
|
||||
revision = 0,
|
||||
self.asMember(),
|
||||
others[0].asMember(),
|
||||
others[1].asMember()
|
||||
)
|
||||
}
|
||||
|
||||
InstrumentationApplicationDependencyProvider.clearHandlers()
|
||||
}
|
||||
|
||||
@@ -90,9 +78,6 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
|
||||
val preferences: SharedPreferences = application.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0)
|
||||
preferences.edit().putBoolean("passphrase_initialized", true).commit()
|
||||
|
||||
SignalStore.account().generateAciIdentityKeyIfNecessary()
|
||||
SignalStore.account().generatePniIdentityKeyIfNecessary()
|
||||
|
||||
val registrationRepository = RegistrationRepository(application)
|
||||
|
||||
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(Put("/v2/keys") { MockResponse().success() })
|
||||
@@ -107,19 +92,13 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
|
||||
pniRegistrationId = registrationRepository.pniRegistrationId,
|
||||
recoveryPassword = "asdfasdfasdfasdf"
|
||||
),
|
||||
VerifyResponse(
|
||||
verifyAccountResponse = VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false),
|
||||
masterKey = null,
|
||||
pin = null,
|
||||
aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().aciPreKeys),
|
||||
pniPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().pniPreKeys)
|
||||
),
|
||||
VerifyResponse(VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false), null, null),
|
||||
false
|
||||
).blockingGet()
|
||||
|
||||
ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow
|
||||
|
||||
SignalStore.svr().optOut()
|
||||
SignalStore.kbsValues().optOut()
|
||||
RegistrationUtil.maybeMarkRegistrationComplete()
|
||||
SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson"))
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import org.junit.rules.TestWatcher
|
||||
import org.junit.runner.Description
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
import org.whispersystems.signalservice.api.push.ACI
|
||||
import org.whispersystems.signalservice.api.push.PNI
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
@@ -34,7 +34,7 @@ class SignalDatabaseRule(
|
||||
|
||||
private fun deleteAllThreads() {
|
||||
if (deleteAllThreadsOnEachRun) {
|
||||
SignalDatabase.threads.clearForTests()
|
||||
SignalDatabase.messages.deleteAllThreads()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.thoughtcrime.securesms.testing
|
||||
|
||||
import com.google.protobuf.ByteString
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.AddressProto
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.MetadataProto
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
|
||||
import java.util.UUID
|
||||
import kotlin.random.Random
|
||||
|
||||
class TestProtos private constructor() {
|
||||
fun address(
|
||||
uuid: UUID = UUID.randomUUID()
|
||||
): AddressProto.Builder {
|
||||
return AddressProto.newBuilder()
|
||||
.setUuid(ServiceId.from(uuid).toByteString())
|
||||
}
|
||||
|
||||
fun metadata(
|
||||
address: AddressProto = address().build()
|
||||
): MetadataProto.Builder {
|
||||
return MetadataProto.newBuilder()
|
||||
.setAddress(address)
|
||||
}
|
||||
|
||||
fun groupContextV2(
|
||||
revision: Int = 0,
|
||||
masterKeyBytes: ByteArray = Random.Default.nextBytes(GroupMasterKey.SIZE)
|
||||
): GroupContextV2.Builder {
|
||||
return GroupContextV2.newBuilder()
|
||||
.setRevision(revision)
|
||||
.setMasterKey(ByteString.copyFrom(masterKeyBytes))
|
||||
}
|
||||
|
||||
fun storyContext(
|
||||
sentTimestamp: Long = Random.nextLong(),
|
||||
authorUuid: String = UUID.randomUUID().toString()
|
||||
): DataMessage.StoryContext.Builder {
|
||||
return DataMessage.StoryContext.newBuilder()
|
||||
.setAuthorUuid(authorUuid)
|
||||
.setSentTimestamp(sentTimestamp)
|
||||
}
|
||||
|
||||
fun dataMessage(): DataMessage.Builder {
|
||||
return DataMessage.newBuilder()
|
||||
}
|
||||
|
||||
fun content(): SignalServiceProtos.Content.Builder {
|
||||
return SignalServiceProtos.Content.newBuilder()
|
||||
}
|
||||
|
||||
fun serviceContent(
|
||||
localAddress: AddressProto = address().build(),
|
||||
metadata: MetadataProto = metadata().build()
|
||||
): SignalServiceContentProto.Builder {
|
||||
return SignalServiceContentProto.newBuilder()
|
||||
.setLocalAddress(localAddress)
|
||||
.setMetadata(metadata)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun <T> build(buildFn: TestProtos.() -> T): T {
|
||||
return TestProtos().buildFn()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,11 @@
|
||||
package org.thoughtcrime.securesms.testing
|
||||
|
||||
import android.database.Cursor
|
||||
import android.util.Base64
|
||||
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.logging.Log
|
||||
import org.signal.core.util.readToList
|
||||
import org.signal.core.util.select
|
||||
import org.thoughtcrime.securesms.database.MessageTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.util.MessageTableTestUtils
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
@@ -61,29 +53,3 @@ fun CountDownLatch.awaitFor(duration: Duration) {
|
||||
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 dumpTable(table: String): List<List<Pair<String, String?>>> {
|
||||
return SignalDatabase.rawDatabase
|
||||
.select()
|
||||
.from(table)
|
||||
.run()
|
||||
.readToList { cursor ->
|
||||
val map: List<Pair<String, String?>> = cursor.columnNames.map { column ->
|
||||
val index = cursor.getColumnIndex(column)
|
||||
var data: String? = when (cursor.getType(index)) {
|
||||
Cursor.FIELD_TYPE_BLOB -> Base64.encodeToString(cursor.getBlob(index), 0)
|
||||
else -> cursor.getString(index)
|
||||
}
|
||||
if (table == MessageTable.TABLE_NAME && column == MessageTable.TYPE) {
|
||||
data = MessageTableTestUtils.typeColumnToString(cursor.getLong(index))
|
||||
}
|
||||
|
||||
column to data
|
||||
}
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,10 @@ object MessageTableTestUtils {
|
||||
isKeyExchangeType:${type and MessageTypes.KEY_EXCHANGE_BIT != 0L}
|
||||
isIdentityVerified:${type and MessageTypes.KEY_EXCHANGE_IDENTITY_VERIFIED_BIT != 0L}
|
||||
isIdentityDefault:${type and MessageTypes.KEY_EXCHANGE_IDENTITY_DEFAULT_BIT != 0L}
|
||||
isCorruptedKeyExchange:${type and MessageTypes.KEY_EXCHANGE_CORRUPTED_BIT != 0L}
|
||||
isInvalidVersionKeyExchange:${type and MessageTypes.KEY_EXCHANGE_INVALID_VERSION_BIT != 0L}
|
||||
isBundleKeyExchange:${type and MessageTypes.KEY_EXCHANGE_BUNDLE_BIT != 0L}
|
||||
isContentBundleKeyExchange:${type and MessageTypes.KEY_EXCHANGE_CONTENT_FORMAT != 0L}
|
||||
isIdentityUpdate:${type and MessageTypes.KEY_EXCHANGE_IDENTITY_UPDATE_BIT != 0L}
|
||||
isRateLimited:${type and MessageTypes.MESSAGE_RATE_LIMITED_BIT != 0L}
|
||||
isExpirationTimerUpdate:${type and MessageTypes.EXPIRATION_TIMER_UPDATE_BIT != 0L}
|
||||
|
||||
@@ -6,6 +6,8 @@ import org.signal.benchmark.setup.TestMessages
|
||||
import org.signal.benchmark.setup.TestUsers
|
||||
import org.thoughtcrime.securesms.BaseActivity
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
|
||||
import org.thoughtcrime.securesms.mms.QuoteModel
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
||||
class BenchmarkSetupActivity : BaseActivity() {
|
||||
@@ -51,6 +53,13 @@ class BenchmarkSetupActivity : BaseActivity() {
|
||||
TestMessages.insertOutgoingTextMessage(other = recipient, body = "Test message $i", timestamp = generator.nextTimestamp())
|
||||
}
|
||||
|
||||
val voiceMessageId = TestMessages.insertIncomingVoiceMessage(other = recipient, timestamp = generator.nextTimestamp())
|
||||
val mmsRecord = SignalDatabase.messages.getMessageRecord(voiceMessageId) as MediaMmsMessageRecord
|
||||
TestMessages.insertOutgoingImageMessage(other = recipient, body = "test", 2, generator.nextTimestamp())
|
||||
TestMessages.insertIncomingTextMessage(other = recipient, "reply to the test message", generator.nextTimestamp())
|
||||
TestMessages.insertIncomingQuoteTextMessage(other = recipient, quote = QuoteModel(mmsRecord.timestamp, recipient.id, "Fake voice message text", false, mmsRecord.slideDeck.asAttachments(), null, QuoteModel.Type.NORMAL, null), body = "Here is a cool quote", timestamp = generator.nextTimestamp())
|
||||
TestMessages.insertOutgoingTextMessage(other = recipient, body = "longaweorijoaijwerijoiajwer", timestamp = generator.nextTimestamp())
|
||||
|
||||
SignalDatabase.threads.update(SignalDatabase.threads.getOrCreateThreadIdFor(recipient = recipient), true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package org.signal.benchmark
|
||||
|
||||
import android.content.Context
|
||||
import org.signal.libsignal.protocol.IdentityKey
|
||||
import org.signal.libsignal.protocol.state.PreKeyRecord
|
||||
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
|
||||
import org.thoughtcrime.securesms.BuildConfig
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.push.AccountManagerFactory
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager
|
||||
import org.whispersystems.signalservice.api.account.PreKeyUpload
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
import org.whispersystems.signalservice.api.push.ACI
|
||||
import org.whispersystems.signalservice.api.push.PNI
|
||||
import org.whispersystems.signalservice.api.push.ServiceIdType
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
|
||||
import java.io.IOException
|
||||
import java.util.Optional
|
||||
@@ -34,7 +37,7 @@ class DummyAccountManagerFactory : AccountManagerFactory() {
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun setPreKeys(preKeyUpload: PreKeyUpload) {
|
||||
override fun setPreKeys(serviceIdType: ServiceIdType, identityKey: IdentityKey, signedPreKey: SignedPreKeyRecord, oneTimePreKeys: List<PreKeyRecord>) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,9 @@ package org.signal.benchmark.setup
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.PointerAttachment
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
import org.thoughtcrime.securesms.database.MessageType
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.TestDbUtils
|
||||
import org.thoughtcrime.securesms.mms.IncomingMessage
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage
|
||||
import org.thoughtcrime.securesms.mms.QuoteModel
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
@@ -66,8 +65,7 @@ object TestMessages {
|
||||
return insert
|
||||
}
|
||||
fun insertIncomingTextMessage(other: Recipient, body: String, timestamp: Long? = null) {
|
||||
val message = IncomingMessage(
|
||||
type = MessageType.NORMAL,
|
||||
val message = IncomingMediaMessage(
|
||||
from = other.id,
|
||||
body = body,
|
||||
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
|
||||
@@ -75,11 +73,10 @@ object TestMessages {
|
||||
receivedTimeMillis = timestamp ?: System.currentTimeMillis()
|
||||
)
|
||||
|
||||
SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get().messageId
|
||||
SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get().messageId
|
||||
}
|
||||
fun insertIncomingQuoteTextMessage(other: Recipient, body: String, quote: QuoteModel, timestamp: Long?) {
|
||||
val message = IncomingMessage(
|
||||
type = MessageType.NORMAL,
|
||||
val message = IncomingMediaMessage(
|
||||
from = other.id,
|
||||
body = body,
|
||||
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
|
||||
@@ -93,30 +90,28 @@ object TestMessages {
|
||||
val attachments: List<SignalServiceAttachmentPointer> = (0 until attachmentCount).map {
|
||||
imageAttachment()
|
||||
}
|
||||
val message = IncomingMessage(
|
||||
type = MessageType.NORMAL,
|
||||
val message = IncomingMediaMessage(
|
||||
from = other.id,
|
||||
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
|
||||
serverTimeMillis = timestamp ?: System.currentTimeMillis(),
|
||||
receivedTimeMillis = timestamp ?: System.currentTimeMillis(),
|
||||
attachments = PointerAttachment.forPointers(Optional.of(attachments))
|
||||
)
|
||||
return insertIncomingMessage(recipient = other, message = message, failed = failed)
|
||||
return insertIncomingMediaMessage(recipient = other, message = message, failed = failed)
|
||||
}
|
||||
|
||||
fun insertIncomingVoiceMessage(other: Recipient, timestamp: Long? = null): Long {
|
||||
val message = IncomingMessage(
|
||||
type = MessageType.NORMAL,
|
||||
val message = IncomingMediaMessage(
|
||||
from = other.id,
|
||||
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
|
||||
serverTimeMillis = timestamp ?: System.currentTimeMillis(),
|
||||
receivedTimeMillis = timestamp ?: System.currentTimeMillis(),
|
||||
attachments = PointerAttachment.forPointers(Optional.of(Collections.singletonList(voiceAttachment()) as List<SignalServiceAttachment>))
|
||||
)
|
||||
return insertIncomingMessage(recipient = other, message = message, failed = false)
|
||||
return insertIncomingMediaMessage(recipient = other, message = message, failed = false)
|
||||
}
|
||||
|
||||
private fun insertIncomingMessage(recipient: Recipient, message: IncomingMessage, failed: Boolean = false): Long {
|
||||
private fun insertIncomingMediaMessage(recipient: Recipient, message: IncomingMediaMessage, failed: Boolean = false): Long {
|
||||
val id = insertIncomingMessage(recipient = recipient, message = message)
|
||||
if (failed) {
|
||||
setMessageMediaFailed(id)
|
||||
@@ -127,8 +122,8 @@ object TestMessages {
|
||||
return id
|
||||
}
|
||||
|
||||
private fun insertIncomingMessage(recipient: Recipient, message: IncomingMessage): Long {
|
||||
return SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(recipient)).get().messageId
|
||||
private fun insertIncomingMessage(recipient: Recipient, message: IncomingMediaMessage): Long {
|
||||
return SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(recipient)).get().messageId
|
||||
}
|
||||
|
||||
private fun setMessageMediaFailed(messageId: Long) {
|
||||
@@ -153,8 +148,6 @@ object TestMessages {
|
||||
1024,
|
||||
1024,
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
0,
|
||||
Optional.of("/not-there.jpg"),
|
||||
false,
|
||||
false,
|
||||
@@ -176,8 +169,6 @@ object TestMessages {
|
||||
1024,
|
||||
1024,
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
0,
|
||||
Optional.of("/not-there.aac"),
|
||||
true,
|
||||
false,
|
||||
|
||||
@@ -4,7 +4,6 @@ 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
|
||||
@@ -23,7 +22,7 @@ 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.ACI
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse
|
||||
import org.whispersystems.signalservice.internal.ServiceResponseProcessor
|
||||
@@ -44,9 +43,6 @@ object TestUsers {
|
||||
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",
|
||||
@@ -58,26 +54,16 @@ object TestUsers {
|
||||
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)
|
||||
)
|
||||
|
||||
val verifyResponse = VerifyResponse(VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false), null, null)
|
||||
AccountManagerFactory.setInstance(DummyAccountManagerFactory())
|
||||
|
||||
val response: ServiceResponse<VerifyResponse> = registrationRepository.registerAccount(
|
||||
registrationData,
|
||||
verifyResponse,
|
||||
false
|
||||
).safeBlockingGet()
|
||||
|
||||
).blockingGet()
|
||||
ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow
|
||||
|
||||
SignalStore.svr().optOut()
|
||||
SignalStore.kbsValues().optOut()
|
||||
RegistrationUtil.maybeMarkRegistrationComplete()
|
||||
SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson"))
|
||||
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.springboard
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.navGraphViewModels
|
||||
import org.signal.core.ui.Rows
|
||||
import org.signal.core.ui.Scaffolds
|
||||
import org.signal.core.ui.theme.SignalTheme
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
|
||||
/**
|
||||
* Configuration fragment for the internal conversation test fragment.
|
||||
*/
|
||||
class InternalConversationSpringboardFragment : ComposeFragment() {
|
||||
|
||||
private val viewModel: InternalConversationSpringboardViewModel by navGraphViewModels(R.id.app_settings)
|
||||
|
||||
@Composable
|
||||
override fun FragmentContent() {
|
||||
Content(this::navigateBack, this::launchTestFragment, viewModel.hasWallpaper)
|
||||
}
|
||||
|
||||
private fun navigateBack() {
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
|
||||
private fun launchTestFragment() {
|
||||
findNavController().navigate(
|
||||
InternalConversationSpringboardFragmentDirections
|
||||
.actionInternalConversationSpringboardFragmentToInternalConversationTestFragment()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ContentPreview() {
|
||||
val hasWallpaper = remember { mutableStateOf(false) }
|
||||
|
||||
SignalTheme(isDarkMode = true) {
|
||||
Content(onBackPressed = {}, onLaunchTestFragment = {}, hasWallpaper = hasWallpaper)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(
|
||||
onBackPressed: () -> Unit,
|
||||
onLaunchTestFragment: () -> Unit,
|
||||
hasWallpaper: MutableState<Boolean>
|
||||
) {
|
||||
Scaffolds.Settings(
|
||||
title = "Conversation Test Springboard",
|
||||
onNavigationClick = onBackPressed,
|
||||
navigationIconPainter = rememberVectorPainter(ImageVector.vectorResource(id = R.drawable.symbol_arrow_left_24))
|
||||
) {
|
||||
Column(modifier = Modifier.padding(it)) {
|
||||
Rows.TextRow(
|
||||
text = "Launch Conversation Test Fragment",
|
||||
onClick = onLaunchTestFragment
|
||||
)
|
||||
|
||||
Rows.ToggleRow(
|
||||
checked = hasWallpaper.value,
|
||||
text = "Enable Wallpaper",
|
||||
onCheckChanged = { hasWallpaper.value = it }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.springboard
|
||||
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class InternalConversationSpringboardViewModel : ViewModel() {
|
||||
val hasWallpaper = mutableStateOf(false)
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.test
|
||||
|
||||
import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationMessageFactory
|
||||
import org.thoughtcrime.securesms.conversation.v2.data.ConversationElementKey
|
||||
import org.thoughtcrime.securesms.conversation.v2.data.IncomingTextOnly
|
||||
import org.thoughtcrime.securesms.conversation.v2.data.OutgoingTextOnly
|
||||
import org.thoughtcrime.securesms.database.MessageTypes
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
|
||||
import java.security.SecureRandom
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
/**
|
||||
* Generates random conversation messages via the given set of parameters.
|
||||
*/
|
||||
class ConversationElementGenerator {
|
||||
private val mappingModelCache = mutableMapOf<ConversationElementKey, MappingModel<*>>()
|
||||
private val random = SecureRandom()
|
||||
|
||||
private val wordBank = listOf(
|
||||
"A",
|
||||
"Test",
|
||||
"Message",
|
||||
"To",
|
||||
"Display",
|
||||
"Content",
|
||||
"In",
|
||||
"Bubbles",
|
||||
"User",
|
||||
"Signal",
|
||||
"The"
|
||||
)
|
||||
|
||||
fun getMappingModel(key: ConversationElementKey): MappingModel<*> {
|
||||
val cached = mappingModelCache[key]
|
||||
if (cached != null) {
|
||||
return cached
|
||||
}
|
||||
|
||||
val messageModel = generateMessage(key)
|
||||
mappingModelCache[key] = messageModel
|
||||
return messageModel
|
||||
}
|
||||
|
||||
private fun getIncomingType(): Long {
|
||||
return MessageTypes.BASE_INBOX_TYPE or MessageTypes.SECURE_MESSAGE_BIT
|
||||
}
|
||||
|
||||
private fun getSentOutgoingType(): Long {
|
||||
return MessageTypes.BASE_SENT_TYPE or MessageTypes.SECURE_MESSAGE_BIT
|
||||
}
|
||||
|
||||
private fun getSentFailedOutgoingType(): Long {
|
||||
return MessageTypes.BASE_SENT_FAILED_TYPE or MessageTypes.SECURE_MESSAGE_BIT
|
||||
}
|
||||
|
||||
private fun getPendingOutgoingType(): Long {
|
||||
return MessageTypes.BASE_OUTBOX_TYPE or MessageTypes.SECURE_MESSAGE_BIT
|
||||
}
|
||||
|
||||
private fun generateMessage(key: ConversationElementKey): MappingModel<*> {
|
||||
val messageId = key.requireMessageId()
|
||||
val now = getNow()
|
||||
|
||||
val testMessageWordLength = random.nextInt(3) + 1
|
||||
val testMessage = (0 until testMessageWordLength).map {
|
||||
wordBank.random()
|
||||
}.joinToString(" ")
|
||||
|
||||
val isIncoming = random.nextBoolean()
|
||||
|
||||
val record = MediaMmsMessageRecord(
|
||||
messageId,
|
||||
if (isIncoming) Recipient.UNKNOWN else Recipient.self(),
|
||||
0,
|
||||
if (isIncoming) Recipient.self() else Recipient.UNKNOWN,
|
||||
now,
|
||||
now,
|
||||
now,
|
||||
1,
|
||||
1,
|
||||
testMessage,
|
||||
SlideDeck(),
|
||||
if (isIncoming) getIncomingType() else getPendingOutgoingType(),
|
||||
emptySet(),
|
||||
emptySet(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
1,
|
||||
null,
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
false,
|
||||
emptyList(),
|
||||
false,
|
||||
false,
|
||||
now,
|
||||
1,
|
||||
now,
|
||||
null,
|
||||
StoryType.NONE,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
-1,
|
||||
null,
|
||||
null,
|
||||
0
|
||||
)
|
||||
|
||||
val conversationMessage = ConversationMessageFactory.createWithUnresolvedData(
|
||||
ApplicationDependencies.getApplication(),
|
||||
record,
|
||||
Recipient.UNKNOWN
|
||||
)
|
||||
|
||||
return if (isIncoming) {
|
||||
IncomingTextOnly(conversationMessage)
|
||||
} else {
|
||||
OutgoingTextOnly(conversationMessage)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNow(): Long {
|
||||
val now = System.currentTimeMillis()
|
||||
return now - random.nextInt(20.milliseconds.inWholeMilliseconds.toInt()).toLong()
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.test
|
||||
|
||||
import org.signal.paging.PagedDataSource
|
||||
import org.thoughtcrime.securesms.conversation.v2.data.ConversationElementKey
|
||||
import org.thoughtcrime.securesms.conversation.v2.data.ConversationMessageElement
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
|
||||
import kotlin.math.min
|
||||
|
||||
class InternalConversationTestDataSource(
|
||||
private val size: Int,
|
||||
private val generator: ConversationElementGenerator
|
||||
) : PagedDataSource<ConversationElementKey, MappingModel<*>> {
|
||||
override fun size(): Int = size
|
||||
|
||||
override fun load(start: Int, length: Int, totalSize: Int, cancellationSignal: PagedDataSource.CancellationSignal): MutableList<MappingModel<*>> {
|
||||
val end = min(start + length, totalSize)
|
||||
return (start until end).map {
|
||||
load(ConversationElementKey.forMessage(it.toLong()))!!
|
||||
}.toMutableList()
|
||||
}
|
||||
|
||||
override fun getKey(data: MappingModel<*>): ConversationElementKey {
|
||||
check(data is ConversationMessageElement)
|
||||
|
||||
return ConversationElementKey.forMessage(data.conversationMessage.messageRecord.id)
|
||||
}
|
||||
|
||||
override fun load(key: ConversationElementKey?): MappingModel<*>? {
|
||||
return key?.let { generator.getMappingModel(it) }
|
||||
}
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.test
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.navigation.navGraphViewModels
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.ringrtc.CallLinkRootKey
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.ViewBinderDelegate
|
||||
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager
|
||||
import org.thoughtcrime.securesms.components.settings.app.internal.conversation.springboard.InternalConversationSpringboardViewModel
|
||||
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState
|
||||
import org.thoughtcrime.securesms.contactshare.Contact
|
||||
import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener
|
||||
import org.thoughtcrime.securesms.conversation.ConversationItem
|
||||
import org.thoughtcrime.securesms.conversation.ConversationMessage
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette
|
||||
import org.thoughtcrime.securesms.conversation.colors.Colorizer
|
||||
import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationAdapterV2
|
||||
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.databinding.ConversationTestFragmentBinding
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator
|
||||
import org.thoughtcrime.securesms.util.doAfterNextLayout
|
||||
|
||||
class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fragment) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(InternalConversationTestFragment::class.java)
|
||||
}
|
||||
|
||||
private val binding by ViewBinderDelegate(ConversationTestFragmentBinding::bind)
|
||||
private val viewModel: InternalConversationTestViewModel by viewModels()
|
||||
private val lifecycleDisposable = LifecycleDisposable()
|
||||
private val springboardViewModel: InternalConversationSpringboardViewModel by navGraphViewModels(R.id.app_settings)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val adapter = ConversationAdapterV2(
|
||||
lifecycleOwner = viewLifecycleOwner,
|
||||
glideRequests = GlideApp.with(this),
|
||||
clickListener = ClickListener(),
|
||||
hasWallpaper = springboardViewModel.hasWallpaper.value,
|
||||
colorizer = Colorizer(),
|
||||
startExpirationTimeout = {}
|
||||
)
|
||||
|
||||
if (springboardViewModel.hasWallpaper.value) {
|
||||
binding.root.setBackgroundColor(0xFF32C7E2.toInt())
|
||||
}
|
||||
|
||||
var startTime = 0L
|
||||
var firstRender = true
|
||||
lifecycleDisposable.bindTo(viewLifecycleOwner)
|
||||
adapter.setPagingController(viewModel.controller)
|
||||
lifecycleDisposable += viewModel.data.observeOn(AndroidSchedulers.mainThread()).subscribeBy {
|
||||
if (firstRender) {
|
||||
startTime = System.currentTimeMillis()
|
||||
}
|
||||
adapter.submitList(it) {
|
||||
if (firstRender) {
|
||||
firstRender = false
|
||||
binding.root.doAfterNextLayout {
|
||||
val endTime = System.currentTimeMillis()
|
||||
Log.d(TAG, "First render in ${endTime - startTime} millis")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.recycler.layoutManager = SmoothScrollingLinearLayoutManager(requireContext(), true)
|
||||
binding.recycler.adapter = adapter
|
||||
|
||||
RecyclerViewColorizer(binding.recycler).apply {
|
||||
setChatColors(ChatColorsPalette.Bubbles.default.withId(ChatColors.Id.Auto))
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ClickListener : ItemClickListener {
|
||||
override fun onQuoteClicked(messageRecord: MmsMessageRecord?) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onLinkPreviewClicked(linkPreview: LinkPreview) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onQuotedIndicatorClicked(messageRecord: MessageRecord) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onMoreTextClicked(conversationRecipientId: RecipientId, messageId: Long, isMms: Boolean) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onStickerClicked(stickerLocator: StickerLocator) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onViewOnceMessageClicked(messageRecord: MmsMessageRecord) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onSharedContactDetailsClicked(contact: Contact, avatarTransitionView: View) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onAddToContactsClicked(contact: Contact) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onMessageSharedContactClicked(choices: MutableList<Recipient>) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onInviteSharedContactClicked(choices: MutableList<Recipient>) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onReactionClicked(multiselectPart: MultiselectPart, messageId: Long, isMms: Boolean) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onGroupMemberClicked(recipientId: RecipientId, groupId: GroupId) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onMessageWithErrorClicked(messageRecord: MessageRecord) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onMessageWithRecaptchaNeededClicked(messageRecord: MessageRecord) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onIncomingIdentityMismatchClicked(recipientId: RecipientId) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onRegisterVoiceNoteCallbacks(onPlaybackStartObserver: Observer<VoiceNotePlaybackState>) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onUnregisterVoiceNoteCallbacks(onPlaybackStartObserver: Observer<VoiceNotePlaybackState>) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onVoiceNotePause(uri: Uri) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onVoiceNotePlay(uri: Uri, messageId: Long, position: Double) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onVoiceNoteSeekTo(uri: Uri, position: Double) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onVoiceNotePlaybackSpeedChanged(uri: Uri, speed: Float) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onGroupMigrationLearnMoreClicked(membershipChange: GroupMigrationMembershipChange) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onChatSessionRefreshLearnMoreClicked() {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onBadDecryptLearnMoreClicked(author: RecipientId) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onSafetyNumberLearnMoreClicked(recipient: Recipient) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onJoinGroupCallClicked() {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onInviteFriendsToGroupClicked(groupId: GroupId.V2) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onEnableCallNotificationsClicked() {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onPlayInlineContent(conversationMessage: ConversationMessage?) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onInMemoryMessageClicked(messageRecord: InMemoryMessageRecord) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onViewGroupDescriptionChange(groupId: GroupId?, description: String, isMessageRequestAccepted: Boolean) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onChangeNumberUpdateContact(recipient: Recipient) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onCallToAction(action: String) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onDonateClicked() {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onBlockJoinRequest(recipient: Recipient) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onRecipientNameClicked(target: RecipientId) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onInviteToSignalClicked() {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onActivatePaymentsClicked() {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onSendPaymentClicked(recipientId: RecipientId) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onScheduledIndicatorClicked(view: View, conversationMessage: ConversationMessage) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onUrlClicked(url: String): Boolean {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onViewGiftBadgeClicked(messageRecord: MessageRecord) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onGiftBadgeRevealed(messageRecord: MessageRecord) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun goToMediaPreview(parent: ConversationItem?, sharedElement: View?, args: MediaIntentFactory.MediaPreviewArgs?) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onEditedIndicatorClicked(messageRecord: MessageRecord) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onShowGroupDescriptionClicked(groupName: String, description: String, shouldLinkifyWebLinks: Boolean) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onJoinCallLink(callLinkRootKey: CallLinkRootKey) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onItemClick(item: MultiselectPart?) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onItemLongClick(itemView: View?, item: MultiselectPart?) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.test
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import org.signal.paging.PagedData
|
||||
import org.signal.paging.PagingConfig
|
||||
|
||||
class InternalConversationTestViewModel : ViewModel() {
|
||||
private val generator = ConversationElementGenerator()
|
||||
private val dataSource = InternalConversationTestDataSource(
|
||||
500,
|
||||
generator
|
||||
)
|
||||
|
||||
private val config = PagingConfig.Builder().setPageSize(25)
|
||||
.setBufferPages(2)
|
||||
.build()
|
||||
|
||||
private val pagedData = PagedData.createForObservable(dataSource, config)
|
||||
|
||||
val controller = pagedData.controller
|
||||
val data = pagedData.data
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
<uses-feature android:name="android.hardware.wifi" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.portrait" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.telephony" android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||
<uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"/>
|
||||
@@ -44,8 +43,6 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
|
||||
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" android:usesPermissionFlags="neverForLocation" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.READ_CALL_STATE"/>
|
||||
@@ -96,10 +93,6 @@
|
||||
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
|
||||
<application android:name=".ApplicationContext"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
@@ -140,21 +133,18 @@
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
|
||||
android:taskAffinity=".calling"
|
||||
android:resizeableActivity="true"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="false" />
|
||||
android:launchMode="singleTask"/>
|
||||
|
||||
<activity android:name=".messagerequests.CalleeMustAcceptMessageRequestActivity"
|
||||
android:theme="@style/TextSecure.DarkNoActionBar"
|
||||
android:noHistory="true"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false" />
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".InviteActivity"
|
||||
android:theme="@style/Signal.Light.NoActionBar.Invite"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:parentActivityName=".MainActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false">
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".MainActivity" />
|
||||
@@ -163,8 +153,7 @@
|
||||
<activity android:name=".PromptMmsActivity"
|
||||
android:label="Configure MMS Settings"
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".DeviceProvisioningActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
@@ -186,13 +175,11 @@
|
||||
</activity>
|
||||
|
||||
<activity android:name=".preferences.MmsPreferencesActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false" />
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".sharing.interstitial.ShareInterstitialActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="false" />
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
|
||||
<activity android:name=".sharing.v2.ShareActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
@@ -252,7 +239,6 @@
|
||||
|
||||
<activity-alias android:name=".RoutingActivity"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:resizeableActivity="true"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
@@ -616,13 +602,10 @@
|
||||
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="sgnl" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="signal.link" />
|
||||
<data android:scheme="https"
|
||||
android:host="signal.link" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
@@ -630,19 +613,26 @@
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:parentActivityName=".MainActivity"
|
||||
android:resizeableActivity="true"
|
||||
android:exported="false">
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.thoughtcrime.securesms.MainActivity" />
|
||||
</activity>
|
||||
|
||||
<activity android:name=".conversation.ConversationActivity"
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.thoughtcrime.securesms.MainActivity" />
|
||||
</activity>
|
||||
|
||||
<activity android:name=".conversation.BubbleConversationActivity"
|
||||
android:theme="@style/Signal.DayNight"
|
||||
android:allowEmbedded="true"
|
||||
android:resizeableActivity="true"
|
||||
android:exported="false"/>
|
||||
android:theme="@style/Signal.DayNight"
|
||||
android:allowEmbedded="true"
|
||||
android:resizeableActivity="true" />
|
||||
|
||||
<activity android:name=".conversation.ConversationPopupActivity"
|
||||
android:windowSoftInputMode="stateVisible"
|
||||
@@ -650,93 +640,78 @@
|
||||
android:taskAffinity=""
|
||||
android:excludeFromRecents="true"
|
||||
android:theme="@style/TextSecure.LightTheme.Popup"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".groups.ui.invitesandrequests.ManagePendingAndRequestingMembersActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:exported="false"/>
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
|
||||
|
||||
<activity android:name=".recipients.ui.disappearingmessages.RecipientDisappearingMessagesActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="false"/>
|
||||
android:windowSoftInputMode="adjustResize"/>
|
||||
|
||||
<activity android:name=".migrations.ApplicationMigrationActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".PassphraseCreateActivity"
|
||||
<activity android:name=".PassphraseCreateActivity"
|
||||
android:label="@string/AndroidManifest__create_passphrase"
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".PassphrasePromptActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/TextSecure.LightIntroTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".NewConversationActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateAlwaysVisible"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".calls.links.details.CallLinkDetailsActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateAlwaysHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateAlwaysHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".calls.new.NewCallActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateAlwaysVisible"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateAlwaysVisible"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".PushContactSelectionActivity"
|
||||
android:label="@string/AndroidManifest__select_contacts"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:label="@string/AndroidManifest__select_contacts"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".giph.ui.GiphyActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".mediasend.v2.MediaSelectionActivity"
|
||||
android:theme="@style/TextSecure.DarkNoActionBar"
|
||||
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
|
||||
android:launchMode="singleTop"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
||||
android:exported="false"/>
|
||||
<activity android:name=".mediasend.v2.MediaSelectionActivity"
|
||||
android:theme="@style/TextSecure.DarkNoActionBar"
|
||||
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
|
||||
android:launchMode="singleTop"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"/>
|
||||
|
||||
<activity android:name=".conversation.mutiselect.forward.MultiselectForwardActivity"
|
||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
<activity android:name=".conversation.mutiselect.forward.MultiselectForwardActivity"
|
||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".mediasend.v2.stories.StoriesMultiselectForwardActivity"
|
||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
<activity android:name=".mediasend.v2.stories.StoriesMultiselectForwardActivity"
|
||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".PassphraseChangeActivity"
|
||||
android:label="@string/AndroidManifest__change_passphrase"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".verify.VerifyIdentityActivity"
|
||||
android:exported="false"
|
||||
@@ -758,15 +733,13 @@
|
||||
android:name=".stories.my.MyStoriesActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateAlwaysHidden"
|
||||
android:exported="false"/>
|
||||
android:windowSoftInputMode="stateAlwaysHidden" />
|
||||
|
||||
<activity
|
||||
android:name=".stories.settings.StorySettingsActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateAlwaysHidden|adjustResize"
|
||||
android:exported="false"/>
|
||||
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" />
|
||||
|
||||
<activity
|
||||
android:name=".stories.viewer.StoryViewerActivity"
|
||||
@@ -775,99 +748,82 @@
|
||||
android:theme="@style/TextSecure.DarkNoActionBar.StoryViewer"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
|
||||
android:parentActivityName=".MainActivity"
|
||||
android:exported="false">
|
||||
android:parentActivityName=".MainActivity">
|
||||
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.thoughtcrime.securesms.MainActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".components.settings.app.changenumber.ChangeNumberLockActivity"
|
||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
<activity android:name=".components.settings.app.changenumber.ChangeNumberLockActivity"
|
||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity
|
||||
android:name=".components.settings.conversation.ConversationSettingsActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
|
||||
android:theme="@style/Signal.DayNight.ConversationSettings"
|
||||
android:windowSoftInputMode="stateAlwaysHidden"
|
||||
android:exported="false"/>
|
||||
<activity android:name=".components.settings.conversation.ConversationSettingsActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
|
||||
android:theme="@style/Signal.DayNight.ConversationSettings"
|
||||
android:windowSoftInputMode="stateAlwaysHidden">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".components.settings.conversation.CallInfoActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
|
||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateAlwaysHidden"
|
||||
android:exported="false"/>
|
||||
<activity android:name=".components.settings.conversation.CallInfoActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
|
||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateAlwaysHidden">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".badges.gifts.flow.GiftFlowActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
|
||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateAlwaysHidden"
|
||||
android:exported="false"/>
|
||||
<activity android:name=".badges.gifts.flow.GiftFlowActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
|
||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateAlwaysHidden">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".wallpaper.ChatWallpaperActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:windowSoftInputMode="stateAlwaysHidden"
|
||||
android:exported="false"/>
|
||||
<activity android:name=".wallpaper.ChatWallpaperActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:windowSoftInputMode="stateAlwaysHidden">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".wallpaper.ChatWallpaperPreviewActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:windowSoftInputMode="stateAlwaysHidden"
|
||||
android:exported="false"/>
|
||||
<activity android:name=".wallpaper.ChatWallpaperPreviewActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:windowSoftInputMode="stateAlwaysHidden">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".devicetransfer.olddevice.OldDeviceTransferActivity"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
<activity android:name=".devicetransfer.olddevice.OldDeviceTransferActivity"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity
|
||||
android:name=".devicetransfer.olddevice.OldDeviceExitActivity"
|
||||
android:noHistory="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
<activity android:name=".devicetransfer.olddevice.OldDeviceExitActivity"
|
||||
android:noHistory="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".registration.RegistrationNavigationActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".revealable.ViewOnceMessageActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/TextSecure.FullScreenMedia"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:excludeFromRecents="true"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".stickers.StickerManagementActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".DeviceActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:label="@string/AndroidManifest__linked_devices"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".logsubmit.SubmitDebugLogActivity"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".mediapreview.MediaPreviewV2Activity"
|
||||
android:label="@string/AndroidManifest__media_preview"
|
||||
@@ -880,21 +836,19 @@
|
||||
<activity android:name=".AvatarPreviewActivity"
|
||||
android:label="@string/AndroidManifest__media_preview"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".avatar.photo.PhotoEditorActivity"
|
||||
<activity
|
||||
android:name=".avatar.photo.PhotoEditorActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
||||
android:label="@string/AndroidManifest__media_preview"
|
||||
android:theme="@style/TextSecure.DarkNoActionBar"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:exported="false"/>
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
|
||||
<activity android:name=".mediaoverview.MediaOverviewActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".DummyActivity"
|
||||
android:theme="@android:style/Theme.NoDisplay"
|
||||
@@ -905,8 +859,7 @@
|
||||
android:alwaysRetainTaskState="false"
|
||||
android:stateNotNeeded="true"
|
||||
android:clearTaskOnLaunch="true"
|
||||
android:finishOnTaskLaunch="true"
|
||||
android:exported="false"/>
|
||||
android:finishOnTaskLaunch="true" />
|
||||
|
||||
<activity android:name=".PlayServicesProblemActivity"
|
||||
android:exported="false"
|
||||
@@ -949,191 +902,136 @@
|
||||
|
||||
<activity android:name=".mediasend.AvatarSelectionActivity"
|
||||
android:theme="@style/TextSecure.DarkNoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".blocked.BlockedUsersActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".scribbles.ImageEditorStickerSelectActivity"
|
||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".profiles.edit.CreateProfileActivity"
|
||||
<activity android:name=".profiles.edit.EditProfileActivity"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize"
|
||||
android:exported="false"/>
|
||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||
|
||||
<activity android:name=".profiles.username.AddAUsernameActivity"
|
||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize"
|
||||
android:exported="false"/>
|
||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||
|
||||
<activity android:name=".profiles.manage.EditProfileActivity"
|
||||
<activity android:name=".profiles.manage.ManageProfileActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize"
|
||||
android:exported="false"/>
|
||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||
|
||||
<activity
|
||||
android:name=".payments.preferences.PaymentsActivity"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
<activity android:name=".payments.preferences.PaymentsActivity"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity
|
||||
android:name=".lock.v2.CreateSvrPinActivity"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
<activity android:name=".lock.v2.CreateKbsPinActivity"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity
|
||||
android:name=".lock.v2.SvrMigrationActivity"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
<activity android:name=".lock.v2.KbsMigrationActivity"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".contacts.TurnOffContactJoinedNotificationsActivity"
|
||||
android:theme="@style/Theme.AppCompat.Dialog.Alert"
|
||||
android:exported="false"/>
|
||||
android:theme="@style/Theme.AppCompat.Dialog.Alert" />
|
||||
|
||||
<activity android:name=".contactshare.ContactShareEditActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".contactshare.ContactNameEditActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".contactshare.SharedContactDetailsActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".ShortcutLauncherActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:exported="true"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".maps.PlacePickerActivity"
|
||||
android:label="@string/PlacePickerActivity_title"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
<activity
|
||||
android:name=".maps.PlacePickerActivity"
|
||||
android:label="@string/PlacePickerActivity_title"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".MainActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:resizeableActivity="true"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".pin.PinRestoreActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".groups.ui.creategroup.CreateGroupActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:exported="false"/>
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
|
||||
|
||||
<activity android:name=".groups.ui.addtogroup.AddToGroupsActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:exported="false"/>
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
|
||||
|
||||
<activity android:name=".groups.ui.addmembers.AddMembersActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:exported="false"/>
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
|
||||
|
||||
<activity android:name=".groups.ui.creategroup.details.AddGroupDetailsActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:exported="false"/>
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
|
||||
|
||||
<activity android:name=".groups.ui.chooseadmin.ChooseNewAdminActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".megaphone.ClientDeprecatedActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="false"/>
|
||||
android:launchMode="singleTask" />
|
||||
|
||||
<activity android:name=".megaphone.SmsExportMegaphoneActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:screenOrientation="portrait"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="false"/>
|
||||
android:launchMode="singleTask" />
|
||||
|
||||
<activity android:name=".ratelimit.RecaptchaProofActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode" />
|
||||
|
||||
<activity android:name=".wallpaper.crop.WallpaperImageSelectionActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:theme="@style/TextSecure.DarkNoActionBar"
|
||||
android:exported="false"/>
|
||||
android:theme="@style/TextSecure.DarkNoActionBar" />
|
||||
|
||||
<activity android:name=".wallpaper.crop.WallpaperCropActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.Signal.WallpaperCropper"
|
||||
android:exported="false"/>
|
||||
android:theme="@style/Theme.Signal.WallpaperCropper" />
|
||||
|
||||
<activity android:name=".reactions.edit.EditReactionsActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".exporter.flow.SmsExportActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="portrait"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".components.settings.app.subscription.donate.DonateToSignalActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:exported="false"/>
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<service
|
||||
android:enabled="true"
|
||||
android:name=".exporter.SignalSmsExportService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:exported="false"/>
|
||||
|
||||
<service
|
||||
android:enabled="true"
|
||||
android:name=".service.webrtc.WebRtcCallService"
|
||||
android:foregroundServiceType="camera|microphone"
|
||||
android:exported="false"/>
|
||||
|
||||
<service
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:name=".service.KeyCachingService" />
|
||||
|
||||
<service
|
||||
android:enabled="true"
|
||||
android:name=".messages.IncomingMessageObserver$ForegroundService"
|
||||
android:exported="false"/>
|
||||
|
||||
<service
|
||||
android:enabled="true"
|
||||
android:name=".messages.IncomingMessageObserver$BackgroundService"
|
||||
android:exported="false"/>
|
||||
|
||||
<service
|
||||
android:name=".service.webrtc.AndroidCallConnectionService"
|
||||
<service android:enabled="true" android:name=".exporter.SignalSmsExportService" android:foregroundServiceType="dataSync" />
|
||||
<service android:enabled="true" android:name=".service.webrtc.WebRtcCallService" android:foregroundServiceType="camera|microphone"/>
|
||||
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
|
||||
<service android:enabled="true" android:name=".messages.IncomingMessageObserver$ForegroundService"/>
|
||||
<service android:enabled="true" android:name=".messages.IncomingMessageObserver$BackgroundService"/>
|
||||
<service android:name=".service.webrtc.AndroidCallConnectionService"
|
||||
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
@@ -1141,13 +1039,9 @@
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".components.voice.VoiceNotePlaybackService"
|
||||
android:foregroundServiceType="mediaPlayback"
|
||||
android:exported="true">
|
||||
<service android:name=".components.voice.VoiceNotePlaybackService" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.media.browse.MediaBrowserService" />
|
||||
<action android:name="androidx.media3.session.MediaSessionService"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
@@ -1185,21 +1079,11 @@
|
||||
<meta-data android:name="android.provider.CONTACTS_STRUCTURE" android:resource="@xml/contactsformat" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".service.GenericForegroundService"
|
||||
android:exported="false"/>
|
||||
<service android:name=".service.GenericForegroundService"/>
|
||||
|
||||
<service
|
||||
android:name=".service.AttachmentProgressService"
|
||||
android:exported="false"/>
|
||||
<service android:name=".gcm.FcmFetchBackgroundService" />
|
||||
|
||||
<service
|
||||
android:name=".gcm.FcmFetchBackgroundService"
|
||||
android:exported="false"/>
|
||||
|
||||
<service
|
||||
android:name=".gcm.FcmFetchForegroundService"
|
||||
android:exported="false"/>
|
||||
<service android:name=".gcm.FcmFetchForegroundService" />
|
||||
|
||||
<service android:name=".gcm.FcmReceiveService" android:exported="true">
|
||||
<intent-filter>
|
||||
@@ -1207,6 +1091,18 @@
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver android:name=".service.SmsListener"
|
||||
android:permission="android.permission.BROADCAST_SMS"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter android:priority="1001">
|
||||
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.provider.Telephony.SMS_DELIVER"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".service.SmsDeliveryListener"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
@@ -1214,6 +1110,20 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".service.MmsListener"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BROADCAST_WAP_PUSH">
|
||||
<intent-filter android:priority="1001">
|
||||
<action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED"/>
|
||||
<data android:mimeType="application/vnd.wap.mms-message" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
|
||||
<data android:mimeType="application/vnd.wap.mms-message" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".notifications.MarkReadReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
@@ -1230,38 +1140,19 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".service.ExpirationListener"
|
||||
android:exported="false"/>
|
||||
<receiver android:name=".service.ExpirationListener" />
|
||||
|
||||
<receiver
|
||||
android:name=".service.ExpiringStoriesManager$ExpireStoriesAlarm"
|
||||
android:exported="false"/>
|
||||
<receiver android:name=".service.ExpiringStoriesManager$ExpireStoriesAlarm" />
|
||||
|
||||
<receiver
|
||||
android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm"
|
||||
android:exported="false"/>
|
||||
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
|
||||
|
||||
<receiver
|
||||
android:name=".service.ScheduledMessageManager$ScheduledMessagesAlarm"
|
||||
android:exported="false"/>
|
||||
<receiver android:name=".service.ScheduledMessageManager$ScheduledMessagesAlarm" />
|
||||
|
||||
<receiver
|
||||
android:name=".service.PendingRetryReceiptManager$PendingRetryReceiptAlarm"
|
||||
android:exported="false"/>
|
||||
<receiver android:name=".service.PendingRetryReceiptManager$PendingRetryReceiptAlarm" />
|
||||
|
||||
<receiver
|
||||
android:name=".service.TrimThreadsByDateManager$TrimThreadsByDateAlarm"
|
||||
android:exported="false"/>
|
||||
<receiver android:name=".service.TrimThreadsByDateManager$TrimThreadsByDateAlarm" />
|
||||
|
||||
<receiver
|
||||
android:name=".payments.backup.phrase.ClearClipboardAlarmReceiver"
|
||||
android:exported="false"/>
|
||||
|
||||
<provider android:name=".providers.AvatarProvider"
|
||||
android:authorities="${applicationId}.avatar"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true" />
|
||||
<receiver android:name=".payments.backup.phrase.ClearClipboardAlarmReceiver" />
|
||||
|
||||
<provider android:name=".providers.PartProvider"
|
||||
android:grantUriPermissions="true"
|
||||
@@ -1312,7 +1203,7 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".messageprocessingalarm.RoutineMessageFetchReceiver" android:exported="false">
|
||||
<receiver android:name=".messageprocessingalarm.MessageProcessReceiver" android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="org.thoughtcrime.securesms.action.PROCESS_MESSAGES" />
|
||||
@@ -1359,14 +1250,12 @@
|
||||
android:name=".gcm.FcmJobService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:enabled="@bool/enable_job_service"
|
||||
android:exported="false"
|
||||
tools:targetApi="26" />
|
||||
|
||||
<service
|
||||
android:name=".jobmanager.JobSchedulerScheduler$SystemService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:enabled="@bool/enable_job_service"
|
||||
android:exported="false"
|
||||
tools:targetApi="26" />
|
||||
|
||||
<service
|
||||
|
||||
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 166 KiB |
BIN
app/src/main/assets/emoji/Objects.webp
Normal file
|
After Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 174 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 91 KiB |
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package androidx.recyclerview.widget
|
||||
|
||||
import android.content.Context
|
||||
import org.signal.core.util.logging.Log
|
||||
|
||||
/**
|
||||
* Variation of a vertical, reversed [LinearLayoutManager] that makes specific assumptions in how it will
|
||||
* be used by Conversation view to support easier scrolling to the initial start position.
|
||||
*
|
||||
* Primarily, it assumes that an initial scroll to position call will always happen and that the implementation
|
||||
* of [LinearLayoutManager] remains unchanged with respect to how it assigns [mPendingScrollPosition] and
|
||||
* [mPendingScrollPositionOffset] in [LinearLayoutManager.scrollToPositionWithOffset] and how it always clears
|
||||
* the pending state variables in every call to [LinearLayoutManager.onLayoutCompleted].
|
||||
*
|
||||
* The assumptions are necessary to force the requested scroll position/layout to occur even if the request
|
||||
* happens prior to the data source populating the recycler view/adapter.
|
||||
*/
|
||||
class ConversationLayoutManager(context: Context) : LinearLayoutManager(context, RecyclerView.VERTICAL, true) {
|
||||
|
||||
private var afterScroll: (() -> Unit)? = null
|
||||
|
||||
override fun supportsPredictiveItemAnimations(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to the desired position and be notified when the layout manager has completed the request
|
||||
* via [afterScroll] callback.
|
||||
*/
|
||||
fun scrollToPositionWithOffset(position: Int, offset: Int, afterScroll: () -> Unit) {
|
||||
this.afterScroll = afterScroll
|
||||
super.scrollToPositionWithOffset(position, offset)
|
||||
}
|
||||
|
||||
/**
|
||||
* If a scroll to position request is made and a layout pass occurs prior to the list being populated with via the data source,
|
||||
* the base implementation clears the request as if it was never made.
|
||||
*
|
||||
* This override will capture the pending scroll position and offset, determine if the scroll request was satisfied, and
|
||||
* re-request the scroll to position to force another attempt if not satisfied.
|
||||
*
|
||||
* A pending scroll request will be re-requested if the pending scroll position is outside the bounds of the current known size of
|
||||
* items in the list.
|
||||
*/
|
||||
override fun onLayoutCompleted(state: RecyclerView.State?) {
|
||||
val pendingScrollPosition = mPendingScrollPosition
|
||||
val pendingScrollOffset = mPendingScrollPositionOffset
|
||||
|
||||
val reRequestPendingPosition = pendingScrollPosition >= (state?.mItemCount ?: 0)
|
||||
|
||||
// Base implementation always clears mPendingScrollPosition+mPendingScrollPositionOffset
|
||||
super.onLayoutCompleted(state)
|
||||
|
||||
// Re-request scroll to position request if necessary thus forcing mPendingScrollPosition+mPendingScrollPositionOffset to be re-assigned
|
||||
if (reRequestPendingPosition) {
|
||||
Log.d(TAG, "Re-requesting pending scroll position: $pendingScrollPosition offset: $pendingScrollOffset")
|
||||
if (pendingScrollOffset != INVALID_OFFSET) {
|
||||
scrollToPositionWithOffset(pendingScrollPosition, pendingScrollOffset)
|
||||
} else {
|
||||
scrollToPosition(pendingScrollPosition)
|
||||
}
|
||||
} else {
|
||||
afterScroll?.invoke()
|
||||
afterScroll = null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ConversationLayoutManager::class.java)
|
||||
}
|
||||
}
|
||||
@@ -1,800 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.conscrypt;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Provider;
|
||||
import java.security.cert.X509Certificate;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLContextSpi;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLEngineResult;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLServerSocketFactory;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSessionContext;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
/**
|
||||
* Core API for creating and configuring all Conscrypt types.
|
||||
* This is identical to the original Conscrypt.java, except with the slow
|
||||
* version initialization code removed.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public final class ConscryptSignal {
|
||||
private ConscryptSignal() {}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the Conscrypt native library has been successfully loaded.
|
||||
*/
|
||||
public static boolean isAvailable() {
|
||||
try {
|
||||
checkAvailability();
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// BEGIN MODIFICATION
|
||||
/*public static class Version {
|
||||
private final int major;
|
||||
private final int minor;
|
||||
private final int patch;
|
||||
|
||||
private Version(int major, int minor, int patch) {
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
this.patch = patch;
|
||||
}
|
||||
|
||||
public int major() { return major; }
|
||||
public int minor() { return minor; }
|
||||
public int patch() { return patch; }
|
||||
}
|
||||
|
||||
private static final Version VERSION;
|
||||
|
||||
static {
|
||||
int major = -1;
|
||||
int minor = -1;
|
||||
int patch = -1;
|
||||
InputStream stream = null;
|
||||
try {
|
||||
stream = Conscrypt.class.getResourceAsStream("conscrypt.properties");
|
||||
if (stream != null) {
|
||||
Properties props = new Properties();
|
||||
props.load(stream);
|
||||
major = Integer.parseInt(props.getProperty("org.conscrypt.version.major", "-1"));
|
||||
minor = Integer.parseInt(props.getProperty("org.conscrypt.version.minor", "-1"));
|
||||
patch = Integer.parseInt(props.getProperty("org.conscrypt.version.patch", "-1"));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// TODO(prb): This should probably be fatal or have some fallback behaviour
|
||||
} finally {
|
||||
IoUtils.closeQuietly(stream);
|
||||
}
|
||||
if ((major >= 0) && (minor >= 0) && (patch >= 0)) {
|
||||
VERSION = new Version(major, minor, patch);
|
||||
} else {
|
||||
VERSION = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of this distribution of Conscrypt. If version information is
|
||||
* unavailable, returns {@code null}.
|
||||
*/
|
||||
/*public static Version version() {
|
||||
return VERSION;
|
||||
}*/
|
||||
|
||||
// END MODIFICATION
|
||||
|
||||
/**
|
||||
* Checks that the Conscrypt support is available for the system.
|
||||
*
|
||||
* @throws UnsatisfiedLinkError if unavailable
|
||||
*/
|
||||
public static void checkAvailability() {
|
||||
NativeCrypto.checkAvailability();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the given {@link Provider} was created by this distribution of Conscrypt.
|
||||
*/
|
||||
public static boolean isConscrypt(Provider provider) {
|
||||
return provider instanceof OpenSSLProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Provider} with the default name.
|
||||
*/
|
||||
public static Provider newProvider() {
|
||||
checkAvailability();
|
||||
return new OpenSSLProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Provider} with the given name.
|
||||
*
|
||||
* @deprecated Use {@link #newProviderBuilder()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Provider newProvider(String providerName) {
|
||||
checkAvailability();
|
||||
return newProviderBuilder().setName(providerName).build();
|
||||
}
|
||||
|
||||
public static class ProviderBuilder {
|
||||
private String name = Platform.getDefaultProviderName();
|
||||
private boolean provideTrustManager = Platform.provideTrustManagerByDefault();
|
||||
private String defaultTlsProtocol = NativeCrypto.SUPPORTED_PROTOCOL_TLSV1_3;
|
||||
|
||||
private ProviderBuilder() {}
|
||||
|
||||
/**
|
||||
* Sets the name of the Provider to be built.
|
||||
*/
|
||||
public ProviderBuilder setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes the returned provider to provide an implementation of
|
||||
* {@link javax.net.ssl.TrustManagerFactory}.
|
||||
* @deprecated Use provideTrustManager(true)
|
||||
*/
|
||||
@Deprecated
|
||||
public ProviderBuilder provideTrustManager() {
|
||||
return provideTrustManager(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies whether the returned provider will provide an implementation of
|
||||
* {@link javax.net.ssl.TrustManagerFactory}.
|
||||
*/
|
||||
public ProviderBuilder provideTrustManager(boolean provide) {
|
||||
this.provideTrustManager = provide;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies what the default TLS protocol should be for SSLContext identifiers
|
||||
* {@code TLS}, {@code SSL}, and {@code Default}.
|
||||
*/
|
||||
public ProviderBuilder defaultTlsProtocol(String defaultTlsProtocol) {
|
||||
this.defaultTlsProtocol = defaultTlsProtocol;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Provider build() {
|
||||
return new OpenSSLProvider(name, provideTrustManager, defaultTlsProtocol);
|
||||
}
|
||||
}
|
||||
|
||||
public static ProviderBuilder newProviderBuilder() {
|
||||
return new ProviderBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum length (in bytes) of an encrypted packet.
|
||||
*/
|
||||
public static int maxEncryptedPacketLength() {
|
||||
return NativeConstants.SSL3_RT_MAX_PACKET_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default X.509 trust manager.
|
||||
*/
|
||||
@ExperimentalApi
|
||||
public static X509TrustManager getDefaultX509TrustManager() throws KeyManagementException {
|
||||
checkAvailability();
|
||||
return SSLParametersImpl.getDefaultX509TrustManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the given {@link SSLContext} was created by this distribution of Conscrypt.
|
||||
*/
|
||||
public static boolean isConscrypt(SSLContext context) {
|
||||
return context.getProvider() instanceof OpenSSLProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of the preferred {@link SSLContextSpi}.
|
||||
*/
|
||||
public static SSLContextSpi newPreferredSSLContextSpi() {
|
||||
checkAvailability();
|
||||
return OpenSSLContextImpl.getPreferred();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client-side persistent cache to be used by the context.
|
||||
*/
|
||||
public static void setClientSessionCache(SSLContext context, SSLClientSessionCache cache) {
|
||||
SSLSessionContext clientContext = context.getClientSessionContext();
|
||||
if (!(clientContext instanceof ClientSessionContext)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Not a conscrypt client context: " + clientContext.getClass().getName());
|
||||
}
|
||||
((ClientSessionContext) clientContext).setPersistentCache(cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the server-side persistent cache to be used by the context.
|
||||
*/
|
||||
public static void setServerSessionCache(SSLContext context, SSLServerSessionCache cache) {
|
||||
SSLSessionContext serverContext = context.getServerSessionContext();
|
||||
if (!(serverContext instanceof ServerSessionContext)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Not a conscrypt client context: " + serverContext.getClass().getName());
|
||||
}
|
||||
((ServerSessionContext) serverContext).setPersistentCache(cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the given {@link SSLSocketFactory} was created by this distribution of
|
||||
* Conscrypt.
|
||||
*/
|
||||
public static boolean isConscrypt(SSLSocketFactory factory) {
|
||||
return factory instanceof OpenSSLSocketFactoryImpl;
|
||||
}
|
||||
|
||||
private static OpenSSLSocketFactoryImpl toConscrypt(SSLSocketFactory factory) {
|
||||
if (!isConscrypt(factory)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Not a conscrypt socket factory: " + factory.getClass().getName());
|
||||
}
|
||||
return (OpenSSLSocketFactoryImpl) factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the default socket to be created for all socket factory instances.
|
||||
*/
|
||||
@ExperimentalApi
|
||||
public static void setUseEngineSocketByDefault(boolean useEngineSocket) {
|
||||
OpenSSLSocketFactoryImpl.setUseEngineSocketByDefault(useEngineSocket);
|
||||
OpenSSLServerSocketFactoryImpl.setUseEngineSocketByDefault(useEngineSocket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the socket to be created for the given socket factory instance.
|
||||
*/
|
||||
@ExperimentalApi
|
||||
public static void setUseEngineSocket(SSLSocketFactory factory, boolean useEngineSocket) {
|
||||
toConscrypt(factory).setUseEngineSocket(useEngineSocket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the given {@link SSLServerSocketFactory} was created by this distribution
|
||||
* of Conscrypt.
|
||||
*/
|
||||
public static boolean isConscrypt(SSLServerSocketFactory factory) {
|
||||
return factory instanceof OpenSSLServerSocketFactoryImpl;
|
||||
}
|
||||
|
||||
private static OpenSSLServerSocketFactoryImpl toConscrypt(SSLServerSocketFactory factory) {
|
||||
if (!isConscrypt(factory)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Not a conscrypt server socket factory: " + factory.getClass().getName());
|
||||
}
|
||||
return (OpenSSLServerSocketFactoryImpl) factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the socket to be created for the given server socket factory instance.
|
||||
*/
|
||||
@ExperimentalApi
|
||||
public static void setUseEngineSocket(SSLServerSocketFactory factory, boolean useEngineSocket) {
|
||||
toConscrypt(factory).setUseEngineSocket(useEngineSocket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the given {@link SSLSocket} was created by this distribution of Conscrypt.
|
||||
*/
|
||||
public static boolean isConscrypt(SSLSocket socket) {
|
||||
return socket instanceof AbstractConscryptSocket;
|
||||
}
|
||||
|
||||
private static AbstractConscryptSocket toConscrypt(SSLSocket socket) {
|
||||
if (!isConscrypt(socket)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Not a conscrypt socket: " + socket.getClass().getName());
|
||||
}
|
||||
return (AbstractConscryptSocket) socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method enables Server Name Indication (SNI) and overrides the hostname supplied
|
||||
* during socket creation. If the hostname is not a valid SNI hostname, the SNI extension
|
||||
* will be omitted from the handshake.
|
||||
*
|
||||
* @param socket the socket
|
||||
* @param hostname the desired SNI hostname, or null to disable
|
||||
*/
|
||||
public static void setHostname(SSLSocket socket, String hostname) {
|
||||
toConscrypt(socket).setHostname(hostname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns either the hostname supplied during socket creation or via
|
||||
* {@link #setHostname(SSLSocket, String)}. No DNS resolution is attempted before
|
||||
* returning the hostname.
|
||||
*/
|
||||
public static String getHostname(SSLSocket socket) {
|
||||
return toConscrypt(socket).getHostname();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method attempts to create a textual representation of the peer host or IP. Does
|
||||
* not perform a reverse DNS lookup. This is typically used during session creation.
|
||||
*/
|
||||
public static String getHostnameOrIP(SSLSocket socket) {
|
||||
return toConscrypt(socket).getHostnameOrIP();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method enables session ticket support.
|
||||
*
|
||||
* @param socket the socket
|
||||
* @param useSessionTickets True to enable session tickets
|
||||
*/
|
||||
public static void setUseSessionTickets(SSLSocket socket, boolean useSessionTickets) {
|
||||
toConscrypt(socket).setUseSessionTickets(useSessionTickets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables/disables TLS Channel ID for the given server-side socket.
|
||||
*
|
||||
* <p>This method needs to be invoked before the handshake starts.
|
||||
*
|
||||
* @param socket the socket
|
||||
* @param enabled Whether to enable channel ID.
|
||||
* @throws IllegalStateException if this is a client socket or if the handshake has already
|
||||
* started.
|
||||
*/
|
||||
public static void setChannelIdEnabled(SSLSocket socket, boolean enabled) {
|
||||
toConscrypt(socket).setChannelIdEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the TLS Channel ID for the given server-side socket. Channel ID is only available
|
||||
* once the handshake completes.
|
||||
*
|
||||
* @param socket the socket
|
||||
* @return channel ID or {@code null} if not available.
|
||||
* @throws IllegalStateException if this is a client socket or if the handshake has not yet
|
||||
* completed.
|
||||
* @throws SSLException if channel ID is available but could not be obtained.
|
||||
*/
|
||||
public static byte[] getChannelId(SSLSocket socket) throws SSLException {
|
||||
return toConscrypt(socket).getChannelId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link PrivateKey} to be used for TLS Channel ID by this client socket.
|
||||
*
|
||||
* <p>This method needs to be invoked before the handshake starts.
|
||||
*
|
||||
* @param socket the socket
|
||||
* @param privateKey private key (enables TLS Channel ID) or {@code null} for no key
|
||||
* (disables TLS Channel ID).
|
||||
* The private key must be an Elliptic Curve (EC) key based on the NIST P-256 curve (aka
|
||||
* SECG secp256r1 or ANSI
|
||||
* X9.62 prime256v1).
|
||||
* @throws IllegalStateException if this is a server socket or if the handshake has already
|
||||
* started.
|
||||
*/
|
||||
public static void setChannelIdPrivateKey(SSLSocket socket, PrivateKey privateKey) {
|
||||
toConscrypt(socket).setChannelIdPrivateKey(privateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ALPN protocol agreed upon by client and server.
|
||||
*
|
||||
* @param socket the socket
|
||||
* @return the selected protocol or {@code null} if no protocol was agreed upon.
|
||||
*/
|
||||
public static String getApplicationProtocol(SSLSocket socket) {
|
||||
return toConscrypt(socket).getApplicationProtocol();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an application-provided ALPN protocol selector. If provided, this will override
|
||||
* the list of protocols set by {@link #setApplicationProtocols(SSLSocket, String[])}.
|
||||
*
|
||||
* @param socket the socket
|
||||
* @param selector the ALPN protocol selector
|
||||
*/
|
||||
public static void setApplicationProtocolSelector(SSLSocket socket,
|
||||
ApplicationProtocolSelector selector) {
|
||||
toConscrypt(socket).setApplicationProtocolSelector(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the application-layer protocols (ALPN) in prioritization order.
|
||||
*
|
||||
* @param socket the socket being configured
|
||||
* @param protocols the protocols in descending order of preference. If empty, no protocol
|
||||
* indications will be used. This array will be copied.
|
||||
* @throws IllegalArgumentException - if protocols is null, or if any element in a non-empty
|
||||
* array is null or an empty (zero-length) string
|
||||
*/
|
||||
public static void setApplicationProtocols(SSLSocket socket, String[] protocols) {
|
||||
toConscrypt(socket).setApplicationProtocols(protocols);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the application-layer protocols (ALPN) in prioritization order.
|
||||
*
|
||||
* @param socket the socket
|
||||
* @return the protocols in descending order of preference, or an empty array if protocol
|
||||
* indications are not being used. Always returns a new array.
|
||||
*/
|
||||
public static String[] getApplicationProtocols(SSLSocket socket) {
|
||||
return toConscrypt(socket).getApplicationProtocols();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tls-unique channel binding value for this connection, per RFC 5929. This
|
||||
* will return {@code null} if there is no such value available, such as if the handshake
|
||||
* has not yet completed or this connection is closed.
|
||||
*/
|
||||
public static byte[] getTlsUnique(SSLSocket socket) {
|
||||
return toConscrypt(socket).getTlsUnique();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a value derived from the TLS master secret as described in RFC 5705.
|
||||
*
|
||||
* @param label the label to use in calculating the exported value. This must be
|
||||
* an ASCII-only string.
|
||||
* @param context the application-specific context value to use in calculating the
|
||||
* exported value. This may be {@code null} to use no application context, which is
|
||||
* treated differently than an empty byte array.
|
||||
* @param length the number of bytes of keying material to return.
|
||||
* @return a value of the specified length, or {@code null} if the handshake has not yet
|
||||
* completed or the connection has been closed.
|
||||
* @throws SSLException if the value could not be exported.
|
||||
*/
|
||||
public static byte[] exportKeyingMaterial(SSLSocket socket, String label, byte[] context,
|
||||
int length) throws SSLException {
|
||||
return toConscrypt(socket).exportKeyingMaterial(label, context, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the given {@link SSLEngine} was created by this distribution of Conscrypt.
|
||||
*/
|
||||
public static boolean isConscrypt(SSLEngine engine) {
|
||||
return engine instanceof AbstractConscryptEngine;
|
||||
}
|
||||
|
||||
private static AbstractConscryptEngine toConscrypt(SSLEngine engine) {
|
||||
if (!isConscrypt(engine)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Not a conscrypt engine: " + engine.getClass().getName());
|
||||
}
|
||||
return (AbstractConscryptEngine) engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the given engine with the provided bufferAllocator.
|
||||
* @throws IllegalArgumentException if the provided engine is not a Conscrypt engine.
|
||||
* @throws IllegalStateException if the provided engine has already begun its handshake.
|
||||
*/
|
||||
@ExperimentalApi
|
||||
public static void setBufferAllocator(SSLEngine engine, BufferAllocator bufferAllocator) {
|
||||
toConscrypt(engine).setBufferAllocator(bufferAllocator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the given socket with the provided bufferAllocator. If the given socket is a
|
||||
* Conscrypt socket but does not use buffer allocators, this method does nothing.
|
||||
* @throws IllegalArgumentException if the provided socket is not a Conscrypt socket.
|
||||
* @throws IllegalStateException if the provided socket has already begun its handshake.
|
||||
*/
|
||||
@ExperimentalApi
|
||||
public static void setBufferAllocator(SSLSocket socket, BufferAllocator bufferAllocator) {
|
||||
AbstractConscryptSocket s = toConscrypt(socket);
|
||||
if (s instanceof ConscryptEngineSocket) {
|
||||
((ConscryptEngineSocket) s).setBufferAllocator(bufferAllocator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the default {@link BufferAllocator} to be used by all future
|
||||
* {@link SSLEngine} instances from this provider.
|
||||
*/
|
||||
@ExperimentalApi
|
||||
public static void setDefaultBufferAllocator(BufferAllocator bufferAllocator) {
|
||||
ConscryptEngine.setDefaultBufferAllocator(bufferAllocator);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method enables Server Name Indication (SNI) and overrides the hostname supplied
|
||||
* during engine creation.
|
||||
*
|
||||
* @param engine the engine
|
||||
* @param hostname the desired SNI hostname, or {@code null} to disable
|
||||
*/
|
||||
public static void setHostname(SSLEngine engine, String hostname) {
|
||||
toConscrypt(engine).setHostname(hostname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns either the hostname supplied during socket creation or via
|
||||
* {@link #setHostname(SSLEngine, String)}. No DNS resolution is attempted before
|
||||
* returning the hostname.
|
||||
*/
|
||||
public static String getHostname(SSLEngine engine) {
|
||||
return toConscrypt(engine).getHostname();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum overhead, in bytes, of sealing a record with SSL.
|
||||
*/
|
||||
public static int maxSealOverhead(SSLEngine engine) {
|
||||
return toConscrypt(engine).maxSealOverhead();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a listener on the given engine for completion of the TLS handshake
|
||||
*/
|
||||
public static void setHandshakeListener(SSLEngine engine, HandshakeListener handshakeListener) {
|
||||
toConscrypt(engine).setHandshakeListener(handshakeListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables/disables TLS Channel ID for the given server-side engine.
|
||||
*
|
||||
* <p>This method needs to be invoked before the handshake starts.
|
||||
*
|
||||
* @param engine the engine
|
||||
* @param enabled Whether to enable channel ID.
|
||||
* @throws IllegalStateException if this is a client engine or if the handshake has already
|
||||
* started.
|
||||
*/
|
||||
public static void setChannelIdEnabled(SSLEngine engine, boolean enabled) {
|
||||
toConscrypt(engine).setChannelIdEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the TLS Channel ID for the given server-side engine. Channel ID is only available
|
||||
* once the handshake completes.
|
||||
*
|
||||
* @param engine the engine
|
||||
* @return channel ID or {@code null} if not available.
|
||||
* @throws IllegalStateException if this is a client engine or if the handshake has not yet
|
||||
* completed.
|
||||
* @throws SSLException if channel ID is available but could not be obtained.
|
||||
*/
|
||||
public static byte[] getChannelId(SSLEngine engine) throws SSLException {
|
||||
return toConscrypt(engine).getChannelId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link PrivateKey} to be used for TLS Channel ID by this client engine.
|
||||
*
|
||||
* <p>This method needs to be invoked before the handshake starts.
|
||||
*
|
||||
* @param engine the engine
|
||||
* @param privateKey private key (enables TLS Channel ID) or {@code null} for no key
|
||||
* (disables TLS Channel ID).
|
||||
* The private key must be an Elliptic Curve (EC) key based on the NIST P-256 curve (aka
|
||||
* SECG secp256r1 or ANSI X9.62 prime256v1).
|
||||
* @throws IllegalStateException if this is a server engine or if the handshake has already
|
||||
* started.
|
||||
*/
|
||||
public static void setChannelIdPrivateKey(SSLEngine engine, PrivateKey privateKey) {
|
||||
toConscrypt(engine).setChannelIdPrivateKey(privateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended unwrap method for multiple source and destination buffers.
|
||||
*
|
||||
* @param engine the target engine for the unwrap
|
||||
* @param srcs the source buffers
|
||||
* @param dsts the destination buffers
|
||||
* @return the result of the unwrap operation
|
||||
* @throws SSLException thrown if an SSL error occurred
|
||||
*/
|
||||
public static SSLEngineResult unwrap(SSLEngine engine, final ByteBuffer[] srcs,
|
||||
final ByteBuffer[] dsts) throws SSLException {
|
||||
return toConscrypt(engine).unwrap(srcs, dsts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exteneded unwrap method for multiple source and destination buffers.
|
||||
*
|
||||
* @param engine the target engine for the unwrap.
|
||||
* @param srcs the source buffers
|
||||
* @param srcsOffset the offset in the {@code srcs} array of the first source buffer
|
||||
* @param srcsLength the number of source buffers starting at {@code srcsOffset}
|
||||
* @param dsts the destination buffers
|
||||
* @param dstsOffset the offset in the {@code dsts} array of the first destination buffer
|
||||
* @param dstsLength the number of destination buffers starting at {@code dstsOffset}
|
||||
* @return the result of the unwrap operation
|
||||
* @throws SSLException thrown if an SSL error occurred
|
||||
*/
|
||||
public static SSLEngineResult unwrap(SSLEngine engine, final ByteBuffer[] srcs, int srcsOffset,
|
||||
final int srcsLength, final ByteBuffer[] dsts, final int dstsOffset,
|
||||
final int dstsLength) throws SSLException {
|
||||
return toConscrypt(engine).unwrap(
|
||||
srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method enables session ticket support.
|
||||
*
|
||||
* @param engine the engine
|
||||
* @param useSessionTickets True to enable session tickets
|
||||
*/
|
||||
public static void setUseSessionTickets(SSLEngine engine, boolean useSessionTickets) {
|
||||
toConscrypt(engine).setUseSessionTickets(useSessionTickets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the application-layer protocols (ALPN) in prioritization order.
|
||||
*
|
||||
* @param engine the engine being configured
|
||||
* @param protocols the protocols in descending order of preference. If empty, no protocol
|
||||
* indications will be used. This array will be copied.
|
||||
* @throws IllegalArgumentException - if protocols is null, or if any element in a non-empty
|
||||
* array is null or an empty (zero-length) string
|
||||
*/
|
||||
public static void setApplicationProtocols(SSLEngine engine, String[] protocols) {
|
||||
toConscrypt(engine).setApplicationProtocols(protocols);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the application-layer protocols (ALPN) in prioritization order.
|
||||
*
|
||||
* @param engine the engine
|
||||
* @return the protocols in descending order of preference, or an empty array if protocol
|
||||
* indications are not being used. Always returns a new array.
|
||||
*/
|
||||
public static String[] getApplicationProtocols(SSLEngine engine) {
|
||||
return toConscrypt(engine).getApplicationProtocols();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an application-provided ALPN protocol selector. If provided, this will override
|
||||
* the list of protocols set by {@link #setApplicationProtocols(SSLEngine, String[])}.
|
||||
*
|
||||
* @param engine the engine
|
||||
* @param selector the ALPN protocol selector
|
||||
*/
|
||||
public static void setApplicationProtocolSelector(SSLEngine engine,
|
||||
ApplicationProtocolSelector selector) {
|
||||
toConscrypt(engine).setApplicationProtocolSelector(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ALPN protocol agreed upon by client and server.
|
||||
*
|
||||
* @param engine the engine
|
||||
* @return the selected protocol or {@code null} if no protocol was agreed upon.
|
||||
*/
|
||||
public static String getApplicationProtocol(SSLEngine engine) {
|
||||
return toConscrypt(engine).getApplicationProtocol();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tls-unique channel binding value for this connection, per RFC 5929. This
|
||||
* will return {@code null} if there is no such value available, such as if the handshake
|
||||
* has not yet completed or this connection is closed.
|
||||
*/
|
||||
public static byte[] getTlsUnique(SSLEngine engine) {
|
||||
return toConscrypt(engine).getTlsUnique();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a value derived from the TLS master secret as described in RFC 5705.
|
||||
*
|
||||
* @param label the label to use in calculating the exported value. This must be
|
||||
* an ASCII-only string.
|
||||
* @param context the application-specific context value to use in calculating the
|
||||
* exported value. This may be {@code null} to use no application context, which is
|
||||
* treated differently than an empty byte array.
|
||||
* @param length the number of bytes of keying material to return.
|
||||
* @return a value of the specified length, or {@code null} if the handshake has not yet
|
||||
* completed or the connection has been closed.
|
||||
* @throws SSLException if the value could not be exported.
|
||||
*/
|
||||
public static byte[] exportKeyingMaterial(SSLEngine engine, String label, byte[] context,
|
||||
int length) throws SSLException {
|
||||
return toConscrypt(engine).exportKeyingMaterial(label, context, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the given {@link TrustManager} was created by this distribution of
|
||||
* Conscrypt.
|
||||
*/
|
||||
public static boolean isConscrypt(TrustManager trustManager) {
|
||||
return trustManager instanceof TrustManagerImpl;
|
||||
}
|
||||
|
||||
private static TrustManagerImpl toConscrypt(TrustManager trustManager) {
|
||||
if (!isConscrypt(trustManager)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Not a Conscrypt trust manager: " + trustManager.getClass().getName());
|
||||
}
|
||||
return (TrustManagerImpl) trustManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default hostname verifier that will be used for HTTPS endpoint identification by
|
||||
* Conscrypt trust managers. If {@code null} (the default), endpoint identification will use
|
||||
* the default hostname verifier set in
|
||||
* {@link HttpsURLConnection#setDefaultHostnameVerifier(javax.net.ssl.HostnameVerifier)}.
|
||||
*/
|
||||
public synchronized static void setDefaultHostnameVerifier(ConscryptHostnameVerifier verifier) {
|
||||
TrustManagerImpl.setDefaultHostnameVerifier(verifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently-set default hostname verifier for Conscrypt trust managers.
|
||||
*
|
||||
* @see #setDefaultHostnameVerifier(ConscryptHostnameVerifier)
|
||||
*/
|
||||
public synchronized static ConscryptHostnameVerifier getDefaultHostnameVerifier(TrustManager trustManager) {
|
||||
return TrustManagerImpl.getDefaultHostnameVerifier();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the hostname verifier that will be used for HTTPS endpoint identification by the
|
||||
* given trust manager. If {@code null} (the default), endpoint identification will use the
|
||||
* default hostname verifier set in {@link #setDefaultHostnameVerifier(ConscryptHostnameVerifier)}.
|
||||
*
|
||||
* @throws IllegalArgumentException if the provided trust manager is not a Conscrypt trust
|
||||
* manager per {@link #isConscrypt(TrustManager)}
|
||||
*/
|
||||
public static void setHostnameVerifier(TrustManager trustManager, ConscryptHostnameVerifier verifier) {
|
||||
toConscrypt(trustManager).setHostnameVerifier(verifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently-set hostname verifier for the given trust manager.
|
||||
*
|
||||
* @throws IllegalArgumentException if the provided trust manager is not a Conscrypt trust
|
||||
* manager per {@link #isConscrypt(TrustManager)}
|
||||
*
|
||||
* @see #setHostnameVerifier(TrustManager, ConscryptHostnameVerifier)
|
||||
*/
|
||||
public static ConscryptHostnameVerifier getHostnameVerifier(TrustManager trustManager) {
|
||||
return toConscrypt(trustManager).getHostnameVerifier();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the HttpsURLConnection.HostnameVerifier into a ConscryptHostnameVerifier
|
||||
*/
|
||||
public static ConscryptHostnameVerifier wrapHostnameVerifier(final HostnameVerifier verifier) {
|
||||
return new ConscryptHostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(X509Certificate[] certificates, String hostname, SSLSession session) {
|
||||
return verifier.verify(hostname, session);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.signal.glide.transforms
|
||||
|
||||
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
object SignalDownsampleStrategy {
|
||||
/**
|
||||
* Center outside, but don't up-scale, only downscale. You should be setting centerOutside
|
||||
* on the target image view to still maintain center outside behavior.
|
||||
*/
|
||||
@JvmField
|
||||
val CENTER_OUTSIDE_NO_UPSCALE: DownsampleStrategy = CenterOutsideNoUpscale()
|
||||
|
||||
private class CenterOutsideNoUpscale : DownsampleStrategy() {
|
||||
override fun getScaleFactor(
|
||||
sourceWidth: Int,
|
||||
sourceHeight: Int,
|
||||
requestedWidth: Int,
|
||||
requestedHeight: Int
|
||||
): Float {
|
||||
val widthPercentage = requestedWidth / sourceWidth.toFloat()
|
||||
val heightPercentage = requestedHeight / sourceHeight.toFloat()
|
||||
return min(MAX_SCALE_FACTOR, max(widthPercentage, heightPercentage))
|
||||
}
|
||||
|
||||
override fun getSampleSizeRounding(
|
||||
sourceWidth: Int,
|
||||
sourceHeight: Int,
|
||||
requestedWidth: Int,
|
||||
requestedHeight: Int
|
||||
): SampleSizeRounding {
|
||||
return SampleSizeRounding.QUALITY
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MAX_SCALE_FACTOR = 1f
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.insights.InsightsOptOut;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob;
|
||||
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
|
||||
@@ -29,6 +30,7 @@ public final class AppInitialization {
|
||||
public static void onFirstEverAppLaunch(@NonNull Context context) {
|
||||
Log.i(TAG, "onFirstEverAppLaunch()");
|
||||
|
||||
InsightsOptOut.userRequestedOptOut(context);
|
||||
TextSecurePreferences.setAppMigrationVersion(context, ApplicationMigrations.CURRENT_VERSION);
|
||||
TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION);
|
||||
TextSecurePreferences.setLastVersionCode(context, Util.getCanonicalVersionCode());
|
||||
@@ -69,6 +71,7 @@ public final class AppInitialization {
|
||||
public static void onRepairFirstEverAppLaunch(@NonNull Context context) {
|
||||
Log.w(TAG, "onRepairFirstEverAppLaunch()");
|
||||
|
||||
InsightsOptOut.userRequestedOptOut(context);
|
||||
TextSecurePreferences.setAppMigrationVersion(context, ApplicationMigrations.CURRENT_VERSION);
|
||||
TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION);
|
||||
TextSecurePreferences.setLastVersionCode(context, Util.getCanonicalVersionCode());
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -26,11 +27,9 @@ import androidx.multidex.MultiDexApplication;
|
||||
|
||||
import com.google.android.gms.security.ProviderInstaller;
|
||||
|
||||
import org.conscrypt.ConscryptSignal;
|
||||
import org.conscrypt.Conscrypt;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.signal.aesgcmprovider.AesGcmProvider;
|
||||
import org.signal.core.util.MemoryTracker;
|
||||
import org.signal.core.util.concurrent.AnrDetector;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.AndroidLogger;
|
||||
import org.signal.core.util.logging.Log;
|
||||
@@ -48,12 +47,11 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider;
|
||||
import org.thoughtcrime.securesms.emoji.EmojiSource;
|
||||
import org.thoughtcrime.securesms.emoji.JumboEmoji;
|
||||
import org.thoughtcrime.securesms.gcm.FcmFetchManager;
|
||||
import org.thoughtcrime.securesms.gcm.FcmJobService;
|
||||
import org.thoughtcrime.securesms.jobs.AccountConsistencyWorkerJob;
|
||||
import org.thoughtcrime.securesms.jobs.CheckServiceReachabilityJob;
|
||||
import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob;
|
||||
import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob;
|
||||
import org.thoughtcrime.securesms.jobs.ExternalLaunchDonationJob;
|
||||
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.FontDownloaderJob;
|
||||
import org.thoughtcrime.securesms.jobs.GroupV2UpdateSelfProfileKeyJob;
|
||||
@@ -61,7 +59,8 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.PnpInitializeDevicesJob;
|
||||
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob;
|
||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshSvrCredentialsJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshKbsCredentialsJob;
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveRemoteAnnouncementsJob;
|
||||
import org.thoughtcrime.securesms.jobs.StoryOnboardingDownloadJob;
|
||||
@@ -70,7 +69,7 @@ import org.thoughtcrime.securesms.keyvalue.KeepMessagesDuration;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
||||
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
||||
import org.thoughtcrime.securesms.messageprocessingalarm.RoutineMessageFetchReceiver;
|
||||
import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver;
|
||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.mms.SignalGlideComponents;
|
||||
@@ -85,7 +84,7 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.service.LocalBackupListener;
|
||||
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
|
||||
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
||||
import org.thoughtcrime.securesms.apkupdate.ApkUpdateRefreshListener;
|
||||
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
||||
import org.thoughtcrime.securesms.service.webrtc.AndroidTelecomUtil;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.util.AppForegroundObserver;
|
||||
@@ -110,7 +109,6 @@ import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
|
||||
import io.reactivex.rxjava3.exceptions.UndeliverableException;
|
||||
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import kotlin.Unit;
|
||||
import rxdogtag2.RxDogTag;
|
||||
|
||||
/**
|
||||
@@ -125,6 +123,9 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
|
||||
private static final String TAG = Log.tag(ApplicationContext.class);
|
||||
|
||||
@VisibleForTesting
|
||||
protected PersistentLogger persistentLogger;
|
||||
|
||||
public static ApplicationContext getInstance(Context context) {
|
||||
return (ApplicationContext)context.getApplicationContext();
|
||||
}
|
||||
@@ -153,7 +154,6 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
initializeLogging();
|
||||
Log.i(TAG, "onCreate()");
|
||||
})
|
||||
.addBlocking("anr-detector", this::startAnrDetector)
|
||||
.addBlocking("security-provider", this::initializeSecurityProvider)
|
||||
.addBlocking("crash-handling", this::initializeCrashHandling)
|
||||
.addBlocking("rx-init", this::initializeRx)
|
||||
@@ -168,7 +168,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
.addBlocking("proxy-init", () -> {
|
||||
if (SignalStore.proxy().isProxyEnabled()) {
|
||||
Log.w(TAG, "Proxy detected. Enabling Conscrypt.setUseEngineSocketByDefault()");
|
||||
ConscryptSignal.setUseEngineSocketByDefault(true);
|
||||
Conscrypt.setUseEngineSocketByDefault(true);
|
||||
}
|
||||
})
|
||||
.addBlocking("blob-provider", this::initializeBlobProvider)
|
||||
@@ -184,6 +184,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
.addNonBlocking(PreKeysSyncJob::enqueueIfNeeded)
|
||||
.addNonBlocking(this::initializePeriodicTasks)
|
||||
.addNonBlocking(this::initializeCircumvention)
|
||||
.addNonBlocking(this::initializePendingMessages)
|
||||
.addNonBlocking(this::initializeCleanup)
|
||||
.addNonBlocking(this::initializeGlideCodecs)
|
||||
.addNonBlocking(StorageSyncHelper::scheduleRoutineSync)
|
||||
@@ -197,7 +198,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
.addPostRender(this::initializeExpiringMessageManager)
|
||||
.addPostRender(() -> SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this)))
|
||||
.addPostRender(this::initializeTrimThreadsByDateManager)
|
||||
.addPostRender(RefreshSvrCredentialsJob::enqueueIfNecessary)
|
||||
.addPostRender(RefreshKbsCredentialsJob::enqueueIfNecessary)
|
||||
.addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this))
|
||||
.addPostRender(EmojiSearchIndexDownloadJob::scheduleIfNecessary)
|
||||
.addPostRender(() -> SignalDatabase.messageLog().trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge()))
|
||||
@@ -228,9 +229,6 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
ApplicationDependencies.getMegaphoneRepository().onAppForegrounded();
|
||||
ApplicationDependencies.getDeadlockDetector().start();
|
||||
SubscriptionKeepAliveJob.enqueueAndTrackTimeIfNecessary();
|
||||
ExternalLaunchDonationJob.enqueueIfNecessary();
|
||||
FcmFetchManager.onForeground(this);
|
||||
startAnrDetector();
|
||||
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
FeatureFlags.refreshIfNecessary();
|
||||
@@ -239,7 +237,6 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
KeyCachingService.onAppForegrounded(this);
|
||||
ApplicationDependencies.getShakeToReport().enable();
|
||||
checkBuildExpiration();
|
||||
MemoryTracker.start();
|
||||
|
||||
long lastForegroundTime = SignalStore.misc().getLastForegroundTime();
|
||||
long currentTime = System.currentTimeMillis();
|
||||
@@ -263,8 +260,10 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
ApplicationDependencies.getFrameRateTracker().stop();
|
||||
ApplicationDependencies.getShakeToReport().disable();
|
||||
ApplicationDependencies.getDeadlockDetector().stop();
|
||||
MemoryTracker.stop();
|
||||
AnrDetector.stop();
|
||||
}
|
||||
|
||||
public PersistentLogger getPersistentLogger() {
|
||||
return persistentLogger;
|
||||
}
|
||||
|
||||
public void checkBuildExpiration() {
|
||||
@@ -274,17 +273,6 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: this is purposefully "started" twice -- once during application create, and once during foreground.
|
||||
* This is so we can capture ANR's that happen on boot before the foreground event.
|
||||
*/
|
||||
private void startAnrDetector() {
|
||||
AnrDetector.start(TimeUnit.SECONDS.toMillis(5), FeatureFlags::internalUser, (dumps) -> {
|
||||
LogDatabase.getInstance(this).anrs().save(System.currentTimeMillis(), dumps);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeSecurityProvider() {
|
||||
int aesPosition = Security.insertProviderAt(new AesGcmProvider(), 1);
|
||||
Log.i(TAG, "Installed AesGcmProvider: " + aesPosition);
|
||||
@@ -294,7 +282,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
throw new ProviderInitializationException();
|
||||
}
|
||||
|
||||
int conscryptPosition = Security.insertProviderAt(ConscryptSignal.newProvider(), 2);
|
||||
int conscryptPosition = Security.insertProviderAt(Conscrypt.newProvider(), 2);
|
||||
Log.i(TAG, "Installed Conscrypt provider: " + conscryptPosition);
|
||||
|
||||
if (conscryptPosition < 0) {
|
||||
@@ -304,14 +292,14 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
|
||||
@VisibleForTesting
|
||||
protected void initializeLogging() {
|
||||
Log.initialize(FeatureFlags::internalUser, new AndroidLogger(), new PersistentLogger(this));
|
||||
persistentLogger = new PersistentLogger(this);
|
||||
org.signal.core.util.logging.Log.initialize(FeatureFlags::internalUser, new AndroidLogger(), persistentLogger);
|
||||
|
||||
SignalProtocolLoggerProvider.setProvider(new CustomSignalProtocolLogger());
|
||||
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
Log.blockUntilAllWritesFinished();
|
||||
LogDatabase.getInstance(this).logs().trimToSize();
|
||||
LogDatabase.getInstance(this).crashes().trimToSize();
|
||||
LogDatabase.getInstance(this).trimToSize();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -413,10 +401,10 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
DirectoryRefreshListener.schedule(this);
|
||||
LocalBackupListener.schedule(this);
|
||||
RotateSenderCertificateListener.schedule(this);
|
||||
RoutineMessageFetchReceiver.startOrUpdateAlarm(this);
|
||||
MessageProcessReceiver.startOrUpdateAlarm(this);
|
||||
|
||||
if (BuildConfig.MANAGES_APP_UPDATES) {
|
||||
ApkUpdateRefreshListener.schedule(this);
|
||||
if (BuildConfig.PLAY_STORE_DISABLED) {
|
||||
UpdateApkRefreshListener.schedule(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,6 +444,18 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
}
|
||||
}
|
||||
|
||||
private void initializePendingMessages() {
|
||||
if (TextSecurePreferences.getNeedsMessagePull(this)) {
|
||||
Log.i(TAG, "Scheduling a message fetch.");
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
FcmJobService.schedule(this);
|
||||
} else {
|
||||
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob());
|
||||
}
|
||||
TextSecurePreferences.setNeedsMessagePull(this, false);
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void initializeBlobProvider() {
|
||||
BlobProvider.getInstance().initialize(this);
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.transition.TransitionInflater;
|
||||
import android.view.View;
|
||||
|
||||