new baseline - 1

This commit is contained in:
TELEMESSAGE\Shilo 2024-01-23 11:32:42 +02:00
parent 4a84ae1954
commit 1c47e8ba52
2688 changed files with 217611 additions and 59138 deletions

View file

@ -18,6 +18,8 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: set up JDK 17
uses: actions/setup-java@v3

View file

@ -15,6 +15,7 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
submodules: true
ref: ${{ github.event.pull_request.base.sha }}
- name: set up JDK 17
@ -45,6 +46,7 @@ jobs:
- uses: actions/checkout@v3
with:
submodules: true
clean: 'false'
- name: Build with Gradle

3
.gitignore vendored
View file

@ -3,6 +3,7 @@ captures/
project.properties
keystore.debug.properties
keystore.staging.properties
nightly-url.txt
.project
.settings
bin/
@ -28,4 +29,4 @@ jni/libspeex/.deps/
pkcs11.password
dev.keystore
maps.key
local/
local/

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "libwebp"]
path = libwebp
url = https://github.com/webmproject/libwebp.git

View file

@ -15,6 +15,12 @@ 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

View file

@ -59,45 +59,3 @@ Copyright 2013-2023 Signal
Licensed under the GNU AGPLv3: https://www.gnu.org/licenses/agpl-3.0.html
Google Play and the Google Play logo are trademarks of Google LLC.
//**TM_SA**//
Signal New Base-line
1. Download the Signal official open source from this link:
https://github.com/signalapp/Signal-Android
2. Rename each folder at the next order:
a. thoughtcrime  tm
b. securesms  archive
3. Replace all old package mentions vie “replace all” function (Ctrl +Shift + R)
a. org.thoughtcrime.securesms -> org.tm.archive
4. Add our archiver SDK and Common library to new folder “libs” and compile them via dependencies.
5. Add Archiver folder with all archiving class with util etc. (Take them from src->main->java->org)
6. Search “ArchiveLogger.Companion.sendArchiveLog” in the current project and add all those mentions to the updated project.
7. Add launcher icon app and change the round icon path in the manifest
8. Add proguard-event_bus from the current project to the updated one.
9. Go to the current TeleMessage Signal project and search via ctrl+alt+F : //**TM_SA**//
There are dozens of references to this string please move on the result one by one and replace or add the code with this string ( //**TM_SA**//, in order to create continuation to baseline updating method)
intune
1. add dependencies using //**TM_SA**//
2. add MAMSDK folder with aar and jar
3. 1. register the app to intune server
2. https://aad.portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/Overview3. Azure Active Directory > App registrations > New Registration
3. Authentication, add platform -> add uri -> package name.
4. then add auth-config file using the View button that show it. put it in resource-> raw
5. API permissions...
4. 1. https://aad.portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/RegisteredApps
2. then, App configuration policies -> create app configuration policy -> manage apps -> Settings -> add the values(managerID etc)
3. Then, Assignments -> include groups you want or assign everyone.
5. http://everythingaboutintune.com/2021/07/guide-for-integrating-intune-sdk-and-msal-to-lob-application/
https://www.youtube.com/watch?v=1AyGpcdDRkY&t=741s&ab_channel=EverythingAboutIntune
https://github.com/msintuneappsdk/Taskr-Sample-Intune-Android-App#readme

View file

@ -1,761 +0,0 @@
import com.android.build.api.dsl.ManagedVirtualDevice
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt' //**TM_SA**
// id 'com.microsoft.intune.mam' //**TM_SA**
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 'translations'
}
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
}
sourcePath {
srcDir 'src/main/protowire'
}
protoPath {
srcDir "${project.rootDir}/libsignal/service/src/main/protowire"
}
}
ktlint {
version = "0.49.1"
}
//**TM_SA**//Start - Change the version code and version name upon the current version
def canonicalVersionCode = 1336
def canonicalVersionName = "6.31.3.6"
def signal_teleMessage_version = "6.31.3.6"//Change this param in Jenkins builder and delete it.
//**TM_SA**//end
def postFixSize = 100
def abiPostFix = ['universal' : 0,
'armeabi-v7a' : 1,
'arm64-v8a' : 2,
'x86' : 3,
'x86_64' : 4]
def keystores = [ 'debug' : loadKeystoreProperties('keystore.debug.properties') ]
def selectableVariants = [
'nightlyProdSpinner',
'nightlyProdPerf',
'nightlyProdRelease',
'nightlyStagingRelease',
'nightlyPnpPerf',
'nightlyPnpRelease',
'playProdDebug',
'playProdSpinner',
'playProdCanary',
'playProdPerf',
'playProdBenchmark',
'playProdInstrumentation',
'playProdRelease',
'playStagingDebug',
'playStagingCanary',
'playStagingSpinner',
'playStagingPerf',
'playStagingInstrumentation',
'playPnpDebug',
'playPnpSpinner',
'playStagingRelease',
'websiteProdSpinner',
'websiteProdRelease',
]
android {
namespace 'org.tm.archive'
buildToolsVersion = signalBuildToolsVersion
compileSdkVersion = signalCompileSdkVersion
flavorDimensions 'distribution', 'environment'
useLibrary 'org.apache.http.legacy'
testBuildType 'instrumentation'
kotlinOptions {
jvmTarget = signalKotlinJvmTarget
freeCompilerArgs = ["-Xallow-result-return-type"]
}
signingConfigs {
if (keystores.debug != null) {
debug {
storeFile file("${project.rootDir}/${keystores.debug.storeFile}")
storePassword keystores.debug.storePassword
keyAlias keystores.debug.keyAlias
keyPassword keystores.debug.keyPassword
}
}
//**TM_SA**//Start
release {
keyAlias 'telemessage brand'
keyPassword 'Granit08'
storeFile file('../../KeyStore/TelemessageBrand/telemessagebrand.keystore')
storePassword 'Granit08'
}
//**TM_SA**//End
}
testOptions {
execution 'ANDROIDX_TEST_ORCHESTRATOR'
unitTests {
includeAndroidResources = true
}
managedDevices {
devices {
pixel3api30 (ManagedVirtualDevice) {
device = "Pixel 3"
apiLevel = 30
systemImageSource = "google-atd"
require64Bit = false
}
}
}
}
sourceSets {
test {
java.srcDirs += "$projectDir/src/testShared"
}
androidTest {
java.srcDirs += "$projectDir/src/testShared"
}
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility signalJavaVersion
targetCompatibility signalJavaVersion
}
packagingOptions {
resources {
excludes += ['LICENSE.txt', 'LICENSE', 'NOTICE', 'asm-license.txt', 'META-INF/LICENSE', 'META-INF/LICENSE.md', 'META-INF/NOTICE', 'META-INF/LICENSE-notice.md', 'META-INF/proguard/androidx-annotations.pro', 'libsignal_jni.dylib', 'signal_jni.dll']
}
}
buildFeatures {
viewBinding true
compose true
}
composeOptions {
kotlinCompilerExtensionVersion = '1.4.4'
}
defaultConfig {
versionCode canonicalVersionCode * postFixSize
versionName canonicalVersionName
minSdkVersion signalMinSdkVersion
targetSdkVersion signalTargetSdkVersion
multiDexEnabled true
vectorDrawables.useSupportLibrary = true
project.ext.set("archivesBaseName", "Signal")
//**TM_SA**// change key
manifestPlaceholders = [mapsKey:"AIzaSyAVa3EZMZWSbiUAfgiJTb-7Ljo0se6YWys"]
buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L"
buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\""
buildConfigField "String", "SIGNAL_URL", "\"https://chat.signal.org\""
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_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_SFU_URL", "\"https://sfu.voip.signal.org\""
buildConfigField "String", "SIGNAL_STAGING_SFU_URL", "\"https://sfu.staging.voip.signal.org\""
buildConfigField "String[]", "SIGNAL_SFU_INTERNAL_NAMES", "new String[]{\"Test\", \"Staging\", \"Development\"}"
buildConfigField "String[]", "SIGNAL_SFU_INTERNAL_URLS", "new String[]{\"https://sfu.test.voip.signal.org\", \"https://sfu.staging.voip.signal.org\", \"https://sfu.staging.test.voip.signal.org\"}"
buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\""
buildConfigField "int", "CONTENT_PROXY_PORT", "443"
buildConfigField "String[]", "SIGNAL_SERVICE_IPS", service_ips
buildConfigField "String[]", "SIGNAL_STORAGE_IPS", storage_ips
buildConfigField "String[]", "SIGNAL_CDN_IPS", cdn_ips
buildConfigField "String[]", "SIGNAL_CDN2_IPS", cdn2_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 "org.tm.archive.KbsEnclave", "KBS_ENCLAVE", "new org.tm.archive.KbsEnclave(\"e18376436159cda3ad7a45d9320e382e4a497f26b0dca34d8eab0bd0139483b5\", " +
"\"3a485adb56e2058ef7737764c738c4069dd62bc457637eafb6bbce1ce29ddb89\", " +
"\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
buildConfigField "org.tm.archive.KbsEnclave[]", "KBS_FALLBACKS", "new org.tm.archive.KbsEnclave[] { new org.tm.archive.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\""
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
//**TM_SA**//Start
buildConfigField "String", "signal_teleMessage_version", "\"$signal_teleMessage_version\""
//**TM_SA**//End
buildConfigField "String", "DEFAULT_CURRENCIES", "\"EUR,AUD,GBP,CAD,CNY\""
buildConfigField "String", "GIPHY_API_KEY", "\"3o6ZsYH6U6Eri53TXy\""
buildConfigField "String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/registration/generate.html\""
buildConfigField "String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/challenge/generate.html\""
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"unset\""
buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"unset\""
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"unset\""
buildConfigField "String", "BADGE_STATIC_ROOT", "\"https://updates2.signal.org/static/badges/\""
buildConfigField "String", "STRIPE_PUBLISHABLE_KEY", "\"pk_live_6cmGZopuTsV8novGgJJW9JpC00vLIgtQ1D\""
buildConfigField "boolean", "TRACING_ENABLED", "false"
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
resourceConfigurations += []
splits {
abi {
enable !project.hasProperty('generateBaselineProfile')
reset()
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
universalApk true
}
}
testInstrumentationRunner "org.tm.archive.testing.SignalTestRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
}
buildTypes {
debug {
if (keystores['debug'] != null) {
signingConfig signingConfigs.debug
}
isDefault true
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard/proguard-firebase-messaging.pro',
'proguard/proguard-google-play-services.pro',
'proguard/proguard-jackson.pro',
'proguard/proguard-sqlite.pro',
'proguard/proguard-appcompat-v7.pro',
'proguard/proguard-square-okhttp.pro',
'proguard/proguard-square-okio.pro',
'proguard/proguard-rounded-image-view.pro',
'proguard/proguard-glide.pro',
'proguard/proguard-shortcutbadger.pro',
'proguard/proguard-retrofit.pro',
'proguard/proguard-webrtc.pro',
'proguard/proguard-klinker.pro',
'proguard/proguard-mobilecoin.pro',
'proguard/proguard-retrolambda.pro',
'proguard/proguard-okhttp.pro',
'proguard/proguard-ez-vcard.pro',
'proguard/proguard.cfg'
testProguardFiles 'proguard/proguard-automation.pro',
'proguard/proguard.cfg'
'proguard/proguard-event_bus.pro' //**TM_SA**// ++++++++ADD THIS FILE TO THE PROGUARD FOLDER++++++++
manifestPlaceholders = [mapsKey:getMapsKey()]
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Debug\""
}
instrumentation {
initWith debug
isDefault false
minifyEnabled false
matchingFallbacks = ['debug']
applicationIdSuffix ".instrumentation"
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Instrumentation\""
}
spinner {
initWith debug
isDefault false
minifyEnabled false
matchingFallbacks = ['debug']
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Spinner\""
}
release {
minifyEnabled true
proguardFiles = buildTypes.debug.proguardFiles
//**TM_SA**//Start
signingConfig signingConfigs.release
//**TM_SA**//End
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Release\""
}
perf {
initWith debug
isDefault false
debuggable false
minifyEnabled true
matchingFallbacks = ['debug']
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Perf\""
buildConfigField "boolean", "TRACING_ENABLED", "true"
}
benchmark {
initWith debug
isDefault false
debuggable false
minifyEnabled true
matchingFallbacks = ['debug']
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Benchmark\""
buildConfigField "boolean", "TRACING_ENABLED", "true"
}
canary {
initWith debug
isDefault false
minifyEnabled false
matchingFallbacks = ['debug']
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Canary\""
}
}
productFlavors {
play {
dimension 'distribution'
isDefault true
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'
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 {
dimension 'distribution'
versionNameSuffix "-nightly-untagged-${getDateSuffix()}"
ext.websiteUpdateUrl = "null"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"nightly\""
}
prod {
dimension 'environment'
// applicationIdSuffix ".prodshilo" //**TM_SA**//to comment it. it's for intune debugs
isDefault true
buildConfigField "String", "MOBILE_COIN_ENVIRONMENT", "\"mainnet\""
buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"Prod\""
}
staging {
dimension 'environment'
applicationIdSuffix ".staging"
buildConfigField "String", "SIGNAL_URL", "\"https://chat.staging.signal.org\""
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_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.tm.archive.KbsEnclave", "KBS_ENCLAVE", "new org.tm.archive.KbsEnclave(\"39963b736823d5780be96ab174869a9499d56d66497aa8f9b2244f777ebc366b\", " +
"\"ee1d0d972b7ea903615670de43ab1b6e7a825e811c70a29bb5fe0f819e0975fa\", " +
"\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
buildConfigField "org.tm.archive.KbsEnclave[]", "KBS_FALLBACKS", "new org.tm.archive.KbsEnclave[] { new org.tm.archive.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\""
buildConfigField "String", "MOBILE_COIN_ENVIRONMENT", "\"testnet\""
buildConfigField "String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/staging/registration/generate.html\""
buildConfigField "String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/staging/challenge/generate.html\""
buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"Staging\""
buildConfigField "String", "STRIPE_PUBLISHABLE_KEY", "\"pk_test_sngOd8FnXNkpce9nPXawKrJD00kIDngZkD\""
}
pnp {
dimension 'environment'
initWith staging
applicationIdSuffix ".pnp"
buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"Pnp\""
}
}
lint {
abortOnError true
baseline file('lint-baseline.xml')
checkReleaseBuilds false
disable 'LintError'
}
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
if (output.baseName.contains('nightly')) {
output.versionCodeOverride = canonicalVersionCode * postFixSize + 5
def tag = getCurrentGitTag()
if (tag != null && tag.length() > 0) {
if (tag.startsWith("v")) {
tag = tag.substring(1)
}
output.versionNameOverride = tag
}
} else {
output.outputFileName = output.outputFileName.replace(".apk", "-${variant.versionName}.apk")
def abiName = output.getFilter("ABI") ?: 'universal'
def postFix = abiPostFix.get(abiName, 0)
if (postFix >= postFixSize) throw new AssertionError("postFix is too large")
output.versionCodeOverride = canonicalVersionCode * postFixSize + postFix
}
}
}
android.variantFilter { variant ->
def distribution = variant.getFlavors().get(0).name
def environment = variant.getFlavors().get(1).name
def buildType = variant.buildType.name
def fullName = distribution + environment.capitalize() + buildType.capitalize()
if (!selectableVariants.contains(fullName)) {
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 {
implementation libs.androidx.fragment.ktx
lintChecks project(':lintchecks')
coreLibraryDesugaring libs.android.tools.desugar
implementation (libs.androidx.appcompat) {
version {
strictly '1.6.1'
}
}
implementation libs.androidx.window.window
implementation libs.androidx.window.java
implementation libs.androidx.recyclerview
implementation libs.material.material
implementation libs.androidx.legacy.support
implementation libs.androidx.preference
implementation libs.androidx.legacy.preference
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
implementation libs.androidx.navigation.ui.ktx
implementation libs.androidx.lifecycle.viewmodel.ktx
implementation libs.androidx.lifecycle.livedata.ktx
implementation libs.androidx.lifecycle.process
implementation libs.androidx.lifecycle.viewmodel.savedstate
implementation libs.androidx.lifecycle.common.java8
implementation libs.androidx.lifecycle.reactivestreams.ktx
implementation libs.androidx.camera.core
implementation libs.androidx.camera.camera2
implementation libs.androidx.camera.lifecycle
implementation libs.androidx.camera.view
implementation libs.androidx.concurrent.futures
implementation libs.androidx.autofill
implementation libs.androidx.biometric
implementation libs.androidx.sharetarget
implementation libs.androidx.profileinstaller
implementation libs.androidx.asynclayoutinflater
implementation libs.androidx.asynclayoutinflater.appcompat
implementation (libs.firebase.messaging) {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
}
implementation libs.google.play.services.maps
implementation libs.google.play.services.auth
implementation libs.bundles.media3
implementation libs.conscrypt.android
implementation libs.signal.aesgcmprovider
implementation project(':libsignal-service')
implementation project(':paging')
implementation project(':core-util')
implementation project(':glide-config')
implementation project(':video')
implementation project(':device-transfer')
implementation project(':image-editor')
implementation project(':donations')
implementation project(':contacts')
implementation project(':qr')
implementation project(':sms-exporter')
implementation project(':sticky-header-grid')
implementation project(':photoview')
implementation libs.libsignal.android
implementation libs.google.protobuf.javalite
implementation(libs.mobilecoin) {
exclude group: 'com.google.protobuf'
}
implementation libs.signal.ringrtc
implementation libs.leolin.shortcutbadger
implementation libs.emilsjolander.stickylistheaders
implementation libs.apache.httpclient.android
implementation libs.glide.glide
implementation libs.roundedimageview
implementation libs.materialish.progress
implementation libs.greenrobot.eventbus
implementation libs.google.zxing.android.integration
implementation libs.google.zxing.core
implementation libs.google.flexbox
implementation (libs.subsampling.scale.image.view) {
exclude group: 'com.android.support', module: 'support-annotations'
}
implementation (libs.android.tooltips) {
exclude group: 'com.android.support', module: 'appcompat-v7'
}
implementation (libs.android.smsmms) {
exclude group: 'com.squareup.okhttp', module: 'okhttp'
exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection'
}
implementation libs.stream
implementation libs.lottie
implementation libs.signal.android.database.sqlcipher
implementation libs.androidx.sqlite
implementation (libs.google.ez.vcard) {
exclude group: 'com.fasterxml.jackson.core'
exclude group: 'org.freemarker'
}
implementation libs.dnsjava
implementation libs.kotlinx.collections.immutable
implementation libs.accompanist.permissions
spinnerImplementation project(":spinner")
canaryImplementation libs.square.leakcanary
testImplementation testLibs.junit.junit
testImplementation testLibs.assertj.core
testImplementation testLibs.mockito.core
testImplementation testLibs.mockito.kotlin
testImplementation testLibs.androidx.test.core
testImplementation (testLibs.robolectric.robolectric) {
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.hamcrest.hamcrest
testImplementation testLibs.mockk
testImplementation(testFixtures(project(":libsignal-service")))
androidTestImplementation testLibs.androidx.test.ext.junit
androidTestImplementation testLibs.espresso.core
androidTestImplementation testLibs.androidx.test.core
androidTestImplementation testLibs.androidx.test.core.ktx
androidTestImplementation testLibs.androidx.test.ext.junit.ktx
androidTestImplementation testLibs.mockito.android
androidTestImplementation testLibs.mockito.kotlin
androidTestImplementation testLibs.mockk.android
androidTestImplementation testLibs.square.okhttp.mockserver
instrumentationImplementation (libs.androidx.fragment.testing) {
exclude group: 'androidx.test', module: 'core'
}
testImplementation testLibs.espresso.core
implementation libs.kotlin.stdlib.jdk8
implementation libs.kotlin.reflect
implementation libs.jackson.module.kotlin
implementation libs.rxjava3.rxandroid
implementation libs.rxjava3.rxkotlin
implementation libs.rxdogtag
androidTestUtil testLibs.androidx.test.orchestrator
implementation project(':core-ui')
ktlintRuleset libs.ktlint.twitter.compose
//**TM_SA**//Start
implementation 'com.squareup.okhttp3:okhttp:3.8.1'
implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.8.1'
implementation 'com.squareup.okhttp3:logging-interceptor:3.8.1'
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
implementation 'androidx.work:work-runtime:2.7.1'
implementation 'org.tinylog:tinylog-impl:2.6.2'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'commons-io:commons-io:2.6'
implementation 'org.apache.commons:commons-text:1.9'
implementation group: 'commons-io', name: 'commons-io', version: '2.6' //For test copy file
api fileTree(include: ['*.aar'], dir: 'libs')
// Include the MAM SDK
implementation files('MAMSDK/Microsoft.Intune.MAM.SDK.aar')
// Include MSAL
implementation 'com.microsoft.identity.client:msal:2.0.8'
//**TM_SA**//End
}
def getLastCommitTimestamp() {
if (!(new File('.git').exists())) {
return System.currentTimeMillis().toString()
}
new ByteArrayOutputStream().withStream { os ->
exec {
executable = 'git'
args = ['log', '-1', '--pretty=format:%ct']
standardOutput = os
}
return os.toString() + "000"
}
}
def getGitHash() {
if (!(new File('.git').exists())) {
throw new IllegalStateException("Must be a git repository to guarantee reproducible builds! (git hash is part of APK)")
}
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-parse', 'HEAD'
standardOutput = stdout
}
return stdout.toString().trim().substring(0, 12)
}
def getCurrentGitTag() {
if (!(new File('.git').exists())) {
throw new IllegalStateException("Must be a git repository to guarantee reproducible builds! (git hash is part of APK)")
}
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'tag', '--points-at', 'HEAD'
standardOutput = stdout
}
def output = stdout.toString().trim()
if (output != null && output.size() > 0) {
def tags = output.split('\n').toList()
return tags.stream().filter(t -> t.contains('nightly')).findFirst().orElse(tags.get(0))
} else {
return null
}
}
tasks.withType(Test) {
testLogging {
events "failed"
exceptionFormat "full"
showCauses true
showExceptions true
showStackTraces true
}
}
def loadKeystoreProperties(filename) {
def keystorePropertiesFile = file("${project.rootDir}/${filename}")
if (keystorePropertiesFile.exists()) {
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
return keystoreProperties
} else {
return null
}
}
static def getDateSuffix() {
def date = new Date()
def formattedDate = date.format('yyyy-MM-dd-HH:mm')
return formattedDate
}
def getMapsKey() {
def mapKey = file("${project.rootDir}/maps.key")
if (mapKey.exists()) {
return mapKey.readLines()[0]
}
return "AIzaSyAVa3EZMZWSbiUAfgiJTb-7Ljo0se6YWys"//**TM_SA**// change key
}

745
app/build.gradle.kts Normal file
View file

@ -0,0 +1,745 @@
import com.android.build.api.dsl.ManagedVirtualDevice
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import java.io.ByteArrayOutputStream
import java.io.FileInputStream
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Properties
plugins {
id("com.android.application")
id("kotlin-android")
id ("kotlin-kapt") //**TM_SA**
// id ("com.microsoft.intune.mam") //**TM_SA**
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("translations")
id("licenses")
}
apply(from = "static-ips.gradle.kts")
val canonicalVersionCode = 1376
val canonicalVersionName = "6.44.2"
val postFixSize = 100
val abiPostFix: Map<String, Int> = mapOf(
"universal" to 0,
"armeabi-v7a" to 1,
"arm64-v8a" to 2,
"x86" to 3,
"x86_64" to 4
)
val keystores: Map<String, Properties?> = mapOf("debug" to loadKeystoreProperties("keystore.debug.properties"))
val selectableVariants = listOf(
"nightlyProdSpinner",
"nightlyProdPerf",
"nightlyProdRelease",
"nightlyStagingRelease",
"nightlyPnpPerf",
"nightlyPnpRelease",
"playProdDebug",
"playProdSpinner",
"playProdCanary",
"playProdPerf",
"playProdBenchmark",
"playProdInstrumentation",
"playProdRelease",
"playStagingDebug",
"playStagingCanary",
"playStagingSpinner",
"playStagingPerf",
"playStagingInstrumentation",
"playPnpDebug",
"playPnpSpinner",
"playStagingRelease",
"websiteProdSpinner",
"websiteProdRelease"
)
val signalBuildToolsVersion: String by rootProject.extra
val signalCompileSdkVersion: String by rootProject.extra
val signalTargetSdkVersion: Int by rootProject.extra
val signalMinSdkVersion: Int by rootProject.extra
val signalJavaVersion: JavaVersion by rootProject.extra
val signalKotlinJvmTarget: String by rootProject.extra
wire {
kotlin {
javaInterop = true
}
sourcePath {
srcDir("src/main/protowire")
}
protoPath {
srcDir("${project.rootDir}/libsignal-service/src/main/protowire")
}
}
ktlint {
version.set("0.49.1")
}
android {
namespace = "org.tm.archive"
buildToolsVersion = signalBuildToolsVersion
compileSdkVersion = signalCompileSdkVersion
flavorDimensions += listOf("distribution", "environment")
useLibrary("org.apache.http.legacy")
testBuildType = "instrumentation"
kotlinOptions {
jvmTarget = signalKotlinJvmTarget
freeCompilerArgs = listOf("-Xallow-result-return-type")
}
keystores["debug"]?.let { properties ->
signingConfigs.getByName("debug").apply {
storeFile = file("${project.rootDir}/${properties.getProperty("storeFile")}")
storePassword = properties.getProperty("storePassword")
keyAlias = properties.getProperty("keyAlias")
keyPassword = properties.getProperty("keyPassword")
}
}
testOptions {
execution = "ANDROIDX_TEST_ORCHESTRATOR"
unitTests {
isIncludeAndroidResources = true
}
managedDevices {
devices {
create<ManagedVirtualDevice>("pixel3api30") {
device = "Pixel 3"
apiLevel = 30
systemImageSource = "google-atd"
require64Bit = false
}
}
}
}
sourceSets {
getByName("test") {
java.srcDir("$projectDir/src/testShared")
}
getByName("androidTest") {
java.srcDir("$projectDir/src/testShared")
}
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = signalJavaVersion
targetCompatibility = signalJavaVersion
}
packagingOptions {
resources {
excludes += setOf("LICENSE.txt", "LICENSE", "NOTICE", "asm-license.txt", "META-INF/LICENSE", "META-INF/LICENSE.md", "META-INF/NOTICE", "META-INF/LICENSE-notice.md", "META-INF/proguard/androidx-annotations.pro", "libsignal_jni.dylib", "signal_jni.dll")
}
}
buildFeatures {
viewBinding = true
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.4"
}
defaultConfig {
versionCode = canonicalVersionCode * postFixSize
versionName = canonicalVersionName
minSdkVersion(signalMinSdkVersion)
targetSdkVersion(signalTargetSdkVersion)
multiDexEnabled = true
vectorDrawables.useSupportLibrary = true
project.ext.set("archivesBaseName", "Signal")
manifestPlaceholders["mapsKey"] = "AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"
buildConfigField("long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L")
buildConfigField("String", "GIT_HASH", "\"${getGitHash()}\"")
buildConfigField("String", "SIGNAL_URL", "\"https://chat.signal.org\"")
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_SFU_URL", "\"https://sfu.voip.signal.org\"")
buildConfigField("String", "SIGNAL_STAGING_SFU_URL", "\"https://sfu.staging.voip.signal.org\"")
buildConfigField("String[]", "SIGNAL_SFU_INTERNAL_NAMES", "new String[]{\"Test\", \"Staging\", \"Development\"}")
buildConfigField("String[]", "SIGNAL_SFU_INTERNAL_URLS", "new String[]{\"https://sfu.test.voip.signal.org\", \"https://sfu.staging.voip.signal.org\", \"https://sfu.staging.test.voip.signal.org\"}")
buildConfigField("String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\"")
buildConfigField("int", "CONTENT_PROXY_PORT", "443")
buildConfigField("String[]", "SIGNAL_SERVICE_IPS", rootProject.extra["service_ips"] as String)
buildConfigField("String[]", "SIGNAL_STORAGE_IPS", rootProject.extra["storage_ips"] as String)
buildConfigField("String[]", "SIGNAL_CDN_IPS", rootProject.extra["cdn_ips"] as String)
buildConfigField("String[]", "SIGNAL_CDN2_IPS", rootProject.extra["cdn2_ips"] as String)
buildConfigField("String[]", "SIGNAL_CDN3_IPS", rootProject.extra["cdn3_ips"] as String)
buildConfigField("String[]", "SIGNAL_SFU_IPS", rootProject.extra["sfu_ips"] as String)
buildConfigField("String[]", "SIGNAL_CONTENT_PROXY_IPS", rootProject.extra["content_proxy_ips"] as String)
buildConfigField("String[]", "SIGNAL_CDSI_IPS", rootProject.extra["cdsi_ips"] as String)
buildConfigField("String[]", "SIGNAL_SVR2_IPS", rootProject.extra["svr2_ips"] as String)
buildConfigField("String", "SIGNAL_AGENT", "\"OWA\"")
buildConfigField("String", "CDSI_MRENCLAVE", "\"0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57\"")
buildConfigField("String", "SVR2_MRENCLAVE_DEPRECATED", "\"6ee1042f9e20f880326686dd4ba50c25359f01e9f733eeba4382bca001d45094\"")
buildConfigField("String", "SVR2_MRENCLAVE", "\"a6622ad4656e1abcd0bc0ff17c229477747d2ded0495c4ebee7ed35c1789fa97\"")
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+NameAZYOD12qRkxosQQP5uux6B2nRyZ7sAV54DgFyLiRcq1FvwKw2EPQdk4HDoePrO/RNUbyNddnM/mMgj4FW65xCoT1LmjrIjsv/Ggdlx46ueczhMgtBunx1/w8k8V+l8LVZ8gAT6wkU5J+DPQalQguMg12Jzug3q4TbdHiGCmD9EunCwOmsLuLJkz6EcSYXtrlDEnAM+hicw7iergYLLlMXpfTdGxJCWJmP4zqUFeTTmsmhsjGBt7NiEB/9pFFEB3pSbf4iiUukw63Eo8Aqnf4iwob6X1QviCWuc8t0I=\"")
buildConfigField("String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AByD873dTilmOSG0TjKrvpeaKEsUmIO8Vx9BeMmftwUs9v7ikPwM8P3OHyT0+X3EUMZrSe9VUp26Wai51Q9I8mdk0hX/yo7CeFGJyzoOqn8e/i4Ygbn5HoAyXJx5eXfIbqpc0bIxzju4H/HOQeOpt6h742qii5u/cbwOhFZCsMIbElZTaeU+BWMBQiZHIGHT5IE0qCordQKZ5iPZom0HeFa8Yq0ShuEyAl0WINBiY6xE3H/9WnvzXBbMuuk//eRxXgzO8ieCeK8FwQNxbfXqZm6Ro1cMhCOF3u7xoX83QhpN\"")
buildConfigField("String", "BACKUP_SERVER_PUBLIC_PARAMS", "\"AJwNSU55fsFCbgaxGRD11wO1juAs8Yr5GF8FPlGzzvdJJIKH5/4CC7ZJSOe3yL2vturVaRU2Cx0n751Vt8wkj1bozK3CBV1UokxV09GWf+hdVImLGjXGYLLhnI1J2TWEe7iWHyb553EEnRb5oxr9n3lUbNAJuRmFM7hrr0Al0F0wrDD4S8lo2mGaXe0MJCOM166F8oYRQqpFeEHfiLnxA1O8ZLh7vMdv4g9jI5phpRBTsJ5IjiJrWeP0zdIGHEssUeprDZ9OUJ14m0v61eYJMKsf59Bn+mAT2a7YfB+Don9O\"")
buildConfigField("String[]", "LANGUAGES", "new String[]{ ${languageList().map { "\"$it\"" }.joinToString(separator = ", ")} }")
buildConfigField("int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode")
buildConfigField("String", "DEFAULT_CURRENCIES", "\"EUR,AUD,GBP,CAD,CNY\"")
buildConfigField("String", "GIPHY_API_KEY", "\"3o6ZsYH6U6Eri53TXy\"")
buildConfigField("String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/registration/generate.html\"")
buildConfigField("String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/challenge/generate.html\"")
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"unset\"")
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"unset\"")
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"unset\"")
buildConfigField("String", "BADGE_STATIC_ROOT", "\"https://updates2.signal.org/static/badges/\"")
buildConfigField("String", "STRIPE_PUBLISHABLE_KEY", "\"pk_live_6cmGZopuTsV8novGgJJW9JpC00vLIgtQ1D\"")
buildConfigField("boolean", "TRACING_ENABLED", "false")
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
}
resourceConfigurations += listOf()
splits {
abi {
isEnable = !project.hasProperty("generateBaselineProfile")
reset()
include("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
isUniversalApk = true
}
}
testInstrumentationRunner = "org.tm.archive.testing.SignalTestRunner"
testInstrumentationRunnerArguments["clearPackageData"] = "true"
}
buildTypes {
getByName("debug") {
if (keystores["debug"] != null) {
signingConfig = signingConfigs["debug"]
}
isDefault = true
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android.txt"),
"proguard/proguard-firebase-messaging.pro",
"proguard/proguard-google-play-services.pro",
"proguard/proguard-jackson.pro",
"proguard/proguard-sqlite.pro",
"proguard/proguard-appcompat-v7.pro",
"proguard/proguard-square-okhttp.pro",
"proguard/proguard-square-okio.pro",
"proguard/proguard-rounded-image-view.pro",
"proguard/proguard-glide.pro",
"proguard/proguard-shortcutbadger.pro",
"proguard/proguard-retrofit.pro",
"proguard/proguard-webrtc.pro",
"proguard/proguard-klinker.pro",
"proguard/proguard-mobilecoin.pro",
"proguard/proguard-retrolambda.pro",
"proguard/proguard-okhttp.pro",
"proguard/proguard-ez-vcard.pro",
"proguard/proguard.cfg"
)
testProguardFiles(
"proguard/proguard-automation.pro",
"proguard/proguard.cfg"
)
manifestPlaceholders["mapsKey"] = getMapsKey()
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Debug\"")
}
getByName("release") {
isMinifyEnabled = true
proguardFiles(*buildTypes["debug"].proguardFiles.toTypedArray())
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Release\"")
}
create("instrumentation") {
initWith(getByName("debug"))
isDefault = false
isMinifyEnabled = false
matchingFallbacks += "debug"
applicationIdSuffix = ".instrumentation"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Instrumentation\"")
}
create("spinner") {
initWith(getByName("debug"))
isDefault = false
isMinifyEnabled = false
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Spinner\"")
}
create("perf") {
initWith(getByName("debug"))
isDefault = false
isDebuggable = false
isMinifyEnabled = true
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Perf\"")
buildConfigField("boolean", "TRACING_ENABLED", "true")
}
create("benchmark") {
initWith(getByName("debug"))
isDefault = false
isDebuggable = false
isMinifyEnabled = true
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Benchmark\"")
buildConfigField("boolean", "TRACING_ENABLED", "true")
}
create("canary") {
initWith(getByName("debug"))
isDefault = false
isMinifyEnabled = false
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Canary\"")
}
}
productFlavors {
create("play") {
dimension = "distribution"
isDefault = true
buildConfigField("boolean", "MANAGES_APP_UPDATES", "false")
buildConfigField("String", "APK_UPDATE_MANIFEST_URL", "null")
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"play\"")
}
create("website") {
dimension = "distribution"
buildConfigField("boolean", "MANAGES_APP_UPDATES", "true")
buildConfigField("String", "APK_UPDATE_MANIFEST_URL", "\"https://updates.signal.org/android/latest.json\"")
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"website\"")
}
create("nightly") {
val apkUpdateManifestUrl = if (file("${project.rootDir}/nightly-url.txt").exists()) {
file("${project.rootDir}/nightly-url.txt").readText().trim()
} else {
"<unset>"
}
dimension = "distribution"
versionNameSuffix = "-nightly-untagged-${getDateSuffix()}"
buildConfigField("boolean", "MANAGES_APP_UPDATES", "true")
buildConfigField("String", "APK_UPDATE_MANIFEST_URL", "\"${apkUpdateManifestUrl}\"")
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"nightly\"")
}
create("prod") {
dimension = "environment"
isDefault = true
buildConfigField("String", "MOBILE_COIN_ENVIRONMENT", "\"mainnet\"")
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"Prod\"")
}
create("staging") {
dimension = "environment"
applicationIdSuffix = ".staging"
buildConfigField("String", "SIGNAL_URL", "\"https://chat.staging.signal.org\"")
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_DEPRECATED", "\"a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95\"")
buildConfigField("String", "SVR2_MRENCLAVE", "\"acb1973aa0bbbd14b3b4e06f145497d948fd4a98efc500fcce363b3b743ec482\"")
buildConfigField("String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\"")
buildConfigField("String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUjlENAErBme1YHmOSpU6tr6doJ66dPzVAWIanmO/5mgjNEDeK7DDqQdB1xd03HT2Qs2TxY3kCK8aAb/0iM0HQiXjxZ9HIgYhbtvGEnDKW5ILSUydqH/KBhW4Pb0jZWnqN/YgbWDKeJxnDbYcUob5ZY5Lt5ZCMKuaGUvCJRrCtuugSMaqjowCGRempsDdJEt+cMaalhZ6gczklJB/IbdwENW9KeVFPoFNFzhxWUIS5ML9riVYhAtE6JE5jX0xiHNVIIPthb458cfA8daR0nYfYAUKogQArm0iBezOO+mPk5vCM=\"")
buildConfigField("String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AHILOIrFPXX9laLbalbA9+L1CXpSbM/bTJXZGZiuyK1JaI6dK5FHHWL6tWxmHKYAZTSYmElmJ5z2A5YcirjO/yfoemE03FItyaf8W1fE4p14hzb5qnrmfXUSiAIVrhaXVwIwSzH6RL/+EO8jFIjJ/YfExfJ8aBl48CKHgu1+A6kWynhttonvWWx6h7924mIzW0Czj2ROuh4LwQyZypex4GuOPW8sgIT21KNZaafgg+KbV7XM1x1tF3XA17B4uGUaDbDw2O+nR1+U5p6qHPzmJ7ggFjSN6Utu+35dS1sS0P9N\"")
buildConfigField("String", "BACKUP_SERVER_PUBLIC_PARAMS", "\"AHYrGb9IfugAAJiPKp+mdXUx+OL9zBolPYHYQz6GI1gWjpEu5me3zVNSvmYY4zWboZHif+HG1sDHSuvwFd0QszSwuSF4X4kRP3fJREdTZ5MCR0n55zUppTwfHRW2S4sdQ0JGz7YDQIJCufYSKh0pGNEHL6hv79Agrdnr4momr3oXdnkpVBIp3HWAQ6IbXQVSG18X36GaicI1vdT0UFmTwU2KTneluC2eyL9c5ff8PcmiS+YcLzh0OKYQXB5ZfQ06d6DiINvDQLy75zcfUOniLAj0lGJiHxGczin/RXisKSR8\"")
buildConfigField("String", "MOBILE_COIN_ENVIRONMENT", "\"testnet\"")
buildConfigField("String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/staging/registration/generate.html\"")
buildConfigField("String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/staging/challenge/generate.html\"")
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"Staging\"")
buildConfigField("String", "STRIPE_PUBLISHABLE_KEY", "\"pk_test_sngOd8FnXNkpce9nPXawKrJD00kIDngZkD\"")
}
create("pnp") {
dimension = "environment"
initWith(getByName("staging"))
applicationIdSuffix = ".pnp"
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"Pnp\"")
}
}
lint {
abortOnError = true
baseline = file("lint-baseline.xml")
checkReleaseBuilds = false
disable += "LintError"
}
applicationVariants.all {
val variant = this
variant.outputs
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
.forEach { output ->
if (output.baseName.contains("nightly")) {
output.versionCodeOverride = canonicalVersionCode * postFixSize + 5
var tag = getCurrentGitTag()
if (!tag.isNullOrEmpty()) {
if (tag.startsWith("v")) {
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")
val abiName: String = output.getFilter("ABI") ?: "universal"
val postFix: Int = abiPostFix[abiName]!!
if (postFix >= postFixSize) {
throw AssertionError("postFix is too large")
}
output.versionCodeOverride = canonicalVersionCode * postFixSize + postFix
}
}
}
android.variantFilter {
val distribution: String = flavors[0].name
val environment: String = flavors[1].name
val buildType: String = buildType.name
val fullName: String = distribution + environment.capitalize() + buildType.capitalize()
if (!selectableVariants.contains(fullName)) {
ignore = true
}
}
android.buildTypes.forEach {
val path: String = if (it.name == "release") {
"$projectDir/src/release/java"
} else {
"$projectDir/src/debug/java"
}
sourceSets.findByName(it.name)!!.java.srcDir(path)
}
}
dependencies {
lintChecks(project(":lintchecks"))
ktlintRuleset(libs.ktlint.twitter.compose)
coreLibraryDesugaring(libs.android.tools.desugar)
implementation(project(":libsignal-service"))
implementation(project(":paging"))
implementation(project(":core-util"))
implementation(project(":glide-config"))
implementation(project(":video"))
implementation(project(":device-transfer"))
implementation(project(":image-editor"))
implementation(project(":donations"))
implementation(project(":contacts"))
implementation(project(":qr"))
implementation(project(":sms-exporter"))
implementation(project(":sticky-header-grid"))
implementation(project(":photoview"))
implementation(project(":glide-webp"))
implementation(project(":core-ui"))
implementation(libs.androidx.fragment.ktx)
implementation(libs.androidx.appcompat) {
version {
strictly("1.6.1")
}
}
implementation(libs.androidx.window.window)
implementation(libs.androidx.window.java)
implementation(libs.androidx.recyclerview)
implementation(libs.material.material)
implementation(libs.androidx.legacy.support)
implementation(libs.androidx.preference)
implementation(libs.androidx.legacy.preference)
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)
implementation(libs.androidx.navigation.ui.ktx)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.lifecycle.livedata.ktx)
implementation(libs.androidx.lifecycle.process)
implementation(libs.androidx.lifecycle.viewmodel.savedstate)
implementation(libs.androidx.lifecycle.common.java8)
implementation(libs.androidx.lifecycle.reactivestreams.ktx)
implementation(libs.androidx.camera.core)
implementation(libs.androidx.camera.camera2)
implementation(libs.androidx.camera.lifecycle)
implementation(libs.androidx.camera.view)
implementation(libs.androidx.concurrent.futures)
implementation(libs.androidx.autofill)
implementation(libs.androidx.biometric)
implementation(libs.androidx.sharetarget)
implementation(libs.androidx.profileinstaller)
implementation(libs.androidx.asynclayoutinflater)
implementation(libs.androidx.asynclayoutinflater.appcompat)
implementation(libs.firebase.messaging) {
exclude(group = "com.google.firebase", module = "firebase-core")
exclude(group = "com.google.firebase", module = "firebase-analytics")
exclude(group = "com.google.firebase", module = "firebase-measurement-connector")
}
implementation(libs.google.play.services.maps)
implementation(libs.google.play.services.auth)
implementation(libs.bundles.media3)
implementation(libs.conscrypt.android)
implementation(libs.signal.aesgcmprovider)
implementation(libs.libsignal.android)
implementation(libs.mobilecoin)
implementation(libs.signal.ringrtc)
implementation(libs.leolin.shortcutbadger)
implementation(libs.emilsjolander.stickylistheaders)
implementation(libs.apache.httpclient.android)
implementation(libs.glide.glide)
implementation(libs.roundedimageview)
implementation(libs.materialish.progress)
implementation(libs.greenrobot.eventbus)
implementation(libs.google.zxing.android.integration)
implementation(libs.google.zxing.core)
implementation(libs.google.flexbox)
implementation(libs.subsampling.scale.image.view) {
exclude(group = "com.android.support", module = "support-annotations")
}
implementation(libs.android.tooltips) {
exclude(group = "com.android.support", module = "appcompat-v7")
}
implementation(libs.android.smsmms) {
exclude(group = "com.squareup.okhttp", module = "okhttp")
exclude(group = "com.squareup.okhttp", module = "okhttp-urlconnection")
}
implementation(libs.stream)
implementation(libs.lottie)
implementation(libs.signal.android.database.sqlcipher)
implementation(libs.androidx.sqlite)
implementation(libs.google.ez.vcard) {
exclude(group = "com.fasterxml.jackson.core")
exclude(group = "org.freemarker")
}
implementation(libs.dnsjava)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.accompanist.permissions)
implementation(libs.kotlin.stdlib.jdk8)
implementation(libs.kotlin.reflect)
implementation(libs.jackson.module.kotlin)
implementation(libs.rxjava3.rxandroid)
implementation(libs.rxjava3.rxkotlin)
implementation(libs.rxdogtag)
"spinnerImplementation"(project(":spinner"))
"canaryImplementation"(libs.square.leakcanary)
"instrumentationImplementation"(libs.androidx.fragment.testing) {
exclude(group = "androidx.test", module = "core")
}
testImplementation(testLibs.junit.junit)
testImplementation(testLibs.assertj.core)
testImplementation(testLibs.mockito.core)
testImplementation(testLibs.mockito.kotlin)
testImplementation(testLibs.androidx.test.core)
testImplementation(testLibs.robolectric.robolectric) {
exclude(group = "com.google.protobuf", module = "protobuf-java")
}
testImplementation(testLibs.robolectric.shadows.multidex)
testImplementation(testLibs.bouncycastle.bcprov.jdk15on) {
version {
strictly("1.70")
}
}
testImplementation(testLibs.bouncycastle.bcpkix.jdk15on) {
version {
strictly("1.70")
}
}
testImplementation(testLibs.conscrypt.openjdk.uber)
testImplementation(testLibs.hamcrest.hamcrest)
testImplementation(testLibs.mockk)
testImplementation(testFixtures(project(":libsignal-service")))
testImplementation(testLibs.espresso.core)
androidTestImplementation(testLibs.androidx.test.ext.junit)
androidTestImplementation(testLibs.espresso.core)
androidTestImplementation(testLibs.androidx.test.core)
androidTestImplementation(testLibs.androidx.test.core.ktx)
androidTestImplementation(testLibs.androidx.test.ext.junit.ktx)
androidTestImplementation(testLibs.mockito.android)
androidTestImplementation(testLibs.mockito.kotlin)
androidTestImplementation(testLibs.mockk.android)
androidTestImplementation(testLibs.square.okhttp.mockserver)
androidTestUtil(testLibs.androidx.test.orchestrator)
}
fun assertIsGitRepo() {
if (!file("${project.rootDir}/.git").exists()) {
throw IllegalStateException("Must be a git repository to guarantee reproducible builds! (git hash is part of APK)")
}
}
fun getLastCommitTimestamp(): String {
assertIsGitRepo()
ByteArrayOutputStream().use { os ->
exec {
executable = "git"
args = listOf("log", "-1", "--pretty=format:%ct")
standardOutput = os
}
return os.toString() + "000"
}
}
fun getGitHash(): String {
assertIsGitRepo()
val stdout = ByteArrayOutputStream()
exec {
commandLine = listOf("git", "rev-parse", "HEAD")
standardOutput = stdout
}
return stdout.toString().trim().substring(0, 12)
}
fun getCurrentGitTag(): String? {
assertIsGitRepo()
val stdout = ByteArrayOutputStream()
exec {
commandLine = listOf("git", "tag", "--points-at", "HEAD")
standardOutput = stdout
}
val output: String = stdout.toString().trim()
return if (output.isNotEmpty()) {
val tags = output.split("\n").toList()
tags.firstOrNull { it.contains("nightly") } ?: tags[0]
} else {
null
}
}
tasks.withType<Test>().configureEach {
testLogging {
events("failed")
exceptionFormat = TestExceptionFormat.FULL
showCauses = true
showExceptions = true
showStackTraces = true
}
}
project.tasks.configureEach {
if (name.lowercase().contains("nightly") && name != "checkNightlyParams") {
dependsOn(tasks.getByName("checkNightlyParams"))
}
}
tasks.register("checkNightlyParams") {
doFirst {
if (project.gradle.startParameter.taskNames.any { it.lowercase().contains("nightly") }) {
if (!file("${project.rootDir}/nightly-url.txt").exists()) {
throw GradleException("Cannot find 'nightly-url.txt' for nightly build! It must exist in the root of this project and contain the location of the nightly manifest.")
}
}
}
}
fun loadKeystoreProperties(filename: String): Properties? {
val keystorePropertiesFile = file("${project.rootDir}/$filename")
return if (keystorePropertiesFile.exists()) {
val keystoreProperties = Properties()
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
keystoreProperties
} else {
null
}
}
fun getDateSuffix(): String {
return SimpleDateFormat("yyyy-MM-dd-HH:mm").format(Date())
}
fun getMapsKey(): String {
val mapKey = file("${project.rootDir}/maps.key")
return if (mapKey.exists()) {
mapKey.readLines()[0]
} else {
"AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"
}
}
fun Project.languageList(): List<String> {
return fileTree("src/main/res") { include("**/strings.xml") }
.map { stringFile -> stringFile.parentFile.name }
.map { valuesFolderName -> valuesFolderName.replace("values-", "") }
.filter { valuesFolderName -> valuesFolderName != "values" }
.map { languageCode -> languageCode.replace("-r", "_") }
.distinct() + "en"
}
fun String.capitalize(): String {
return this.replaceFirstChar { it.uppercase() }
}

View file

@ -7,7 +7,5 @@ LOCAL_C_INCLUDES := $(JNI_DIR)/utils/
LOCAL_CFLAGS += -Wall
LOCAL_SRC_FILES := $(JNI_DIR)/utils/org_thoughtcrime_securesms_util_FileUtils.cpp
#//**TM_SA**//Start
LOCAL_SRC_FILES := $(JNI_DIR)/utils/org_tm_archive_util_FileUtils.cpp
# //**TM_SA**//End
include $(BUILD_SHARED_LIBRARY)

View file

@ -5,8 +5,8 @@
#include <unistd.h>
#include <linux/memfd.h>
#include <syscall.h>
//**TM_SA**//Change the package name to be our name.
jint JNICALL Java_org_tm_archive_util_FileUtils_getFileDescriptorOwner
jint JNICALL Java_org_thoughtcrime_securesms_util_FileUtils_getFileDescriptorOwner
(JNIEnv *env, jclass clazz, jobject fileDescriptor)
{
jclass fdClass = env->GetObjectClass(fileDescriptor);
@ -32,8 +32,7 @@ jint JNICALL Java_org_tm_archive_util_FileUtils_getFileDescriptorOwner
return stat_struct.st_uid;
}
//**TM_SA**//Change the package name to be our name.
JNIEXPORT jint JNICALL Java_org_tm_archive_util_FileUtils_createMemoryFileDescriptor
JNIEXPORT jint JNICALL Java_org_thoughtcrime_securesms_util_FileUtils_createMemoryFileDescriptor
(JNIEnv *env, jclass clazz, jstring jname)
{
const char *name = env->GetStringUTFChars(jname, NULL);

View file

@ -12,8 +12,7 @@ extern "C" {
* Method: getFileDescriptorOwner
* Signature: (Ljava/io/FileDescriptor;)I
*/
//**TM_SA**//Change the package name to be our name.
JNIEXPORT jint JNICALL Java_org_tm_archive_util_FileUtils_getFileDescriptorOwner
JNIEXPORT jint JNICALL Java_org_thoughtcrime_securesms_util_FileUtils_getFileDescriptorOwner
(JNIEnv *, jclass, jobject);
/*
@ -21,8 +20,7 @@ JNIEXPORT jint JNICALL Java_org_tm_archive_util_FileUtils_getFileDescriptorOwner
* Method: createMemoryFileDescriptor
* Signature: (Ljava/lang/String;)I
*/
//**TM_SA**//Change the package name to be our name.
JNIEXPORT jint JNICALL Java_org_tm_archive_util_FileUtils_createMemoryFileDescriptor
JNIEXPORT jint JNICALL Java_org_thoughtcrime_securesms_util_FileUtils_createMemoryFileDescriptor
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus

Binary file not shown.

View file

@ -19,12 +19,10 @@
message="Expected resource of type styleable"
errorLine1=" drawables.getColor(1, 0xff000000);"
errorLine2=" ~">
<!--//**TM_SA**//Start-->
<location
file="src/main/java/org/tm/archive/contacts/ContactSelectionListAdapter.java"
line="187"
column="36"/>
<!--//**TM_SA**//End-->
column="36"/><!--TM_SA change package name-->
</issue>
<issue

View file

@ -35,11 +35,11 @@
<issue id="AlertDialogBuilderUsage" severity="warning" />
<issue id="RestrictedApi" severity="error">
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/VideoCapture.java" />
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/CameraXModule.java" />
<ignore path="*/org/thoughtcrime/securesms/conversation/*.java" />
<ignore path="*/org/thoughtcrime/securesms/lock/v2/CreateKbsPinViewModel.java" />
<ignore path="*/org/thoughtcrime/securesms/jobs/StickerPackDownloadJob.java" />
<ignore path="*/org/tm/archive/mediasend/camerax/VideoCapture.java" /><!--TM_SA-->
<ignore path="*/org/tm/archive/mediasend/camerax/CameraXModule.java" /><!--TM_SA-->
<ignore path="*/org/tm/archive/conversation/*.java" /><!--TM_SA-->
<ignore path="*/org/tm/archive/lock/v2/CreateKbsPinViewModel.java" /><!--TM_SA-->
<ignore path="*/org/tm/archive/jobs/StickerPackDownloadJob.java" /><!--TM_SA-->
</issue>
<issue id="OptionalUsedAsFieldOrParameterType" severity="ignore" />

View file

@ -1,5 +0,0 @@
-keepattributes *Annotation*
-keepclassmembers class * {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }

View file

@ -4,17 +4,10 @@
-keep class org.whispersystems.** { *; }
-keep class org.signal.libsignal.protocol.** { *; }
-keep class org.tm.archive.** { *; }
-keep class org.signal.donations.json.** { *; }
-keepclassmembers class ** {
public void onEvent*(**);
}
#TM_SA start
-keepclassmembers class com.tm.authenticatorsdk.** {
public <init>();
}
-keepclassmembers class com.tm.androidcopysdk.** {
public <init>();
}
#TM_SA end
# Protobuf lite
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
@ -26,50 +19,4 @@
-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
#TM_SA start
-dontwarn com.arthenica.mobileffmpeg.FFmpeg
-dontwarn com.github.underscore.lodash.$
-dontwarn com.google.crypto.tink.subtle.Ed25519Sign$KeyPair
-dontwarn com.google.crypto.tink.subtle.Ed25519Sign
-dontwarn com.google.crypto.tink.subtle.Ed25519Verify
-dontwarn com.google.crypto.tink.subtle.X25519
-dontwarn java.beans.BeanInfo
-dontwarn java.beans.IntrospectionException
-dontwarn java.beans.Introspector
-dontwarn java.beans.PropertyDescriptor
-dontwarn org.bouncycastle.asn1.ASN1Encodable
-dontwarn org.bouncycastle.asn1.pkcs.PrivateKeyInfo
-dontwarn org.bouncycastle.asn1.x509.AlgorithmIdentifier
-dontwarn org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
-dontwarn org.bouncycastle.cert.X509CertificateHolder
-dontwarn org.bouncycastle.cert.jcajce.JcaX509CertificateHolder
-dontwarn org.bouncycastle.crypto.BlockCipher
-dontwarn org.bouncycastle.crypto.CipherParameters
-dontwarn org.bouncycastle.crypto.InvalidCipherTextException
-dontwarn org.bouncycastle.crypto.engines.AESEngine
-dontwarn org.bouncycastle.crypto.modes.GCMBlockCipher
-dontwarn org.bouncycastle.crypto.params.AEADParameters
-dontwarn org.bouncycastle.crypto.params.KeyParameter
-dontwarn org.bouncycastle.jce.provider.BouncyCastleProvider
-dontwarn org.bouncycastle.openssl.PEMException
-dontwarn org.bouncycastle.openssl.PEMKeyPair
-dontwarn org.bouncycastle.openssl.PEMParser
-dontwarn org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
-dontwarn retrofit2.adapter.rxjava.RxJavaCallAdapterFactory
-dontwarn rx.Observable
-dontwarn rx.Observer
-dontwarn rx.Scheduler
-dontwarn rx.Subscription
-dontwarn rx.android.schedulers.AndroidSchedulers
-dontwarn rx.functions.Func1
-dontwarn rx.schedulers.Schedulers
-dontwarn javax.naming.NamingEnumeration
-dontwarn javax.naming.NamingException
-dontwarn javax.naming.directory.Attribute
-dontwarn javax.naming.directory.Attributes
-dontwarn javax.naming.directory.DirContext
-dontwarn javax.naming.directory.InitialDirContext
#TM_SA end
-dontwarn sun.net.spi.nameservice.NameServiceDescriptor

View file

@ -26,15 +26,13 @@ class SignalInstrumentationApplicationContext : ApplicationContext() {
}
override fun initializeLogging() {
persistentLogger = PersistentLogger(this)
Log.initialize({ true }, AndroidLogger(), persistentLogger, inMemoryLogger)
Log.initialize({ true }, AndroidLogger(), PersistentLogger(this), inMemoryLogger)
SignalProtocolLoggerProvider.setProvider(CustomSignalProtocolLogger())
SignalExecutors.UNBOUNDED.execute {
Log.blockUntilAllWritesFinished()
LogDatabase.getInstance(this).trimToSize()
LogDatabase.getInstance(this).logs.trimToSize()
}
}
}

View file

@ -0,0 +1,675 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.tm.archive.backup.v2
import android.content.ContentValues
import android.database.Cursor
import androidx.core.content.contentValuesOf
import net.zetetic.database.sqlcipher.SQLiteDatabase
import org.junit.Before
import org.junit.Test
import org.signal.core.util.Hex
import org.signal.core.util.SqlUtil
import org.signal.core.util.insertInto
import org.signal.core.util.readToList
import org.signal.core.util.readToSingleObject
import org.signal.core.util.requireBlob
import org.signal.core.util.requireLong
import org.signal.core.util.requireString
import org.signal.core.util.select
import org.signal.core.util.toInt
import org.signal.core.util.withinTransaction
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.tm.archive.backup.v2.database.clearAllDataForBackupRestore
import org.tm.archive.database.CallTable
import org.tm.archive.database.EmojiSearchTable
import org.tm.archive.database.MessageTable
import org.tm.archive.database.MessageTypes
import org.tm.archive.database.RecipientTable
import org.tm.archive.database.SignalDatabase
import org.tm.archive.database.model.databaseprotos.BodyRangeList
import org.tm.archive.dependencies.ApplicationDependencies
import org.tm.archive.keyvalue.PhoneNumberPrivacyValues
import org.tm.archive.keyvalue.SignalStore
import org.tm.archive.mms.QuoteModel
import org.tm.archive.profiles.ProfileName
import org.tm.archive.recipients.Recipient
import org.tm.archive.recipients.RecipientId
import org.tm.archive.subscription.Subscriber
import org.tm.archive.testing.assertIs
import org.tm.archive.util.TextSecurePreferences
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import java.io.ByteArrayInputStream
import java.util.UUID
import kotlin.random.Random
typealias DatabaseData = Map<String, List<Map<String, Any?>>>
class BackupTest {
companion object {
val SELF_ACI = ACI.from(UUID.fromString("77770000-b477-4f35-a824-d92987a63641"))
val SELF_PNI = PNI.from(UUID.fromString("77771111-b014-41fb-bf73-05cb2ec52910"))
const val SELF_E164 = "+10000000000"
val SELF_PROFILE_KEY = ProfileKey(Random.nextBytes(32))
val ALICE_ACI = ACI.from(UUID.fromString("aaaa0000-5a76-47fa-a98a-7e72c948a82e"))
val ALICE_PNI = PNI.from(UUID.fromString("aaaa1111-c960-4f6c-8385-671ad2ffb999"))
val ALICE_E164 = "+12222222222"
/** Columns that we don't need to check equality of */
private val IGNORED_COLUMNS: Map<String, Set<String>> = mapOf(
RecipientTable.TABLE_NAME to setOf(RecipientTable.STORAGE_SERVICE_ID),
MessageTable.TABLE_NAME to setOf(MessageTable.FROM_DEVICE_ID)
)
/** Tables we don't need to check equality of */
private val IGNORED_TABLES: Set<String> = setOf(
EmojiSearchTable.TABLE_NAME,
"sqlite_sequence",
"message_fts_data",
"message_fts_idx",
"message_fts_docsize"
)
}
@Before
fun setup() {
SignalStore.account().setE164(SELF_E164)
SignalStore.account().setAci(SELF_ACI)
SignalStore.account().setPni(SELF_PNI)
SignalStore.account().generateAciIdentityKeyIfNecessary()
SignalStore.account().generatePniIdentityKeyIfNecessary()
}
@Test
fun emptyDatabase() {
backupTest { }
}
@Test
fun noteToSelf() {
backupTest {
individualChat(aci = SELF_ACI, givenName = "Note to Self") {
standardMessage(outgoing = true, body = "A")
standardMessage(outgoing = true, body = "B")
standardMessage(outgoing = true, body = "C")
}
}
}
@Test
fun individualChat() {
backupTest {
individualChat(aci = ALICE_ACI, givenName = "Alice") {
val m1 = standardMessage(outgoing = true, body = "Outgoing 1")
val m2 = standardMessage(outgoing = false, body = "Incoming 1", read = true)
standardMessage(outgoing = true, body = "Outgoing 2", quotes = m2)
standardMessage(outgoing = false, body = "Incoming 2", quotes = m1, quoteTargetMissing = true, read = false)
standardMessage(outgoing = true, body = "Outgoing 3, with mention", randomMention = true)
standardMessage(outgoing = false, body = "Incoming 3, with style", read = false, randomStyling = true)
remoteDeletedMessage(outgoing = true)
remoteDeletedMessage(outgoing = false)
}
}
}
@Test
fun individualRecipients() {
backupTest {
// Comprehensive example
individualRecipient(
aci = ALICE_ACI,
pni = ALICE_PNI,
e164 = ALICE_E164,
givenName = "Alice",
familyName = "Smith",
username = "alice.99",
hidden = false,
registeredState = RecipientTable.RegisteredState.REGISTERED,
profileKey = ProfileKey(Random.nextBytes(32)),
profileSharing = true,
hideStory = false
)
// Trying to get coverage of all the various values
individualRecipient(aci = ACI.from(UUID.randomUUID()), registeredState = RecipientTable.RegisteredState.NOT_REGISTERED)
individualRecipient(aci = ACI.from(UUID.randomUUID()), registeredState = RecipientTable.RegisteredState.UNKNOWN)
individualRecipient(pni = PNI.from(UUID.randomUUID()))
individualRecipient(e164 = "+15551234567")
individualRecipient(aci = ACI.from(UUID.randomUUID()), givenName = "Bob")
individualRecipient(aci = ACI.from(UUID.randomUUID()), familyName = "Smith")
individualRecipient(aci = ACI.from(UUID.randomUUID()), profileSharing = false)
individualRecipient(aci = ACI.from(UUID.randomUUID()), hideStory = true)
individualRecipient(aci = ACI.from(UUID.randomUUID()), hidden = true)
}
}
@Test
fun individualCallLogs() {
backupTest {
val aliceId = individualRecipient(
aci = ALICE_ACI,
pni = ALICE_PNI,
e164 = ALICE_E164,
givenName = "Alice",
familyName = "Smith",
username = "alice.99",
hidden = false,
registeredState = RecipientTable.RegisteredState.REGISTERED,
profileKey = ProfileKey(Random.nextBytes(32)),
profileSharing = true,
hideStory = false
)
insertOneToOneCallVariations(1, 1, aliceId)
}
}
private fun insertOneToOneCallVariations(callId: Long, timestamp: Long, id: RecipientId): Long {
val directions = arrayOf(CallTable.Direction.INCOMING, CallTable.Direction.OUTGOING)
val callTypes = arrayOf(CallTable.Type.AUDIO_CALL, CallTable.Type.VIDEO_CALL)
val events = arrayOf(
CallTable.Event.MISSED,
CallTable.Event.OUTGOING_RING,
CallTable.Event.ONGOING,
CallTable.Event.ACCEPTED,
CallTable.Event.NOT_ACCEPTED
)
var callTimestamp: Long = timestamp
var currentCallId = callId
for (direction in directions) {
for (event in events) {
for (type in callTypes) {
insertOneToOneCall(callId = currentCallId, callTimestamp, id, type, direction, event)
callTimestamp++
currentCallId++
}
}
}
return currentCallId
}
private fun insertOneToOneCall(callId: Long, timestamp: Long, peer: RecipientId, type: CallTable.Type, direction: CallTable.Direction, event: CallTable.Event) {
val messageType: Long = CallTable.Call.getMessageType(type, direction, event)
SignalDatabase.rawDatabase.withinTransaction {
val recipient = Recipient.resolved(peer)
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
val outgoing = direction == CallTable.Direction.OUTGOING
val messageValues = contentValuesOf(
MessageTable.FROM_RECIPIENT_ID to if (outgoing) Recipient.self().id.serialize() else peer.serialize(),
MessageTable.FROM_DEVICE_ID to 1,
MessageTable.TO_RECIPIENT_ID to if (outgoing) peer.serialize() else Recipient.self().id.serialize(),
MessageTable.DATE_RECEIVED to timestamp,
MessageTable.DATE_SENT to timestamp,
MessageTable.READ to 1,
MessageTable.TYPE to messageType,
MessageTable.THREAD_ID to threadId
)
val messageId = SignalDatabase.rawDatabase.insert(MessageTable.TABLE_NAME, null, messageValues)
val values = contentValuesOf(
CallTable.CALL_ID to callId,
CallTable.MESSAGE_ID to messageId,
CallTable.PEER to peer.serialize(),
CallTable.TYPE to CallTable.Type.serialize(type),
CallTable.DIRECTION to CallTable.Direction.serialize(direction),
CallTable.EVENT to CallTable.Event.serialize(event),
CallTable.TIMESTAMP to timestamp
)
SignalDatabase.rawDatabase.insert(CallTable.TABLE_NAME, null, values)
SignalDatabase.threads.update(threadId, true)
}
}
@Test
fun accountData() {
val context = ApplicationDependencies.getApplication()
backupTest(validateKeyValue = true) {
val self = Recipient.self()
// TODO note-to-self archived
// TODO note-to-self unread
SignalStore.account().setAci(SELF_ACI)
SignalStore.account().setPni(SELF_PNI)
SignalStore.account().setE164(SELF_E164)
SignalStore.account().generateAciIdentityKeyIfNecessary()
SignalStore.account().generatePniIdentityKeyIfNecessary()
SignalDatabase.recipients.setProfileKey(self.id, ProfileKey(Random.nextBytes(32)))
SignalDatabase.recipients.setProfileName(self.id, ProfileName.fromParts("Peter", "Parker"))
SignalDatabase.recipients.setProfileAvatar(self.id, "https://example.com/")
SignalStore.donationsValues().markUserManuallyCancelled()
SignalStore.donationsValues().setSubscriber(Subscriber(SubscriberId.generate(), "USD"))
SignalStore.donationsValues().setDisplayBadgesOnProfile(false)
SignalStore.phoneNumberPrivacy().phoneNumberListingMode = PhoneNumberPrivacyValues.PhoneNumberListingMode.UNLISTED
SignalStore.phoneNumberPrivacy().phoneNumberSharingMode = PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY
SignalStore.settings().isLinkPreviewsEnabled = false
SignalStore.settings().isPreferSystemContactPhotos = true
SignalStore.settings().universalExpireTimer = 42
SignalStore.settings().setKeepMutedChatsArchived(true)
SignalStore.storyValues().viewedReceiptsEnabled = false
SignalStore.storyValues().userHasViewedOnboardingStory = true
SignalStore.storyValues().isFeatureDisabled = false
SignalStore.storyValues().userHasBeenNotifiedAboutStories = true
SignalStore.storyValues().userHasSeenGroupStoryEducationSheet = true
SignalStore.emojiValues().reactions = listOf("a", "b", "c")
TextSecurePreferences.setTypingIndicatorsEnabled(context, false)
TextSecurePreferences.setReadReceiptsEnabled(context, false)
TextSecurePreferences.setShowUnidentifiedDeliveryIndicatorsEnabled(context, true)
}
// Have to check TextSecurePreferences ourselves, since they're not in a database
TextSecurePreferences.isTypingIndicatorsEnabled(context) assertIs false
TextSecurePreferences.isReadReceiptsEnabled(context) assertIs false
TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context) assertIs true
}
/**
* Sets up the database, then executes your setup code, then compares snapshots of the database
* before an after an import to ensure that no data was lost/changed.
*
* @param validateKeyValue If true, this will also validate the KeyValueDatabase. You only want to do this if you
* intend on setting most of the values. Otherwise stuff tends to not match since values are lazily written.
*/
private fun backupTest(validateKeyValue: Boolean = false, content: () -> Unit) {
// Under normal circumstances, My Story ends up being the first recipient in the table, and is added automatically.
// This screws with the tests by offsetting all the recipientIds in the initial state.
// Easiest way to get around this is to make the DB a true clean slate by clearing everything.
// (We only really need to clear Recipient/dlists, but doing everything to be consistent.)
SignalDatabase.distributionLists.clearAllDataForBackupRestore()
SignalDatabase.recipients.clearAllDataForBackupRestore()
SignalDatabase.messages.clearAllDataForBackupRestore()
SignalDatabase.threads.clearAllDataForBackupRestore()
// Again, for comparison purposes, because we always import self first, we want to ensure it's the first item
// in the table when we export.
individualRecipient(
aci = SELF_ACI,
pni = SELF_PNI,
e164 = SELF_E164,
profileKey = SELF_PROFILE_KEY,
profileSharing = true
)
content()
val startingMainData: DatabaseData = SignalDatabase.rawDatabase.readAllContents()
val startingKeyValueData: DatabaseData = if (validateKeyValue) SignalDatabase.rawDatabase.readAllContents() else emptyMap()
val exported: ByteArray = BackupRepository.export()
BackupRepository.import(length = exported.size.toLong(), inputStreamFactory = { ByteArrayInputStream(exported) }, selfData = BackupRepository.SelfData(SELF_ACI, SELF_PNI, SELF_E164, SELF_PROFILE_KEY))
val endingData: DatabaseData = SignalDatabase.rawDatabase.readAllContents()
val endingKeyValueData: DatabaseData = if (validateKeyValue) SignalDatabase.rawDatabase.readAllContents() else emptyMap()
assertDatabaseMatches(startingMainData, endingData)
assertDatabaseMatches(startingKeyValueData, endingKeyValueData)
}
private fun individualChat(aci: ACI, givenName: String, familyName: String? = null, init: IndividualChatCreator.() -> Unit) {
val recipientId = individualRecipient(aci = aci, givenName = givenName, familyName = familyName, profileSharing = true)
val threadId: Long = SignalDatabase.threads.getOrCreateThreadIdFor(recipientId, false)
IndividualChatCreator(SignalDatabase.rawDatabase, recipientId, threadId).init()
SignalDatabase.threads.update(threadId, false)
}
private fun individualRecipient(
aci: ACI? = null,
pni: PNI? = null,
e164: String? = null,
givenName: String? = null,
familyName: String? = null,
username: String? = null,
hidden: Boolean = false,
registeredState: RecipientTable.RegisteredState = RecipientTable.RegisteredState.UNKNOWN,
profileKey: ProfileKey? = null,
profileSharing: Boolean = false,
hideStory: Boolean = false
): RecipientId {
check(aci != null || pni != null || e164 != null)
val recipientId = SignalDatabase.recipients.getAndPossiblyMerge(aci, pni, e164, pniVerified = true, changeSelf = true)
if (givenName != null || familyName != null) {
SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts(givenName, familyName))
}
if (username != null) {
SignalDatabase.recipients.setUsername(recipientId, username)
}
if (registeredState == RecipientTable.RegisteredState.REGISTERED) {
SignalDatabase.recipients.markRegistered(recipientId, aci ?: pni!!)
} else if (registeredState == RecipientTable.RegisteredState.NOT_REGISTERED) {
SignalDatabase.recipients.markUnregistered(recipientId)
}
if (profileKey != null) {
SignalDatabase.recipients.setProfileKey(recipientId, profileKey)
}
SignalDatabase.recipients.setProfileSharing(recipientId, profileSharing)
SignalDatabase.recipients.setHideStory(recipientId, hideStory)
if (hidden) {
SignalDatabase.recipients.markHidden(recipientId)
}
return recipientId
}
private inner class IndividualChatCreator(
private val db: SQLiteDatabase,
private val recipientId: RecipientId,
private val threadId: Long
) {
fun standardMessage(
outgoing: Boolean,
sentTimestamp: Long = System.currentTimeMillis(),
receivedTimestamp: Long = if (outgoing) sentTimestamp else sentTimestamp + 1,
serverTimestamp: Long = sentTimestamp,
body: String? = null,
read: Boolean = true,
quotes: Long? = null,
quoteTargetMissing: Boolean = false,
randomMention: Boolean = false,
randomStyling: Boolean = false
): Long {
return db.insertMessage(
from = if (outgoing) Recipient.self().id else recipientId,
to = if (outgoing) recipientId else Recipient.self().id,
outgoing = outgoing,
threadId = threadId,
sentTimestamp = sentTimestamp,
receivedTimestamp = receivedTimestamp,
serverTimestamp = serverTimestamp,
body = body,
read = read,
quotes = quotes,
quoteTargetMissing = quoteTargetMissing,
randomMention = randomMention,
randomStyling = randomStyling
)
}
fun remoteDeletedMessage(
outgoing: Boolean,
sentTimestamp: Long = System.currentTimeMillis(),
receivedTimestamp: Long = if (outgoing) sentTimestamp else sentTimestamp + 1,
serverTimestamp: Long = sentTimestamp
): Long {
return db.insertMessage(
from = if (outgoing) Recipient.self().id else recipientId,
to = if (outgoing) recipientId else Recipient.self().id,
outgoing = outgoing,
threadId = threadId,
sentTimestamp = sentTimestamp,
receivedTimestamp = receivedTimestamp,
serverTimestamp = serverTimestamp,
remoteDeleted = true
)
}
}
private fun SQLiteDatabase.insertMessage(
from: RecipientId,
to: RecipientId,
outgoing: Boolean,
threadId: Long,
sentTimestamp: Long = System.currentTimeMillis(),
receivedTimestamp: Long = if (outgoing) sentTimestamp else sentTimestamp + 1,
serverTimestamp: Long = sentTimestamp,
body: String? = null,
read: Boolean = true,
quotes: Long? = null,
quoteTargetMissing: Boolean = false,
randomMention: Boolean = false,
randomStyling: Boolean = false,
remoteDeleted: Boolean = false
): Long {
val type = if (outgoing) {
MessageTypes.BASE_SENT_TYPE
} else {
MessageTypes.BASE_INBOX_TYPE
} or MessageTypes.SECURE_MESSAGE_BIT or MessageTypes.PUSH_MESSAGE_BIT
val contentValues = ContentValues()
contentValues.put(MessageTable.DATE_SENT, sentTimestamp)
contentValues.put(MessageTable.DATE_RECEIVED, receivedTimestamp)
contentValues.put(MessageTable.FROM_RECIPIENT_ID, from.serialize())
contentValues.put(MessageTable.TO_RECIPIENT_ID, to.serialize())
contentValues.put(MessageTable.THREAD_ID, threadId)
contentValues.put(MessageTable.BODY, body)
contentValues.put(MessageTable.TYPE, type)
contentValues.put(MessageTable.READ, if (read) 1 else 0)
if (!outgoing) {
contentValues.put(MessageTable.DATE_SERVER, serverTimestamp)
}
if (remoteDeleted) {
contentValues.put(MessageTable.REMOTE_DELETED, 1)
return this
.insertInto(MessageTable.TABLE_NAME)
.values(contentValues)
.run()
}
if (quotes != null) {
val quoteDetails = this.getQuoteDetailsFor(quotes)
contentValues.put(MessageTable.QUOTE_ID, if (quoteTargetMissing) MessageTable.QUOTE_TARGET_MISSING_ID else quoteDetails.quotedSentTimestamp)
contentValues.put(MessageTable.QUOTE_AUTHOR, quoteDetails.authorId.serialize())
contentValues.put(MessageTable.QUOTE_BODY, quoteDetails.body)
contentValues.put(MessageTable.QUOTE_BODY_RANGES, quoteDetails.bodyRanges)
contentValues.put(MessageTable.QUOTE_TYPE, quoteDetails.type)
contentValues.put(MessageTable.QUOTE_MISSING, quoteTargetMissing.toInt())
}
if (body != null && (randomMention || randomStyling)) {
val ranges: MutableList<BodyRangeList.BodyRange> = mutableListOf()
if (randomMention) {
ranges += BodyRangeList.BodyRange(
start = 0,
length = Random.nextInt(body.length),
mentionUuid = if (outgoing) Recipient.resolved(to).requireAci().toString() else Recipient.resolved(from).requireAci().toString()
)
}
if (randomStyling) {
ranges += BodyRangeList.BodyRange(
start = 0,
length = Random.nextInt(body.length),
style = BodyRangeList.BodyRange.Style.fromValue(Random.nextInt(BodyRangeList.BodyRange.Style.values().size))
)
}
contentValues.put(MessageTable.MESSAGE_RANGES, BodyRangeList(ranges = ranges).encode())
}
return this
.insertInto(MessageTable.TABLE_NAME)
.values(contentValues)
.run()
}
private fun assertDatabaseMatches(expected: DatabaseData, actual: DatabaseData) {
assert(expected.keys.size == actual.keys.size) { "Mismatched table count! Expected: ${expected.keys} || Actual: ${actual.keys}" }
assert(expected.keys.containsAll(actual.keys)) { "Table names differ! Expected: ${expected.keys} || Actual: ${actual.keys}" }
val tablesToCheck = expected.keys.filter { !IGNORED_TABLES.contains(it) }
for (table in tablesToCheck) {
val expectedTable: List<Map<String, Any?>> = expected[table]!!
val actualTable: List<Map<String, Any?>> = actual[table]!!
assert(expectedTable.size == actualTable.size) { "Mismatched number of rows for table '$table'! Expected: ${expectedTable.size} || Actual: ${actualTable.size}\n $actualTable" }
val expectedFiltered: List<Map<String, Any?>> = expectedTable.withoutExcludedColumns(IGNORED_COLUMNS[table])
val actualFiltered: List<Map<String, Any?>> = actualTable.withoutExcludedColumns(IGNORED_COLUMNS[table])
assert(contentEquals(expectedFiltered, actualFiltered)) { "Data did not match for table '$table'!\n${prettyDiff(expectedFiltered, actualFiltered)}" }
}
}
private fun contentEquals(expectedRows: List<Map<String, Any?>>, actualRows: List<Map<String, Any?>>): Boolean {
if (expectedRows == actualRows) {
return true
}
assert(expectedRows.size == actualRows.size)
for (i in expectedRows.indices) {
val expectedRow = expectedRows[i]
val actualRow = actualRows[i]
for (key in expectedRow.keys) {
val expectedValue = expectedRow[key]
val actualValue = actualRow[key]
if (!contentEquals(expectedValue, actualValue)) {
return false
}
}
}
return true
}
private fun contentEquals(lhs: Any?, rhs: Any?): Boolean {
return if (lhs is ByteArray && rhs is ByteArray) {
lhs.contentEquals(rhs)
} else {
lhs == rhs
}
}
private fun prettyDiff(expectedRows: List<Map<String, Any?>>, actualRows: List<Map<String, Any?>>): String {
val builder = StringBuilder()
assert(expectedRows.size == actualRows.size)
for (i in expectedRows.indices) {
val expectedRow = expectedRows[i]
val actualRow = actualRows[i]
var describedRow = false
for (key in expectedRow.keys) {
val expectedValue = expectedRow[key]
val actualValue = actualRow[key]
if (!contentEquals(expectedValue, actualValue)) {
if (!describedRow) {
builder.append("-- ROW ${i + 1}\n")
describedRow = true
}
builder.append("  [$key] Expected: ${expectedValue.prettyPrint()} || Actual: ${actualValue.prettyPrint()} \n")
}
}
if (describedRow) {
builder.append("\n")
builder.append("Expected: $expectedRow\n")
builder.append("Actual: $actualRow\n")
}
}
return builder.toString()
}
private fun Any?.prettyPrint(): String {
return when (this) {
is ByteArray -> "Bytes(${Hex.toString(this)})"
else -> this.toString()
}
}
private fun List<Map<String, Any?>>.withoutExcludedColumns(ignored: Set<String>?): List<Map<String, Any?>> {
return if (ignored != null) {
this.map { row ->
row.filterKeys { !ignored.contains(it) }
}
} else {
this
}
}
private fun SQLiteDatabase.getQuoteDetailsFor(messageId: Long): QuoteDetails {
return this
.select(
MessageTable.DATE_SENT,
MessageTable.FROM_RECIPIENT_ID,
MessageTable.BODY,
MessageTable.MESSAGE_RANGES
)
.from(MessageTable.TABLE_NAME)
.where("${MessageTable.ID} = ?", messageId)
.run()
.readToSingleObject { cursor ->
QuoteDetails(
quotedSentTimestamp = cursor.requireLong(MessageTable.DATE_SENT),
authorId = RecipientId.from(cursor.requireLong(MessageTable.FROM_RECIPIENT_ID)),
body = cursor.requireString(MessageTable.BODY),
bodyRanges = cursor.requireBlob(MessageTable.MESSAGE_RANGES),
type = QuoteModel.Type.NORMAL.code
)
}!!
}
private fun SQLiteDatabase.readAllContents(): DatabaseData {
return SqlUtil.getAllTables(this).associateWith { table -> this.getAllTableData(table) }
}
private fun SQLiteDatabase.getAllTableData(table: String): List<Map<String, Any?>> {
return this
.select()
.from(table)
.run()
.readToList { cursor ->
val map: MutableMap<String, Any?> = mutableMapOf()
for (i in 0 until cursor.columnCount) {
val column = cursor.getColumnName(i)
when (cursor.getType(i)) {
Cursor.FIELD_TYPE_INTEGER -> map[column] = cursor.getInt(i)
Cursor.FIELD_TYPE_FLOAT -> map[column] = cursor.getFloat(i)
Cursor.FIELD_TYPE_STRING -> map[column] = cursor.getString(i)
Cursor.FIELD_TYPE_BLOB -> map[column] = cursor.getBlob(i)
Cursor.FIELD_TYPE_NULL -> map[column] = null
}
}
map
}
}
private data class QuoteDetails(
val quotedSentTimestamp: Long,
val authorId: RecipientId,
val body: String?,
val bodyRanges: ByteArray?,
val type: Int
)
}

View file

@ -50,7 +50,6 @@ class ChangeNumberViewModelTest {
@Before
fun setUp() {
ApplicationDependencies.getSignalServiceAccountManager().setSoTimeoutMillis(1000)
ThreadUtil.runOnMainSync {
viewModel = ChangeNumberViewModel(
localNumber = harness.self.requireE164(),
@ -231,8 +230,6 @@ class ChangeNumberViewModelTest {
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
MockProvider.mockGetRegistrationLockStringFlow()
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) },
@ -319,8 +316,6 @@ class ChangeNumberViewModelTest {
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
MockProvider.mockGetRegistrationLockStringFlow()
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) },

View file

@ -9,8 +9,9 @@ import org.junit.runner.RunWith
import org.signal.core.util.ThreadUtil
import org.tm.archive.attachments.PointerAttachment
import org.tm.archive.conversation.v2.ConversationActivity
import org.tm.archive.database.MessageType
import org.tm.archive.database.SignalDatabase
import org.tm.archive.mms.IncomingMediaMessage
import org.tm.archive.mms.IncomingMessage
import org.tm.archive.mms.OutgoingMessage
import org.tm.archive.profiles.ProfileName
import org.tm.archive.recipients.Recipient
@ -64,7 +65,8 @@ class ConversationItemPreviewer {
attachment()
}
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
body = body,
sentTimeMillis = System.currentTimeMillis(),
@ -73,7 +75,7 @@ class ConversationItemPreviewer {
attachments = PointerAttachment.forPointers(Optional.of(attachments))
)
SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
ThreadUtil.sleep(1)
}
@ -83,7 +85,8 @@ class ConversationItemPreviewer {
attachment()
}
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
body = body,
sentTimeMillis = System.currentTimeMillis(),
@ -92,7 +95,7 @@ class ConversationItemPreviewer {
attachments = PointerAttachment.forPointers(Optional.of(attachments))
)
val insert = SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
val insert = SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
SignalDatabase.attachments.getAttachmentsForMessage(insert.messageId).forEachIndexed { index, attachment ->
// if (index != 1) {
@ -144,6 +147,7 @@ class ConversationItemPreviewer {
1024,
Optional.empty(),
Optional.empty(),
0,
Optional.of("/not-there.jpg"),
false,
false,

View file

@ -8,7 +8,7 @@ package org.tm.archive.conversation.v2.items
import android.net.Uri
import android.view.View
import androidx.lifecycle.Observer
import androidx.test.platform.app.InstrumentationRegistry
import io.mockk.mockk
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
@ -29,7 +29,6 @@ import org.tm.archive.groups.GroupId
import org.tm.archive.groups.GroupMigrationMembershipChange
import org.tm.archive.linkpreview.LinkPreview
import org.tm.archive.mediapreview.MediaIntentFactory
import org.tm.archive.mms.GlideApp
import org.tm.archive.mms.GlideRequests
import org.tm.archive.recipients.Recipient
import org.tm.archive.recipients.RecipientId
@ -48,7 +47,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(),
isGroupThread = false,
adapterPosition = 5
@ -70,7 +68,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.END
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
@ -92,7 +89,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.START
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(prev),
isGroupThread = false,
adapterPosition = 5
@ -116,7 +112,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.MIDDLE
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
@ -138,7 +133,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
@ -160,7 +154,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(prev),
isGroupThread = false,
adapterPosition = 5
@ -184,7 +177,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
@ -211,13 +203,14 @@ class V2ConversationItemShapeTest {
private val colorizer = Colorizer()
override val displayMode: ConversationItemDisplayMode = ConversationItemDisplayMode.STANDARD
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 = GlideApp.with(InstrumentationRegistry.getInstrumentation().context)
override val glideRequests: GlideRequests = mockk()
override val isParentInScroll: Boolean = false
override fun onStartExpirationTimeout(messageRecord: MessageRecord) = Unit

View file

@ -61,8 +61,8 @@ class AttachmentTableTest {
false
)
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA)
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA)
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA_FILE)
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA_FILE)
assertNotEquals(attachment1Info, attachment2Info)
}
@ -89,8 +89,8 @@ class AttachmentTableTest {
true
)
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA)
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA)
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA_FILE)
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA_FILE)
assertNotEquals(attachment1Info, attachment2Info)
}
@ -124,9 +124,9 @@ class AttachmentTableTest {
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)!!
val previousInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(previousDatabaseAttachmentId, AttachmentTable.DATA_FILE)!!
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
assertNotEquals(standardInfo, highInfo)
standardInfo.file assertIs previousInfo.file
@ -158,9 +158,9 @@ class AttachmentTableTest {
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)!!
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
val secondHighInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(secondHighDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
highInfo.file assertIsNot standardInfo.file
secondHighInfo.file assertIs highInfo.file

View file

@ -214,6 +214,175 @@ class CallTableTest {
assertEquals(CallTable.Event.JOINED, acceptedCall?.event)
}
@Test
fun givenAnOutgoingRingCall_whenIAcceptedOutgoingGroupCall_thenIExpectOutgoingRing() {
val callId = 1L
SignalDatabase.calls.insertAcceptedGroupCall(
callId = callId,
recipientId = groupRecipientId,
direction = CallTable.Direction.OUTGOING,
timestamp = 1
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.OUTGOING_RING, call?.event)
SignalDatabase.calls.acceptOutgoingGroupCall(
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.OUTGOING_RING, acceptedCall?.event)
}
@Test
fun givenARingingCall_whenIAcceptedOutgoingGroupCall_thenIExpectAccepted() {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.RINGING, call?.event)
SignalDatabase.calls.acceptOutgoingGroupCall(
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@Test
fun givenAMissedCall_whenIAcceptedOutgoingGroupCall_thenIExpectAccepted() {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
SignalDatabase.calls.acceptOutgoingGroupCall(
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@Test
fun givenADeclinedCall_whenIAcceptedOutgoingGroupCall_thenIExpectAccepted() {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
SignalDatabase.calls.acceptOutgoingGroupCall(
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@Test
fun givenAnAcceptedCall_whenIAcceptedOutgoingGroupCall_thenIExpectAccepted() {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.RINGING, call?.event)
SignalDatabase.calls.acceptIncomingGroupCall(
call!!
)
SignalDatabase.calls.acceptOutgoingGroupCall(
SignalDatabase.calls.getCallById(callId, groupRecipientId)!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@Test
fun givenAGenericGroupCall_whenIAcceptedOutgoingGroupCall_thenIExpectOutgoingRing() {
val era = "aaa"
val callId = CallId.fromEra(era).longValue()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = System.currentTimeMillis(),
peekGroupCallEraId = "aaa",
peekJoinedUuids = emptyList(),
isCallFull = false
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
SignalDatabase.calls.acceptOutgoingGroupCall(
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.OUTGOING_RING, acceptedCall?.event)
}
@Test
fun givenAJoinedGroupCall_whenIAcceptedOutgoingGroupCall_thenIExpectOutgoingRing() {
val era = "aaa"
val callId = CallId.fromEra(era).longValue()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = System.currentTimeMillis(),
peekGroupCallEraId = "aaa",
peekJoinedUuids = emptyList(),
isCallFull = false
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
SignalDatabase.calls.acceptIncomingGroupCall(
call!!
)
SignalDatabase.calls.acceptOutgoingGroupCall(SignalDatabase.calls.getCallById(callId, groupRecipientId)!!)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.OUTGOING_RING, acceptedCall?.event)
}
@Test
fun givenNoPriorCallEvent_whenIReceiveAGroupCallUpdateMessage_thenIExpectAGenericGroupCall() {
val era = "aaa"

View file

@ -293,22 +293,22 @@ class GroupTableTest {
private fun insertPushGroup(
members: List<DecryptedMember> = listOf(
DecryptedMember.newBuilder()
.setAciBytes(harness.self.requireAci().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
DecryptedMember.Builder()
.aciBytes(harness.self.requireAci().toByteString())
.joinedAtRevision(0)
.role(Member.Role.DEFAULT)
.build(),
DecryptedMember.newBuilder()
.setAciBytes(Recipient.resolved(harness.others[0]).requireAci().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
DecryptedMember.Builder()
.aciBytes(Recipient.resolved(harness.others[0]).requireAci().toByteString())
.joinedAtRevision(0)
.role(Member.Role.DEFAULT)
.build()
)
): GroupId {
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val decryptedGroupState = DecryptedGroup.newBuilder()
.addAllMembers(members)
.setRevision(0)
val decryptedGroupState = DecryptedGroup.Builder()
.members(members)
.revision(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.newBuilder()
.setAciBytes(harness.self.requireAci().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
val selfMember: DecryptedMember = DecryptedMember.Builder()
.aciBytes(harness.self.requireAci().toByteString())
.joinedAtRevision(0)
.role(Member.Role.DEFAULT)
.build()
val otherMembers: List<DecryptedMember> = others.map { id ->
DecryptedMember.newBuilder()
.setAciBytes(Recipient.resolved(id).requireAci().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
DecryptedMember.Builder()
.aciBytes(Recipient.resolved(id).requireAci().toByteString())
.joinedAtRevision(0)
.role(Member.Role.DEFAULT)
.build()
}
val decryptedGroupState = DecryptedGroup.newBuilder()
.addAllMembers(listOf(selfMember) + otherMembers)
.setRevision(0)
val decryptedGroupState = DecryptedGroup.Builder()
.members(listOf(selfMember) + otherMembers)
.revision(0)
.build()
return groupTable.create(groupMasterKey, decryptedGroupState)!!

View file

@ -0,0 +1,288 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.tm.archive.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.tm.archive.crash.CrashConfig
import org.tm.archive.dependencies.ApplicationDependencies
import org.tm.archive.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
}
}
}
}

View file

@ -48,7 +48,7 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val result = mms.setOutgoingGiftsRevealed(listOf(messageId))
@ -62,7 +62,7 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
mms.setOutgoingGiftsRevealed(listOf(messageId))
@ -76,13 +76,13 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val result = mms.setOutgoingGiftsRevealed(listOf(messageId))
@ -96,13 +96,13 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val result = mms.setOutgoingGiftsRevealed(listOf(messageId, messageId2))
@ -115,13 +115,13 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
MmsHelper.insert(
@ -140,13 +140,13 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val messageId3 = MmsHelper.insert(
@ -165,13 +165,13 @@ class MessageTableTest_gifts {
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val messageId3 = MmsHelper.insert(

View file

@ -3,7 +3,7 @@ package org.tm.archive.database
import org.tm.archive.database.model.ParentStoryId
import org.tm.archive.database.model.StoryType
import org.tm.archive.database.model.databaseprotos.GiftBadge
import org.tm.archive.mms.IncomingMediaMessage
import org.tm.archive.mms.IncomingMessage
import org.tm.archive.mms.OutgoingMessage
import org.tm.archive.recipients.Recipient
import java.util.Optional
@ -55,9 +55,9 @@ object MmsHelper {
}
fun insert(
message: IncomingMediaMessage,
message: IncomingMessage,
threadId: Long
): Optional<MessageTable.InsertResult> {
return SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, threadId)
return SignalDatabase.messages.insertMessageInbox(message, threadId)
}
}

View file

@ -13,7 +13,7 @@ import org.tm.archive.database.model.DistributionListId
import org.tm.archive.database.model.ParentStoryId
import org.tm.archive.database.model.StoryType
import org.tm.archive.keyvalue.SignalStore
import org.tm.archive.mms.IncomingMediaMessage
import org.tm.archive.mms.IncomingMessage
import org.tm.archive.recipients.Recipient
import org.tm.archive.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
@ -73,7 +73,8 @@ class MmsTableTest_stories {
)
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = sender,
sentTimeMillis = 2,
serverTimeMillis = 2,
@ -95,7 +96,8 @@ class MmsTableTest_stories {
// GIVEN
val sender = recipients[0]
val messageId = MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = sender,
sentTimeMillis = 2,
serverTimeMillis = 2,
@ -122,7 +124,8 @@ class MmsTableTest_stories {
// GIVEN
val messageIds = recipients.take(5).map {
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = it,
sentTimeMillis = 2,
serverTimeMillis = 2,
@ -154,7 +157,8 @@ class MmsTableTest_stories {
val unviewedIds: List<Long> = (0 until 5).map {
Thread.sleep(5)
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = recipients[it],
sentTimeMillis = System.currentTimeMillis(),
serverTimeMillis = 2,
@ -168,7 +172,8 @@ class MmsTableTest_stories {
val viewedIds: List<Long> = (0 until 5).map {
Thread.sleep(5)
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = recipients[it],
sentTimeMillis = System.currentTimeMillis(),
serverTimeMillis = 2,
@ -213,7 +218,8 @@ class MmsTableTest_stories {
fun givenNoOutgoingStories_whenICheckIsOutgoingStoryAlreadyInDatabase_thenIExpectFalse() {
// GIVEN
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = recipients[0],
sentTimeMillis = 200,
serverTimeMillis = 2,
@ -321,7 +327,8 @@ class MmsTableTest_stories {
)
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = myStory.id,
sentTimeMillis = 201,
serverTimeMillis = 201,

View file

@ -38,8 +38,8 @@ class RecipientTableTest_applyStorageSyncContactUpdate {
val newProto = oldRecord
.toProto()
.toBuilder()
.setIdentityState(ContactRecord.IdentityState.DEFAULT)
.newBuilder()
.identityState(ContactRecord.IdentityState.DEFAULT)
.build()
val newRecord = SignalContactRecord(oldRecord.id, newProto)

View file

@ -14,8 +14,10 @@ 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
@ -34,12 +36,10 @@ import org.tm.archive.database.model.databaseprotos.ThreadMergeEvent
import org.tm.archive.dependencies.ApplicationDependencies
import org.tm.archive.groups.GroupId
import org.tm.archive.keyvalue.SignalStore
import org.tm.archive.mms.IncomingMediaMessage
import org.tm.archive.mms.IncomingMessage
import org.tm.archive.notifications.profiles.NotificationProfile
import org.tm.archive.recipients.Recipient
import org.tm.archive.recipients.RecipientId
import org.tm.archive.sms.IncomingTextMessage
import org.tm.archive.util.Base64
import org.tm.archive.util.FeatureFlags
import org.tm.archive.util.FeatureFlagsAccessor
import org.tm.archive.util.Util
@ -142,6 +142,30 @@ 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)
@ -502,6 +526,18 @@ class RecipientTableTest_getAndPossiblyMerge {
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)
@ -789,9 +825,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.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 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 threadIdAci: Long = SignalDatabase.threads.getThreadIdFor(recipientIdAci)!!
val threadIdE164: Long = SignalDatabase.threads.getThreadIdFor(recipientIdE164)!!
@ -911,12 +947,30 @@ 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()): IncomingTextMessage {
return IncomingTextMessage(sender, 1, time, time, time, body, groupId, 0, true, null)
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 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 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 identityKey(value: Byte): IdentityKey {
@ -1228,7 +1282,7 @@ class RecipientTableTest_getAndPossiblyMerge {
.use { cursor: Cursor ->
if (cursor.moveToFirst()) {
val bytes = Base64.decode(cursor.requireNonNullString(MessageTable.BODY))
ThreadMergeEvent.parseFrom(bytes)
ThreadMergeEvent.ADAPTER.decode(bytes)
} else {
null
}
@ -1246,7 +1300,7 @@ class RecipientTableTest_getAndPossiblyMerge {
.use { cursor: Cursor ->
if (cursor.moveToFirst()) {
val bytes = Base64.decode(cursor.requireNonNullString(MessageTable.BODY))
SessionSwitchoverEvent.parseFrom(bytes)
SessionSwitchoverEvent.ADAPTER.decode(bytes)
} else {
null
}

View file

@ -18,12 +18,10 @@ import org.tm.archive.database.model.databaseprotos.groupChange
import org.tm.archive.database.model.databaseprotos.groupContext
import org.tm.archive.groups.GroupId
import org.tm.archive.keyvalue.SignalStore
import org.tm.archive.mms.IncomingMessage
import org.tm.archive.recipients.RecipientId
import org.tm.archive.sms.IncomingGroupUpdateMessage
import org.tm.archive.sms.IncomingTextMessage
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.Optional
import java.util.UUID
@Suppress("ClassName", "TestFunctionName")
@ -272,13 +270,28 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
assertThat("latest message should be deleted", sms.getMessageRecordOrNull(latestMessage.messageId), nullValue())
}
private fun smsMessage(sender: RecipientId, body: String? = ""): IncomingTextMessage {
private fun smsMessage(sender: RecipientId, body: String? = ""): IncomingMessage {
wallClock++
return IncomingTextMessage(sender, 1, wallClock, wallClock, wallClock, body, Optional.of(groupId), 0, true, null)
return IncomingMessage(
type = MessageType.NORMAL,
from = sender,
sentTimeMillis = wallClock,
serverTimeMillis = wallClock,
receivedTimeMillis = wallClock,
body = body,
groupId = groupId,
isUnidentified = true
)
}
private fun groupUpdateMessage(sender: RecipientId, groupContext: DecryptedGroupV2Context): IncomingGroupUpdateMessage {
return IncomingGroupUpdateMessage(smsMessage(sender, null), groupContext)
private fun groupUpdateMessage(sender: RecipientId, groupContext: DecryptedGroupV2Context): IncomingMessage {
wallClock++
return IncomingMessage.groupUpdate(
from = sender,
timestamp = wallClock,
groupId = groupId,
groupContext = groupContext
)
}
companion object {

View file

@ -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.tm.archive.BuildConfig
import org.tm.archive.KbsEnclave
import org.tm.archive.push.SignalServiceNetworkAccess
import org.tm.archive.push.SignalServiceTrustStore
import org.tm.archive.recipients.LiveRecipientCache
@ -23,32 +23,25 @@ import org.tm.archive.testing.Get
import org.tm.archive.testing.Verb
import org.tm.archive.testing.runSync
import org.tm.archive.testing.success
import org.tm.archive.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] and
* [KeyBackupService].
* Handles setting up a mock web server for API calls, and provides mockable versions of [SignalServiceNetworkAccess].
*/
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 {
@ -80,7 +73,6 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
0 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
2 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT))
),
signalKeyBackupServiceUrls = arrayOf(SignalKeyBackupServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalStorageUrls = arrayOf(SignalStorageUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalCdsiUrls = arrayOf(SignalCdsiUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalSvr2Urls = arrayOf(SignalSvr2Url(baseUrl, serviceTrustStore, "localhost", ConnectionSpec.CLEARTEXT)),
@ -88,7 +80,8 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
dns = Optional.of(SignalServiceNetworkAccess.DNS),
signalProxy = Optional.empty(),
zkGroupServerPublicParams = Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS),
genericServerPublicParams = Base64.decode(BuildConfig.GENERIC_SERVER_PUBLIC_PARAMS)
genericServerPublicParams = Base64.decode(BuildConfig.GENERIC_SERVER_PUBLIC_PARAMS),
backupServerPublicParams = Base64.decode(BuildConfig.BACKUP_SERVER_PUBLIC_PARAMS)
)
serviceNetworkAccessMock = mock {
@ -97,8 +90,6 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
on { uncensoredConfiguration } doReturn uncensoredConfiguration
}
keyBackupService = mock()
recipientCache = LiveRecipientCache(application) { r -> r.run() }
}
@ -106,10 +97,6 @@ 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
}

View file

@ -1,181 +0,0 @@
package org.tm.archive.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.tm.archive.database.SignalDatabase
import org.tm.archive.dependencies.InstrumentationApplicationDependencyProvider
import org.tm.archive.keyvalue.SignalStore
import org.tm.archive.testing.Delete
import org.tm.archive.testing.Get
import org.tm.archive.testing.Put
import org.tm.archive.testing.SignalActivityRule
import org.tm.archive.testing.failure
import org.tm.archive.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() {
// GIVEN
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Delete("/v1/accounts/username_hash") { MockResponse().success() }
)
// 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)
}
}

View file

@ -22,8 +22,9 @@ import org.tm.archive.testing.MessageContentFuzzer
import org.tm.archive.testing.SignalActivityRule
import org.tm.archive.testing.assertIs
import org.tm.archive.util.MessageTableTestUtils
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.EditMessage
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.EditMessage
import org.whispersystems.signalservice.internal.push.SyncMessage
import kotlin.time.Duration.Companion.seconds
@RunWith(AndroidJUnit4::class)
@ -38,7 +39,6 @@ class EditMessageSyncProcessorTest {
)
private val IGNORE_ATTACHMENT_COLUMNS = listOf(
AttachmentTable.UNIQUE_ID,
AttachmentTable.TRANSFER_FILE
)
}
@ -67,16 +67,17 @@ class EditMessageSyncProcessorTest {
val content = MessageContentFuzzer.fuzzTextMessage()
val metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, toRecipient.id)
val syncContent = SignalServiceProtos.Content.newBuilder().setSyncMessage(
SignalServiceProtos.SyncMessage.newBuilder().setSent(
SignalServiceProtos.SyncMessage.Sent.newBuilder()
.setDestinationServiceId(metadata.destinationServiceId.toString())
.setTimestamp(originalTimestamp)
.setExpirationStartTimestamp(originalTimestamp)
.setMessage(content.dataMessage)
)
val syncContent = Content.Builder().syncMessage(
SyncMessage.Builder().sent(
SyncMessage.Sent.Builder()
.destinationServiceId(metadata.destinationServiceId.toString())
.timestamp(originalTimestamp)
.expirationStartTimestamp(originalTimestamp)
.message(content.dataMessage)
.build()
).build()
).build()
SignalDatabase.recipients.setExpireMessages(toRecipient.id, content.dataMessage.expireTimer)
SignalDatabase.recipients.setExpireMessages(toRecipient.id, content.dataMessage?.expireTimer ?: 0)
val syncTextMessage = TestMessage(
envelope = MessageContentFuzzer.envelope(originalTimestamp),
content = syncContent,
@ -86,18 +87,20 @@ class EditMessageSyncProcessorTest {
val editTimestamp = originalTimestamp + 200
val editedContent = MessageContentFuzzer.fuzzTextMessage()
val editSyncContent = SignalServiceProtos.Content.newBuilder().setSyncMessage(
SignalServiceProtos.SyncMessage.newBuilder().setSent(
SignalServiceProtos.SyncMessage.Sent.newBuilder()
.setDestinationServiceId(metadata.destinationServiceId.toString())
.setTimestamp(editTimestamp)
.setExpirationStartTimestamp(editTimestamp)
.setEditMessage(
EditMessage.newBuilder()
.setDataMessage(editedContent.dataMessage)
.setTargetSentTimestamp(originalTimestamp)
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()
)
)
.build()
).build()
).build()
val syncEditMessage = TestMessage(
@ -109,38 +112,38 @@ class EditMessageSyncProcessorTest {
testResult.runSync(listOf(syncTextMessage, syncEditMessage))
SignalDatabase.recipients.setExpireMessages(toRecipient.id, content.dataMessage.expireTimer / 1000)
SignalDatabase.recipients.setExpireMessages(toRecipient.id, (content.dataMessage?.expireTimer ?: 0) / 1000)
val originalTextMessage = OutgoingMessage(
threadRecipient = toRecipient,
sentTimeMillis = originalTimestamp,
body = content.dataMessage.body,
expiresIn = content.dataMessage.expireTimer.seconds.inWholeMilliseconds,
body = content.dataMessage?.body ?: "",
expiresIn = content.dataMessage?.expireTimer?.seconds?.inWholeMilliseconds ?: 0,
isUrgent = true,
isSecure = true,
bodyRanges = content.dataMessage.bodyRangesList.toBodyRangeList()
bodyRanges = content.dataMessage?.bodyRanges.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) {
if ((content.dataMessage?.expireTimer ?: 0) > 0) {
SignalDatabase.messages.markExpireStarted(originalMessageId, originalTimestamp)
}
val editMessage = OutgoingMessage(
threadRecipient = toRecipient,
sentTimeMillis = editTimestamp,
body = editedContent.dataMessage.body,
expiresIn = content.dataMessage.expireTimer.seconds.inWholeMilliseconds,
body = editedContent.dataMessage?.body ?: "",
expiresIn = content.dataMessage?.expireTimer?.seconds?.inWholeMilliseconds ?: 0,
isUrgent = true,
isSecure = true,
bodyRanges = editedContent.dataMessage.bodyRangesList.toBodyRangeList(),
bodyRanges = editedContent.dataMessage?.bodyRanges.toBodyRangeList(),
messageToEdit = originalMessageId
)
val editMessageId = SignalDatabase.messages.insertMessageOutbox(editMessage, threadId, false, null)
SignalDatabase.messages.markAsSent(editMessageId, true)
if (content.dataMessage.expireTimer > 0) {
if ((content.dataMessage?.expireTimer ?: 0) > 0) {
SignalDatabase.messages.markExpireStarted(editMessageId, originalTimestamp)
}
testResult.collectLocal()
@ -167,7 +170,7 @@ class EditMessageSyncProcessorTest {
fun runSync(messages: List<TestMessage>) {
messages.forEach { (envelope, content, metadata, serverDeliveredTimestamp) ->
if (content.hasSyncMessage()) {
if (content.syncMessage != null) {
processorV2.process(
envelope,
content,

View file

@ -1,13 +1,13 @@
package org.tm.archive.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.tm.archive.database.GroupReceiptTable
import org.tm.archive.database.SignalDatabase
import org.tm.archive.database.model.toProtoByteString
import org.tm.archive.messages.SignalServiceProtoUtil.buildWith
import org.tm.archive.testing.GroupTestingUtils
import org.tm.archive.testing.GroupTestingUtils.asMember
@ -15,8 +15,8 @@ import org.tm.archive.testing.MessageContentFuzzer
import org.tm.archive.testing.SignalActivityRule
import org.tm.archive.testing.assertIs
import org.tm.archive.util.MessageTableTestUtils
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2
import org.whispersystems.signalservice.internal.push.DataMessage
import org.whispersystems.signalservice.internal.push.GroupContextV2
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
@ -41,9 +41,9 @@ 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.newBuilder().setRevision(0).setMasterKey(masterKey.serialize().toProtoByteString()).build()
val groupContextV2 = GroupContextV2.Builder().revision(0).masterKey(masterKey.serialize().toByteString()).build()
val initialTextMessage = DataMessage.newBuilder().buildWith {
val initialTextMessage = DataMessage.Builder().buildWith {
body = MessageContentFuzzer.string()
groupV2 = groupContextV2
timestamp = envelopeTimestamp

View file

@ -6,7 +6,6 @@ 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
@ -26,7 +25,7 @@ import org.tm.archive.testing.Entry
import org.tm.archive.testing.FakeClientHelpers
import org.tm.archive.testing.SignalActivityRule
import org.tm.archive.testing.awaitFor
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.websocket.WebSocketMessage
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage
import java.util.regex.Pattern
@ -93,7 +92,7 @@ class MessageProcessingPerformanceTest {
val messageCount = 100
val envelopes = generateInboundEnvelopes(bobClient, messageCount)
val firstTimestamp = envelopes.first().timestamp
val lastTimestamp = envelopes.last().timestamp
val lastTimestamp = envelopes.last().timestamp ?: 0
// Inject the envelopes into the websocket
Thread {
@ -190,7 +189,7 @@ class MessageProcessingPerformanceTest {
path = "/api/v1/message",
id = Random(System.currentTimeMillis()).nextLong(),
headers = listOf("X-Signal-Timestamp: ${this.timestamp}"),
body = this.toByteArray().toByteString()
body = this.encodeByteString()
)
).encodeByteString()
}

View file

@ -1,11 +1,12 @@
package org.tm.archive.messages
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.Envelope
data class TestMessage(
val envelope: SignalServiceProtos.Envelope,
val content: SignalServiceProtos.Content,
val envelope: Envelope,
val content: Content,
val metadata: EnvelopeMetadata,
val serverDeliveredTimestamp: Long
)

View file

@ -5,7 +5,8 @@ import org.signal.core.util.logging.Log
import org.tm.archive.testing.LogPredicate
import org.tm.archive.util.SignalLocalMetrics
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.Envelope
class TimingMessageContentProcessor(context: Context) : MessageContentProcessor(context) {
companion object {
@ -19,9 +20,9 @@ class TimingMessageContentProcessor(context: Context) : MessageContentProcessor(
fun endTag(timestamp: Long) = "$timestamp end"
}
override fun process(envelope: SignalServiceProtos.Envelope, content: SignalServiceProtos.Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long, processingEarlyContent: Boolean, localMetric: SignalLocalMetrics.MessageReceive?) {
Log.d(TAG, startTag(envelope.timestamp))
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))
Log.d(TAG, endTag(envelope.timestamp!!))
}
}

View file

@ -33,6 +33,7 @@ import org.tm.archive.testing.SignalActivityRule
import org.tm.archive.testing.assertIsNotNull
import org.tm.archive.testing.assertIsNull
import org.tm.archive.testing.success
import org.whispersystems.signalservice.api.util.Usernames
import org.whispersystems.signalservice.internal.push.ReserveUsernameResponse
import java.util.concurrent.TimeUnit
@ -96,7 +97,7 @@ class UsernameEditFragmentTest {
fun testNicknameUpdateHappyPath() {
val nickname = "Spiderman"
val discriminator = "4578"
val username = "$nickname${UsernameState.DELIMITER}$discriminator"
val username = "$nickname${Usernames.DELIMITER}$discriminator"
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Put("/v1/accounts/username/reserved") {

View file

@ -6,12 +6,12 @@ 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.tm.archive.database.RecipientTable
import org.tm.archive.database.SignalDatabase
import org.tm.archive.keyvalue.SignalStore
import org.tm.archive.recipients.RecipientId
import org.tm.archive.util.Base64
import org.tm.archive.util.FeatureFlags
import org.tm.archive.util.FeatureFlagsAccessor
import org.whispersystems.signalservice.api.push.ServiceId.ACI
@ -38,15 +38,21 @@ class ContactRecordProcessorTest {
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
setStorageId(originalId, STORAGE_ID_A)
val remote1 = buildRecord(STORAGE_ID_B) {
setAci(ACI_A.toString())
setUnregisteredAtTimestamp(100)
}
val remote1 = buildRecord(
STORAGE_ID_B,
ContactRecord(
aci = ACI_A.toString(),
unregisteredAtTimestamp = 100
)
)
val remote2 = buildRecord(STORAGE_ID_C) {
setPni(PNI_A.toString())
setE164(E164_A)
}
val remote2 = buildRecord(
STORAGE_ID_C,
ContactRecord(
pni = PNI_A.toString(),
e164 = E164_A
)
)
// WHEN
val subject = ContactRecordProcessor()
@ -69,16 +75,22 @@ class ContactRecordProcessorTest {
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
setStorageId(originalId, STORAGE_ID_A)
val remote1 = buildRecord(STORAGE_ID_B) {
setAci(ACI_A.toString())
setUnregisteredAtTimestamp(0)
}
val remote1 = buildRecord(
STORAGE_ID_B,
ContactRecord(
aci = ACI_A.toString(),
unregisteredAtTimestamp = 0
)
)
val remote2 = buildRecord(STORAGE_ID_C) {
setAci(PNI_A.toString())
setPni(PNI_A.toString())
setE164(E164_A)
}
val remote2 = buildRecord(
STORAGE_ID_C,
ContactRecord(
aci = PNI_A.toString(),
pni = PNI_A.toString(),
e164 = E164_A
)
)
// WHEN
val subject = ContactRecordProcessor()
@ -94,14 +106,14 @@ class ContactRecordProcessorTest {
assertEquals(byAci, byE164)
}
private fun buildRecord(id: StorageId, applyParams: ContactRecord.Builder.() -> ContactRecord.Builder): SignalContactRecord {
return SignalContactRecord(id, ContactRecord.getDefaultInstance().toBuilder().applyParams().build())
private fun buildRecord(id: StorageId, record: ContactRecord): SignalContactRecord {
return SignalContactRecord(id, record)
}
private fun setStorageId(recipientId: RecipientId, storageId: StorageId) {
SignalDatabase.rawDatabase
.update(RecipientTable.TABLE_NAME)
.values(RecipientTable.STORAGE_SERVICE_ID to Base64.encodeBytes(storageId.raw))
.values(RecipientTable.STORAGE_SERVICE_ID to Base64.encodeWithPadding(storageId.raw))
.where("${RecipientTable.ID} = ?", recipientId)
.run()
}

View file

@ -11,7 +11,7 @@ import org.tm.archive.recipients.Recipient
import org.tm.archive.testing.FakeClientHelpers.toEnvelope
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.push.Envelope
/**
* Welcome to Alice's Client.

View file

@ -31,11 +31,10 @@ 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.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.Envelope
import java.util.Optional
import java.util.UUID
import java.util.concurrent.locks.ReentrantLock
import kotlin.UnsupportedOperationException
/**
* Welcome to Bob's Client.
@ -61,7 +60,7 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
}
/** Inspired by SignalServiceMessageSender#getEncryptedMessage */
fun encrypt(now: Long): SignalServiceProtos.Envelope {
fun encrypt(now: Long): Envelope {
val envelopeContent = FakeClientHelpers.encryptedTextMessage(now)
val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, null)
@ -72,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: SignalServiceProtos.Envelope, serverDeliveredTimestamp: Long) {
fun decrypt(envelope: Envelope, serverDeliveredTimestamp: Long) {
val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, UnidentifiedAccessUtil.getCertificateValidator())
cipher.decrypt(envelope, serverDeliveredTimestamp)
}
@ -166,7 +165,7 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
override fun storeSenderKey(sender: SignalProtocolAddress?, distributionId: UUID?, record: SenderKeyRecord?) = throw UnsupportedOperationException()
override fun loadSenderKey(sender: SignalProtocolAddress?, distributionId: UUID?): SenderKeyRecord = throw UnsupportedOperationException()
override fun archiveSession(address: SignalProtocolAddress?) = throw UnsupportedOperationException()
override fun getAllAddressesWithActiveSessions(addressNames: MutableList<String>?): MutableSet<SignalProtocolAddress> = throw UnsupportedOperationException()
override fun getAllAddressesWithActiveSessions(addressNames: MutableList<String>?): MutableMap<SignalProtocolAddress, SessionRecord> = throw UnsupportedOperationException()
override fun getSenderKeySharedWith(distributionId: DistributionId?): MutableSet<SignalProtocolAddress> = throw UnsupportedOperationException()
override fun markSenderKeySharedWith(distributionId: DistributionId?, addresses: MutableCollection<SignalProtocolAddress>?) = throw UnsupportedOperationException()
override fun clearSenderKeySharedWith(addresses: MutableCollection<SignalProtocolAddress>?) = throw UnsupportedOperationException()

View file

@ -1,5 +1,7 @@
package org.tm.archive.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
@ -9,16 +11,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.tm.archive.database.model.toProtoByteString
import org.tm.archive.messages.SignalServiceProtoUtil.buildWith
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
@ -52,9 +54,9 @@ object FakeClientHelpers {
}
fun encryptedTextMessage(now: Long, message: String = "Test body message"): EnvelopeContent {
val content = SignalServiceProtos.Content.newBuilder().apply {
setDataMessage(
SignalServiceProtos.DataMessage.newBuilder().apply {
val content = Content.Builder().apply {
dataMessage(
DataMessage.Builder().buildWith {
body = message
timestamp = now
}
@ -64,16 +66,16 @@ object FakeClientHelpers {
}
fun OutgoingPushMessage.toEnvelope(timestamp: Long, destination: ServiceId): Envelope {
return Envelope.newBuilder()
.setType(Envelope.Type.valueOf(this.type))
.setSourceDevice(1)
.setTimestamp(timestamp)
.setServerTimestamp(timestamp + 1)
.setDestinationServiceId(destination.toString())
.setServerGuid(UUID.randomUUID().toString())
.setContent(Base64.decode(this.content).toProtoByteString())
.setUrgent(true)
.setStory(false)
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)
.build()
}
}

View file

@ -16,19 +16,19 @@ import kotlin.random.Random
*/
object GroupTestingUtils {
fun member(aci: ACI, revision: Int = 0, role: Member.Role = Member.Role.ADMINISTRATOR): DecryptedMember {
return DecryptedMember.newBuilder()
.setAciBytes(aci.toByteString())
.setJoinedAtRevision(revision)
.setRole(role)
return DecryptedMember.Builder()
.aciBytes(aci.toByteString())
.joinedAtRevision(revision)
.role(role)
.build()
}
fun insertGroup(revision: Int = 0, vararg members: DecryptedMember): TestGroupInfo {
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val decryptedGroupState = DecryptedGroup.newBuilder()
.addAllMembers(members.toList())
.setRevision(revision)
.setTitle(MessageContentFuzzer.string())
val decryptedGroupState = DecryptedGroup.Builder()
.members(members.toList())
.revision(revision)
.title(MessageContentFuzzer.string())
.build()
val groupId = SignalDatabase.groups.create(groupMasterKey, decryptedGroupState)!!

View file

@ -1,21 +1,20 @@
package org.tm.archive.testing
import com.google.protobuf.ByteString
import org.tm.archive.database.model.toProtoByteString
import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.tm.archive.groups.GroupId
import org.tm.archive.messages.SignalServiceProtoUtil.buildWith
import org.tm.archive.messages.TestMessage
import org.tm.archive.recipients.Recipient
import org.tm.archive.recipients.RecipientId
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
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 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 java.util.UUID
import kotlin.random.Random
import kotlin.random.nextInt
@ -35,10 +34,10 @@ object MessageContentFuzzer {
* Create an [Envelope].
*/
fun envelope(timestamp: Long): Envelope {
return Envelope.newBuilder()
.setTimestamp(timestamp)
.setServerTimestamp(timestamp + 5)
.setServerGuidBytes(UuidUtil.toByteString(UUID.randomUUID()))
return Envelope.Builder()
.timestamp(timestamp)
.serverTimestamp(timestamp + 5)
.serverGuid(UUID.randomUUID().toString())
.build()
}
@ -62,20 +61,22 @@ object MessageContentFuzzer {
* - Bold style body ranges
*/
fun fuzzTextMessage(groupContextV2: GroupContextV2? = null): Content {
return Content.newBuilder()
.setDataMessage(
DataMessage.newBuilder().buildWith {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
body = string()
if (random.nextBoolean()) {
expireTimer = random.nextInt(0..28.days.inWholeSeconds.toInt())
}
if (random.nextBoolean()) {
addBodyRanges(
SignalServiceProtos.BodyRange.newBuilder().buildWith {
start = 0
length = 1
style = SignalServiceProtos.BodyRange.Style.BOLD
}
bodyRanges(
listOf(
BodyRange.Builder().buildWith {
start = 0
length = 1
style = BodyRange.Style.BOLD
}
)
)
}
if (groupContextV2 != null) {
@ -95,16 +96,16 @@ object MessageContentFuzzer {
recipientUpdate: Boolean = false
): Content {
return Content
.newBuilder()
.setSyncMessage(
SyncMessage.newBuilder().buildWith {
sent = SyncMessage.Sent.newBuilder().buildWith {
.Builder()
.syncMessage(
SyncMessage.Builder().buildWith {
sent = SyncMessage.Sent.Builder().buildWith {
timestamp = textMessage.timestamp
message = textMessage
isRecipientUpdate = recipientUpdate
addAllUnidentifiedStatus(
unidentifiedStatus(
deliveredTo.map {
SyncMessage.Sent.UnidentifiedDeliveryStatus.newBuilder().buildWith {
SyncMessage.Sent.UnidentifiedDeliveryStatus.Builder().buildWith {
destinationServiceId = Recipient.resolved(it).requireServiceId().toString()
unidentified = true
}
@ -123,9 +124,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.newBuilder()
.setDataMessage(
DataMessage.newBuilder().buildWith {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
if (random.nextBoolean()) {
body = string()
}
@ -133,28 +134,28 @@ object MessageContentFuzzer {
if (random.nextBoolean() && quoteAble.isNotEmpty()) {
body = string()
val quoted = quoteAble.random(random)
quote = DataMessage.Quote.newBuilder().buildWith {
quote = DataMessage.Quote.Builder().buildWith {
id = quoted.envelope.timestamp
authorAci = quoted.metadata.sourceServiceId.toString()
text = quoted.content.dataMessage.body
addAllAttachments(quoted.content.dataMessage.attachmentsList)
addAllBodyRanges(quoted.content.dataMessage.bodyRangesList)
text = quoted.content.dataMessage?.body
attachments(quoted.content.dataMessage?.attachments ?: emptyList())
bodyRanges(quoted.content.dataMessage?.bodyRanges ?: emptyList())
type = DataMessage.Quote.Type.NORMAL
}
}
if (random.nextFloat() < 0.1 && quoteAble.isNotEmpty()) {
val quoted = quoteAble.random(random)
quote = DataMessage.Quote.newBuilder().buildWith {
id = random.nextLong(quoted.envelope.timestamp - 1000000, quoted.envelope.timestamp)
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
text = quoted.content.dataMessage?.body
}
}
if (random.nextFloat() < 0.25) {
val total = random.nextInt(1, 2)
(0..total).forEach { _ -> addAttachments(attachmentPointer()) }
attachments((0..total).map { attachmentPointer() })
}
}
)
@ -166,12 +167,12 @@ object MessageContentFuzzer {
* - A reaction to a prior message
*/
fun fuzzMediaMessageNoContent(previousMessages: List<TestMessage> = emptyList()): Content {
return Content.newBuilder()
.setDataMessage(
DataMessage.newBuilder().buildWith {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
if (random.nextFloat() < 0.25) {
val reactTo = previousMessages.random(random)
reaction = DataMessage.Reaction.newBuilder().buildWith {
reaction = DataMessage.Reaction.Builder().buildWith {
emoji = emojis.random(random)
remove = false
targetAuthorAci = reactTo.metadata.sourceServiceId.toString()
@ -187,15 +188,15 @@ object MessageContentFuzzer {
* - A sticker
*/
fun fuzzMediaMessageNoText(previousMessages: List<TestMessage> = emptyList()): Content {
return Content.newBuilder()
.setDataMessage(
DataMessage.newBuilder().buildWith {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
if (random.nextFloat() < 0.9) {
sticker = DataMessage.Sticker.newBuilder().buildWith {
sticker = DataMessage.Sticker.Builder().buildWith {
packId = byteString(length = 24)
packKey = byteString(length = 128)
stickerId = random.nextInt()
data = attachmentPointer()
data_ = attachmentPointer()
emoji = emojis.random(random)
}
}
@ -223,14 +224,14 @@ object MessageContentFuzzer {
* Generate a random [ByteString].
*/
fun byteString(length: Int = 512): ByteString {
return random.nextBytes(length).toProtoByteString()
return random.nextBytes(length).toByteString()
}
/**
* Generate a random [AttachmentPointer].
*/
fun attachmentPointer(): AttachmentPointer {
return AttachmentPointer.newBuilder().run {
return AttachmentPointer.Builder().run {
cdnKey = string()
contentType = mediaTypes.random(random)
key = byteString()

View file

@ -1,22 +1,12 @@
package org.tm.archive.testing
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
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.tm.archive.crypto.PreKeyUtil
import org.tm.archive.dependencies.ApplicationDependencies
import org.tm.archive.keyvalue.SignalStore
import org.tm.archive.test.BuildConfig
import org.whispersystems.signalservice.api.KeyBackupService
import org.whispersystems.signalservice.api.SvrPinData
import org.whispersystems.signalservice.api.kbs.MasterKey
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity
@ -78,18 +68,6 @@ object MockProvider {
}
}
fun mockGetRegistrationLockStringFlow() {
val session: KeyBackupService.RestoreSession = object : KeyBackupService.RestoreSession {
override fun hashSalt(): ByteArray = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8")
override fun restorePin(hashedPin: PinHash?): SvrPinData = SvrPinData(MasterKey.createNew(SecureRandom()), null)
}
val kbsService = ApplicationDependencies.getKeyBackupService(BuildConfig.KBS_ENCLAVE)
kbsService.stub {
on { newRegistrationSession(anyOrNull(), anyOrNull()) } 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())

View file

@ -1,70 +0,0 @@
package org.tm.archive.testing
import com.google.protobuf.ByteString
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.whispersystems.signalservice.api.push.ServiceId.ACI
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(ACI.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()
.setAuthorAci(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()
}
}
}

View file

@ -38,10 +38,8 @@ 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}

View file

@ -2,9 +2,10 @@ package org.signal.benchmark.setup
import org.tm.archive.attachments.PointerAttachment
import org.tm.archive.database.AttachmentTable
import org.tm.archive.database.MessageType
import org.tm.archive.database.SignalDatabase
import org.tm.archive.database.TestDbUtils
import org.tm.archive.mms.IncomingMediaMessage
import org.tm.archive.mms.IncomingMessage
import org.tm.archive.mms.OutgoingMessage
import org.tm.archive.mms.QuoteModel
import org.tm.archive.recipients.Recipient
@ -65,7 +66,8 @@ object TestMessages {
return insert
}
fun insertIncomingTextMessage(other: Recipient, body: String, timestamp: Long? = null) {
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
body = body,
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
@ -73,10 +75,11 @@ object TestMessages {
receivedTimeMillis = timestamp ?: System.currentTimeMillis()
)
SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get().messageId
SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get().messageId
}
fun insertIncomingQuoteTextMessage(other: Recipient, body: String, quote: QuoteModel, timestamp: Long?) {
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
body = body,
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
@ -90,28 +93,30 @@ object TestMessages {
val attachments: List<SignalServiceAttachmentPointer> = (0 until attachmentCount).map {
imageAttachment()
}
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
serverTimeMillis = timestamp ?: System.currentTimeMillis(),
receivedTimeMillis = timestamp ?: System.currentTimeMillis(),
attachments = PointerAttachment.forPointers(Optional.of(attachments))
)
return insertIncomingMediaMessage(recipient = other, message = message, failed = failed)
return insertIncomingMessage(recipient = other, message = message, failed = failed)
}
fun insertIncomingVoiceMessage(other: Recipient, timestamp: Long? = null): Long {
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
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 insertIncomingMediaMessage(recipient = other, message = message, failed = false)
return insertIncomingMessage(recipient = other, message = message, failed = false)
}
private fun insertIncomingMediaMessage(recipient: Recipient, message: IncomingMediaMessage, failed: Boolean = false): Long {
private fun insertIncomingMessage(recipient: Recipient, message: IncomingMessage, failed: Boolean = false): Long {
val id = insertIncomingMessage(recipient = recipient, message = message)
if (failed) {
setMessageMediaFailed(id)
@ -122,8 +127,8 @@ object TestMessages {
return id
}
private fun insertIncomingMessage(recipient: Recipient, message: IncomingMediaMessage): Long {
return SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(recipient)).get().messageId
private fun insertIncomingMessage(recipient: Recipient, message: IncomingMessage): Long {
return SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(recipient)).get().messageId
}
private fun setMessageMediaFailed(messageId: Long) {
@ -149,6 +154,7 @@ object TestMessages {
1024,
Optional.empty(),
Optional.empty(),
0,
Optional.of("/not-there.jpg"),
false,
false,
@ -171,6 +177,7 @@ object TestMessages {
1024,
Optional.empty(),
Optional.empty(),
0,
Optional.of("/not-there.aac"),
true,
false,

View file

@ -10,7 +10,7 @@ import org.tm.archive.conversation.v2.data.ConversationElementKey
import org.tm.archive.conversation.v2.data.IncomingTextOnly
import org.tm.archive.conversation.v2.data.OutgoingTextOnly
import org.tm.archive.database.MessageTypes
import org.tm.archive.database.model.MediaMmsMessageRecord
import org.tm.archive.database.model.MmsMessageRecord
import org.tm.archive.database.model.StoryType
import org.tm.archive.dependencies.ApplicationDependencies
import org.tm.archive.mms.SlideDeck
@ -78,7 +78,7 @@ class ConversationElementGenerator {
val isIncoming = random.nextBoolean()
val record = MediaMmsMessageRecord(
val record = MmsMessageRecord(
messageId,
if (isIncoming) Recipient.UNKNOWN else Recipient.self(),
0,
@ -86,7 +86,7 @@ class ConversationElementGenerator {
now,
now,
now,
1,
true,
1,
testMessage,
SlideDeck(),
@ -97,7 +97,7 @@ class ConversationElementGenerator {
0,
0,
false,
1,
true,
null,
emptyList(),
emptyList(),
@ -106,7 +106,7 @@ class ConversationElementGenerator {
false,
false,
now,
1,
true,
now,
null,
StoryType.NONE,
@ -117,7 +117,8 @@ class ConversationElementGenerator {
-1,
null,
null,
0
0,
false
)
val conversationMessage = ConversationMessageFactory.createWithUnresolvedData(

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,13 @@
package com.google.android.material.bottomsheet
import android.view.View
import android.widget.FrameLayout
import java.lang.ref.WeakReference
/**
* Manually adjust the nested scrolling child for a given [BottomSheetBehavior].
*/
object BottomSheetBehaviorHack {
fun setNestedScrollingChild(behavior: BottomSheetBehavior<FrameLayout>, view: View) {
fun <T : View> setNestedScrollingChild(behavior: BottomSheetBehavior<T>, view: View) {
behavior.nestedScrollingChildRef = WeakReference(view)
}
}

View file

@ -1,57 +0,0 @@
package org.archiver
class ArchiveConstants {
companion object{
const val SIGNAL_ARCHIVE_VERSION = "V1"
const val signalTestUserName = "signal"
const val signalTestPassword = "Aa!123456"
const val signalCurrentPassword = ""/*"Aa123456"*/
const val signalCurrentUser = "qasam"
const val signalTestMobileNumber = "+972520123456"
const val isTestMode = false
// const val signalTestMobileNumber = "+447520619489"
//const val signalTestMobileNumber = "+972520099696" //EnterP
const val integration = "https://integration.telemessage.co.il"
const val integrationKeeper = "https://api-gateway-integration.devops.telemessage.co.il"
const val charlieProduction = "https://rest.telemessage.com"
const val prodKeeper = "https://archive.telemessage.com"
const val ARCHIVE_TYPE_APP_MESSAGE = "Signal message"
const val ARCHIVE_TYPE_SMS = "SMS"
const val ARCHIVE_SUBJECT_CHAT_GROUP = "chat group"
const val ARCHIVE_SUBJECT_FROM_TEXT = "from"
const val ARCHIVE_SUBJECT_TO_TEXT = "to"
const val ARCHIVE_FILE_FOLDER_NAME = "aa_archiver"
const val SIGNAL_ARCHIVE_ATTACHMENT_TEMPLATE_PREFIX = SIGNAL_ARCHIVE_VERSION + "_" + "Signal" + "_"
const val SIGNAL_PART_PATH = "/part/"
const val SIGNAL_STICKER_PATH = "/sticker/"
const val SIGNAL_BLOB_PATH = ".blob"
const val isNeedToSetTeleMessageBackgroundAsDefault = true
const val GENERATE_TOK_NAME = "logfile"
const val GENERATE_TOK_PASS = "enRR8UVVywXYbFkqU#QDPRkO"
const val SHARED_PREFERENCE_SELECTED_BASE_URL_PRODUCTION_KEY = "sharedPreferenceBaseURLKeyProduction"
const val SHARED_PREFERENCE_SELECTED_BASE_URL_KEEPER_KEY = "sharedPreferenceBaseURLKeyKeeper"
const val MAX_MEMBER_NAMES = 256
}
enum class ProtocolType(val type: String) {
ARCHIVE_PARAM_PROTOCOL_SEND("1"),
ARCHIVE_PARAM_PROTOCOL_INBOX("0")
}
}

View file

@ -1,576 +0,0 @@
package org.archiver;
import android.Manifest;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.webkit.MimeTypeMap;
import com.tm.logger.Log;
import org.jetbrains.annotations.Nullable;
import org.tm.archive.attachments.Attachment;
import org.tm.archive.attachments.AttachmentId;
import org.tm.archive.attachments.DatabaseAttachment;
import org.tm.archive.contactshare.Contact;
import org.tm.archive.database.SignalDatabase;
import org.tm.archive.dependencies.ApplicationDependencies;
import org.tm.archive.providers.BlobProvider;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Random;
public class ArchiveFileUtil {
public static String getPath(Context context, Uri uri){
String[] projection = {MediaStore.MediaColumns.DATA};
String path = "";
ContentResolver cr = context.getApplicationContext().getContentResolver();
Cursor metaCursor = cr.query(uri, projection, null, null, null);
if (metaCursor != null) {
try {
if (metaCursor.moveToFirst()) {
path = metaCursor.getString(0);
}
} finally {
metaCursor.close();
}
}
return path;
}
/*
This method can parse out the real local file path from a file URI.
*/
public static String getUriRealPath(Context ctx, Uri uri)
{
String ret = "";
if( isAboveKitKat() )
{
// Android sdk version number bigger than 19.
ret = getUriRealPathAboveKitkat(ctx, uri);
}else
{
// Android sdk version number smaller than 19.
ret = getImageRealPath(ctx.getContentResolver(), uri, null);
}
return ret;
}
/*
This method will parse out the real local file path from the file content URI.
The method is only applied to android sdk version number that is bigger than 19.
*/
public static String getUriRealPathAboveKitkat(Context ctx, Uri uri)
{
String ret = "";
if(ctx != null && uri != null) {
if(isContentUri(uri))
{
if(isGooglePhotoDoc(uri.getAuthority()))
{
ret = uri.getLastPathSegment();
}else {
ret = getImageRealPath(ctx.getContentResolver(), uri, null);
}
}else if(isFileUri(uri)) {
ret = uri.getPath();
}else if(isDocumentUri(ctx, uri)){
// Get uri related document id.
String documentId = DocumentsContract.getDocumentId(uri);
// Get uri authority.
String uriAuthority = uri.getAuthority();
if(isMediaDoc(uriAuthority))
{
String idArr[] = documentId.split(":");
if(idArr.length == 2)
{
// First item is document type.
String docType = idArr[0];
// Second item is document real id.
String realDocId = idArr[1];
// Get content uri by document type.
Uri mediaContentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
if("image".equals(docType))
{
mediaContentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
}else if("video".equals(docType))
{
mediaContentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
}else if("audio".equals(docType))
{
mediaContentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
// Get where clause with real document id.
String whereClause = MediaStore.Images.Media._ID + " = " + realDocId;
ret = getImageRealPath(ctx.getContentResolver(), mediaContentUri, whereClause);
}
}else if(isDownloadDoc(uriAuthority))
{
// Build download uri.
Uri downloadUri = Uri.parse("content://downloads/public_downloads");
// Append download document id at uri end.
Uri downloadUriAppendId = ContentUris.withAppendedId(downloadUri, Long.valueOf(documentId));
ret = getImageRealPath(ctx.getContentResolver(), downloadUriAppendId, null);
}else if(isExternalStoreDoc(uriAuthority))
{
String idArr[] = documentId.split(":");
if(idArr.length == 2)
{
String type = idArr[0];
String realDocId = idArr[1];
if("primary".equalsIgnoreCase(type))
{
ret = Environment.getExternalStorageDirectory() + "/" + realDocId;
}
}
}
}
}
return ret;
}
/* Check whether current android os version is bigger than kitkat or not. */
public static boolean isAboveKitKat()
{
boolean ret = false;
ret = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
return ret;
}
/* Check whether this uri represent a document or not. */
public static boolean isDocumentUri(Context ctx, Uri uri)
{
boolean ret = false;
if(ctx != null && uri != null) {
ret = DocumentsContract.isDocumentUri(ctx, uri);
}
return ret;
}
/* Check whether this uri is a content uri or not.
* content uri like content://media/external/images/media/1302716
* */
public static boolean isContentUri(Uri uri)
{
boolean ret = false;
if(uri != null) {
String uriSchema = uri.getScheme();
if("content".equalsIgnoreCase(uriSchema))
{
ret = true;
}
}
return ret;
}
/* Check whether this uri is a file uri or not.
* file uri like file:///storage/41B7-12F1/DCIM/Camera/IMG_20180211_095139.jpg
* */
public static boolean isFileUri(Uri uri)
{
boolean ret = false;
if(uri != null) {
String uriSchema = uri.getScheme();
if("file".equalsIgnoreCase(uriSchema))
{
ret = true;
}
}
return ret;
}
/* Check whether this document is provided by ExternalStorageProvider. Return true means the file is saved in external storage. */
public static boolean isExternalStoreDoc(String uriAuthority)
{
boolean ret = false;
if("com.android.externalstorage.documents".equals(uriAuthority))
{
ret = true;
}
return ret;
}
/* Check whether this document is provided by DownloadsProvider. return true means this file is a downloaed file. */
public static boolean isDownloadDoc(String uriAuthority)
{
boolean ret = false;
if("com.android.providers.downloads.documents".equals(uriAuthority))
{
ret = true;
}
return ret;
}
/*
Check if MediaProvider provide this document, if true means this image is created in android media app.
*/
public static boolean isMediaDoc(String uriAuthority)
{
boolean ret = false;
if("com.android.providers.media.documents".equals(uriAuthority))
{
ret = true;
}
return ret;
}
/*
Check whether google photos provide this document, if true means this image is created in google photos app.
*/
public static boolean isGooglePhotoDoc(String uriAuthority)
{
boolean ret = false;
if("com.google.android.apps.photos.content".equals(uriAuthority))
{
ret = true;
}
return ret;
}
/* Return uri represented document file real local path.*/
public static String getImageRealPath(ContentResolver contentResolver, Uri uri, String whereClause)
{
String ret = "";
// Query the uri with condition.
Cursor cursor = contentResolver.query(uri, null, whereClause, null, null);
if(cursor!=null)
{
boolean moveToFirst = cursor.moveToFirst();
if(moveToFirst)
{
// Get columns name by uri type.
String columnName = MediaStore.Images.Media.DATA;
if( uri==MediaStore.Images.Media.EXTERNAL_CONTENT_URI )
{
columnName = MediaStore.Images.Media.DATA;
}else if( uri==MediaStore.Audio.Media.EXTERNAL_CONTENT_URI )
{
columnName = MediaStore.Audio.Media.DATA;
}else if( uri==MediaStore.Video.Media.EXTERNAL_CONTENT_URI )
{
columnName = MediaStore.Video.Media.DATA;
}
// Get column index.
int imageColumnIndex = cursor.getColumnIndex(columnName);
// Get column value which is the uri related file local path.
ret = cursor.getString(imageColumnIndex);
}
}
return ret;
}
public static String getRealPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
System.out.println("getPath() uri: " + uri.toString());
System.out.println("getPath() uri authority: " + uri.getAuthority());
System.out.println("getPath() uri path: " + uri.getPath());
// ExternalStorageProvider
if ("com.android.externalstorage.documents".equals(uri.getAuthority())) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
System.out.println("getPath() docId: " + docId + ", split: " + split.length + ", type: " + type);
// This is for checking Main Memory
if ("primary".equalsIgnoreCase(type)) {
if (split.length > 1) {
return Environment.getExternalStorageDirectory() + "/" + split[1] + "/";
} else {
return Environment.getExternalStorageDirectory() + "/";
}
// This is for checking SD Card
} else {
return "storage" + "/" + docId.replace(":", "/");
}
}
}
return null;
}
public static void copyInputStreamToFile(InputStream in, File file) {
OutputStream out = null;
try {
out = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len;
while((len=in.read(buf))>0){
out.write(buf,0,len);
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
// Ensure that the InputStreams are closed even if there's an exception.
try {
if ( out != null ) {
out.close();
}
// If you want to close the "in" InputStream yourself then remove this
// from here but ensure that you close it yourself eventually.
in.close();
}
catch ( IOException e ) {
e.printStackTrace();
}
}
}
public static void deleteFile(Context context,String dirName,String fileName){
File dir = new File(context.getCacheDir().toString());
if(dir.exists()){
for (File file : dir.listFiles()) {
if(file.getName().equalsIgnoreCase(fileName)){
file.delete();
break;
}
}
}
}
public static File createFileFromContentUri(Context context, String contentUri){
File resultFile = null;
if(contentUri.contains(ArchiveConstants.SIGNAL_PART_PATH) ) {
resultFile = getFileFromDataBaseUri(context, contentUri);
}else if(contentUri.contains(ArchiveConstants.SIGNAL_STICKER_PATH)){
resultFile = getStickerFileFromBlobProvider(context, contentUri);
}else if(contentUri.contains(ArchiveConstants.SIGNAL_BLOB_PATH)){
resultFile = getFileFromBlobProvider(context, contentUri);
}else {
resultFile = getFileFromDeviceUri(context, contentUri);
}
return resultFile;
}
public static File getFileFromDataBaseUri(Context context, String contentUri) {
String[] splitUri = contentUri.split("/");
int splitLength = splitUri.length;
DatabaseAttachment databaseAttachment = SignalDatabase.attachments().getAttachment(new AttachmentId(Long.parseLong(splitUri[splitLength - 1]),Long.parseLong(splitUri[splitLength - 2])));
InputStream attachmentInputStream = null;
try {
attachmentInputStream = SignalDatabase.attachments().getAttachmentStream(databaseAttachment.getAttachmentId(),0);
} catch (IOException e) {
e.printStackTrace();
}
String fileName = getFileNameWithType(databaseAttachment.getFileName(), 0 ,databaseAttachment.getAttachmentId().getRowId(),databaseAttachment.getContentType());
File resultFile = new File(context.getCacheDir(), fileName);
ArchiveFileUtil.copyInputStreamToFile(attachmentInputStream, resultFile);
return resultFile;
}
public static String getFileType(DatabaseAttachment databaseAttachment) {
String fileType;
try {
fileType = databaseAttachment.getFileName().split("\\.")[1];
}catch (Exception e){
fileType = MimeTypeMap.getSingleton().getExtensionFromMimeType(databaseAttachment.getContentType());
}
return fileType;
}
public static String getFileType(String fileName, String contentType) {
String fileType;
try {
if(fileName != null) {
fileType = fileName.split("\\.")[fileName.split("\\.").length - 1];
}else{
fileType = MimeTypeMap.getSingleton().getExtensionFromMimeType(contentType);
}
}catch (Exception e){
fileType = MimeTypeMap.getSingleton().getExtensionFromMimeType(contentType);
}
return fileType;
}
public static String getFileType(Attachment attachment) {
String fileType;
try {
fileType = attachment.getFileName().split("\\.")[1];
}catch (Exception e){
fileType = MimeTypeMap.getSingleton().getExtensionFromMimeType(attachment.getContentType());
}
return fileType;
}
private static File getFileFromBlobProvider(Context context, String contentUri) {
File resultFile = null;
String fileName = "";
String fileType = MimeTypeMap.getSingleton().getExtensionFromMimeType(context.getContentResolver().getType(Uri.parse(contentUri)));
InputStream stream = null;
try {
stream = BlobProvider.getInstance().getStream(ApplicationDependencies.getApplication(), Uri.parse(contentUri));
} catch (IOException e) {
e.printStackTrace();
Log.e("ArchiveFileUtil", "getFileFromBlobProvider -> error getStream!!!!--------------");
return null;
}
fileName = contentUri.split("/")[contentUri.split("/").length - 1].split("\\.")[0] + "." + fileType;
resultFile = new File(context.getCacheDir(), fileName);
ArchiveFileUtil.copyInputStreamToFile(stream, resultFile);
return resultFile;
}
private static File getStickerFileFromBlobProvider(Context context, String contentUri) {
File resultFile = null;
String fileName = "";
InputStream stream = null;
try {
stream = SignalDatabase.stickers().getStickerStream(ContentUris.parseId(Uri.parse(contentUri)));
} catch (IOException e) {
e.printStackTrace();
}
fileName = contentUri.split("/")[contentUri.split("/").length - 1].split("\\.")[0] + "." + "webp";
resultFile = new File(context.getCacheDir(), fileName);
ArchiveFileUtil.copyInputStreamToFile(stream, resultFile);
return resultFile;
}
public static boolean checkWriteExternalPermission(Context context)
{
String permission = Manifest.permission.READ_EXTERNAL_STORAGE;
int res = context.checkCallingOrSelfPermission(permission);
return (res == PackageManager.PERMISSION_GRANTED);
}
@Nullable
private static File getFileFromDeviceUri(Context context, String contentUri) {
if(checkWriteExternalPermission(context)) {
File resultFile = null;
String fileName = "";
String fileType = MimeTypeMap.getSingleton().getExtensionFromMimeType(context.getContentResolver().getType(Uri.parse(contentUri)));
InputStream inputStream = null;
fileName = contentUri.split("/")[contentUri.split("/").length - 1].split("\\.")[0] + "." + fileType;
resultFile = new File(context.getCacheDir(), fileName);
try {
inputStream = context.getContentResolver().openInputStream(Uri.parse(contentUri));
ArchiveFileUtil.copyInputStreamToFile(inputStream, resultFile);
} catch (Exception e) {
e.printStackTrace();
}
return resultFile;
}
return null;
}
public static File createVCFFileFromContact(Context context, Contact contact){
File vcfFile = new File(context.getCacheDir(), contact.getName().isEmpty()? "contact" : contact.getName().getDisplayName().replaceAll(" ","_") + ".vcf");
FileWriter fw = null;
try {
fw = new FileWriter(vcfFile);
fw.write("BEGIN:VCARD\r\n");
fw.write("VERSION:3.0\r\n");
fw.write("N:" + contact.getName().getFamilyName() + ";" + contact.getName().getGivenName() + "\r\n");
fw.write("FN:" + contact.getName().getGivenName() + " " + contact.getName().getFamilyName() + "\r\n");
if(contact.getOrganization() != null) {
fw.write("ORG:" + contact.getOrganization() + "\r\n");
}
for (int i = 0; i < contact.getPostalAddresses().size(); i++) {
fw.write("TEL;TYPE=WORK,VOICE:" + contact.getPostalAddresses().get(0).getLabel() + "\r\n");
}
for (int i = 0; i < contact.getPhoneNumbers().size(); i++) {
fw.write("TEL;TYPE=WORK,VOICE:" + contact.getPhoneNumbers().get(i).getNumber() + "\r\n");
}
fw.write("ADR;TYPE=WORK:;;" + contact.getPostalAddresses()+ "\r\n");
for (int i = 0; i < contact.getEmails().size(); i++) {
fw.write("EMAIL;TYPE=PREF,INTERNET:" + contact.getEmails().get(i).getEmail() + "\r\n");
}
fw.write("END:VCARD\r\n");
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
return vcfFile;
}
public static String getFileNameWithType(String fileName, long messageId, long attachmentId, String contentType) {
return getFileNameWithType(fileName, messageId, attachmentId, contentType, false);
}
public static String getFileNameWithType(String fileName, long messageId, long attachmentId, String contentType, boolean isIncoming) {
if (isIncoming || fileName == null){
return ArchiveUtil.Companion.generateAttachmentName(messageId, attachmentId) + "." + ArchiveFileUtil.getFileType(fileName, contentType);
}else{
return fileName.replace(" ", "_");
}
}
}

View file

@ -1,17 +0,0 @@
package org.archiver
import android.util.Log
class ArchiveLogger {
companion object{
const val LOGGER_TAG = "TMSignalArchive"
fun sendArchiveLog(log : String){
Log.d(LOGGER_TAG, log)
}
}
}

View file

@ -1,23 +0,0 @@
package org.archiver
class ArchivePreferenceConstants {
companion object{
const val FCM_TOKEN_SHARED_PREFERENCE_NAME = "archiveConfig"
const val FCM_TOKEN_PREFERENCE_KEY = "FCMTokenPreferenceKey"
const val PREF_KEY_DEVICE_PHONE_NUMBER = "devicePhoneNumber"
const val PREF_KEY_DEVICE_NAME = "fullNamePref"
const val PREF_KEY_MAIN_ACTIVITY_RESTART = "isMainActivityAlreadyRestarted4"
const val GENERATE_TOK_NAME = "logfile"
const val GENERATE_TOK_PASS = "enRR8UVVywXYbFkqU#QDPRkO"
}
}

View file

@ -1,338 +0,0 @@
package org.archiver
import android.content.Context
import com.tm.androidcopysdk.DataGrabber
import com.tm.androidcopysdk.utils.Contact
import com.tm.logger.Log
import org.archiver.ArchiveLogger.Companion.sendArchiveLog
import org.archiver.ArchiveUtil.Companion.cleanMessageBodyFromUnusedCharacters
import org.archiver.ArchiveUtil.Companion.createMessageNameList
import org.archiver.ArchiveUtil.Companion.createMessageNameListV2
import org.archiver.ArchiveUtil.Companion.createSubjectForArchiving
import org.archiver.ArchiveUtil.Companion.createToRecipientList
import org.archiver.ArchiveUtil.Companion.createToRecipientListV2
import org.archiver.ArchiveUtil.Companion.fromContactName
import org.archiver.ArchiveUtil.Companion.getChatMode
import org.archiver.ArchiveUtil.Companion.getChatName
import org.archiver.ArchiveUtil.Companion.getChatNameV2
import org.archiver.ArchiveUtil.Companion.getFromPartForSubject
import org.archiver.ArchiveUtil.Companion.getGroupInboxRecipientNumber
import org.archiver.ArchiveUtil.Companion.groupId
import org.tm.archive.attachments.DatabaseAttachment
import org.tm.archive.database.SignalDatabase
import org.tm.archive.database.model.MediaMmsMessageRecord
import org.tm.archive.database.model.MessageRecord
import org.tm.archive.mms.IncomingMediaMessage
import org.tm.archive.mms.OutgoingMessage
import org.tm.archive.recipients.Recipient
import org.tm.archive.sms.IncomingTextMessage
import java.io.File
class ArchiveSender {
companion object{
const val TAG = "ArchiveSender"
private fun sendArchiveMessage(context: Context, uniqueMessageId: String , aProtocolType: ArchiveConstants.ProtocolType, toRecipientsList: Array<String>, from: String, messageBody: String?, dateInTimeStamp: Long, subject: String, chatMode: DataGrabber.CHAT_MODE, chatName: String, chatId: String?, fromNameString: Contact, toRecipientsListNames: Array<Contact>, archiveFile: Array<File?>? = null){
// Log.d(TAG, "messageId = $uniqueMessageId message text $messageBody")
Log.d(TAG, "sendArchiveMessage -> body = $messageBody sub = $subject")
if(archiveFile == null || archiveFile[0] == null) {
Log.d(TAG, "sendArchiveMessage -> only text")
DataGrabber.getInstance(context).setMessage(aProtocolType.type, toRecipientsList, from, messageBody, uniqueMessageId, dateInTimeStamp.toString(), subject, ArchiveUtil.getPhoneNumberInTestMode(context), chatMode, chatName, chatId, fromNameString, from, toRecipientsListNames, toRecipientsList)
}else {
Log.d(TAG, "sendArchiveMessage -> also file")
DataGrabber.getInstance(context).setMmsMessage(aProtocolType.type, toRecipientsList, from, messageBody, uniqueMessageId /*+ "M"*/, dateInTimeStamp.toString(), subject, ArchiveUtil.getPhoneNumberInTestMode(context), chatMode, chatName, chatId, fromNameString, from, toRecipientsListNames, toRecipientsList, archiveFile)
}
}
fun updateArchiveSDKToSendMMSMessage(context: Context, fileName: String, needCompress: Boolean){
DataGrabber.getInstance(context).updateFileMms(fileName, needCompress)
}
fun archiveMessageInbox(context: Context, type: ArchiveConstants.ProtocolType, archiveRecipient: Recipient, message: IncomingTextMessage, messageId: Long, groupTile: String) {
Log.d(TAG, "archiveMessageInbox")
val isInbox = type === ArchiveConstants.ProtocolType.ARCHIVE_PARAM_PROTOCOL_INBOX
val isGroup = message.groupId != null
var inboxRecipient = ""
if (archiveRecipient.isGroup) {
inboxRecipient = getGroupInboxRecipientNumber(archiveRecipient, message)
}
val from = getFromPartForSubject(context, isInbox, archiveRecipient, inboxRecipient)
val toRecipientsList = createToRecipientList(context, isInbox, archiveRecipient, isGroup, from)
val subject = createSubjectForArchiving(context, isInbox, isGroup, archiveRecipient, inboxRecipient, false, groupTile)
val chatMode = getChatMode(isGroup)
val chatName = getChatName(context, archiveRecipient, isGroup)
val chatId = groupId(archiveRecipient)
val fromContactName = fromContactName(context, archiveRecipient, isInbox)
val toName = createMessageNameList(context, archiveRecipient, isInbox, ArchiveUtil.getRecipientsListFromParticipantIds(archiveRecipient), isGroup, Contact(from))
val messageBody = if(message.messageBody != null){
message.messageBody
}else{
""
}
val uniqueMessageId = ArchiveUtil.getUniqueMessageId(context, message.sentTimestampMillis, from)
sendArchiveMessage(context,uniqueMessageId , type, toRecipientsList, from, messageBody, System.currentTimeMillis(), subject, chatMode, chatName, chatId, fromContactName, toName)
sendArchiveLog("archiveMessageInbox --> type = $type uniqueMessageId Message ID = $uniqueMessageId subject = $subject group name = $groupTile")
}
fun archiveMessageInboxV2(context: Context, type: ArchiveConstants.ProtocolType, senderRecipient: Recipient, threadRecipient: Recipient, messageBody: String , messageSendingTime: Long) {
Log.d(TAG, "archiveMessageInboxV2 -> message = $messageBody")
val isInbox = type === ArchiveConstants.ProtocolType.ARCHIVE_PARAM_PROTOCOL_INBOX
val isGroup = threadRecipient.isGroup
var inboxRecipient = ""
if (isGroup) {
inboxRecipient = if(senderRecipient.e164.isPresent) senderRecipient.e164.get() else "0"//getGroupInboxRecipientNumberV2(senderRecipient, threadRecipient)
}
val groupTile = if(threadRecipient.getGroupName(context) != null) threadRecipient.getGroupName(context) else ""
val from = getFromPartForSubject(context, isInbox, senderRecipient, inboxRecipient)
val toRecipientsList = createToRecipientListV2(context, isInbox, senderRecipient,threadRecipient, isGroup, from)
val subject = createSubjectForArchiving(context, isInbox, isGroup, senderRecipient, inboxRecipient, false, groupTile)
val chatMode = getChatMode(isGroup)
val chatName = getChatNameV2(context, threadRecipient, isGroup)
val chatId = groupId(threadRecipient)
val fromContactName = fromContactName(context, senderRecipient, isInbox)
val toName = createMessageNameListV2(context, senderRecipient, threadRecipient, isInbox, ArchiveUtil.getRecipientsListFromParticipantIds(senderRecipient), isGroup, Contact(from))
val uniqueMessageId = ArchiveUtil.getUniqueMessageId(context, messageSendingTime, from)
sendArchiveMessage(context,uniqueMessageId , type, toRecipientsList, from, messageBody, System.currentTimeMillis(), subject, chatMode, chatName, chatId, fromContactName, toName)
sendArchiveLog("archiveMessageInbox --> type = $type uniqueMessageId Message ID = $uniqueMessageId subject = $subject group name = $groupTile")
}
fun archiveMessageOutboxV1(context: Context, type: ArchiveConstants.ProtocolType, archiveRecipient: Recipient, messageBody: String, messageId: Long, sendingTime: Long) {
Log.d(TAG, "archiveMessageOutboxV1 -> message = $messageBody")
val isInbox = type === ArchiveConstants.ProtocolType.ARCHIVE_PARAM_PROTOCOL_INBOX
val isGroup = archiveRecipient.isGroup
val inboxRecipient = ""
val from = getFromPartForSubject(context, isInbox, archiveRecipient, inboxRecipient)
val toRecipientsList = createToRecipientList(context, isInbox, archiveRecipient, isGroup, from)
val chatName = getChatName(context, archiveRecipient, isGroup)
val subject = createSubjectForArchiving(context, isInbox, isGroup, archiveRecipient, inboxRecipient, false, chatName)
val chatMode = getChatMode(isGroup)
val chatId = groupId(archiveRecipient)
val fromContactName = fromContactName(context, archiveRecipient, isInbox)
val toName = createMessageNameList(context, archiveRecipient, isInbox, ArchiveUtil.getRecipientsListFromParticipantIds(archiveRecipient), isGroup, Contact(from))
val cleanMessageBody = cleanMessageBodyFromUnusedCharacters(messageBody)
val uniqueMessageId = ArchiveUtil.getUniqueMessageId(context, sendingTime, from)
sendArchiveMessage(context,uniqueMessageId, type, toRecipientsList, from, cleanMessageBody, System.currentTimeMillis(), subject, chatMode, chatName, chatId, fromContactName, toName)
sendArchiveLog("archiveMessageOutbox --> type = $type subject = $subject uniqueMessageId Message ID = $messageId")
}
//This method also sent sms if attachments list size is 0
fun archiveMessageOutboxMMS(context: Context, type: ArchiveConstants.ProtocolType, archiveRecipient: Recipient, message: OutgoingMessage, messageId: Long, archiveFile: Array<File?>? = null) {
val isInbox = type === ArchiveConstants.ProtocolType.ARCHIVE_PARAM_PROTOCOL_INBOX
val isGroup = archiveRecipient.isGroup
val inboxRecipient = ""
var groupTitle = ""
if (message.threadRecipient.groupId.isPresent) {
groupTitle = SignalDatabase.groups.getGroup(message.threadRecipient.groupId.get()).get().title!!
}
val from = getFromPartForSubject(context, isInbox, archiveRecipient, inboxRecipient)
val toRecipientsList = createToRecipientList(context, isInbox, archiveRecipient, isGroup, from)
val subject = createSubjectForArchiving(context, isInbox, isGroup, archiveRecipient, inboxRecipient, false, groupTitle)
val chatMode = getChatMode(isGroup)
val chatName = getChatName(context, archiveRecipient, isGroup)
val chatId = groupId(archiveRecipient)
val fromContactName = fromContactName(context, archiveRecipient, isInbox)
val toName = createMessageNameList(context, archiveRecipient, isInbox, ArchiveUtil.getRecipientsListFromParticipantIds(archiveRecipient), isGroup, Contact(from))
val messageBody = ArchiveUtil.createPreviewLinkBody(null, message)
val uniqueMessageId = ArchiveUtil.getUniqueMessageId(context, message.sentTimeMillis, from)
sendArchiveMessage(context, uniqueMessageId, type, toRecipientsList, from, messageBody , System.currentTimeMillis(), subject, chatMode, chatName, chatId, fromContactName, toName, archiveFile)
sendArchiveLog("archiveMessageOutboxMMS --> type = $type subject = $subject uniqueMessageId Message ID = $uniqueMessageId")
}
fun archiveMessageInboxMMS(aContext: Context, aGroupTitle: String?, aType: ArchiveConstants.ProtocolType, aArchiveRecipient: Recipient, aRecipientList: MutableList<Recipient>?, aMessage: IncomingMediaMessage, aMessageId: Long, aArchiveFile: File? = null) {
var listOfFile: Array<File?>? = null
if(aArchiveFile != null) {
listOfFile = Array(1) { aArchiveFile }
}
archiveMessageInboxMMS(aContext, aGroupTitle, aType, aArchiveRecipient, aRecipientList, aMessage, aMessageId, listOfFile)
}
fun archiveMessageInboxMMS(context: Context, groupTitle: String?, type: ArchiveConstants.ProtocolType, archiveRecipient: Recipient, recipientList: MutableList<Recipient>?, message: IncomingMediaMessage, messageId: Long, archiveFile: Array<File?>? = null) {
val isInbox = type === ArchiveConstants.ProtocolType.ARCHIVE_PARAM_PROTOCOL_INBOX
val isGroup = message.isGroupMessage
val inboxRecipient = ""
val from = getFromPartForSubject(context, isInbox, archiveRecipient, inboxRecipient)
val toRecipientsList = createToRecipientList(context, isInbox, archiveRecipient, isGroup, from, recipientList)
val subject = createSubjectForArchiving(context, isInbox, isGroup, archiveRecipient, inboxRecipient, false, groupTitle)
val chatMode = getChatMode(isGroup)
val chatName = getChatName(context, archiveRecipient, isGroup,groupTitle?: "")
val chatId = groupId(archiveRecipient, message.groupId)
val fromContactName = fromContactName(context, archiveRecipient, isInbox)
val toName = createMessageNameList(context, archiveRecipient, isInbox, recipientList, isGroup, Contact(from))
val messageBody = ArchiveUtil.createPreviewLinkBody(message, null)
val uniqueMessageId = ArchiveUtil.getUniqueMessageId(context, message.sentTimeMillis, from)
sendArchiveMessage(context, uniqueMessageId, type, toRecipientsList, from, messageBody, System.currentTimeMillis(), subject, chatMode, chatName, chatId, fromContactName, toName, archiveFile)
sendArchiveLog("archiveMessageInboxMMS --> type = $type subject = $subject recipientList $recipientList uniqueMessageId Message ID = $uniqueMessageId")
}
fun archiveMessageInboxMMSV2(context: Context, type: ArchiveConstants.ProtocolType, senderRecipient: Recipient, threadRecipient: Recipient, messageBody: String , messageSendingTime: Long, aArchiveFile: File? = null) {
var listOfFile: Array<File?>? = null
if(aArchiveFile != null) {
listOfFile = Array(1) { aArchiveFile }
}
archiveMessageInboxMMSV2(context, type, senderRecipient, threadRecipient, messageBody, messageSendingTime, listOfFile)
}
fun archiveMessageInboxMMSV2(context: Context, type: ArchiveConstants.ProtocolType, senderRecipient: Recipient, threadRecipient: Recipient, messageBody: String? , messageSendingTime: Long, archiveFile: Array<File?>? = null) {
val isInbox = type === ArchiveConstants.ProtocolType.ARCHIVE_PARAM_PROTOCOL_INBOX
val isGroup = threadRecipient.isGroup
val inboxRecipient = ""
val groupTile = if(threadRecipient.getGroupName(context) != null) threadRecipient.getGroupName(context) else ""
val from = getFromPartForSubject(context, isInbox, senderRecipient, inboxRecipient)
val toRecipientsList = createToRecipientListV2(context, isInbox, senderRecipient,threadRecipient, isGroup, from)
val subject = createSubjectForArchiving(context, isInbox, isGroup, senderRecipient, inboxRecipient, false, groupTile)
val chatMode = getChatMode(isGroup)
val chatName = getChatNameV2(context, threadRecipient, isGroup)
val chatId = groupId(threadRecipient)
val fromContactName = fromContactName(context, senderRecipient, isInbox)
val toName = createMessageNameListV2(context, senderRecipient, threadRecipient, isInbox, ArchiveUtil.getRecipientsListFromParticipantIds(senderRecipient), isGroup, Contact(from))
// val messageBody = ArchiveUtil.createPreviewLinkBody(message, null) TODO: FIXIT!!
val uniqueMessageId = ArchiveUtil.getUniqueMessageId(context, messageSendingTime, from)
sendArchiveMessage(context, uniqueMessageId, type, toRecipientsList, from,
messageBody ?: "", System.currentTimeMillis(), subject, chatMode, chatName, chatId, fromContactName, toName, archiveFile)
// sendArchiveLog("archiveMessageInboxMMS --> type = $type subject = $subject recipientList $recipientList uniqueMessageId Message ID = $uniqueMessageId")
}
fun archiveMessageOutboxSyncMMS(context: Context, groupTitle: String, type: ArchiveConstants.ProtocolType, archiveRecipient: Recipient, recipientList: MutableList<Recipient>?, message: OutgoingMessage, messageId: Long, archiveFile: Array<File?>? = null) {
val isInbox = type === ArchiveConstants.ProtocolType.ARCHIVE_PARAM_PROTOCOL_INBOX
val isGroup = archiveRecipient.isGroup
val inboxRecipient = ""
val from = getFromPartForSubject(context, isInbox, archiveRecipient, inboxRecipient)
val toRecipientsList = createToRecipientList(context, isInbox, archiveRecipient, isGroup, from, recipientList)
val subject = createSubjectForArchiving(context, isInbox, isGroup, archiveRecipient, inboxRecipient, false, groupTitle)
val chatMode = getChatMode(isGroup)
val chatName = getChatName(context, archiveRecipient, isGroup)
val chatId = groupId(archiveRecipient)
val fromContactName = fromContactName(context, archiveRecipient, isInbox)
val toName = createMessageNameList(context, archiveRecipient, isInbox, recipientList, isGroup, Contact(from))
val messageBody = ArchiveUtil.createPreviewLinkBody( null, message)
val uniqueMessageId = ArchiveUtil.getUniqueMessageId(context, message.sentTimeMillis, from)
sendArchiveMessage(context,uniqueMessageId, type, toRecipientsList, from, messageBody, System.currentTimeMillis(), subject, chatMode, chatName, chatId, fromContactName, toName, archiveFile)
sendArchiveLog("archiveMessageOutboxSyncMMS --> type = $type subject = $subject uniqueMessageId Message ID = $uniqueMessageId")
}
///////////DELETE MESSAGE/////////
fun sendArchiveDeleteMessage(context: Context, message: MessageRecord, type: ArchiveConstants.ProtocolType, isDeletedForAll: Boolean){
val archiveRecipient = message.fromRecipient
val isInbox = type === ArchiveConstants.ProtocolType.ARCHIVE_PARAM_PROTOCOL_INBOX
val isGroup = archiveRecipient.isGroup
val status = getMessageStatus(isGroup, message)
val deletePrefixMessage = getDeleteMessagePrefix(isDeletedForAll, status)
val inboxRecipient = ""
val from = getFromPartForSubject(context, isInbox, archiveRecipient, inboxRecipient)
val toRecipientsList = createToRecipientList(context, isInbox, archiveRecipient, isGroup, from)
val chatName = getChatName(context, archiveRecipient, isGroup)
val subject = createSubjectForArchiving(
context,
isInbox,
isGroup,
archiveRecipient,
inboxRecipient,
false,
chatName
)
val chatMode = getChatMode(isGroup)
val chatId = groupId(archiveRecipient)
val fromContactName = fromContactName(context, archiveRecipient, isInbox)
val toName = createMessageNameList(context, archiveRecipient, isInbox, ArchiveUtil.getRecipientsListFromParticipantIds(archiveRecipient), isGroup, Contact(from))
val cleanMessageBody = cleanMessageBodyFromUnusedCharacters(message.body)
val listFileAsString = getListFileAsString(message)
val uniqueMessageId = ArchiveUtil.getUniqueDeleteMessageId(message.timestamp, from, true, isDeletedForAll)
val originalMessageId = "Original Message (Msg ID - " + ArchiveUtil.getUniqueDeleteMessageId(message.timestamp, from, false, isDeletedForAll) + ")"
val deleteMessageBody = deletePrefixMessage + subject + "\n\n" + originalMessageId + "\n\n" + cleanMessageBody + listFileAsString
sendArchiveMessage(context,uniqueMessageId, type, toRecipientsList, from, deleteMessageBody, System.currentTimeMillis(), deletePrefixMessage + subject, chatMode, chatName, chatId, fromContactName, toName)
}
private fun getDeleteMessagePrefix(
isDeletedForAll: Boolean,
status: String
) = if (isDeletedForAll) {
"DELETED For All $status "
} else {
"DELETED For Me $status "
}
private fun getMessageStatus(
isGroup: Boolean,
message: MessageRecord
) = if (!isGroup) {
if (message.readReceiptCount == 0) "UNREAD" else "READ"
} else {
"UNKNOWN"
}
private fun getListFileAsString(
messageRecord: MessageRecord
): String {
//(message as MediaMmsMessageRecord).slideDeck
if (!messageRecord.isMms) {
return ""
}
val filesName = (messageRecord as MediaMmsMessageRecord).slideDeck
val listOfDeletedFileNames = StringBuilder()
listOfDeletedFileNames.append("\n").append("Files: ").append("\n")
for (i in filesName.slides) {
listOfDeletedFileNames.append(ArchiveUtil.generateAttachmentName((i.asAttachment() as DatabaseAttachment).attachmentId.rowId, (i.asAttachment() as DatabaseAttachment).attachmentId.uniqueId)).append("\n")
}
return "$listOfDeletedFileNames"
}
}
}

View file

@ -1,769 +0,0 @@
package org.archiver
import android.content.Context
import android.net.Uri
import com.google.android.gms.tasks.OnCompleteListener
import com.google.firebase.messaging.FirebaseMessaging
import com.google.gson.Gson
import com.tm.androidcopysdk.DataGrabber
import com.tm.androidcopysdk.network.NetworkManager
import com.tm.androidcopysdk.network.keepAlive.KeepALiveResponse
import com.tm.androidcopysdk.network.keepAlive.KeepAliveRequest
import com.tm.androidcopysdk.utils.Contact
import com.tm.androidcopysdk.utils.PrefManager
import com.tm.authenticatorsdk.selfAuthenticator.AuthenticationAppType
import com.tm.logger.Log
import com.tm.utils.Definitions
import org.archiver.ArchiveConstants.Companion.ARCHIVE_SUBJECT_CHAT_GROUP
import org.archiver.ArchiveConstants.Companion.ARCHIVE_SUBJECT_FROM_TEXT
import org.archiver.ArchiveConstants.Companion.ARCHIVE_SUBJECT_TO_TEXT
import org.archiver.ArchiveConstants.Companion.SIGNAL_ARCHIVE_ATTACHMENT_TEMPLATE_PREFIX
import org.archiver.ArchiveConstants.Companion.isTestMode
import org.archiver.ArchiveConstants.Companion.signalTestMobileNumber
import org.archiver.ArchiveSender.Companion.archiveMessageOutboxMMS
import org.archiver.ArchiveSender.Companion.archiveMessageOutboxV1
import org.archiver.ArchiveSender.Companion.updateArchiveSDKToSendMMSMessage
import org.tm.archive.BuildConfig
import org.tm.archive.attachments.AttachmentId
import org.tm.archive.database.SignalDatabase
import org.tm.archive.database.model.Mention
import org.tm.archive.dependencies.ApplicationDependencies
import org.tm.archive.groups.GroupId
import org.tm.archive.linkpreview.LinkPreview
import org.tm.archive.mms.IncomingMediaMessage
import org.tm.archive.mms.OutgoingMessage
import org.tm.archive.providers.BlobProvider
import org.tm.archive.recipients.Recipient
import org.tm.archive.recipients.RecipientId
import org.tm.archive.sms.IncomingTextMessage
import java.io.File
import java.io.IOException
import java.util.function.Function
import java.util.function.Predicate
import java.util.stream.Collectors
class ArchiveUtil {
companion object {
const val TAG = "ArchiveUtil"
@JvmStatic
var listAttachmentId : List<AttachmentId> = emptyList()
@JvmStatic
fun createToRecipientList(
context: Context,
isInboxArchiveMessage: Boolean,
aRecipient: Recipient,
isGroup: Boolean,
from: String,
recipientList: MutableList<Recipient>? = null
): Array<String> {
var recipientListFromRecipient: List<String> = if (isGroup) {
recipientList?.filter { it.e164.isPresent }?.map { it.e164.get() }
?: getRecipientsListFromParticipantIds(aRecipient).filter { it.e164.isPresent }.map { it.e164.get() }
} else {
if (isInboxArchiveMessage) {
listOf(getPhoneNumberInTestMode(context))
} else {
if (aRecipient.e164.isPresent) {
listOf(aRecipient.e164.get().toString())
} else {
listOf("")
}
}
}
recipientListFromRecipient = if (!isInboxArchiveMessage) {
if (recipientListFromRecipient.size > 1) {
recipientListFromRecipient.filter { it != getPhoneNumberInTestMode(context) }
} else {
//Sending message in group that contains only me
recipientListFromRecipient
}
} else {
recipientListFromRecipient.filter { it != from }
}
return recipientListFromRecipient.toTypedArray();
}
@JvmStatic
fun createToRecipientListV2(
context: Context,
isInboxArchiveMessage: Boolean,
aRecipient: Recipient,
threadRecipient: Recipient,
isGroup: Boolean,
from: String,
recipientList: MutableList<Recipient>? = null
): Array<String> {
var recipientListFromRecipient: List<String> = if (isGroup) {
getRecipientsListFromThreadRecipient(threadRecipient).filter { it.e164.isPresent }.map { it.e164.get() }
} else {
if (isInboxArchiveMessage) {
listOf(getPhoneNumberInTestMode(context))
} else {
if (aRecipient.e164.isPresent) {
listOf(aRecipient.e164.get().toString())
} else {
listOf("")
}
}
}
recipientListFromRecipient = if (!isInboxArchiveMessage) {
if (recipientListFromRecipient.size > 1) {
recipientListFromRecipient.filter { it != getPhoneNumberInTestMode(context) }
} else {
//Sending message in group that contains only me
recipientListFromRecipient
}
} else {
recipientListFromRecipient.filter { it != from }
}
return recipientListFromRecipient.toTypedArray();
}
@JvmStatic
fun createSubjectForArchiving(
context: Context,
isInboxArchiveMessage: Boolean,
isGroup: Boolean,
recipient: Recipient,
inboxRecipient: String = "",
forceSms: Boolean,
groupTitle: String? = "",
deletePrefix : String =""
): String {
val archiveType: String = getArchiveType(isInboxArchiveMessage, isGroup, forceSms)
val to = getToPartForSubject(context, isInboxArchiveMessage, recipient, isGroup, groupTitle)
val from = getFromPartForSubject(context, isInboxArchiveMessage, recipient, inboxRecipient)
val clearSubject = "$archiveType $ARCHIVE_SUBJECT_FROM_TEXT ${
from.toString().replace("+", "")
} $ARCHIVE_SUBJECT_TO_TEXT ${to.replace("+", "")}"
return deletePrefix + clearSubject.replace("\u2069", "").replace("\u2068", "")
}
@JvmStatic
private fun getToPartForSubject(
context: Context,
isInboxArchiveMessage: Boolean,
recipient: Recipient,
isGroup: Boolean,
groupTitle: String?
): String {
return when {
isGroup -> {
"$ARCHIVE_SUBJECT_CHAT_GROUP $groupTitle"
}
isInboxArchiveMessage -> {
getPhoneNumberInTestMode(context)
}
else -> {
if (recipient.e164.isPresent) {
recipient.e164.get()
} else {
""
}
}
}
}
@JvmStatic
fun getFromPartForSubject(
context: Context,
isInboxArchiveMessage: Boolean,
recipient: Recipient,
inboxRecipient: String = ""
): String {
return when {
isInboxArchiveMessage -> {
when {
recipient.e164.isPresent -> {
recipient.e164.get()
}
inboxRecipient.isNotEmpty() -> {
inboxRecipient
}
else -> {
recipient.requireE164()
}
}
}
else -> {
getPhoneNumberInTestMode(context)
}
}
}
@JvmStatic
fun getArchiveType(
isInboxArchiveMessage: Boolean,
isGroupMessage: Boolean,
forceSms: Boolean
): String {
return if (isInboxArchiveMessage || isGroupMessage) {
ArchiveConstants.ARCHIVE_TYPE_APP_MESSAGE
} else {
if (forceSms) {
ArchiveConstants.ARCHIVE_TYPE_SMS
} else {
ArchiveConstants.ARCHIVE_TYPE_APP_MESSAGE
}
}
}
@JvmStatic
fun getPhoneNumberInTestMode(context: Context): String {
return if (isTestMode) {
signalTestMobileNumber
} else {
PrefManager.getStringPref(
context,
ArchivePreferenceConstants.PREF_KEY_DEVICE_PHONE_NUMBER,
""
);
}
}
@JvmStatic
fun getChatMode(isGroup: Boolean): DataGrabber.CHAT_MODE {
return when {
isGroup -> {
DataGrabber.CHAT_MODE.group
}
else -> {
DataGrabber.CHAT_MODE.chat
}
}
}
@JvmStatic
fun getChatName(
context: Context,
recipient: Recipient,
isGroup: Boolean,
groupTitle: String = ""
): String {
return if (isGroup) {
if (groupTitle.isNotEmpty()) {
groupTitle
} else {
recipient.getGroupName(context) ?: ""
}
} else {
""
}
}
@JvmStatic
fun getChatNameV2(
context: Context,
threadRecipient: Recipient,
isGroup: Boolean,
groupTitle: String = ""
): String {
return if (isGroup) {
if (groupTitle.isNotEmpty()) {
groupTitle
} else {
threadRecipient.getGroupName(context) ?: ""
}
} else {
""
}
}
@JvmStatic
fun getGroupInboxRecipientNumber(
archiveRecipient: Recipient,
message: IncomingTextMessage
): String {
val recipientList = getRecipientsListFromParticipantIds(archiveRecipient).filter {
message.authorId.toLong() == it.id.toLong()
}
return recipientList[0].e164.get()
}
@JvmStatic
fun groupId(recipient: Recipient, groupId: GroupId? = null): String? {
return when {
recipient.isGroup -> {
recipient.groupId.get().toString()
}
groupId != null -> {
groupId.toString()
}
else -> {
""
}
}
}
@JvmStatic
fun fromContactName(
context: Context,
recipient: Recipient,
isInboxArchiveMessage: Boolean
): Contact {
return if (isInboxArchiveMessage) {
Contact(recipient.getDisplayName(context)).cleanContactNameFromUnUsedCharacters()
} else {
Contact(Recipient.self().profileName.toString()).cleanContactNameFromUnUsedCharacters()
}
}
@JvmStatic
fun getRecipientsListFromParticipantIds(recipient: Recipient) : MutableList<Recipient> {
val selfId = ApplicationDependencies.getRecipientCache().selfId
return recipient.participantIds.stream()
.filter(Predicate { id: RecipientId -> id != selfId })
.limit(ArchiveConstants.MAX_MEMBER_NAMES.toLong())
.map(Function { id: RecipientId? -> Recipient.resolved(id!!) })
.collect(Collectors.toList())
}
@JvmStatic
fun getRecipientsListFromThreadRecipient(threadRecipient: Recipient) : List<Recipient> {
return threadRecipient.participantIds.stream()
.limit(ArchiveConstants.MAX_MEMBER_NAMES.toLong())
.map(Function { id: RecipientId? -> Recipient.resolved(id!!) })
.collect(Collectors.toList())
}
@JvmStatic
fun createMessageNameList(
context: Context,
recipient: Recipient,
isInboxArchiveMessage: Boolean,
recipientList: List<Recipient>? = null,
isGroup: Boolean,
from: Contact = Contact("")
): Array<Contact> {
val tempRecipientList = getRecipientsListFromParticipantIds(recipient)
val rl = if (!isInboxArchiveMessage) {
if (recipientList!!.size > 1) {
recipientList!!.filter {
it.e164.isPresent && it.e164.get() != getPhoneNumberInTestMode(context)
} ?:tempRecipientList.filter {
it.e164.isPresent && it.e164.get() != getPhoneNumberInTestMode(context)
}
} else {
//Sending message in group that contains only me
recipientList
}
} else {
recipientList?.filter {
it.e164.isPresent && it.e164.get() != from.toString()
}
?: tempRecipientList.filter {
it.e164.isPresent && it.e164.get() != from.toString()
}
}
val recipientListFromRecipient: List<Contact> = if (isGroup) {
rl.map {
Contact(it.getDisplayName(context))
}
} else {
if (isInboxArchiveMessage) {
listOf(Contact(Recipient.self().profileName.toString()))
} else {
listOf(Contact(recipient.getDisplayName(context)))
}
}
if (recipientListFromRecipient.toTypedArray().isEmpty()) {
return arrayOf(Contact())
}
//SIG-437 - Clean list from [FSI]*[PDI]
recipientListFromRecipient.forEachIndexed { index, contact ->
contact.firstName = contact.cleanContactNameFromUnUsedCharacters().firstName
contact.lastName = contact.cleanContactNameFromUnUsedCharacters().lastName
}
return recipientListFromRecipient.toTypedArray()
}
@JvmStatic
fun createMessageNameListV2(
context: Context,
recipient: Recipient,
threadRecipient: Recipient,
isInboxArchiveMessage: Boolean,
recipientList: List<Recipient>? = null,
isGroup: Boolean,
from: Contact = Contact("")
): Array<Contact> {
val threadRecipientList = getRecipientsListFromThreadRecipient(threadRecipient)
val rl = if (!isInboxArchiveMessage) {
if (recipientList!!.size > 1) {
recipientList!!.filter {
it.e164.isPresent && it.e164.get() != getPhoneNumberInTestMode(context)
} ?:threadRecipientList.filter {
it.e164.isPresent && it.e164.get() != getPhoneNumberInTestMode(context)
}
} else {
//Sending message in group that contains only me
recipientList
}
} else {
threadRecipientList.filter {
it.e164.isPresent && it.e164.get() != from.toString()
}
}
val recipientListFromRecipient: List<Contact> = if (isGroup) {
rl.map {
Contact(it.getDisplayName(context))
}
} else {
if (isInboxArchiveMessage) {
listOf(Contact(Recipient.self().profileName.toString()))
} else {
listOf(Contact(recipient.getDisplayName(context)))
}
}
if (recipientListFromRecipient.toTypedArray().isEmpty()) {
return arrayOf(Contact())
}
//SIG-437 - Clean list from [FSI]*[PDI]
recipientListFromRecipient.forEachIndexed { index, contact ->
contact.firstName = contact.cleanContactNameFromUnUsedCharacters().firstName
contact.lastName = contact.cleanContactNameFromUnUsedCharacters().lastName
}
return recipientListFromRecipient.toTypedArray()
}
@JvmStatic
fun generateAttachmentName(messageId: Long, attachmentId: Long): String {
return SIGNAL_ARCHIVE_ATTACHMENT_TEMPLATE_PREFIX + attachmentId + "_" + messageId
}
@JvmStatic
fun getFileFromAttachmentId(context: Context, attachmentId: AttachmentId) : File {
val uri = SignalDatabase.attachments.getAttachment(attachmentId)!!.uri
Log.d("ArchiveUtil", "getFileFromAttachmentId -> uri $uri")
return ArchiveFileUtil.getFileFromDataBaseUri(context, uri.toString())
}
@JvmStatic
fun getMessageBody(messageBody: String?, mentionsList: List<Mention>): String? {
return if (messageBody != null) {
var result = messageBody ?: ""
return if (mentionsList.isNotEmpty()) {
mentionsList.forEachIndexed { index, mention ->
val givenName =
getRecipientFromRecipientID(mentionsList[index].recipientId).profileName.givenName
val e164Object = getRecipientFromRecipientID(mentionsList[index].recipientId).e164
val name = if (givenName.isEmpty()) {
if (e164Object.isPresent) {
e164Object.get()
} else {
""
}
} else {
givenName
}
result = result.replaceFirst("\uFFFC", "\u0040" + name)
}
result
} else {
messageBody
}
} else {
null
}
}
@JvmStatic
fun createPreviewLinkBody(
incomingMediaMessage: IncomingMediaMessage?,
outComingMediaMessage: OutgoingMessage?
): String? {
var body = ""
if (incomingMediaMessage != null) {
return if (incomingMediaMessage.linkPreviews.isEmpty()) {
getMessageBody(incomingMediaMessage.body, incomingMediaMessage.mentions)
} else {
generateBodyFromLinkPreview(incomingMediaMessage.linkPreviews[0]) + "\n" + incomingMediaMessage.body
}
} else if (outComingMediaMessage != null) {
return if (outComingMediaMessage.linkPreviews.isEmpty()) {
getMessageBody(outComingMediaMessage.body, outComingMediaMessage.mentions)
} else {
generateBodyFromLinkPreview(outComingMediaMessage.linkPreviews[0]) + "\n" + outComingMediaMessage.body
}
}
return ""
}
@JvmStatic
private fun generateBodyFromLinkPreview(linkPreview: LinkPreview?): String {
var body = ""
if (linkPreview!!.title.isNotEmpty()) {
body += """
Site title: ${linkPreview.title}
""".trimIndent()
}
if (linkPreview.url.isNotEmpty()) {
body += """
Site url: ${linkPreview.url}
""".trimIndent()
}
if (linkPreview.description.isNotEmpty()) {
body += """
Site description: ${linkPreview.description}
""".trimIndent()
}
return body
}
@JvmStatic
fun cleanMessageBodyFromUnusedCharacters(messageBody: String?): String {
return if (messageBody != null && messageBody.isNotEmpty()) {
messageBody.replace("\u2069", "").replace("\u2068", "")
} else {
""
}
}
@JvmStatic
fun getRecipientFromRecipientID(recipientId: RecipientId): Recipient {
return Recipient.resolved(recipientId)
}
@JvmStatic
fun archiveOutboxMessage(context: Context, messageId: Long, message: OutgoingMessage) {
var tempFileForArchiving: File? = null
var isMediaMessage = false
var filesToSend = arrayOfNulls<File>(message.attachments.size)
for (i in message.attachments.indices) {
tempFileForArchiving =
ArchiveFileUtil.createFileFromContentUri(context, message.attachments[i].uri.toString())
filesToSend[i] = tempFileForArchiving
isMediaMessage = true
}
if (message.linkPreviews.isNotEmpty()) {
if (message.linkPreviews[0].thumbnail.isPresent && message.linkPreviews[0].thumbnail.get().uri.toString()
.isNotEmpty()
) {
filesToSend = arrayOfNulls(1)
filesToSend[0] = ArchiveFileUtil.createFileFromContentUri(
context,
message.linkPreviews[0].thumbnail.get().uri.toString()
)
isMediaMessage = true
} else {
isMediaMessage = false
}
}
if (message.sharedContacts.size > 0) {
filesToSend = arrayOfNulls<File>(1)
val vcfFile = ArchiveFileUtil.createVCFFileFromContact(context, message.sharedContacts[0])
filesToSend[0] = vcfFile
isMediaMessage = true
}
//TODO - Archiving of "replay messages" (the original message is in "message.getQuote")
if (message.outgoingQuote != null) {
archiveMessageOutboxMMS(
context,
ArchiveConstants.ProtocolType.ARCHIVE_PARAM_PROTOCOL_SEND,
message.threadRecipient,
message,
messageId,
null
)
isMediaMessage = true
}
if (isMediaMessage) {
archiveMessageOutboxMMS(
context,
ArchiveConstants.ProtocolType.ARCHIVE_PARAM_PROTOCOL_SEND,
message.threadRecipient,
message,
messageId,
filesToSend
)
for (i in filesToSend.indices) {
if (filesToSend[i] != null) {
updateArchiveSDKToSendMMSMessage(context, filesToSend[i]!!.name, true)
}
}
} else {
if (!message.isGroupUpdate
&& !message.isExpirationUpdate
) {
val messageBody = createPreviewLinkBody(null, message)
archiveMessageOutboxV1(
context,
ArchiveConstants.ProtocolType.ARCHIVE_PARAM_PROTOCOL_SEND,
message.threadRecipient,
messageBody!!,
messageId,
message.sentTimeMillis
)
} else {
//TODO - Group events/updates!!
}
}
}
@JvmStatic
fun fetchFCMToken(context: Context, aITokenCallback : ITokenCallback?) {
Log.d(TAG, "Starting fetch FCM token..")
FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
if (!task.isSuccessful) {
Log.d("ArchiverUtil", "fetchFCMToken Fetching FCM registration token failed ${task.exception}")
Log.d(TAG, "fetchFCMToken FCM_TM_UTILS Fetching FCM registration token failed ${task.exception}")
aITokenCallback?.onFetchingFailed()
return@OnCompleteListener
}
// Get new FCM registration token
val token = task.result
Log.d(TAG, "FCM token is = $token")
Log.d(TAG,"fetchFCMToken FCM_TM_UTILS FCM token is = $token")
PrefManager.setStringPref(
context,
ArchivePreferenceConstants.FCM_TOKEN_PREFERENCE_KEY,
token
)
aITokenCallback?.onFetchingSucceed()
})
}
interface ITokenCallback {
fun onFetchingFailed()
fun onFetchingSucceed()
}
@JvmStatic
fun getFCMTokenIfExists(context: Context): String? {
return PrefManager.getStringPref(
context,
ArchivePreferenceConstants.FCM_TOKEN_PREFERENCE_KEY,
""
)
}
@JvmStatic
fun getUniqueMessageId(context: Context, messageSendingTime: Long, from: String): String {
return "${messageSendingTime}_${from.replace("+","")}"
}
@JvmStatic
fun getUniqueDeleteMessageId(messageSendingTime: Long, from: String, isDeleteMessage: Boolean = false, deletedForAll : Boolean = false): String {
var uniqueMessageId = "${messageSendingTime}_${from.replace("+","")}"
if(isDeleteMessage){
uniqueMessageId = (if (deletedForAll) "DELETE_FOR_ALL-" else "DELETE_FOR_ME-") + uniqueMessageId
}
return uniqueMessageId
}
@JvmStatic
fun doTeleMessageKeepAlivePing(context: Context) {
object : Thread() {
override fun run() {
try {
val nm = NetworkManager(
context,
PrefManager.getStringPref(context, "baseurl", Definitions.BaseUrl)
)
var sr: KeepAliveRequest? = null
sr = if (getFCMTokenIfExists(context) == null || getFCMTokenIfExists(context)!!
.isEmpty()
) {
Log.d(TAG, "KeepAliveRequest with 1 param")
KeepAliveRequest(AuthenticationAppType.TELEGRAM.aAppServerId.toString())
} else {
Log.d(TAG, "KeepAliveRequest with 4 param")
KeepAliveRequest(
AuthenticationAppType.SIGNAL.aAppServerId.toString(),
BuildConfig.VERSION_NAME,
BuildConfig.VERSION_CODE.toString(),
getFCMTokenIfExists(context)
)
}
Log.d(TAG, "request KeepAlive: " + Gson().toJson(sr))
//Log.d(TAG, "send message:" + sr.getTextContent());
val res = nm.start<KeepALiveResponse>(sr, null, context, true);
// Log.d(TAG, "response KeepAlive: " + new Gson().toJson(res));
if (res == null) {
Log.d(TAG, "response is null")
} else if (res.isSuccessful) {
Log.d(TAG, "response.isSuccessful() = true")
} else {
Log.d(TAG, "response.isSuccessful() = false")
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}.start()
}
fun Contact.cleanContactNameFromUnUsedCharacters() : Contact{
this.firstName = this.firstName.replace("\u2068", "").replace("\u2069","").replace("\u202c","")
this.lastName = this.lastName.replace("\u2068", "").replace("\u2069","").replace("\u202c","")
return this
}
}
enum class InboxArchiveTypes {
MEDIA,
HYPER_LINK,
STICKER,
CONTACT,
MENTIONS;
}
}

View file

@ -1,141 +0,0 @@
package org.archiver
import android.content.Context
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
import com.tm.androidcopysdk.AndroidCopySDK
import com.tm.androidcopysdk.utils.PrefManager
import com.tm.logger.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.archiver.ArchiveUtil.Companion.fetchFCMToken
import org.archiver.ArchiveUtil.Companion.getFCMTokenIfExists
import org.tm.archive.ApplicationContext
import org.tm.archive.dependencies.ApplicationDependencies
import org.tm.archive.gcm.FcmUtil
import org.tm.archive.jobs.FcmRefreshJob
import org.tm.archive.keyvalue.SignalStore
class FCMConnector {
companion object {
const val TAG = "FCMConnector"
const val RETRIEVE_ONE_TIME_PIN_FCM_FROM_TYPE = "Get Inbox"
const val RETRIEVE_ONE_TIME_PIN_FCM_MSG = "MESSAGE"
const val RETRIEVE_ONE_TIME_PIN_CODE_SUCCESSES_SUB_STRING = "code : "
@JvmStatic
fun initOfficialSignalFirebaseAccount(context: Context) {
Log.d(TAG, "init---Official---SignalFirebaseAccount");
val options = FirebaseOptions.Builder()
.setApplicationId("1:312334754206:android:a9297b152879f266")
.setApiKey("AIzaSyDrfzNAPBPzX6key51hqo3p5LZXF5Y-yxU")
.setProjectId("api-project-312334754206")
.setGcmSenderId("312334754206")
.build()
try {
FirebaseApp.clearInstancesForTest()
FirebaseApp.initializeApp(
ApplicationContext.getInstance(context).applicationContext,
options
)
} catch (e: Exception) {
Log.d(TAG, "App already exists " + e.message)
}
CoroutineScope(Dispatchers.IO).launch {
val token = FcmUtil.getToken(context)
if (token.isPresent) {
val oldToken = SignalStore.account().fcmToken
Log.i(
TAG,
"fcmconnector -> FCM_TM_UTILS token: " + token.get().length
)
if (token.get() != oldToken) {
val oldLength = oldToken?.length ?: -1
Log.i(
TAG,
"fcmconnector -> FCM_TM_UTILS Token changed. oldLength: " + oldLength + " newLength: " + token.get().length
)
Log.i(
TAG, "FCM_TM_UTILS Token changed. new Token: " + token.get()
)
} else {
Log.i(TAG, "Token didn't change.")
}
if (SignalStore.account().isRegistered) {
ApplicationDependencies.getJobManager().add(FcmRefreshJob())
}
}
}
}
@JvmStatic
fun initTeleMessageSignalFirebaseAccount(context: Context, fcmName: String?, isClearAll: Boolean) {
Log.d(TAG,"init---Telemessage---SignalFirebaseAccount")
val isAlreadyDoneSelfAuthentication =
PrefManager.getBooleanPref(context, "isAlreadyDoneSelfAuthentication", false)
Log.d(
TAG,
"SelfAuthenticatorProcess -> onCreate = isAlreadyDoneSelfAuthentication = $isAlreadyDoneSelfAuthentication"
)
if (getFCMTokenIfExists(context) == null || getFCMTokenIfExists(context)!!
.isEmpty() || !isAlreadyDoneSelfAuthentication
) {
Log.d(TAG, "ArchiveUtil.getFCMTokenIfExists(this) == null --" + (getFCMTokenIfExists(context) == null))
Log.d(TAG, "ArchiveUtil.getFCMTokenIfExists(this).isEmpty() --" + getFCMTokenIfExists(context)!!.isEmpty())
Log.d(TAG, "!isAlreadyDoneSelfAuthentication --" + !isAlreadyDoneSelfAuthentication)
Log.i(TAG, "init Telemessage -> current FCM: " + FirebaseApp.getInstance().options.projectId)
val options = FirebaseOptions.Builder()
.setApplicationId("1:578202328450:android:0c71bb144fc9cf628e039b")
.setApiKey("AIzaSyAl8hz1VyCAniywmN4_3yUTK17-PNmn98M")
.setProjectId("signal-d0e5e")
.setGcmSenderId("578202328450")
.build()
try {
if (isClearAll) {
FirebaseApp.clearInstancesForTest()
}
if (fcmName == null || fcmName.isEmpty()) {
FirebaseApp.initializeApp(
ApplicationContext.getInstance(context).applicationContext,
options
)
} else {
FirebaseApp.initializeApp(
ApplicationContext.getInstance(context).applicationContext,
options,
fcmName
)
}
Log.d(TAG, "FirebaseApp.getApps(context): " + FirebaseApp.getApps(context))
Log.i(
TAG,
"init telemessage account"
)
} catch (e: java.lang.Exception) {
Log.d(TAG, "App already exists")
}
}
fetchFCMToken(context, null)
}
@JvmStatic
fun updateSignUpCredentials(context: Context, userName: String?, password: String?) {
AndroidCopySDK.getInstance(context)
.signupSucess(userName, password)
}
}
}

View file

@ -0,0 +1,800 @@
/*
* 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);
}
};
}
}

View file

@ -1,77 +0,0 @@
package org.intune
import android.app.Activity
import android.app.AlertDialog
import com.tm.authenticatorsdk.mamsdk.MDMAuthenticator
import com.tm.authenticatorsdk.selfAuthenticator.IAuthenticationStatus
import com.tm.logger.Log
import org.tm.archive.R
import kotlin.system.exitProcess
//**TM_SA**//add all intune package
object IntuneAuthManager {
const val MDM_Auth_Status_String = "MDMAuthStatus"
enum class MdmAuthStatus{
ALREADY_SIGN,
START_INTUNE_AUTH,
START_SELF_AUTH
}
fun continueIntuneAuthentication(aIAuthenticationStatus: IAuthenticationStatus) {
/*if (!SelfAuthenticatorManager.isSelfAuthenticationAlreadyStarted()) {
SelfAuthenticatorManager.saveSelfAuthenticationFirstTimeTryingTime()
}*/
MDMAuthenticator.continueIntuneSelfAuthentication(aIAuthenticationStatus)
}
fun showDialog(context: Activity, listener: MDMDialogListener) {
val dialogBuilder = AlertDialog.Builder(context)
Log.d("IntuneAuthManager", "showDialog")
// set message of alert dialog
dialogBuilder.setTitle(context.resources.getString(R.string.intune_auth_title))
dialogBuilder.setMessage(context.resources.getString(R.string.intune_auth_message))
// if the dialog is cancelable
.setCancelable(false)
// positive button text and action
.setNegativeButton(
context.resources.getString(R.string.intune_auth_ok)
) { _, _ ->
listener.startIntuneAgain()
}
.setPositiveButton(
context.resources.getString(R.string.intune_auth_cancel)
) { _, _ ->
exitProcess(0)
}
val alertDialog = dialogBuilder.create()
alertDialog.show()
}
/**
* update app status of auth.
* it has two cases, intune auth already finish successfully or start self auth even it's managed device
* 0 - intune auth finished successfully. don't start auth again
* 1 - start intune auth
* 2 - start self auth even it's managed device
*/
/*fun updatedIntuneAuthenticatorPreference(_MDMAuthStatus: Int) {
Log.d("IntuneAuthManager",
"updatedIntuneAuthenticatorPreference," +
" status auth is(" +
"0 - already signed, " +
"1 - should sign intune auth," +
" 2 - should sign self auth) ? $_MDMAuthStatus")
val mDMAuthStatusString = "MDMAuthStatus"
val preferences = ApplicationContext.getInstance().getSharedPreferences(
INTUNE_AUTHENTICATION_PREFERENCE_NAME, Context.MODE_PRIVATE
)
val editor = preferences.edit()
editor.putInt(mDMAuthStatusString, _MDMAuthStatus)
editor.apply()
}*/
}

View file

@ -1,5 +0,0 @@
package org.intune
interface MDMDialogListener {
fun startIntuneAgain()
}

View file

@ -1,17 +0,0 @@
package org.selfAuthentication
import android.app.Activity
import android.os.Build
class AuthenticationUtils {
companion object{
fun forceCloseApplication(aContext: Activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
aContext.finishAndRemoveTask()
} else {
aContext.finishAffinity()
}
}
}
}

View file

@ -1,23 +0,0 @@
package org.selfAuthentication
import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater
import org.tm.archive.R
class ProgressDialog {
companion object {
fun progressDialog(context: Context): Dialog {
val dialog = Dialog(context)
val inflate = LayoutInflater.from(context).inflate(R.layout.progress_dialog, null)
dialog.setContentView(inflate)
dialog.setCancelable(false)
dialog.window!!.setBackgroundDrawable(
ColorDrawable(Color.TRANSPARENT)
)
return dialog
}
}
}

View file

@ -1,249 +0,0 @@
package com.tm.authenticatorsdk.selfAuthenticator
import android.app.Activity
import android.app.AlertDialog
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
import android.os.Handler
import android.os.Looper
import com.tm.androidcopysdk.AndroidCopySDK
import com.tm.androidcopysdk.ISendLogCallback
import com.tm.androidcopysdk.utils.PrefManager
import com.tm.logger.BuildConfig
import com.tm.logger.Log
import org.archiver.ArchiveConstants
import org.archiver.ArchivePreferenceConstants
import org.selfAuthentication.AuthenticationUtils
import org.selfAuthentication.ProgressDialog
import org.selfAuthentication.SelfAuthenticatorManager
import org.tm.archive.ApplicationContext
import org.tm.archive.R
import java.util.*
class SelfAuthenticationDialogBuilder : ISendLogCallback{
lateinit var mFirstFailureWarningAlertDialog : AlertDialog
lateinit var mFirstFailureWarningLogsSent : AlertDialog
lateinit var mFirstFailureSecondFailureWarning : AlertDialog
lateinit var mLogsSentContext : Activity
lateinit var mProgressDialog : Dialog
companion object{
val TEXT_MESSAGE_FOR_SENDING_LOGS = "Signal signup failure for " + org.tm.archive.BuildConfig.signal_teleMessage_version + " authentication version could not locate the TeleMessage account. Please help."
}
fun showSelfAuthenticationFirstFailureWarning(
context: Activity
) {
if(!::mFirstFailureWarningAlertDialog.isInitialized || !mFirstFailureWarningAlertDialog.isShowing) {
initFirstAuthenticationFailureMessageDialog(context)
}
if(!mFirstFailureWarningAlertDialog.isShowing){
if(!context.isFinishing){
mFirstFailureWarningAlertDialog.show()
}
}
}
fun showSelfAuthenticationFirstFailureWarningLogsSent(context: Activity) {
if(!::mFirstFailureWarningLogsSent.isInitialized || !mFirstFailureWarningLogsSent.isShowing) {
initAuthenticationFailureLogsSentMessageDialog(context)
}
if(!mFirstFailureWarningLogsSent.isShowing){
if(!context.isFinishing){
mFirstFailureWarningLogsSent.show()
}
// showDialogInTheActivity(context, mFirstFailureWarningLogsSent)
}
}
fun checkIfNeedToCloseTheAppOrJustDismissTheDialog(context: Activity, dialog: DialogInterface?) {
if (SelfAuthenticatorManager.isAppValidationTimePassed(context)) {
AuthenticationUtils.forceCloseApplication(context)
} else {
dialog?.dismiss()
}
}
fun showSelfAuthenticationSecondFailureWarning(
context: Activity
) {
Log.d("SelfAuthenticatorProcess", "${!::mFirstFailureSecondFailureWarning.isInitialized}")
if(::mFirstFailureSecondFailureWarning.isInitialized){
Log.d("SelfAuthenticatorProcess", "---> ${!mFirstFailureSecondFailureWarning.isShowing}")
}
if (!::mFirstFailureSecondFailureWarning.isInitialized || !mFirstFailureSecondFailureWarning.isShowing) {
initSecondAuthenticationFailureMessageDialog(context)
}else {
Log.d("SelfAuthenticatorProcess", "else to -- > !::mFirstFailureSecondFailureWarning.isInitialized || !mFirstFailureSecondFailureWarning.isShowing")
if(!context.isFinishing){
mFirstFailureSecondFailureWarning.show()
}
}
}
private fun initFirstAuthenticationFailureMessageDialog(context: Activity) {
val mFirstFailureWarningDialogBuilder = AlertDialog.Builder(context)
// set message of alert dialog
mFirstFailureWarningDialogBuilder.setMessage(context.resources.getString(R.string.get_self_authentication_first_warning_message_step_1))
// if the dialog is cancelable
.setCancelable(false)
// positive button text and action
.setNegativeButton(
context.resources.getString(R.string.get_self_authentication_first_warning_message_step_1_dismiss),
DialogInterface.OnClickListener { dialog, id ->
checkIfNeedToCloseTheAppOrJustDismissTheDialog(context, dialog)
})
.setPositiveButton(
context.resources.getString(R.string.get_self_authentication_failure_contact_us),
DialogInterface.OnClickListener { dialog, id ->
onTeleMessageSendLogsToSupportClicked(context)
if (!context.isFinishing) {
getAuthenticationProgressDialog(context).show()
}
dialog.dismiss()
})
mFirstFailureWarningAlertDialog = mFirstFailureWarningDialogBuilder.create()
}
private fun initSecondAuthenticationFailureMessageDialog(context: Activity) {
val dialogBuilder = AlertDialog.Builder(context)
Log.d(
"SelfAuthenticatorProcess",
"!::mFirstFailureSecondFailureWarning.isInitialized || !mFirstFailureSecondFailureWarning.isShowing"
)
// set message of alert dialog
dialogBuilder.setMessage(context.resources.getString(R.string.get_self_authentication_second_warning_message_step_2))
// if the dialog is cancelable
.setCancelable(false)
// positive button text and action
.setNegativeButton(
context.resources.getString(R.string.get_self_authentication_failure_message_close),
DialogInterface.OnClickListener { dialog, id ->
checkIfNeedToCloseTheAppOrJustDismissTheDialog(context, dialog)
})
.setPositiveButton(
context.resources.getString(R.string.get_self_authentication_failure_contact_us),
DialogInterface.OnClickListener { dialog, id ->
onTeleMessageSendLogsToSupportClicked(context)
if (!context.isFinishing) {
getAuthenticationProgressDialog(context).show()
}
})
mFirstFailureSecondFailureWarning = dialogBuilder.create()
if (!context.isFinishing) {
mFirstFailureSecondFailureWarning.show()
}
}
private fun initAuthenticationFailureLogsSentMessageDialog(context: Activity) {
val dialogBuilder = AlertDialog.Builder(context)
// set message of alert dialog
dialogBuilder.setMessage(context.resources.getString(R.string.get_self_authentication_contact_us_sent))
// if the dialog is cancelable
.setCancelable(false)
// positive button text and action
.setNegativeButton(
context.resources.getString(R.string.get_self_authentication_failure_message_close),
DialogInterface.OnClickListener { dialog, id ->
checkIfNeedToCloseTheAppOrJustDismissTheDialog(context, dialog)
})
// create dialog box
mFirstFailureWarningLogsSent = dialogBuilder.create()
}
fun onTeleMessageSendLogsToSupportClicked(context: Activity) {
Log.d("SelfAuthenticatorProcess", "onTeleMessageSendLogsToSupportClicked started")
mLogsSentContext = context
val name = PrefManager.getStringPref(context, ArchivePreferenceConstants.PREF_KEY_DEVICE_NAME, "")
val freeText = TEXT_MESSAGE_FOR_SENDING_LOGS
AndroidCopySDK.getInstance(context).sentLogs(
context,
this,
PrefManager.getStringPref(context, ArchivePreferenceConstants.PREF_KEY_DEVICE_PHONE_NUMBER, ""),
"Signal logs - " + Calendar.getInstance().time.toString(),
name,
freeText,
"",
"",
"",
ArchiveConstants.GENERATE_TOK_NAME,
ArchiveConstants.GENERATE_TOK_PASS
)
}
fun showDialogInTheActivity(activity: Activity, dialog: AlertDialog){
Log.d("SelfAuthenticatorProcess", "showDialogInTheActivity ")
if(!activity.isFinishing){
Log.d("SelfAuthenticatorProcess", "showDialogInTheActivity !activity.isFinishing")
//dialog.show()
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({
dialog.show()
}, 100)
}
}
fun getAuthenticationProgressDialog(activity: Activity): Dialog {
if(!::mProgressDialog.isInitialized || (mProgressDialog.ownerActivity != null && mProgressDialog.ownerActivity!!.localClassName != activity.localClassName)){
mProgressDialog = ProgressDialog.progressDialog(activity)
}
return mProgressDialog
}
fun isOneOfTheAuthenticationDialogIsShowing(): Boolean{
return ::mFirstFailureWarningAlertDialog.isInitialized && mFirstFailureWarningAlertDialog.isShowing ||
::mFirstFailureWarningLogsSent.isInitialized && mFirstFailureWarningLogsSent.isShowing ||
::mFirstFailureSecondFailureWarning.isInitialized && mFirstFailureSecondFailureWarning.isShowing ||
::mProgressDialog.isInitialized && mProgressDialog.isShowing
}
override fun sendLogFailure() {
if(::mProgressDialog.isInitialized) {
mProgressDialog.dismiss()
}
}
override fun sendLogSucceed() {
if(::mProgressDialog.isInitialized) {
mProgressDialog.dismiss()
}
SelfAuthenticatorManager.showLogSentIfNeeded(mLogsSentContext)
}
fun showProgressDialog(activity: Activity) {
if(!::mProgressDialog.isInitialized || (mProgressDialog.ownerActivity != null && mProgressDialog.ownerActivity!!.localClassName != activity.localClassName)){
mProgressDialog = AlertDialog.Builder(activity).setCancelable(false).create()
}
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({
mProgressDialog.show()
}, 1)
}
fun hideProgressDialog() {
if(::mProgressDialog.isInitialized) {
Handler(Looper.getMainLooper()).post(Runnable {
mProgressDialog.hide()
})
}
}
}

View file

@ -1,13 +0,0 @@
package org.archive.selfAuthentication
class SelfAuthenticatorConstants {
companion object{
val selfVersion = "1.1.1"
val selfAuthenticationSucceed = "selfAuthenticationSucceed"
val selfAuthenticationFailed = "selfAuthenticationFailed"
var isAuthenticationProcessOpened = false
}
}

View file

@ -1,128 +0,0 @@
package org.selfAuthentication
import android.app.Activity
import android.content.Context
import com.tm.authenticatorsdk.selfAuthenticator.*
import com.tm.logger.Log
import org.archive.selfAuthentication.SelfAuthenticatorConstants
import org.tm.archive.ApplicationContext
import org.tm.archive.BuildConfig
import java.util.concurrent.TimeUnit
//In order to change the environment base url call to this method:
//ApiUtil.Companion.selectServerEnvironment(Context)
//The default environment is charlieProduction = https://rest.telemessage.com
object SelfAuthenticatorManager {
const val SELF_AUTHENTICATION_PREFERENCE_NAME = "SelfAuthenticatorPref"
const val SELF_AUTHENRICATION_PREF_FIRST_TIME_TRYING_KEY = "firstTimeTrying"
val SELF_AUTHENRICATION_WHEN_TO_SHOW_FIRST_WARNNING_IN_HOURS = 2 * 24
val SELF_AUTHENRICATION_WHEN_TO_SHOW_SECOND_WARNNING_IN_HOURS = 7 * 24
init {
Log.d("SelfAuthenticatorProcess","class SelfAuthenticatorManager started.")
}
lateinit var selfAuthenticator: SelfAuthenticator
val mSelfAuthenticationDialogBuilder = SelfAuthenticationDialogBuilder()
fun initAuthenticator(phoneNumber: String) {
selfAuthenticator = SelfAuthenticator
Log.d("SelfAuthenticatorProcess", "initAuthenticator - The phone number is: $phoneNumber")
selfAuthenticator.initSelfAuthenticator(
AuthenticationAppType.SIGNAL,
phoneNumber,
BuildConfig.VERSION_NAME
)
}
fun startAuthentication(context: Context, aIAuthenticationStatus: IAuthenticationStatus) {
if (!isSelfAuthenticationAlreadyStarted(context)) {
saveSelfAuthenticationFirstTimeTryingTime(context)
}
selfAuthenticator.startSelfAuthentication(aIAuthenticationStatus)
}
fun isSelfAuthenticationAlreadyStarted(context: Context): Boolean {
return getSelfAuthenticationFirstTimeTryingInHours(context) != -1
}
fun saveSelfAuthenticationFirstTimeTryingTime(context: Context) {
if (!isSelfAuthenticationAlreadyStarted(context)) {
ApplicationContext.getInstance(context).applicationContext.getSharedPreferences(
SELF_AUTHENTICATION_PREFERENCE_NAME,
Context.MODE_PRIVATE
).apply {
edit().apply {
putLong(
SELF_AUTHENRICATION_PREF_FIRST_TIME_TRYING_KEY,
System.currentTimeMillis()
)
apply()
}
}
}
}
fun getSelfAuthenticationFirstTimeTryingInHours(context: Context): Int {
val sharedPreferences = ApplicationContext.getInstance(context).getSharedPreferences(SELF_AUTHENTICATION_PREFERENCE_NAME, Context.MODE_PRIVATE)
val firstTimeInstallInMill = sharedPreferences.getLong(SELF_AUTHENRICATION_PREF_FIRST_TIME_TRYING_KEY, -1)
if(firstTimeInstallInMill != (-1).toLong()){
Log.d("SelfAuthenticatorProcess", "hourDifferenceFromNow() = " + (hourDifferenceFromNow(firstTimeInstallInMill)).toInt())
return (hourDifferenceFromNow(firstTimeInstallInMill)).toInt()
}
Log.d("SelfAuthenticatorProcess", "hourDifferenceFromNow() = " + -1)
return -1
}
private fun hourDifferenceFromNow(millisTime: Long): Long {
val now = System.currentTimeMillis()
return TimeUnit.MILLISECONDS.toHours(now - millisTime)
}
fun showTheRelevantDialogIfNeeded(
aContext: Activity
) {
Log.d("SelfAuthenticatorProcess", "getSelfAuthenticationFirstTimeTryingInHours() = " + getSelfAuthenticationFirstTimeTryingInHours(aContext))
Log.d("SelfAuthenticatorProcess", "getSelfAuthenticationFirstTimeTryingInHours() > SELF_AUTHENRICATION_WHEN_TO_SHOW_FIRST_WARNNING_IN_DAYS = " + (getSelfAuthenticationFirstTimeTryingInHours(aContext) > SELF_AUTHENRICATION_WHEN_TO_SHOW_FIRST_WARNNING_IN_HOURS))
Log.d("SelfAuthenticatorProcess", "isAppValidationTimePassed() = " + isAppValidationTimePassed(aContext))
if (getSelfAuthenticationFirstTimeTryingInHours(aContext) > SELF_AUTHENRICATION_WHEN_TO_SHOW_FIRST_WARNNING_IN_HOURS) {
if (!isAppValidationTimePassed(aContext)) {
mSelfAuthenticationDialogBuilder.showSelfAuthenticationFirstFailureWarning(aContext)
} else {
mSelfAuthenticationDialogBuilder.showSelfAuthenticationSecondFailureWarning(aContext)
}
}
}
fun isAppValidationTimePassed(aContext: Context): Boolean{
return getSelfAuthenticationFirstTimeTryingInHours(aContext) > SELF_AUTHENRICATION_WHEN_TO_SHOW_SECOND_WARNNING_IN_HOURS
}
fun showLogSentIfNeeded(
aContext: Activity
){
mSelfAuthenticationDialogBuilder.showSelfAuthenticationFirstFailureWarningLogsSent(aContext)
}
fun isOneOfTheAuthenticationDialogIsShowing(): Boolean{
return mSelfAuthenticationDialogBuilder.isOneOfTheAuthenticationDialogIsShowing()
}
fun showProgressDialog(activity: Activity){
mSelfAuthenticationDialogBuilder.showProgressDialog(activity)
}
fun hideProgressDialog(){
mSelfAuthenticationDialogBuilder.hideProgressDialog()
}
}

View file

@ -1,6 +1,5 @@
package org.tm.archive
import org.tm.archive.util.FeatureFlags
import org.whispersystems.signalservice.api.account.AccountAttributes
object AppCapabilities {
@ -17,7 +16,7 @@ object AppCapabilities {
changeNumber = true,
stories = true,
giftBadges = true,
pni = FeatureFlags.phoneNumberPrivacy(),
pni = true,
paymentActivation = true
)
}

View file

@ -16,7 +16,6 @@
*/
package org.tm.archive;
import android.app.Application;
import android.content.Context;
import androidx.annotation.NonNull;
@ -26,24 +25,16 @@ import androidx.annotation.WorkerThread;
import androidx.multidex.MultiDexApplication;
import com.google.android.gms.security.ProviderInstaller;
import com.google.firebase.FirebaseApp;
import com.tm.androidcopysdk.AndroidCopySDK;
import com.tm.androidcopysdk.AndroidCopySettings;
import com.tm.androidcopysdk.BackupService;
import com.tm.androidcopysdk.CommonUtils;
import com.tm.androidcopysdk.utils.PrefManager;
import com.tm.authenticatorsdk.selfAuthenticator.AuthenticatorConstants;
import org.archiver.ArchiveConstants;
import org.archiver.ArchiveLogger;
import org.archiver.FCMConnector;
import org.conscrypt.Conscrypt;
import org.conscrypt.ConscryptSignal;
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;
import org.signal.core.util.logging.Scrubber;
import org.signal.core.util.tracing.Tracer;
import org.signal.glide.SignalGlideCodecs;
import org.signal.libsignal.protocol.logging.SignalProtocolLoggerProvider;
@ -63,8 +54,10 @@ import org.tm.archive.jobs.AccountConsistencyWorkerJob;
import org.tm.archive.jobs.CheckServiceReachabilityJob;
import org.tm.archive.jobs.DownloadLatestEmojiDataJob;
import org.tm.archive.jobs.EmojiSearchIndexDownloadJob;
import org.tm.archive.jobs.ExternalLaunchDonationJob;
import org.tm.archive.jobs.FcmRefreshJob;
import org.tm.archive.jobs.FontDownloaderJob;
import org.tm.archive.jobs.GroupRingCleanupJob;
import org.tm.archive.jobs.GroupV2UpdateSelfProfileKeyJob;
import org.tm.archive.jobs.MultiDeviceContactUpdateJob;
import org.tm.archive.jobs.PnpInitializeDevicesJob;
@ -94,15 +87,13 @@ import org.tm.archive.service.KeyCachingService;
import org.tm.archive.service.LocalBackupListener;
import org.tm.archive.service.RotateSenderCertificateListener;
import org.tm.archive.service.RotateSignedPreKeyListener;
import org.tm.archive.service.UpdateApkRefreshListener;
import org.tm.archive.apkupdate.ApkUpdateRefreshListener;
import org.tm.archive.service.webrtc.AndroidTelecomUtil;
import org.tm.archive.storage.StorageSyncHelper;
import org.tm.archive.util.AppForegroundObserver;
import org.tm.archive.util.AppStartup;
import org.tm.archive.util.DynamicTheme;
import org.tm.archive.util.FeatureFlags;
import org.tm.archive.util.PowerManagerCompat;
import org.tm.archive.util.ServiceUtil;
import org.tm.archive.util.SignalLocalMetrics;
import org.tm.archive.util.SignalUncaughtExceptionHandler;
import org.tm.archive.util.TextSecurePreferences;
@ -121,11 +112,9 @@ 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.Pair;
import kotlin.Unit;
import rxdogtag2.RxDogTag;
import static org.archiver.ArchiveConstants.isTestMode;
/**
* Will be called once when the TextSecure process is created.
*
@ -137,18 +126,11 @@ import static org.archiver.ArchiveConstants.isTestMode;
public class ApplicationContext extends MultiDexApplication implements AppForegroundObserver.Listener {
private static final String TAG = Log.tag(ApplicationContext.class);
private static Application mApplicationContext;//**TM_SA**//
@VisibleForTesting
protected PersistentLogger persistentLogger;
public static ApplicationContext getInstance(Context context) {
return (ApplicationContext)context.getApplicationContext();
}
public static Application getInstance() {//**TM_SA**//
return mApplicationContext;
}
@Override
public void onCreate() {
Tracer.getInstance().start("Application#onCreate()");
@ -173,27 +155,29 @@ 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)
.addBlocking("event-bus", () -> EventBus.builder().logNoSubscriberMessages(false).installDefaultEventBus())
.addBlocking("app-dependencies", this::initializeAppDependencies)
.addBlocking("scrubber", () -> Scrubber.setIdentifierHmacKeyProvider(() -> SignalStore.svr().getOrCreateMasterKey().deriveLoggingKey()))
.addBlocking("first-launch", this::initializeFirstEverAppLaunch)
.addBlocking("app-migrations", this::initializeApplicationMigrations)
.addBlocking("mark-registration", () -> RegistrationUtil.maybeMarkRegistrationComplete())
.addBlocking("lifecycle-observer", () -> ApplicationDependencies.getAppForegroundObserver().addListener(this))
.addBlocking("message-retriever", this::initializeMessageRetrieval)
.addBlocking("dynamic-theme", () -> DynamicTheme.setDefaultDayNightMode(this))
.addBlocking("proxy-init", () -> {
if (SignalStore.proxy().isProxyEnabled()) {
Log.w(TAG, "Proxy detected. Enabling Conscrypt.setUseEngineSocketByDefault()");
Conscrypt.setUseEngineSocketByDefault(true);
ConscryptSignal.setUseEngineSocketByDefault(true);
}
})
.addBlocking("blob-provider", this::initializeBlobProvider)
.addBlocking("feature-flags", FeatureFlags::init)
.addBlocking("ring-rtc", this::initializeRingRtc)
.addBlocking("glide", () -> SignalGlideModule.setRegisterGlideComponents(new SignalGlideComponents()))
.addNonBlocking(() -> RegistrationUtil.maybeMarkRegistrationComplete())
.addNonBlocking(() -> GlideApp.get(this))
.addNonBlocking(this::cleanAvatarStorage)
.addNonBlocking(this::initializeRevealableMessageManager)
@ -231,72 +215,14 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
.addPostRender(() -> ApplicationDependencies.getExoPlayerPool().getPoolStats().getMaxUnreserved())
.addPostRender(() -> ApplicationDependencies.getRecipientCache().warmUp())
.addPostRender(AccountConsistencyWorkerJob::enqueueIfNecessary)
.addPostRender(GroupRingCleanupJob::enqueue)
.execute();
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
SignalLocalMetrics.ColdStart.onApplicationCreateFinished();
Tracer.getInstance().end("Application#onCreate()");
//**TM_SA**// start
com.tm.logger.Log.i(TAG, "1 current FCM: " + FirebaseApp.getInstance().getOptions().getProjectId());
mApplicationContext = this;
com.tm.logger.Log.createInstance(getApplicationContext());
ArchiveLogger.Companion.sendArchiveLog("TeleMessage logger created");
initArchiveUrlsAndStartArchive();
}
private void initArchiveUrlsAndStartArchive() {
if(CommonUtils.isMyServiceRunning(mApplicationContext, BackupService.class)){
CommonUtils.stopBackupService(mApplicationContext, false);
}
ArchiveLogger.Companion.sendArchiveLog("initializeTMAndroidArchive \nsetUrl: \nchosenUrl =" + ArchiveConstants.charlieProduction + "\nKeeperUrl =" + ArchiveConstants.prodKeeper);
if(org.tm.archive.BuildConfig.DEBUG){
String baseUrlPrefProd = PrefManager.getStringPref(mApplicationContext, ArchiveConstants.SHARED_PREFERENCE_SELECTED_BASE_URL_PRODUCTION_KEY, ArchiveConstants.charlieProduction);
String baseUrlPrefKeeper = PrefManager.getStringPref(mApplicationContext, ArchiveConstants.SHARED_PREFERENCE_SELECTED_BASE_URL_KEEPER_KEY,ArchiveConstants.prodKeeper);
AuthenticatorConstants.Companion.setBASE_URL(new Pair(baseUrlPrefProd, baseUrlPrefKeeper));
CommonUtils.setUrl(mApplicationContext, baseUrlPrefProd, baseUrlPrefKeeper);
}else {
CommonUtils.setUrl(mApplicationContext, ArchiveConstants.charlieProduction, ArchiveConstants.prodKeeper);
}
CommonUtils.setSqlInfo(getApplicationContext(), ArchiveConstants.isTestMode ? ArchiveConstants.signalTestPassword : ArchiveConstants.signalCurrentPassword);
//set SDK to active -> need to change it with the self register
boolean installationEventSent = PrefManager.getBooleanPref(getApplicationContext(), R.string.installation_event_sent, false);
PrefManager.setBooleanPref(getApplicationContext(), "activated_aa" ,true);
if(isTestMode || !installationEventSent) {
initializeTMAndroidArchive();
ArchiveLogger.Companion.sendArchiveLog("initializeTMAndroidArchive");
}
CommonUtils.startBackupService(getApplicationContext());
ArchiveLogger.Companion.sendArchiveLog("Backup service started");
}
private void initializeTMAndroidArchive() {
AndroidCopySettings mSettings = new AndroidCopySettings();
PrefManager.setStringPref(getApplicationContext(),"wifi3g","WIFI3G");
mSettings.setData(AndroidCopySettings.DataSaving.WIFI3G);
com.tm.logger.Log.d("initializeTMAndroidArchive", "signupSucess with emptey password and user name");
AndroidCopySDK.getInstance(getApplicationContext()).signupSucess(/*ArchiveConstants.signalTestUserName, ArchiveConstants.signalTestPassword*/ "", "");
ArchiveLogger.Companion.sendArchiveLog("User name = " + "Password = ");
boolean installationEventSent = PrefManager.getBooleanPref(getApplicationContext(), R.string.installation_event_sent, false);
// InstallEvent should be sent only once
if(!installationEventSent) {
PrefManager.setBooleanPref(getApplicationContext(),R.string.installation_event_sent,true);
}
}
//**TM_SA**// End
@Override
public void onForeground() {
long startTime = System.currentTimeMillis();
@ -306,11 +232,13 @@ 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();
RetrieveProfileJob.enqueueRoutineFetchIfNecessary(this);
RetrieveProfileJob.enqueueRoutineFetchIfNecessary();
executePendingContactSync();
KeyCachingService.onAppForegrounded(this);
ApplicationDependencies.getShakeToReport().enable();
@ -340,10 +268,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
ApplicationDependencies.getShakeToReport().disable();
ApplicationDependencies.getDeadlockDetector().stop();
MemoryTracker.stop();
}
public PersistentLogger getPersistentLogger() {
return persistentLogger;
AnrDetector.stop();
}
public void checkBuildExpiration() {
@ -353,6 +278,17 @@ 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);
@ -362,7 +298,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
throw new ProviderInitializationException();
}
int conscryptPosition = Security.insertProviderAt(Conscrypt.newProvider(), 2);
int conscryptPosition = Security.insertProviderAt(ConscryptSignal.newProvider(), 2);
Log.i(TAG, "Installed Conscrypt provider: " + conscryptPosition);
if (conscryptPosition < 0) {
@ -372,14 +308,14 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
@VisibleForTesting
protected void initializeLogging() {
persistentLogger = new PersistentLogger(this);
org.signal.core.util.logging.Log.initialize(FeatureFlags::internalUser, new AndroidLogger(), persistentLogger);
Log.initialize(FeatureFlags::internalUser, new AndroidLogger(), new PersistentLogger(this));
SignalProtocolLoggerProvider.setProvider(new CustomSignalProtocolLogger());
SignalExecutors.UNBOUNDED.execute(() -> {
Log.blockUntilAllWritesFinished();
LogDatabase.getInstance(this).trimToSize();
LogDatabase.getInstance(this).logs().trimToSize();
LogDatabase.getInstance(this).crashes().trimToSize();
});
}
@ -448,8 +384,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
long nextSetTime = SignalStore.account().getFcmTokenLastSetTime() + TimeUnit.HOURS.toMillis(6);
if (SignalStore.account().getFcmToken() == null || nextSetTime <= System.currentTimeMillis()) {
FCMConnector.initOfficialSignalFirebaseAccount(this);//**TM_SA**//
// ApplicationDependencies.getJobManager().add(new FcmRefreshJob());//**TM_SA**//
ApplicationDependencies.getJobManager().add(new FcmRefreshJob());
}
}
}
@ -484,8 +419,8 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
RotateSenderCertificateListener.schedule(this);
RoutineMessageFetchReceiver.startOrUpdateAlarm(this);
if (BuildConfig.PLAY_STORE_DISABLED) {
UpdateApkRefreshListener.schedule(this);
if (BuildConfig.MANAGES_APP_UPDATES) {
ApkUpdateRefreshListener.schedule(this);
}
}
@ -495,6 +430,9 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
if (FeatureFlags.callingFieldTrialAnyAddressPortsKillSwitch()) {
fieldTrials.put("RingRTC-AnyAddressPortsKillSwitch", "Enabled");
}
if (!SignalStore.internalValues().callingDisableLBRed()) {
fieldTrials.put("RingRTC-Audio-LBRed-For-Opus", "Enabled,bitrate_pri:22000");
}
CallManager.initialize(this, new RingRtcLogger(), fieldTrials);
} catch (UnsatisfiedLinkError e) {
throw new AssertionError("Unable to load ringrtc library", e);

View file

@ -6,7 +6,6 @@ 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;

View file

@ -5,7 +5,6 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
@ -18,7 +17,6 @@ import org.signal.core.util.logging.Log;
import org.tm.archive.dependencies.ApplicationDependencies;
import org.tm.archive.util.AppStartup;
import org.tm.archive.util.ConfigurationUtil;
import org.tm.archive.util.TextSecurePreferences;
import org.tm.archive.util.WindowUtil;
import org.tm.archive.util.dynamiclanguage.DynamicLanguageContextWrapper;

View file

@ -57,6 +57,10 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
void setEventListener(@Nullable EventListener listener);
default void setParentScrolling(boolean isParentScrolling) {
// Intentionally Blank.
}
default void updateTimestamps() {
// Intentionally Blank.
}

View file

@ -20,4 +20,5 @@ public interface BindableConversationListItem extends Unbindable {
void setSelectedConversations(@NonNull ConversationSet conversations);
void updateTypingIndicator(@NonNull Set<Long> typingThreads);
void updateTimestamp();
}

View file

@ -35,7 +35,6 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.appcompat.app.AlertDialog;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
@ -50,6 +49,8 @@ import androidx.transition.TransitionManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.pnikosis.materialishprogress.ProgressWheel;
import org.signal.core.util.concurrent.LifecycleDisposable;
import org.signal.core.util.concurrent.RxExtensions;
import org.signal.core.util.concurrent.SimpleTask;
import org.signal.core.util.logging.Log;
import org.tm.archive.components.RecyclerViewFastScroller;
@ -70,10 +71,11 @@ import org.tm.archive.contacts.sync.ContactDiscovery;
import org.tm.archive.groups.SelectionLimits;
import org.tm.archive.groups.ui.GroupLimitDialog;
import org.tm.archive.permissions.Permissions;
import org.tm.archive.profiles.manage.UsernameRepository;
import org.tm.archive.profiles.manage.UsernameRepository.UsernameAciFetchResult;
import org.tm.archive.recipients.Recipient;
import org.tm.archive.recipients.RecipientId;
import org.tm.archive.util.CommunicationActions;
import org.signal.core.util.concurrent.LifecycleDisposable;
import org.tm.archive.util.TextSecurePreferences;
import org.tm.archive.util.UsernameUtil;
import org.tm.archive.util.ViewUtil;
@ -85,6 +87,7 @@ import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
@ -145,6 +148,8 @@ public final class ContactSelectionListFragment extends LoggingFragment {
private Set<RecipientId> currentSelection;
private boolean isMulti;
private boolean canSelectSelf;
private boolean resetPositionOnCommit = false;
private ListClickListener listClickListener = new ListClickListener();
@Nullable private SwipeRefreshLayout.OnRefreshListener onRefreshListener;
@ -423,6 +428,10 @@ public final class ContactSelectionListFragment extends LoggingFragment {
onRefreshListener = null;
}
public int getSelectedMembersSize() {
return contactSearchMediator.getSelectedMembersSize();
}
private @NonNull Bundle safeArguments() {
return getArguments() != null ? getArguments() : new Bundle();
}
@ -519,12 +528,21 @@ public final class ContactSelectionListFragment extends LoggingFragment {
}
public void setQueryFilter(String filter) {
this.cursorFilter = filter;
if (Objects.equals(filter, this.cursorFilter)) {
return;
}
this.resetPositionOnCommit = true;
this.cursorFilter = filter;
contactSearchMediator.onFilterChanged(filter);
}
public void resetQueryFilter() {
setQueryFilter(null);
this.resetPositionOnCommit = true;
swipeRefresh.setRefreshing(false);
}
@ -542,11 +560,12 @@ public final class ContactSelectionListFragment extends LoggingFragment {
headerActionView.setVisibility(View.GONE);
}
public void setRecyclerViewPaddingBottom(@Px int paddingBottom) {
ViewUtil.setPaddingBottom(recyclerView, paddingBottom);
}
private void onLoadFinished(int count) {
if (resetPositionOnCommit) {
resetPositionOnCommit = false;
recyclerView.scrollToPosition(0);
}
swipeRefresh.setVisibility(View.VISIBLE);
showContactsLayout.setVisibility(View.GONE);
@ -666,11 +685,18 @@ public final class ContactSelectionListFragment extends LoggingFragment {
AlertDialog loadingDialog = SimpleProgressDialog.show(requireContext());
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> {
return UsernameUtil.fetchAciForUsername(username);
}, uuid -> {
try {
return RxExtensions.safeBlockingGet(UsernameRepository.fetchAciForUsername(UsernameUtil.sanitizeUsernameFromSearch(username)));
} catch (InterruptedException e) {
Log.w(TAG, "Interrupted?", e);
return UsernameAciFetchResult.NetworkError.INSTANCE;
}
}, result -> {
loadingDialog.dismiss();
if (uuid.isPresent()) {
Recipient recipient = Recipient.externalUsername(uuid.get(), username);
// TODO Could be more specific with errors
if (result instanceof UsernameAciFetchResult.Success success) {
Recipient recipient = Recipient.externalUsername(success.getAci(), username);
SelectedContact selected = SelectedContact.forUsername(recipient.getId(), username);
if (onContactSelectedListener != null) {

View file

@ -5,13 +5,11 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Vibrator;
import android.text.TextUtils;
import android.transition.TransitionInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
@ -32,7 +30,7 @@ import org.tm.archive.crypto.ProfileKeyUtil;
import org.tm.archive.dependencies.ApplicationDependencies;
import org.tm.archive.keyvalue.SignalStore;
import org.tm.archive.permissions.Permissions;
import org.tm.archive.util.Base64;
import org.signal.core.util.Base64;
import org.tm.archive.util.DynamicLanguage;
import org.tm.archive.util.DynamicNoActionBarTheme;
import org.tm.archive.util.DynamicTheme;
@ -197,7 +195,7 @@ public class DeviceActivity extends PassphraseRequiredActivity
ProfileKey profileKey = ProfileKeyUtil.getSelfProfileKey();
TextSecurePreferences.setMultiDevice(DeviceActivity.this, true);
accountManager.addDevice(ephemeralId, publicKey, aciIdentityKeyPair, pniIdentityKeyPair, profileKey, verificationCode);
accountManager.addDevice(ephemeralId, publicKey, aciIdentityKeyPair, pniIdentityKeyPair, profileKey, SignalStore.svr().getOrCreateMasterKey(), verificationCode);
return SUCCESS;
} catch (NotFoundException e) {

View file

@ -38,7 +38,7 @@ public class DeviceProvisioningActivity extends PassphraseRequiredActivity {
.setOnDismissListener(dialog13 -> finish())
.create();
dialog.setIcon(getResources().getDrawable(R.drawable.ic_launcher_foreground_new));//**TM_SA**//
dialog.setIcon(getResources().getDrawable(R.drawable.icon_dialog));
dialog.show();
}
}

View file

@ -6,8 +6,6 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.view.ViewTreeObserver;
@ -17,20 +15,20 @@ import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.tm.androidcopysdk.utils.PrefManager;
import org.archiver.ArchivePreferenceConstants;
import org.signal.core.util.concurrent.LifecycleDisposable;
import org.signal.core.util.logging.Log;
import org.signal.donations.StripeApi;
import org.tm.archive.components.DebugLogsPromptDialogFragment;
import org.tm.archive.components.PromptBatterySaverDialogFragment;
import org.tm.archive.components.settings.app.AppSettingsActivity;
import org.tm.archive.components.voice.VoiceNoteMediaController;
import org.tm.archive.components.voice.VoiceNoteMediaControllerOwner;
import org.tm.archive.conversationlist.RelinkDevicesReminderBottomSheetFragment;
import org.tm.archive.dependencies.ApplicationDependencies;
import org.tm.archive.devicetransfer.olddevice.OldDeviceExitActivity;
import org.tm.archive.keyvalue.SignalStore;
import org.tm.archive.net.DeviceTransferBlockingInterceptor;
import org.tm.archive.notifications.SlowNotificationsViewModel;
import org.tm.archive.notifications.VitalsViewModel;
import org.tm.archive.stories.tabs.ConversationListTabRepository;
import org.tm.archive.stories.tabs.ConversationListTabsViewModel;
import org.tm.archive.util.AppStartup;
@ -50,7 +48,7 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
private VoiceNoteMediaController mediaController;
private ConversationListTabsViewModel conversationListTabsViewModel;
private SlowNotificationsViewModel slowNotificationsViewModel;
private VitalsViewModel vitalsViewModel;
private final LifecycleDisposable lifecycleDisposable = new LifecycleDisposable();
@ -94,35 +92,34 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
ConversationListTabRepository repository = new ConversationListTabRepository();
ConversationListTabsViewModel.Factory factory = new ConversationListTabsViewModel.Factory(repository);
handleGroupLinkInIntent(getIntent());
handleProxyInIntent(getIntent());
handleSignalMeIntent(getIntent());
handleCallLinkInIntent(getIntent());
handleDeeplinkIntent(getIntent());
CachedInflater.from(this).clear();
conversationListTabsViewModel = new ViewModelProvider(this, factory).get(ConversationListTabsViewModel.class);
updateTabVisibility();
slowNotificationsViewModel = new ViewModelProvider(this).get(SlowNotificationsViewModel.class);
vitalsViewModel = new ViewModelProvider(this).get(VitalsViewModel.class);
lifecycleDisposable.add(
slowNotificationsViewModel
.getSlowNotificationState()
.subscribe(this::presentSlowNotificationState)
vitalsViewModel
.getVitalsState()
.subscribe(this::presentVitalsState)
);
}
@SuppressLint("NewApi")
private void presentSlowNotificationState(SlowNotificationsViewModel.State slowNotificationState) {
switch (slowNotificationState) {
private void presentVitalsState(VitalsViewModel.State state) {
switch (state) {
case NONE:
break;
case PROMPT_BATTERY_SAVER_DIALOG:
PromptBatterySaverDialogFragment.show(getSupportFragmentManager());
break;
case PROMPT_DEBUGLOGS:
DebugLogsPromptDialogFragment.show(this, getSupportFragmentManager());
case PROMPT_DEBUGLOGS_FOR_NOTIFICATIONS:
DebugLogsPromptDialogFragment.show(this, DebugLogsPromptDialogFragment.Purpose.NOTIFICATIONS);
case PROMPT_DEBUGLOGS_FOR_CRASH:
DebugLogsPromptDialogFragment.show(this, DebugLogsPromptDialogFragment.Purpose.CRASH);
break;
}
}
@ -137,10 +134,7 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handleGroupLinkInIntent(intent);
handleProxyInIntent(intent);
handleSignalMeIntent(intent);
handleCallLinkInIntent(intent);
handleDeeplinkIntent(intent);
}
@Override
@ -173,35 +167,9 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
updateTabVisibility();
slowNotificationsViewModel.checkSlowNotificationHeuristics();
//**TM_SA**// start
notifyMessageIfNeeded();
vitalsViewModel.checkSlowNotificationHeuristics();
}
private void notifyMessageIfNeeded() {
boolean isAlreadyRestarted = PrefManager.getBooleanPref(this, ArchivePreferenceConstants.PREF_KEY_MAIN_ACTIVITY_RESTART, false);
if(!isAlreadyRestarted){
PrefManager.setBooleanPref(this, ArchivePreferenceConstants.PREF_KEY_MAIN_ACTIVITY_RESTART, true);
final Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
@Override
public void run() {
notifyMessage();
}
}, 4000);
}
}
public synchronized void notifyMessage(){
synchronized (ApplicationDependencies.getIncomingMessageObserver()) {
ApplicationDependencies.getIncomingMessageObserver().notifyAll();
}
}
//**TM_SA**// End
@Override
protected void onStop() {
super.onStop();
@ -232,6 +200,14 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
return navigator;
}
private void handleDeeplinkIntent(Intent intent) {
handleGroupLinkInIntent(intent);
handleProxyInIntent(intent);
handleSignalMeIntent(intent);
handleCallLinkInIntent(intent);
handleDonateReturnIntent(intent);
}
private void handleGroupLinkInIntent(Intent intent) {
Uri data = intent.getData();
if (data != null) {
@ -260,6 +236,13 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
}
}
private void handleDonateReturnIntent(Intent intent) {
Uri data = intent.getData();
if (data != null && data.toString().startsWith(StripeApi.RETURN_URL_IDEAL)) {
startActivity(AppSettingsActivity.manageSubscriptions(this));
}
}
public void onFirstRender() {
onFirstRender = true;
}

View file

@ -51,7 +51,6 @@ import org.tm.archive.keyvalue.SignalStore;
import org.tm.archive.recipients.Recipient;
import org.tm.archive.recipients.RecipientId;
import org.tm.archive.util.CommunicationActions;
import org.tm.archive.util.FeatureFlags;
import org.tm.archive.util.views.SimpleProgressDialog;
import java.io.IOException;
@ -313,7 +312,7 @@ public class NewConversationActivity extends ContactSelectionActivity
}
private @Nullable ActionItem createRemoveActionItem(@NonNull Recipient recipient) {
if (!FeatureFlags.hideContacts() || recipient.isSelf() || recipient.isGroup()) {
if (recipient.isSelf() || recipient.isGroup()) {
return null;
}

View file

@ -21,7 +21,6 @@ import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.InputType;

View file

@ -24,7 +24,7 @@ import org.tm.archive.lock.v2.CreateSvrPinActivity;
import org.tm.archive.migrations.ApplicationMigrationActivity;
import org.tm.archive.migrations.ApplicationMigrations;
import org.tm.archive.pin.PinRestoreActivity;
import org.tm.archive.profiles.edit.EditProfileActivity;
import org.tm.archive.profiles.edit.CreateProfileActivity;
import org.tm.archive.push.SignalServiceNetworkAccess;
import org.tm.archive.recipients.Recipient;
import org.tm.archive.registration.RegistrationNavigationActivity;
@ -228,7 +228,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
}
private Intent getCreateProfileNameIntent() {
Intent intent = EditProfileActivity.getIntentForUserProfile(this);
Intent intent = CreateProfileActivity.getIntentForUserProfile(this);
return getRoutedIntent(intent, getIntent());
}

View file

@ -57,7 +57,8 @@ import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.IdentityKey;
import org.tm.archive.components.TooltipPopup;
import org.tm.archive.components.sensors.DeviceOrientationMonitor;
import org.tm.archive.components.webrtc.CallLinkInfoSheet;
import org.tm.archive.components.webrtc.CallLinkProfileKeySender;
import org.tm.archive.components.webrtc.CallOverflowPopupWindow;
import org.tm.archive.components.webrtc.CallParticipantsListUpdatePopupWindow;
import org.tm.archive.components.webrtc.CallParticipantsState;
import org.tm.archive.components.webrtc.CallStateUpdatePopupWindow;
@ -72,13 +73,16 @@ import org.tm.archive.components.webrtc.WebRtcCallView;
import org.tm.archive.components.webrtc.WebRtcCallViewModel;
import org.tm.archive.components.webrtc.WebRtcControls;
import org.tm.archive.components.webrtc.WifiToCellularPopupWindow;
import org.tm.archive.components.webrtc.controls.ControlsAndInfoController;
import org.tm.archive.components.webrtc.controls.ControlsAndInfoViewModel;
import org.tm.archive.components.webrtc.participantslist.CallParticipantsListDialog;
import org.tm.archive.components.webrtc.requests.CallLinkIncomingRequestSheet;
import org.tm.archive.conversation.ui.error.SafetyNumberChangeDialog;
import org.tm.archive.dependencies.ApplicationDependencies;
import org.tm.archive.events.WebRtcViewModel;
import org.tm.archive.messagerequests.CalleeMustAcceptMessageRequestActivity;
import org.tm.archive.permissions.Permissions;
import org.tm.archive.recipients.LiveRecipient;
import org.tm.archive.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment;
import org.tm.archive.recipients.Recipient;
import org.tm.archive.recipients.RecipientId;
import org.tm.archive.safety.SafetyNumberBottomSheet;
@ -93,11 +97,13 @@ import org.tm.archive.util.TextSecurePreferences;
import org.tm.archive.util.ThrottledDebouncer;
import org.tm.archive.util.Util;
import org.tm.archive.util.VibrateUtil;
import org.tm.archive.util.WindowUtil;
import org.tm.archive.util.livedata.LiveDataUtil;
import org.tm.archive.webrtc.CallParticipantsViewState;
import org.tm.archive.webrtc.audio.SignalAudioManager;
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@ -109,7 +115,7 @@ import io.reactivex.rxjava3.disposables.Disposable;
import static org.tm.archive.components.sensors.Orientation.PORTRAIT_BOTTOM_EDGE;
public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChangeDialog.Callback {
public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChangeDialog.Callback, ReactWithAnyEmojiBottomSheetDialogFragment.Callback {
private static final String TAG = Log.tag(WebRtcCallActivity.class);
@ -134,13 +140,16 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
private CallParticipantsListUpdatePopupWindow participantUpdateWindow;
private CallStateUpdatePopupWindow callStateUpdatePopupWindow;
private CallOverflowPopupWindow callOverflowPopupWindow;
private WifiToCellularPopupWindow wifiToCellularPopupWindow;
private DeviceOrientationMonitor deviceOrientationMonitor;
private FullscreenHelper fullscreenHelper;
private WebRtcCallView callScreen;
private TooltipPopup videoTooltip;
private TooltipPopup switchCameraTooltip;
private WebRtcCallViewModel viewModel;
private ControlsAndInfoViewModel controlsAndInfoViewModel;
private boolean enableVideoIfAvailable;
private boolean hasWarnedAboutBluetooth;
private WindowLayoutInfoConsumer windowLayoutInfoConsumer;
@ -149,6 +158,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
private PictureInPictureParams.Builder pipBuilderParams;
private LifecycleDisposable lifecycleDisposable;
private long lastCallLinkDisconnectDialogShowTime;
private ControlsAndInfoController controlsAndInfo;
private Disposable ephemeralStateDisposable = Disposable.empty();
@ -158,7 +168,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
super.attachBaseContext(newBase);
}
@SuppressLint("SourceLockedOrientationActivity")
@SuppressLint({ "SourceLockedOrientationActivity", "MissingInflatedId" })
@Override
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate(" + getIntent().getBooleanExtra(EXTRA_STARTED_FROM_FULLSCREEN, false) + ")");
@ -186,6 +196,16 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
initializeViewModel(isLandscapeEnabled);
initializePictureInPictureParams();
controlsAndInfo = new ControlsAndInfoController(this, callScreen, callOverflowPopupWindow, viewModel, controlsAndInfoViewModel);
controlsAndInfo.addVisibilityListener(new FadeCallback());
fullscreenHelper.showAndHideWithSystemUI(getWindow(),
findViewById(R.id.call_screen_header_gradient),
findViewById(R.id.webrtc_call_view_toolbar_text),
findViewById(R.id.webrtc_call_view_toolbar_no_text));
lifecycleDisposable.add(controlsAndInfo);
logIntent(getIntent());
if (ANSWER_VIDEO_ACTION.equals(getIntent().getAction())) {
@ -207,6 +227,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
requestNewSizesThrottle = new ThrottledDebouncer(TimeUnit.SECONDS.toMillis(1));
initializePendingParticipantFragmentListener();
WindowUtil.setNavigationBarColor(this, ContextCompat.getColor(this, R.color.signal_dark_colorSurface));
}
@Override
@ -416,6 +438,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
participantUpdateWindow = new CallParticipantsListUpdatePopupWindow(callScreen);
callStateUpdatePopupWindow = new CallStateUpdatePopupWindow(callScreen);
wifiToCellularPopupWindow = new WifiToCellularPopupWindow(callScreen);
callOverflowPopupWindow = new CallOverflowPopupWindow(this, callScreen, () -> {
CallParticipantsState state = viewModel.getCallParticipantsStateSnapshot();
if (state == null) {
return false;
}
return state.getLocalParticipant().isHandRaised();
});
}
private void initializeViewModel(boolean isLandscapeEnabled) {
@ -428,7 +457,10 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
viewModel.setIsLandscapeEnabled(isLandscapeEnabled);
viewModel.setIsInPipMode(isInPipMode());
viewModel.getMicrophoneEnabled().observe(this, callScreen::setMicEnabled);
viewModel.getWebRtcControls().observe(this, callScreen::setWebRtcControls);
viewModel.getWebRtcControls().observe(this, controls -> {
callScreen.setWebRtcControls(controls);
controlsAndInfo.updateControls(controls);
});
viewModel.getEvents().observe(this, this::handleViewModelEvent);
lifecycleDisposable.add(viewModel.getInCallstatus().subscribe(this::handleInCallStatus));
@ -472,6 +504,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
.subscribe(callScreen::updatePendingParticipantsList);
lifecycleDisposable.add(disposable);
controlsAndInfoViewModel = new ViewModelProvider(this).get(ControlsAndInfoViewModel.class);
}
private void initializePictureInPictureParams() {
@ -519,7 +553,6 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
.setText(R.string.WebRtcCallActivity__tap_here_to_turn_on_your_video)
.setOnDismissListener(() -> viewModel.onDismissedVideoTooltip())
.show(TooltipPopup.POSITION_ABOVE);
return;
}
} else if (event instanceof WebRtcCallViewModel.Event.DismissVideoTooltip) {
if (videoTooltip != null) {
@ -528,6 +561,20 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
}
} else if (event instanceof WebRtcCallViewModel.Event.ShowWifiToCellularPopup) {
wifiToCellularPopupWindow.show();
} else if (event instanceof WebRtcCallViewModel.Event.ShowSwitchCameraTooltip) {
if (switchCameraTooltip == null) {
switchCameraTooltip = TooltipPopup.forTarget(callScreen.getSwitchCameraTooltipTarget())
.setBackgroundTint(ContextCompat.getColor(this, R.color.core_ultramarine))
.setTextColor(ContextCompat.getColor(this, R.color.core_white))
.setText(R.string.WebRtcCallActivity__flip_camera_tooltip)
.setOnDismissListener(() -> viewModel.onDismissedSwitchCameraTooltip())
.show(TooltipPopup.POSITION_ABOVE);
}
} else if (event instanceof WebRtcCallViewModel.Event.DismissSwitchCameraTooltip) {
if (switchCameraTooltip != null) {
switchCameraTooltip.dismiss();
switchCameraTooltip = null;
}
} else {
throw new IllegalArgumentException("Unknown event: " + event);
}
@ -774,6 +821,10 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
return;
}
if (state.isCallLink()) {
CallLinkProfileKeySender.onSendAnyway(new HashSet<>(changedRecipients));
}
if (state.getGroupCallState().isConnected()) {
ApplicationDependencies.getSignalCallManager().groupApproveSafetyChange(changedRecipients);
} else {
@ -818,6 +869,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
viewModel.setRecipient(event.getRecipient());
callScreen.setRecipient(event.getRecipient());
controlsAndInfoViewModel.setRecipient(event.getRecipient());
switch (event.getState()) {
case CALL_PRE_JOIN:
@ -905,7 +957,6 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
private void handleCallPreJoin(@NonNull WebRtcViewModel event) {
if (event.getGroupState().isNotIdle()) {
callScreen.setStatusFromGroupCallState(event.getGroupState());
callScreen.setRingGroup(event.shouldRingGroup());
if (event.shouldRingGroup() && event.areRemoteDevicesInCall()) {
@ -926,6 +977,15 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
MessageSender.onMessageSent();
}
@Override
public void onReactWithAnyEmojiDialogDismissed() { /* no-op */ }
@Override
public void onReactWithAnyEmojiSelected(@NonNull String emoji) {
ApplicationDependencies.getSignalCallManager().react(emoji);
callOverflowPopupWindow.dismiss();
}
private final class ControlsListener implements WebRtcCallView.ControlsListener {
@Override
@ -939,22 +999,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
}
@Override
public void onControlsFadeOut() {
if (videoTooltip != null) {
videoTooltip.dismiss();
public void toggleControls() {
WebRtcControls controlState = viewModel.getWebRtcControls().getValue();
if (controlState != null && !controlState.displayIncomingCallButtons()) {
controlsAndInfo.toggleControls();
}
}
@Override
public void showSystemUI() {
fullscreenHelper.showSystemUI();
}
@Override
public void hideSystemUI() {
fullscreenHelper.hideSystemUI();
}
@Override
public void onAudioOutputChanged(@NonNull WebRtcAudioOutput audioOutput) {
maybeDisplaySpeakerphonePopup(audioOutput);
@ -1015,6 +1066,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
handleAnswerWithAudio();
}
@Override
public void onOverflowClicked() {
controlsAndInfo.toggleOverflowPopup();
}
@Override
public void onAcceptCallPressed() {
if (viewModel.isAnswerWithVideoAvailable()) {
@ -1032,6 +1088,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
@Override
public void onLocalPictureInPictureClicked() {
viewModel.onLocalPictureInPictureClicked();
controlsAndInfo.restartHideControlsTimer();
}
@Override
@ -1048,13 +1105,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
@Override
public void onCallInfoClicked() {
LiveRecipient liveRecipient = viewModel.getRecipient();
if (liveRecipient.get().isCallLink()) {
CallLinkInfoSheet.show(getSupportFragmentManager(), liveRecipient.get().requireCallLinkRoomId());
} else {
CallParticipantsListDialog.show(getSupportFragmentManager());
}
controlsAndInfo.showCallInfo();
}
@Override
@ -1088,6 +1139,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
public void onLaunchPendingRequestsSheet() {
new PendingParticipantsBottomSheet().show(getSupportFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
}
@Override
public void onLaunchRecipientSheet(@NonNull Recipient pendingRecipient) {
CallLinkIncomingRequestSheet.show(getSupportFragmentManager(), pendingRecipient.getId());
}
}
private class WindowLayoutInfoConsumer implements Consumer<WindowLayoutInfo> {
@ -1112,4 +1168,20 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
}
}
}
private class FadeCallback implements ControlsAndInfoController.BottomSheetVisibilityListener {
@Override
public void onShown() {
fullscreenHelper.showSystemUI();
}
@Override
public void onHidden() {
fullscreenHelper.hideSystemUI();
if (videoTooltip != null) {
videoTooltip.dismiss();
}
}
}
}

View file

@ -1,10 +1,10 @@
package org.tm.archive.absbackup.backupables
import com.google.protobuf.InvalidProtocolBufferException
import org.signal.core.util.logging.Log
import org.tm.archive.absbackup.AndroidBackupItem
import org.tm.archive.absbackup.protos.SvrAuthToken
import org.tm.archive.keyvalue.SignalStore
import java.io.IOException
/**
* This backs up the not-secret KBS Auth tokens, which can be combined with a PIN to prove ownership of a phone number in order to complete the registration process.
@ -30,7 +30,7 @@ object SvrAuthTokens : AndroidBackupItem {
val proto = SvrAuthToken.ADAPTER.decode(data)
SignalStore.svr().putAuthTokenList(proto.tokens)
} catch (e: InvalidProtocolBufferException) {
} catch (e: IOException) {
Log.w(TAG, "Cannot restore KbsAuthToken from backup service.")
}
}

View file

@ -1,5 +1,6 @@
package org.tm.archive.animation;
import android.graphics.Point;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
@ -16,6 +17,10 @@ public class ResizeAnimation extends Animation {
private int startWidth;
private int startHeight;
public ResizeAnimation(@NonNull View target, @NonNull Point dimension) {
this(target, dimension.x, dimension.y);
}
public ResizeAnimation(@NonNull View target, int targetWidthPx, int targetHeightPx) {
this.target = target;
this.targetWidthPx = targetWidthPx;

View file

@ -0,0 +1,42 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.tm.archive.apkupdate
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import org.signal.core.util.logging.Log
import org.tm.archive.keyvalue.SignalStore
/**
* Provided to the DownloadManager as a callback receiver for when it has finished downloading the APK we're trying to install.
*
* Registered in the manifest to list to [DownloadManager.ACTION_DOWNLOAD_COMPLETE].
*/
class ApkUpdateDownloadManagerReceiver : BroadcastReceiver() {
companion object {
private val TAG = Log.tag(ApkUpdateDownloadManagerReceiver::class.java)
}
override fun onReceive(context: Context, intent: Intent) {
Log.i(TAG, "onReceive()")
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE != intent.action) {
Log.i(TAG, "Unexpected action: " + intent.action)
return
}
val downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -2)
if (downloadId != SignalStore.apkUpdate().downloadId) {
Log.w(TAG, "downloadId doesn't match the one we're waiting for! Ignoring.")
return
}
ApkUpdateInstaller.installOrPromptForInstall(context, downloadId, userInitiated = false)
}
}

View file

@ -0,0 +1,150 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.tm.archive.apkupdate
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import android.os.Build
import org.signal.core.util.PendingIntentFlags
import org.signal.core.util.StreamUtil
import org.signal.core.util.getDownloadManager
import org.signal.core.util.logging.Log
import org.tm.archive.dependencies.ApplicationDependencies
import org.tm.archive.jobs.ApkUpdateJob
import org.tm.archive.keyvalue.SignalStore
import org.tm.archive.util.Environment
import org.tm.archive.util.FileUtils
import java.io.FileInputStream
import java.io.IOException
import java.io.InputStream
import java.security.MessageDigest
object ApkUpdateInstaller {
private val TAG = Log.tag(ApkUpdateInstaller::class.java)
/**
* Installs the downloaded APK silently if possible. If not, prompts the user with a notification to install.
* May show errors instead under certain conditions.
*
* A common pattern you may see is that this is called with [userInitiated] = false (or some other state
* that prevents us from auto-updating, like the app being in the foreground), causing this function
* to show an install prompt notification. The user clicks that notification, calling this with
* [userInitiated] = true, and then everything installs.
*/
fun installOrPromptForInstall(context: Context, downloadId: Long, userInitiated: Boolean) {
if (downloadId != SignalStore.apkUpdate().downloadId) {
Log.w(TAG, "DownloadId doesn't match the one we're waiting for (current: $downloadId, expected: ${SignalStore.apkUpdate().downloadId})! We likely have newer data. Ignoring.")
ApkUpdateNotifications.dismissInstallPrompt(context)
ApplicationDependencies.getJobManager().add(ApkUpdateJob())
return
}
val digest = SignalStore.apkUpdate().digest
if (digest == null) {
Log.w(TAG, "DownloadId matches, but digest is null! Inconsistent state. Failing and clearing state.")
SignalStore.apkUpdate().clearDownloadAttributes()
ApkUpdateNotifications.showInstallFailed(context, ApkUpdateNotifications.FailureReason.UNKNOWN)
return
}
if (!isMatchingDigest(context, downloadId, digest)) {
Log.w(TAG, "DownloadId matches, but digest does not! Bad download or inconsistent state. Failing and clearing state.")
SignalStore.apkUpdate().clearDownloadAttributes()
ApkUpdateNotifications.showInstallFailed(context, ApkUpdateNotifications.FailureReason.UNKNOWN)
return
}
if (!userInitiated && !shouldAutoUpdate()) {
Log.w(TAG, "Not user-initiated and not eligible for auto-update. Prompting. (API=${Build.VERSION.SDK_INT}, Foreground=${ApplicationDependencies.getAppForegroundObserver().isForegrounded}, AutoUpdate=${SignalStore.apkUpdate().autoUpdate})")
ApkUpdateNotifications.showInstallPrompt(context, downloadId)
return
}
try {
installApk(context, downloadId, userInitiated)
} catch (e: IOException) {
Log.w(TAG, "Hit IOException when trying to install APK!", e)
SignalStore.apkUpdate().clearDownloadAttributes()
ApkUpdateNotifications.showInstallFailed(context, ApkUpdateNotifications.FailureReason.UNKNOWN)
} catch (e: SecurityException) {
Log.w(TAG, "Hit SecurityException when trying to install APK!", e)
SignalStore.apkUpdate().clearDownloadAttributes()
ApkUpdateNotifications.showInstallFailed(context, ApkUpdateNotifications.FailureReason.UNKNOWN)
}
}
@Throws(IOException::class, SecurityException::class)
private fun installApk(context: Context, downloadId: Long, userInitiated: Boolean) {
val apkInputStream: InputStream? = getDownloadedApkInputStream(context, downloadId)
if (apkInputStream == null) {
Log.w(TAG, "Could not open download APK input stream!")
return
}
Log.d(TAG, "Beginning APK install...")
val packageInstaller: PackageInstaller = context.packageManager.packageInstaller
val sessionParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL).apply {
// At this point, we always want to set this if possible, since we've already prompted the user with our own notification when necessary.
// This lets us skip the system-generated notification.
if (Build.VERSION.SDK_INT >= 31) {
setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
}
}
Log.d(TAG, "Creating install session...")
val sessionId: Int = packageInstaller.createSession(sessionParams)
val session: PackageInstaller.Session = packageInstaller.openSession(sessionId)
Log.d(TAG, "Writing APK data...")
session.use { activeSession ->
val sessionOutputStream = activeSession.openWrite(context.packageName, 0, -1)
StreamUtil.copy(apkInputStream, sessionOutputStream)
}
val installerPendingIntent = PendingIntent.getBroadcast(
context,
sessionId,
Intent(context, ApkUpdatePackageInstallerReceiver::class.java).apply {
putExtra(ApkUpdatePackageInstallerReceiver.EXTRA_USER_INITIATED, userInitiated)
putExtra(ApkUpdatePackageInstallerReceiver.EXTRA_DOWNLOAD_ID, downloadId)
},
PendingIntentFlags.mutable() or PendingIntentFlags.updateCurrent()
)
Log.d(TAG, "Committing session...")
session.commit(installerPendingIntent.intentSender)
}
private fun getDownloadedApkInputStream(context: Context, downloadId: Long): InputStream? {
return try {
FileInputStream(context.getDownloadManager().openDownloadedFile(downloadId).fileDescriptor)
} catch (e: IOException) {
Log.w(TAG, e)
null
}
}
private fun isMatchingDigest(context: Context, downloadId: Long, expectedDigest: ByteArray): Boolean {
return try {
FileInputStream(context.getDownloadManager().openDownloadedFile(downloadId).fileDescriptor).use { stream ->
val digest = FileUtils.getFileDigest(stream)
MessageDigest.isEqual(digest, expectedDigest)
}
} catch (e: IOException) {
Log.w(TAG, e)
false
}
}
private fun shouldAutoUpdate(): Boolean {
// TODO Auto-updates temporarily restricted to nightlies. Once we have designs for allowing users to opt-out of auto-updates, we can re-enable this
return Environment.IS_NIGHTLY && Build.VERSION.SDK_INT >= 31 && SignalStore.apkUpdate().autoUpdate && !ApplicationDependencies.getAppForegroundObserver().isForegrounded
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.tm.archive.apkupdate
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import org.signal.core.util.logging.Log
/**
* Receiver that is triggered based on various notification actions that can be taken on update-related notifications.
*/
class ApkUpdateNotificationReceiver : BroadcastReceiver() {
companion object {
private val TAG = Log.tag(ApkUpdateNotificationReceiver::class.java)
const val ACTION_INITIATE_INSTALL = "signal.apk_update_notification.initiate_install"
const val EXTRA_DOWNLOAD_ID = "signal.download_id"
}
override fun onReceive(context: Context, intent: Intent?) {
if (intent == null) {
Log.w(TAG, "Null intent")
return
}
val downloadId: Long = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -2)
when (val action: String? = intent.action) {
ACTION_INITIATE_INSTALL -> handleInstall(context, downloadId)
else -> Log.w(TAG, "Unrecognized notification action: $action")
}
}
private fun handleInstall(context: Context, downloadId: Long) {
Log.i(TAG, "Got action to install.")
ApkUpdateInstaller.installOrPromptForInstall(context, downloadId, userInitiated = true)
}
}

View file

@ -0,0 +1,118 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.tm.archive.apkupdate
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import org.signal.core.util.PendingIntentFlags
import org.signal.core.util.logging.Log
import org.tm.archive.MainActivity
import org.tm.archive.R
import org.tm.archive.notifications.NotificationChannels
import org.tm.archive.notifications.NotificationIds
import org.tm.archive.util.ServiceUtil
object ApkUpdateNotifications {
val TAG = Log.tag(ApkUpdateNotifications::class.java)
/**
* Shows a notification to prompt the user to install the app update. Only shown when silently auto-updating is not possible or are disabled by the user.
* Note: This is an 'ongoing' notification (i.e. not-user dismissable) and never dismissed programatically. This is because the act of installing the APK
* will dismiss it for us.
*/
@SuppressLint("LaunchActivityFromNotification")
fun showInstallPrompt(context: Context, downloadId: Long) {
Log.d(TAG, "Showing install prompt. DownloadId: $downloadId")
ServiceUtil.getNotificationManager(context).cancel(NotificationIds.APK_UPDATE_FAILED_INSTALL)
val pendingIntent = PendingIntent.getBroadcast(
context,
1,
Intent(context, ApkUpdateNotificationReceiver::class.java).apply {
action = ApkUpdateNotificationReceiver.ACTION_INITIATE_INSTALL
putExtra(ApkUpdateNotificationReceiver.EXTRA_DOWNLOAD_ID, downloadId)
},
PendingIntentFlags.updateCurrent()
)
val notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().APP_UPDATES)
.setOngoing(true)
.setContentTitle(context.getString(R.string.ApkUpdateNotifications_prompt_install_title))
.setContentText(context.getString(R.string.ApkUpdateNotifications_prompt_install_body))
.setSmallIcon(R.drawable.ic_notification)
.setColor(ContextCompat.getColor(context, R.color.core_ultramarine))
.setContentIntent(pendingIntent)
.build()
ServiceUtil.getNotificationManager(context).notify(NotificationIds.APK_UPDATE_PROMPT_INSTALL, notification)
}
fun dismissInstallPrompt(context: Context) {
Log.d(TAG, "Dismissing install prompt.")
ServiceUtil.getNotificationManager(context).cancel(NotificationIds.APK_UPDATE_PROMPT_INSTALL)
}
fun showInstallFailed(context: Context, reason: FailureReason) {
Log.d(TAG, "Showing failed notification. Reason: $reason")
ServiceUtil.getNotificationManager(context).cancel(NotificationIds.APK_UPDATE_PROMPT_INSTALL)
val pendingIntent = PendingIntent.getActivity(
context,
0,
Intent(context, MainActivity::class.java),
PendingIntentFlags.immutable()
)
val notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().APP_UPDATES)
.setContentTitle(context.getString(R.string.ApkUpdateNotifications_failed_general_title))
.setContentText(context.getString(R.string.ApkUpdateNotifications_failed_general_body))
.setSmallIcon(R.drawable.ic_notification)
.setColor(ContextCompat.getColor(context, R.color.core_ultramarine))
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build()
ServiceUtil.getNotificationManager(context).notify(NotificationIds.APK_UPDATE_FAILED_INSTALL, notification)
}
fun showAutoUpdateSuccess(context: Context) {
val pendingIntent = PendingIntent.getActivity(
context,
0,
Intent(context, MainActivity::class.java),
PendingIntentFlags.immutable()
)
val appVersionName = context.packageManager.getPackageInfo(context.packageName, 0).versionName
val notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().APP_UPDATES)
.setContentTitle(context.getString(R.string.ApkUpdateNotifications_auto_update_success_title))
.setContentText(context.getString(R.string.ApkUpdateNotifications_auto_update_success_body, appVersionName))
.setSmallIcon(R.drawable.ic_notification)
.setColor(ContextCompat.getColor(context, R.color.core_ultramarine))
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build()
ServiceUtil.getNotificationManager(context).notify(NotificationIds.APK_UPDATE_SUCCESSFUL_INSTALL, notification)
}
enum class FailureReason {
UNKNOWN,
ABORTED,
BLOCKED,
INCOMPATIBLE,
INVALID,
CONFLICT,
STORAGE,
TIMEOUT
}
}

View file

@ -0,0 +1,84 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.tm.archive.apkupdate
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import org.signal.core.util.getParcelableExtraCompat
import org.signal.core.util.logging.Log
import org.tm.archive.apkupdate.ApkUpdateNotifications.FailureReason
import org.tm.archive.keyvalue.SignalStore
/**
* This is the receiver that is triggered by the [PackageInstaller] to notify of various events. Package installation is initiated
* in [ApkUpdateInstaller].
*/
class ApkUpdatePackageInstallerReceiver : BroadcastReceiver() {
companion object {
private val TAG = Log.tag(ApkUpdatePackageInstallerReceiver::class.java)
const val EXTRA_USER_INITIATED = "signal.user_initiated"
const val EXTRA_DOWNLOAD_ID = "signal.download_id"
}
override fun onReceive(context: Context, intent: Intent?) {
val statusCode: Int = intent?.getIntExtra(PackageInstaller.EXTRA_STATUS, -1) ?: -1
val statusMessage: String? = intent?.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
val userInitiated = intent?.getBooleanExtra(EXTRA_USER_INITIATED, false) ?: false
Log.w(TAG, "[onReceive] Status: $statusCode, Message: $statusMessage")
when (statusCode) {
PackageInstaller.STATUS_SUCCESS -> {
if (SignalStore.apkUpdate().lastApkUploadTime != SignalStore.apkUpdate().pendingApkUploadTime) {
Log.i(TAG, "Update installed successfully! Updating our lastApkUploadTime to ${SignalStore.apkUpdate().pendingApkUploadTime}")
SignalStore.apkUpdate().lastApkUploadTime = SignalStore.apkUpdate().pendingApkUploadTime
ApkUpdateNotifications.showAutoUpdateSuccess(context)
} else {
Log.i(TAG, "Spurious 'success' notification?")
}
}
PackageInstaller.STATUS_PENDING_USER_ACTION -> handlePendingUserAction(context, userInitiated, intent!!)
PackageInstaller.STATUS_FAILURE_ABORTED -> ApkUpdateNotifications.showInstallFailed(context, FailureReason.ABORTED)
PackageInstaller.STATUS_FAILURE_BLOCKED -> ApkUpdateNotifications.showInstallFailed(context, FailureReason.BLOCKED)
PackageInstaller.STATUS_FAILURE_INCOMPATIBLE -> ApkUpdateNotifications.showInstallFailed(context, FailureReason.INCOMPATIBLE)
PackageInstaller.STATUS_FAILURE_INVALID -> ApkUpdateNotifications.showInstallFailed(context, FailureReason.INVALID)
PackageInstaller.STATUS_FAILURE_CONFLICT -> ApkUpdateNotifications.showInstallFailed(context, FailureReason.CONFLICT)
PackageInstaller.STATUS_FAILURE_STORAGE -> ApkUpdateNotifications.showInstallFailed(context, FailureReason.STORAGE)
PackageInstaller.STATUS_FAILURE_TIMEOUT -> ApkUpdateNotifications.showInstallFailed(context, FailureReason.TIMEOUT)
PackageInstaller.STATUS_FAILURE -> ApkUpdateNotifications.showInstallFailed(context, FailureReason.UNKNOWN)
else -> Log.w(TAG, "Unknown status! $statusCode")
}
}
private fun handlePendingUserAction(context: Context, userInitiated: Boolean, intent: Intent) {
val downloadId = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -2)
if (!userInitiated) {
Log.w(TAG, "Not user-initiated, but needs user action! Showing prompt notification.")
ApkUpdateNotifications.showInstallPrompt(context, downloadId)
return
}
val promptIntent: Intent? = intent.getParcelableExtraCompat(Intent.EXTRA_INTENT, Intent::class.java)
if (promptIntent == null) {
Log.w(TAG, "Missing prompt intent! Showing prompt notification instead.")
ApkUpdateNotifications.showInstallPrompt(context, downloadId)
return
}
promptIntent.apply {
putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, "com.android.vending")
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(promptIntent)
}
}

View file

@ -1,22 +1,28 @@
package org.tm.archive.service;
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.tm.archive.apkupdate;
import android.content.Context;
import android.content.Intent;
import org.signal.core.util.logging.Log;
import org.tm.archive.BuildConfig;
import org.tm.archive.dependencies.ApplicationDependencies;
import org.tm.archive.jobs.UpdateApkJob;
import org.tm.archive.jobs.ApkUpdateJob;
import org.tm.archive.service.PersistentAlarmManagerListener;
import org.tm.archive.util.Environment;
import org.tm.archive.util.TextSecurePreferences;
import java.util.concurrent.TimeUnit;
public class UpdateApkRefreshListener extends PersistentAlarmManagerListener {
public class ApkUpdateRefreshListener extends PersistentAlarmManagerListener {
private static final String TAG = Log.tag(UpdateApkRefreshListener.class);
private static final String TAG = Log.tag(ApkUpdateRefreshListener.class);
private static final long INTERVAL = TimeUnit.HOURS.toMillis(6);
private static final long INTERVAL = Environment.IS_NIGHTLY ? TimeUnit.HOURS.toMillis(2) : TimeUnit.HOURS.toMillis(6);
@Override
protected long getNextScheduledExecutionTime(Context context) {
@ -27,9 +33,9 @@ public class UpdateApkRefreshListener extends PersistentAlarmManagerListener {
protected long onAlarm(Context context, long scheduledTime) {
Log.i(TAG, "onAlarm...");
if (scheduledTime != 0 && BuildConfig.PLAY_STORE_DISABLED) {
if (scheduledTime != 0 && BuildConfig.MANAGES_APP_UPDATES) {
Log.i(TAG, "Queueing APK update job...");
ApplicationDependencies.getJobManager().add(new UpdateApkJob());
ApplicationDependencies.getJobManager().add(new ApkUpdateJob());
}
long newTime = System.currentTimeMillis() + INTERVAL;
@ -39,7 +45,7 @@ public class UpdateApkRefreshListener extends PersistentAlarmManagerListener {
}
public static void schedule(Context context) {
new UpdateApkRefreshListener().onReceive(context, getScheduleIntent());
new ApkUpdateRefreshListener().onReceive(context, getScheduleIntent());
}
}

View file

@ -1,234 +0,0 @@
package org.tm.archive.attachments;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.tm.archive.audio.AudioHash;
import org.tm.archive.blurhash.BlurHash;
import org.tm.archive.database.AttachmentTable;
import org.tm.archive.database.AttachmentTable.TransformProperties;
import org.tm.archive.stickers.StickerLocator;
public abstract class Attachment {
@NonNull
private final String contentType;
private final int transferState;
private final long size;
@Nullable
private final String fileName;
private final int cdnNumber;
@Nullable
private final String location;
@Nullable
private final String key;
@Nullable
private final String relay;
@Nullable
private final byte[] digest;
@Nullable
private final byte[] incrementalDigest;
@Nullable
private final String fastPreflightId;
private final boolean voiceNote;
private final boolean borderless;
private final boolean videoGif;
private final int width;
private final int height;
private final boolean quote;
private final long uploadTimestamp;
@Nullable
private final String caption;
@Nullable
private final StickerLocator stickerLocator;
@Nullable
private final BlurHash blurHash;
@Nullable
private final AudioHash audioHash;
@NonNull
private final TransformProperties transformProperties;
public Attachment(@NonNull String contentType,
int transferState,
long size,
@Nullable String fileName,
int cdnNumber,
@Nullable String location,
@Nullable String key,
@Nullable String relay,
@Nullable byte[] digest,
@Nullable byte[] incrementalDigest,
@Nullable String fastPreflightId,
boolean voiceNote,
boolean borderless,
boolean videoGif,
int width,
int height,
boolean quote,
long uploadTimestamp,
@Nullable String caption,
@Nullable StickerLocator stickerLocator,
@Nullable BlurHash blurHash,
@Nullable AudioHash audioHash,
@Nullable TransformProperties transformProperties)
{
this.contentType = contentType;
this.transferState = transferState;
this.size = size;
this.fileName = fileName;
this.cdnNumber = cdnNumber;
this.location = location;
this.key = key;
this.relay = relay;
this.digest = digest;
this.incrementalDigest = incrementalDigest;
this.fastPreflightId = fastPreflightId;
this.voiceNote = voiceNote;
this.borderless = borderless;
this.videoGif = videoGif;
this.width = width;
this.height = height;
this.quote = quote;
this.uploadTimestamp = uploadTimestamp;
this.stickerLocator = stickerLocator;
this.caption = caption;
this.blurHash = blurHash;
this.audioHash = audioHash;
this.transformProperties = transformProperties != null ? transformProperties : TransformProperties.empty();
}
@Nullable
public abstract Uri getUri();
public abstract @Nullable Uri getPublicUri();
public int getTransferState() {
return transferState;
}
public boolean isInProgress() {
return transferState != AttachmentTable.TRANSFER_PROGRESS_DONE &&
transferState != AttachmentTable.TRANSFER_PROGRESS_FAILED &&
transferState != AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE;
}
public boolean isPermanentlyFailed() {
return transferState == AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE;
}
public long getSize() {
return size;
}
@Nullable
public String getFileName() {
return fileName;
}
@NonNull
public String getContentType() {
return contentType;
}
public int getCdnNumber() {
return cdnNumber;
}
@Nullable
public String getLocation() {
return location;
}
@Nullable
public String getKey() {
return key;
}
@Nullable
public String getRelay() {
return relay;
}
@Nullable
public byte[] getDigest() {
return digest;
}
@Nullable
public byte[] getIncrementalDigest() {
return incrementalDigest;
}
@Nullable
public String getFastPreflightId() {
return fastPreflightId;
}
public boolean isVoiceNote() {
return voiceNote;
}
public boolean isBorderless() {
return borderless;
}
public boolean isVideoGif() {
return videoGif;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public boolean isQuote() {
return quote;
}
public long getUploadTimestamp() {
return uploadTimestamp;
}
public boolean isSticker() {
return stickerLocator != null;
}
public @Nullable StickerLocator getSticker() {
return stickerLocator;
}
public @Nullable BlurHash getBlurHash() {
return blurHash;
}
public @Nullable AudioHash getAudioHash() {
return audioHash;
}
public @Nullable String getCaption() {
return caption;
}
public @NonNull TransformProperties getTransformProperties() {
return transformProperties;
}
}

View file

@ -0,0 +1,152 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.tm.archive.attachments
import android.net.Uri
import android.os.Parcel
import android.os.Parcelable
import androidx.core.os.ParcelCompat
import org.tm.archive.audio.AudioHash
import org.tm.archive.blurhash.BlurHash
import org.tm.archive.database.AttachmentTable
import org.tm.archive.database.AttachmentTable.TransformProperties
import org.tm.archive.stickers.StickerLocator
import org.tm.archive.util.ParcelUtil
/**
* Note: We have to use our own Parcelable implementation because we need to do custom stuff to preserve
* subclass information.
*/
abstract class Attachment(
@JvmField
val contentType: String,
@JvmField
val transferState: Int,
@JvmField
val size: Long,
@JvmField
val fileName: String?,
@JvmField
val cdnNumber: Int,
@JvmField
val remoteLocation: String?,
@JvmField
val remoteKey: String?,
@JvmField
val remoteDigest: ByteArray?,
@JvmField
val incrementalDigest: ByteArray?,
@JvmField
val fastPreflightId: String?,
@JvmField
val voiceNote: Boolean,
@JvmField
val borderless: Boolean,
@JvmField
val videoGif: Boolean,
@JvmField
val width: Int,
@JvmField
val height: Int,
@JvmField
val incrementalMacChunkSize: Int,
@JvmField
val quote: Boolean,
@JvmField
val uploadTimestamp: Long,
@JvmField
val caption: String?,
@JvmField
val stickerLocator: StickerLocator?,
@JvmField
val blurHash: BlurHash?,
@JvmField
val audioHash: AudioHash?,
@JvmField
val transformProperties: TransformProperties?
) : Parcelable {
abstract val uri: Uri?
abstract val publicUri: Uri?
protected constructor(parcel: Parcel) : this(
contentType = parcel.readString()!!,
transferState = parcel.readInt(),
size = parcel.readLong(),
fileName = parcel.readString(),
cdnNumber = parcel.readInt(),
remoteLocation = parcel.readString(),
remoteKey = parcel.readString(),
remoteDigest = ParcelUtil.readByteArray(parcel),
incrementalDigest = ParcelUtil.readByteArray(parcel),
fastPreflightId = parcel.readString(),
voiceNote = ParcelUtil.readBoolean(parcel),
borderless = ParcelUtil.readBoolean(parcel),
videoGif = ParcelUtil.readBoolean(parcel),
width = parcel.readInt(),
height = parcel.readInt(),
incrementalMacChunkSize = parcel.readInt(),
quote = ParcelUtil.readBoolean(parcel),
uploadTimestamp = parcel.readLong(),
caption = parcel.readString(),
stickerLocator = ParcelCompat.readParcelable(parcel, StickerLocator::class.java.classLoader, StickerLocator::class.java),
blurHash = ParcelCompat.readParcelable(parcel, BlurHash::class.java.classLoader, BlurHash::class.java),
audioHash = ParcelCompat.readParcelable(parcel, AudioHash::class.java.classLoader, AudioHash::class.java),
transformProperties = ParcelCompat.readParcelable(parcel, TransformProperties::class.java.classLoader, TransformProperties::class.java)
)
override fun writeToParcel(dest: Parcel, flags: Int) {
AttachmentCreator.writeSubclass(dest, this)
dest.writeString(contentType)
dest.writeInt(transferState)
dest.writeLong(size)
dest.writeString(fileName)
dest.writeInt(cdnNumber)
dest.writeString(remoteLocation)
dest.writeString(remoteKey)
ParcelUtil.writeByteArray(dest, remoteDigest)
ParcelUtil.writeByteArray(dest, incrementalDigest)
dest.writeString(fastPreflightId)
ParcelUtil.writeBoolean(dest, voiceNote)
ParcelUtil.writeBoolean(dest, borderless)
ParcelUtil.writeBoolean(dest, videoGif)
dest.writeInt(width)
dest.writeInt(height)
dest.writeInt(incrementalMacChunkSize)
ParcelUtil.writeBoolean(dest, quote)
dest.writeLong(uploadTimestamp)
dest.writeString(caption)
dest.writeParcelable(stickerLocator, 0)
dest.writeParcelable(blurHash, 0)
dest.writeParcelable(audioHash, 0)
dest.writeParcelable(transformProperties, 0)
}
override fun describeContents(): Int {
return 0
}
val isInProgress: Boolean
get() = transferState != AttachmentTable.TRANSFER_PROGRESS_DONE && transferState != AttachmentTable.TRANSFER_PROGRESS_FAILED && transferState != AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE
val isPermanentlyFailed: Boolean
get() = transferState == AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE
val isSticker: Boolean
get() = stickerLocator != null
fun getIncrementalDigest(): ByteArray? {
return if (incrementalDigest != null && incrementalDigest.size > 0) {
incrementalDigest
} else {
null
}
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<Attachment> = AttachmentCreator
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.tm.archive.attachments
import android.os.Parcel
import android.os.Parcelable
/**
* Parcelable Creator for Attachments. Encapsulates the logic around dealing with
* subclasses, since Attachment is abstract and has several children.
*/
object AttachmentCreator : Parcelable.Creator<Attachment> {
enum class Subclass(val clazz: Class<out Attachment>, val code: String) {
DATABASE(DatabaseAttachment::class.java, "database"),
POINTER(PointerAttachment::class.java, "pointer"),
TOMBSTONE(TombstoneAttachment::class.java, "tombstone"),
URI(UriAttachment::class.java, "uri")
}
@JvmStatic
fun writeSubclass(dest: Parcel, instance: Attachment) {
val subclass = Subclass.values().firstOrNull { it.clazz == instance::class.java } ?: error("Unexpected subtype ${instance::class.java.simpleName}")
dest.writeString(subclass.code)
}
override fun createFromParcel(source: Parcel): Attachment {
val rawCode = source.readString()!!
return when (Subclass.values().first { rawCode == it.code }) {
Subclass.DATABASE -> DatabaseAttachment(source)
Subclass.POINTER -> PointerAttachment(source)
Subclass.TOMBSTONE -> TombstoneAttachment(source)
Subclass.URI -> UriAttachment(source)
}
}
override fun newArray(size: Int): Array<Attachment?> = arrayOfNulls(size)
}

View file

@ -1,89 +0,0 @@
package org.tm.archive.attachments;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.tm.archive.util.Util;
public class AttachmentId implements Parcelable {
@JsonProperty
private final long rowId;
@JsonProperty
private final long uniqueId;
public AttachmentId(@JsonProperty("rowId") long rowId, @JsonProperty("uniqueId") long uniqueId) {
this.rowId = rowId;
this.uniqueId = uniqueId;
}
private AttachmentId(Parcel in) {
this.rowId = in.readLong();
this.uniqueId = in.readLong();
}
public long getRowId() {
return rowId;
}
public long getUniqueId() {
return uniqueId;
}
public String[] toStrings() {
return new String[] {String.valueOf(rowId), String.valueOf(uniqueId)};
}
public @NonNull String toString() {
return "AttachmentId::(" + rowId + ", " + uniqueId + ")";
}
public boolean isValid() {
return rowId >= 0 && uniqueId >= 0;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AttachmentId attachmentId = (AttachmentId)o;
if (rowId != attachmentId.rowId) return false;
return uniqueId == attachmentId.uniqueId;
}
@Override
public int hashCode() {
return Util.hashCode(rowId, uniqueId);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(rowId);
dest.writeLong(uniqueId);
}
public static final Creator<AttachmentId> CREATOR = new Creator<AttachmentId>() {
@Override
public AttachmentId createFromParcel(Parcel in) {
return new AttachmentId(in);
}
@Override
public AttachmentId[] newArray(int size) {
return new AttachmentId[size];
}
};
}

View file

@ -0,0 +1,20 @@
package org.tm.archive.attachments
import android.os.Parcelable
import com.fasterxml.jackson.annotation.JsonProperty
import kotlinx.parcelize.Parcelize
@Parcelize
data class AttachmentId(
@JsonProperty("rowId")
@JvmField
val id: Long
) : Parcelable {
val isValid: Boolean
get() = id >= 0
override fun toString(): String {
return "AttachmentId::$id"
}
}

View file

@ -1,117 +0,0 @@
package org.tm.archive.attachments;
import android.net.Uri;
import androidx.annotation.Nullable;
import org.tm.archive.audio.AudioHash;
import org.tm.archive.blurhash.BlurHash;
import org.tm.archive.database.AttachmentTable.TransformProperties;
import org.tm.archive.mms.PartAuthority;
import org.tm.archive.stickers.StickerLocator;
import java.util.Comparator;
public class DatabaseAttachment extends Attachment {
private final AttachmentId attachmentId;
private final long mmsId;
private final boolean hasData;
private final boolean hasThumbnail;
private final int displayOrder;
public DatabaseAttachment(AttachmentId attachmentId,
long mmsId,
boolean hasData,
boolean hasThumbnail,
String contentType,
int transferProgress,
long size,
String fileName,
int cdnNumber,
String location,
String key,
String relay,
byte[] digest,
byte[] incrementalDigest,
String fastPreflightId,
boolean voiceNote,
boolean borderless,
boolean videoGif,
int width,
int height,
boolean quote,
@Nullable String caption,
@Nullable StickerLocator stickerLocator,
@Nullable BlurHash blurHash,
@Nullable AudioHash audioHash,
@Nullable TransformProperties transformProperties,
int displayOrder,
long uploadTimestamp)
{
super(contentType, transferProgress, size, fileName, cdnNumber, location, key, relay, digest, incrementalDigest, fastPreflightId, voiceNote, borderless, videoGif, width, height, quote, uploadTimestamp, caption, stickerLocator, blurHash, audioHash, transformProperties);
this.attachmentId = attachmentId;
this.hasData = hasData;
this.hasThumbnail = hasThumbnail;
this.mmsId = mmsId;
this.displayOrder = displayOrder;
}
@Override
@Nullable
public Uri getUri() {
if (hasData) {
return PartAuthority.getAttachmentDataUri(attachmentId);
} else {
return null;
}
}
@Override
public @Nullable Uri getPublicUri() {
if (hasData) {
return PartAuthority.getAttachmentPublicUri(getUri());
} else {
return null;
}
}
public AttachmentId getAttachmentId() {
return attachmentId;
}
public int getDisplayOrder() {
return displayOrder;
}
@Override
public boolean equals(Object other) {
return other != null &&
other instanceof DatabaseAttachment &&
((DatabaseAttachment) other).attachmentId.equals(this.attachmentId);
}
@Override
public int hashCode() {
return attachmentId.hashCode();
}
public long getMmsId() {
return mmsId;
}
public boolean hasData() {
return hasData;
}
public boolean hasThumbnail() {
return hasThumbnail;
}
public static class DisplayOrderComparator implements Comparator<DatabaseAttachment> {
@Override
public int compare(DatabaseAttachment lhs, DatabaseAttachment rhs) {
return Integer.compare(lhs.getDisplayOrder(), rhs.getDisplayOrder());
}
}
}

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