mirror of
https://github.com/micahflee/TM-SGNL-Android.git
synced 2025-07-07 13:25:18 +00:00
new baseline - 1
This commit is contained in:
parent
4a84ae1954
commit
1c47e8ba52
2688 changed files with 217611 additions and 59138 deletions
2
.github/workflows/android.yml
vendored
2
.github/workflows/android.yml
vendored
|
@ -18,6 +18,8 @@ jobs:
|
|||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
|
|
2
.github/workflows/diffuse.yml
vendored
2
.github/workflows/diffuse.yml
vendored
|
@ -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
3
.gitignore
vendored
|
@ -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
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "libwebp"]
|
||||
path = libwebp
|
||||
url = https://github.com/webmproject/libwebp.git
|
|
@ -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
|
||||
|
|
42
README.md
42
README.md
|
@ -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
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
761
app/build.gradle
761
app/build.gradle
|
@ -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
745
app/build.gradle.kts
Normal 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() }
|
||||
}
|
|
@ -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)
|
|
@ -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);
|
||||
|
|
|
@ -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.
Binary file not shown.
Binary file not shown.
|
@ -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
|
||||
|
|
10
app/lint.xml
10
app/lint.xml
|
@ -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" />
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
-keepattributes *Annotation*
|
||||
-keepclassmembers class * {
|
||||
@org.greenrobot.eventbus.Subscribe <methods>;
|
||||
}
|
||||
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
|
|
@ -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
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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) },
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)!!
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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!!))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)!!
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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(" ", "_");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
800
app/src/main/java/org/conscrypt/ConscryptSignal.java
Normal file
800
app/src/main/java/org/conscrypt/ConscryptSignal.java
Normal 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}*/
|
||||
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
package org.intune
|
||||
|
||||
interface MDMDialogListener {
|
||||
fun startIntuneAgain()
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -20,4 +20,5 @@ public interface BindableConversationListItem extends Unbindable {
|
|||
|
||||
void setSelectedConversations(@NonNull ConversationSet conversations);
|
||||
void updateTypingIndicator(@NonNull Set<Long> typingThreads);
|
||||
void updateTimestamp();
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
150
app/src/main/java/org/tm/archive/apkupdate/ApkUpdateInstaller.kt
Normal file
150
app/src/main/java/org/tm/archive/apkupdate/ApkUpdateInstaller.kt
Normal 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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
152
app/src/main/java/org/tm/archive/attachments/Attachment.kt
Normal file
152
app/src/main/java/org/tm/archive/attachments/Attachment.kt
Normal 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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
20
app/src/main/java/org/tm/archive/attachments/AttachmentId.kt
Normal file
20
app/src/main/java/org/tm/archive/attachments/AttachmentId.kt
Normal 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"
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue