diff --git a/.gitignore b/.gitignore
index 01ec4c41c..1fe35a0e7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,4 @@ signing.properties
ffpr
*.sh
pkcs11.password
-play
+app/play
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000..b650b98b1
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "libsession-util/libsession-util"]
+ path = libsession-util/libsession-util
+ url = https://github.com/oxen-io/libsession-util.git
diff --git a/BUILDING.md b/BUILDING.md
index e78207d96..48b4412dd 100644
--- a/BUILDING.md
+++ b/BUILDING.md
@@ -32,6 +32,13 @@ Setting up a development environment and building from Android Studio
4. Android Studio should detect the presence of a project file and ask you whether to open it. Click "yes".
5. Default config options should be good enough.
6. Project initialization and building should proceed.
+7. Clone submodules with `git submodule update --init --recursive`
+
+If you would like to build the Huawei Flavor with Huawei HMS push notifications you will need to pass 'huawei' as a command line arg to include the required dependencies.
+
+e.g. `./gradlew assembleHuaweiDebug -Phuawei`
+
+If you are building in Android Studio then add `-Phuawei` to `Preferences > Build, Execution, Deployment > Gradle-Android Compiler > Command-line Options`
Contributing code
-----------------
diff --git a/README.md b/README.md
index 341fd4284..723d50c75 100644
--- a/README.md
+++ b/README.md
@@ -4,13 +4,13 @@
Add the [F-Droid repo](https://fdroid.getsession.org/)
-[Download the APK from here](https://github.com/loki-project/session-android/releases/latest)
+[Download the APK from here](https://github.com/oxen-io/session-android/releases/latest)
## Summary
Session integrates directly with [Oxen Service Nodes](https://docs.oxen.io/about-the-oxen-blockchain/oxen-service-nodes), which are a set of distributed, decentralized and Sybil resistant nodes. Service Nodes act as servers which store messages offline, and a set of nodes which allow for onion routing functionality obfuscating users' IP addresses. For a full understanding of how Session works, read the [Session Whitepaper](https://getsession.org/whitepaper).
-
+
## Want to contribute? Found a bug or have a feature request?
diff --git a/app/build.gradle b/app/build.gradle
index 3dcc19726..61f560189 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,3 +1,4 @@
+
buildscript {
repositories {
google()
@@ -13,12 +14,16 @@ buildscript {
}
}
+plugins {
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'witness'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-parcelize'
-apply plugin: 'com.google.gms.google-services'
apply plugin: 'kotlinx-serialization'
apply plugin: 'dagger.hilt.android.plugin'
@@ -26,141 +31,8 @@ configurations.all {
exclude module: "commons-logging"
}
-dependencies {
- implementation "androidx.appcompat:appcompat:$appcompatVersion"
- implementation 'androidx.recyclerview:recyclerview:1.2.1'
- implementation "com.google.android.material:material:$materialVersion"
- implementation 'com.google.android:flexbox:2.0.1'
- implementation 'androidx.legacy:legacy-support-v13:1.0.0'
- implementation 'androidx.cardview:cardview:1.0.0'
- implementation "androidx.preference:preference-ktx:$preferenceVersion"
- implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
- implementation 'androidx.gridlayout:gridlayout:1.0.0'
- implementation 'androidx.exifinterface:exifinterface:1.3.4'
- implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
- implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
- implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
- implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
- implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
- implementation "androidx.lifecycle:lifecycle-process:$lifecycleVersion"
- implementation "androidx.paging:paging-runtime-ktx:$pagingVersion"
- implementation 'androidx.activity:activity-ktx:1.5.1'
- implementation 'androidx.fragment:fragment-ktx:1.5.3'
- implementation "androidx.core:core-ktx:$coreVersion"
- implementation "androidx.work:work-runtime-ktx:2.7.1"
- implementation ("com.google.firebase:firebase-messaging:18.0.0") {
- 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 'com.google.android.exoplayer:exoplayer-core:2.9.1'
- implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1'
- implementation 'org.conscrypt:conscrypt-android:2.0.0'
- implementation 'org.signal:aesgcmprovider:0.0.3'
- implementation 'org.webrtc:google-webrtc:1.0.32006'
- implementation "me.leolin:ShortcutBadger:1.1.16"
- implementation 'se.emilsjolander:stickylistheaders:2.7.0'
- implementation 'com.jpardogo.materialtabstrip:library:1.0.9'
- implementation 'org.apache.httpcomponents:httpclient-android:4.3.5'
- implementation 'commons-net:commons-net:3.7.2'
- implementation 'com.github.chrisbanes:PhotoView:2.1.3'
- implementation "com.github.bumptech.glide:glide:$glideVersion"
- annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"
- kapt "com.github.bumptech.glide:compiler:$glideVersion"
- implementation 'com.makeramen:roundedimageview:2.1.0'
- implementation 'com.pnikosis:materialish-progress:1.5'
- implementation 'org.greenrobot:eventbus:3.0.0'
- implementation 'pl.tajchert:waitingdots:0.1.0'
- implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0'
- implementation 'com.melnykov:floatingactionbutton:1.3.0'
- implementation 'com.google.zxing:android-integration:3.1.0'
- implementation "com.google.dagger:hilt-android:$daggerVersion"
- kapt "com.google.dagger:hilt-compiler:$daggerVersion"
- implementation 'mobi.upod:time-duration-picker:1.1.3'
- implementation 'com.google.zxing:core:3.2.1'
- implementation ('com.davemorrissey.labs:subsampling-scale-image-view:3.6.0') {
- exclude group: 'com.android.support', module: 'support-annotations'
- }
- implementation ('cn.carbswang.android:NumberPickerView:1.0.9') {
- exclude group: 'com.android.support', module: 'appcompat-v7'
- }
- implementation ('com.tomergoldst.android:tooltips:1.0.6') {
- exclude group: 'com.android.support', module: 'appcompat-v7'
- }
- implementation ('com.klinkerapps:android-smsmms:4.0.1') {
- exclude group: 'com.squareup.okhttp', module: 'okhttp'
- exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection'
- }
- implementation 'com.annimon:stream:1.1.8'
- implementation 'com.takisoft.fix:colorpicker:1.0.1'
- implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4'
- implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2'
- implementation 'androidx.sqlite:sqlite-ktx:2.2.0'
- implementation 'net.zetetic:sqlcipher-android:4.5.3@aar'
- implementation ('com.googlecode.ez-vcard:ez-vcard:0.9.11') {
- exclude group: 'com.fasterxml.jackson.core'
- exclude group: 'org.freemarker'
- }
- implementation project(":libsignal")
- implementation project(":libsession")
- implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxJsonVersion"
- implementation "com.github.oxen-io.session-android-curve-25519:curve25519-java:$curve25519Version"
- implementation project(":liblazysodium")
- implementation "net.java.dev.jna:jna:5.8.0@aar"
- implementation "com.google.protobuf:protobuf-java:$protobufVersion"
- implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonDatabindVersion"
- implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
- implementation 'app.cash.copper:copper-flow:1.0.0'
- implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
- implementation "nl.komponents.kovenant:kovenant:$kovenantVersion"
- implementation "nl.komponents.kovenant:kovenant-android:$kovenantVersion"
- implementation "com.github.lelloman:android-identicons:v11"
- implementation "com.prof.rssparser:rssparser:2.0.4"
- implementation "com.jakewharton.rxbinding3:rxbinding:3.1.0"
- implementation "com.github.tbruyelle:rxpermissions:0.10.2"
- implementation "com.github.ybq:Android-SpinKit:1.4.0"
- implementation "com.opencsv:opencsv:4.6"
- testImplementation "junit:junit:$junitVersion"
- testImplementation 'org.assertj:assertj-core:3.11.1'
- testImplementation "org.mockito:mockito-inline:4.0.0"
- testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
- testImplementation 'org.powermock:powermock-api-mockito:1.6.1'
- testImplementation 'org.powermock:powermock-module-junit4:1.6.1'
- testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.1'
- testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1'
- testImplementation "androidx.test:core:$testCoreVersion"
- testImplementation "androidx.arch.core:core-testing:2.1.0"
- testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
- androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
- // Core library
- androidTestImplementation 'androidx.test:core:1.4.0'
-
- // AndroidJUnitRunner and JUnit Rules
- androidTestImplementation 'androidx.test:runner:1.4.0'
- androidTestImplementation 'androidx.test:rules:1.4.0'
-
- // Assertions
- androidTestImplementation 'androidx.test.ext:junit:1.1.3'
- androidTestImplementation 'androidx.test.ext:truth:1.4.0'
- androidTestImplementation 'com.google.truth:truth:1.1.3'
-
- // Espresso dependencies
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
- androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.4.0'
- androidTestImplementation 'androidx.test.espresso:espresso-intents:3.4.0'
- androidTestImplementation 'androidx.test.espresso:espresso-accessibility:3.4.0'
- androidTestImplementation 'androidx.test.espresso:espresso-web:3.4.0'
- androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.4.0'
- androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.4.0'
- androidTestUtil 'androidx.test:orchestrator:1.4.1'
-
- testImplementation 'org.robolectric:robolectric:4.4'
- testImplementation 'org.robolectric:shadows-multidex:4.4'
-}
-
-def canonicalVersionCode = 323
-def canonicalVersionName = "1.16.3"
+def canonicalVersionCode = 354
+def canonicalVersionName = "1.17.0"
def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1,
@@ -202,6 +74,13 @@ android {
}
}
+ buildFeatures {
+ compose true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion '1.4.7'
+ }
+
defaultConfig {
versionCode canonicalVersionCode * postFixSize
versionName canonicalVersionName
@@ -250,14 +129,28 @@ android {
flavorDimensions "distribution"
productFlavors {
play {
+ dimension "distribution"
+ apply plugin: 'com.google.gms.google-services'
ext.websiteUpdateUrl = "null"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
+ buildConfigField "org.session.libsession.utilities.Device", "DEVICE", "org.session.libsession.utilities.Device.ANDROID"
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
}
+ huawei {
+ dimension "distribution"
+ ext.websiteUpdateUrl = "null"
+ buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
+ buildConfigField "org.session.libsession.utilities.Device", "DEVICE", "org.session.libsession.utilities.Device.HUAWEI"
+ buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
+
+ }
+
website {
+ dimension "distribution"
ext.websiteUpdateUrl = "https://github.com/oxen-io/session-android/releases"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
+ buildConfigField "org.session.libsession.utilities.Device", "DEVICE", "org.session.libsession.utilities.Device.ANDROID"
buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\""
}
}
@@ -288,6 +181,166 @@ android {
dataBinding true
viewBinding true
}
+
+ def huaweiEnabled = project.properties['huawei'] != null
+
+ applicationVariants.configureEach { variant ->
+ if (variant.flavorName == 'huawei') {
+ variant.getPreBuildProvider().configure { task ->
+ task.doFirst {
+ if (!huaweiEnabled) {
+ def message = 'Huawei is not enabled. Please add -Phuawei command line arg. See BUILDING.md'
+ logger.error(message)
+ throw new GradleException(message)
+ }
+ }
+ }
+ }
+ }
+}
+
+dependencies {
+
+ implementation("com.google.dagger:hilt-android:2.46.1")
+ kapt("com.google.dagger:hilt-android-compiler:2.44")
+
+ implementation "androidx.appcompat:appcompat:$appcompatVersion"
+ implementation 'androidx.recyclerview:recyclerview:1.2.1'
+ implementation "com.google.android.material:material:$materialVersion"
+ implementation 'com.google.android:flexbox:2.0.1'
+ implementation 'androidx.legacy:legacy-support-v13:1.0.0'
+ implementation 'androidx.cardview:cardview:1.0.0'
+ implementation "androidx.preference:preference-ktx:$preferenceVersion"
+ implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
+ implementation 'androidx.gridlayout:gridlayout:1.0.0'
+ implementation 'androidx.exifinterface:exifinterface:1.3.4'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
+ implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
+ implementation "androidx.lifecycle:lifecycle-process:$lifecycleVersion"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+ implementation "androidx.paging:paging-runtime-ktx:$pagingVersion"
+ implementation 'androidx.activity:activity-ktx:1.5.1'
+ implementation 'androidx.fragment:fragment-ktx:1.5.3'
+ implementation "androidx.core:core-ktx:$coreVersion"
+ implementation "androidx.work:work-runtime-ktx:2.7.1"
+ playImplementation ("com.google.firebase:firebase-messaging:18.0.0") {
+ 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'
+ }
+ if (project.hasProperty('huawei')) huaweiImplementation 'com.huawei.hms:push:6.7.0.300'
+ implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1'
+ implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1'
+ implementation 'org.conscrypt:conscrypt-android:2.0.0'
+ implementation 'org.signal:aesgcmprovider:0.0.3'
+ implementation 'org.webrtc:google-webrtc:1.0.32006'
+ implementation "me.leolin:ShortcutBadger:1.1.16"
+ implementation 'se.emilsjolander:stickylistheaders:2.7.0'
+ implementation 'com.jpardogo.materialtabstrip:library:1.0.9'
+ implementation 'org.apache.httpcomponents:httpclient-android:4.3.5'
+ implementation 'commons-net:commons-net:3.7.2'
+ implementation 'com.github.chrisbanes:PhotoView:2.1.3'
+ implementation "com.github.bumptech.glide:glide:$glideVersion"
+ annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"
+ kapt "com.github.bumptech.glide:compiler:$glideVersion"
+ implementation 'com.makeramen:roundedimageview:2.1.0'
+ implementation 'com.pnikosis:materialish-progress:1.5'
+ implementation 'org.greenrobot:eventbus:3.0.0'
+ implementation 'pl.tajchert:waitingdots:0.1.0'
+ implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0'
+ implementation 'com.melnykov:floatingactionbutton:1.3.0'
+ implementation 'com.google.zxing:android-integration:3.1.0'
+ implementation "com.google.dagger:hilt-android:$daggerVersion"
+ kapt "com.google.dagger:hilt-compiler:$daggerVersion"
+ implementation 'mobi.upod:time-duration-picker:1.1.3'
+ implementation 'com.google.zxing:core:3.2.1'
+ implementation ('com.davemorrissey.labs:subsampling-scale-image-view:3.6.0') {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ }
+ implementation ('cn.carbswang.android:NumberPickerView:1.0.9') {
+ exclude group: 'com.android.support', module: 'appcompat-v7'
+ }
+ implementation ('com.tomergoldst.android:tooltips:1.0.6') {
+ exclude group: 'com.android.support', module: 'appcompat-v7'
+ }
+ implementation ('com.klinkerapps:android-smsmms:4.0.1') {
+ exclude group: 'com.squareup.okhttp', module: 'okhttp'
+ exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection'
+ }
+ implementation 'com.annimon:stream:1.1.8'
+ implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4'
+ implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2'
+ implementation 'androidx.sqlite:sqlite-ktx:2.3.1'
+ implementation 'net.zetetic:sqlcipher-android:4.5.4@aar'
+ implementation project(":libsignal")
+ implementation project(":libsession")
+ implementation project(":libsession-util")
+ implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxJsonVersion"
+ implementation "com.github.oxen-io.session-android-curve-25519:curve25519-java:$curve25519Version"
+ implementation project(":liblazysodium")
+ implementation "net.java.dev.jna:jna:5.8.0@aar"
+ implementation "com.google.protobuf:protobuf-java:$protobufVersion"
+ implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonDatabindVersion"
+ implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
+ implementation 'app.cash.copper:copper-flow:1.0.0'
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
+ implementation "nl.komponents.kovenant:kovenant:$kovenantVersion"
+ implementation "nl.komponents.kovenant:kovenant-android:$kovenantVersion"
+ implementation "com.jakewharton.rxbinding3:rxbinding:3.1.0"
+ implementation "com.github.tbruyelle:rxpermissions:0.10.2"
+ implementation "com.github.ybq:Android-SpinKit:1.4.0"
+ implementation "com.opencsv:opencsv:4.6"
+ testImplementation "junit:junit:$junitVersion"
+ testImplementation 'org.assertj:assertj-core:3.11.1'
+ testImplementation "org.mockito:mockito-inline:4.10.0"
+ testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
+ androidTestImplementation "org.mockito:mockito-android:4.10.0"
+ androidTestImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
+ testImplementation "androidx.test:core:$testCoreVersion"
+ testImplementation "androidx.arch.core:core-testing:2.2.0"
+ testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
+ androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
+ // Core library
+ androidTestImplementation "androidx.test:core:$testCoreVersion"
+
+ androidTestImplementation('com.adevinta.android:barista:4.2.0') {
+ exclude group: 'org.jetbrains.kotlin'
+ }
+
+ // AndroidJUnitRunner and JUnit Rules
+ androidTestImplementation 'androidx.test:runner:1.5.2'
+ androidTestImplementation 'androidx.test:rules:1.5.0'
+
+ // Assertions
+ androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+ androidTestImplementation 'androidx.test.ext:truth:1.5.0'
+ androidTestImplementation 'com.google.truth:truth:1.1.3'
+
+ // Espresso dependencies
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-accessibility:3.5.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-web:3.5.1'
+ androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.5.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.5.1'
+ androidTestUtil 'androidx.test:orchestrator:1.4.2'
+
+ testImplementation 'org.robolectric:robolectric:4.4'
+ testImplementation 'org.robolectric:shadows-multidex:4.4'
+
+ implementation 'com.github.bumptech.glide:compose:1.0.0-alpha.1'
+ implementation 'androidx.compose.ui:ui:1.4.3'
+ implementation 'androidx.compose.ui:ui-tooling:1.4.3'
+ implementation "com.google.accompanist:accompanist-themeadapter-appcompat:0.31.5-beta"
+ implementation "com.google.accompanist:accompanist-pager-indicators:0.31.5-beta"
+ implementation "androidx.compose.runtime:runtime-livedata:1.4.3"
+
+ implementation 'androidx.compose.foundation:foundation-layout:1.5.0-alpha02'
+ implementation 'androidx.compose.material:material:1.5.0-alpha02'
}
static def getLastCommitTimestamp() {
@@ -308,3 +361,8 @@ def autoResConfig() {
.collect { matcher -> matcher.group(1) }
.sort()
}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes = true
+}
diff --git a/app/src/androidTest/java/network/loki/messenger/HomeActivityTests.kt b/app/src/androidTest/java/network/loki/messenger/HomeActivityTests.kt
index 087d48689..eabe06f7d 100644
--- a/app/src/androidTest/java/network/loki/messenger/HomeActivityTests.kt
+++ b/app/src/androidTest/java/network/loki/messenger/HomeActivityTests.kt
@@ -1,5 +1,6 @@
package network.loki.messenger
+import android.Manifest
import android.app.Instrumentation
import android.content.ClipboardManager
import android.content.Context
@@ -21,6 +22,7 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
+import com.adevinta.android.barista.interaction.PermissionGranter
import network.loki.messenger.util.InputBarButtonDrawableMatcher.Companion.inputButtonWithDrawable
import org.hamcrest.Matcher
import org.hamcrest.Matchers.allOf
@@ -85,6 +87,8 @@ class HomeActivityTests {
}
onView(withId(R.id.backgroundPollingOptionView)).perform(ViewActions.click())
onView(withId(R.id.registerButton)).perform(ViewActions.click())
+ // allow notification permission
+ PermissionGranter.allowPermissionsIfNeeded(Manifest.permission.POST_NOTIFICATIONS)
}
private fun goToMyChat() {
@@ -100,6 +104,7 @@ class HomeActivityTests {
copied = clipboardManager.primaryClip!!.getItemAt(0).text.toString()
}
onView(withId(R.id.publicKeyEditText)).perform(ViewActions.typeText(copied))
+ onView(withId(R.id.publicKeyEditText)).perform(ViewActions.closeSoftKeyboard())
onView(withId(R.id.createPrivateChatButton)).perform(ViewActions.click())
}
diff --git a/app/src/androidTest/java/network/loki/messenger/LibSessionTests.kt b/app/src/androidTest/java/network/loki/messenger/LibSessionTests.kt
new file mode 100644
index 000000000..59cb8ede0
--- /dev/null
+++ b/app/src/androidTest/java/network/loki/messenger/LibSessionTests.kt
@@ -0,0 +1,102 @@
+package network.loki.messenger
+
+import androidx.core.content.edit
+import androidx.preference.PreferenceManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import network.loki.messenger.libsession_util.ConfigBase
+import network.loki.messenger.libsession_util.Contacts
+import network.loki.messenger.libsession_util.util.Contact
+import network.loki.messenger.libsession_util.util.ExpiryMode
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.argThat
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.session.libsession.messaging.MessagingModuleConfiguration
+import org.session.libsession.utilities.TextSecurePreferences
+import org.session.libsignal.utilities.KeyHelper
+import org.session.libsignal.utilities.hexEncodedPublicKey
+import org.thoughtcrime.securesms.ApplicationContext
+import org.thoughtcrime.securesms.crypto.KeyPairUtilities
+import kotlin.random.Random
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class LibSessionTests {
+
+ private fun randomSeedBytes() = (0 until 16).map { Random.nextInt(UByte.MAX_VALUE.toInt()).toByte() }
+ private fun randomKeyPair() = KeyPairUtilities.generate(randomSeedBytes().toByteArray())
+ private fun randomSessionId() = randomKeyPair().x25519KeyPair.hexEncodedPublicKey
+
+ private var fakeHashI = 0
+ private val nextFakeHash: String
+ get() = "fakehash${fakeHashI++}"
+
+ private fun maybeGetUserInfo(): Pair? {
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as ApplicationContext
+ val prefs = appContext.prefs
+ val localUserPublicKey = prefs.getLocalNumber()
+ val secretKey = with(appContext) {
+ val edKey = KeyPairUtilities.getUserED25519KeyPair(this) ?: return null
+ edKey.secretKey.asBytes
+ }
+ return if (localUserPublicKey == null || secretKey == null) null
+ else secretKey to localUserPublicKey
+ }
+
+ private fun buildContactMessage(contactList: List): ByteArray {
+ val (key,_) = maybeGetUserInfo()!!
+ val contacts = Contacts.Companion.newInstance(key)
+ contactList.forEach { contact ->
+ contacts.set(contact)
+ }
+ return contacts.push().config
+ }
+
+ private fun fakePollNewConfig(configBase: ConfigBase, toMerge: ByteArray) {
+ configBase.merge(nextFakeHash to toMerge)
+ MessagingModuleConfiguration.shared.configFactory.persist(configBase, System.currentTimeMillis())
+ }
+
+ @Before
+ fun setupUser() {
+ PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getInstrumentation().targetContext.applicationContext).edit {
+ putBoolean(TextSecurePreferences.HAS_FORCED_NEW_CONFIG, true).apply()
+ }
+ val newBytes = randomSeedBytes().toByteArray()
+ val context = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
+ val kp = KeyPairUtilities.generate(newBytes)
+ KeyPairUtilities.store(context, kp.seed, kp.ed25519KeyPair, kp.x25519KeyPair)
+ val registrationID = KeyHelper.generateRegistrationId(false)
+ TextSecurePreferences.setLocalRegistrationId(context, registrationID)
+ TextSecurePreferences.setLocalNumber(context, kp.x25519KeyPair.hexEncodedPublicKey)
+ TextSecurePreferences.setRestorationTime(context, 0)
+ TextSecurePreferences.setHasViewedSeed(context, false)
+ }
+
+ @Test
+ fun migration_one_to_ones() {
+ val app = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as ApplicationContext
+ val storageSpy = spy(app.storage)
+ app.storage = storageSpy
+
+ val newContactId = randomSessionId()
+ val singleContact = Contact(
+ id = newContactId,
+ approved = true,
+ expiryMode = ExpiryMode.NONE
+ )
+ val newContactMerge = buildContactMessage(listOf(singleContact))
+ val contacts = MessagingModuleConfiguration.shared.configFactory.contacts!!
+ fakePollNewConfig(contacts, newContactMerge)
+ verify(storageSpy).addLibSessionContacts(argThat {
+ first().let { it.id == newContactId && it.approved } && size == 1
+ })
+ verify(storageSpy).setRecipientApproved(argThat { address.serialize() == newContactId }, eq(true))
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/huawei/AndroidManifest.xml b/app/src/huawei/AndroidManifest.xml
new file mode 100644
index 000000000..dad7ab3ac
--- /dev/null
+++ b/app/src/huawei/AndroidManifest.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/huawei/agconnect-services.json b/app/src/huawei/agconnect-services.json
new file mode 100644
index 000000000..0c81d0477
--- /dev/null
+++ b/app/src/huawei/agconnect-services.json
@@ -0,0 +1,96 @@
+{
+ "agcgw":{
+ "backurl":"connect-dre.hispace.hicloud.com",
+ "url":"connect-dre.dbankcloud.cn",
+ "websocketbackurl":"connect-ws-dre.hispace.dbankcloud.com",
+ "websocketurl":"connect-ws-dre.hispace.dbankcloud.cn"
+ },
+ "agcgw_all":{
+ "CN":"connect-drcn.dbankcloud.cn",
+ "CN_back":"connect-drcn.hispace.hicloud.com",
+ "DE":"connect-dre.dbankcloud.cn",
+ "DE_back":"connect-dre.hispace.hicloud.com",
+ "RU":"connect-drru.hispace.dbankcloud.ru",
+ "RU_back":"connect-drru.hispace.dbankcloud.cn",
+ "SG":"connect-dra.dbankcloud.cn",
+ "SG_back":"connect-dra.hispace.hicloud.com"
+ },
+ "websocketgw_all":{
+ "CN":"connect-ws-drcn.hispace.dbankcloud.cn",
+ "CN_back":"connect-ws-drcn.hispace.dbankcloud.com",
+ "DE":"connect-ws-dre.hispace.dbankcloud.cn",
+ "DE_back":"connect-ws-dre.hispace.dbankcloud.com",
+ "RU":"connect-ws-drru.hispace.dbankcloud.ru",
+ "RU_back":"connect-ws-drru.hispace.dbankcloud.cn",
+ "SG":"connect-ws-dra.hispace.dbankcloud.cn",
+ "SG_back":"connect-ws-dra.hispace.dbankcloud.com"
+ },
+ "client":{
+ "cp_id":"890061000023000573",
+ "product_id":"99536292102532562",
+ "client_id":"954244311350791232",
+ "client_secret":"555999202D718B6744DAD2E923B386DC17F3F4E29F5105CE0D061EED328DADEE",
+ "project_id":"99536292102532562",
+ "app_id":"107205081",
+ "api_key":"DAEDABeddLEqUy0QRwa1THLwRA0OqrSuyci/HjNvVSmsdWsXRM2U2hRaCyqfvGYH1IFOKrauArssz/WPMLRHCYxliWf+DTj9bDwlWA==",
+ "package_name":"network.loki.messenger"
+ },
+ "oauth_client":{
+ "client_id":"107205081",
+ "client_type":1
+ },
+ "app_info":{
+ "app_id":"107205081",
+ "package_name":"network.loki.messenger"
+ },
+ "service":{
+ "analytics":{
+ "collector_url":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
+ "collector_url_ru":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com",
+ "collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn",
+ "collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
+ "collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn",
+ "resource_id":"p1",
+ "channel_id":""
+ },
+ "edukit":{
+ "edu_url":"edukit.edu.cloud.huawei.com.cn",
+ "dh_url":"edukit.edu.cloud.huawei.com.cn"
+ },
+ "search":{
+ "url":"https://search-dre.cloud.huawei.com"
+ },
+ "cloudstorage":{
+ "storage_url_sg_back":"https://agc-storage-dra.cloud.huawei.asia",
+ "storage_url_ru_back":"https://agc-storage-drru.cloud.huawei.ru",
+ "storage_url_ru":"https://agc-storage-drru.cloud.huawei.ru",
+ "storage_url_de_back":"https://agc-storage-dre.cloud.huawei.eu",
+ "storage_url_de":"https://ops-dre.agcstorage.link",
+ "storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn",
+ "storage_url_sg":"https://ops-dra.agcstorage.link",
+ "storage_url_cn_back":"https://agc-storage-drcn.cloud.huawei.com.cn",
+ "storage_url_cn":"https://agc-storage-drcn.platform.dbankcloud.cn"
+ },
+ "ml":{
+ "mlservice_url":"ml-api-dre.ai.dbankcloud.com,ml-api-dre.ai.dbankcloud.cn"
+ }
+ },
+ "region":"DE",
+ "configuration_version":"3.0",
+ "appInfos":[
+ {
+ "package_name":"network.loki.messenger",
+ "client":{
+ "app_id":"107205081"
+ },
+ "app_info":{
+ "package_name":"network.loki.messenger",
+ "app_id":"107205081"
+ },
+ "oauth_client":{
+ "client_type":1,
+ "client_id":"107205081"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt
new file mode 100644
index 000000000..26a484df1
--- /dev/null
+++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt
@@ -0,0 +1,13 @@
+package org.thoughtcrime.securesms.notifications
+
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class HuaweiBindingModule {
+ @Binds
+ abstract fun bindTokenFetcher(tokenFetcher: HuaweiTokenFetcher): TokenFetcher
+}
diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushService.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushService.kt
new file mode 100644
index 000000000..dc7bf893d
--- /dev/null
+++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushService.kt
@@ -0,0 +1,40 @@
+package org.thoughtcrime.securesms.notifications
+
+import android.os.Bundle
+import com.huawei.hms.push.HmsMessageService
+import com.huawei.hms.push.RemoteMessage
+import dagger.hilt.android.AndroidEntryPoint
+import org.json.JSONException
+import org.session.libsession.utilities.TextSecurePreferences
+import org.session.libsignal.utilities.Base64
+import org.session.libsignal.utilities.Log
+import java.lang.Exception
+import javax.inject.Inject
+
+private val TAG = HuaweiPushService::class.java.simpleName
+
+@AndroidEntryPoint
+class HuaweiPushService: HmsMessageService() {
+ @Inject lateinit var pushRegistry: PushRegistry
+ @Inject lateinit var pushReceiver: PushReceiver
+
+ override fun onMessageReceived(message: RemoteMessage?) {
+ Log.d(TAG, "onMessageReceived")
+ message?.dataOfMap?.takeIf { it.isNotEmpty() }?.let(pushReceiver::onPush) ?:
+ pushReceiver.onPush(message?.data?.let(Base64::decode))
+ }
+
+ override fun onNewToken(token: String?) {
+ pushRegistry.register(token)
+ }
+
+ override fun onNewToken(token: String?, bundle: Bundle?) {
+ Log.d(TAG, "New HCM token: $token.")
+ pushRegistry.register(token)
+ }
+
+ override fun onDeletedMessages() {
+ Log.d(TAG, "onDeletedMessages")
+ pushRegistry.refresh(false)
+ }
+}
diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiTokenFetcher.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiTokenFetcher.kt
new file mode 100644
index 000000000..9d9b61ce9
--- /dev/null
+++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiTokenFetcher.kt
@@ -0,0 +1,29 @@
+package org.thoughtcrime.securesms.notifications
+
+import android.content.Context
+import com.huawei.hms.aaid.HmsInstanceId
+import dagger.Lazy
+import dagger.hilt.android.qualifiers.ApplicationContext
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.session.libsignal.utilities.Log
+import javax.inject.Inject
+import javax.inject.Singleton
+
+private const val APP_ID = "107205081"
+private const val TOKEN_SCOPE = "HCM"
+
+@Singleton
+class HuaweiTokenFetcher @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val pushRegistry: Lazy,
+): TokenFetcher {
+ override suspend fun fetch(): String? = HmsInstanceId.getInstance(context).run {
+ // https://developer.huawei.com/consumer/en/doc/development/HMS-Guides/push-basic-capability#h2-1576218800370
+ // getToken may return an empty string, if so HuaweiPushService#onNewToken will be called.
+ withContext(Dispatchers.IO) { getToken(APP_ID, TOKEN_SCOPE) }
+ }
+}
diff --git a/app/src/huawei/res/values/strings.xml b/app/src/huawei/res/values/strings.xml
new file mode 100644
index 000000000..78d42b3e3
--- /dev/null
+++ b/app/src/huawei/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+
+ You\'ll be notified of new messages reliably and immediately using Huawei’s notification servers.
+ You\'ll be notified of new messages reliably and immediately using Huawei’s notification servers.
+
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5cdaec7a8..331cd53e0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -29,12 +29,16 @@
android:name="android.hardware.touchscreen"
android:required="false" />
+
+
+
+
@@ -313,14 +317,6 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.home.HomeActivity" />
-
-
-
-
-
-
-
-
-
-
@@ -453,17 +443,9 @@
-
-
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
index e506967ff..1102c68e7 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
@@ -41,6 +41,8 @@ import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPol
import org.session.libsession.messaging.sending_receiving.pollers.Poller;
import org.session.libsession.snode.SnodeModule;
import org.session.libsession.utilities.Address;
+import org.session.libsession.utilities.ConfigFactoryUpdateListener;
+import org.session.libsession.utilities.Device;
import org.session.libsession.utilities.ProfilePictureUtilities;
import org.session.libsession.utilities.SSKEnvironment;
import org.session.libsession.utilities.TextSecurePreferences;
@@ -56,35 +58,29 @@ import org.signal.aesgcmprovider.AesGcmProvider;
import org.thoughtcrime.securesms.components.TypingStatusSender;
import org.thoughtcrime.securesms.crypto.KeyPairUtilities;
import org.thoughtcrime.securesms.database.EmojiSearchDatabase;
-import org.thoughtcrime.securesms.database.JobDatabase;
import org.thoughtcrime.securesms.database.LokiAPIDatabase;
import org.thoughtcrime.securesms.database.Storage;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.EmojiSearchData;
+import org.thoughtcrime.securesms.dependencies.AppComponent;
+import org.thoughtcrime.securesms.dependencies.ConfigFactory;
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
import org.thoughtcrime.securesms.dependencies.DatabaseModule;
import org.thoughtcrime.securesms.emoji.EmojiSource;
import org.thoughtcrime.securesms.groups.OpenGroupManager;
-import org.thoughtcrime.securesms.groups.OpenGroupMigrator;
import org.thoughtcrime.securesms.home.HomeActivity;
-import org.thoughtcrime.securesms.jobmanager.JobManager;
-import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
-import org.thoughtcrime.securesms.jobs.FastJobStorage;
-import org.thoughtcrime.securesms.jobs.JobManagerFactories;
import org.thoughtcrime.securesms.logging.AndroidLogger;
import org.thoughtcrime.securesms.logging.PersistentLogger;
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
import org.thoughtcrime.securesms.notifications.BackgroundPollWorker;
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier;
-import org.thoughtcrime.securesms.notifications.FcmUtils;
-import org.thoughtcrime.securesms.notifications.LokiPushNotificationManager;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier;
+import org.thoughtcrime.securesms.notifications.PushRegistry;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.service.KeyCachingService;
-import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
import org.thoughtcrime.securesms.sskenvironment.ProfileManager;
import org.thoughtcrime.securesms.sskenvironment.ReadReceiptManager;
import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository;
@@ -114,7 +110,8 @@ import dagger.hilt.EntryPoints;
import dagger.hilt.android.HiltAndroidApp;
import kotlin.Unit;
import kotlinx.coroutines.Job;
-import network.loki.messenger.BuildConfig;
+import network.loki.messenger.libsession_util.ConfigBase;
+import network.loki.messenger.libsession_util.UserProfile;
/**
* Will be called once when the TextSecure process is created.
@@ -125,7 +122,7 @@ import network.loki.messenger.BuildConfig;
* @author Moxie Marlinspike
*/
@HiltAndroidApp
-public class ApplicationContext extends Application implements DefaultLifecycleObserver {
+public class ApplicationContext extends Application implements DefaultLifecycleObserver, ConfigFactoryUpdateListener {
public static final String PREFERENCES_NAME = "SecureSMS-Preferences";
@@ -134,7 +131,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
private ExpiringMessageManager expiringMessageManager;
private TypingStatusRepository typingStatusRepository;
private TypingStatusSender typingStatusSender;
- private JobManager jobManager;
private ReadReceiptManager readReceiptManager;
private ProfileManager profileManager;
public MessageNotifier messageNotifier = null;
@@ -147,10 +143,12 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
private PersistentLogger persistentLogger;
@Inject LokiAPIDatabase lokiAPIDatabase;
- @Inject StorageProtocol storage;
+ @Inject public Storage storage;
+ @Inject Device device;
@Inject MessageDataProvider messageDataProvider;
- @Inject JobDatabase jobDatabase;
@Inject TextSecurePreferences textSecurePreferences;
+ @Inject PushRegistry pushRegistry;
+ @Inject ConfigFactory configFactory;
CallMessageProcessor callMessageProcessor;
MessagingModuleConfiguration messagingModuleConfiguration;
@@ -169,7 +167,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
}
public TextSecurePreferences getPrefs() {
- return textSecurePreferences;
+ return EntryPoints.get(getApplicationContext(), AppComponent.class).getPrefs();
}
public DatabaseComponent getDatabaseComponent() {
@@ -198,18 +196,28 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
return this.persistentLogger;
}
+ @Override
+ public void notifyUpdates(@NonNull ConfigBase forConfigObject) {
+ // forward to the config factory / storage ig
+ if (forConfigObject instanceof UserProfile && !textSecurePreferences.getConfigurationMessageSynced()) {
+ textSecurePreferences.setConfigurationMessageSynced(true);
+ }
+ storage.notifyConfigUpdates(forConfigObject);
+ }
+
@Override
public void onCreate() {
DatabaseModule.init(this);
MessagingModuleConfiguration.configure(this);
super.onCreate();
- messagingModuleConfiguration = new MessagingModuleConfiguration(this,
+ messagingModuleConfiguration = new MessagingModuleConfiguration(
+ this,
storage,
+ device,
messageDataProvider,
- ()-> KeyPairUtilities.INSTANCE.getUserED25519KeyPair(this));
- // migrate session open group data
- OpenGroupMigrator.migrate(getDatabaseComponent());
- // end migration
+ ()-> KeyPairUtilities.INSTANCE.getUserED25519KeyPair(this),
+ configFactory
+ );
callMessageProcessor = new CallMessageProcessor(this, textSecurePreferences, ProcessLifecycleOwner.get().getLifecycle(), storage);
Log.i(TAG, "onCreate()");
startKovenant();
@@ -223,10 +231,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
broadcaster = new Broadcaster(this);
LokiAPIDatabase apiDB = getDatabaseComponent().lokiAPIDatabase();
SnodeModule.Companion.configure(apiDB, broadcaster);
- String userPublicKey = TextSecurePreferences.getLocalNumber(this);
- if (userPublicKey != null) {
- registerForFCMIfNeeded(false);
- }
initializeExpiringMessageManager();
initializeTypingStatusRepository();
initializeTypingStatusSender();
@@ -234,7 +238,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
initializeProfileManager();
initializePeriodicTasks();
SSKEnvironment.Companion.configure(getTypingStatusRepository(), getReadReceiptManager(), getProfileManager(), messageNotifier, getExpiringMessageManager());
- initializeJobManager();
initializeWebRtc();
initializeBlobProvider();
resubmitProfilePictureIfNeeded();
@@ -277,7 +280,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
if (poller != null) {
poller.stopIfNeeded();
}
- ClosedGroupPollerV2.getShared().stop();
+ ClosedGroupPollerV2.getShared().stopAll();
}
@Override
@@ -291,10 +294,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
LocaleParser.Companion.configure(new LocaleParseHelper());
}
- public JobManager getJobManager() {
- return jobManager;
- }
-
public ExpiringMessageManager getExpiringMessageManager() {
return expiringMessageManager;
}
@@ -357,16 +356,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionLogger(originalHandler));
}
- private void initializeJobManager() {
- this.jobManager = new JobManager(this, new JobManager.Configuration.Builder()
- .setDataSerializer(new JsonDataSerializer())
- .setJobFactories(JobManagerFactories.getJobFactories(this))
- .setConstraintFactories(JobManagerFactories.getConstraintFactories(this))
- .setConstraintObservers(JobManagerFactories.getConstraintObservers(this))
- .setJobStorage(new FastJobStorage(jobDatabase))
- .build());
- }
-
private void initializeExpiringMessageManager() {
this.expiringMessageManager = new ExpiringMessageManager(this);
}
@@ -380,7 +369,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
}
private void initializeProfileManager() {
- this.profileManager = new ProfileManager();
+ this.profileManager = new ProfileManager(this, configFactory);
}
private void initializeTypingStatusSender() {
@@ -389,10 +378,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
private void initializePeriodicTasks() {
BackgroundPollWorker.schedulePeriodic(this);
-
- if (BuildConfig.PLAY_STORE_DISABLED) {
- UpdateApkRefreshListener.schedule(this);
- }
}
private void initializeWebRtc() {
@@ -443,29 +428,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
}
private static class ProviderInitializationException extends RuntimeException { }
-
- public void registerForFCMIfNeeded(final Boolean force) {
- if (firebaseInstanceIdJob != null && firebaseInstanceIdJob.isActive() && !force) return;
- if (force && firebaseInstanceIdJob != null) {
- firebaseInstanceIdJob.cancel(null);
- }
- firebaseInstanceIdJob = FcmUtils.getFcmInstanceId(task->{
- if (!task.isSuccessful()) {
- Log.w("Loki", "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.getException());
- return Unit.INSTANCE;
- }
- String token = task.getResult().getToken();
- String userPublicKey = TextSecurePreferences.getLocalNumber(this);
- if (userPublicKey == null) return Unit.INSTANCE;
- if (TextSecurePreferences.isUsingFCM(this)) {
- LokiPushNotificationManager.register(token, userPublicKey, this, force);
- } else {
- LokiPushNotificationManager.unregister(token, this);
- }
- return Unit.INSTANCE;
- });
- }
-
private void setUpPollingIfNeeded() {
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
if (userPublicKey == null) return;
@@ -473,7 +435,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
poller.setUserPublicKey(userPublicKey);
return;
}
- poller = new Poller();
+ poller = new Poller(configFactory, new Timer());
}
public void startPollingIfNeeded() {
@@ -516,6 +478,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
});
} catch (Exception exception) {
// Do nothing
+ Log.e("Loki-Avatar", "Uploading avatar failed", exception);
}
});
}
@@ -535,24 +498,21 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
}
public void clearAllData(boolean isMigratingToV2KeyPair) {
- String token = TextSecurePreferences.getFCMToken(this);
- if (token != null && !token.isEmpty()) {
- LokiPushNotificationManager.unregister(token, this);
- }
if (firebaseInstanceIdJob != null && firebaseInstanceIdJob.isActive()) {
firebaseInstanceIdJob.cancel(null);
}
String displayName = TextSecurePreferences.getProfileName(this);
- boolean isUsingFCM = TextSecurePreferences.isUsingFCM(this);
+ boolean isUsingFCM = TextSecurePreferences.isPushEnabled(this);
TextSecurePreferences.clearAll(this);
if (isMigratingToV2KeyPair) {
- TextSecurePreferences.setIsUsingFCM(this, isUsingFCM);
+ TextSecurePreferences.setPushEnabled(this, isUsingFCM);
TextSecurePreferences.setProfileName(this, displayName);
}
getSharedPreferences(PREFERENCES_NAME, 0).edit().clear().commit();
if (!deleteDatabase(SQLCipherOpenHelper.DATABASE_NAME)) {
Log.d("Loki", "Failed to delete database.");
}
+ configFactory.keyPairChanged();
Util.runOnMain(() -> new Handler().postDelayed(ApplicationContext.this::restartApplication, 200));
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/BaseActionBarActivity.java b/app/src/main/java/org/thoughtcrime/securesms/BaseActionBarActivity.java
index 7d82c760c..51f66ec32 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/BaseActionBarActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/BaseActionBarActivity.java
@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms;
+import static android.os.Build.VERSION.SDK_INT;
import static org.session.libsession.utilities.TextSecurePreferences.SELECTED_ACCENT_COLOR;
import android.app.ActivityManager;
@@ -18,6 +19,7 @@ import androidx.appcompat.app.AppCompatActivity;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageActivityHelper;
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper;
+import org.thoughtcrime.securesms.conversation.v2.WindowUtil;
import org.thoughtcrime.securesms.util.ActivityUtilitiesKt;
import org.thoughtcrime.securesms.util.ThemeState;
import org.thoughtcrime.securesms.util.UiModeUtilities;
@@ -92,6 +94,11 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
if (!currentThemeState.equals(ActivityUtilitiesKt.themeState(getPreferences()))) {
recreate();
}
+
+ // apply lightStatusBar manually as API 26 does not update properly via applyTheme
+ // https://issuetracker.google.com/issues/65883460?pli=1
+ if (SDK_INT >= 26 && SDK_INT <= 27) WindowUtil.setLightStatusBarFromTheme(this);
+ if (SDK_INT == 27) WindowUtil.setLightNavigationBarFromTheme(this);
}
@Override
diff --git a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java
deleted file mode 100644
index 93313e527..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.thoughtcrime.securesms;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-
-import org.thoughtcrime.securesms.database.model.MessageRecord;
-import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
-import org.thoughtcrime.securesms.mms.GlideRequests;
-import org.session.libsignal.utilities.guava.Optional;
-
-import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
-import org.session.libsession.utilities.Address;
-import org.session.libsession.utilities.recipients.Recipient;
-
-import java.util.Locale;
-import java.util.Set;
-
-public interface BindableConversationItem extends Unbindable {
- void bind(@NonNull MessageRecord messageRecord,
- @NonNull Optional previousMessageRecord,
- @NonNull Optional nextMessageRecord,
- @NonNull GlideRequests glideRequests,
- @NonNull Locale locale,
- @NonNull Set batchSelected,
- @NonNull Recipient recipients,
- @Nullable String searchQuery,
- boolean pulseHighlight);
-
- MessageRecord getMessageRecord();
-
- void setEventListener(@Nullable EventListener listener);
-
- interface EventListener {
- void onQuoteClicked(MmsMessageRecord messageRecord);
- void onLinkPreviewClicked(@NonNull LinkPreview linkPreview);
- void onMoreTextClicked(@NonNull Address conversationAddress, long messageId, boolean isMms);
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/DeleteMediaDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/DeleteMediaDialog.kt
new file mode 100644
index 000000000..af38c31ff
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/DeleteMediaDialog.kt
@@ -0,0 +1,28 @@
+package org.thoughtcrime.securesms
+
+import android.content.Context
+import network.loki.messenger.R
+
+class DeleteMediaDialog {
+ companion object {
+ @JvmStatic
+ fun show(context: Context, recordCount: Int, doDelete: Runnable) = context.showSessionDialog {
+ iconAttribute(R.attr.dialog_alert_icon)
+ title(
+ context.resources.getQuantityString(
+ R.plurals.MediaOverviewActivity_Media_delete_confirm_title,
+ recordCount,
+ recordCount
+ )
+ )
+ text(
+ context.resources.getQuantityString(R.plurals.MediaOverviewActivity_Media_delete_confirm_message,
+ recordCount,
+ recordCount
+ )
+ )
+ button(R.string.delete) { doDelete.run() }
+ cancelButton()
+ }
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/DeleteMediaPreviewDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/DeleteMediaPreviewDialog.kt
new file mode 100644
index 000000000..0390a3007
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/DeleteMediaPreviewDialog.kt
@@ -0,0 +1,19 @@
+package org.thoughtcrime.securesms
+
+import android.content.Context
+import network.loki.messenger.R
+
+class DeleteMediaPreviewDialog {
+ companion object {
+ @JvmStatic
+ fun show(context: Context, doDelete: Runnable) {
+ context.showSessionDialog {
+ iconAttribute(R.attr.dialog_alert_icon)
+ title(R.string.MediaPreviewActivity_media_delete_confirmation_title)
+ text(R.string.MediaPreviewActivity_media_delete_confirmation_message)
+ button(R.string.delete) { doDelete.run() }
+ cancelButton()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/DeviceModule.kt b/app/src/main/java/org/thoughtcrime/securesms/DeviceModule.kt
new file mode 100644
index 000000000..bdfa9b608
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/DeviceModule.kt
@@ -0,0 +1,16 @@
+package org.thoughtcrime.securesms
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import network.loki.messenger.BuildConfig
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object DeviceModule {
+ @Provides
+ @Singleton
+ fun provides() = BuildConfig.DEVICE
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ExpirationDialog.java b/app/src/main/java/org/thoughtcrime/securesms/ExpirationDialog.java
deleted file mode 100644
index 469629ed3..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/ExpirationDialog.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package org.thoughtcrime.securesms;
-
-import android.content.Context;
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.TextView;
-
-import org.session.libsession.utilities.ExpirationUtil;
-
-import cn.carbswang.android.numberpickerview.library.NumberPickerView;
-import network.loki.messenger.R;
-
-public class ExpirationDialog extends AlertDialog {
-
- protected ExpirationDialog(Context context) {
- super(context);
- }
-
- protected ExpirationDialog(Context context, int theme) {
- super(context, theme);
- }
-
- protected ExpirationDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
- super(context, cancelable, cancelListener);
- }
-
- public static void show(final Context context,
- final int currentExpiration,
- final @NonNull OnClickListener listener)
- {
- final View view = createNumberPickerView(context, currentExpiration);
-
- AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(context.getString(R.string.ExpirationDialog_disappearing_messages));
- builder.setView(view);
- builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
- int selected = ((NumberPickerView)view.findViewById(R.id.expiration_number_picker)).getValue();
- listener.onClick(context.getResources().getIntArray(R.array.expiration_times)[selected]);
- });
- builder.setNegativeButton(android.R.string.cancel, null);
- builder.show();
- }
-
- private static View createNumberPickerView(final Context context, final int currentExpiration) {
- final LayoutInflater inflater = LayoutInflater.from(context);
- final View view = inflater.inflate(R.layout.expiration_dialog, null);
- final NumberPickerView numberPickerView = view.findViewById(R.id.expiration_number_picker);
- final TextView textView = view.findViewById(R.id.expiration_details);
- final int[] expirationTimes = context.getResources().getIntArray(R.array.expiration_times);
- final String[] expirationDisplayValues = new String[expirationTimes.length];
-
- int selectedIndex = expirationTimes.length - 1;
-
- for (int i=0;i= expirationTimes[i]) &&
- (i == expirationTimes.length -1 || currentExpiration < expirationTimes[i+1])) {
- selectedIndex = i;
- }
- }
-
- numberPickerView.setDisplayedValues(expirationDisplayValues);
- numberPickerView.setMinValue(0);
- numberPickerView.setMaxValue(expirationTimes.length-1);
-
- NumberPickerView.OnValueChangeListener listener = (picker, oldVal, newVal) -> {
- if (newVal == 0) {
- textView.setText(R.string.ExpirationDialog_your_messages_will_not_expire);
- } else {
- textView.setText(context.getString(R.string.ExpirationDialog_your_messages_will_disappear_s_after_they_have_been_seen, picker.getDisplayedValues()[newVal]));
- }
- };
-
- numberPickerView.setOnValueChangedListener(listener);
- numberPickerView.setValue(selectedIndex);
- listener.onValueChange(numberPickerView, selectedIndex, selectedIndex);
-
- return view;
- }
-
- public interface OnClickListener {
- public void onClick(int expirationTime);
- }
-
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ExpirationDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/ExpirationDialog.kt
new file mode 100644
index 000000000..9a34c1ec4
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/ExpirationDialog.kt
@@ -0,0 +1,51 @@
+package org.thoughtcrime.securesms
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.widget.TextView
+import androidx.appcompat.app.AlertDialog
+import cn.carbswang.android.numberpickerview.library.NumberPickerView
+import network.loki.messenger.R
+import org.session.libsession.utilities.ExpirationUtil
+
+fun Context.showExpirationDialog(
+ expiration: Int,
+ onExpirationTime: (Int) -> Unit
+): AlertDialog {
+ val view = LayoutInflater.from(this).inflate(R.layout.expiration_dialog, null)
+ val numberPickerView = view.findViewById(R.id.expiration_number_picker)
+
+ fun updateText(index: Int) {
+ view.findViewById(R.id.expiration_details).text = when (index) {
+ 0 -> getString(R.string.ExpirationDialog_your_messages_will_not_expire)
+ else -> getString(
+ R.string.ExpirationDialog_your_messages_will_disappear_s_after_they_have_been_seen,
+ numberPickerView.displayedValues[index]
+ )
+ }
+ }
+
+ val expirationTimes = resources.getIntArray(R.array.expiration_times)
+ val expirationDisplayValues = expirationTimes
+ .map { ExpirationUtil.getExpirationDisplayValue(this, it) }
+ .toTypedArray()
+
+ val selectedIndex = expirationTimes.run { indexOfFirst { it >= expiration }.coerceIn(indices) }
+
+ numberPickerView.apply {
+ displayedValues = expirationDisplayValues
+ minValue = 0
+ maxValue = expirationTimes.lastIndex
+ setOnValueChangedListener { _, _, index -> updateText(index) }
+ value = selectedIndex
+ }
+
+ updateText(selectedIndex)
+
+ return showSessionDialog {
+ title(getString(R.string.ExpirationDialog_disappearing_messages))
+ view(view)
+ okButton { onExpirationTime(numberPickerView.let { expirationTimes[it.value] }) }
+ cancelButton()
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java
index 63b287433..49527c238 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java
@@ -57,6 +57,7 @@ import org.session.libsession.database.StorageProtocol;
import org.session.libsession.messaging.MessagingModuleConfiguration;
import org.session.libsession.messaging.messages.control.DataExtractionNotification;
import org.session.libsession.messaging.sending_receiving.MessageSender;
+import org.session.libsession.snode.SnodeAPI;
import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.GroupRecord;
import org.session.libsession.utilities.TextSecurePreferences;
@@ -356,9 +357,9 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity i
@SuppressWarnings("CodeBlock2Expr")
@SuppressLint({"InlinedApi", "StaticFieldLeak"})
private void handleSaveMedia(@NonNull Collection mediaRecords) {
- final Context context = getContext();
+ final Context context = requireContext();
- SaveAttachmentTask.showWarningDialog(context, (dialogInterface, which) -> {
+ SaveAttachmentTask.showWarningDialog(context, mediaRecords.size(), () -> {
Permissions.with(this)
.request(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
.maxSdkVersion(Build.VERSION_CODES.P)
@@ -400,34 +401,25 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity i
}.execute();
})
.execute();
- }, mediaRecords.size());
+ return Unit.INSTANCE;
+ });
}
private void sendMediaSavedNotificationIfNeeded() {
if (recipient.isGroupRecipient()) return;
- DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis()));
+ DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(SnodeAPI.getNowWithOffset()));
MessageSender.send(message, recipient.getAddress());
}
@SuppressLint("StaticFieldLeak")
private void handleDeleteMedia(@NonNull Collection mediaRecords) {
int recordCount = mediaRecords.size();
- Resources res = getContext().getResources();
- String confirmTitle = res.getQuantityString(R.plurals.MediaOverviewActivity_Media_delete_confirm_title,
- recordCount,
- recordCount);
- String confirmMessage = res.getQuantityString(R.plurals.MediaOverviewActivity_Media_delete_confirm_message,
- recordCount,
- recordCount);
- AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
- builder.setIconAttribute(R.attr.dialog_alert_icon);
- builder.setTitle(confirmTitle);
- builder.setMessage(confirmMessage);
- builder.setCancelable(true);
-
- builder.setPositiveButton(R.string.delete, (dialogInterface, i) -> {
- new ProgressDialogAsyncTask(getContext(),
+ DeleteMediaDialog.show(
+ requireContext(),
+ recordCount,
+ () ->
+ new ProgressDialogAsyncTask(requireContext(),
R.string.MediaOverviewActivity_Media_delete_progress_title,
R.string.MediaOverviewActivity_Media_delete_progress_message) {
@Override
@@ -442,11 +434,8 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity i
return null;
}
- }.execute(mediaRecords.toArray(new MediaDatabase.MediaRecord[mediaRecords.size()]));
- });
- builder.setNegativeButton(android.R.string.cancel, null);
- builder.show();
- }
+ }.execute(mediaRecords.toArray(new MediaDatabase.MediaRecord[mediaRecords.size()])));
+ }
private void handleSelectAllMedia() {
getListAdapter().selectAllMedia();
diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java
index b21c7dac8..f19a1fc45 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java
@@ -60,6 +60,7 @@ import androidx.viewpager.widget.ViewPager;
import org.session.libsession.messaging.messages.control.DataExtractionNotification;
import org.session.libsession.messaging.sending_receiving.MessageSender;
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment;
+import org.session.libsession.snode.SnodeAPI;
import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.Util;
import org.session.libsession.utilities.recipients.Recipient;
@@ -84,6 +85,7 @@ import java.io.IOException;
import java.util.Locale;
import java.util.WeakHashMap;
+import kotlin.Unit;
import network.loki.messenger.R;
/**
@@ -145,6 +147,10 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
}
};
+ public static Intent getPreviewIntent(Context context, MediaPreviewArgs args) {
+ return getPreviewIntent(context, args.getSlide(), args.getMmsRecord(), args.getThread());
+ }
+
public static Intent getPreviewIntent(Context context, Slide slide, MmsMessageRecord mms, Recipient threadRecipient) {
Intent previewIntent = null;
if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) {
@@ -415,7 +421,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
MediaItem mediaItem = getCurrentMediaItem();
if (mediaItem == null) return;
- SaveAttachmentTask.showWarningDialog(this, (dialogInterface, i) -> {
+ SaveAttachmentTask.showWarningDialog(this, 1, () -> {
Permissions.with(this)
.request(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
.maxSdkVersion(Build.VERSION_CODES.P)
@@ -423,7 +429,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
.onAnyDenied(() -> Toast.makeText(this, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show())
.onAllGranted(() -> {
SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this);
- long saveDate = (mediaItem.date > 0) ? mediaItem.date : System.currentTimeMillis();
+ long saveDate = (mediaItem.date > 0) ? mediaItem.date : SnodeAPI.getNowWithOffset();
saveTask.executeOnExecutor(
AsyncTask.THREAD_POOL_EXECUTOR,
new Attachment(mediaItem.uri, mediaItem.type, saveDate, null));
@@ -432,12 +438,13 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
}
})
.execute();
+ return Unit.INSTANCE;
});
}
private void sendMediaSavedNotificationIfNeeded() {
if (conversationRecipient.isGroupRecipient()) return;
- DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis()));
+ DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(SnodeAPI.getNowWithOffset()));
MessageSender.send(message, conversationRecipient.getAddress());
}
@@ -448,29 +455,20 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
return;
}
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setIconAttribute(R.attr.dialog_alert_icon);
- builder.setTitle(R.string.MediaPreviewActivity_media_delete_confirmation_title);
- builder.setMessage(R.string.MediaPreviewActivity_media_delete_confirmation_message);
- builder.setCancelable(true);
-
- builder.setPositiveButton(R.string.delete, (dialogInterface, which) -> {
- new AsyncTask() {
- @Override
- protected Void doInBackground(Void... voids) {
- if (mediaItem.attachment == null) {
- return null;
- }
- AttachmentUtil.deleteAttachment(MediaPreviewActivity.this.getApplicationContext(),
- mediaItem.attachment);
- return null;
- }
- }.execute();
+ DeleteMediaPreviewDialog.show(this, () -> {
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... voids) {
+ DatabaseAttachment attachment = mediaItem.attachment;
+ if (attachment != null) {
+ AttachmentUtil.deleteAttachment(getApplicationContext(), attachment);
+ }
+ return null;
+ }
+ }.execute();
finish();
});
- builder.setNegativeButton(android.R.string.cancel, null);
- builder.show();
}
@Override
@@ -530,7 +528,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
@Override
public void onLoadFinished(@NonNull Loader> loader, @Nullable Pair data) {
if (data != null) {
- @SuppressWarnings("ConstantConditions")
CursorPagerAdapter adapter = new CursorPagerAdapter(this, GlideApp.with(this), getWindow(), data.first, data.second, leftIsRecent);
mediaPager.setAdapter(adapter);
adapter.setActive(true);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewArgs.kt b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewArgs.kt
new file mode 100644
index 000000000..00e2c3d6d
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewArgs.kt
@@ -0,0 +1,11 @@
+package org.thoughtcrime.securesms
+
+import org.session.libsession.utilities.recipients.Recipient
+import org.thoughtcrime.securesms.database.model.MmsMessageRecord
+import org.thoughtcrime.securesms.mms.Slide
+
+data class MediaPreviewArgs(
+ val slide: Slide,
+ val mmsRecord: MmsMessageRecord?,
+ val thread: Recipient?,
+)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java
deleted file mode 100644
index ca6cf8f6c..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java
+++ /dev/null
@@ -1,111 +0,0 @@
-package org.thoughtcrime.securesms;
-
-import android.content.Context;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.BaseAdapter;
-
-import androidx.annotation.NonNull;
-
-
-import org.thoughtcrime.securesms.database.model.MessageRecord;
-import org.thoughtcrime.securesms.contacts.UserView;
-import org.thoughtcrime.securesms.mms.GlideRequests;
-import org.session.libsession.utilities.recipients.Recipient;
-import org.session.libsession.utilities.Conversions;
-
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.List;
-
-class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.RecyclerListener {
-
- private final Context context;
- private final GlideRequests glideRequests;
- private final MessageRecord record;
- private final List members;
- private final boolean isPushGroup;
-
- MessageDetailsRecipientAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests,
- @NonNull MessageRecord record, @NonNull List members,
- boolean isPushGroup)
- {
- this.context = context;
- this.glideRequests = glideRequests;
- this.record = record;
- this.isPushGroup = isPushGroup;
- this.members = members;
- }
-
- @Override
- public int getCount() {
- return members.size();
- }
-
- @Override
- public Object getItem(int position) {
- return members.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- try {
- return Conversions.byteArrayToLong(MessageDigest.getInstance("SHA1").digest(members.get(position).recipient.getAddress().serialize().getBytes()));
- } catch (NoSuchAlgorithmException e) {
- throw new AssertionError(e);
- }
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- UserView result = new UserView(context);
- Recipient recipient = members.get(position).getRecipient();
- result.setOpenGroupThreadID(record.getThreadId());
- result.bind(recipient, glideRequests, UserView.ActionIndicator.None, false);
- return result;
- }
-
- @Override
- public void onMovedToScrapHeap(View view) {
- ((UserView)view).unbind();
- }
-
-
- static class RecipientDeliveryStatus {
-
- enum Status {
- UNKNOWN, PENDING, SENT, DELIVERED, READ
- }
-
- private final Recipient recipient;
- private final Status deliveryStatus;
- private final boolean isUnidentified;
- private final long timestamp;
-
- RecipientDeliveryStatus(Recipient recipient, Status deliveryStatus, boolean isUnidentified, long timestamp) {
- this.recipient = recipient;
- this.deliveryStatus = deliveryStatus;
- this.isUnidentified = isUnidentified;
- this.timestamp = timestamp;
- }
-
- Status getDeliveryStatus() {
- return deliveryStatus;
- }
-
- boolean isUnidentified() {
- return isUnidentified;
- }
-
- public long getTimestamp() {
- return timestamp;
- }
-
- public Recipient getRecipient() {
- return recipient;
- }
-
- }
-
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/MuteDialog.java b/app/src/main/java/org/thoughtcrime/securesms/MuteDialog.java
deleted file mode 100644
index acca9f837..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/MuteDialog.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.thoughtcrime.securesms;
-
-import android.content.Context;
-import android.content.DialogInterface;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
-
-import java.util.concurrent.TimeUnit;
-
-import network.loki.messenger.R;
-
-public class MuteDialog extends AlertDialog {
-
-
- protected MuteDialog(Context context) {
- super(context);
- }
-
- protected MuteDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
- super(context, cancelable, cancelListener);
- }
-
- protected MuteDialog(Context context, int theme) {
- super(context, theme);
- }
-
- public static void show(final Context context, final @NonNull MuteSelectionListener listener) {
- AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(R.string.MuteDialog_mute_notifications);
- builder.setItems(R.array.mute_durations, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, final int which) {
- final long muteUntil;
-
- switch (which) {
- case 1: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(2); break;
- case 2: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); break;
- case 3: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(7); break;
- case 4: muteUntil = Long.MAX_VALUE; break;
- default: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break;
- }
-
- listener.onMuted(muteUntil);
- }
- });
-
- builder.show();
-
- }
-
- public interface MuteSelectionListener {
- public void onMuted(long until);
- }
-
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/MuteDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/MuteDialog.kt
new file mode 100644
index 000000000..f294e387f
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/MuteDialog.kt
@@ -0,0 +1,27 @@
+package org.thoughtcrime.securesms
+
+import android.content.Context
+import androidx.annotation.StringRes
+import androidx.appcompat.app.AlertDialog
+import network.loki.messenger.R
+import java.util.concurrent.TimeUnit
+
+fun showMuteDialog(
+ context: Context,
+ onMuteDuration: (Long) -> Unit
+): AlertDialog = context.showSessionDialog {
+ title(R.string.MuteDialog_mute_notifications)
+ items(Option.values().map { it.stringRes }.map(context::getString).toTypedArray()) {
+ onMuteDuration(Option.values()[it].getTime())
+ }
+}
+
+private enum class Option(@StringRes val stringRes: Int, val getTime: () -> Long) {
+ ONE_HOUR(R.string.arrays__mute_for_one_hour, duration = TimeUnit.HOURS.toMillis(1)),
+ TWO_HOURS(R.string.arrays__mute_for_two_hours, duration = TimeUnit.DAYS.toMillis(2)),
+ ONE_DAY(R.string.arrays__mute_for_one_day, duration = TimeUnit.DAYS.toMillis(1)),
+ SEVEN_DAYS(R.string.arrays__mute_for_seven_days, duration = TimeUnit.DAYS.toMillis(7)),
+ FOREVER(R.string.arrays__mute_forever, getTime = { Long.MAX_VALUE });
+
+ constructor(@StringRes stringRes: Int, duration: Long): this(stringRes, { System.currentTimeMillis() + duration })
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/PassphrasePromptActivity.java b/app/src/main/java/org/thoughtcrime/securesms/PassphrasePromptActivity.java
index 63b42c493..afc993df8 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/PassphrasePromptActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/PassphrasePromptActivity.java
@@ -210,8 +210,7 @@ public class PassphrasePromptActivity extends BaseActionBarActivity {
try {
signature = biometricSecretProvider.getOrCreateBiometricSignature(this);
hasSignatureObject = true;
- throw new InvalidKeyException("e");
- } catch (InvalidKeyException e) {
+ } catch (Exception e) {
signature = null;
hasSignatureObject = false;
Log.e(TAG, "Error getting / creating signature", e);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/SessionDialogBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/SessionDialogBuilder.kt
new file mode 100644
index 000000000..44c30741e
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/SessionDialogBuilder.kt
@@ -0,0 +1,146 @@
+package org.thoughtcrime.securesms
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.Button
+import android.widget.LinearLayout
+import android.widget.LinearLayout.VERTICAL
+import android.widget.TextView
+import androidx.annotation.AttrRes
+import androidx.annotation.LayoutRes
+import androidx.annotation.StringRes
+import androidx.annotation.StyleRes
+import androidx.appcompat.app.AlertDialog
+import androidx.core.view.setMargins
+import androidx.core.view.setPadding
+import androidx.core.view.updateMargins
+import androidx.fragment.app.Fragment
+import network.loki.messenger.R
+import org.thoughtcrime.securesms.util.toPx
+
+
+@DslMarker
+@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
+annotation class DialogDsl
+
+@DialogDsl
+class SessionDialogBuilder(val context: Context) {
+
+ private val dp20 = toPx(20, context.resources)
+ private val dp40 = toPx(40, context.resources)
+
+ private val dialogBuilder: AlertDialog.Builder = AlertDialog.Builder(context)
+
+ private var dialog: AlertDialog? = null
+ private fun dismiss() = dialog?.dismiss()
+
+ private val topView = LinearLayout(context).apply { orientation = VERTICAL }
+ .also(dialogBuilder::setCustomTitle)
+ private val contentView = LinearLayout(context).apply { orientation = VERTICAL }
+ private val buttonLayout = LinearLayout(context)
+
+ private val root = LinearLayout(context).apply { orientation = VERTICAL }
+ .also(dialogBuilder::setView)
+ .apply {
+ addView(contentView)
+ addView(buttonLayout)
+ }
+
+ fun title(@StringRes id: Int) = title(context.getString(id))
+
+ fun title(text: CharSequence?) = title(text?.toString())
+ fun title(text: String?) {
+ text(text, R.style.TextAppearance_AppCompat_Title) { setPadding(dp20) }
+ }
+
+ fun text(@StringRes id: Int, style: Int = 0) = text(context.getString(id), style)
+ fun text(text: CharSequence?, @StyleRes style: Int = 0) {
+ text(text, style) {
+ layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
+ .apply { updateMargins(dp40, 0, dp40, dp20) }
+ }
+ }
+
+
+ private fun text(text: CharSequence?, @StyleRes style: Int, modify: TextView.() -> Unit) {
+ text ?: return
+ TextView(context, null, 0, style)
+ .apply {
+ setText(text)
+ textAlignment = View.TEXT_ALIGNMENT_CENTER
+ modify()
+ }.let(topView::addView)
+ }
+
+ fun view(view: View) = contentView.addView(view)
+
+ fun view(@LayoutRes layout: Int): View = LayoutInflater.from(context).inflate(layout, contentView)
+
+ fun iconAttribute(@AttrRes icon: Int): AlertDialog.Builder = dialogBuilder.setIconAttribute(icon)
+
+ fun singleChoiceItems(
+ options: Collection,
+ currentSelected: Int = 0,
+ onSelect: (Int) -> Unit
+ ) = singleChoiceItems(options.toTypedArray(), currentSelected, onSelect)
+
+ fun singleChoiceItems(
+ options: Array,
+ currentSelected: Int = 0,
+ onSelect: (Int) -> Unit
+ ): AlertDialog.Builder = dialogBuilder.setSingleChoiceItems(
+ options,
+ currentSelected
+ ) { dialog, it -> onSelect(it); dialog.dismiss() }
+
+ fun items(
+ options: Array,
+ onSelect: (Int) -> Unit
+ ): AlertDialog.Builder = dialogBuilder.setItems(
+ options,
+ ) { dialog, it -> onSelect(it); dialog.dismiss() }
+
+ fun destructiveButton(
+ @StringRes text: Int,
+ @StringRes contentDescription: Int,
+ listener: () -> Unit = {}
+ ) = button(
+ text,
+ contentDescription,
+ R.style.Widget_Session_Button_Dialog_DestructiveText,
+ ) { listener() }
+
+ fun okButton(listener: (() -> Unit) = {}) = button(android.R.string.ok) { listener() }
+ fun cancelButton(listener: (() -> Unit) = {}) = button(android.R.string.cancel, R.string.AccessibilityId_cancel_button) { listener() }
+
+ fun button(
+ @StringRes text: Int,
+ @StringRes contentDescriptionRes: Int = text,
+ @StyleRes style: Int = R.style.Widget_Session_Button_Dialog_UnimportantText,
+ dismiss: Boolean = true,
+ listener: (() -> Unit) = {}
+ ) = Button(context, null, 0, style).apply {
+ setText(text)
+ contentDescription = resources.getString(contentDescriptionRes)
+ layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, 1f)
+ .apply { setMargins(toPx(20, resources)) }
+ setOnClickListener {
+ listener.invoke()
+ if (dismiss) dismiss()
+ }
+ }.let(buttonLayout::addView)
+
+ fun create(): AlertDialog = dialogBuilder.create().also { dialog = it }
+ fun show(): AlertDialog = dialogBuilder.show().also { dialog = it }
+}
+
+fun Context.showSessionDialog(build: SessionDialogBuilder.() -> Unit): AlertDialog =
+ SessionDialogBuilder(this).apply { build() }.show()
+
+fun Fragment.showSessionDialog(build: SessionDialogBuilder.() -> Unit): AlertDialog =
+ SessionDialogBuilder(requireContext()).apply { build() }.show()
+fun Fragment.createSessionDialog(build: SessionDialogBuilder.() -> Unit): AlertDialog =
+ SessionDialogBuilder(requireContext()).apply { build() }.create()
diff --git a/app/src/main/java/org/thoughtcrime/securesms/Unbindable.java b/app/src/main/java/org/thoughtcrime/securesms/Unbindable.java
deleted file mode 100644
index 3dd5cd8cc..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/Unbindable.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.thoughtcrime.securesms;
-
-public interface Unbindable {
- public void unbind();
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/ScreenshotObserver.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/ScreenshotObserver.kt
index 84a9b6cfc..9c7ca21e8 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/attachments/ScreenshotObserver.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/ScreenshotObserver.kt
@@ -7,6 +7,10 @@ import android.os.Build
import android.os.Handler
import android.provider.MediaStore
import androidx.annotation.RequiresApi
+import org.session.libsignal.utilities.Log
+import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer
+
+private const val TAG = "ScreenshotObserver"
class ScreenshotObserver(private val context: Context, handler: Handler, private val screenshotTriggered: ()->Unit): ContentObserver(handler) {
@@ -31,22 +35,26 @@ class ScreenshotObserver(private val context: Context, handler: Handler, private
val projection = arrayOf(
MediaStore.Images.Media.DATA
)
- context.contentResolver.query(
- uri,
- projection,
- null,
- null,
- null
- )?.use { cursor ->
- val dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA)
- while (cursor.moveToNext()) {
- val path = cursor.getString(dataColumn)
- if (path.contains("screenshot", true)) {
- if (cache.add(uri.hashCode())) {
- screenshotTriggered()
+ try {
+ context.contentResolver.query(
+ uri,
+ projection,
+ null,
+ null,
+ null
+ )?.use { cursor ->
+ val dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA)
+ while (cursor.moveToNext()) {
+ val path = cursor.getString(dataColumn)
+ if (path.contains("screenshot", true)) {
+ if (cache.add(uri.hashCode())) {
+ screenshotTriggered()
+ }
}
}
}
+ } catch (e: SecurityException) {
+ Log.e(TAG, e)
}
}
@@ -56,28 +64,32 @@ class ScreenshotObserver(private val context: Context, handler: Handler, private
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.RELATIVE_PATH
)
- context.contentResolver.query(
- uri,
- projection,
- null,
- null,
- null
- )?.use { cursor ->
- val relativePathColumn =
- cursor.getColumnIndex(MediaStore.Images.Media.RELATIVE_PATH)
- val displayNameColumn =
- cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
- while (cursor.moveToNext()) {
- val name = cursor.getString(displayNameColumn)
- val relativePath = cursor.getString(relativePathColumn)
- if (name.contains("screenshot", true) or
- relativePath.contains("screenshot", true)) {
- if (cache.add(uri.hashCode())) {
- screenshotTriggered()
+
+ try {
+ context.contentResolver.query(
+ uri,
+ projection,
+ null,
+ null,
+ null
+ )?.use { cursor ->
+ val relativePathColumn =
+ cursor.getColumnIndex(MediaStore.Images.Media.RELATIVE_PATH)
+ val displayNameColumn =
+ cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
+ while (cursor.moveToNext()) {
+ val name = cursor.getString(displayNameColumn)
+ val relativePath = cursor.getString(relativePathColumn)
+ if (name.contains("screenshot", true) or
+ relativePath.contains("screenshot", true)) {
+ if (cache.add(uri.hashCode())) {
+ screenshotTriggered()
+ }
}
}
}
+ } catch (e: IllegalStateException) {
+ Log.e(TAG, e)
}
}
-
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupEvent.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupEvent.kt
deleted file mode 100644
index 614dc30bb..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupEvent.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.thoughtcrime.securesms.backup
-
-data class BackupEvent constructor(val type: Type, val count: Int, val exception: Exception?) {
-
- enum class Type {
- PROGRESS, FINISHED
- }
-
- companion object {
- @JvmStatic fun createProgress(count: Int) = BackupEvent(Type.PROGRESS, count, null)
- @JvmStatic fun createFinished() = BackupEvent(Type.FINISHED, 0, null)
- @JvmStatic fun createFinished(e: Exception?) = BackupEvent(Type.FINISHED, 0, e)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupPassphrase.java b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupPassphrase.java
deleted file mode 100644
index eec2a2e58..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupPassphrase.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package org.thoughtcrime.securesms.backup;
-
-import android.content.Context;
-import android.os.Build;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.thoughtcrime.securesms.crypto.KeyStoreHelper;
-import org.session.libsignal.utilities.Log;
-import org.session.libsession.utilities.TextSecurePreferences;
-
-/**
- * Allows the getting and setting of the backup passphrase, which is stored encrypted on API >= 23.
- */
-public class BackupPassphrase {
-
- private static final String TAG = BackupPassphrase.class.getSimpleName();
-
- public static @Nullable String get(@NonNull Context context) {
- String passphrase = TextSecurePreferences.getBackupPassphrase(context);
- String encryptedPassphrase = TextSecurePreferences.getEncryptedBackupPassphrase(context);
-
- if (Build.VERSION.SDK_INT < 23 || (passphrase == null && encryptedPassphrase == null)) {
- return passphrase;
- }
-
- if (encryptedPassphrase == null) {
- Log.i(TAG, "Migrating to encrypted passphrase.");
- set(context, passphrase);
- encryptedPassphrase = TextSecurePreferences.getEncryptedBackupPassphrase(context);
- }
-
- KeyStoreHelper.SealedData data = KeyStoreHelper.SealedData.fromString(encryptedPassphrase);
- return new String(KeyStoreHelper.unseal(data));
- }
-
- public static void set(@NonNull Context context, @Nullable String passphrase) {
- if (passphrase == null || Build.VERSION.SDK_INT < 23) {
- TextSecurePreferences.setBackupPassphrase(context, passphrase);
- TextSecurePreferences.setEncryptedBackupPassphrase(context, null);
- } else {
- KeyStoreHelper.SealedData encryptedPassphrase = KeyStoreHelper.seal(passphrase.getBytes());
- TextSecurePreferences.setEncryptedBackupPassphrase(context, encryptedPassphrase.serialize());
- TextSecurePreferences.setBackupPassphrase(context, null);
- }
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupPreferences.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupPreferences.kt
deleted file mode 100644
index 8ddfc23a8..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupPreferences.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-package org.thoughtcrime.securesms.backup
-
-import android.content.Context
-import android.content.SharedPreferences
-import android.os.Build
-import android.preference.PreferenceManager
-import android.preference.PreferenceManager.getDefaultSharedPreferencesName
-import org.session.libsession.utilities.TextSecurePreferences
-import org.session.libsignal.utilities.Log
-import org.thoughtcrime.securesms.backup.FullBackupImporter.PREF_PREFIX_TYPE_BOOLEAN
-import org.thoughtcrime.securesms.backup.FullBackupImporter.PREF_PREFIX_TYPE_INT
-import java.util.*
-
-object BackupPreferences {
- // region Backup related
- fun getBackupRecords(context: Context): List {
- val preferences = PreferenceManager.getDefaultSharedPreferences(context)
- val prefsFileName: String
- prefsFileName = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- getDefaultSharedPreferencesName(context)
- } else {
- context.packageName + "_preferences"
- }
- val prefList: LinkedList = LinkedList()
- addBackupEntryInt(prefList, preferences, prefsFileName, TextSecurePreferences.LOCAL_REGISTRATION_ID_PREF)
- addBackupEntryString(prefList, preferences, prefsFileName, TextSecurePreferences.LOCAL_NUMBER_PREF)
- addBackupEntryString(prefList, preferences, prefsFileName, TextSecurePreferences.PROFILE_NAME_PREF)
- addBackupEntryString(prefList, preferences, prefsFileName, TextSecurePreferences.PROFILE_AVATAR_URL_PREF)
- addBackupEntryInt(prefList, preferences, prefsFileName, TextSecurePreferences.PROFILE_AVATAR_ID_PREF)
- addBackupEntryString(prefList, preferences, prefsFileName, TextSecurePreferences.PROFILE_KEY_PREF)
- addBackupEntryBoolean(prefList, preferences, prefsFileName, TextSecurePreferences.IS_USING_FCM)
- return prefList
- }
-
- private fun addBackupEntryString(
- outPrefList: MutableList,
- prefs: SharedPreferences,
- prefFileName: String,
- prefKey: String,
- ) {
- val value = prefs.getString(prefKey, null)
- if (value == null) {
- logBackupEntry(prefKey, false)
- return
- }
- outPrefList.add(BackupProtos.SharedPreference.newBuilder()
- .setFile(prefFileName)
- .setKey(prefKey)
- .setValue(value)
- .build())
- logBackupEntry(prefKey, true)
- }
-
- private fun addBackupEntryInt(
- outPrefList: MutableList,
- prefs: SharedPreferences,
- prefFileName: String,
- prefKey: String,
- ) {
- val value = prefs.getInt(prefKey, -1)
- if (value == -1) {
- logBackupEntry(prefKey, false)
- return
- }
- outPrefList.add(BackupProtos.SharedPreference.newBuilder()
- .setFile(prefFileName)
- .setKey(PREF_PREFIX_TYPE_INT + prefKey) // The prefix denotes the type of the preference.
- .setValue(value.toString())
- .build())
- logBackupEntry(prefKey, true)
- }
-
- private fun addBackupEntryBoolean(
- outPrefList: MutableList,
- prefs: SharedPreferences,
- prefFileName: String,
- prefKey: String,
- ) {
- if (!prefs.contains(prefKey)) {
- logBackupEntry(prefKey, false)
- return
- }
- outPrefList.add(BackupProtos.SharedPreference.newBuilder()
- .setFile(prefFileName)
- .setKey(PREF_PREFIX_TYPE_BOOLEAN + prefKey) // The prefix denotes the type of the preference.
- .setValue(prefs.getBoolean(prefKey, false).toString())
- .build())
- logBackupEntry(prefKey, true)
- }
-
- private fun logBackupEntry(prefName: String, wasIncluded: Boolean) {
- val sb = StringBuilder()
- sb.append("Backup preference ")
- sb.append(if (wasIncluded) "+ " else "- ")
- sb.append('\"').append(prefName).append("\" ")
- if (!wasIncluded) {
- sb.append("(is empty and not included)")
- }
- Log.d("Loki", sb.toString())
- } // endregion
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupProtos.java b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupProtos.java
deleted file mode 100644
index f3b78606f..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupProtos.java
+++ /dev/null
@@ -1,6778 +0,0 @@
-// Generated by the protocol buffer compiler. DO NOT EDIT!
-// source: Backups.proto
-
-package org.thoughtcrime.securesms.backup;
-
-public final class BackupProtos {
- private BackupProtos() {}
- public static void registerAllExtensions(
- com.google.protobuf.ExtensionRegistry registry) {
- }
- public interface SqlStatementOrBuilder
- extends com.google.protobuf.MessageOrBuilder {
-
- // optional string statement = 1;
- /**
- * optional string statement = 1;
- */
- boolean hasStatement();
- /**
- * optional string statement = 1;
- */
- java.lang.String getStatement();
- /**
- * optional string statement = 1;
- */
- com.google.protobuf.ByteString
- getStatementBytes();
-
- // repeated .signal.SqlStatement.SqlParameter parameters = 2;
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- java.util.List
- getParametersList();
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter getParameters(int index);
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- int getParametersCount();
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- java.util.List extends org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameterOrBuilder>
- getParametersOrBuilderList();
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameterOrBuilder getParametersOrBuilder(
- int index);
- }
- /**
- * Protobuf type {@code signal.SqlStatement}
- */
- public static final class SqlStatement extends
- com.google.protobuf.GeneratedMessage
- implements SqlStatementOrBuilder {
- // Use SqlStatement.newBuilder() to construct.
- private SqlStatement(com.google.protobuf.GeneratedMessage.Builder> builder) {
- super(builder);
- this.unknownFields = builder.getUnknownFields();
- }
- private SqlStatement(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); }
-
- private static final SqlStatement defaultInstance;
- public static SqlStatement getDefaultInstance() {
- return defaultInstance;
- }
-
- public SqlStatement getDefaultInstanceForType() {
- return defaultInstance;
- }
-
- private final com.google.protobuf.UnknownFieldSet unknownFields;
- @java.lang.Override
- public final com.google.protobuf.UnknownFieldSet
- getUnknownFields() {
- return this.unknownFields;
- }
- private SqlStatement(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- initFields();
- int mutable_bitField0_ = 0;
- com.google.protobuf.UnknownFieldSet.Builder unknownFields =
- com.google.protobuf.UnknownFieldSet.newBuilder();
- try {
- boolean done = false;
- while (!done) {
- int tag = input.readTag();
- switch (tag) {
- case 0:
- done = true;
- break;
- default: {
- if (!parseUnknownField(input, unknownFields,
- extensionRegistry, tag)) {
- done = true;
- }
- break;
- }
- case 10: {
- bitField0_ |= 0x00000001;
- statement_ = input.readBytes();
- break;
- }
- case 18: {
- if (!((mutable_bitField0_ & 0x00000002) == 0x00000002)) {
- parameters_ = new java.util.ArrayList();
- mutable_bitField0_ |= 0x00000002;
- }
- parameters_.add(input.readMessage(org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.PARSER, extensionRegistry));
- break;
- }
- }
- }
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- throw e.setUnfinishedMessage(this);
- } catch (java.io.IOException e) {
- throw new com.google.protobuf.InvalidProtocolBufferException(
- e.getMessage()).setUnfinishedMessage(this);
- } finally {
- if (((mutable_bitField0_ & 0x00000002) == 0x00000002)) {
- parameters_ = java.util.Collections.unmodifiableList(parameters_);
- }
- this.unknownFields = unknownFields.build();
- makeExtensionsImmutable();
- }
- }
- public static final com.google.protobuf.Descriptors.Descriptor
- getDescriptor() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_descriptor;
- }
-
- protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internalGetFieldAccessorTable() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_fieldAccessorTable
- .ensureFieldAccessorsInitialized(
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.class, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.Builder.class);
- }
-
- public static com.google.protobuf.Parser PARSER =
- new com.google.protobuf.AbstractParser() {
- public SqlStatement parsePartialFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return new SqlStatement(input, extensionRegistry);
- }
- };
-
- @java.lang.Override
- public com.google.protobuf.Parser getParserForType() {
- return PARSER;
- }
-
- public interface SqlParameterOrBuilder
- extends com.google.protobuf.MessageOrBuilder {
-
- // optional string stringParamter = 1;
- /**
- * optional string stringParamter = 1;
- */
- boolean hasStringParamter();
- /**
- * optional string stringParamter = 1;
- */
- java.lang.String getStringParamter();
- /**
- * optional string stringParamter = 1;
- */
- com.google.protobuf.ByteString
- getStringParamterBytes();
-
- // optional uint64 integerParameter = 2;
- /**
- * optional uint64 integerParameter = 2;
- */
- boolean hasIntegerParameter();
- /**
- * optional uint64 integerParameter = 2;
- */
- long getIntegerParameter();
-
- // optional double doubleParameter = 3;
- /**
- * optional double doubleParameter = 3;
- */
- boolean hasDoubleParameter();
- /**
- * optional double doubleParameter = 3;
- */
- double getDoubleParameter();
-
- // optional bytes blobParameter = 4;
- /**
- * optional bytes blobParameter = 4;
- */
- boolean hasBlobParameter();
- /**
- * optional bytes blobParameter = 4;
- */
- com.google.protobuf.ByteString getBlobParameter();
-
- // optional bool nullparameter = 5;
- /**
- * optional bool nullparameter = 5;
- */
- boolean hasNullparameter();
- /**
- * optional bool nullparameter = 5;
- */
- boolean getNullparameter();
- }
- /**
- * Protobuf type {@code signal.SqlStatement.SqlParameter}
- */
- public static final class SqlParameter extends
- com.google.protobuf.GeneratedMessage
- implements SqlParameterOrBuilder {
- // Use SqlParameter.newBuilder() to construct.
- private SqlParameter(com.google.protobuf.GeneratedMessage.Builder> builder) {
- super(builder);
- this.unknownFields = builder.getUnknownFields();
- }
- private SqlParameter(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); }
-
- private static final SqlParameter defaultInstance;
- public static SqlParameter getDefaultInstance() {
- return defaultInstance;
- }
-
- public SqlParameter getDefaultInstanceForType() {
- return defaultInstance;
- }
-
- private final com.google.protobuf.UnknownFieldSet unknownFields;
- @java.lang.Override
- public final com.google.protobuf.UnknownFieldSet
- getUnknownFields() {
- return this.unknownFields;
- }
- private SqlParameter(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- initFields();
- int mutable_bitField0_ = 0;
- com.google.protobuf.UnknownFieldSet.Builder unknownFields =
- com.google.protobuf.UnknownFieldSet.newBuilder();
- try {
- boolean done = false;
- while (!done) {
- int tag = input.readTag();
- switch (tag) {
- case 0:
- done = true;
- break;
- default: {
- if (!parseUnknownField(input, unknownFields,
- extensionRegistry, tag)) {
- done = true;
- }
- break;
- }
- case 10: {
- bitField0_ |= 0x00000001;
- stringParamter_ = input.readBytes();
- break;
- }
- case 16: {
- bitField0_ |= 0x00000002;
- integerParameter_ = input.readUInt64();
- break;
- }
- case 25: {
- bitField0_ |= 0x00000004;
- doubleParameter_ = input.readDouble();
- break;
- }
- case 34: {
- bitField0_ |= 0x00000008;
- blobParameter_ = input.readBytes();
- break;
- }
- case 40: {
- bitField0_ |= 0x00000010;
- nullparameter_ = input.readBool();
- break;
- }
- }
- }
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- throw e.setUnfinishedMessage(this);
- } catch (java.io.IOException e) {
- throw new com.google.protobuf.InvalidProtocolBufferException(
- e.getMessage()).setUnfinishedMessage(this);
- } finally {
- this.unknownFields = unknownFields.build();
- makeExtensionsImmutable();
- }
- }
- public static final com.google.protobuf.Descriptors.Descriptor
- getDescriptor() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_SqlParameter_descriptor;
- }
-
- protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internalGetFieldAccessorTable() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_SqlParameter_fieldAccessorTable
- .ensureFieldAccessorsInitialized(
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.class, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder.class);
- }
-
- public static com.google.protobuf.Parser PARSER =
- new com.google.protobuf.AbstractParser() {
- public SqlParameter parsePartialFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return new SqlParameter(input, extensionRegistry);
- }
- };
-
- @java.lang.Override
- public com.google.protobuf.Parser getParserForType() {
- return PARSER;
- }
-
- private int bitField0_;
- // optional string stringParamter = 1;
- public static final int STRINGPARAMTER_FIELD_NUMBER = 1;
- private java.lang.Object stringParamter_;
- /**
- * optional string stringParamter = 1;
- */
- public boolean hasStringParamter() {
- return ((bitField0_ & 0x00000001) == 0x00000001);
- }
- /**
- * optional string stringParamter = 1;
- */
- public java.lang.String getStringParamter() {
- java.lang.Object ref = stringParamter_;
- if (ref instanceof java.lang.String) {
- return (java.lang.String) ref;
- } else {
- com.google.protobuf.ByteString bs =
- (com.google.protobuf.ByteString) ref;
- java.lang.String s = bs.toStringUtf8();
- if (bs.isValidUtf8()) {
- stringParamter_ = s;
- }
- return s;
- }
- }
- /**
- * optional string stringParamter = 1;
- */
- public com.google.protobuf.ByteString
- getStringParamterBytes() {
- java.lang.Object ref = stringParamter_;
- if (ref instanceof java.lang.String) {
- com.google.protobuf.ByteString b =
- com.google.protobuf.ByteString.copyFromUtf8(
- (java.lang.String) ref);
- stringParamter_ = b;
- return b;
- } else {
- return (com.google.protobuf.ByteString) ref;
- }
- }
-
- // optional uint64 integerParameter = 2;
- public static final int INTEGERPARAMETER_FIELD_NUMBER = 2;
- private long integerParameter_;
- /**
- * optional uint64 integerParameter = 2;
- */
- public boolean hasIntegerParameter() {
- return ((bitField0_ & 0x00000002) == 0x00000002);
- }
- /**
- * optional uint64 integerParameter = 2;
- */
- public long getIntegerParameter() {
- return integerParameter_;
- }
-
- // optional double doubleParameter = 3;
- public static final int DOUBLEPARAMETER_FIELD_NUMBER = 3;
- private double doubleParameter_;
- /**
- * optional double doubleParameter = 3;
- */
- public boolean hasDoubleParameter() {
- return ((bitField0_ & 0x00000004) == 0x00000004);
- }
- /**
- * optional double doubleParameter = 3;
- */
- public double getDoubleParameter() {
- return doubleParameter_;
- }
-
- // optional bytes blobParameter = 4;
- public static final int BLOBPARAMETER_FIELD_NUMBER = 4;
- private com.google.protobuf.ByteString blobParameter_;
- /**
- * optional bytes blobParameter = 4;
- */
- public boolean hasBlobParameter() {
- return ((bitField0_ & 0x00000008) == 0x00000008);
- }
- /**
- * optional bytes blobParameter = 4;
- */
- public com.google.protobuf.ByteString getBlobParameter() {
- return blobParameter_;
- }
-
- // optional bool nullparameter = 5;
- public static final int NULLPARAMETER_FIELD_NUMBER = 5;
- private boolean nullparameter_;
- /**
- * optional bool nullparameter = 5;
- */
- public boolean hasNullparameter() {
- return ((bitField0_ & 0x00000010) == 0x00000010);
- }
- /**
- * optional bool nullparameter = 5;
- */
- public boolean getNullparameter() {
- return nullparameter_;
- }
-
- private void initFields() {
- stringParamter_ = "";
- integerParameter_ = 0L;
- doubleParameter_ = 0D;
- blobParameter_ = com.google.protobuf.ByteString.EMPTY;
- nullparameter_ = false;
- }
- private byte memoizedIsInitialized = -1;
- public final boolean isInitialized() {
- byte isInitialized = memoizedIsInitialized;
- if (isInitialized != -1) return isInitialized == 1;
-
- memoizedIsInitialized = 1;
- return true;
- }
-
- public void writeTo(com.google.protobuf.CodedOutputStream output)
- throws java.io.IOException {
- getSerializedSize();
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- output.writeBytes(1, getStringParamterBytes());
- }
- if (((bitField0_ & 0x00000002) == 0x00000002)) {
- output.writeUInt64(2, integerParameter_);
- }
- if (((bitField0_ & 0x00000004) == 0x00000004)) {
- output.writeDouble(3, doubleParameter_);
- }
- if (((bitField0_ & 0x00000008) == 0x00000008)) {
- output.writeBytes(4, blobParameter_);
- }
- if (((bitField0_ & 0x00000010) == 0x00000010)) {
- output.writeBool(5, nullparameter_);
- }
- getUnknownFields().writeTo(output);
- }
-
- private int memoizedSerializedSize = -1;
- public int getSerializedSize() {
- int size = memoizedSerializedSize;
- if (size != -1) return size;
-
- size = 0;
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- size += com.google.protobuf.CodedOutputStream
- .computeBytesSize(1, getStringParamterBytes());
- }
- if (((bitField0_ & 0x00000002) == 0x00000002)) {
- size += com.google.protobuf.CodedOutputStream
- .computeUInt64Size(2, integerParameter_);
- }
- if (((bitField0_ & 0x00000004) == 0x00000004)) {
- size += com.google.protobuf.CodedOutputStream
- .computeDoubleSize(3, doubleParameter_);
- }
- if (((bitField0_ & 0x00000008) == 0x00000008)) {
- size += com.google.protobuf.CodedOutputStream
- .computeBytesSize(4, blobParameter_);
- }
- if (((bitField0_ & 0x00000010) == 0x00000010)) {
- size += com.google.protobuf.CodedOutputStream
- .computeBoolSize(5, nullparameter_);
- }
- size += getUnknownFields().getSerializedSize();
- memoizedSerializedSize = size;
- return size;
- }
-
- private static final long serialVersionUID = 0L;
- @java.lang.Override
- protected java.lang.Object writeReplace()
- throws java.io.ObjectStreamException {
- return super.writeReplace();
- }
-
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseFrom(
- com.google.protobuf.ByteString data)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseFrom(
- com.google.protobuf.ByteString data,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseFrom(byte[] data)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseFrom(
- byte[] data,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseFrom(java.io.InputStream input)
- throws java.io.IOException {
- return PARSER.parseFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseFrom(
- java.io.InputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseFrom(input, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseDelimitedFrom(java.io.InputStream input)
- throws java.io.IOException {
- return PARSER.parseDelimitedFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseDelimitedFrom(
- java.io.InputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseDelimitedFrom(input, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseFrom(
- com.google.protobuf.CodedInputStream input)
- throws java.io.IOException {
- return PARSER.parseFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseFrom(input, extensionRegistry);
- }
-
- public static Builder newBuilder() { return Builder.create(); }
- public Builder newBuilderForType() { return newBuilder(); }
- public static Builder newBuilder(org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter prototype) {
- return newBuilder().mergeFrom(prototype);
- }
- public Builder toBuilder() { return newBuilder(this); }
-
- @java.lang.Override
- protected Builder newBuilderForType(
- com.google.protobuf.GeneratedMessage.BuilderParent parent) {
- Builder builder = new Builder(parent);
- return builder;
- }
- /**
- * Protobuf type {@code signal.SqlStatement.SqlParameter}
- */
- public static final class Builder extends
- com.google.protobuf.GeneratedMessage.Builder
- implements org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameterOrBuilder {
- public static final com.google.protobuf.Descriptors.Descriptor
- getDescriptor() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_SqlParameter_descriptor;
- }
-
- protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internalGetFieldAccessorTable() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_SqlParameter_fieldAccessorTable
- .ensureFieldAccessorsInitialized(
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.class, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder.class);
- }
-
- // Construct using org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.newBuilder()
- private Builder() {
- maybeForceBuilderInitialization();
- }
-
- private Builder(
- com.google.protobuf.GeneratedMessage.BuilderParent parent) {
- super(parent);
- maybeForceBuilderInitialization();
- }
- private void maybeForceBuilderInitialization() {
- if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
- }
- }
- private static Builder create() {
- return new Builder();
- }
-
- public Builder clear() {
- super.clear();
- stringParamter_ = "";
- bitField0_ = (bitField0_ & ~0x00000001);
- integerParameter_ = 0L;
- bitField0_ = (bitField0_ & ~0x00000002);
- doubleParameter_ = 0D;
- bitField0_ = (bitField0_ & ~0x00000004);
- blobParameter_ = com.google.protobuf.ByteString.EMPTY;
- bitField0_ = (bitField0_ & ~0x00000008);
- nullparameter_ = false;
- bitField0_ = (bitField0_ & ~0x00000010);
- return this;
- }
-
- public Builder clone() {
- return create().mergeFrom(buildPartial());
- }
-
- public com.google.protobuf.Descriptors.Descriptor
- getDescriptorForType() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_SqlParameter_descriptor;
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter getDefaultInstanceForType() {
- return org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.getDefaultInstance();
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter build() {
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter result = buildPartial();
- if (!result.isInitialized()) {
- throw newUninitializedMessageException(result);
- }
- return result;
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter buildPartial() {
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter result = new org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter(this);
- int from_bitField0_ = bitField0_;
- int to_bitField0_ = 0;
- if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
- to_bitField0_ |= 0x00000001;
- }
- result.stringParamter_ = stringParamter_;
- if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
- to_bitField0_ |= 0x00000002;
- }
- result.integerParameter_ = integerParameter_;
- if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
- to_bitField0_ |= 0x00000004;
- }
- result.doubleParameter_ = doubleParameter_;
- if (((from_bitField0_ & 0x00000008) == 0x00000008)) {
- to_bitField0_ |= 0x00000008;
- }
- result.blobParameter_ = blobParameter_;
- if (((from_bitField0_ & 0x00000010) == 0x00000010)) {
- to_bitField0_ |= 0x00000010;
- }
- result.nullparameter_ = nullparameter_;
- result.bitField0_ = to_bitField0_;
- onBuilt();
- return result;
- }
-
- public Builder mergeFrom(com.google.protobuf.Message other) {
- if (other instanceof org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter) {
- return mergeFrom((org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter)other);
- } else {
- super.mergeFrom(other);
- return this;
- }
- }
-
- public Builder mergeFrom(org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter other) {
- if (other == org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.getDefaultInstance()) return this;
- if (other.hasStringParamter()) {
- bitField0_ |= 0x00000001;
- stringParamter_ = other.stringParamter_;
- onChanged();
- }
- if (other.hasIntegerParameter()) {
- setIntegerParameter(other.getIntegerParameter());
- }
- if (other.hasDoubleParameter()) {
- setDoubleParameter(other.getDoubleParameter());
- }
- if (other.hasBlobParameter()) {
- setBlobParameter(other.getBlobParameter());
- }
- if (other.hasNullparameter()) {
- setNullparameter(other.getNullparameter());
- }
- this.mergeUnknownFields(other.getUnknownFields());
- return this;
- }
-
- public final boolean isInitialized() {
- return true;
- }
-
- public Builder mergeFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parsedMessage = null;
- try {
- parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- parsedMessage = (org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter) e.getUnfinishedMessage();
- throw e;
- } finally {
- if (parsedMessage != null) {
- mergeFrom(parsedMessage);
- }
- }
- return this;
- }
- private int bitField0_;
-
- // optional string stringParamter = 1;
- private java.lang.Object stringParamter_ = "";
- /**
- * optional string stringParamter = 1;
- */
- public boolean hasStringParamter() {
- return ((bitField0_ & 0x00000001) == 0x00000001);
- }
- /**
- * optional string stringParamter = 1;
- */
- public java.lang.String getStringParamter() {
- java.lang.Object ref = stringParamter_;
- if (!(ref instanceof java.lang.String)) {
- java.lang.String s = ((com.google.protobuf.ByteString) ref)
- .toStringUtf8();
- stringParamter_ = s;
- return s;
- } else {
- return (java.lang.String) ref;
- }
- }
- /**
- * optional string stringParamter = 1;
- */
- public com.google.protobuf.ByteString
- getStringParamterBytes() {
- java.lang.Object ref = stringParamter_;
- if (ref instanceof String) {
- com.google.protobuf.ByteString b =
- com.google.protobuf.ByteString.copyFromUtf8(
- (java.lang.String) ref);
- stringParamter_ = b;
- return b;
- } else {
- return (com.google.protobuf.ByteString) ref;
- }
- }
- /**
- * optional string stringParamter = 1;
- */
- public Builder setStringParamter(
- java.lang.String value) {
- if (value == null) {
- throw new NullPointerException();
- }
- bitField0_ |= 0x00000001;
- stringParamter_ = value;
- onChanged();
- return this;
- }
- /**
- * optional string stringParamter = 1;
- */
- public Builder clearStringParamter() {
- bitField0_ = (bitField0_ & ~0x00000001);
- stringParamter_ = getDefaultInstance().getStringParamter();
- onChanged();
- return this;
- }
- /**
- * optional string stringParamter = 1;
- */
- public Builder setStringParamterBytes(
- com.google.protobuf.ByteString value) {
- if (value == null) {
- throw new NullPointerException();
- }
- bitField0_ |= 0x00000001;
- stringParamter_ = value;
- onChanged();
- return this;
- }
-
- // optional uint64 integerParameter = 2;
- private long integerParameter_ ;
- /**
- * optional uint64 integerParameter = 2;
- */
- public boolean hasIntegerParameter() {
- return ((bitField0_ & 0x00000002) == 0x00000002);
- }
- /**
- * optional uint64 integerParameter = 2;
- */
- public long getIntegerParameter() {
- return integerParameter_;
- }
- /**
- * optional uint64 integerParameter = 2;
- */
- public Builder setIntegerParameter(long value) {
- bitField0_ |= 0x00000002;
- integerParameter_ = value;
- onChanged();
- return this;
- }
- /**
- * optional uint64 integerParameter = 2;
- */
- public Builder clearIntegerParameter() {
- bitField0_ = (bitField0_ & ~0x00000002);
- integerParameter_ = 0L;
- onChanged();
- return this;
- }
-
- // optional double doubleParameter = 3;
- private double doubleParameter_ ;
- /**
- * optional double doubleParameter = 3;
- */
- public boolean hasDoubleParameter() {
- return ((bitField0_ & 0x00000004) == 0x00000004);
- }
- /**
- * optional double doubleParameter = 3;
- */
- public double getDoubleParameter() {
- return doubleParameter_;
- }
- /**
- * optional double doubleParameter = 3;
- */
- public Builder setDoubleParameter(double value) {
- bitField0_ |= 0x00000004;
- doubleParameter_ = value;
- onChanged();
- return this;
- }
- /**
- * optional double doubleParameter = 3;
- */
- public Builder clearDoubleParameter() {
- bitField0_ = (bitField0_ & ~0x00000004);
- doubleParameter_ = 0D;
- onChanged();
- return this;
- }
-
- // optional bytes blobParameter = 4;
- private com.google.protobuf.ByteString blobParameter_ = com.google.protobuf.ByteString.EMPTY;
- /**
- * optional bytes blobParameter = 4;
- */
- public boolean hasBlobParameter() {
- return ((bitField0_ & 0x00000008) == 0x00000008);
- }
- /**
- * optional bytes blobParameter = 4;
- */
- public com.google.protobuf.ByteString getBlobParameter() {
- return blobParameter_;
- }
- /**
- * optional bytes blobParameter = 4;
- */
- public Builder setBlobParameter(com.google.protobuf.ByteString value) {
- if (value == null) {
- throw new NullPointerException();
- }
- bitField0_ |= 0x00000008;
- blobParameter_ = value;
- onChanged();
- return this;
- }
- /**
- * optional bytes blobParameter = 4;
- */
- public Builder clearBlobParameter() {
- bitField0_ = (bitField0_ & ~0x00000008);
- blobParameter_ = getDefaultInstance().getBlobParameter();
- onChanged();
- return this;
- }
-
- // optional bool nullparameter = 5;
- private boolean nullparameter_ ;
- /**
- * optional bool nullparameter = 5;
- */
- public boolean hasNullparameter() {
- return ((bitField0_ & 0x00000010) == 0x00000010);
- }
- /**
- * optional bool nullparameter = 5;
- */
- public boolean getNullparameter() {
- return nullparameter_;
- }
- /**
- * optional bool nullparameter = 5;
- */
- public Builder setNullparameter(boolean value) {
- bitField0_ |= 0x00000010;
- nullparameter_ = value;
- onChanged();
- return this;
- }
- /**
- * optional bool nullparameter = 5;
- */
- public Builder clearNullparameter() {
- bitField0_ = (bitField0_ & ~0x00000010);
- nullparameter_ = false;
- onChanged();
- return this;
- }
-
- // @@protoc_insertion_point(builder_scope:signal.SqlStatement.SqlParameter)
- }
-
- static {
- defaultInstance = new SqlParameter(true);
- defaultInstance.initFields();
- }
-
- // @@protoc_insertion_point(class_scope:signal.SqlStatement.SqlParameter)
- }
-
- private int bitField0_;
- // optional string statement = 1;
- public static final int STATEMENT_FIELD_NUMBER = 1;
- private java.lang.Object statement_;
- /**
- * optional string statement = 1;
- */
- public boolean hasStatement() {
- return ((bitField0_ & 0x00000001) == 0x00000001);
- }
- /**
- * optional string statement = 1;
- */
- public java.lang.String getStatement() {
- java.lang.Object ref = statement_;
- if (ref instanceof java.lang.String) {
- return (java.lang.String) ref;
- } else {
- com.google.protobuf.ByteString bs =
- (com.google.protobuf.ByteString) ref;
- java.lang.String s = bs.toStringUtf8();
- if (bs.isValidUtf8()) {
- statement_ = s;
- }
- return s;
- }
- }
- /**
- * optional string statement = 1;
- */
- public com.google.protobuf.ByteString
- getStatementBytes() {
- java.lang.Object ref = statement_;
- if (ref instanceof java.lang.String) {
- com.google.protobuf.ByteString b =
- com.google.protobuf.ByteString.copyFromUtf8(
- (java.lang.String) ref);
- statement_ = b;
- return b;
- } else {
- return (com.google.protobuf.ByteString) ref;
- }
- }
-
- // repeated .signal.SqlStatement.SqlParameter parameters = 2;
- public static final int PARAMETERS_FIELD_NUMBER = 2;
- private java.util.List parameters_;
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public java.util.List getParametersList() {
- return parameters_;
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public java.util.List extends org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameterOrBuilder>
- getParametersOrBuilderList() {
- return parameters_;
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public int getParametersCount() {
- return parameters_.size();
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter getParameters(int index) {
- return parameters_.get(index);
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameterOrBuilder getParametersOrBuilder(
- int index) {
- return parameters_.get(index);
- }
-
- private void initFields() {
- statement_ = "";
- parameters_ = java.util.Collections.emptyList();
- }
- private byte memoizedIsInitialized = -1;
- public final boolean isInitialized() {
- byte isInitialized = memoizedIsInitialized;
- if (isInitialized != -1) return isInitialized == 1;
-
- memoizedIsInitialized = 1;
- return true;
- }
-
- public void writeTo(com.google.protobuf.CodedOutputStream output)
- throws java.io.IOException {
- getSerializedSize();
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- output.writeBytes(1, getStatementBytes());
- }
- for (int i = 0; i < parameters_.size(); i++) {
- output.writeMessage(2, parameters_.get(i));
- }
- getUnknownFields().writeTo(output);
- }
-
- private int memoizedSerializedSize = -1;
- public int getSerializedSize() {
- int size = memoizedSerializedSize;
- if (size != -1) return size;
-
- size = 0;
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- size += com.google.protobuf.CodedOutputStream
- .computeBytesSize(1, getStatementBytes());
- }
- for (int i = 0; i < parameters_.size(); i++) {
- size += com.google.protobuf.CodedOutputStream
- .computeMessageSize(2, parameters_.get(i));
- }
- size += getUnknownFields().getSerializedSize();
- memoizedSerializedSize = size;
- return size;
- }
-
- private static final long serialVersionUID = 0L;
- @java.lang.Override
- protected java.lang.Object writeReplace()
- throws java.io.ObjectStreamException {
- return super.writeReplace();
- }
-
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseFrom(
- com.google.protobuf.ByteString data)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseFrom(
- com.google.protobuf.ByteString data,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseFrom(byte[] data)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseFrom(
- byte[] data,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseFrom(java.io.InputStream input)
- throws java.io.IOException {
- return PARSER.parseFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseFrom(
- java.io.InputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseFrom(input, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseDelimitedFrom(java.io.InputStream input)
- throws java.io.IOException {
- return PARSER.parseDelimitedFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseDelimitedFrom(
- java.io.InputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseDelimitedFrom(input, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseFrom(
- com.google.protobuf.CodedInputStream input)
- throws java.io.IOException {
- return PARSER.parseFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseFrom(input, extensionRegistry);
- }
-
- public static Builder newBuilder() { return Builder.create(); }
- public Builder newBuilderForType() { return newBuilder(); }
- public static Builder newBuilder(org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement prototype) {
- return newBuilder().mergeFrom(prototype);
- }
- public Builder toBuilder() { return newBuilder(this); }
-
- @java.lang.Override
- protected Builder newBuilderForType(
- com.google.protobuf.GeneratedMessage.BuilderParent parent) {
- Builder builder = new Builder(parent);
- return builder;
- }
- /**
- * Protobuf type {@code signal.SqlStatement}
- */
- public static final class Builder extends
- com.google.protobuf.GeneratedMessage.Builder
- implements org.thoughtcrime.securesms.backup.BackupProtos.SqlStatementOrBuilder {
- public static final com.google.protobuf.Descriptors.Descriptor
- getDescriptor() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_descriptor;
- }
-
- protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internalGetFieldAccessorTable() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_fieldAccessorTable
- .ensureFieldAccessorsInitialized(
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.class, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.Builder.class);
- }
-
- // Construct using org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.newBuilder()
- private Builder() {
- maybeForceBuilderInitialization();
- }
-
- private Builder(
- com.google.protobuf.GeneratedMessage.BuilderParent parent) {
- super(parent);
- maybeForceBuilderInitialization();
- }
- private void maybeForceBuilderInitialization() {
- if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
- getParametersFieldBuilder();
- }
- }
- private static Builder create() {
- return new Builder();
- }
-
- public Builder clear() {
- super.clear();
- statement_ = "";
- bitField0_ = (bitField0_ & ~0x00000001);
- if (parametersBuilder_ == null) {
- parameters_ = java.util.Collections.emptyList();
- bitField0_ = (bitField0_ & ~0x00000002);
- } else {
- parametersBuilder_.clear();
- }
- return this;
- }
-
- public Builder clone() {
- return create().mergeFrom(buildPartial());
- }
-
- public com.google.protobuf.Descriptors.Descriptor
- getDescriptorForType() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_descriptor;
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement getDefaultInstanceForType() {
- return org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.getDefaultInstance();
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement build() {
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement result = buildPartial();
- if (!result.isInitialized()) {
- throw newUninitializedMessageException(result);
- }
- return result;
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement buildPartial() {
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement result = new org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement(this);
- int from_bitField0_ = bitField0_;
- int to_bitField0_ = 0;
- if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
- to_bitField0_ |= 0x00000001;
- }
- result.statement_ = statement_;
- if (parametersBuilder_ == null) {
- if (((bitField0_ & 0x00000002) == 0x00000002)) {
- parameters_ = java.util.Collections.unmodifiableList(parameters_);
- bitField0_ = (bitField0_ & ~0x00000002);
- }
- result.parameters_ = parameters_;
- } else {
- result.parameters_ = parametersBuilder_.build();
- }
- result.bitField0_ = to_bitField0_;
- onBuilt();
- return result;
- }
-
- public Builder mergeFrom(com.google.protobuf.Message other) {
- if (other instanceof org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement) {
- return mergeFrom((org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement)other);
- } else {
- super.mergeFrom(other);
- return this;
- }
- }
-
- public Builder mergeFrom(org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement other) {
- if (other == org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.getDefaultInstance()) return this;
- if (other.hasStatement()) {
- bitField0_ |= 0x00000001;
- statement_ = other.statement_;
- onChanged();
- }
- if (parametersBuilder_ == null) {
- if (!other.parameters_.isEmpty()) {
- if (parameters_.isEmpty()) {
- parameters_ = other.parameters_;
- bitField0_ = (bitField0_ & ~0x00000002);
- } else {
- ensureParametersIsMutable();
- parameters_.addAll(other.parameters_);
- }
- onChanged();
- }
- } else {
- if (!other.parameters_.isEmpty()) {
- if (parametersBuilder_.isEmpty()) {
- parametersBuilder_.dispose();
- parametersBuilder_ = null;
- parameters_ = other.parameters_;
- bitField0_ = (bitField0_ & ~0x00000002);
- parametersBuilder_ =
- com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ?
- getParametersFieldBuilder() : null;
- } else {
- parametersBuilder_.addAllMessages(other.parameters_);
- }
- }
- }
- this.mergeUnknownFields(other.getUnknownFields());
- return this;
- }
-
- public final boolean isInitialized() {
- return true;
- }
-
- public Builder mergeFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parsedMessage = null;
- try {
- parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- parsedMessage = (org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement) e.getUnfinishedMessage();
- throw e;
- } finally {
- if (parsedMessage != null) {
- mergeFrom(parsedMessage);
- }
- }
- return this;
- }
- private int bitField0_;
-
- // optional string statement = 1;
- private java.lang.Object statement_ = "";
- /**
- * optional string statement = 1;
- */
- public boolean hasStatement() {
- return ((bitField0_ & 0x00000001) == 0x00000001);
- }
- /**
- * optional string statement = 1;
- */
- public java.lang.String getStatement() {
- java.lang.Object ref = statement_;
- if (!(ref instanceof java.lang.String)) {
- java.lang.String s = ((com.google.protobuf.ByteString) ref)
- .toStringUtf8();
- statement_ = s;
- return s;
- } else {
- return (java.lang.String) ref;
- }
- }
- /**
- * optional string statement = 1;
- */
- public com.google.protobuf.ByteString
- getStatementBytes() {
- java.lang.Object ref = statement_;
- if (ref instanceof String) {
- com.google.protobuf.ByteString b =
- com.google.protobuf.ByteString.copyFromUtf8(
- (java.lang.String) ref);
- statement_ = b;
- return b;
- } else {
- return (com.google.protobuf.ByteString) ref;
- }
- }
- /**
- * optional string statement = 1;
- */
- public Builder setStatement(
- java.lang.String value) {
- if (value == null) {
- throw new NullPointerException();
- }
- bitField0_ |= 0x00000001;
- statement_ = value;
- onChanged();
- return this;
- }
- /**
- * optional string statement = 1;
- */
- public Builder clearStatement() {
- bitField0_ = (bitField0_ & ~0x00000001);
- statement_ = getDefaultInstance().getStatement();
- onChanged();
- return this;
- }
- /**
- * optional string statement = 1;
- */
- public Builder setStatementBytes(
- com.google.protobuf.ByteString value) {
- if (value == null) {
- throw new NullPointerException();
- }
- bitField0_ |= 0x00000001;
- statement_ = value;
- onChanged();
- return this;
- }
-
- // repeated .signal.SqlStatement.SqlParameter parameters = 2;
- private java.util.List parameters_ =
- java.util.Collections.emptyList();
- private void ensureParametersIsMutable() {
- if (!((bitField0_ & 0x00000002) == 0x00000002)) {
- parameters_ = new java.util.ArrayList(parameters_);
- bitField0_ |= 0x00000002;
- }
- }
-
- private com.google.protobuf.RepeatedFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameterOrBuilder> parametersBuilder_;
-
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public java.util.List getParametersList() {
- if (parametersBuilder_ == null) {
- return java.util.Collections.unmodifiableList(parameters_);
- } else {
- return parametersBuilder_.getMessageList();
- }
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public int getParametersCount() {
- if (parametersBuilder_ == null) {
- return parameters_.size();
- } else {
- return parametersBuilder_.getCount();
- }
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter getParameters(int index) {
- if (parametersBuilder_ == null) {
- return parameters_.get(index);
- } else {
- return parametersBuilder_.getMessage(index);
- }
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public Builder setParameters(
- int index, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter value) {
- if (parametersBuilder_ == null) {
- if (value == null) {
- throw new NullPointerException();
- }
- ensureParametersIsMutable();
- parameters_.set(index, value);
- onChanged();
- } else {
- parametersBuilder_.setMessage(index, value);
- }
- return this;
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public Builder setParameters(
- int index, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder builderForValue) {
- if (parametersBuilder_ == null) {
- ensureParametersIsMutable();
- parameters_.set(index, builderForValue.build());
- onChanged();
- } else {
- parametersBuilder_.setMessage(index, builderForValue.build());
- }
- return this;
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public Builder addParameters(org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter value) {
- if (parametersBuilder_ == null) {
- if (value == null) {
- throw new NullPointerException();
- }
- ensureParametersIsMutable();
- parameters_.add(value);
- onChanged();
- } else {
- parametersBuilder_.addMessage(value);
- }
- return this;
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public Builder addParameters(
- int index, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter value) {
- if (parametersBuilder_ == null) {
- if (value == null) {
- throw new NullPointerException();
- }
- ensureParametersIsMutable();
- parameters_.add(index, value);
- onChanged();
- } else {
- parametersBuilder_.addMessage(index, value);
- }
- return this;
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public Builder addParameters(
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder builderForValue) {
- if (parametersBuilder_ == null) {
- ensureParametersIsMutable();
- parameters_.add(builderForValue.build());
- onChanged();
- } else {
- parametersBuilder_.addMessage(builderForValue.build());
- }
- return this;
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public Builder addParameters(
- int index, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder builderForValue) {
- if (parametersBuilder_ == null) {
- ensureParametersIsMutable();
- parameters_.add(index, builderForValue.build());
- onChanged();
- } else {
- parametersBuilder_.addMessage(index, builderForValue.build());
- }
- return this;
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public Builder addAllParameters(
- java.lang.Iterable extends org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter> values) {
- if (parametersBuilder_ == null) {
- ensureParametersIsMutable();
- super.addAll(values, parameters_);
- onChanged();
- } else {
- parametersBuilder_.addAllMessages(values);
- }
- return this;
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public Builder clearParameters() {
- if (parametersBuilder_ == null) {
- parameters_ = java.util.Collections.emptyList();
- bitField0_ = (bitField0_ & ~0x00000002);
- onChanged();
- } else {
- parametersBuilder_.clear();
- }
- return this;
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public Builder removeParameters(int index) {
- if (parametersBuilder_ == null) {
- ensureParametersIsMutable();
- parameters_.remove(index);
- onChanged();
- } else {
- parametersBuilder_.remove(index);
- }
- return this;
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder getParametersBuilder(
- int index) {
- return getParametersFieldBuilder().getBuilder(index);
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameterOrBuilder getParametersOrBuilder(
- int index) {
- if (parametersBuilder_ == null) {
- return parameters_.get(index); } else {
- return parametersBuilder_.getMessageOrBuilder(index);
- }
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public java.util.List extends org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameterOrBuilder>
- getParametersOrBuilderList() {
- if (parametersBuilder_ != null) {
- return parametersBuilder_.getMessageOrBuilderList();
- } else {
- return java.util.Collections.unmodifiableList(parameters_);
- }
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder addParametersBuilder() {
- return getParametersFieldBuilder().addBuilder(
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.getDefaultInstance());
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder addParametersBuilder(
- int index) {
- return getParametersFieldBuilder().addBuilder(
- index, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.getDefaultInstance());
- }
- /**
- * repeated .signal.SqlStatement.SqlParameter parameters = 2;
- */
- public java.util.List
- getParametersBuilderList() {
- return getParametersFieldBuilder().getBuilderList();
- }
- private com.google.protobuf.RepeatedFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameterOrBuilder>
- getParametersFieldBuilder() {
- if (parametersBuilder_ == null) {
- parametersBuilder_ = new com.google.protobuf.RepeatedFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameterOrBuilder>(
- parameters_,
- ((bitField0_ & 0x00000002) == 0x00000002),
- getParentForChildren(),
- isClean());
- parameters_ = null;
- }
- return parametersBuilder_;
- }
-
- // @@protoc_insertion_point(builder_scope:signal.SqlStatement)
- }
-
- static {
- defaultInstance = new SqlStatement(true);
- defaultInstance.initFields();
- }
-
- // @@protoc_insertion_point(class_scope:signal.SqlStatement)
- }
-
- public interface SharedPreferenceOrBuilder
- extends com.google.protobuf.MessageOrBuilder {
-
- // optional string file = 1;
- /**
- * optional string file = 1;
- */
- boolean hasFile();
- /**
- * optional string file = 1;
- */
- java.lang.String getFile();
- /**
- * optional string file = 1;
- */
- com.google.protobuf.ByteString
- getFileBytes();
-
- // optional string key = 2;
- /**
- * optional string key = 2;
- */
- boolean hasKey();
- /**
- * optional string key = 2;
- */
- java.lang.String getKey();
- /**
- * optional string key = 2;
- */
- com.google.protobuf.ByteString
- getKeyBytes();
-
- // optional string value = 3;
- /**
- * optional string value = 3;
- */
- boolean hasValue();
- /**
- * optional string value = 3;
- */
- java.lang.String getValue();
- /**
- * optional string value = 3;
- */
- com.google.protobuf.ByteString
- getValueBytes();
- }
- /**
- * Protobuf type {@code signal.SharedPreference}
- */
- public static final class SharedPreference extends
- com.google.protobuf.GeneratedMessage
- implements SharedPreferenceOrBuilder {
- // Use SharedPreference.newBuilder() to construct.
- private SharedPreference(com.google.protobuf.GeneratedMessage.Builder> builder) {
- super(builder);
- this.unknownFields = builder.getUnknownFields();
- }
- private SharedPreference(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); }
-
- private static final SharedPreference defaultInstance;
- public static SharedPreference getDefaultInstance() {
- return defaultInstance;
- }
-
- public SharedPreference getDefaultInstanceForType() {
- return defaultInstance;
- }
-
- private final com.google.protobuf.UnknownFieldSet unknownFields;
- @java.lang.Override
- public final com.google.protobuf.UnknownFieldSet
- getUnknownFields() {
- return this.unknownFields;
- }
- private SharedPreference(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- initFields();
- int mutable_bitField0_ = 0;
- com.google.protobuf.UnknownFieldSet.Builder unknownFields =
- com.google.protobuf.UnknownFieldSet.newBuilder();
- try {
- boolean done = false;
- while (!done) {
- int tag = input.readTag();
- switch (tag) {
- case 0:
- done = true;
- break;
- default: {
- if (!parseUnknownField(input, unknownFields,
- extensionRegistry, tag)) {
- done = true;
- }
- break;
- }
- case 10: {
- bitField0_ |= 0x00000001;
- file_ = input.readBytes();
- break;
- }
- case 18: {
- bitField0_ |= 0x00000002;
- key_ = input.readBytes();
- break;
- }
- case 26: {
- bitField0_ |= 0x00000004;
- value_ = input.readBytes();
- break;
- }
- }
- }
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- throw e.setUnfinishedMessage(this);
- } catch (java.io.IOException e) {
- throw new com.google.protobuf.InvalidProtocolBufferException(
- e.getMessage()).setUnfinishedMessage(this);
- } finally {
- this.unknownFields = unknownFields.build();
- makeExtensionsImmutable();
- }
- }
- public static final com.google.protobuf.Descriptors.Descriptor
- getDescriptor() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SharedPreference_descriptor;
- }
-
- protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internalGetFieldAccessorTable() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SharedPreference_fieldAccessorTable
- .ensureFieldAccessorsInitialized(
- org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.class, org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.Builder.class);
- }
-
- public static com.google.protobuf.Parser PARSER =
- new com.google.protobuf.AbstractParser() {
- public SharedPreference parsePartialFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return new SharedPreference(input, extensionRegistry);
- }
- };
-
- @java.lang.Override
- public com.google.protobuf.Parser getParserForType() {
- return PARSER;
- }
-
- private int bitField0_;
- // optional string file = 1;
- public static final int FILE_FIELD_NUMBER = 1;
- private java.lang.Object file_;
- /**
- * optional string file = 1;
- */
- public boolean hasFile() {
- return ((bitField0_ & 0x00000001) == 0x00000001);
- }
- /**
- * optional string file = 1;
- */
- public java.lang.String getFile() {
- java.lang.Object ref = file_;
- if (ref instanceof java.lang.String) {
- return (java.lang.String) ref;
- } else {
- com.google.protobuf.ByteString bs =
- (com.google.protobuf.ByteString) ref;
- java.lang.String s = bs.toStringUtf8();
- if (bs.isValidUtf8()) {
- file_ = s;
- }
- return s;
- }
- }
- /**
- * optional string file = 1;
- */
- public com.google.protobuf.ByteString
- getFileBytes() {
- java.lang.Object ref = file_;
- if (ref instanceof java.lang.String) {
- com.google.protobuf.ByteString b =
- com.google.protobuf.ByteString.copyFromUtf8(
- (java.lang.String) ref);
- file_ = b;
- return b;
- } else {
- return (com.google.protobuf.ByteString) ref;
- }
- }
-
- // optional string key = 2;
- public static final int KEY_FIELD_NUMBER = 2;
- private java.lang.Object key_;
- /**
- * optional string key = 2;
- */
- public boolean hasKey() {
- return ((bitField0_ & 0x00000002) == 0x00000002);
- }
- /**
- * optional string key = 2;
- */
- public java.lang.String getKey() {
- java.lang.Object ref = key_;
- if (ref instanceof java.lang.String) {
- return (java.lang.String) ref;
- } else {
- com.google.protobuf.ByteString bs =
- (com.google.protobuf.ByteString) ref;
- java.lang.String s = bs.toStringUtf8();
- if (bs.isValidUtf8()) {
- key_ = s;
- }
- return s;
- }
- }
- /**
- * optional string key = 2;
- */
- public com.google.protobuf.ByteString
- getKeyBytes() {
- java.lang.Object ref = key_;
- if (ref instanceof java.lang.String) {
- com.google.protobuf.ByteString b =
- com.google.protobuf.ByteString.copyFromUtf8(
- (java.lang.String) ref);
- key_ = b;
- return b;
- } else {
- return (com.google.protobuf.ByteString) ref;
- }
- }
-
- // optional string value = 3;
- public static final int VALUE_FIELD_NUMBER = 3;
- private java.lang.Object value_;
- /**
- * optional string value = 3;
- */
- public boolean hasValue() {
- return ((bitField0_ & 0x00000004) == 0x00000004);
- }
- /**
- * optional string value = 3;
- */
- public java.lang.String getValue() {
- java.lang.Object ref = value_;
- if (ref instanceof java.lang.String) {
- return (java.lang.String) ref;
- } else {
- com.google.protobuf.ByteString bs =
- (com.google.protobuf.ByteString) ref;
- java.lang.String s = bs.toStringUtf8();
- if (bs.isValidUtf8()) {
- value_ = s;
- }
- return s;
- }
- }
- /**
- * optional string value = 3;
- */
- public com.google.protobuf.ByteString
- getValueBytes() {
- java.lang.Object ref = value_;
- if (ref instanceof java.lang.String) {
- com.google.protobuf.ByteString b =
- com.google.protobuf.ByteString.copyFromUtf8(
- (java.lang.String) ref);
- value_ = b;
- return b;
- } else {
- return (com.google.protobuf.ByteString) ref;
- }
- }
-
- private void initFields() {
- file_ = "";
- key_ = "";
- value_ = "";
- }
- private byte memoizedIsInitialized = -1;
- public final boolean isInitialized() {
- byte isInitialized = memoizedIsInitialized;
- if (isInitialized != -1) return isInitialized == 1;
-
- memoizedIsInitialized = 1;
- return true;
- }
-
- public void writeTo(com.google.protobuf.CodedOutputStream output)
- throws java.io.IOException {
- getSerializedSize();
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- output.writeBytes(1, getFileBytes());
- }
- if (((bitField0_ & 0x00000002) == 0x00000002)) {
- output.writeBytes(2, getKeyBytes());
- }
- if (((bitField0_ & 0x00000004) == 0x00000004)) {
- output.writeBytes(3, getValueBytes());
- }
- getUnknownFields().writeTo(output);
- }
-
- private int memoizedSerializedSize = -1;
- public int getSerializedSize() {
- int size = memoizedSerializedSize;
- if (size != -1) return size;
-
- size = 0;
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- size += com.google.protobuf.CodedOutputStream
- .computeBytesSize(1, getFileBytes());
- }
- if (((bitField0_ & 0x00000002) == 0x00000002)) {
- size += com.google.protobuf.CodedOutputStream
- .computeBytesSize(2, getKeyBytes());
- }
- if (((bitField0_ & 0x00000004) == 0x00000004)) {
- size += com.google.protobuf.CodedOutputStream
- .computeBytesSize(3, getValueBytes());
- }
- size += getUnknownFields().getSerializedSize();
- memoizedSerializedSize = size;
- return size;
- }
-
- private static final long serialVersionUID = 0L;
- @java.lang.Override
- protected java.lang.Object writeReplace()
- throws java.io.ObjectStreamException {
- return super.writeReplace();
- }
-
- public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseFrom(
- com.google.protobuf.ByteString data)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseFrom(
- com.google.protobuf.ByteString data,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseFrom(byte[] data)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseFrom(
- byte[] data,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseFrom(java.io.InputStream input)
- throws java.io.IOException {
- return PARSER.parseFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseFrom(
- java.io.InputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseFrom(input, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseDelimitedFrom(java.io.InputStream input)
- throws java.io.IOException {
- return PARSER.parseDelimitedFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseDelimitedFrom(
- java.io.InputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseDelimitedFrom(input, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseFrom(
- com.google.protobuf.CodedInputStream input)
- throws java.io.IOException {
- return PARSER.parseFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseFrom(input, extensionRegistry);
- }
-
- public static Builder newBuilder() { return Builder.create(); }
- public Builder newBuilderForType() { return newBuilder(); }
- public static Builder newBuilder(org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference prototype) {
- return newBuilder().mergeFrom(prototype);
- }
- public Builder toBuilder() { return newBuilder(this); }
-
- @java.lang.Override
- protected Builder newBuilderForType(
- com.google.protobuf.GeneratedMessage.BuilderParent parent) {
- Builder builder = new Builder(parent);
- return builder;
- }
- /**
- * Protobuf type {@code signal.SharedPreference}
- */
- public static final class Builder extends
- com.google.protobuf.GeneratedMessage.Builder
- implements org.thoughtcrime.securesms.backup.BackupProtos.SharedPreferenceOrBuilder {
- public static final com.google.protobuf.Descriptors.Descriptor
- getDescriptor() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SharedPreference_descriptor;
- }
-
- protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internalGetFieldAccessorTable() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SharedPreference_fieldAccessorTable
- .ensureFieldAccessorsInitialized(
- org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.class, org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.Builder.class);
- }
-
- // Construct using org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.newBuilder()
- private Builder() {
- maybeForceBuilderInitialization();
- }
-
- private Builder(
- com.google.protobuf.GeneratedMessage.BuilderParent parent) {
- super(parent);
- maybeForceBuilderInitialization();
- }
- private void maybeForceBuilderInitialization() {
- if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
- }
- }
- private static Builder create() {
- return new Builder();
- }
-
- public Builder clear() {
- super.clear();
- file_ = "";
- bitField0_ = (bitField0_ & ~0x00000001);
- key_ = "";
- bitField0_ = (bitField0_ & ~0x00000002);
- value_ = "";
- bitField0_ = (bitField0_ & ~0x00000004);
- return this;
- }
-
- public Builder clone() {
- return create().mergeFrom(buildPartial());
- }
-
- public com.google.protobuf.Descriptors.Descriptor
- getDescriptorForType() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SharedPreference_descriptor;
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference getDefaultInstanceForType() {
- return org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.getDefaultInstance();
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference build() {
- org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference result = buildPartial();
- if (!result.isInitialized()) {
- throw newUninitializedMessageException(result);
- }
- return result;
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference buildPartial() {
- org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference result = new org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference(this);
- int from_bitField0_ = bitField0_;
- int to_bitField0_ = 0;
- if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
- to_bitField0_ |= 0x00000001;
- }
- result.file_ = file_;
- if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
- to_bitField0_ |= 0x00000002;
- }
- result.key_ = key_;
- if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
- to_bitField0_ |= 0x00000004;
- }
- result.value_ = value_;
- result.bitField0_ = to_bitField0_;
- onBuilt();
- return result;
- }
-
- public Builder mergeFrom(com.google.protobuf.Message other) {
- if (other instanceof org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference) {
- return mergeFrom((org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference)other);
- } else {
- super.mergeFrom(other);
- return this;
- }
- }
-
- public Builder mergeFrom(org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference other) {
- if (other == org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.getDefaultInstance()) return this;
- if (other.hasFile()) {
- bitField0_ |= 0x00000001;
- file_ = other.file_;
- onChanged();
- }
- if (other.hasKey()) {
- bitField0_ |= 0x00000002;
- key_ = other.key_;
- onChanged();
- }
- if (other.hasValue()) {
- bitField0_ |= 0x00000004;
- value_ = other.value_;
- onChanged();
- }
- this.mergeUnknownFields(other.getUnknownFields());
- return this;
- }
-
- public final boolean isInitialized() {
- return true;
- }
-
- public Builder mergeFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parsedMessage = null;
- try {
- parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- parsedMessage = (org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference) e.getUnfinishedMessage();
- throw e;
- } finally {
- if (parsedMessage != null) {
- mergeFrom(parsedMessage);
- }
- }
- return this;
- }
- private int bitField0_;
-
- // optional string file = 1;
- private java.lang.Object file_ = "";
- /**
- * optional string file = 1;
- */
- public boolean hasFile() {
- return ((bitField0_ & 0x00000001) == 0x00000001);
- }
- /**
- * optional string file = 1;
- */
- public java.lang.String getFile() {
- java.lang.Object ref = file_;
- if (!(ref instanceof java.lang.String)) {
- java.lang.String s = ((com.google.protobuf.ByteString) ref)
- .toStringUtf8();
- file_ = s;
- return s;
- } else {
- return (java.lang.String) ref;
- }
- }
- /**
- * optional string file = 1;
- */
- public com.google.protobuf.ByteString
- getFileBytes() {
- java.lang.Object ref = file_;
- if (ref instanceof String) {
- com.google.protobuf.ByteString b =
- com.google.protobuf.ByteString.copyFromUtf8(
- (java.lang.String) ref);
- file_ = b;
- return b;
- } else {
- return (com.google.protobuf.ByteString) ref;
- }
- }
- /**
- * optional string file = 1;
- */
- public Builder setFile(
- java.lang.String value) {
- if (value == null) {
- throw new NullPointerException();
- }
- bitField0_ |= 0x00000001;
- file_ = value;
- onChanged();
- return this;
- }
- /**
- * optional string file = 1;
- */
- public Builder clearFile() {
- bitField0_ = (bitField0_ & ~0x00000001);
- file_ = getDefaultInstance().getFile();
- onChanged();
- return this;
- }
- /**
- * optional string file = 1;
- */
- public Builder setFileBytes(
- com.google.protobuf.ByteString value) {
- if (value == null) {
- throw new NullPointerException();
- }
- bitField0_ |= 0x00000001;
- file_ = value;
- onChanged();
- return this;
- }
-
- // optional string key = 2;
- private java.lang.Object key_ = "";
- /**
- * optional string key = 2;
- */
- public boolean hasKey() {
- return ((bitField0_ & 0x00000002) == 0x00000002);
- }
- /**
- * optional string key = 2;
- */
- public java.lang.String getKey() {
- java.lang.Object ref = key_;
- if (!(ref instanceof java.lang.String)) {
- java.lang.String s = ((com.google.protobuf.ByteString) ref)
- .toStringUtf8();
- key_ = s;
- return s;
- } else {
- return (java.lang.String) ref;
- }
- }
- /**
- * optional string key = 2;
- */
- public com.google.protobuf.ByteString
- getKeyBytes() {
- java.lang.Object ref = key_;
- if (ref instanceof String) {
- com.google.protobuf.ByteString b =
- com.google.protobuf.ByteString.copyFromUtf8(
- (java.lang.String) ref);
- key_ = b;
- return b;
- } else {
- return (com.google.protobuf.ByteString) ref;
- }
- }
- /**
- * optional string key = 2;
- */
- public Builder setKey(
- java.lang.String value) {
- if (value == null) {
- throw new NullPointerException();
- }
- bitField0_ |= 0x00000002;
- key_ = value;
- onChanged();
- return this;
- }
- /**
- * optional string key = 2;
- */
- public Builder clearKey() {
- bitField0_ = (bitField0_ & ~0x00000002);
- key_ = getDefaultInstance().getKey();
- onChanged();
- return this;
- }
- /**
- * optional string key = 2;
- */
- public Builder setKeyBytes(
- com.google.protobuf.ByteString value) {
- if (value == null) {
- throw new NullPointerException();
- }
- bitField0_ |= 0x00000002;
- key_ = value;
- onChanged();
- return this;
- }
-
- // optional string value = 3;
- private java.lang.Object value_ = "";
- /**
- * optional string value = 3;
- */
- public boolean hasValue() {
- return ((bitField0_ & 0x00000004) == 0x00000004);
- }
- /**
- * optional string value = 3;
- */
- public java.lang.String getValue() {
- java.lang.Object ref = value_;
- if (!(ref instanceof java.lang.String)) {
- java.lang.String s = ((com.google.protobuf.ByteString) ref)
- .toStringUtf8();
- value_ = s;
- return s;
- } else {
- return (java.lang.String) ref;
- }
- }
- /**
- * optional string value = 3;
- */
- public com.google.protobuf.ByteString
- getValueBytes() {
- java.lang.Object ref = value_;
- if (ref instanceof String) {
- com.google.protobuf.ByteString b =
- com.google.protobuf.ByteString.copyFromUtf8(
- (java.lang.String) ref);
- value_ = b;
- return b;
- } else {
- return (com.google.protobuf.ByteString) ref;
- }
- }
- /**
- * optional string value = 3;
- */
- public Builder setValue(
- java.lang.String value) {
- if (value == null) {
- throw new NullPointerException();
- }
- bitField0_ |= 0x00000004;
- value_ = value;
- onChanged();
- return this;
- }
- /**
- * optional string value = 3;
- */
- public Builder clearValue() {
- bitField0_ = (bitField0_ & ~0x00000004);
- value_ = getDefaultInstance().getValue();
- onChanged();
- return this;
- }
- /**
- * optional string value = 3;
- */
- public Builder setValueBytes(
- com.google.protobuf.ByteString value) {
- if (value == null) {
- throw new NullPointerException();
- }
- bitField0_ |= 0x00000004;
- value_ = value;
- onChanged();
- return this;
- }
-
- // @@protoc_insertion_point(builder_scope:signal.SharedPreference)
- }
-
- static {
- defaultInstance = new SharedPreference(true);
- defaultInstance.initFields();
- }
-
- // @@protoc_insertion_point(class_scope:signal.SharedPreference)
- }
-
- public interface AttachmentOrBuilder
- extends com.google.protobuf.MessageOrBuilder {
-
- // optional uint64 rowId = 1;
- /**
- * optional uint64 rowId = 1;
- */
- boolean hasRowId();
- /**
- * optional uint64 rowId = 1;
- */
- long getRowId();
-
- // optional uint64 attachmentId = 2;
- /**
- * optional uint64 attachmentId = 2;
- */
- boolean hasAttachmentId();
- /**
- * optional uint64 attachmentId = 2;
- */
- long getAttachmentId();
-
- // optional uint32 length = 3;
- /**
- * optional uint32 length = 3;
- */
- boolean hasLength();
- /**
- * optional uint32 length = 3;
- */
- int getLength();
- }
- /**
- * Protobuf type {@code signal.Attachment}
- */
- public static final class Attachment extends
- com.google.protobuf.GeneratedMessage
- implements AttachmentOrBuilder {
- // Use Attachment.newBuilder() to construct.
- private Attachment(com.google.protobuf.GeneratedMessage.Builder> builder) {
- super(builder);
- this.unknownFields = builder.getUnknownFields();
- }
- private Attachment(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); }
-
- private static final Attachment defaultInstance;
- public static Attachment getDefaultInstance() {
- return defaultInstance;
- }
-
- public Attachment getDefaultInstanceForType() {
- return defaultInstance;
- }
-
- private final com.google.protobuf.UnknownFieldSet unknownFields;
- @java.lang.Override
- public final com.google.protobuf.UnknownFieldSet
- getUnknownFields() {
- return this.unknownFields;
- }
- private Attachment(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- initFields();
- int mutable_bitField0_ = 0;
- com.google.protobuf.UnknownFieldSet.Builder unknownFields =
- com.google.protobuf.UnknownFieldSet.newBuilder();
- try {
- boolean done = false;
- while (!done) {
- int tag = input.readTag();
- switch (tag) {
- case 0:
- done = true;
- break;
- default: {
- if (!parseUnknownField(input, unknownFields,
- extensionRegistry, tag)) {
- done = true;
- }
- break;
- }
- case 8: {
- bitField0_ |= 0x00000001;
- rowId_ = input.readUInt64();
- break;
- }
- case 16: {
- bitField0_ |= 0x00000002;
- attachmentId_ = input.readUInt64();
- break;
- }
- case 24: {
- bitField0_ |= 0x00000004;
- length_ = input.readUInt32();
- break;
- }
- }
- }
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- throw e.setUnfinishedMessage(this);
- } catch (java.io.IOException e) {
- throw new com.google.protobuf.InvalidProtocolBufferException(
- e.getMessage()).setUnfinishedMessage(this);
- } finally {
- this.unknownFields = unknownFields.build();
- makeExtensionsImmutable();
- }
- }
- public static final com.google.protobuf.Descriptors.Descriptor
- getDescriptor() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Attachment_descriptor;
- }
-
- protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internalGetFieldAccessorTable() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Attachment_fieldAccessorTable
- .ensureFieldAccessorsInitialized(
- org.thoughtcrime.securesms.backup.BackupProtos.Attachment.class, org.thoughtcrime.securesms.backup.BackupProtos.Attachment.Builder.class);
- }
-
- public static com.google.protobuf.Parser PARSER =
- new com.google.protobuf.AbstractParser() {
- public Attachment parsePartialFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return new Attachment(input, extensionRegistry);
- }
- };
-
- @java.lang.Override
- public com.google.protobuf.Parser getParserForType() {
- return PARSER;
- }
-
- private int bitField0_;
- // optional uint64 rowId = 1;
- public static final int ROWID_FIELD_NUMBER = 1;
- private long rowId_;
- /**
- * optional uint64 rowId = 1;
- */
- public boolean hasRowId() {
- return ((bitField0_ & 0x00000001) == 0x00000001);
- }
- /**
- * optional uint64 rowId = 1;
- */
- public long getRowId() {
- return rowId_;
- }
-
- // optional uint64 attachmentId = 2;
- public static final int ATTACHMENTID_FIELD_NUMBER = 2;
- private long attachmentId_;
- /**
- * optional uint64 attachmentId = 2;
- */
- public boolean hasAttachmentId() {
- return ((bitField0_ & 0x00000002) == 0x00000002);
- }
- /**
- * optional uint64 attachmentId = 2;
- */
- public long getAttachmentId() {
- return attachmentId_;
- }
-
- // optional uint32 length = 3;
- public static final int LENGTH_FIELD_NUMBER = 3;
- private int length_;
- /**
- * optional uint32 length = 3;
- */
- public boolean hasLength() {
- return ((bitField0_ & 0x00000004) == 0x00000004);
- }
- /**
- * optional uint32 length = 3;
- */
- public int getLength() {
- return length_;
- }
-
- private void initFields() {
- rowId_ = 0L;
- attachmentId_ = 0L;
- length_ = 0;
- }
- private byte memoizedIsInitialized = -1;
- public final boolean isInitialized() {
- byte isInitialized = memoizedIsInitialized;
- if (isInitialized != -1) return isInitialized == 1;
-
- memoizedIsInitialized = 1;
- return true;
- }
-
- public void writeTo(com.google.protobuf.CodedOutputStream output)
- throws java.io.IOException {
- getSerializedSize();
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- output.writeUInt64(1, rowId_);
- }
- if (((bitField0_ & 0x00000002) == 0x00000002)) {
- output.writeUInt64(2, attachmentId_);
- }
- if (((bitField0_ & 0x00000004) == 0x00000004)) {
- output.writeUInt32(3, length_);
- }
- getUnknownFields().writeTo(output);
- }
-
- private int memoizedSerializedSize = -1;
- public int getSerializedSize() {
- int size = memoizedSerializedSize;
- if (size != -1) return size;
-
- size = 0;
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- size += com.google.protobuf.CodedOutputStream
- .computeUInt64Size(1, rowId_);
- }
- if (((bitField0_ & 0x00000002) == 0x00000002)) {
- size += com.google.protobuf.CodedOutputStream
- .computeUInt64Size(2, attachmentId_);
- }
- if (((bitField0_ & 0x00000004) == 0x00000004)) {
- size += com.google.protobuf.CodedOutputStream
- .computeUInt32Size(3, length_);
- }
- size += getUnknownFields().getSerializedSize();
- memoizedSerializedSize = size;
- return size;
- }
-
- private static final long serialVersionUID = 0L;
- @java.lang.Override
- protected java.lang.Object writeReplace()
- throws java.io.ObjectStreamException {
- return super.writeReplace();
- }
-
- public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment parseFrom(
- com.google.protobuf.ByteString data)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment parseFrom(
- com.google.protobuf.ByteString data,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment parseFrom(byte[] data)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment parseFrom(
- byte[] data,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment parseFrom(java.io.InputStream input)
- throws java.io.IOException {
- return PARSER.parseFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment parseFrom(
- java.io.InputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseFrom(input, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment parseDelimitedFrom(java.io.InputStream input)
- throws java.io.IOException {
- return PARSER.parseDelimitedFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment parseDelimitedFrom(
- java.io.InputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseDelimitedFrom(input, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment parseFrom(
- com.google.protobuf.CodedInputStream input)
- throws java.io.IOException {
- return PARSER.parseFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment parseFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseFrom(input, extensionRegistry);
- }
-
- public static Builder newBuilder() { return Builder.create(); }
- public Builder newBuilderForType() { return newBuilder(); }
- public static Builder newBuilder(org.thoughtcrime.securesms.backup.BackupProtos.Attachment prototype) {
- return newBuilder().mergeFrom(prototype);
- }
- public Builder toBuilder() { return newBuilder(this); }
-
- @java.lang.Override
- protected Builder newBuilderForType(
- com.google.protobuf.GeneratedMessage.BuilderParent parent) {
- Builder builder = new Builder(parent);
- return builder;
- }
- /**
- * Protobuf type {@code signal.Attachment}
- */
- public static final class Builder extends
- com.google.protobuf.GeneratedMessage.Builder
- implements org.thoughtcrime.securesms.backup.BackupProtos.AttachmentOrBuilder {
- public static final com.google.protobuf.Descriptors.Descriptor
- getDescriptor() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Attachment_descriptor;
- }
-
- protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internalGetFieldAccessorTable() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Attachment_fieldAccessorTable
- .ensureFieldAccessorsInitialized(
- org.thoughtcrime.securesms.backup.BackupProtos.Attachment.class, org.thoughtcrime.securesms.backup.BackupProtos.Attachment.Builder.class);
- }
-
- // Construct using org.thoughtcrime.securesms.backup.BackupProtos.Attachment.newBuilder()
- private Builder() {
- maybeForceBuilderInitialization();
- }
-
- private Builder(
- com.google.protobuf.GeneratedMessage.BuilderParent parent) {
- super(parent);
- maybeForceBuilderInitialization();
- }
- private void maybeForceBuilderInitialization() {
- if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
- }
- }
- private static Builder create() {
- return new Builder();
- }
-
- public Builder clear() {
- super.clear();
- rowId_ = 0L;
- bitField0_ = (bitField0_ & ~0x00000001);
- attachmentId_ = 0L;
- bitField0_ = (bitField0_ & ~0x00000002);
- length_ = 0;
- bitField0_ = (bitField0_ & ~0x00000004);
- return this;
- }
-
- public Builder clone() {
- return create().mergeFrom(buildPartial());
- }
-
- public com.google.protobuf.Descriptors.Descriptor
- getDescriptorForType() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Attachment_descriptor;
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.Attachment getDefaultInstanceForType() {
- return org.thoughtcrime.securesms.backup.BackupProtos.Attachment.getDefaultInstance();
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.Attachment build() {
- org.thoughtcrime.securesms.backup.BackupProtos.Attachment result = buildPartial();
- if (!result.isInitialized()) {
- throw newUninitializedMessageException(result);
- }
- return result;
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.Attachment buildPartial() {
- org.thoughtcrime.securesms.backup.BackupProtos.Attachment result = new org.thoughtcrime.securesms.backup.BackupProtos.Attachment(this);
- int from_bitField0_ = bitField0_;
- int to_bitField0_ = 0;
- if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
- to_bitField0_ |= 0x00000001;
- }
- result.rowId_ = rowId_;
- if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
- to_bitField0_ |= 0x00000002;
- }
- result.attachmentId_ = attachmentId_;
- if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
- to_bitField0_ |= 0x00000004;
- }
- result.length_ = length_;
- result.bitField0_ = to_bitField0_;
- onBuilt();
- return result;
- }
-
- public Builder mergeFrom(com.google.protobuf.Message other) {
- if (other instanceof org.thoughtcrime.securesms.backup.BackupProtos.Attachment) {
- return mergeFrom((org.thoughtcrime.securesms.backup.BackupProtos.Attachment)other);
- } else {
- super.mergeFrom(other);
- return this;
- }
- }
-
- public Builder mergeFrom(org.thoughtcrime.securesms.backup.BackupProtos.Attachment other) {
- if (other == org.thoughtcrime.securesms.backup.BackupProtos.Attachment.getDefaultInstance()) return this;
- if (other.hasRowId()) {
- setRowId(other.getRowId());
- }
- if (other.hasAttachmentId()) {
- setAttachmentId(other.getAttachmentId());
- }
- if (other.hasLength()) {
- setLength(other.getLength());
- }
- this.mergeUnknownFields(other.getUnknownFields());
- return this;
- }
-
- public final boolean isInitialized() {
- return true;
- }
-
- public Builder mergeFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- org.thoughtcrime.securesms.backup.BackupProtos.Attachment parsedMessage = null;
- try {
- parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- parsedMessage = (org.thoughtcrime.securesms.backup.BackupProtos.Attachment) e.getUnfinishedMessage();
- throw e;
- } finally {
- if (parsedMessage != null) {
- mergeFrom(parsedMessage);
- }
- }
- return this;
- }
- private int bitField0_;
-
- // optional uint64 rowId = 1;
- private long rowId_ ;
- /**
- * optional uint64 rowId = 1;
- */
- public boolean hasRowId() {
- return ((bitField0_ & 0x00000001) == 0x00000001);
- }
- /**
- * optional uint64 rowId = 1;
- */
- public long getRowId() {
- return rowId_;
- }
- /**
- * optional uint64 rowId = 1;
- */
- public Builder setRowId(long value) {
- bitField0_ |= 0x00000001;
- rowId_ = value;
- onChanged();
- return this;
- }
- /**
- * optional uint64 rowId = 1;
- */
- public Builder clearRowId() {
- bitField0_ = (bitField0_ & ~0x00000001);
- rowId_ = 0L;
- onChanged();
- return this;
- }
-
- // optional uint64 attachmentId = 2;
- private long attachmentId_ ;
- /**
- * optional uint64 attachmentId = 2;
- */
- public boolean hasAttachmentId() {
- return ((bitField0_ & 0x00000002) == 0x00000002);
- }
- /**
- * optional uint64 attachmentId = 2;
- */
- public long getAttachmentId() {
- return attachmentId_;
- }
- /**
- * optional uint64 attachmentId = 2;
- */
- public Builder setAttachmentId(long value) {
- bitField0_ |= 0x00000002;
- attachmentId_ = value;
- onChanged();
- return this;
- }
- /**
- * optional uint64 attachmentId = 2;
- */
- public Builder clearAttachmentId() {
- bitField0_ = (bitField0_ & ~0x00000002);
- attachmentId_ = 0L;
- onChanged();
- return this;
- }
-
- // optional uint32 length = 3;
- private int length_ ;
- /**
- * optional uint32 length = 3;
- */
- public boolean hasLength() {
- return ((bitField0_ & 0x00000004) == 0x00000004);
- }
- /**
- * optional uint32 length = 3;
- */
- public int getLength() {
- return length_;
- }
- /**
- * optional uint32 length = 3;
- */
- public Builder setLength(int value) {
- bitField0_ |= 0x00000004;
- length_ = value;
- onChanged();
- return this;
- }
- /**
- * optional uint32 length = 3;
- */
- public Builder clearLength() {
- bitField0_ = (bitField0_ & ~0x00000004);
- length_ = 0;
- onChanged();
- return this;
- }
-
- // @@protoc_insertion_point(builder_scope:signal.Attachment)
- }
-
- static {
- defaultInstance = new Attachment(true);
- defaultInstance.initFields();
- }
-
- // @@protoc_insertion_point(class_scope:signal.Attachment)
- }
-
- public interface StickerOrBuilder
- extends com.google.protobuf.MessageOrBuilder {
-
- // optional uint64 rowId = 1;
- /**
- * optional uint64 rowId = 1;
- */
- boolean hasRowId();
- /**
- * optional uint64 rowId = 1;
- */
- long getRowId();
-
- // optional uint32 length = 2;
- /**
- * optional uint32 length = 2;
- */
- boolean hasLength();
- /**
- * optional uint32 length = 2;
- */
- int getLength();
- }
- /**
- * Protobuf type {@code signal.Sticker}
- */
- public static final class Sticker extends
- com.google.protobuf.GeneratedMessage
- implements StickerOrBuilder {
- // Use Sticker.newBuilder() to construct.
- private Sticker(com.google.protobuf.GeneratedMessage.Builder> builder) {
- super(builder);
- this.unknownFields = builder.getUnknownFields();
- }
- private Sticker(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); }
-
- private static final Sticker defaultInstance;
- public static Sticker getDefaultInstance() {
- return defaultInstance;
- }
-
- public Sticker getDefaultInstanceForType() {
- return defaultInstance;
- }
-
- private final com.google.protobuf.UnknownFieldSet unknownFields;
- @java.lang.Override
- public final com.google.protobuf.UnknownFieldSet
- getUnknownFields() {
- return this.unknownFields;
- }
- private Sticker(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- initFields();
- int mutable_bitField0_ = 0;
- com.google.protobuf.UnknownFieldSet.Builder unknownFields =
- com.google.protobuf.UnknownFieldSet.newBuilder();
- try {
- boolean done = false;
- while (!done) {
- int tag = input.readTag();
- switch (tag) {
- case 0:
- done = true;
- break;
- default: {
- if (!parseUnknownField(input, unknownFields,
- extensionRegistry, tag)) {
- done = true;
- }
- break;
- }
- case 8: {
- bitField0_ |= 0x00000001;
- rowId_ = input.readUInt64();
- break;
- }
- case 16: {
- bitField0_ |= 0x00000002;
- length_ = input.readUInt32();
- break;
- }
- }
- }
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- throw e.setUnfinishedMessage(this);
- } catch (java.io.IOException e) {
- throw new com.google.protobuf.InvalidProtocolBufferException(
- e.getMessage()).setUnfinishedMessage(this);
- } finally {
- this.unknownFields = unknownFields.build();
- makeExtensionsImmutable();
- }
- }
- public static final com.google.protobuf.Descriptors.Descriptor
- getDescriptor() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Sticker_descriptor;
- }
-
- protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internalGetFieldAccessorTable() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Sticker_fieldAccessorTable
- .ensureFieldAccessorsInitialized(
- org.thoughtcrime.securesms.backup.BackupProtos.Sticker.class, org.thoughtcrime.securesms.backup.BackupProtos.Sticker.Builder.class);
- }
-
- public static com.google.protobuf.Parser PARSER =
- new com.google.protobuf.AbstractParser() {
- public Sticker parsePartialFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return new Sticker(input, extensionRegistry);
- }
- };
-
- @java.lang.Override
- public com.google.protobuf.Parser getParserForType() {
- return PARSER;
- }
-
- private int bitField0_;
- // optional uint64 rowId = 1;
- public static final int ROWID_FIELD_NUMBER = 1;
- private long rowId_;
- /**
- * optional uint64 rowId = 1;
- */
- public boolean hasRowId() {
- return ((bitField0_ & 0x00000001) == 0x00000001);
- }
- /**
- * optional uint64 rowId = 1;
- */
- public long getRowId() {
- return rowId_;
- }
-
- // optional uint32 length = 2;
- public static final int LENGTH_FIELD_NUMBER = 2;
- private int length_;
- /**
- * optional uint32 length = 2;
- */
- public boolean hasLength() {
- return ((bitField0_ & 0x00000002) == 0x00000002);
- }
- /**
- * optional uint32 length = 2;
- */
- public int getLength() {
- return length_;
- }
-
- private void initFields() {
- rowId_ = 0L;
- length_ = 0;
- }
- private byte memoizedIsInitialized = -1;
- public final boolean isInitialized() {
- byte isInitialized = memoizedIsInitialized;
- if (isInitialized != -1) return isInitialized == 1;
-
- memoizedIsInitialized = 1;
- return true;
- }
-
- public void writeTo(com.google.protobuf.CodedOutputStream output)
- throws java.io.IOException {
- getSerializedSize();
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- output.writeUInt64(1, rowId_);
- }
- if (((bitField0_ & 0x00000002) == 0x00000002)) {
- output.writeUInt32(2, length_);
- }
- getUnknownFields().writeTo(output);
- }
-
- private int memoizedSerializedSize = -1;
- public int getSerializedSize() {
- int size = memoizedSerializedSize;
- if (size != -1) return size;
-
- size = 0;
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- size += com.google.protobuf.CodedOutputStream
- .computeUInt64Size(1, rowId_);
- }
- if (((bitField0_ & 0x00000002) == 0x00000002)) {
- size += com.google.protobuf.CodedOutputStream
- .computeUInt32Size(2, length_);
- }
- size += getUnknownFields().getSerializedSize();
- memoizedSerializedSize = size;
- return size;
- }
-
- private static final long serialVersionUID = 0L;
- @java.lang.Override
- protected java.lang.Object writeReplace()
- throws java.io.ObjectStreamException {
- return super.writeReplace();
- }
-
- public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker parseFrom(
- com.google.protobuf.ByteString data)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker parseFrom(
- com.google.protobuf.ByteString data,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker parseFrom(byte[] data)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker parseFrom(
- byte[] data,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker parseFrom(java.io.InputStream input)
- throws java.io.IOException {
- return PARSER.parseFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker parseFrom(
- java.io.InputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseFrom(input, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker parseDelimitedFrom(java.io.InputStream input)
- throws java.io.IOException {
- return PARSER.parseDelimitedFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker parseDelimitedFrom(
- java.io.InputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseDelimitedFrom(input, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker parseFrom(
- com.google.protobuf.CodedInputStream input)
- throws java.io.IOException {
- return PARSER.parseFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker parseFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseFrom(input, extensionRegistry);
- }
-
- public static Builder newBuilder() { return Builder.create(); }
- public Builder newBuilderForType() { return newBuilder(); }
- public static Builder newBuilder(org.thoughtcrime.securesms.backup.BackupProtos.Sticker prototype) {
- return newBuilder().mergeFrom(prototype);
- }
- public Builder toBuilder() { return newBuilder(this); }
-
- @java.lang.Override
- protected Builder newBuilderForType(
- com.google.protobuf.GeneratedMessage.BuilderParent parent) {
- Builder builder = new Builder(parent);
- return builder;
- }
- /**
- * Protobuf type {@code signal.Sticker}
- */
- public static final class Builder extends
- com.google.protobuf.GeneratedMessage.Builder
- implements org.thoughtcrime.securesms.backup.BackupProtos.StickerOrBuilder {
- public static final com.google.protobuf.Descriptors.Descriptor
- getDescriptor() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Sticker_descriptor;
- }
-
- protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internalGetFieldAccessorTable() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Sticker_fieldAccessorTable
- .ensureFieldAccessorsInitialized(
- org.thoughtcrime.securesms.backup.BackupProtos.Sticker.class, org.thoughtcrime.securesms.backup.BackupProtos.Sticker.Builder.class);
- }
-
- // Construct using org.thoughtcrime.securesms.backup.BackupProtos.Sticker.newBuilder()
- private Builder() {
- maybeForceBuilderInitialization();
- }
-
- private Builder(
- com.google.protobuf.GeneratedMessage.BuilderParent parent) {
- super(parent);
- maybeForceBuilderInitialization();
- }
- private void maybeForceBuilderInitialization() {
- if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
- }
- }
- private static Builder create() {
- return new Builder();
- }
-
- public Builder clear() {
- super.clear();
- rowId_ = 0L;
- bitField0_ = (bitField0_ & ~0x00000001);
- length_ = 0;
- bitField0_ = (bitField0_ & ~0x00000002);
- return this;
- }
-
- public Builder clone() {
- return create().mergeFrom(buildPartial());
- }
-
- public com.google.protobuf.Descriptors.Descriptor
- getDescriptorForType() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Sticker_descriptor;
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.Sticker getDefaultInstanceForType() {
- return org.thoughtcrime.securesms.backup.BackupProtos.Sticker.getDefaultInstance();
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.Sticker build() {
- org.thoughtcrime.securesms.backup.BackupProtos.Sticker result = buildPartial();
- if (!result.isInitialized()) {
- throw newUninitializedMessageException(result);
- }
- return result;
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.Sticker buildPartial() {
- org.thoughtcrime.securesms.backup.BackupProtos.Sticker result = new org.thoughtcrime.securesms.backup.BackupProtos.Sticker(this);
- int from_bitField0_ = bitField0_;
- int to_bitField0_ = 0;
- if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
- to_bitField0_ |= 0x00000001;
- }
- result.rowId_ = rowId_;
- if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
- to_bitField0_ |= 0x00000002;
- }
- result.length_ = length_;
- result.bitField0_ = to_bitField0_;
- onBuilt();
- return result;
- }
-
- public Builder mergeFrom(com.google.protobuf.Message other) {
- if (other instanceof org.thoughtcrime.securesms.backup.BackupProtos.Sticker) {
- return mergeFrom((org.thoughtcrime.securesms.backup.BackupProtos.Sticker)other);
- } else {
- super.mergeFrom(other);
- return this;
- }
- }
-
- public Builder mergeFrom(org.thoughtcrime.securesms.backup.BackupProtos.Sticker other) {
- if (other == org.thoughtcrime.securesms.backup.BackupProtos.Sticker.getDefaultInstance()) return this;
- if (other.hasRowId()) {
- setRowId(other.getRowId());
- }
- if (other.hasLength()) {
- setLength(other.getLength());
- }
- this.mergeUnknownFields(other.getUnknownFields());
- return this;
- }
-
- public final boolean isInitialized() {
- return true;
- }
-
- public Builder mergeFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- org.thoughtcrime.securesms.backup.BackupProtos.Sticker parsedMessage = null;
- try {
- parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- parsedMessage = (org.thoughtcrime.securesms.backup.BackupProtos.Sticker) e.getUnfinishedMessage();
- throw e;
- } finally {
- if (parsedMessage != null) {
- mergeFrom(parsedMessage);
- }
- }
- return this;
- }
- private int bitField0_;
-
- // optional uint64 rowId = 1;
- private long rowId_ ;
- /**
- * optional uint64 rowId = 1;
- */
- public boolean hasRowId() {
- return ((bitField0_ & 0x00000001) == 0x00000001);
- }
- /**
- * optional uint64 rowId = 1;
- */
- public long getRowId() {
- return rowId_;
- }
- /**
- * optional uint64 rowId = 1;
- */
- public Builder setRowId(long value) {
- bitField0_ |= 0x00000001;
- rowId_ = value;
- onChanged();
- return this;
- }
- /**
- * optional uint64 rowId = 1;
- */
- public Builder clearRowId() {
- bitField0_ = (bitField0_ & ~0x00000001);
- rowId_ = 0L;
- onChanged();
- return this;
- }
-
- // optional uint32 length = 2;
- private int length_ ;
- /**
- * optional uint32 length = 2;
- */
- public boolean hasLength() {
- return ((bitField0_ & 0x00000002) == 0x00000002);
- }
- /**
- * optional uint32 length = 2;
- */
- public int getLength() {
- return length_;
- }
- /**
- * optional uint32 length = 2;
- */
- public Builder setLength(int value) {
- bitField0_ |= 0x00000002;
- length_ = value;
- onChanged();
- return this;
- }
- /**
- * optional uint32 length = 2;
- */
- public Builder clearLength() {
- bitField0_ = (bitField0_ & ~0x00000002);
- length_ = 0;
- onChanged();
- return this;
- }
-
- // @@protoc_insertion_point(builder_scope:signal.Sticker)
- }
-
- static {
- defaultInstance = new Sticker(true);
- defaultInstance.initFields();
- }
-
- // @@protoc_insertion_point(class_scope:signal.Sticker)
- }
-
- public interface AvatarOrBuilder
- extends com.google.protobuf.MessageOrBuilder {
-
- // optional string name = 1;
- /**
- * optional string name = 1;
- */
- boolean hasName();
- /**
- * optional string name = 1;
- */
- java.lang.String getName();
- /**
- * optional string name = 1;
- */
- com.google.protobuf.ByteString
- getNameBytes();
-
- // optional uint32 length = 2;
- /**
- * optional uint32 length = 2;
- */
- boolean hasLength();
- /**
- * optional uint32 length = 2;
- */
- int getLength();
- }
- /**
- * Protobuf type {@code signal.Avatar}
- */
- public static final class Avatar extends
- com.google.protobuf.GeneratedMessage
- implements AvatarOrBuilder {
- // Use Avatar.newBuilder() to construct.
- private Avatar(com.google.protobuf.GeneratedMessage.Builder> builder) {
- super(builder);
- this.unknownFields = builder.getUnknownFields();
- }
- private Avatar(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); }
-
- private static final Avatar defaultInstance;
- public static Avatar getDefaultInstance() {
- return defaultInstance;
- }
-
- public Avatar getDefaultInstanceForType() {
- return defaultInstance;
- }
-
- private final com.google.protobuf.UnknownFieldSet unknownFields;
- @java.lang.Override
- public final com.google.protobuf.UnknownFieldSet
- getUnknownFields() {
- return this.unknownFields;
- }
- private Avatar(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- initFields();
- int mutable_bitField0_ = 0;
- com.google.protobuf.UnknownFieldSet.Builder unknownFields =
- com.google.protobuf.UnknownFieldSet.newBuilder();
- try {
- boolean done = false;
- while (!done) {
- int tag = input.readTag();
- switch (tag) {
- case 0:
- done = true;
- break;
- default: {
- if (!parseUnknownField(input, unknownFields,
- extensionRegistry, tag)) {
- done = true;
- }
- break;
- }
- case 10: {
- bitField0_ |= 0x00000001;
- name_ = input.readBytes();
- break;
- }
- case 16: {
- bitField0_ |= 0x00000002;
- length_ = input.readUInt32();
- break;
- }
- }
- }
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- throw e.setUnfinishedMessage(this);
- } catch (java.io.IOException e) {
- throw new com.google.protobuf.InvalidProtocolBufferException(
- e.getMessage()).setUnfinishedMessage(this);
- } finally {
- this.unknownFields = unknownFields.build();
- makeExtensionsImmutable();
- }
- }
- public static final com.google.protobuf.Descriptors.Descriptor
- getDescriptor() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Avatar_descriptor;
- }
-
- protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internalGetFieldAccessorTable() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Avatar_fieldAccessorTable
- .ensureFieldAccessorsInitialized(
- org.thoughtcrime.securesms.backup.BackupProtos.Avatar.class, org.thoughtcrime.securesms.backup.BackupProtos.Avatar.Builder.class);
- }
-
- public static com.google.protobuf.Parser PARSER =
- new com.google.protobuf.AbstractParser() {
- public Avatar parsePartialFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return new Avatar(input, extensionRegistry);
- }
- };
-
- @java.lang.Override
- public com.google.protobuf.Parser getParserForType() {
- return PARSER;
- }
-
- private int bitField0_;
- // optional string name = 1;
- public static final int NAME_FIELD_NUMBER = 1;
- private java.lang.Object name_;
- /**
- * optional string name = 1;
- */
- public boolean hasName() {
- return ((bitField0_ & 0x00000001) == 0x00000001);
- }
- /**
- * optional string name = 1;
- */
- public java.lang.String getName() {
- java.lang.Object ref = name_;
- if (ref instanceof java.lang.String) {
- return (java.lang.String) ref;
- } else {
- com.google.protobuf.ByteString bs =
- (com.google.protobuf.ByteString) ref;
- java.lang.String s = bs.toStringUtf8();
- if (bs.isValidUtf8()) {
- name_ = s;
- }
- return s;
- }
- }
- /**
- * optional string name = 1;
- */
- public com.google.protobuf.ByteString
- getNameBytes() {
- java.lang.Object ref = name_;
- if (ref instanceof java.lang.String) {
- com.google.protobuf.ByteString b =
- com.google.protobuf.ByteString.copyFromUtf8(
- (java.lang.String) ref);
- name_ = b;
- return b;
- } else {
- return (com.google.protobuf.ByteString) ref;
- }
- }
-
- // optional uint32 length = 2;
- public static final int LENGTH_FIELD_NUMBER = 2;
- private int length_;
- /**
- * optional uint32 length = 2;
- */
- public boolean hasLength() {
- return ((bitField0_ & 0x00000002) == 0x00000002);
- }
- /**
- * optional uint32 length = 2;
- */
- public int getLength() {
- return length_;
- }
-
- private void initFields() {
- name_ = "";
- length_ = 0;
- }
- private byte memoizedIsInitialized = -1;
- public final boolean isInitialized() {
- byte isInitialized = memoizedIsInitialized;
- if (isInitialized != -1) return isInitialized == 1;
-
- memoizedIsInitialized = 1;
- return true;
- }
-
- public void writeTo(com.google.protobuf.CodedOutputStream output)
- throws java.io.IOException {
- getSerializedSize();
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- output.writeBytes(1, getNameBytes());
- }
- if (((bitField0_ & 0x00000002) == 0x00000002)) {
- output.writeUInt32(2, length_);
- }
- getUnknownFields().writeTo(output);
- }
-
- private int memoizedSerializedSize = -1;
- public int getSerializedSize() {
- int size = memoizedSerializedSize;
- if (size != -1) return size;
-
- size = 0;
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- size += com.google.protobuf.CodedOutputStream
- .computeBytesSize(1, getNameBytes());
- }
- if (((bitField0_ & 0x00000002) == 0x00000002)) {
- size += com.google.protobuf.CodedOutputStream
- .computeUInt32Size(2, length_);
- }
- size += getUnknownFields().getSerializedSize();
- memoizedSerializedSize = size;
- return size;
- }
-
- private static final long serialVersionUID = 0L;
- @java.lang.Override
- protected java.lang.Object writeReplace()
- throws java.io.ObjectStreamException {
- return super.writeReplace();
- }
-
- public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar parseFrom(
- com.google.protobuf.ByteString data)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar parseFrom(
- com.google.protobuf.ByteString data,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar parseFrom(byte[] data)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar parseFrom(
- byte[] data,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar parseFrom(java.io.InputStream input)
- throws java.io.IOException {
- return PARSER.parseFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar parseFrom(
- java.io.InputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseFrom(input, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar parseDelimitedFrom(java.io.InputStream input)
- throws java.io.IOException {
- return PARSER.parseDelimitedFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar parseDelimitedFrom(
- java.io.InputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseDelimitedFrom(input, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar parseFrom(
- com.google.protobuf.CodedInputStream input)
- throws java.io.IOException {
- return PARSER.parseFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar parseFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseFrom(input, extensionRegistry);
- }
-
- public static Builder newBuilder() { return Builder.create(); }
- public Builder newBuilderForType() { return newBuilder(); }
- public static Builder newBuilder(org.thoughtcrime.securesms.backup.BackupProtos.Avatar prototype) {
- return newBuilder().mergeFrom(prototype);
- }
- public Builder toBuilder() { return newBuilder(this); }
-
- @java.lang.Override
- protected Builder newBuilderForType(
- com.google.protobuf.GeneratedMessage.BuilderParent parent) {
- Builder builder = new Builder(parent);
- return builder;
- }
- /**
- * Protobuf type {@code signal.Avatar}
- */
- public static final class Builder extends
- com.google.protobuf.GeneratedMessage.Builder
- implements org.thoughtcrime.securesms.backup.BackupProtos.AvatarOrBuilder {
- public static final com.google.protobuf.Descriptors.Descriptor
- getDescriptor() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Avatar_descriptor;
- }
-
- protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internalGetFieldAccessorTable() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Avatar_fieldAccessorTable
- .ensureFieldAccessorsInitialized(
- org.thoughtcrime.securesms.backup.BackupProtos.Avatar.class, org.thoughtcrime.securesms.backup.BackupProtos.Avatar.Builder.class);
- }
-
- // Construct using org.thoughtcrime.securesms.backup.BackupProtos.Avatar.newBuilder()
- private Builder() {
- maybeForceBuilderInitialization();
- }
-
- private Builder(
- com.google.protobuf.GeneratedMessage.BuilderParent parent) {
- super(parent);
- maybeForceBuilderInitialization();
- }
- private void maybeForceBuilderInitialization() {
- if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
- }
- }
- private static Builder create() {
- return new Builder();
- }
-
- public Builder clear() {
- super.clear();
- name_ = "";
- bitField0_ = (bitField0_ & ~0x00000001);
- length_ = 0;
- bitField0_ = (bitField0_ & ~0x00000002);
- return this;
- }
-
- public Builder clone() {
- return create().mergeFrom(buildPartial());
- }
-
- public com.google.protobuf.Descriptors.Descriptor
- getDescriptorForType() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Avatar_descriptor;
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.Avatar getDefaultInstanceForType() {
- return org.thoughtcrime.securesms.backup.BackupProtos.Avatar.getDefaultInstance();
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.Avatar build() {
- org.thoughtcrime.securesms.backup.BackupProtos.Avatar result = buildPartial();
- if (!result.isInitialized()) {
- throw newUninitializedMessageException(result);
- }
- return result;
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.Avatar buildPartial() {
- org.thoughtcrime.securesms.backup.BackupProtos.Avatar result = new org.thoughtcrime.securesms.backup.BackupProtos.Avatar(this);
- int from_bitField0_ = bitField0_;
- int to_bitField0_ = 0;
- if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
- to_bitField0_ |= 0x00000001;
- }
- result.name_ = name_;
- if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
- to_bitField0_ |= 0x00000002;
- }
- result.length_ = length_;
- result.bitField0_ = to_bitField0_;
- onBuilt();
- return result;
- }
-
- public Builder mergeFrom(com.google.protobuf.Message other) {
- if (other instanceof org.thoughtcrime.securesms.backup.BackupProtos.Avatar) {
- return mergeFrom((org.thoughtcrime.securesms.backup.BackupProtos.Avatar)other);
- } else {
- super.mergeFrom(other);
- return this;
- }
- }
-
- public Builder mergeFrom(org.thoughtcrime.securesms.backup.BackupProtos.Avatar other) {
- if (other == org.thoughtcrime.securesms.backup.BackupProtos.Avatar.getDefaultInstance()) return this;
- if (other.hasName()) {
- bitField0_ |= 0x00000001;
- name_ = other.name_;
- onChanged();
- }
- if (other.hasLength()) {
- setLength(other.getLength());
- }
- this.mergeUnknownFields(other.getUnknownFields());
- return this;
- }
-
- public final boolean isInitialized() {
- return true;
- }
-
- public Builder mergeFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- org.thoughtcrime.securesms.backup.BackupProtos.Avatar parsedMessage = null;
- try {
- parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- parsedMessage = (org.thoughtcrime.securesms.backup.BackupProtos.Avatar) e.getUnfinishedMessage();
- throw e;
- } finally {
- if (parsedMessage != null) {
- mergeFrom(parsedMessage);
- }
- }
- return this;
- }
- private int bitField0_;
-
- // optional string name = 1;
- private java.lang.Object name_ = "";
- /**
- * optional string name = 1;
- */
- public boolean hasName() {
- return ((bitField0_ & 0x00000001) == 0x00000001);
- }
- /**
- * optional string name = 1;
- */
- public java.lang.String getName() {
- java.lang.Object ref = name_;
- if (!(ref instanceof java.lang.String)) {
- java.lang.String s = ((com.google.protobuf.ByteString) ref)
- .toStringUtf8();
- name_ = s;
- return s;
- } else {
- return (java.lang.String) ref;
- }
- }
- /**
- * optional string name = 1;
- */
- public com.google.protobuf.ByteString
- getNameBytes() {
- java.lang.Object ref = name_;
- if (ref instanceof String) {
- com.google.protobuf.ByteString b =
- com.google.protobuf.ByteString.copyFromUtf8(
- (java.lang.String) ref);
- name_ = b;
- return b;
- } else {
- return (com.google.protobuf.ByteString) ref;
- }
- }
- /**
- * optional string name = 1;
- */
- public Builder setName(
- java.lang.String value) {
- if (value == null) {
- throw new NullPointerException();
- }
- bitField0_ |= 0x00000001;
- name_ = value;
- onChanged();
- return this;
- }
- /**
- * optional string name = 1;
- */
- public Builder clearName() {
- bitField0_ = (bitField0_ & ~0x00000001);
- name_ = getDefaultInstance().getName();
- onChanged();
- return this;
- }
- /**
- * optional string name = 1;
- */
- public Builder setNameBytes(
- com.google.protobuf.ByteString value) {
- if (value == null) {
- throw new NullPointerException();
- }
- bitField0_ |= 0x00000001;
- name_ = value;
- onChanged();
- return this;
- }
-
- // optional uint32 length = 2;
- private int length_ ;
- /**
- * optional uint32 length = 2;
- */
- public boolean hasLength() {
- return ((bitField0_ & 0x00000002) == 0x00000002);
- }
- /**
- * optional uint32 length = 2;
- */
- public int getLength() {
- return length_;
- }
- /**
- * optional uint32 length = 2;
- */
- public Builder setLength(int value) {
- bitField0_ |= 0x00000002;
- length_ = value;
- onChanged();
- return this;
- }
- /**
- * optional uint32 length = 2;
- */
- public Builder clearLength() {
- bitField0_ = (bitField0_ & ~0x00000002);
- length_ = 0;
- onChanged();
- return this;
- }
-
- // @@protoc_insertion_point(builder_scope:signal.Avatar)
- }
-
- static {
- defaultInstance = new Avatar(true);
- defaultInstance.initFields();
- }
-
- // @@protoc_insertion_point(class_scope:signal.Avatar)
- }
-
- public interface DatabaseVersionOrBuilder
- extends com.google.protobuf.MessageOrBuilder {
-
- // optional uint32 version = 1;
- /**
- * optional uint32 version = 1;
- */
- boolean hasVersion();
- /**
- * optional uint32 version = 1;
- */
- int getVersion();
- }
- /**
- * Protobuf type {@code signal.DatabaseVersion}
- */
- public static final class DatabaseVersion extends
- com.google.protobuf.GeneratedMessage
- implements DatabaseVersionOrBuilder {
- // Use DatabaseVersion.newBuilder() to construct.
- private DatabaseVersion(com.google.protobuf.GeneratedMessage.Builder> builder) {
- super(builder);
- this.unknownFields = builder.getUnknownFields();
- }
- private DatabaseVersion(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); }
-
- private static final DatabaseVersion defaultInstance;
- public static DatabaseVersion getDefaultInstance() {
- return defaultInstance;
- }
-
- public DatabaseVersion getDefaultInstanceForType() {
- return defaultInstance;
- }
-
- private final com.google.protobuf.UnknownFieldSet unknownFields;
- @java.lang.Override
- public final com.google.protobuf.UnknownFieldSet
- getUnknownFields() {
- return this.unknownFields;
- }
- private DatabaseVersion(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- initFields();
- int mutable_bitField0_ = 0;
- com.google.protobuf.UnknownFieldSet.Builder unknownFields =
- com.google.protobuf.UnknownFieldSet.newBuilder();
- try {
- boolean done = false;
- while (!done) {
- int tag = input.readTag();
- switch (tag) {
- case 0:
- done = true;
- break;
- default: {
- if (!parseUnknownField(input, unknownFields,
- extensionRegistry, tag)) {
- done = true;
- }
- break;
- }
- case 8: {
- bitField0_ |= 0x00000001;
- version_ = input.readUInt32();
- break;
- }
- }
- }
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- throw e.setUnfinishedMessage(this);
- } catch (java.io.IOException e) {
- throw new com.google.protobuf.InvalidProtocolBufferException(
- e.getMessage()).setUnfinishedMessage(this);
- } finally {
- this.unknownFields = unknownFields.build();
- makeExtensionsImmutable();
- }
- }
- public static final com.google.protobuf.Descriptors.Descriptor
- getDescriptor() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_DatabaseVersion_descriptor;
- }
-
- protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internalGetFieldAccessorTable() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_DatabaseVersion_fieldAccessorTable
- .ensureFieldAccessorsInitialized(
- org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.class, org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.Builder.class);
- }
-
- public static com.google.protobuf.Parser PARSER =
- new com.google.protobuf.AbstractParser() {
- public DatabaseVersion parsePartialFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return new DatabaseVersion(input, extensionRegistry);
- }
- };
-
- @java.lang.Override
- public com.google.protobuf.Parser getParserForType() {
- return PARSER;
- }
-
- private int bitField0_;
- // optional uint32 version = 1;
- public static final int VERSION_FIELD_NUMBER = 1;
- private int version_;
- /**
- * optional uint32 version = 1;
- */
- public boolean hasVersion() {
- return ((bitField0_ & 0x00000001) == 0x00000001);
- }
- /**
- * optional uint32 version = 1;
- */
- public int getVersion() {
- return version_;
- }
-
- private void initFields() {
- version_ = 0;
- }
- private byte memoizedIsInitialized = -1;
- public final boolean isInitialized() {
- byte isInitialized = memoizedIsInitialized;
- if (isInitialized != -1) return isInitialized == 1;
-
- memoizedIsInitialized = 1;
- return true;
- }
-
- public void writeTo(com.google.protobuf.CodedOutputStream output)
- throws java.io.IOException {
- getSerializedSize();
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- output.writeUInt32(1, version_);
- }
- getUnknownFields().writeTo(output);
- }
-
- private int memoizedSerializedSize = -1;
- public int getSerializedSize() {
- int size = memoizedSerializedSize;
- if (size != -1) return size;
-
- size = 0;
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- size += com.google.protobuf.CodedOutputStream
- .computeUInt32Size(1, version_);
- }
- size += getUnknownFields().getSerializedSize();
- memoizedSerializedSize = size;
- return size;
- }
-
- private static final long serialVersionUID = 0L;
- @java.lang.Override
- protected java.lang.Object writeReplace()
- throws java.io.ObjectStreamException {
- return super.writeReplace();
- }
-
- public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseFrom(
- com.google.protobuf.ByteString data)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseFrom(
- com.google.protobuf.ByteString data,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseFrom(byte[] data)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseFrom(
- byte[] data,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseFrom(java.io.InputStream input)
- throws java.io.IOException {
- return PARSER.parseFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseFrom(
- java.io.InputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseFrom(input, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseDelimitedFrom(java.io.InputStream input)
- throws java.io.IOException {
- return PARSER.parseDelimitedFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseDelimitedFrom(
- java.io.InputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseDelimitedFrom(input, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseFrom(
- com.google.protobuf.CodedInputStream input)
- throws java.io.IOException {
- return PARSER.parseFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseFrom(input, extensionRegistry);
- }
-
- public static Builder newBuilder() { return Builder.create(); }
- public Builder newBuilderForType() { return newBuilder(); }
- public static Builder newBuilder(org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion prototype) {
- return newBuilder().mergeFrom(prototype);
- }
- public Builder toBuilder() { return newBuilder(this); }
-
- @java.lang.Override
- protected Builder newBuilderForType(
- com.google.protobuf.GeneratedMessage.BuilderParent parent) {
- Builder builder = new Builder(parent);
- return builder;
- }
- /**
- * Protobuf type {@code signal.DatabaseVersion}
- */
- public static final class Builder extends
- com.google.protobuf.GeneratedMessage.Builder
- implements org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersionOrBuilder {
- public static final com.google.protobuf.Descriptors.Descriptor
- getDescriptor() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_DatabaseVersion_descriptor;
- }
-
- protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internalGetFieldAccessorTable() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_DatabaseVersion_fieldAccessorTable
- .ensureFieldAccessorsInitialized(
- org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.class, org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.Builder.class);
- }
-
- // Construct using org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.newBuilder()
- private Builder() {
- maybeForceBuilderInitialization();
- }
-
- private Builder(
- com.google.protobuf.GeneratedMessage.BuilderParent parent) {
- super(parent);
- maybeForceBuilderInitialization();
- }
- private void maybeForceBuilderInitialization() {
- if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
- }
- }
- private static Builder create() {
- return new Builder();
- }
-
- public Builder clear() {
- super.clear();
- version_ = 0;
- bitField0_ = (bitField0_ & ~0x00000001);
- return this;
- }
-
- public Builder clone() {
- return create().mergeFrom(buildPartial());
- }
-
- public com.google.protobuf.Descriptors.Descriptor
- getDescriptorForType() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_DatabaseVersion_descriptor;
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion getDefaultInstanceForType() {
- return org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.getDefaultInstance();
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion build() {
- org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion result = buildPartial();
- if (!result.isInitialized()) {
- throw newUninitializedMessageException(result);
- }
- return result;
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion buildPartial() {
- org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion result = new org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion(this);
- int from_bitField0_ = bitField0_;
- int to_bitField0_ = 0;
- if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
- to_bitField0_ |= 0x00000001;
- }
- result.version_ = version_;
- result.bitField0_ = to_bitField0_;
- onBuilt();
- return result;
- }
-
- public Builder mergeFrom(com.google.protobuf.Message other) {
- if (other instanceof org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion) {
- return mergeFrom((org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion)other);
- } else {
- super.mergeFrom(other);
- return this;
- }
- }
-
- public Builder mergeFrom(org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion other) {
- if (other == org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.getDefaultInstance()) return this;
- if (other.hasVersion()) {
- setVersion(other.getVersion());
- }
- this.mergeUnknownFields(other.getUnknownFields());
- return this;
- }
-
- public final boolean isInitialized() {
- return true;
- }
-
- public Builder mergeFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parsedMessage = null;
- try {
- parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- parsedMessage = (org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion) e.getUnfinishedMessage();
- throw e;
- } finally {
- if (parsedMessage != null) {
- mergeFrom(parsedMessage);
- }
- }
- return this;
- }
- private int bitField0_;
-
- // optional uint32 version = 1;
- private int version_ ;
- /**
- * optional uint32 version = 1;
- */
- public boolean hasVersion() {
- return ((bitField0_ & 0x00000001) == 0x00000001);
- }
- /**
- * optional uint32 version = 1;
- */
- public int getVersion() {
- return version_;
- }
- /**
- * optional uint32 version = 1;
- */
- public Builder setVersion(int value) {
- bitField0_ |= 0x00000001;
- version_ = value;
- onChanged();
- return this;
- }
- /**
- * optional uint32 version = 1;
- */
- public Builder clearVersion() {
- bitField0_ = (bitField0_ & ~0x00000001);
- version_ = 0;
- onChanged();
- return this;
- }
-
- // @@protoc_insertion_point(builder_scope:signal.DatabaseVersion)
- }
-
- static {
- defaultInstance = new DatabaseVersion(true);
- defaultInstance.initFields();
- }
-
- // @@protoc_insertion_point(class_scope:signal.DatabaseVersion)
- }
-
- public interface HeaderOrBuilder
- extends com.google.protobuf.MessageOrBuilder {
-
- // optional bytes iv = 1;
- /**
- * optional bytes iv = 1;
- */
- boolean hasIv();
- /**
- * optional bytes iv = 1;
- */
- com.google.protobuf.ByteString getIv();
-
- // optional bytes salt = 2;
- /**
- * optional bytes salt = 2;
- */
- boolean hasSalt();
- /**
- * optional bytes salt = 2;
- */
- com.google.protobuf.ByteString getSalt();
- }
- /**
- * Protobuf type {@code signal.Header}
- */
- public static final class Header extends
- com.google.protobuf.GeneratedMessage
- implements HeaderOrBuilder {
- // Use Header.newBuilder() to construct.
- private Header(com.google.protobuf.GeneratedMessage.Builder> builder) {
- super(builder);
- this.unknownFields = builder.getUnknownFields();
- }
- private Header(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); }
-
- private static final Header defaultInstance;
- public static Header getDefaultInstance() {
- return defaultInstance;
- }
-
- public Header getDefaultInstanceForType() {
- return defaultInstance;
- }
-
- private final com.google.protobuf.UnknownFieldSet unknownFields;
- @java.lang.Override
- public final com.google.protobuf.UnknownFieldSet
- getUnknownFields() {
- return this.unknownFields;
- }
- private Header(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- initFields();
- int mutable_bitField0_ = 0;
- com.google.protobuf.UnknownFieldSet.Builder unknownFields =
- com.google.protobuf.UnknownFieldSet.newBuilder();
- try {
- boolean done = false;
- while (!done) {
- int tag = input.readTag();
- switch (tag) {
- case 0:
- done = true;
- break;
- default: {
- if (!parseUnknownField(input, unknownFields,
- extensionRegistry, tag)) {
- done = true;
- }
- break;
- }
- case 10: {
- bitField0_ |= 0x00000001;
- iv_ = input.readBytes();
- break;
- }
- case 18: {
- bitField0_ |= 0x00000002;
- salt_ = input.readBytes();
- break;
- }
- }
- }
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- throw e.setUnfinishedMessage(this);
- } catch (java.io.IOException e) {
- throw new com.google.protobuf.InvalidProtocolBufferException(
- e.getMessage()).setUnfinishedMessage(this);
- } finally {
- this.unknownFields = unknownFields.build();
- makeExtensionsImmutable();
- }
- }
- public static final com.google.protobuf.Descriptors.Descriptor
- getDescriptor() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Header_descriptor;
- }
-
- protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internalGetFieldAccessorTable() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Header_fieldAccessorTable
- .ensureFieldAccessorsInitialized(
- org.thoughtcrime.securesms.backup.BackupProtos.Header.class, org.thoughtcrime.securesms.backup.BackupProtos.Header.Builder.class);
- }
-
- public static com.google.protobuf.Parser PARSER =
- new com.google.protobuf.AbstractParser() {
- public Header parsePartialFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return new Header(input, extensionRegistry);
- }
- };
-
- @java.lang.Override
- public com.google.protobuf.Parser getParserForType() {
- return PARSER;
- }
-
- private int bitField0_;
- // optional bytes iv = 1;
- public static final int IV_FIELD_NUMBER = 1;
- private com.google.protobuf.ByteString iv_;
- /**
- * optional bytes iv = 1;
- */
- public boolean hasIv() {
- return ((bitField0_ & 0x00000001) == 0x00000001);
- }
- /**
- * optional bytes iv = 1;
- */
- public com.google.protobuf.ByteString getIv() {
- return iv_;
- }
-
- // optional bytes salt = 2;
- public static final int SALT_FIELD_NUMBER = 2;
- private com.google.protobuf.ByteString salt_;
- /**
- * optional bytes salt = 2;
- */
- public boolean hasSalt() {
- return ((bitField0_ & 0x00000002) == 0x00000002);
- }
- /**
- * optional bytes salt = 2;
- */
- public com.google.protobuf.ByteString getSalt() {
- return salt_;
- }
-
- private void initFields() {
- iv_ = com.google.protobuf.ByteString.EMPTY;
- salt_ = com.google.protobuf.ByteString.EMPTY;
- }
- private byte memoizedIsInitialized = -1;
- public final boolean isInitialized() {
- byte isInitialized = memoizedIsInitialized;
- if (isInitialized != -1) return isInitialized == 1;
-
- memoizedIsInitialized = 1;
- return true;
- }
-
- public void writeTo(com.google.protobuf.CodedOutputStream output)
- throws java.io.IOException {
- getSerializedSize();
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- output.writeBytes(1, iv_);
- }
- if (((bitField0_ & 0x00000002) == 0x00000002)) {
- output.writeBytes(2, salt_);
- }
- getUnknownFields().writeTo(output);
- }
-
- private int memoizedSerializedSize = -1;
- public int getSerializedSize() {
- int size = memoizedSerializedSize;
- if (size != -1) return size;
-
- size = 0;
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- size += com.google.protobuf.CodedOutputStream
- .computeBytesSize(1, iv_);
- }
- if (((bitField0_ & 0x00000002) == 0x00000002)) {
- size += com.google.protobuf.CodedOutputStream
- .computeBytesSize(2, salt_);
- }
- size += getUnknownFields().getSerializedSize();
- memoizedSerializedSize = size;
- return size;
- }
-
- private static final long serialVersionUID = 0L;
- @java.lang.Override
- protected java.lang.Object writeReplace()
- throws java.io.ObjectStreamException {
- return super.writeReplace();
- }
-
- public static org.thoughtcrime.securesms.backup.BackupProtos.Header parseFrom(
- com.google.protobuf.ByteString data)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Header parseFrom(
- com.google.protobuf.ByteString data,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Header parseFrom(byte[] data)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Header parseFrom(
- byte[] data,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Header parseFrom(java.io.InputStream input)
- throws java.io.IOException {
- return PARSER.parseFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Header parseFrom(
- java.io.InputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseFrom(input, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Header parseDelimitedFrom(java.io.InputStream input)
- throws java.io.IOException {
- return PARSER.parseDelimitedFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Header parseDelimitedFrom(
- java.io.InputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseDelimitedFrom(input, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Header parseFrom(
- com.google.protobuf.CodedInputStream input)
- throws java.io.IOException {
- return PARSER.parseFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.Header parseFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseFrom(input, extensionRegistry);
- }
-
- public static Builder newBuilder() { return Builder.create(); }
- public Builder newBuilderForType() { return newBuilder(); }
- public static Builder newBuilder(org.thoughtcrime.securesms.backup.BackupProtos.Header prototype) {
- return newBuilder().mergeFrom(prototype);
- }
- public Builder toBuilder() { return newBuilder(this); }
-
- @java.lang.Override
- protected Builder newBuilderForType(
- com.google.protobuf.GeneratedMessage.BuilderParent parent) {
- Builder builder = new Builder(parent);
- return builder;
- }
- /**
- * Protobuf type {@code signal.Header}
- */
- public static final class Builder extends
- com.google.protobuf.GeneratedMessage.Builder
- implements org.thoughtcrime.securesms.backup.BackupProtos.HeaderOrBuilder {
- public static final com.google.protobuf.Descriptors.Descriptor
- getDescriptor() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Header_descriptor;
- }
-
- protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internalGetFieldAccessorTable() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Header_fieldAccessorTable
- .ensureFieldAccessorsInitialized(
- org.thoughtcrime.securesms.backup.BackupProtos.Header.class, org.thoughtcrime.securesms.backup.BackupProtos.Header.Builder.class);
- }
-
- // Construct using org.thoughtcrime.securesms.backup.BackupProtos.Header.newBuilder()
- private Builder() {
- maybeForceBuilderInitialization();
- }
-
- private Builder(
- com.google.protobuf.GeneratedMessage.BuilderParent parent) {
- super(parent);
- maybeForceBuilderInitialization();
- }
- private void maybeForceBuilderInitialization() {
- if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
- }
- }
- private static Builder create() {
- return new Builder();
- }
-
- public Builder clear() {
- super.clear();
- iv_ = com.google.protobuf.ByteString.EMPTY;
- bitField0_ = (bitField0_ & ~0x00000001);
- salt_ = com.google.protobuf.ByteString.EMPTY;
- bitField0_ = (bitField0_ & ~0x00000002);
- return this;
- }
-
- public Builder clone() {
- return create().mergeFrom(buildPartial());
- }
-
- public com.google.protobuf.Descriptors.Descriptor
- getDescriptorForType() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Header_descriptor;
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.Header getDefaultInstanceForType() {
- return org.thoughtcrime.securesms.backup.BackupProtos.Header.getDefaultInstance();
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.Header build() {
- org.thoughtcrime.securesms.backup.BackupProtos.Header result = buildPartial();
- if (!result.isInitialized()) {
- throw newUninitializedMessageException(result);
- }
- return result;
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.Header buildPartial() {
- org.thoughtcrime.securesms.backup.BackupProtos.Header result = new org.thoughtcrime.securesms.backup.BackupProtos.Header(this);
- int from_bitField0_ = bitField0_;
- int to_bitField0_ = 0;
- if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
- to_bitField0_ |= 0x00000001;
- }
- result.iv_ = iv_;
- if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
- to_bitField0_ |= 0x00000002;
- }
- result.salt_ = salt_;
- result.bitField0_ = to_bitField0_;
- onBuilt();
- return result;
- }
-
- public Builder mergeFrom(com.google.protobuf.Message other) {
- if (other instanceof org.thoughtcrime.securesms.backup.BackupProtos.Header) {
- return mergeFrom((org.thoughtcrime.securesms.backup.BackupProtos.Header)other);
- } else {
- super.mergeFrom(other);
- return this;
- }
- }
-
- public Builder mergeFrom(org.thoughtcrime.securesms.backup.BackupProtos.Header other) {
- if (other == org.thoughtcrime.securesms.backup.BackupProtos.Header.getDefaultInstance()) return this;
- if (other.hasIv()) {
- setIv(other.getIv());
- }
- if (other.hasSalt()) {
- setSalt(other.getSalt());
- }
- this.mergeUnknownFields(other.getUnknownFields());
- return this;
- }
-
- public final boolean isInitialized() {
- return true;
- }
-
- public Builder mergeFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- org.thoughtcrime.securesms.backup.BackupProtos.Header parsedMessage = null;
- try {
- parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- parsedMessage = (org.thoughtcrime.securesms.backup.BackupProtos.Header) e.getUnfinishedMessage();
- throw e;
- } finally {
- if (parsedMessage != null) {
- mergeFrom(parsedMessage);
- }
- }
- return this;
- }
- private int bitField0_;
-
- // optional bytes iv = 1;
- private com.google.protobuf.ByteString iv_ = com.google.protobuf.ByteString.EMPTY;
- /**
- * optional bytes iv = 1;
- */
- public boolean hasIv() {
- return ((bitField0_ & 0x00000001) == 0x00000001);
- }
- /**
- * optional bytes iv = 1;
- */
- public com.google.protobuf.ByteString getIv() {
- return iv_;
- }
- /**
- * optional bytes iv = 1;
- */
- public Builder setIv(com.google.protobuf.ByteString value) {
- if (value == null) {
- throw new NullPointerException();
- }
- bitField0_ |= 0x00000001;
- iv_ = value;
- onChanged();
- return this;
- }
- /**
- * optional bytes iv = 1;
- */
- public Builder clearIv() {
- bitField0_ = (bitField0_ & ~0x00000001);
- iv_ = getDefaultInstance().getIv();
- onChanged();
- return this;
- }
-
- // optional bytes salt = 2;
- private com.google.protobuf.ByteString salt_ = com.google.protobuf.ByteString.EMPTY;
- /**
- * optional bytes salt = 2;
- */
- public boolean hasSalt() {
- return ((bitField0_ & 0x00000002) == 0x00000002);
- }
- /**
- * optional bytes salt = 2;
- */
- public com.google.protobuf.ByteString getSalt() {
- return salt_;
- }
- /**
- * optional bytes salt = 2;
- */
- public Builder setSalt(com.google.protobuf.ByteString value) {
- if (value == null) {
- throw new NullPointerException();
- }
- bitField0_ |= 0x00000002;
- salt_ = value;
- onChanged();
- return this;
- }
- /**
- * optional bytes salt = 2;
- */
- public Builder clearSalt() {
- bitField0_ = (bitField0_ & ~0x00000002);
- salt_ = getDefaultInstance().getSalt();
- onChanged();
- return this;
- }
-
- // @@protoc_insertion_point(builder_scope:signal.Header)
- }
-
- static {
- defaultInstance = new Header(true);
- defaultInstance.initFields();
- }
-
- // @@protoc_insertion_point(class_scope:signal.Header)
- }
-
- public interface BackupFrameOrBuilder
- extends com.google.protobuf.MessageOrBuilder {
-
- // optional .signal.Header header = 1;
- /**
- * optional .signal.Header header = 1;
- */
- boolean hasHeader();
- /**
- * optional .signal.Header header = 1;
- */
- org.thoughtcrime.securesms.backup.BackupProtos.Header getHeader();
- /**
- * optional .signal.Header header = 1;
- */
- org.thoughtcrime.securesms.backup.BackupProtos.HeaderOrBuilder getHeaderOrBuilder();
-
- // optional .signal.SqlStatement statement = 2;
- /**
- * optional .signal.SqlStatement statement = 2;
- */
- boolean hasStatement();
- /**
- * optional .signal.SqlStatement statement = 2;
- */
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement getStatement();
- /**
- * optional .signal.SqlStatement statement = 2;
- */
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatementOrBuilder getStatementOrBuilder();
-
- // optional .signal.SharedPreference preference = 3;
- /**
- * optional .signal.SharedPreference preference = 3;
- */
- boolean hasPreference();
- /**
- * optional .signal.SharedPreference preference = 3;
- */
- org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference getPreference();
- /**
- * optional .signal.SharedPreference preference = 3;
- */
- org.thoughtcrime.securesms.backup.BackupProtos.SharedPreferenceOrBuilder getPreferenceOrBuilder();
-
- // optional .signal.Attachment attachment = 4;
- /**
- * optional .signal.Attachment attachment = 4;
- */
- boolean hasAttachment();
- /**
- * optional .signal.Attachment attachment = 4;
- */
- org.thoughtcrime.securesms.backup.BackupProtos.Attachment getAttachment();
- /**
- * optional .signal.Attachment attachment = 4;
- */
- org.thoughtcrime.securesms.backup.BackupProtos.AttachmentOrBuilder getAttachmentOrBuilder();
-
- // optional .signal.DatabaseVersion version = 5;
- /**
- * optional .signal.DatabaseVersion version = 5;
- */
- boolean hasVersion();
- /**
- * optional .signal.DatabaseVersion version = 5;
- */
- org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion getVersion();
- /**
- * optional .signal.DatabaseVersion version = 5;
- */
- org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersionOrBuilder getVersionOrBuilder();
-
- // optional bool end = 6;
- /**
- * optional bool end = 6;
- */
- boolean hasEnd();
- /**
- * optional bool end = 6;
- */
- boolean getEnd();
-
- // optional .signal.Avatar avatar = 7;
- /**
- * optional .signal.Avatar avatar = 7;
- */
- boolean hasAvatar();
- /**
- * optional .signal.Avatar avatar = 7;
- */
- org.thoughtcrime.securesms.backup.BackupProtos.Avatar getAvatar();
- /**
- * optional .signal.Avatar avatar = 7;
- */
- org.thoughtcrime.securesms.backup.BackupProtos.AvatarOrBuilder getAvatarOrBuilder();
-
- // optional .signal.Sticker sticker = 8;
- /**
- * optional .signal.Sticker sticker = 8;
- */
- boolean hasSticker();
- /**
- * optional .signal.Sticker sticker = 8;
- */
- org.thoughtcrime.securesms.backup.BackupProtos.Sticker getSticker();
- /**
- * optional .signal.Sticker sticker = 8;
- */
- org.thoughtcrime.securesms.backup.BackupProtos.StickerOrBuilder getStickerOrBuilder();
- }
- /**
- * Protobuf type {@code signal.BackupFrame}
- */
- public static final class BackupFrame extends
- com.google.protobuf.GeneratedMessage
- implements BackupFrameOrBuilder {
- // Use BackupFrame.newBuilder() to construct.
- private BackupFrame(com.google.protobuf.GeneratedMessage.Builder> builder) {
- super(builder);
- this.unknownFields = builder.getUnknownFields();
- }
- private BackupFrame(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); }
-
- private static final BackupFrame defaultInstance;
- public static BackupFrame getDefaultInstance() {
- return defaultInstance;
- }
-
- public BackupFrame getDefaultInstanceForType() {
- return defaultInstance;
- }
-
- private final com.google.protobuf.UnknownFieldSet unknownFields;
- @java.lang.Override
- public final com.google.protobuf.UnknownFieldSet
- getUnknownFields() {
- return this.unknownFields;
- }
- private BackupFrame(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- initFields();
- int mutable_bitField0_ = 0;
- com.google.protobuf.UnknownFieldSet.Builder unknownFields =
- com.google.protobuf.UnknownFieldSet.newBuilder();
- try {
- boolean done = false;
- while (!done) {
- int tag = input.readTag();
- switch (tag) {
- case 0:
- done = true;
- break;
- default: {
- if (!parseUnknownField(input, unknownFields,
- extensionRegistry, tag)) {
- done = true;
- }
- break;
- }
- case 10: {
- org.thoughtcrime.securesms.backup.BackupProtos.Header.Builder subBuilder = null;
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- subBuilder = header_.toBuilder();
- }
- header_ = input.readMessage(org.thoughtcrime.securesms.backup.BackupProtos.Header.PARSER, extensionRegistry);
- if (subBuilder != null) {
- subBuilder.mergeFrom(header_);
- header_ = subBuilder.buildPartial();
- }
- bitField0_ |= 0x00000001;
- break;
- }
- case 18: {
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.Builder subBuilder = null;
- if (((bitField0_ & 0x00000002) == 0x00000002)) {
- subBuilder = statement_.toBuilder();
- }
- statement_ = input.readMessage(org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.PARSER, extensionRegistry);
- if (subBuilder != null) {
- subBuilder.mergeFrom(statement_);
- statement_ = subBuilder.buildPartial();
- }
- bitField0_ |= 0x00000002;
- break;
- }
- case 26: {
- org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.Builder subBuilder = null;
- if (((bitField0_ & 0x00000004) == 0x00000004)) {
- subBuilder = preference_.toBuilder();
- }
- preference_ = input.readMessage(org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.PARSER, extensionRegistry);
- if (subBuilder != null) {
- subBuilder.mergeFrom(preference_);
- preference_ = subBuilder.buildPartial();
- }
- bitField0_ |= 0x00000004;
- break;
- }
- case 34: {
- org.thoughtcrime.securesms.backup.BackupProtos.Attachment.Builder subBuilder = null;
- if (((bitField0_ & 0x00000008) == 0x00000008)) {
- subBuilder = attachment_.toBuilder();
- }
- attachment_ = input.readMessage(org.thoughtcrime.securesms.backup.BackupProtos.Attachment.PARSER, extensionRegistry);
- if (subBuilder != null) {
- subBuilder.mergeFrom(attachment_);
- attachment_ = subBuilder.buildPartial();
- }
- bitField0_ |= 0x00000008;
- break;
- }
- case 42: {
- org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.Builder subBuilder = null;
- if (((bitField0_ & 0x00000010) == 0x00000010)) {
- subBuilder = version_.toBuilder();
- }
- version_ = input.readMessage(org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.PARSER, extensionRegistry);
- if (subBuilder != null) {
- subBuilder.mergeFrom(version_);
- version_ = subBuilder.buildPartial();
- }
- bitField0_ |= 0x00000010;
- break;
- }
- case 48: {
- bitField0_ |= 0x00000020;
- end_ = input.readBool();
- break;
- }
- case 58: {
- org.thoughtcrime.securesms.backup.BackupProtos.Avatar.Builder subBuilder = null;
- if (((bitField0_ & 0x00000040) == 0x00000040)) {
- subBuilder = avatar_.toBuilder();
- }
- avatar_ = input.readMessage(org.thoughtcrime.securesms.backup.BackupProtos.Avatar.PARSER, extensionRegistry);
- if (subBuilder != null) {
- subBuilder.mergeFrom(avatar_);
- avatar_ = subBuilder.buildPartial();
- }
- bitField0_ |= 0x00000040;
- break;
- }
- case 66: {
- org.thoughtcrime.securesms.backup.BackupProtos.Sticker.Builder subBuilder = null;
- if (((bitField0_ & 0x00000080) == 0x00000080)) {
- subBuilder = sticker_.toBuilder();
- }
- sticker_ = input.readMessage(org.thoughtcrime.securesms.backup.BackupProtos.Sticker.PARSER, extensionRegistry);
- if (subBuilder != null) {
- subBuilder.mergeFrom(sticker_);
- sticker_ = subBuilder.buildPartial();
- }
- bitField0_ |= 0x00000080;
- break;
- }
- }
- }
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- throw e.setUnfinishedMessage(this);
- } catch (java.io.IOException e) {
- throw new com.google.protobuf.InvalidProtocolBufferException(
- e.getMessage()).setUnfinishedMessage(this);
- } finally {
- this.unknownFields = unknownFields.build();
- makeExtensionsImmutable();
- }
- }
- public static final com.google.protobuf.Descriptors.Descriptor
- getDescriptor() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_BackupFrame_descriptor;
- }
-
- protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internalGetFieldAccessorTable() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_BackupFrame_fieldAccessorTable
- .ensureFieldAccessorsInitialized(
- org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame.class, org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame.Builder.class);
- }
-
- public static com.google.protobuf.Parser PARSER =
- new com.google.protobuf.AbstractParser() {
- public BackupFrame parsePartialFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return new BackupFrame(input, extensionRegistry);
- }
- };
-
- @java.lang.Override
- public com.google.protobuf.Parser getParserForType() {
- return PARSER;
- }
-
- private int bitField0_;
- // optional .signal.Header header = 1;
- public static final int HEADER_FIELD_NUMBER = 1;
- private org.thoughtcrime.securesms.backup.BackupProtos.Header header_;
- /**
- * optional .signal.Header header = 1;
- */
- public boolean hasHeader() {
- return ((bitField0_ & 0x00000001) == 0x00000001);
- }
- /**
- * optional .signal.Header header = 1;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.Header getHeader() {
- return header_;
- }
- /**
- * optional .signal.Header header = 1;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.HeaderOrBuilder getHeaderOrBuilder() {
- return header_;
- }
-
- // optional .signal.SqlStatement statement = 2;
- public static final int STATEMENT_FIELD_NUMBER = 2;
- private org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement statement_;
- /**
- * optional .signal.SqlStatement statement = 2;
- */
- public boolean hasStatement() {
- return ((bitField0_ & 0x00000002) == 0x00000002);
- }
- /**
- * optional .signal.SqlStatement statement = 2;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement getStatement() {
- return statement_;
- }
- /**
- * optional .signal.SqlStatement statement = 2;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatementOrBuilder getStatementOrBuilder() {
- return statement_;
- }
-
- // optional .signal.SharedPreference preference = 3;
- public static final int PREFERENCE_FIELD_NUMBER = 3;
- private org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference preference_;
- /**
- * optional .signal.SharedPreference preference = 3;
- */
- public boolean hasPreference() {
- return ((bitField0_ & 0x00000004) == 0x00000004);
- }
- /**
- * optional .signal.SharedPreference preference = 3;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference getPreference() {
- return preference_;
- }
- /**
- * optional .signal.SharedPreference preference = 3;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.SharedPreferenceOrBuilder getPreferenceOrBuilder() {
- return preference_;
- }
-
- // optional .signal.Attachment attachment = 4;
- public static final int ATTACHMENT_FIELD_NUMBER = 4;
- private org.thoughtcrime.securesms.backup.BackupProtos.Attachment attachment_;
- /**
- * optional .signal.Attachment attachment = 4;
- */
- public boolean hasAttachment() {
- return ((bitField0_ & 0x00000008) == 0x00000008);
- }
- /**
- * optional .signal.Attachment attachment = 4;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.Attachment getAttachment() {
- return attachment_;
- }
- /**
- * optional .signal.Attachment attachment = 4;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.AttachmentOrBuilder getAttachmentOrBuilder() {
- return attachment_;
- }
-
- // optional .signal.DatabaseVersion version = 5;
- public static final int VERSION_FIELD_NUMBER = 5;
- private org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion version_;
- /**
- * optional .signal.DatabaseVersion version = 5;
- */
- public boolean hasVersion() {
- return ((bitField0_ & 0x00000010) == 0x00000010);
- }
- /**
- * optional .signal.DatabaseVersion version = 5;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion getVersion() {
- return version_;
- }
- /**
- * optional .signal.DatabaseVersion version = 5;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersionOrBuilder getVersionOrBuilder() {
- return version_;
- }
-
- // optional bool end = 6;
- public static final int END_FIELD_NUMBER = 6;
- private boolean end_;
- /**
- * optional bool end = 6;
- */
- public boolean hasEnd() {
- return ((bitField0_ & 0x00000020) == 0x00000020);
- }
- /**
- * optional bool end = 6;
- */
- public boolean getEnd() {
- return end_;
- }
-
- // optional .signal.Avatar avatar = 7;
- public static final int AVATAR_FIELD_NUMBER = 7;
- private org.thoughtcrime.securesms.backup.BackupProtos.Avatar avatar_;
- /**
- * optional .signal.Avatar avatar = 7;
- */
- public boolean hasAvatar() {
- return ((bitField0_ & 0x00000040) == 0x00000040);
- }
- /**
- * optional .signal.Avatar avatar = 7;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.Avatar getAvatar() {
- return avatar_;
- }
- /**
- * optional .signal.Avatar avatar = 7;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.AvatarOrBuilder getAvatarOrBuilder() {
- return avatar_;
- }
-
- // optional .signal.Sticker sticker = 8;
- public static final int STICKER_FIELD_NUMBER = 8;
- private org.thoughtcrime.securesms.backup.BackupProtos.Sticker sticker_;
- /**
- * optional .signal.Sticker sticker = 8;
- */
- public boolean hasSticker() {
- return ((bitField0_ & 0x00000080) == 0x00000080);
- }
- /**
- * optional .signal.Sticker sticker = 8;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.Sticker getSticker() {
- return sticker_;
- }
- /**
- * optional .signal.Sticker sticker = 8;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.StickerOrBuilder getStickerOrBuilder() {
- return sticker_;
- }
-
- private void initFields() {
- header_ = org.thoughtcrime.securesms.backup.BackupProtos.Header.getDefaultInstance();
- statement_ = org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.getDefaultInstance();
- preference_ = org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.getDefaultInstance();
- attachment_ = org.thoughtcrime.securesms.backup.BackupProtos.Attachment.getDefaultInstance();
- version_ = org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.getDefaultInstance();
- end_ = false;
- avatar_ = org.thoughtcrime.securesms.backup.BackupProtos.Avatar.getDefaultInstance();
- sticker_ = org.thoughtcrime.securesms.backup.BackupProtos.Sticker.getDefaultInstance();
- }
- private byte memoizedIsInitialized = -1;
- public final boolean isInitialized() {
- byte isInitialized = memoizedIsInitialized;
- if (isInitialized != -1) return isInitialized == 1;
-
- memoizedIsInitialized = 1;
- return true;
- }
-
- public void writeTo(com.google.protobuf.CodedOutputStream output)
- throws java.io.IOException {
- getSerializedSize();
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- output.writeMessage(1, header_);
- }
- if (((bitField0_ & 0x00000002) == 0x00000002)) {
- output.writeMessage(2, statement_);
- }
- if (((bitField0_ & 0x00000004) == 0x00000004)) {
- output.writeMessage(3, preference_);
- }
- if (((bitField0_ & 0x00000008) == 0x00000008)) {
- output.writeMessage(4, attachment_);
- }
- if (((bitField0_ & 0x00000010) == 0x00000010)) {
- output.writeMessage(5, version_);
- }
- if (((bitField0_ & 0x00000020) == 0x00000020)) {
- output.writeBool(6, end_);
- }
- if (((bitField0_ & 0x00000040) == 0x00000040)) {
- output.writeMessage(7, avatar_);
- }
- if (((bitField0_ & 0x00000080) == 0x00000080)) {
- output.writeMessage(8, sticker_);
- }
- getUnknownFields().writeTo(output);
- }
-
- private int memoizedSerializedSize = -1;
- public int getSerializedSize() {
- int size = memoizedSerializedSize;
- if (size != -1) return size;
-
- size = 0;
- if (((bitField0_ & 0x00000001) == 0x00000001)) {
- size += com.google.protobuf.CodedOutputStream
- .computeMessageSize(1, header_);
- }
- if (((bitField0_ & 0x00000002) == 0x00000002)) {
- size += com.google.protobuf.CodedOutputStream
- .computeMessageSize(2, statement_);
- }
- if (((bitField0_ & 0x00000004) == 0x00000004)) {
- size += com.google.protobuf.CodedOutputStream
- .computeMessageSize(3, preference_);
- }
- if (((bitField0_ & 0x00000008) == 0x00000008)) {
- size += com.google.protobuf.CodedOutputStream
- .computeMessageSize(4, attachment_);
- }
- if (((bitField0_ & 0x00000010) == 0x00000010)) {
- size += com.google.protobuf.CodedOutputStream
- .computeMessageSize(5, version_);
- }
- if (((bitField0_ & 0x00000020) == 0x00000020)) {
- size += com.google.protobuf.CodedOutputStream
- .computeBoolSize(6, end_);
- }
- if (((bitField0_ & 0x00000040) == 0x00000040)) {
- size += com.google.protobuf.CodedOutputStream
- .computeMessageSize(7, avatar_);
- }
- if (((bitField0_ & 0x00000080) == 0x00000080)) {
- size += com.google.protobuf.CodedOutputStream
- .computeMessageSize(8, sticker_);
- }
- size += getUnknownFields().getSerializedSize();
- memoizedSerializedSize = size;
- return size;
- }
-
- private static final long serialVersionUID = 0L;
- @java.lang.Override
- protected java.lang.Object writeReplace()
- throws java.io.ObjectStreamException {
- return super.writeReplace();
- }
-
- public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseFrom(
- com.google.protobuf.ByteString data)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseFrom(
- com.google.protobuf.ByteString data,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseFrom(byte[] data)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseFrom(
- byte[] data,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws com.google.protobuf.InvalidProtocolBufferException {
- return PARSER.parseFrom(data, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseFrom(java.io.InputStream input)
- throws java.io.IOException {
- return PARSER.parseFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseFrom(
- java.io.InputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseFrom(input, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseDelimitedFrom(java.io.InputStream input)
- throws java.io.IOException {
- return PARSER.parseDelimitedFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseDelimitedFrom(
- java.io.InputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseDelimitedFrom(input, extensionRegistry);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseFrom(
- com.google.protobuf.CodedInputStream input)
- throws java.io.IOException {
- return PARSER.parseFrom(input);
- }
- public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- return PARSER.parseFrom(input, extensionRegistry);
- }
-
- public static Builder newBuilder() { return Builder.create(); }
- public Builder newBuilderForType() { return newBuilder(); }
- public static Builder newBuilder(org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame prototype) {
- return newBuilder().mergeFrom(prototype);
- }
- public Builder toBuilder() { return newBuilder(this); }
-
- @java.lang.Override
- protected Builder newBuilderForType(
- com.google.protobuf.GeneratedMessage.BuilderParent parent) {
- Builder builder = new Builder(parent);
- return builder;
- }
- /**
- * Protobuf type {@code signal.BackupFrame}
- */
- public static final class Builder extends
- com.google.protobuf.GeneratedMessage.Builder
- implements org.thoughtcrime.securesms.backup.BackupProtos.BackupFrameOrBuilder {
- public static final com.google.protobuf.Descriptors.Descriptor
- getDescriptor() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_BackupFrame_descriptor;
- }
-
- protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internalGetFieldAccessorTable() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_BackupFrame_fieldAccessorTable
- .ensureFieldAccessorsInitialized(
- org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame.class, org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame.Builder.class);
- }
-
- // Construct using org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame.newBuilder()
- private Builder() {
- maybeForceBuilderInitialization();
- }
-
- private Builder(
- com.google.protobuf.GeneratedMessage.BuilderParent parent) {
- super(parent);
- maybeForceBuilderInitialization();
- }
- private void maybeForceBuilderInitialization() {
- if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
- getHeaderFieldBuilder();
- getStatementFieldBuilder();
- getPreferenceFieldBuilder();
- getAttachmentFieldBuilder();
- getVersionFieldBuilder();
- getAvatarFieldBuilder();
- getStickerFieldBuilder();
- }
- }
- private static Builder create() {
- return new Builder();
- }
-
- public Builder clear() {
- super.clear();
- if (headerBuilder_ == null) {
- header_ = org.thoughtcrime.securesms.backup.BackupProtos.Header.getDefaultInstance();
- } else {
- headerBuilder_.clear();
- }
- bitField0_ = (bitField0_ & ~0x00000001);
- if (statementBuilder_ == null) {
- statement_ = org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.getDefaultInstance();
- } else {
- statementBuilder_.clear();
- }
- bitField0_ = (bitField0_ & ~0x00000002);
- if (preferenceBuilder_ == null) {
- preference_ = org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.getDefaultInstance();
- } else {
- preferenceBuilder_.clear();
- }
- bitField0_ = (bitField0_ & ~0x00000004);
- if (attachmentBuilder_ == null) {
- attachment_ = org.thoughtcrime.securesms.backup.BackupProtos.Attachment.getDefaultInstance();
- } else {
- attachmentBuilder_.clear();
- }
- bitField0_ = (bitField0_ & ~0x00000008);
- if (versionBuilder_ == null) {
- version_ = org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.getDefaultInstance();
- } else {
- versionBuilder_.clear();
- }
- bitField0_ = (bitField0_ & ~0x00000010);
- end_ = false;
- bitField0_ = (bitField0_ & ~0x00000020);
- if (avatarBuilder_ == null) {
- avatar_ = org.thoughtcrime.securesms.backup.BackupProtos.Avatar.getDefaultInstance();
- } else {
- avatarBuilder_.clear();
- }
- bitField0_ = (bitField0_ & ~0x00000040);
- if (stickerBuilder_ == null) {
- sticker_ = org.thoughtcrime.securesms.backup.BackupProtos.Sticker.getDefaultInstance();
- } else {
- stickerBuilder_.clear();
- }
- bitField0_ = (bitField0_ & ~0x00000080);
- return this;
- }
-
- public Builder clone() {
- return create().mergeFrom(buildPartial());
- }
-
- public com.google.protobuf.Descriptors.Descriptor
- getDescriptorForType() {
- return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_BackupFrame_descriptor;
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame getDefaultInstanceForType() {
- return org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame.getDefaultInstance();
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame build() {
- org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame result = buildPartial();
- if (!result.isInitialized()) {
- throw newUninitializedMessageException(result);
- }
- return result;
- }
-
- public org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame buildPartial() {
- org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame result = new org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame(this);
- int from_bitField0_ = bitField0_;
- int to_bitField0_ = 0;
- if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
- to_bitField0_ |= 0x00000001;
- }
- if (headerBuilder_ == null) {
- result.header_ = header_;
- } else {
- result.header_ = headerBuilder_.build();
- }
- if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
- to_bitField0_ |= 0x00000002;
- }
- if (statementBuilder_ == null) {
- result.statement_ = statement_;
- } else {
- result.statement_ = statementBuilder_.build();
- }
- if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
- to_bitField0_ |= 0x00000004;
- }
- if (preferenceBuilder_ == null) {
- result.preference_ = preference_;
- } else {
- result.preference_ = preferenceBuilder_.build();
- }
- if (((from_bitField0_ & 0x00000008) == 0x00000008)) {
- to_bitField0_ |= 0x00000008;
- }
- if (attachmentBuilder_ == null) {
- result.attachment_ = attachment_;
- } else {
- result.attachment_ = attachmentBuilder_.build();
- }
- if (((from_bitField0_ & 0x00000010) == 0x00000010)) {
- to_bitField0_ |= 0x00000010;
- }
- if (versionBuilder_ == null) {
- result.version_ = version_;
- } else {
- result.version_ = versionBuilder_.build();
- }
- if (((from_bitField0_ & 0x00000020) == 0x00000020)) {
- to_bitField0_ |= 0x00000020;
- }
- result.end_ = end_;
- if (((from_bitField0_ & 0x00000040) == 0x00000040)) {
- to_bitField0_ |= 0x00000040;
- }
- if (avatarBuilder_ == null) {
- result.avatar_ = avatar_;
- } else {
- result.avatar_ = avatarBuilder_.build();
- }
- if (((from_bitField0_ & 0x00000080) == 0x00000080)) {
- to_bitField0_ |= 0x00000080;
- }
- if (stickerBuilder_ == null) {
- result.sticker_ = sticker_;
- } else {
- result.sticker_ = stickerBuilder_.build();
- }
- result.bitField0_ = to_bitField0_;
- onBuilt();
- return result;
- }
-
- public Builder mergeFrom(com.google.protobuf.Message other) {
- if (other instanceof org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame) {
- return mergeFrom((org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame)other);
- } else {
- super.mergeFrom(other);
- return this;
- }
- }
-
- public Builder mergeFrom(org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame other) {
- if (other == org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame.getDefaultInstance()) return this;
- if (other.hasHeader()) {
- mergeHeader(other.getHeader());
- }
- if (other.hasStatement()) {
- mergeStatement(other.getStatement());
- }
- if (other.hasPreference()) {
- mergePreference(other.getPreference());
- }
- if (other.hasAttachment()) {
- mergeAttachment(other.getAttachment());
- }
- if (other.hasVersion()) {
- mergeVersion(other.getVersion());
- }
- if (other.hasEnd()) {
- setEnd(other.getEnd());
- }
- if (other.hasAvatar()) {
- mergeAvatar(other.getAvatar());
- }
- if (other.hasSticker()) {
- mergeSticker(other.getSticker());
- }
- this.mergeUnknownFields(other.getUnknownFields());
- return this;
- }
-
- public final boolean isInitialized() {
- return true;
- }
-
- public Builder mergeFrom(
- com.google.protobuf.CodedInputStream input,
- com.google.protobuf.ExtensionRegistryLite extensionRegistry)
- throws java.io.IOException {
- org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parsedMessage = null;
- try {
- parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- parsedMessage = (org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame) e.getUnfinishedMessage();
- throw e;
- } finally {
- if (parsedMessage != null) {
- mergeFrom(parsedMessage);
- }
- }
- return this;
- }
- private int bitField0_;
-
- // optional .signal.Header header = 1;
- private org.thoughtcrime.securesms.backup.BackupProtos.Header header_ = org.thoughtcrime.securesms.backup.BackupProtos.Header.getDefaultInstance();
- private com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.Header, org.thoughtcrime.securesms.backup.BackupProtos.Header.Builder, org.thoughtcrime.securesms.backup.BackupProtos.HeaderOrBuilder> headerBuilder_;
- /**
- * optional .signal.Header header = 1;
- */
- public boolean hasHeader() {
- return ((bitField0_ & 0x00000001) == 0x00000001);
- }
- /**
- * optional .signal.Header header = 1;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.Header getHeader() {
- if (headerBuilder_ == null) {
- return header_;
- } else {
- return headerBuilder_.getMessage();
- }
- }
- /**
- * optional .signal.Header header = 1;
- */
- public Builder setHeader(org.thoughtcrime.securesms.backup.BackupProtos.Header value) {
- if (headerBuilder_ == null) {
- if (value == null) {
- throw new NullPointerException();
- }
- header_ = value;
- onChanged();
- } else {
- headerBuilder_.setMessage(value);
- }
- bitField0_ |= 0x00000001;
- return this;
- }
- /**
- * optional .signal.Header header = 1;
- */
- public Builder setHeader(
- org.thoughtcrime.securesms.backup.BackupProtos.Header.Builder builderForValue) {
- if (headerBuilder_ == null) {
- header_ = builderForValue.build();
- onChanged();
- } else {
- headerBuilder_.setMessage(builderForValue.build());
- }
- bitField0_ |= 0x00000001;
- return this;
- }
- /**
- * optional .signal.Header header = 1;
- */
- public Builder mergeHeader(org.thoughtcrime.securesms.backup.BackupProtos.Header value) {
- if (headerBuilder_ == null) {
- if (((bitField0_ & 0x00000001) == 0x00000001) &&
- header_ != org.thoughtcrime.securesms.backup.BackupProtos.Header.getDefaultInstance()) {
- header_ =
- org.thoughtcrime.securesms.backup.BackupProtos.Header.newBuilder(header_).mergeFrom(value).buildPartial();
- } else {
- header_ = value;
- }
- onChanged();
- } else {
- headerBuilder_.mergeFrom(value);
- }
- bitField0_ |= 0x00000001;
- return this;
- }
- /**
- * optional .signal.Header header = 1;
- */
- public Builder clearHeader() {
- if (headerBuilder_ == null) {
- header_ = org.thoughtcrime.securesms.backup.BackupProtos.Header.getDefaultInstance();
- onChanged();
- } else {
- headerBuilder_.clear();
- }
- bitField0_ = (bitField0_ & ~0x00000001);
- return this;
- }
- /**
- * optional .signal.Header header = 1;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.Header.Builder getHeaderBuilder() {
- bitField0_ |= 0x00000001;
- onChanged();
- return getHeaderFieldBuilder().getBuilder();
- }
- /**
- * optional .signal.Header header = 1;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.HeaderOrBuilder getHeaderOrBuilder() {
- if (headerBuilder_ != null) {
- return headerBuilder_.getMessageOrBuilder();
- } else {
- return header_;
- }
- }
- /**
- * optional .signal.Header header = 1;
- */
- private com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.Header, org.thoughtcrime.securesms.backup.BackupProtos.Header.Builder, org.thoughtcrime.securesms.backup.BackupProtos.HeaderOrBuilder>
- getHeaderFieldBuilder() {
- if (headerBuilder_ == null) {
- headerBuilder_ = new com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.Header, org.thoughtcrime.securesms.backup.BackupProtos.Header.Builder, org.thoughtcrime.securesms.backup.BackupProtos.HeaderOrBuilder>(
- header_,
- getParentForChildren(),
- isClean());
- header_ = null;
- }
- return headerBuilder_;
- }
-
- // optional .signal.SqlStatement statement = 2;
- private org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement statement_ = org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.getDefaultInstance();
- private com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.Builder, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatementOrBuilder> statementBuilder_;
- /**
- * optional .signal.SqlStatement statement = 2;
- */
- public boolean hasStatement() {
- return ((bitField0_ & 0x00000002) == 0x00000002);
- }
- /**
- * optional .signal.SqlStatement statement = 2;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement getStatement() {
- if (statementBuilder_ == null) {
- return statement_;
- } else {
- return statementBuilder_.getMessage();
- }
- }
- /**
- * optional .signal.SqlStatement statement = 2;
- */
- public Builder setStatement(org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement value) {
- if (statementBuilder_ == null) {
- if (value == null) {
- throw new NullPointerException();
- }
- statement_ = value;
- onChanged();
- } else {
- statementBuilder_.setMessage(value);
- }
- bitField0_ |= 0x00000002;
- return this;
- }
- /**
- * optional .signal.SqlStatement statement = 2;
- */
- public Builder setStatement(
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.Builder builderForValue) {
- if (statementBuilder_ == null) {
- statement_ = builderForValue.build();
- onChanged();
- } else {
- statementBuilder_.setMessage(builderForValue.build());
- }
- bitField0_ |= 0x00000002;
- return this;
- }
- /**
- * optional .signal.SqlStatement statement = 2;
- */
- public Builder mergeStatement(org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement value) {
- if (statementBuilder_ == null) {
- if (((bitField0_ & 0x00000002) == 0x00000002) &&
- statement_ != org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.getDefaultInstance()) {
- statement_ =
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.newBuilder(statement_).mergeFrom(value).buildPartial();
- } else {
- statement_ = value;
- }
- onChanged();
- } else {
- statementBuilder_.mergeFrom(value);
- }
- bitField0_ |= 0x00000002;
- return this;
- }
- /**
- * optional .signal.SqlStatement statement = 2;
- */
- public Builder clearStatement() {
- if (statementBuilder_ == null) {
- statement_ = org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.getDefaultInstance();
- onChanged();
- } else {
- statementBuilder_.clear();
- }
- bitField0_ = (bitField0_ & ~0x00000002);
- return this;
- }
- /**
- * optional .signal.SqlStatement statement = 2;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.Builder getStatementBuilder() {
- bitField0_ |= 0x00000002;
- onChanged();
- return getStatementFieldBuilder().getBuilder();
- }
- /**
- * optional .signal.SqlStatement statement = 2;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatementOrBuilder getStatementOrBuilder() {
- if (statementBuilder_ != null) {
- return statementBuilder_.getMessageOrBuilder();
- } else {
- return statement_;
- }
- }
- /**
- * optional .signal.SqlStatement statement = 2;
- */
- private com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.Builder, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatementOrBuilder>
- getStatementFieldBuilder() {
- if (statementBuilder_ == null) {
- statementBuilder_ = new com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.Builder, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatementOrBuilder>(
- statement_,
- getParentForChildren(),
- isClean());
- statement_ = null;
- }
- return statementBuilder_;
- }
-
- // optional .signal.SharedPreference preference = 3;
- private org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference preference_ = org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.getDefaultInstance();
- private com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference, org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.Builder, org.thoughtcrime.securesms.backup.BackupProtos.SharedPreferenceOrBuilder> preferenceBuilder_;
- /**
- * optional .signal.SharedPreference preference = 3;
- */
- public boolean hasPreference() {
- return ((bitField0_ & 0x00000004) == 0x00000004);
- }
- /**
- * optional .signal.SharedPreference preference = 3;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference getPreference() {
- if (preferenceBuilder_ == null) {
- return preference_;
- } else {
- return preferenceBuilder_.getMessage();
- }
- }
- /**
- * optional .signal.SharedPreference preference = 3;
- */
- public Builder setPreference(org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference value) {
- if (preferenceBuilder_ == null) {
- if (value == null) {
- throw new NullPointerException();
- }
- preference_ = value;
- onChanged();
- } else {
- preferenceBuilder_.setMessage(value);
- }
- bitField0_ |= 0x00000004;
- return this;
- }
- /**
- * optional .signal.SharedPreference preference = 3;
- */
- public Builder setPreference(
- org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.Builder builderForValue) {
- if (preferenceBuilder_ == null) {
- preference_ = builderForValue.build();
- onChanged();
- } else {
- preferenceBuilder_.setMessage(builderForValue.build());
- }
- bitField0_ |= 0x00000004;
- return this;
- }
- /**
- * optional .signal.SharedPreference preference = 3;
- */
- public Builder mergePreference(org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference value) {
- if (preferenceBuilder_ == null) {
- if (((bitField0_ & 0x00000004) == 0x00000004) &&
- preference_ != org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.getDefaultInstance()) {
- preference_ =
- org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.newBuilder(preference_).mergeFrom(value).buildPartial();
- } else {
- preference_ = value;
- }
- onChanged();
- } else {
- preferenceBuilder_.mergeFrom(value);
- }
- bitField0_ |= 0x00000004;
- return this;
- }
- /**
- * optional .signal.SharedPreference preference = 3;
- */
- public Builder clearPreference() {
- if (preferenceBuilder_ == null) {
- preference_ = org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.getDefaultInstance();
- onChanged();
- } else {
- preferenceBuilder_.clear();
- }
- bitField0_ = (bitField0_ & ~0x00000004);
- return this;
- }
- /**
- * optional .signal.SharedPreference preference = 3;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.Builder getPreferenceBuilder() {
- bitField0_ |= 0x00000004;
- onChanged();
- return getPreferenceFieldBuilder().getBuilder();
- }
- /**
- * optional .signal.SharedPreference preference = 3;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.SharedPreferenceOrBuilder getPreferenceOrBuilder() {
- if (preferenceBuilder_ != null) {
- return preferenceBuilder_.getMessageOrBuilder();
- } else {
- return preference_;
- }
- }
- /**
- * optional .signal.SharedPreference preference = 3;
- */
- private com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference, org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.Builder, org.thoughtcrime.securesms.backup.BackupProtos.SharedPreferenceOrBuilder>
- getPreferenceFieldBuilder() {
- if (preferenceBuilder_ == null) {
- preferenceBuilder_ = new com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference, org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.Builder, org.thoughtcrime.securesms.backup.BackupProtos.SharedPreferenceOrBuilder>(
- preference_,
- getParentForChildren(),
- isClean());
- preference_ = null;
- }
- return preferenceBuilder_;
- }
-
- // optional .signal.Attachment attachment = 4;
- private org.thoughtcrime.securesms.backup.BackupProtos.Attachment attachment_ = org.thoughtcrime.securesms.backup.BackupProtos.Attachment.getDefaultInstance();
- private com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.Attachment, org.thoughtcrime.securesms.backup.BackupProtos.Attachment.Builder, org.thoughtcrime.securesms.backup.BackupProtos.AttachmentOrBuilder> attachmentBuilder_;
- /**
- * optional .signal.Attachment attachment = 4;
- */
- public boolean hasAttachment() {
- return ((bitField0_ & 0x00000008) == 0x00000008);
- }
- /**
- * optional .signal.Attachment attachment = 4;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.Attachment getAttachment() {
- if (attachmentBuilder_ == null) {
- return attachment_;
- } else {
- return attachmentBuilder_.getMessage();
- }
- }
- /**
- * optional .signal.Attachment attachment = 4;
- */
- public Builder setAttachment(org.thoughtcrime.securesms.backup.BackupProtos.Attachment value) {
- if (attachmentBuilder_ == null) {
- if (value == null) {
- throw new NullPointerException();
- }
- attachment_ = value;
- onChanged();
- } else {
- attachmentBuilder_.setMessage(value);
- }
- bitField0_ |= 0x00000008;
- return this;
- }
- /**
- * optional .signal.Attachment attachment = 4;
- */
- public Builder setAttachment(
- org.thoughtcrime.securesms.backup.BackupProtos.Attachment.Builder builderForValue) {
- if (attachmentBuilder_ == null) {
- attachment_ = builderForValue.build();
- onChanged();
- } else {
- attachmentBuilder_.setMessage(builderForValue.build());
- }
- bitField0_ |= 0x00000008;
- return this;
- }
- /**
- * optional .signal.Attachment attachment = 4;
- */
- public Builder mergeAttachment(org.thoughtcrime.securesms.backup.BackupProtos.Attachment value) {
- if (attachmentBuilder_ == null) {
- if (((bitField0_ & 0x00000008) == 0x00000008) &&
- attachment_ != org.thoughtcrime.securesms.backup.BackupProtos.Attachment.getDefaultInstance()) {
- attachment_ =
- org.thoughtcrime.securesms.backup.BackupProtos.Attachment.newBuilder(attachment_).mergeFrom(value).buildPartial();
- } else {
- attachment_ = value;
- }
- onChanged();
- } else {
- attachmentBuilder_.mergeFrom(value);
- }
- bitField0_ |= 0x00000008;
- return this;
- }
- /**
- * optional .signal.Attachment attachment = 4;
- */
- public Builder clearAttachment() {
- if (attachmentBuilder_ == null) {
- attachment_ = org.thoughtcrime.securesms.backup.BackupProtos.Attachment.getDefaultInstance();
- onChanged();
- } else {
- attachmentBuilder_.clear();
- }
- bitField0_ = (bitField0_ & ~0x00000008);
- return this;
- }
- /**
- * optional .signal.Attachment attachment = 4;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.Attachment.Builder getAttachmentBuilder() {
- bitField0_ |= 0x00000008;
- onChanged();
- return getAttachmentFieldBuilder().getBuilder();
- }
- /**
- * optional .signal.Attachment attachment = 4;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.AttachmentOrBuilder getAttachmentOrBuilder() {
- if (attachmentBuilder_ != null) {
- return attachmentBuilder_.getMessageOrBuilder();
- } else {
- return attachment_;
- }
- }
- /**
- * optional .signal.Attachment attachment = 4;
- */
- private com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.Attachment, org.thoughtcrime.securesms.backup.BackupProtos.Attachment.Builder, org.thoughtcrime.securesms.backup.BackupProtos.AttachmentOrBuilder>
- getAttachmentFieldBuilder() {
- if (attachmentBuilder_ == null) {
- attachmentBuilder_ = new com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.Attachment, org.thoughtcrime.securesms.backup.BackupProtos.Attachment.Builder, org.thoughtcrime.securesms.backup.BackupProtos.AttachmentOrBuilder>(
- attachment_,
- getParentForChildren(),
- isClean());
- attachment_ = null;
- }
- return attachmentBuilder_;
- }
-
- // optional .signal.DatabaseVersion version = 5;
- private org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion version_ = org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.getDefaultInstance();
- private com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion, org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.Builder, org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersionOrBuilder> versionBuilder_;
- /**
- * optional .signal.DatabaseVersion version = 5;
- */
- public boolean hasVersion() {
- return ((bitField0_ & 0x00000010) == 0x00000010);
- }
- /**
- * optional .signal.DatabaseVersion version = 5;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion getVersion() {
- if (versionBuilder_ == null) {
- return version_;
- } else {
- return versionBuilder_.getMessage();
- }
- }
- /**
- * optional .signal.DatabaseVersion version = 5;
- */
- public Builder setVersion(org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion value) {
- if (versionBuilder_ == null) {
- if (value == null) {
- throw new NullPointerException();
- }
- version_ = value;
- onChanged();
- } else {
- versionBuilder_.setMessage(value);
- }
- bitField0_ |= 0x00000010;
- return this;
- }
- /**
- * optional .signal.DatabaseVersion version = 5;
- */
- public Builder setVersion(
- org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.Builder builderForValue) {
- if (versionBuilder_ == null) {
- version_ = builderForValue.build();
- onChanged();
- } else {
- versionBuilder_.setMessage(builderForValue.build());
- }
- bitField0_ |= 0x00000010;
- return this;
- }
- /**
- * optional .signal.DatabaseVersion version = 5;
- */
- public Builder mergeVersion(org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion value) {
- if (versionBuilder_ == null) {
- if (((bitField0_ & 0x00000010) == 0x00000010) &&
- version_ != org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.getDefaultInstance()) {
- version_ =
- org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.newBuilder(version_).mergeFrom(value).buildPartial();
- } else {
- version_ = value;
- }
- onChanged();
- } else {
- versionBuilder_.mergeFrom(value);
- }
- bitField0_ |= 0x00000010;
- return this;
- }
- /**
- * optional .signal.DatabaseVersion version = 5;
- */
- public Builder clearVersion() {
- if (versionBuilder_ == null) {
- version_ = org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.getDefaultInstance();
- onChanged();
- } else {
- versionBuilder_.clear();
- }
- bitField0_ = (bitField0_ & ~0x00000010);
- return this;
- }
- /**
- * optional .signal.DatabaseVersion version = 5;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.Builder getVersionBuilder() {
- bitField0_ |= 0x00000010;
- onChanged();
- return getVersionFieldBuilder().getBuilder();
- }
- /**
- * optional .signal.DatabaseVersion version = 5;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersionOrBuilder getVersionOrBuilder() {
- if (versionBuilder_ != null) {
- return versionBuilder_.getMessageOrBuilder();
- } else {
- return version_;
- }
- }
- /**
- * optional .signal.DatabaseVersion version = 5;
- */
- private com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion, org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.Builder, org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersionOrBuilder>
- getVersionFieldBuilder() {
- if (versionBuilder_ == null) {
- versionBuilder_ = new com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion, org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.Builder, org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersionOrBuilder>(
- version_,
- getParentForChildren(),
- isClean());
- version_ = null;
- }
- return versionBuilder_;
- }
-
- // optional bool end = 6;
- private boolean end_ ;
- /**
- * optional bool end = 6;
- */
- public boolean hasEnd() {
- return ((bitField0_ & 0x00000020) == 0x00000020);
- }
- /**
- * optional bool end = 6;
- */
- public boolean getEnd() {
- return end_;
- }
- /**
- * optional bool end = 6;
- */
- public Builder setEnd(boolean value) {
- bitField0_ |= 0x00000020;
- end_ = value;
- onChanged();
- return this;
- }
- /**
- * optional bool end = 6;
- */
- public Builder clearEnd() {
- bitField0_ = (bitField0_ & ~0x00000020);
- end_ = false;
- onChanged();
- return this;
- }
-
- // optional .signal.Avatar avatar = 7;
- private org.thoughtcrime.securesms.backup.BackupProtos.Avatar avatar_ = org.thoughtcrime.securesms.backup.BackupProtos.Avatar.getDefaultInstance();
- private com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.Avatar, org.thoughtcrime.securesms.backup.BackupProtos.Avatar.Builder, org.thoughtcrime.securesms.backup.BackupProtos.AvatarOrBuilder> avatarBuilder_;
- /**
- * optional .signal.Avatar avatar = 7;
- */
- public boolean hasAvatar() {
- return ((bitField0_ & 0x00000040) == 0x00000040);
- }
- /**
- * optional .signal.Avatar avatar = 7;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.Avatar getAvatar() {
- if (avatarBuilder_ == null) {
- return avatar_;
- } else {
- return avatarBuilder_.getMessage();
- }
- }
- /**
- * optional .signal.Avatar avatar = 7;
- */
- public Builder setAvatar(org.thoughtcrime.securesms.backup.BackupProtos.Avatar value) {
- if (avatarBuilder_ == null) {
- if (value == null) {
- throw new NullPointerException();
- }
- avatar_ = value;
- onChanged();
- } else {
- avatarBuilder_.setMessage(value);
- }
- bitField0_ |= 0x00000040;
- return this;
- }
- /**
- * optional .signal.Avatar avatar = 7;
- */
- public Builder setAvatar(
- org.thoughtcrime.securesms.backup.BackupProtos.Avatar.Builder builderForValue) {
- if (avatarBuilder_ == null) {
- avatar_ = builderForValue.build();
- onChanged();
- } else {
- avatarBuilder_.setMessage(builderForValue.build());
- }
- bitField0_ |= 0x00000040;
- return this;
- }
- /**
- * optional .signal.Avatar avatar = 7;
- */
- public Builder mergeAvatar(org.thoughtcrime.securesms.backup.BackupProtos.Avatar value) {
- if (avatarBuilder_ == null) {
- if (((bitField0_ & 0x00000040) == 0x00000040) &&
- avatar_ != org.thoughtcrime.securesms.backup.BackupProtos.Avatar.getDefaultInstance()) {
- avatar_ =
- org.thoughtcrime.securesms.backup.BackupProtos.Avatar.newBuilder(avatar_).mergeFrom(value).buildPartial();
- } else {
- avatar_ = value;
- }
- onChanged();
- } else {
- avatarBuilder_.mergeFrom(value);
- }
- bitField0_ |= 0x00000040;
- return this;
- }
- /**
- * optional .signal.Avatar avatar = 7;
- */
- public Builder clearAvatar() {
- if (avatarBuilder_ == null) {
- avatar_ = org.thoughtcrime.securesms.backup.BackupProtos.Avatar.getDefaultInstance();
- onChanged();
- } else {
- avatarBuilder_.clear();
- }
- bitField0_ = (bitField0_ & ~0x00000040);
- return this;
- }
- /**
- * optional .signal.Avatar avatar = 7;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.Avatar.Builder getAvatarBuilder() {
- bitField0_ |= 0x00000040;
- onChanged();
- return getAvatarFieldBuilder().getBuilder();
- }
- /**
- * optional .signal.Avatar avatar = 7;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.AvatarOrBuilder getAvatarOrBuilder() {
- if (avatarBuilder_ != null) {
- return avatarBuilder_.getMessageOrBuilder();
- } else {
- return avatar_;
- }
- }
- /**
- * optional .signal.Avatar avatar = 7;
- */
- private com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.Avatar, org.thoughtcrime.securesms.backup.BackupProtos.Avatar.Builder, org.thoughtcrime.securesms.backup.BackupProtos.AvatarOrBuilder>
- getAvatarFieldBuilder() {
- if (avatarBuilder_ == null) {
- avatarBuilder_ = new com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.Avatar, org.thoughtcrime.securesms.backup.BackupProtos.Avatar.Builder, org.thoughtcrime.securesms.backup.BackupProtos.AvatarOrBuilder>(
- avatar_,
- getParentForChildren(),
- isClean());
- avatar_ = null;
- }
- return avatarBuilder_;
- }
-
- // optional .signal.Sticker sticker = 8;
- private org.thoughtcrime.securesms.backup.BackupProtos.Sticker sticker_ = org.thoughtcrime.securesms.backup.BackupProtos.Sticker.getDefaultInstance();
- private com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.Sticker, org.thoughtcrime.securesms.backup.BackupProtos.Sticker.Builder, org.thoughtcrime.securesms.backup.BackupProtos.StickerOrBuilder> stickerBuilder_;
- /**
- * optional .signal.Sticker sticker = 8;
- */
- public boolean hasSticker() {
- return ((bitField0_ & 0x00000080) == 0x00000080);
- }
- /**
- * optional .signal.Sticker sticker = 8;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.Sticker getSticker() {
- if (stickerBuilder_ == null) {
- return sticker_;
- } else {
- return stickerBuilder_.getMessage();
- }
- }
- /**
- * optional .signal.Sticker sticker = 8;
- */
- public Builder setSticker(org.thoughtcrime.securesms.backup.BackupProtos.Sticker value) {
- if (stickerBuilder_ == null) {
- if (value == null) {
- throw new NullPointerException();
- }
- sticker_ = value;
- onChanged();
- } else {
- stickerBuilder_.setMessage(value);
- }
- bitField0_ |= 0x00000080;
- return this;
- }
- /**
- * optional .signal.Sticker sticker = 8;
- */
- public Builder setSticker(
- org.thoughtcrime.securesms.backup.BackupProtos.Sticker.Builder builderForValue) {
- if (stickerBuilder_ == null) {
- sticker_ = builderForValue.build();
- onChanged();
- } else {
- stickerBuilder_.setMessage(builderForValue.build());
- }
- bitField0_ |= 0x00000080;
- return this;
- }
- /**
- * optional .signal.Sticker sticker = 8;
- */
- public Builder mergeSticker(org.thoughtcrime.securesms.backup.BackupProtos.Sticker value) {
- if (stickerBuilder_ == null) {
- if (((bitField0_ & 0x00000080) == 0x00000080) &&
- sticker_ != org.thoughtcrime.securesms.backup.BackupProtos.Sticker.getDefaultInstance()) {
- sticker_ =
- org.thoughtcrime.securesms.backup.BackupProtos.Sticker.newBuilder(sticker_).mergeFrom(value).buildPartial();
- } else {
- sticker_ = value;
- }
- onChanged();
- } else {
- stickerBuilder_.mergeFrom(value);
- }
- bitField0_ |= 0x00000080;
- return this;
- }
- /**
- * optional .signal.Sticker sticker = 8;
- */
- public Builder clearSticker() {
- if (stickerBuilder_ == null) {
- sticker_ = org.thoughtcrime.securesms.backup.BackupProtos.Sticker.getDefaultInstance();
- onChanged();
- } else {
- stickerBuilder_.clear();
- }
- bitField0_ = (bitField0_ & ~0x00000080);
- return this;
- }
- /**
- * optional .signal.Sticker sticker = 8;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.Sticker.Builder getStickerBuilder() {
- bitField0_ |= 0x00000080;
- onChanged();
- return getStickerFieldBuilder().getBuilder();
- }
- /**
- * optional .signal.Sticker sticker = 8;
- */
- public org.thoughtcrime.securesms.backup.BackupProtos.StickerOrBuilder getStickerOrBuilder() {
- if (stickerBuilder_ != null) {
- return stickerBuilder_.getMessageOrBuilder();
- } else {
- return sticker_;
- }
- }
- /**
- * optional .signal.Sticker sticker = 8;
- */
- private com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.Sticker, org.thoughtcrime.securesms.backup.BackupProtos.Sticker.Builder, org.thoughtcrime.securesms.backup.BackupProtos.StickerOrBuilder>
- getStickerFieldBuilder() {
- if (stickerBuilder_ == null) {
- stickerBuilder_ = new com.google.protobuf.SingleFieldBuilder<
- org.thoughtcrime.securesms.backup.BackupProtos.Sticker, org.thoughtcrime.securesms.backup.BackupProtos.Sticker.Builder, org.thoughtcrime.securesms.backup.BackupProtos.StickerOrBuilder>(
- sticker_,
- getParentForChildren(),
- isClean());
- sticker_ = null;
- }
- return stickerBuilder_;
- }
-
- // @@protoc_insertion_point(builder_scope:signal.BackupFrame)
- }
-
- static {
- defaultInstance = new BackupFrame(true);
- defaultInstance.initFields();
- }
-
- // @@protoc_insertion_point(class_scope:signal.BackupFrame)
- }
-
- private static com.google.protobuf.Descriptors.Descriptor
- internal_static_signal_SqlStatement_descriptor;
- private static
- com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internal_static_signal_SqlStatement_fieldAccessorTable;
- private static com.google.protobuf.Descriptors.Descriptor
- internal_static_signal_SqlStatement_SqlParameter_descriptor;
- private static
- com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internal_static_signal_SqlStatement_SqlParameter_fieldAccessorTable;
- private static com.google.protobuf.Descriptors.Descriptor
- internal_static_signal_SharedPreference_descriptor;
- private static
- com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internal_static_signal_SharedPreference_fieldAccessorTable;
- private static com.google.protobuf.Descriptors.Descriptor
- internal_static_signal_Attachment_descriptor;
- private static
- com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internal_static_signal_Attachment_fieldAccessorTable;
- private static com.google.protobuf.Descriptors.Descriptor
- internal_static_signal_Sticker_descriptor;
- private static
- com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internal_static_signal_Sticker_fieldAccessorTable;
- private static com.google.protobuf.Descriptors.Descriptor
- internal_static_signal_Avatar_descriptor;
- private static
- com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internal_static_signal_Avatar_fieldAccessorTable;
- private static com.google.protobuf.Descriptors.Descriptor
- internal_static_signal_DatabaseVersion_descriptor;
- private static
- com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internal_static_signal_DatabaseVersion_fieldAccessorTable;
- private static com.google.protobuf.Descriptors.Descriptor
- internal_static_signal_Header_descriptor;
- private static
- com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internal_static_signal_Header_fieldAccessorTable;
- private static com.google.protobuf.Descriptors.Descriptor
- internal_static_signal_BackupFrame_descriptor;
- private static
- com.google.protobuf.GeneratedMessage.FieldAccessorTable
- internal_static_signal_BackupFrame_fieldAccessorTable;
-
- public static com.google.protobuf.Descriptors.FileDescriptor
- getDescriptor() {
- return descriptor;
- }
- private static com.google.protobuf.Descriptors.FileDescriptor
- descriptor;
- static {
- java.lang.String[] descriptorData = {
- "\n\rBackups.proto\022\006signal\"\342\001\n\014SqlStatement" +
- "\022\021\n\tstatement\030\001 \001(\t\0225\n\nparameters\030\002 \003(\0132" +
- "!.signal.SqlStatement.SqlParameter\032\207\001\n\014S" +
- "qlParameter\022\026\n\016stringParamter\030\001 \001(\t\022\030\n\020i" +
- "ntegerParameter\030\002 \001(\004\022\027\n\017doubleParameter" +
- "\030\003 \001(\001\022\025\n\rblobParameter\030\004 \001(\014\022\025\n\rnullpar" +
- "ameter\030\005 \001(\010\"<\n\020SharedPreference\022\014\n\004file" +
- "\030\001 \001(\t\022\013\n\003key\030\002 \001(\t\022\r\n\005value\030\003 \001(\t\"A\n\nAt" +
- "tachment\022\r\n\005rowId\030\001 \001(\004\022\024\n\014attachmentId\030" +
- "\002 \001(\004\022\016\n\006length\030\003 \001(\r\"(\n\007Sticker\022\r\n\005rowI",
- "d\030\001 \001(\004\022\016\n\006length\030\002 \001(\r\"&\n\006Avatar\022\014\n\004nam" +
- "e\030\001 \001(\t\022\016\n\006length\030\002 \001(\r\"\"\n\017DatabaseVersi" +
- "on\022\017\n\007version\030\001 \001(\r\"\"\n\006Header\022\n\n\002iv\030\001 \001(" +
- "\014\022\014\n\004salt\030\002 \001(\014\"\245\002\n\013BackupFrame\022\036\n\006heade" +
- "r\030\001 \001(\0132\016.signal.Header\022\'\n\tstatement\030\002 \001" +
- "(\0132\024.signal.SqlStatement\022,\n\npreference\030\003" +
- " \001(\0132\030.signal.SharedPreference\022&\n\nattach" +
- "ment\030\004 \001(\0132\022.signal.Attachment\022(\n\007versio" +
- "n\030\005 \001(\0132\027.signal.DatabaseVersion\022\013\n\003end\030" +
- "\006 \001(\010\022\036\n\006avatar\030\007 \001(\0132\016.signal.Avatar\022 \n",
- "\007sticker\030\010 \001(\0132\017.signal.StickerB1\n!org.t" +
- "houghtcrime.securesms.backupB\014BackupProt" +
- "os"
- };
- com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
- new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
- public com.google.protobuf.ExtensionRegistry assignDescriptors(
- com.google.protobuf.Descriptors.FileDescriptor root) {
- descriptor = root;
- internal_static_signal_SqlStatement_descriptor =
- getDescriptor().getMessageTypes().get(0);
- internal_static_signal_SqlStatement_fieldAccessorTable = new
- com.google.protobuf.GeneratedMessage.FieldAccessorTable(
- internal_static_signal_SqlStatement_descriptor,
- new java.lang.String[] { "Statement", "Parameters", });
- internal_static_signal_SqlStatement_SqlParameter_descriptor =
- internal_static_signal_SqlStatement_descriptor.getNestedTypes().get(0);
- internal_static_signal_SqlStatement_SqlParameter_fieldAccessorTable = new
- com.google.protobuf.GeneratedMessage.FieldAccessorTable(
- internal_static_signal_SqlStatement_SqlParameter_descriptor,
- new java.lang.String[] { "StringParamter", "IntegerParameter", "DoubleParameter", "BlobParameter", "Nullparameter", });
- internal_static_signal_SharedPreference_descriptor =
- getDescriptor().getMessageTypes().get(1);
- internal_static_signal_SharedPreference_fieldAccessorTable = new
- com.google.protobuf.GeneratedMessage.FieldAccessorTable(
- internal_static_signal_SharedPreference_descriptor,
- new java.lang.String[] { "File", "Key", "Value", });
- internal_static_signal_Attachment_descriptor =
- getDescriptor().getMessageTypes().get(2);
- internal_static_signal_Attachment_fieldAccessorTable = new
- com.google.protobuf.GeneratedMessage.FieldAccessorTable(
- internal_static_signal_Attachment_descriptor,
- new java.lang.String[] { "RowId", "AttachmentId", "Length", });
- internal_static_signal_Sticker_descriptor =
- getDescriptor().getMessageTypes().get(3);
- internal_static_signal_Sticker_fieldAccessorTable = new
- com.google.protobuf.GeneratedMessage.FieldAccessorTable(
- internal_static_signal_Sticker_descriptor,
- new java.lang.String[] { "RowId", "Length", });
- internal_static_signal_Avatar_descriptor =
- getDescriptor().getMessageTypes().get(4);
- internal_static_signal_Avatar_fieldAccessorTable = new
- com.google.protobuf.GeneratedMessage.FieldAccessorTable(
- internal_static_signal_Avatar_descriptor,
- new java.lang.String[] { "Name", "Length", });
- internal_static_signal_DatabaseVersion_descriptor =
- getDescriptor().getMessageTypes().get(5);
- internal_static_signal_DatabaseVersion_fieldAccessorTable = new
- com.google.protobuf.GeneratedMessage.FieldAccessorTable(
- internal_static_signal_DatabaseVersion_descriptor,
- new java.lang.String[] { "Version", });
- internal_static_signal_Header_descriptor =
- getDescriptor().getMessageTypes().get(6);
- internal_static_signal_Header_fieldAccessorTable = new
- com.google.protobuf.GeneratedMessage.FieldAccessorTable(
- internal_static_signal_Header_descriptor,
- new java.lang.String[] { "Iv", "Salt", });
- internal_static_signal_BackupFrame_descriptor =
- getDescriptor().getMessageTypes().get(7);
- internal_static_signal_BackupFrame_fieldAccessorTable = new
- com.google.protobuf.GeneratedMessage.FieldAccessorTable(
- internal_static_signal_BackupFrame_descriptor,
- new java.lang.String[] { "Header", "Statement", "Preference", "Attachment", "Version", "End", "Avatar", "Sticker", });
- return null;
- }
- };
- com.google.protobuf.Descriptors.FileDescriptor
- .internalBuildGeneratedFileFrom(descriptorData,
- new com.google.protobuf.Descriptors.FileDescriptor[] {
- }, assigner);
- }
-
- // @@protoc_insertion_point(outer_class_scope)
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt
deleted file mode 100644
index 6b5d47a2e..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt
+++ /dev/null
@@ -1,447 +0,0 @@
-package org.thoughtcrime.securesms.backup
-
-import android.content.Context
-import android.database.Cursor
-import android.net.Uri
-import android.text.TextUtils
-import androidx.annotation.WorkerThread
-import com.annimon.stream.function.Consumer
-import com.annimon.stream.function.Predicate
-import com.google.protobuf.ByteString
-import net.zetetic.database.sqlcipher.SQLiteDatabase
-import org.greenrobot.eventbus.EventBus
-import org.session.libsession.avatars.AvatarHelper
-import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
-import org.session.libsession.utilities.Conversions
-import org.session.libsession.utilities.Util
-import org.session.libsignal.crypto.kdf.HKDFv3
-import org.session.libsignal.utilities.ByteUtil
-import org.session.libsignal.utilities.Log
-import org.thoughtcrime.securesms.backup.BackupProtos.Attachment
-import org.thoughtcrime.securesms.backup.BackupProtos.Avatar
-import org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame
-import org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion
-import org.thoughtcrime.securesms.backup.BackupProtos.Header
-import org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference
-import org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement
-import org.thoughtcrime.securesms.backup.BackupProtos.Sticker
-import org.thoughtcrime.securesms.crypto.AttachmentSecret
-import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream
-import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream
-import org.thoughtcrime.securesms.database.AttachmentDatabase
-import org.thoughtcrime.securesms.database.GroupReceiptDatabase
-import org.thoughtcrime.securesms.database.JobDatabase
-import org.thoughtcrime.securesms.database.LokiAPIDatabase
-import org.thoughtcrime.securesms.database.LokiBackupFilesDatabase
-import org.thoughtcrime.securesms.database.MmsDatabase
-import org.thoughtcrime.securesms.database.MmsSmsColumns
-import org.thoughtcrime.securesms.database.PushDatabase
-import org.thoughtcrime.securesms.database.SearchDatabase
-import org.thoughtcrime.securesms.database.SmsDatabase
-import org.thoughtcrime.securesms.util.BackupUtil
-import java.io.Closeable
-import java.io.File
-import java.io.FileInputStream
-import java.io.Flushable
-import java.io.IOException
-import java.io.InputStream
-import java.io.OutputStream
-import java.security.InvalidAlgorithmParameterException
-import java.security.InvalidKeyException
-import java.security.NoSuchAlgorithmException
-import java.util.LinkedList
-import javax.crypto.BadPaddingException
-import javax.crypto.Cipher
-import javax.crypto.IllegalBlockSizeException
-import javax.crypto.Mac
-import javax.crypto.NoSuchPaddingException
-import javax.crypto.spec.IvParameterSpec
-import javax.crypto.spec.SecretKeySpec
-
-object FullBackupExporter {
- private val TAG = FullBackupExporter::class.java.simpleName
-
- @JvmStatic
- @WorkerThread
- @Throws(IOException::class)
- fun export(context: Context,
- attachmentSecret: AttachmentSecret,
- input: SQLiteDatabase,
- fileUri: Uri,
- passphrase: String) {
-
- val baseOutputStream = context.contentResolver.openOutputStream(fileUri)
- ?: throw IOException("Cannot open an output stream for the file URI: $fileUri")
-
- var count = 0
- try {
- BackupFrameOutputStream(baseOutputStream, passphrase).use { outputStream ->
- outputStream.writeDatabaseVersion(input.version)
- val tables = exportSchema(input, outputStream)
- for (table in tables) if (shouldExportTable(table)) {
- count = when (table) {
- SmsDatabase.TABLE_NAME, MmsDatabase.TABLE_NAME -> {
- exportTable(table, input, outputStream,
- { cursor: Cursor ->
- cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsColumns.EXPIRES_IN)) <= 0
- },
- null,
- count)
- }
- GroupReceiptDatabase.TABLE_NAME -> {
- exportTable(table, input, outputStream,
- { cursor: Cursor ->
- isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(GroupReceiptDatabase.MMS_ID)))
- },
- null,
- count)
- }
- AttachmentDatabase.TABLE_NAME -> {
- exportTable(table, input, outputStream,
- { cursor: Cursor ->
- isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.MMS_ID)))
- },
- { cursor: Cursor ->
- exportAttachment(attachmentSecret, cursor, outputStream)
- },
- count)
- }
- else -> {
- exportTable(table, input, outputStream, null, null, count)
- }
- }
- }
- for (preference in BackupUtil.getBackupRecords(context)) {
- EventBus.getDefault().post(BackupEvent.createProgress(++count))
- outputStream.writePreferenceEntry(preference)
- }
- for (preference in BackupPreferences.getBackupRecords(context)) {
- EventBus.getDefault().post(BackupEvent.createProgress(++count))
- outputStream.writePreferenceEntry(preference)
- }
- for (avatar in AvatarHelper.getAvatarFiles(context)) {
- EventBus.getDefault().post(BackupEvent.createProgress(++count))
- outputStream.writeAvatar(avatar.name, FileInputStream(avatar), avatar.length())
- }
- outputStream.writeEnd()
- }
- EventBus.getDefault().post(BackupEvent.createFinished())
- } catch (e: Exception) {
- Log.e(TAG, "Failed to make full backup.", e)
- EventBus.getDefault().post(BackupEvent.createFinished(e))
- throw e
- }
- }
-
- private inline fun shouldExportTable(table: String): Boolean {
- return table != PushDatabase.TABLE_NAME &&
-
- table != LokiBackupFilesDatabase.TABLE_NAME &&
- table != LokiAPIDatabase.openGroupProfilePictureTable &&
-
- table != JobDatabase.Jobs.TABLE_NAME &&
- table != JobDatabase.Constraints.TABLE_NAME &&
- table != JobDatabase.Dependencies.TABLE_NAME &&
-
- !table.startsWith(SearchDatabase.SMS_FTS_TABLE_NAME) &&
- !table.startsWith(SearchDatabase.MMS_FTS_TABLE_NAME) &&
- !table.startsWith("sqlite_")
- }
-
- @Throws(IOException::class)
- private fun exportSchema(input: SQLiteDatabase, outputStream: BackupFrameOutputStream): List {
- val tables: MutableList = LinkedList()
- input.rawQuery("SELECT sql, name, type FROM sqlite_master", null).use { cursor ->
- while (cursor != null && cursor.moveToNext()) {
- val sql = cursor.getString(0)
- val name = cursor.getString(1)
- val type = cursor.getString(2)
- if (sql != null) {
- val isSmsFtsSecretTable = name != null && name != SearchDatabase.SMS_FTS_TABLE_NAME && name.startsWith(SearchDatabase.SMS_FTS_TABLE_NAME)
- val isMmsFtsSecretTable = name != null && name != SearchDatabase.MMS_FTS_TABLE_NAME && name.startsWith(SearchDatabase.MMS_FTS_TABLE_NAME)
- if (!isSmsFtsSecretTable && !isMmsFtsSecretTable) {
- if ("table" == type) {
- tables.add(name)
- }
- outputStream.writeSql(SqlStatement.newBuilder().setStatement(cursor.getString(0)).build())
- }
- }
- }
- }
- return tables
- }
-
- @Throws(IOException::class)
- private fun exportTable(table: String,
- input: SQLiteDatabase,
- outputStream: BackupFrameOutputStream,
- predicate: Predicate?,
- postProcess: Consumer?,
- count: Int): Int {
- var count = count
- val template = "INSERT INTO $table VALUES "
- input.rawQuery("SELECT * FROM $table", null).use { cursor ->
- while (cursor != null && cursor.moveToNext()) {
- EventBus.getDefault().post(BackupEvent.createProgress(++count))
- if (predicate != null && !predicate.test(cursor)) continue
-
- val statement = StringBuilder(template)
- val statementBuilder = SqlStatement.newBuilder()
- statement.append('(')
- for (i in 0 until cursor.columnCount) {
- statement.append('?')
- when (cursor.getType(i)) {
- Cursor.FIELD_TYPE_STRING -> {
- statementBuilder.addParameters(SqlStatement.SqlParameter.newBuilder()
- .setStringParamter(cursor.getString(i)))
- }
- Cursor.FIELD_TYPE_FLOAT -> {
- statementBuilder.addParameters(SqlStatement.SqlParameter.newBuilder()
- .setDoubleParameter(cursor.getDouble(i)))
- }
- Cursor.FIELD_TYPE_INTEGER -> {
- statementBuilder.addParameters(SqlStatement.SqlParameter.newBuilder()
- .setIntegerParameter(cursor.getLong(i)))
- }
- Cursor.FIELD_TYPE_BLOB -> {
- statementBuilder.addParameters(SqlStatement.SqlParameter.newBuilder()
- .setBlobParameter(ByteString.copyFrom(cursor.getBlob(i))))
- }
- Cursor.FIELD_TYPE_NULL -> {
- statementBuilder.addParameters(SqlStatement.SqlParameter.newBuilder()
- .setNullparameter(true))
- }
- else -> {
- throw AssertionError("unknown type?" + cursor.getType(i))
- }
- }
- if (i < cursor.columnCount - 1) {
- statement.append(',')
- }
- }
- statement.append(')')
- outputStream.writeSql(statementBuilder.setStatement(statement.toString()).build())
- postProcess?.accept(cursor)
- }
- }
- return count
- }
-
- private fun exportAttachment(attachmentSecret: AttachmentSecret, cursor: Cursor, outputStream: BackupFrameOutputStream) {
- try {
- val rowId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.ROW_ID))
- val uniqueId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.UNIQUE_ID))
- var size = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.SIZE))
- val data = cursor.getString(cursor.getColumnIndexOrThrow(AttachmentDatabase.DATA))
- val random = cursor.getBlob(cursor.getColumnIndexOrThrow(AttachmentDatabase.DATA_RANDOM))
- if (!TextUtils.isEmpty(data) && size <= 0) {
- size = calculateVeryOldStreamLength(attachmentSecret, random, data)
- }
- if (!TextUtils.isEmpty(data) && size > 0) {
- val inputStream: InputStream = if (random != null && random.size == 32) {
- ModernDecryptingPartInputStream.createFor(attachmentSecret, random, File(data), 0)
- } else {
- ClassicDecryptingPartInputStream.createFor(attachmentSecret, File(data))
- }
- outputStream.writeAttachment(AttachmentId(rowId, uniqueId), inputStream, size)
- }
- } catch (e: IOException) {
- Log.w(TAG, e)
- }
- }
-
- @Throws(IOException::class)
- private fun calculateVeryOldStreamLength(attachmentSecret: AttachmentSecret, random: ByteArray?, data: String): Long {
- var result: Long = 0
- val inputStream: InputStream = if (random != null && random.size == 32) {
- ModernDecryptingPartInputStream.createFor(attachmentSecret, random, File(data), 0)
- } else {
- ClassicDecryptingPartInputStream.createFor(attachmentSecret, File(data))
- }
- var read: Int
- val buffer = ByteArray(8192)
- while (inputStream.read(buffer, 0, buffer.size).also { read = it } != -1) {
- result += read.toLong()
- }
- return result
- }
-
- private fun isForNonExpiringMessage(db: SQLiteDatabase, mmsId: Long): Boolean {
- val columns = arrayOf(MmsSmsColumns.EXPIRES_IN)
- val where = MmsSmsColumns.ID + " = ?"
- val args = arrayOf(mmsId.toString())
- db.query(MmsDatabase.TABLE_NAME, columns, where, args, null, null, null).use { mmsCursor ->
- if (mmsCursor != null && mmsCursor.moveToFirst()) {
- return mmsCursor.getLong(0) == 0L
- }
- }
- return false
- }
-
- private class BackupFrameOutputStream : Closeable, Flushable {
-
- private val outputStream: OutputStream
- private var cipher: Cipher
- private var mac: Mac
- private val cipherKey: ByteArray
- private val macKey: ByteArray
- private val iv: ByteArray
-
- private var counter: Int = 0
-
- constructor(outputStream: OutputStream, passphrase: String) : super() {
- try {
- val salt = Util.getSecretBytes(32)
- val key = BackupUtil.computeBackupKey(passphrase, salt)
- val derived = HKDFv3().deriveSecrets(key, "Backup Export".toByteArray(), 64)
- val split = ByteUtil.split(derived, 32, 32)
- cipherKey = split[0]
- macKey = split[1]
- cipher = Cipher.getInstance("AES/CTR/NoPadding")
- mac = Mac.getInstance("HmacSHA256")
- this.outputStream = outputStream
- iv = Util.getSecretBytes(16)
- counter = Conversions.byteArrayToInt(iv)
- mac.init(SecretKeySpec(macKey, "HmacSHA256"))
- val header = BackupFrame.newBuilder().setHeader(Header.newBuilder()
- .setIv(ByteString.copyFrom(iv))
- .setSalt(ByteString.copyFrom(salt)))
- .build().toByteArray()
- outputStream.write(Conversions.intToByteArray(header.size))
- outputStream.write(header)
- } catch (e: Exception) {
- when (e) {
- is NoSuchAlgorithmException,
- is NoSuchPaddingException,
- is InvalidKeyException -> {
- throw AssertionError(e)
- }
- else -> throw e
- }
- }
- }
-
- @Throws(IOException::class)
- fun writeSql(statement: SqlStatement) {
- write(outputStream, BackupFrame.newBuilder().setStatement(statement).build())
- }
-
- @Throws(IOException::class)
- fun writePreferenceEntry(preference: SharedPreference?) {
- write(outputStream, BackupFrame.newBuilder().setPreference(preference).build())
- }
-
- @Throws(IOException::class)
- fun writeAvatar(avatarName: String, inputStream: InputStream, size: Long) {
- write(outputStream, BackupFrame.newBuilder()
- .setAvatar(Avatar.newBuilder()
- .setName(avatarName)
- .setLength(Util.toIntExact(size))
- .build())
- .build())
- writeStream(inputStream)
- }
-
- @Throws(IOException::class)
- fun writeAttachment(attachmentId: AttachmentId, inputStream: InputStream, size: Long) {
- write(outputStream, BackupFrame.newBuilder()
- .setAttachment(Attachment.newBuilder()
- .setRowId(attachmentId.rowId)
- .setAttachmentId(attachmentId.uniqueId)
- .setLength(Util.toIntExact(size))
- .build())
- .build())
- writeStream(inputStream)
- }
-
- @Throws(IOException::class)
- fun writeSticker(rowId: Long, inputStream: InputStream, size: Long) {
- write(outputStream, BackupFrame.newBuilder()
- .setSticker(Sticker.newBuilder()
- .setRowId(rowId)
- .setLength(Util.toIntExact(size))
- .build())
- .build())
- writeStream(inputStream)
- }
-
- @Throws(IOException::class)
- fun writeDatabaseVersion(version: Int) {
- write(outputStream, BackupFrame.newBuilder()
- .setVersion(DatabaseVersion.newBuilder().setVersion(version))
- .build())
- }
-
- @Throws(IOException::class)
- fun writeEnd() {
- write(outputStream, BackupFrame.newBuilder().setEnd(true).build())
- }
-
- @Throws(IOException::class)
- private fun writeStream(inputStream: InputStream) {
- try {
- Conversions.intToByteArray(iv, 0, counter++)
- cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(cipherKey, "AES"), IvParameterSpec(iv))
- mac.update(iv)
- val buffer = ByteArray(8192)
- var read: Int
- while (inputStream.read(buffer).also { read = it } != -1) {
- val ciphertext = cipher.update(buffer, 0, read)
- if (ciphertext != null) {
- outputStream.write(ciphertext)
- mac.update(ciphertext)
- }
- }
- val remainder = cipher.doFinal()
- outputStream.write(remainder)
- mac.update(remainder)
- val attachmentDigest = mac.doFinal()
- outputStream.write(attachmentDigest, 0, 10)
- } catch (e: Exception) {
- when (e) {
- is InvalidKeyException,
- is InvalidAlgorithmParameterException,
- is IllegalBlockSizeException,
- is BadPaddingException -> {
- throw AssertionError(e)
- }
- else -> throw e
- }
- }
- }
-
- @Throws(IOException::class)
- private fun write(out: OutputStream, frame: BackupFrame) {
- try {
- Conversions.intToByteArray(iv, 0, counter++)
- cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(cipherKey, "AES"), IvParameterSpec(iv))
- val frameCiphertext = cipher.doFinal(frame.toByteArray())
- val frameMac = mac.doFinal(frameCiphertext)
- val length = Conversions.intToByteArray(frameCiphertext.size + 10)
- out.write(length)
- out.write(frameCiphertext)
- out.write(frameMac, 0, 10)
- } catch (e: Exception) {
- when (e) {
- is InvalidKeyException,
- is InvalidAlgorithmParameterException,
- is IllegalBlockSizeException,
- is BadPaddingException -> {
- throw AssertionError(e)
- }
- else -> throw e
- }
- }
- }
-
- @Throws(IOException::class)
- override fun flush() {
- outputStream.flush()
- }
-
- @Throws(IOException::class)
- override fun close() {
- outputStream.close()
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt
deleted file mode 100644
index b40c049bc..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt
+++ /dev/null
@@ -1,352 +0,0 @@
-package org.thoughtcrime.securesms.backup
-
-import android.annotation.SuppressLint
-import android.content.ContentValues
-import android.content.Context
-import android.net.Uri
-import androidx.annotation.WorkerThread
-import net.zetetic.database.sqlcipher.SQLiteDatabase
-import org.greenrobot.eventbus.EventBus
-import org.session.libsession.avatars.AvatarHelper
-import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
-import org.session.libsession.utilities.Address
-import org.session.libsession.utilities.Conversions
-import org.session.libsession.utilities.Util
-import org.session.libsignal.crypto.kdf.HKDFv3
-import org.session.libsignal.utilities.ByteUtil
-import org.session.libsignal.utilities.Log
-import org.thoughtcrime.securesms.backup.BackupProtos.Attachment
-import org.thoughtcrime.securesms.backup.BackupProtos.Avatar
-import org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame
-import org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion
-import org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference
-import org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement
-import org.thoughtcrime.securesms.crypto.AttachmentSecret
-import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream
-import org.thoughtcrime.securesms.database.AttachmentDatabase
-import org.thoughtcrime.securesms.database.GroupReceiptDatabase
-import org.thoughtcrime.securesms.database.MmsDatabase
-import org.thoughtcrime.securesms.database.MmsSmsColumns
-import org.thoughtcrime.securesms.database.SearchDatabase
-import org.thoughtcrime.securesms.database.ThreadDatabase
-import org.thoughtcrime.securesms.dependencies.DatabaseComponent
-import org.thoughtcrime.securesms.util.BackupUtil
-import java.io.Closeable
-import java.io.File
-import java.io.FileOutputStream
-import java.io.IOException
-import java.io.InputStream
-import java.io.OutputStream
-import java.security.InvalidAlgorithmParameterException
-import java.security.InvalidKeyException
-import java.security.MessageDigest
-import java.security.NoSuchAlgorithmException
-import java.util.LinkedList
-import java.util.Locale
-import javax.crypto.BadPaddingException
-import javax.crypto.Cipher
-import javax.crypto.IllegalBlockSizeException
-import javax.crypto.Mac
-import javax.crypto.NoSuchPaddingException
-import javax.crypto.spec.IvParameterSpec
-import javax.crypto.spec.SecretKeySpec
-
-object FullBackupImporter {
- /**
- * Because BackupProtos.SharedPreference was made only to serialize string values,
- * we use these 3-char prefixes to explicitly cast the values before inserting to a preference file.
- */
- const val PREF_PREFIX_TYPE_INT = "i__"
- const val PREF_PREFIX_TYPE_BOOLEAN = "b__"
-
- private val TAG = FullBackupImporter::class.java.simpleName
-
- @JvmStatic
- @WorkerThread
- @Throws(IOException::class)
- fun importFromUri(context: Context,
- attachmentSecret: AttachmentSecret,
- db: SQLiteDatabase,
- fileUri: Uri,
- passphrase: String) {
-
- val baseInputStream = context.contentResolver.openInputStream(fileUri)
- ?: throw IOException("Cannot open an input stream for the file URI: $fileUri")
-
- var count = 0
- try {
- BackupRecordInputStream(baseInputStream, passphrase).use { inputStream ->
- db.beginTransaction()
- dropAllTables(db)
- var frame: BackupFrame
- while (!inputStream.readFrame().also { frame = it }.end) {
- if (count++ % 100 == 0) EventBus.getDefault().post(BackupEvent.createProgress(count))
- when {
- frame.hasVersion() -> processVersion(db, frame.version)
- frame.hasStatement() -> processStatement(db, frame.statement)
- frame.hasPreference() -> processPreference(context, frame.preference)
- frame.hasAttachment() -> processAttachment(context, attachmentSecret, db, frame.attachment, inputStream)
- frame.hasAvatar() -> processAvatar(context, frame.avatar, inputStream)
- }
- }
- trimEntriesForExpiredMessages(context, db)
- db.setTransactionSuccessful()
- }
- } finally {
- if (db.inTransaction()) {
- db.endTransaction()
- }
- }
- EventBus.getDefault().post(BackupEvent.createFinished())
- }
-
- @Throws(IOException::class)
- private fun processVersion(db: SQLiteDatabase, version: DatabaseVersion) {
- if (version.version > db.version) {
- throw DatabaseDowngradeException(db.version, version.version)
- }
- db.version = version.version
- }
-
- private fun processStatement(db: SQLiteDatabase, statement: SqlStatement) {
- val isForSmsFtsSecretTable = statement.statement.contains(SearchDatabase.SMS_FTS_TABLE_NAME + "_")
- val isForMmsFtsSecretTable = statement.statement.contains(SearchDatabase.MMS_FTS_TABLE_NAME + "_")
- val isForSqliteSecretTable = statement.statement.toLowerCase(Locale.ENGLISH).startsWith("create table sqlite_")
- if (isForSmsFtsSecretTable || isForMmsFtsSecretTable || isForSqliteSecretTable) {
- Log.i(TAG, "Ignoring import for statement: " + statement.statement)
- return
- }
- val parameters: MutableList = LinkedList()
- for (parameter in statement.parametersList) {
- when {
- parameter.hasStringParamter() -> parameters.add(parameter.stringParamter)
- parameter.hasDoubleParameter() -> parameters.add(parameter.doubleParameter)
- parameter.hasIntegerParameter() -> parameters.add(parameter.integerParameter)
- parameter.hasBlobParameter() -> parameters.add(parameter.blobParameter.toByteArray())
- parameter.hasNullparameter() -> parameters.add(null)
- }
- }
- if (parameters.size > 0) {
- db.execSQL(statement.statement, parameters.toTypedArray())
- } else {
- db.execSQL(statement.statement)
- }
- }
-
- @Throws(IOException::class)
- private fun processAttachment(context: Context, attachmentSecret: AttachmentSecret,
- db: SQLiteDatabase, attachment: Attachment,
- inputStream: BackupRecordInputStream) {
- val partsDirectory = context.getDir(AttachmentDatabase.DIRECTORY, Context.MODE_PRIVATE)
- val dataFile = File.createTempFile("part", ".mms", partsDirectory)
- val output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false)
- inputStream.readAttachmentTo(output.second, attachment.length)
- val contentValues = ContentValues()
- contentValues.put(AttachmentDatabase.DATA, dataFile.absolutePath)
- contentValues.put(AttachmentDatabase.THUMBNAIL, null as String?)
- contentValues.put(AttachmentDatabase.DATA_RANDOM, output.first)
- db.update(AttachmentDatabase.TABLE_NAME, contentValues,
- "${AttachmentDatabase.ROW_ID} = ? AND ${AttachmentDatabase.UNIQUE_ID} = ?",
- arrayOf(attachment.rowId.toString(), attachment.attachmentId.toString()))
- }
-
- @Throws(IOException::class)
- private fun processAvatar(context: Context, avatar: Avatar, inputStream: BackupRecordInputStream) {
- inputStream.readAttachmentTo(FileOutputStream(
- AvatarHelper.getAvatarFile(context, Address.fromExternal(context, avatar.name))), avatar.length)
- }
-
- @SuppressLint("ApplySharedPref")
- private fun processPreference(context: Context, preference: SharedPreference) {
- val preferences = context.getSharedPreferences(preference.file, 0)
- val key = preference.key
- val value = preference.value
-
- // See the comment next to PREF_PREFIX_TYPE_* constants.
- when {
- key.startsWith(PREF_PREFIX_TYPE_INT) ->
- preferences.edit().putInt(
- key.substring(PREF_PREFIX_TYPE_INT.length),
- value.toInt()
- ).commit()
- key.startsWith(PREF_PREFIX_TYPE_BOOLEAN) ->
- preferences.edit().putBoolean(
- key.substring(PREF_PREFIX_TYPE_BOOLEAN.length),
- value.toBoolean()
- ).commit()
- else ->
- preferences.edit().putString(key, value).commit()
- }
- }
-
- private fun dropAllTables(db: SQLiteDatabase) {
- db.rawQuery("SELECT name, type FROM sqlite_master", null).use { cursor ->
- while (cursor != null && cursor.moveToNext()) {
- val name = cursor.getString(0)
- val type = cursor.getString(1)
- if ("table" == type && !name.startsWith("sqlite_")) {
- db.execSQL("DROP TABLE IF EXISTS $name")
- }
- }
- }
- }
-
- private fun trimEntriesForExpiredMessages(context: Context, db: SQLiteDatabase) {
- val trimmedCondition = " NOT IN (SELECT ${MmsSmsColumns.ID} FROM ${MmsDatabase.TABLE_NAME})"
- db.delete(GroupReceiptDatabase.TABLE_NAME, GroupReceiptDatabase.MMS_ID + trimmedCondition, null)
- val columns = arrayOf(AttachmentDatabase.ROW_ID, AttachmentDatabase.UNIQUE_ID)
- val where = AttachmentDatabase.MMS_ID + trimmedCondition
- db.query(AttachmentDatabase.TABLE_NAME, columns, where, null, null, null, null).use { cursor ->
- while (cursor != null && cursor.moveToNext()) {
- DatabaseComponent.get(context).attachmentDatabase()
- .deleteAttachment(AttachmentId(cursor.getLong(0), cursor.getLong(1)))
- }
- }
- db.query(ThreadDatabase.TABLE_NAME, arrayOf(ThreadDatabase.ID),
- ThreadDatabase.EXPIRES_IN + " > 0", null, null, null, null).use { cursor ->
- while (cursor != null && cursor.moveToNext()) {
- DatabaseComponent.get(context).threadDatabase().update(cursor.getLong(0), false)
- }
- }
- }
-
- private class BackupRecordInputStream : Closeable {
- private val inputStream: InputStream
- private val cipher: Cipher
- private val mac: Mac
- private val cipherKey: ByteArray
- private val macKey: ByteArray
- private val iv: ByteArray
-
- private var counter = 0
-
- @Throws(IOException::class)
- constructor(inputStream: InputStream, passphrase: String) : super() {
- try {
- this.inputStream = inputStream
- val headerLengthBytes = ByteArray(4)
- Util.readFully(this.inputStream, headerLengthBytes)
- val headerLength = Conversions.byteArrayToInt(headerLengthBytes)
- val headerFrame = ByteArray(headerLength)
- Util.readFully(this.inputStream, headerFrame)
- val frame = BackupFrame.parseFrom(headerFrame)
- if (!frame.hasHeader()) {
- throw IOException("Backup stream does not start with header!")
- }
- val header = frame.header
- iv = header.iv.toByteArray()
- if (iv.size != 16) {
- throw IOException("Invalid IV length!")
- }
- val key = BackupUtil.computeBackupKey(passphrase, if (header.hasSalt()) header.salt.toByteArray() else null)
- val derived = HKDFv3().deriveSecrets(key, "Backup Export".toByteArray(), 64)
- val split = ByteUtil.split(derived, 32, 32)
- cipherKey = split[0]
- macKey = split[1]
- cipher = Cipher.getInstance("AES/CTR/NoPadding")
- mac = Mac.getInstance("HmacSHA256")
- mac.init(SecretKeySpec(macKey, "HmacSHA256"))
- counter = Conversions.byteArrayToInt(iv)
- } catch (e: Exception) {
- when (e) {
- is NoSuchAlgorithmException,
- is NoSuchPaddingException,
- is InvalidKeyException -> {
- throw AssertionError(e)
- }
- else -> throw e
- }
- }
- }
-
- @Throws(IOException::class)
- fun readFrame(): BackupFrame {
- return readFrame(inputStream)
- }
-
- @Throws(IOException::class)
- fun readAttachmentTo(out: OutputStream, length: Int) {
- var length = length
- try {
- Conversions.intToByteArray(iv, 0, counter++)
- cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(cipherKey, "AES"), IvParameterSpec(iv))
- mac.update(iv)
- val buffer = ByteArray(8192)
- while (length > 0) {
- val read = inputStream.read(buffer, 0, Math.min(buffer.size, length))
- if (read == -1) throw IOException("File ended early!")
- mac.update(buffer, 0, read)
- val plaintext = cipher.update(buffer, 0, read)
- if (plaintext != null) {
- out.write(plaintext, 0, plaintext.size)
- }
- length -= read
- }
- val plaintext = cipher.doFinal()
- if (plaintext != null) {
- out.write(plaintext, 0, plaintext.size)
- }
- out.close()
- val ourMac = ByteUtil.trim(mac.doFinal(), 10)
- val theirMac = ByteArray(10)
- try {
- Util.readFully(inputStream, theirMac)
- } catch (e: IOException) {
- throw IOException(e)
- }
- if (!MessageDigest.isEqual(ourMac, theirMac)) {
- throw IOException("Bad MAC")
- }
- } catch (e: Exception) {
- when (e) {
- is InvalidKeyException,
- is InvalidAlgorithmParameterException,
- is IllegalBlockSizeException,
- is BadPaddingException -> {
- throw AssertionError(e)
- }
- else -> throw e
- }
- }
- }
-
- @Throws(IOException::class)
- private fun readFrame(`in`: InputStream?): BackupFrame {
- return try {
- val length = ByteArray(4)
- Util.readFully(`in`, length)
- val frame = ByteArray(Conversions.byteArrayToInt(length))
- Util.readFully(`in`, frame)
- val theirMac = ByteArray(10)
- System.arraycopy(frame, frame.size - 10, theirMac, 0, theirMac.size)
- mac.update(frame, 0, frame.size - 10)
- val ourMac = ByteUtil.trim(mac.doFinal(), 10)
- if (!MessageDigest.isEqual(ourMac, theirMac)) {
- throw IOException("Bad MAC")
- }
- Conversions.intToByteArray(iv, 0, counter++)
- cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(cipherKey, "AES"), IvParameterSpec(iv))
- val plaintext = cipher.doFinal(frame, 0, frame.size - 10)
- BackupFrame.parseFrom(plaintext)
- } catch (e: Exception) {
- when (e) {
- is InvalidKeyException,
- is InvalidAlgorithmParameterException,
- is IllegalBlockSizeException,
- is BadPaddingException -> {
- throw AssertionError(e)
- }
- else -> throw e
- }
- }
- }
-
- @Throws(IOException::class)
- override fun close() {
- inputStream.close()
- }
- }
-
- class DatabaseDowngradeException internal constructor(currentVersion: Int, backupVersion: Int) :
- IOException("Tried to import a backup with version $backupVersion into a database with version $currentVersion")
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt
index 7e732d1aa..b87eac12c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt
@@ -249,17 +249,12 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
viewModel.callState.collect { state ->
Log.d("Loki", "Consuming view model state $state")
when (state) {
- CALL_RINGING -> {
- if (wantsToAnswer) {
- answerCall()
- wantsToAnswer = false
- }
- }
- CALL_OUTGOING -> {
- }
- CALL_CONNECTED -> {
+ CALL_RINGING -> if (wantsToAnswer) {
+ answerCall()
wantsToAnswer = false
}
+ CALL_CONNECTED -> wantsToAnswer = false
+ else -> {}
}
updateControls(state)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java
index 195c066d4..1ac4f8442 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java
@@ -13,6 +13,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import org.session.libsession.snode.SnodeAPI;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView;
import org.thoughtcrime.securesms.database.model.MessageRecord;
@@ -106,7 +107,7 @@ public class ConversationItemFooter extends LinearLayout {
messageRecord.getExpiresIn());
this.timerView.startAnimation();
- if (messageRecord.getExpireStarted() + messageRecord.getExpiresIn() <= System.currentTimeMillis()) {
+ if (messageRecord.getExpireStarted() + messageRecord.getExpiresIn() <= SnodeAPI.getNowWithOffset()) {
ApplicationContext.getInstance(getContext()).getExpiringMessageManager().checkSchedule();
}
} else if (!messageRecord.isOutgoing() && !messageRecord.isMediaPending()) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/GlideBitmapListeningTarget.java b/app/src/main/java/org/thoughtcrime/securesms/components/GlideBitmapListeningTarget.java
index 61094fb7d..157bc215e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/GlideBitmapListeningTarget.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/GlideBitmapListeningTarget.java
@@ -4,30 +4,48 @@ import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+
+import android.view.View;
import android.widget.ImageView;
import com.bumptech.glide.request.target.BitmapImageViewTarget;
import org.session.libsignal.utilities.SettableFuture;
+import java.lang.ref.WeakReference;
+
public class GlideBitmapListeningTarget extends BitmapImageViewTarget {
private final SettableFuture loaded;
+ private final WeakReference loadingView;
- public GlideBitmapListeningTarget(@NonNull ImageView view, @NonNull SettableFuture loaded) {
+ public GlideBitmapListeningTarget(@NonNull ImageView view, @Nullable View loadingView, @NonNull SettableFuture loaded) {
super(view);
this.loaded = loaded;
+ this.loadingView = new WeakReference(loadingView);
}
@Override
protected void setResource(@Nullable Bitmap resource) {
super.setResource(resource);
loaded.set(true);
+
+ View loadingViewInstance = loadingView.get();
+
+ if (loadingViewInstance != null) {
+ loadingViewInstance.setVisibility(View.GONE);
+ }
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
super.onLoadFailed(errorDrawable);
loaded.set(true);
+
+ View loadingViewInstance = loadingView.get();
+
+ if (loadingViewInstance != null) {
+ loadingViewInstance.setVisibility(View.GONE);
+ }
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/GlideDrawableListeningTarget.java b/app/src/main/java/org/thoughtcrime/securesms/components/GlideDrawableListeningTarget.java
index d17790012..406c878ec 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/GlideDrawableListeningTarget.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/GlideDrawableListeningTarget.java
@@ -3,30 +3,48 @@ package org.thoughtcrime.securesms.components;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+
+import android.view.View;
import android.widget.ImageView;
import com.bumptech.glide.request.target.DrawableImageViewTarget;
import org.session.libsignal.utilities.SettableFuture;
+import java.lang.ref.WeakReference;
+
public class GlideDrawableListeningTarget extends DrawableImageViewTarget {
private final SettableFuture loaded;
+ private final WeakReference loadingView;
- public GlideDrawableListeningTarget(@NonNull ImageView view, @NonNull SettableFuture loaded) {
+ public GlideDrawableListeningTarget(@NonNull ImageView view, @Nullable View loadingView, @NonNull SettableFuture loaded) {
super(view);
this.loaded = loaded;
+ this.loadingView = new WeakReference(loadingView);
}
@Override
protected void setResource(@Nullable Drawable resource) {
super.setResource(resource);
loaded.set(true);
+
+ View loadingViewInstance = loadingView.get();
+
+ if (loadingViewInstance != null) {
+ loadingViewInstance.setVisibility(View.GONE);
+ }
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
super.onLoadFailed(errorDrawable);
loaded.set(true);
+
+ View loadingViewInstance = loadingView.get();
+
+ if (loadingViewInstance != null) {
+ loadingViewInstance.setVisibility(View.GONE);
+ }
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/LabeledSeparatorView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/LabeledSeparatorView.kt
deleted file mode 100644
index df36719db..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/components/LabeledSeparatorView.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-package org.thoughtcrime.securesms.components
-
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.Paint
-import android.graphics.Path
-import android.util.AttributeSet
-import android.view.LayoutInflater
-import android.widget.RelativeLayout
-import network.loki.messenger.R
-import network.loki.messenger.databinding.ViewSeparatorBinding
-import org.thoughtcrime.securesms.util.toPx
-import org.session.libsession.utilities.ThemeUtil
-
-class LabeledSeparatorView : RelativeLayout {
-
- private lateinit var binding: ViewSeparatorBinding
- private val path = Path()
-
- private val paint: Paint by lazy {
- val result = Paint()
- result.style = Paint.Style.STROKE
- result.color = ThemeUtil.getThemedColor(context, R.attr.dividerHorizontal)
- result.strokeWidth = toPx(1, resources).toFloat()
- result.isAntiAlias = true
- result
- }
-
- // region Lifecycle
- constructor(context: Context) : super(context) {
- setUpViewHierarchy()
- }
-
- constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
- setUpViewHierarchy()
- }
-
- constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
- setUpViewHierarchy()
- }
-
- constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
- setUpViewHierarchy()
- }
-
- private fun setUpViewHierarchy() {
- binding = ViewSeparatorBinding.inflate(LayoutInflater.from(context))
- val layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
- addView(binding.root, layoutParams)
- setWillNotDraw(false)
- }
- // endregion
-
- // region Updating
- override fun onDraw(c: Canvas) {
- super.onDraw(c)
- val w = width.toFloat()
- val h = height.toFloat()
- val hMargin = toPx(16, resources).toFloat()
- path.reset()
- path.moveTo(0.0f, h / 2)
- path.lineTo(binding.titleTextView.left - hMargin, h / 2)
- path.addRoundRect(binding.titleTextView.left - hMargin, toPx(1, resources).toFloat(), binding.titleTextView.right + hMargin, h - toPx(1, resources).toFloat(), h / 2, h / 2, Path.Direction.CCW)
- path.moveTo(binding.titleTextView.right + hMargin, h / 2)
- path.lineTo(w, h / 2)
- path.close()
- c.drawPath(path, paint)
- }
- // endregion
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/Outliner.java b/app/src/main/java/org/thoughtcrime/securesms/components/Outliner.java
deleted file mode 100644
index cb6cfc7ab..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/components/Outliner.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package org.thoughtcrime.securesms.components;
-
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.RectF;
-
-import androidx.annotation.ColorInt;
-
-public class Outliner {
-
- private final float[] radii = new float[8];
- private final Path corners = new Path();
- private final RectF bounds = new RectF();
- private final Paint outlinePaint = new Paint();
- {
- outlinePaint.setStyle(Paint.Style.STROKE);
- outlinePaint.setStrokeWidth(1f);
- outlinePaint.setAntiAlias(true);
- }
-
- public void setColor(@ColorInt int color) {
- outlinePaint.setColor(color);
- }
-
- public void draw(Canvas canvas) {
- final float halfStrokeWidth = outlinePaint.getStrokeWidth() / 2;
-
- bounds.left = halfStrokeWidth;
- bounds.top = halfStrokeWidth;
- bounds.right = canvas.getWidth() - halfStrokeWidth;
- bounds.bottom = canvas.getHeight() - halfStrokeWidth;
-
- corners.reset();
- corners.addRoundRect(bounds, radii, Path.Direction.CW);
-
- canvas.drawPath(corners, outlinePaint);
- }
-
- public void setRadius(int radius) {
- setRadii(radius, radius, radius, radius);
- }
-
- public void setRadii(int topLeft, int topRight, int bottomRight, int bottomLeft) {
- radii[0] = radii[1] = topLeft;
- radii[2] = radii[3] = topRight;
- radii[4] = radii[5] = bottomRight;
- radii[6] = radii[7] = bottomLeft;
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt
index a827a7d26..604422460 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components
import android.content.Context
import android.util.AttributeSet
+import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import android.widget.RelativeLayout
@@ -9,6 +10,7 @@ import androidx.annotation.DimenRes
import com.bumptech.glide.load.engine.DiskCacheStrategy
import network.loki.messenger.R
import network.loki.messenger.databinding.ViewProfilePictureBinding
+import network.loki.messenger.databinding.ViewUserBinding
import org.session.libsession.avatars.ContactColors
import org.session.libsession.avatars.PlaceholderAvatarPhoto
import org.session.libsession.avatars.ProfileContactPhoto
@@ -18,13 +20,14 @@ import org.session.libsession.utilities.Address
import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
+import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests
class ProfilePictureView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : RelativeLayout(context, attrs) {
- private val binding: ViewProfilePictureBinding by lazy { ViewProfilePictureBinding.bind(this) }
- lateinit var glide: GlideRequests
+ private val binding = ViewProfilePictureBinding.inflate(LayoutInflater.from(context), this)
+ private val glide: GlideRequests = GlideApp.with(this)
var publicKey: String? = null
var displayName: String? = null
var additionalPublicKey: String? = null
@@ -32,13 +35,18 @@ class ProfilePictureView @JvmOverloads constructor(
var isLarge = false
private val profilePicturesCache = mutableMapOf()
- private val unknownRecipientDrawable = ResourceContactPhoto(R.drawable.ic_profile_default)
- .asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false)
- private val unknownOpenGroupDrawable = ResourceContactPhoto(R.drawable.ic_notification)
- .asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false)
+ private val unknownRecipientDrawable by lazy { ResourceContactPhoto(R.drawable.ic_profile_default)
+ .asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false) }
+ private val unknownOpenGroupDrawable by lazy { ResourceContactPhoto(R.drawable.ic_notification)
+ .asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false) }
+
// endregion
+ constructor(context: Context, sender: Recipient): this(context) {
+ update(sender)
+ }
+
// region Updating
fun update(recipient: Recipient) {
fun getUserDisplayName(publicKey: String): String {
@@ -52,12 +60,19 @@ class ProfilePictureView @JvmOverloads constructor(
.sorted()
.take(2)
.toMutableList()
- val pk = members.getOrNull(0)?.serialize() ?: ""
- publicKey = pk
- displayName = getUserDisplayName(pk)
- val apk = members.getOrNull(1)?.serialize() ?: ""
- additionalPublicKey = apk
- additionalDisplayName = getUserDisplayName(apk)
+ if (members.size <= 1) {
+ publicKey = ""
+ displayName = ""
+ additionalPublicKey = ""
+ additionalDisplayName = ""
+ } else {
+ val pk = members.getOrNull(0)?.serialize() ?: ""
+ publicKey = pk
+ displayName = getUserDisplayName(pk)
+ val apk = members.getOrNull(1)?.serialize() ?: ""
+ additionalPublicKey = apk
+ additionalDisplayName = getUserDisplayName(apk)
+ }
} else if(recipient.isOpenGroupInboxRecipient) {
val publicKey = GroupUtil.getDecodedOpenGroupInbox(recipient.address.serialize())
this.publicKey = publicKey
@@ -73,7 +88,6 @@ class ProfilePictureView @JvmOverloads constructor(
}
fun update() {
- if (!this::glide.isInitialized) return
val publicKey = publicKey ?: return
val additionalPublicKey = additionalPublicKey
if (additionalPublicKey != null) {
@@ -108,30 +122,36 @@ class ProfilePictureView @JvmOverloads constructor(
val signalProfilePicture = recipient.contactPhoto
val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject
+ val placeholder = PlaceholderAvatarPhoto(context, publicKey, displayName ?: "${publicKey.take(4)}...${publicKey.takeLast(4)}")
+
if (signalProfilePicture != null && avatar != "0" && avatar != "") {
glide.clear(imageView)
glide.load(signalProfilePicture)
.placeholder(unknownRecipientDrawable)
.centerCrop()
- .error(unknownRecipientDrawable)
+ .error(glide.load(placeholder))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.circleCrop()
.into(imageView)
} else if (recipient.isOpenGroupRecipient && recipient.groupAvatarId == null) {
glide.clear(imageView)
- imageView.setImageDrawable(unknownOpenGroupDrawable)
+ glide.load(unknownOpenGroupDrawable)
+ .centerCrop()
+ .circleCrop()
+ .into(imageView)
} else {
- val placeholder = PlaceholderAvatarPhoto(context, publicKey, displayName ?: "${publicKey.take(4)}...${publicKey.takeLast(4)}")
-
glide.clear(imageView)
glide.load(placeholder)
.placeholder(unknownRecipientDrawable)
.centerCrop()
+ .circleCrop()
.diskCacheStrategy(DiskCacheStrategy.NONE).circleCrop().into(imageView)
}
profilePicturesCache[publicKey] = recipient.profileAvatar
} else {
- imageView.setImageDrawable(null)
+ glide.load(unknownRecipientDrawable)
+ .centerCrop()
+ .into(imageView)
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/SafeViewPager.kt b/app/src/main/java/org/thoughtcrime/securesms/components/SafeViewPager.kt
new file mode 100644
index 000000000..674847873
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/SafeViewPager.kt
@@ -0,0 +1,30 @@
+package org.thoughtcrime.securesms.components
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import androidx.viewpager.widget.ViewPager
+
+/**
+ * An extension of ViewPager to swallow erroneous multi-touch exceptions.
+ *
+ * @see https://stackoverflow.com/questions/6919292/pointerindex-out-of-range-android-multitouch
+ */
+class SafeViewPager @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null
+) : ViewPager(context, attrs) {
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouchEvent(event: MotionEvent?): Boolean = try {
+ super.onTouchEvent(event)
+ } catch (e: IllegalArgumentException) {
+ false
+ }
+
+ override fun onInterceptTouchEvent(event: MotionEvent?): Boolean = try {
+ super.onInterceptTouchEvent(event)
+ } catch (e: IllegalArgumentException) {
+ false
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiPageBitmap.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiPageBitmap.java
deleted file mode 100644
index 3c3a4fa3e..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiPageBitmap.java
+++ /dev/null
@@ -1,107 +0,0 @@
-package org.thoughtcrime.securesms.components.emoji.parsing;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.AssetManager;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.os.AsyncTask;
-import androidx.annotation.NonNull;
-import org.session.libsignal.utilities.Log;
-
-import org.thoughtcrime.securesms.components.emoji.EmojiPageModel;
-import org.thoughtcrime.securesms.util.Stopwatch;
-
-import org.session.libsession.utilities.ListenableFutureTask;
-import org.session.libsession.utilities.Util;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.ref.SoftReference;
-import java.util.concurrent.Callable;
-
-public class EmojiPageBitmap {
-
- private static final String TAG = EmojiPageBitmap.class.getSimpleName();
-
- private final Context context;
- private final EmojiPageModel model;
- private final float decodeScale;
-
- private SoftReference bitmapReference;
- private ListenableFutureTask task;
-
- public EmojiPageBitmap(@NonNull Context context, @NonNull EmojiPageModel model, float decodeScale) {
- this.context = context.getApplicationContext();
- this.model = model;
- this.decodeScale = decodeScale;
- }
-
- @SuppressLint("StaticFieldLeak")
- public ListenableFutureTask get() {
- Util.assertMainThread();
-
- if (bitmapReference != null && bitmapReference.get() != null) {
- return new ListenableFutureTask<>(bitmapReference.get());
- } else if (task != null) {
- return task;
- } else {
- Callable callable = () -> {
- try {
- Log.i(TAG, "loading page " + model.getSpriteUri().toString());
- return loadPage();
- } catch (IOException ioe) {
- Log.w(TAG, ioe);
- }
- return null;
- };
- task = new ListenableFutureTask<>(callable);
- new AsyncTask() {
- @Override protected Void doInBackground(Void... params) {
- task.run();
- return null;
- }
-
- @Override protected void onPostExecute(Void aVoid) {
- task = null;
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
- return task;
- }
-
- private Bitmap loadPage() throws IOException {
- if (bitmapReference != null && bitmapReference.get() != null) return bitmapReference.get();
-
-
- float scale = decodeScale;
- AssetManager assetManager = context.getAssets();
- InputStream assetStream = assetManager.open(model.getSpriteUri().toString());
- BitmapFactory.Options options = new BitmapFactory.Options();
-
- if (org.thoughtcrime.securesms.util.Util.isLowMemory(context)) {
- Log.i(TAG, "Low memory detected. Changing sample size.");
- options.inSampleSize = 2;
- scale = decodeScale * 2;
- }
-
- Stopwatch stopwatch = new Stopwatch(model.getSpriteUri().toString());
- Bitmap bitmap = BitmapFactory.decodeStream(assetStream, null, options);
- stopwatch.split("decode");
-
- Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, (int)(bitmap.getWidth() * scale), (int)(bitmap.getHeight() * scale), true);
- stopwatch.split("scale");
- stopwatch.stop(TAG);
-
- bitmapReference = new SoftReference<>(scaledBitmap);
- Log.i(TAG, "onPageLoaded(" + model.getSpriteUri().toString() + ") originalByteCount: " + bitmap.getByteCount()
- + " scaledByteCount: " + scaledBitmap.getByteCount()
- + " scaledSize: " + scaledBitmap.getWidth() + "x" + scaledBitmap.getHeight());
- return scaledBitmap;
- }
-
- @Override
- public @NonNull String toString() {
- return model.getSpriteUri().toString();
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/menu/ActionItem.kt b/app/src/main/java/org/thoughtcrime/securesms/components/menu/ActionItem.kt
index 358a9d326..d0b101a9f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/menu/ActionItem.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/menu/ActionItem.kt
@@ -5,8 +5,9 @@ import androidx.annotation.AttrRes
/**
* Represents an action to be rendered
*/
-data class ActionItem(
+data class ActionItem @JvmOverloads constructor(
@AttrRes val iconRes: Int,
val title: CharSequence,
- val action: Runnable
+ val action: Runnable,
+ val contentDescription: String? = null
)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/menu/ContextMenuList.kt b/app/src/main/java/org/thoughtcrime/securesms/components/menu/ContextMenuList.kt
index 65fb1ddbb..c86b40dfa 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/menu/ContextMenuList.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/menu/ContextMenuList.kt
@@ -77,6 +77,7 @@ class ContextMenuList(recyclerView: RecyclerView, onItemClick: () -> Unit) {
context.theme.resolveAttribute(model.item.iconRes, typedValue, true)
icon.setImageDrawable(ContextCompat.getDrawable(context, typedValue.resourceId))
}
+ itemView.contentDescription = model.item.contentDescription
title.text = model.item.title
itemView.setOnClickListener {
model.item.action.run()
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/recyclerview/SmoothScrollingLinearLayoutManager.java b/app/src/main/java/org/thoughtcrime/securesms/components/recyclerview/SmoothScrollingLinearLayoutManager.java
deleted file mode 100644
index a1b45ac2a..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/components/recyclerview/SmoothScrollingLinearLayoutManager.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.thoughtcrime.securesms.components.recyclerview;
-
-import android.content.Context;
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.LinearSmoothScroller;
-import android.util.DisplayMetrics;
-
-public class SmoothScrollingLinearLayoutManager extends LinearLayoutManager {
-
- public SmoothScrollingLinearLayoutManager(Context context, boolean reverseLayout) {
- super(context, LinearLayoutManager.VERTICAL, reverseLayout);
- }
-
- public void smoothScrollToPosition(@NonNull Context context, int position, float millisecondsPerInch) {
- final LinearSmoothScroller scroller = new LinearSmoothScroller(context) {
- @Override
- protected int getVerticalSnapPreference() {
- return LinearSmoothScroller.SNAP_TO_END;
- }
-
- @Override
- protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
- return millisecondsPerInch / displayMetrics.densityDpi;
- }
- };
-
- scroller.setTargetPosition(position);
- startSmoothScroll(scroller);
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/contactshare/ContactUtil.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactUtil.java
similarity index 89%
rename from app/src/main/java/org/thoughtcrime/securesms/contactshare/ContactUtil.java
rename to app/src/main/java/org/thoughtcrime/securesms/contacts/ContactUtil.java
index 4a1059ffd..5284fb001 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/contactshare/ContactUtil.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactUtil.java
@@ -1,4 +1,4 @@
-package org.thoughtcrime.securesms.contactshare;
+package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import androidx.annotation.NonNull;
@@ -24,7 +24,7 @@ public final class ContactUtil {
return SpanUtil.italic(context.getString(R.string.MessageNotifier_unknown_contact_message));
}
- public static @NonNull String getDisplayName(@Nullable Contact contact) {
+ private static @NonNull String getDisplayName(@Nullable Contact contact) {
if (contact == null) {
return "";
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt
index e88cf1d08..36a8c1adf 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt
@@ -7,6 +7,7 @@ import android.view.View
import android.widget.LinearLayout
import network.loki.messenger.R
import network.loki.messenger.databinding.ViewUserBinding
+import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionManagerUtilities
@@ -47,15 +48,14 @@ class UserView : LinearLayout {
// region Updating
fun bind(user: Recipient, glide: GlideRequests, actionIndicator: ActionIndicator, isSelected: Boolean = false) {
+ val isLocalUser = user.isLocalNumber
fun getUserDisplayName(publicKey: String): String {
+ if (isLocalUser) return context.getString(R.string.MessageRecord_you)
val contact = DatabaseComponent.get(context).sessionContactDatabase().getContactWithSessionID(publicKey)
return contact?.displayName(Contact.ContactContext.REGULAR) ?: publicKey
}
- val threadID = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(user)
- MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded(threadID, context) // FIXME: This is a bad place to do this
val address = user.address.serialize()
- binding.profilePictureView.root.glide = glide
- binding.profilePictureView.root.update(user)
+ binding.profilePictureView.update(user)
binding.actionIndicatorImageView.setImageResource(R.drawable.ic_baseline_edit_24)
binding.nameTextView.text = if (user.isGroupRecipient) user.name else getUserDisplayName(address)
when (actionIndicator) {
@@ -87,7 +87,7 @@ class UserView : LinearLayout {
}
fun unbind() {
- binding.profilePictureView.root.recycle()
+ binding.profilePictureView.recycle()
}
// endregion
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/contactshare/ContactModelMapper.java b/app/src/main/java/org/thoughtcrime/securesms/contactshare/ContactModelMapper.java
deleted file mode 100644
index ef783da79..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/contactshare/ContactModelMapper.java
+++ /dev/null
@@ -1,169 +0,0 @@
-package org.thoughtcrime.securesms.contactshare;
-
-import androidx.annotation.NonNull;
-
-import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
-import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment;
-import org.session.libsignal.utilities.guava.Optional;
-import org.session.libsignal.messages.SharedContact;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.session.libsession.utilities.Contact;
-import static org.session.libsession.utilities.Contact.*;
-
-public class ContactModelMapper {
-
- public static SharedContact.Builder localToRemoteBuilder(@NonNull Contact contact) {
- List phoneNumbers = new ArrayList<>(contact.getPhoneNumbers().size());
- List emails = new ArrayList<>(contact.getEmails().size());
- List postalAddresses = new ArrayList<>(contact.getPostalAddresses().size());
-
- for (Phone phone : contact.getPhoneNumbers()) {
- phoneNumbers.add(new SharedContact.Phone.Builder().setValue(phone.getNumber())
- .setType(localToRemoteType(phone.getType()))
- .setLabel(phone.getLabel())
- .build());
- }
-
- for (Email email : contact.getEmails()) {
- emails.add(new SharedContact.Email.Builder().setValue(email.getEmail())
- .setType(localToRemoteType(email.getType()))
- .setLabel(email.getLabel())
- .build());
- }
-
- for (PostalAddress postalAddress : contact.getPostalAddresses()) {
- postalAddresses.add(new SharedContact.PostalAddress.Builder().setType(localToRemoteType(postalAddress.getType()))
- .setLabel(postalAddress.getLabel())
- .setStreet(postalAddress.getStreet())
- .setPobox(postalAddress.getPoBox())
- .setNeighborhood(postalAddress.getNeighborhood())
- .setCity(postalAddress.getCity())
- .setRegion(postalAddress.getRegion())
- .setPostcode(postalAddress.getPostalCode())
- .setCountry(postalAddress.getCountry())
- .build());
- }
-
- SharedContact.Name name = new SharedContact.Name.Builder().setDisplay(contact.getName().getDisplayName())
- .setGiven(contact.getName().getGivenName())
- .setFamily(contact.getName().getFamilyName())
- .setPrefix(contact.getName().getPrefix())
- .setSuffix(contact.getName().getSuffix())
- .setMiddle(contact.getName().getMiddleName())
- .build();
-
- return new SharedContact.Builder().setName(name)
- .withOrganization(contact.getOrganization())
- .withPhones(phoneNumbers)
- .withEmails(emails)
- .withAddresses(postalAddresses);
- }
-
- public static Contact remoteToLocal(@NonNull SharedContact sharedContact) {
- Name name = new Name(sharedContact.getName().getDisplay().orNull(),
- sharedContact.getName().getGiven().orNull(),
- sharedContact.getName().getFamily().orNull(),
- sharedContact.getName().getPrefix().orNull(),
- sharedContact.getName().getSuffix().orNull(),
- sharedContact.getName().getMiddle().orNull());
-
- List phoneNumbers = new LinkedList<>();
- if (sharedContact.getPhone().isPresent()) {
- for (SharedContact.Phone phone : sharedContact.getPhone().get()) {
- phoneNumbers.add(new Phone(phone.getValue(),
- remoteToLocalType(phone.getType()),
- phone.getLabel().orNull()));
- }
- }
-
- List emails = new LinkedList<>();
- if (sharedContact.getEmail().isPresent()) {
- for (SharedContact.Email email : sharedContact.getEmail().get()) {
- emails.add(new Email(email.getValue(),
- remoteToLocalType(email.getType()),
- email.getLabel().orNull()));
- }
- }
-
- List postalAddresses = new LinkedList<>();
- if (sharedContact.getAddress().isPresent()) {
- for (SharedContact.PostalAddress postalAddress : sharedContact.getAddress().get()) {
- postalAddresses.add(new PostalAddress(remoteToLocalType(postalAddress.getType()),
- postalAddress.getLabel().orNull(),
- postalAddress.getStreet().orNull(),
- postalAddress.getPobox().orNull(),
- postalAddress.getNeighborhood().orNull(),
- postalAddress.getCity().orNull(),
- postalAddress.getRegion().orNull(),
- postalAddress.getPostcode().orNull(),
- postalAddress.getCountry().orNull()));
- }
- }
-
- Avatar avatar = null;
- if (sharedContact.getAvatar().isPresent()) {
- Attachment attachment = PointerAttachment.forPointer(Optional.of(sharedContact.getAvatar().get().getAttachment().asPointer())).get();
- boolean isProfile = sharedContact.getAvatar().get().isProfile();
-
- avatar = new Avatar(null, attachment, isProfile);
- }
-
- return new Contact(name, sharedContact.getOrganization().orNull(), phoneNumbers, emails, postalAddresses, avatar);
- }
-
- private static Phone.Type remoteToLocalType(SharedContact.Phone.Type type) {
- switch (type) {
- case HOME: return Phone.Type.HOME;
- case MOBILE: return Phone.Type.MOBILE;
- case WORK: return Phone.Type.WORK;
- default: return Phone.Type.CUSTOM;
- }
- }
-
- private static Email.Type remoteToLocalType(SharedContact.Email.Type type) {
- switch (type) {
- case HOME: return Email.Type.HOME;
- case MOBILE: return Email.Type.MOBILE;
- case WORK: return Email.Type.WORK;
- default: return Email.Type.CUSTOM;
- }
- }
-
- private static PostalAddress.Type remoteToLocalType(SharedContact.PostalAddress.Type type) {
- switch (type) {
- case HOME: return PostalAddress.Type.HOME;
- case WORK: return PostalAddress.Type.WORK;
- default: return PostalAddress.Type.CUSTOM;
- }
- }
-
- private static SharedContact.Phone.Type localToRemoteType(Phone.Type type) {
- switch (type) {
- case HOME: return SharedContact.Phone.Type.HOME;
- case MOBILE: return SharedContact.Phone.Type.MOBILE;
- case WORK: return SharedContact.Phone.Type.WORK;
- default: return SharedContact.Phone.Type.CUSTOM;
- }
- }
-
- private static SharedContact.Email.Type localToRemoteType(Email.Type type) {
- switch (type) {
- case HOME: return SharedContact.Email.Type.HOME;
- case MOBILE: return SharedContact.Email.Type.MOBILE;
- case WORK: return SharedContact.Email.Type.WORK;
- default: return SharedContact.Email.Type.CUSTOM;
- }
- }
-
- private static SharedContact.PostalAddress.Type localToRemoteType(PostalAddress.Type type) {
- switch (type) {
- case HOME: return SharedContact.PostalAddress.Type.HOME;
- case WORK: return SharedContact.PostalAddress.Type.WORK;
- default: return SharedContact.PostalAddress.Type.CUSTOM;
- }
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/ContactListAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/ContactListAdapter.kt
index 99e7c9061..68e2f975c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/ContactListAdapter.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/ContactListAdapter.kt
@@ -32,14 +32,13 @@ class ContactListAdapter(
class ContactViewHolder(private val binding: ViewContactBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(contact: ContactListItem.Contact, glide: GlideRequests, listener: (Recipient) -> Unit) {
- binding.profilePictureView.root.glide = glide
- binding.profilePictureView.root.update(contact.recipient)
+ binding.profilePictureView.update(contact.recipient)
binding.nameTextView.text = contact.displayName
binding.root.setOnClickListener { listener(contact.recipient) }
}
fun unbind() {
- binding.profilePictureView.root.recycle()
+ binding.profilePictureView.recycle()
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationHomeFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationHomeFragment.kt
index 2e62932ab..92f050f76 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationHomeFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/NewConversationHomeFragment.kt
@@ -55,7 +55,7 @@ class NewConversationHomeFragment : Fragment() {
val displayName = contact?.displayName(Contact.ContactContext.REGULAR) ?: sessionId
ContactListItem.Contact(it, displayName)
}.sortedBy { it.displayName }
- .groupBy { if (PublicKeyValidation.isValid(it.displayName)) unknownSectionTitle else it.displayName.first().uppercase() }
+ .groupBy { if (PublicKeyValidation.isValid(it.displayName)) unknownSectionTitle else it.displayName.firstOrNull()?.uppercase() ?: unknownSectionTitle }
.toMutableMap()
contactGroups.remove(unknownSectionTitle)?.let { contactGroups.put(unknownSectionTitle, it) }
adapter.items = contactGroups.flatMap { entry -> listOf(ContactListItem.Header(entry.key)) + entry.value }
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt
index c51ecedd4..7f1947c91 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt
@@ -3,29 +3,50 @@ package org.thoughtcrime.securesms.conversation.v2
import android.Manifest
import android.animation.FloatEvaluator
import android.animation.ValueAnimator
-import android.content.*
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Context
+import android.content.Intent
import android.content.res.Resources
import android.database.Cursor
import android.graphics.Rect
import android.graphics.Typeface
import android.net.Uri
-import android.os.*
+import android.os.AsyncTask
+import android.os.Build
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
import android.provider.MediaStore
+import android.text.SpannableStringBuilder
+import android.text.SpannedString
import android.text.TextUtils
+import android.text.style.StyleSpan
import android.util.Pair
import android.util.TypedValue
-import android.view.*
+import android.view.ActionMode
+import android.view.Menu
+import android.view.MenuItem
+import android.view.MotionEvent
+import android.view.View
+import android.view.WindowManager
import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.Toast
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.annotation.DimenRes
-import androidx.appcompat.app.AlertDialog
+import androidx.core.text.set
+import androidx.core.text.toSpannable
import androidx.core.view.drawToBitmap
import androidx.core.view.isVisible
+import androidx.fragment.app.DialogFragment
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import androidx.recyclerview.widget.LinearLayoutManager
@@ -33,7 +54,13 @@ import androidx.recyclerview.widget.RecyclerView
import com.annimon.stream.Stream
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.consumeAsFlow
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityConversationV2Binding
import network.loki.messenger.databinding.ViewVisibleMessageBinding
@@ -58,8 +85,13 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
import org.session.libsession.messaging.utilities.SessionId
-import org.session.libsession.utilities.*
+import org.session.libsession.snode.SnodeAPI
+import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Address.Companion.fromSerialized
+import org.session.libsession.utilities.GroupUtil
+import org.session.libsession.utilities.MediaTypes
+import org.session.libsession.utilities.Stub
+import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.concurrent.SimpleTask
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsession.utilities.recipients.RecipientModifiedListener
@@ -70,16 +102,19 @@ import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.guava.Optional
import org.session.libsignal.utilities.hexEncodedPrivateKey
import org.thoughtcrime.securesms.ApplicationContext
-import org.thoughtcrime.securesms.ExpirationDialog
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.attachments.ScreenshotObserver
import org.thoughtcrime.securesms.audio.AudioRecorder
import org.thoughtcrime.securesms.contacts.SelectContactsActivity.Companion.selectedContactsKey
-import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher
+import org.thoughtcrime.securesms.util.SimpleTextWatcher
import org.thoughtcrime.securesms.conversation.settings.ConversationSettingsActivityContract
import org.thoughtcrime.securesms.conversation.settings.ConversationSettingsActivityResult
import org.thoughtcrime.securesms.conversation.v2.ConversationReactionOverlay.OnActionSelectedListener
import org.thoughtcrime.securesms.conversation.v2.ConversationReactionOverlay.OnReactionSelectedListener
+import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.MESSAGE_TIMESTAMP
+import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_REPLY
+import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_RESEND
+import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_DELETE
import org.thoughtcrime.securesms.conversation.v2.dialogs.BlockedDialog
import org.thoughtcrime.securesms.conversation.v2.dialogs.LinkPreviewDialog
import org.thoughtcrime.securesms.conversation.v2.dialogs.SendSeedDialog
@@ -94,7 +129,10 @@ import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate
import org.thoughtcrime.securesms.conversation.v2.search.SearchBottomBar
import org.thoughtcrime.securesms.conversation.v2.search.SearchViewModel
-import org.thoughtcrime.securesms.conversation.v2.utilities.*
+import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager
+import org.thoughtcrime.securesms.conversation.v2.utilities.MentionManagerUtilities
+import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities
+import org.thoughtcrime.securesms.conversation.v2.utilities.ResendMessageUtilities
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
import org.thoughtcrime.securesms.database.GroupDatabase
@@ -107,6 +145,7 @@ import org.thoughtcrime.securesms.database.ReactionDatabase
import org.thoughtcrime.securesms.database.RecipientDatabase
import org.thoughtcrime.securesms.database.SessionContactDatabase
import org.thoughtcrime.securesms.database.SmsDatabase
+import org.thoughtcrime.securesms.database.Storage
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.MessageRecord
@@ -120,13 +159,31 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel.LinkPreviewState
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mediasend.MediaSendActivity
-import org.thoughtcrime.securesms.mms.*
+import org.thoughtcrime.securesms.mms.AudioSlide
+import org.thoughtcrime.securesms.mms.GifSlide
+import org.thoughtcrime.securesms.mms.GlideApp
+import org.thoughtcrime.securesms.mms.ImageSlide
+import org.thoughtcrime.securesms.mms.MediaConstraints
+import org.thoughtcrime.securesms.mms.Slide
+import org.thoughtcrime.securesms.mms.SlideDeck
+import org.thoughtcrime.securesms.mms.VideoSlide
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment
-import org.thoughtcrime.securesms.util.*
-import java.util.*
+import org.thoughtcrime.securesms.showExpirationDialog
+import org.thoughtcrime.securesms.showSessionDialog
+import org.thoughtcrime.securesms.util.ActivityDispatcher
+import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
+import org.thoughtcrime.securesms.util.DateUtils
+import org.thoughtcrime.securesms.util.MediaUtil
+import org.thoughtcrime.securesms.util.SaveAttachmentTask
+import org.thoughtcrime.securesms.util.isScrolledToBottom
+import org.thoughtcrime.securesms.util.push
+import org.thoughtcrime.securesms.util.toPx
+import java.lang.ref.WeakReference
+import java.util.Locale
import java.util.concurrent.ExecutionException
+import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject
@@ -202,11 +259,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
it
}
val recipient = Recipient.from(this, address, false)
- threadId = threadDb.getOrCreateThreadIdFor(recipient)
+ threadId = storage.getOrCreateThreadIdFor(recipient.address)
}
} ?: finish()
}
- viewModelFactory.create(threadId, MessagingModuleConfiguration.shared.getUserED25519KeyPair())
+ viewModelFactory.create(threadId, MessagingModuleConfiguration.shared.getUserED25519KeyPair(), contentResolver)
}
private var actionMode: ActionMode? = null
private var unreadCount = 0
@@ -227,11 +284,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val searchViewModel: SearchViewModel by viewModels()
var searchViewItem: MenuItem? = null
+ private val bufferedLastSeenChannel = Channel(capacity = 512, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ private var emojiPickerVisible = false
+
private val isScrolledToBottom: Boolean
- get() {
- val position = layoutManager?.findFirstCompletelyVisibleItemPosition() ?: 0
- return position == 0
- }
+ get() = binding?.conversationRecyclerView?.isScrolledToBottom ?: true
private val layoutManager: LinearLayoutManager?
get() { return binding?.conversationRecyclerView?.layoutManager as LinearLayoutManager? }
@@ -247,11 +304,17 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
MnemonicCodec(loadFileContents).encode(hexEncodedSeed!!, MnemonicCodec.Language.Configuration.english)
}
+ // There is a bug when initially joining a community where all messages will immediately be marked
+ // as read if we reverse the message list so this is now hard-coded to false
+ private val reverseMessageList = false
+
private val adapter by lazy {
- val cursor = mmsSmsDb.getConversation(viewModel.threadId, !isIncomingMessageRequestThread())
+ val cursor = mmsSmsDb.getConversation(viewModel.threadId, reverseMessageList)
val adapter = ConversationAdapter(
this,
cursor,
+ storage.getLastSeen(viewModel.threadId),
+ reverseMessageList,
onItemPress = { message, position, view, event ->
handlePress(message, position, view, event)
},
@@ -293,6 +356,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
private val cameraButton by lazy { InputBarButton(this, R.drawable.ic_baseline_photo_camera_24, hasOpaqueBackground = true) }
private val messageToScrollTimestamp = AtomicLong(-1)
private val messageToScrollAuthor = AtomicReference(null)
+ private val firstLoad = AtomicBoolean(true)
private lateinit var reactionDelegate: ConversationReactionDelegate
private val reactWithAnyEmojiStartPage = -1
@@ -323,12 +387,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
// messageIdToScroll
messageToScrollTimestamp.set(intent.getLongExtra(SCROLL_MESSAGE_ID, -1))
messageToScrollAuthor.set(intent.getParcelableExtra(SCROLL_MESSAGE_AUTHOR))
- val thread = threadDb.getRecipientForThreadId(viewModel.threadId)
- if (thread == null) {
+ val recipient = viewModel.recipient
+ val openGroup = recipient.let { viewModel.openGroup }
+ if (recipient == null || (recipient.isOpenGroupRecipient && openGroup == null)) {
Toast.makeText(this, "This thread has been deleted.", Toast.LENGTH_LONG).show()
return finish()
}
- setUpRecyclerView()
+
setUpToolBar()
setUpInputBar()
setUpLinkPreviewObserver()
@@ -336,41 +401,64 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
setUpUiStateObserver()
binding!!.scrollToBottomButton.setOnClickListener {
val layoutManager = (binding?.conversationRecyclerView?.layoutManager as? LinearLayoutManager) ?: return@setOnClickListener
+ val targetPosition = if (reverseMessageList) 0 else adapter.itemCount
if (layoutManager.isSmoothScrolling) {
- binding?.conversationRecyclerView?.scrollToPosition(0)
+ binding?.conversationRecyclerView?.scrollToPosition(targetPosition)
} else {
// It looks like 'smoothScrollToPosition' will actually load all intermediate items in
// order to do the scroll, this can be very slow if there are a lot of messages so
// instead we check the current position and if there are more than 10 items to scroll
// we jump instantly to the 10th item and scroll from there (this should happen quick
// enough to give a similar scroll effect without having to load everything)
- val position = layoutManager.findFirstVisibleItemPosition()
- if (position > 10) {
- binding?.conversationRecyclerView?.scrollToPosition(10)
- }
+// val position = if (reverseMessageList) layoutManager.findFirstVisibleItemPosition() else layoutManager.findLastVisibleItemPosition()
+// val targetBuffer = if (reverseMessageList) 10 else Math.max(0, (adapter.itemCount - 1) - 10)
+// if (position > targetBuffer) {
+// binding?.conversationRecyclerView?.scrollToPosition(targetBuffer)
+// }
binding?.conversationRecyclerView?.post {
- binding?.conversationRecyclerView?.smoothScrollToPosition(0)
+ binding?.conversationRecyclerView?.smoothScrollToPosition(targetPosition)
}
}
}
- unreadCount = mmsSmsDb.getUnreadCount(viewModel.threadId)
+
updateUnreadCountIndicator()
- setUpTypingObserver()
- setUpRecipientObserver()
updateSubtitle()
- getLatestOpenGroupInfoIfNeeded()
+ updatePlaceholder()
setUpBlockedBanner()
binding!!.searchBottomBar.setEventListener(this)
- setUpSearchResultObserver()
- scrollToFirstUnreadMessageIfNeeded()
+ updateSendAfterApprovalText()
showOrHideInputIfNeeded()
setUpMessageRequestsBar()
- viewModel.recipient?.let { recipient ->
- if (recipient.isOpenGroupRecipient && viewModel.openGroup == null) {
- Toast.makeText(this, "This thread has been deleted.", Toast.LENGTH_LONG).show()
- return finish()
+
+ val weakActivity = WeakReference(this)
+
+ lifecycleScope.launch(Dispatchers.IO) {
+ // Note: We are accessing the `adapter` property because we want it to be loaded on
+ // the background thread to avoid blocking the UI thread and potentially hanging when
+ // transitioning to the activity
+ weakActivity.get()?.adapter ?: return@launch
+
+ // 'Get' instead of 'GetAndSet' here because we want to trigger the highlight in 'onFirstLoad'
+ // by triggering 'jumpToMessage' using these values
+ val messageTimestamp = messageToScrollTimestamp.get()
+ val author = messageToScrollAuthor.get()
+ val targetPosition = if (author != null && messageTimestamp >= 0) mmsSmsDb.getMessagePositionInConversation(viewModel.threadId, messageTimestamp, author, reverseMessageList) else -1
+
+ withContext(Dispatchers.Main) {
+ setUpRecyclerView()
+ setUpTypingObserver()
+ setUpRecipientObserver()
+ getLatestOpenGroupInfoIfNeeded()
+ setUpSearchResultObserver()
+
+ if (author != null && messageTimestamp >= 0 && targetPosition >= 0) {
+ binding?.conversationRecyclerView?.scrollToPosition(targetPosition)
+ }
+ else {
+ scrollToFirstUnreadMessageIfNeeded(true)
+ }
}
}
@@ -378,16 +466,25 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
ViewUtil.findStubById(this, R.id.conversation_reaction_scrubber_stub)
reactionDelegate = ConversationReactionDelegate(reactionOverlayStub)
reactionDelegate.setOnReactionSelectedListener(this)
+ lifecycleScope.launch {
+ lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ // only update the conversation every 3 seconds maximum
+ // channel is rendezvous and shouldn't block on try send calls as often as we want
+ val bufferedFlow = bufferedLastSeenChannel.consumeAsFlow()
+ bufferedFlow.filter {
+ it > storage.getLastSeen(viewModel.threadId)
+ }.collectLatest { latestMessageRead ->
+ withContext(Dispatchers.IO) {
+ storage.markConversationAsRead(viewModel.threadId, latestMessageRead)
+ }
+ }
+ }
+ }
}
override fun onResume() {
super.onResume()
ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(viewModel.threadId)
- val recipient = viewModel.recipient ?: return
-
- lifecycleScope.launch(Dispatchers.IO) {
- threadDb.markAllAsRead(viewModel.threadId, recipient.isOpenGroupRecipient)
- }
contentResolver.registerContentObserver(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
@@ -414,23 +511,45 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
push(intent, false)
}
- override fun showDialog(baseDialog: BaseDialog, tag: String?) {
- baseDialog.show(supportFragmentManager, tag)
+ override fun showDialog(dialogFragment: DialogFragment, tag: String?) {
+ dialogFragment.show(supportFragmentManager, tag)
}
override fun onCreateLoader(id: Int, bundle: Bundle?): Loader {
- return ConversationLoader(viewModel.threadId, !isIncomingMessageRequestThread(), this@ConversationActivityV2)
+ return ConversationLoader(viewModel.threadId, reverseMessageList, this@ConversationActivityV2)
}
override fun onLoadFinished(loader: Loader, cursor: Cursor?) {
+ val oldCount = adapter.itemCount
+ val newCount = cursor?.count ?: 0
adapter.changeCursor(cursor)
+
if (cursor != null) {
val messageTimestamp = messageToScrollTimestamp.getAndSet(-1)
val author = messageToScrollAuthor.getAndSet(null)
+ val initialUnreadCount = mmsSmsDb.getUnreadCount(viewModel.threadId)
+
+ // Update the unreadCount value to be loaded from the database since we got a new message
+ if (firstLoad.get() || oldCount != newCount || initialUnreadCount != unreadCount) {
+ // Update the unreadCount value to be loaded from the database since we got a new
+ // message (we need to store it in a local variable as it can get overwritten on
+ // another thread before the 'firstLoad.getAndSet(false)' case below)
+ unreadCount = initialUnreadCount
+ updateUnreadCountIndicator()
+ }
+
if (author != null && messageTimestamp >= 0) {
- jumpToMessage(author, messageTimestamp, null)
+ jumpToMessage(author, messageTimestamp, firstLoad.get(), null)
+ }
+ else if (firstLoad.getAndSet(false)) {
+ scrollToFirstUnreadMessageIfNeeded(true)
+ handleRecyclerViewScrolled()
+ }
+ else if (oldCount != newCount) {
+ handleRecyclerViewScrolled()
}
}
+ updatePlaceholder()
}
override fun onLoaderReset(cursor: Loader) {
@@ -440,7 +559,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
// called from onCreate
private fun setUpRecyclerView() {
binding!!.conversationRecyclerView.adapter = adapter
- val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, !isIncomingMessageRequestThread())
+ val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, reverseMessageList)
binding!!.conversationRecyclerView.layoutManager = layoutManager
// Workaround for the fact that CursorRecyclerViewAdapter doesn't auto-update automatically (even though it says it will)
LoaderManager.getInstance(this).restartLoader(0, null, this)
@@ -449,18 +568,27 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
handleRecyclerViewScrolled()
}
+
+ override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
+
+ }
})
+
+ binding!!.conversationRecyclerView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+ showScrollToBottomButtonIfApplicable()
+ }
}
// called from onCreate
private fun setUpToolBar() {
- setSupportActionBar(binding?.toolbar)
+ val binding = binding ?: return
+ setSupportActionBar(binding.toolbar)
val actionBar = supportActionBar ?: return
val recipient = viewModel.recipient ?: return
actionBar.title = ""
actionBar.setDisplayHomeAsUpEnabled(true)
actionBar.setHomeButtonEnabled(true)
- binding!!.toolbarContent.conversationTitleView.text = when {
+ binding.toolbarContent.conversationTitleView.text = when {
recipient.isLocalNumber -> getString(R.string.note_to_self)
else -> recipient.toShortString()
}
@@ -470,12 +598,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
R.dimen.small_profile_picture_size
}
val size = resources.getDimension(sizeID).roundToInt()
- binding!!.toolbarContent.profilePictureView.root.layoutParams = LinearLayout.LayoutParams(size, size)
- binding!!.toolbarContent.profilePictureView.root.glide = glide
+ binding.toolbarContent.profilePictureView.layoutParams = LinearLayout.LayoutParams(size, size)
MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded(viewModel.threadId, this)
- val profilePictureView = binding!!.toolbarContent.profilePictureView.root
- profilePictureView.update(recipient)
- profilePictureView.setOnClickListener(this)
+ val profilePictureView = binding.toolbarContent.profilePictureView
+ viewModel.recipient?.let(profilePictureView::update)
}
// called from onCreate
@@ -613,15 +739,37 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
if (uiState.isMessageRequestAccepted == true) {
binding?.messageRequestBar?.visibility = View.GONE
}
+ if (!uiState.conversationExists && !isFinishing) {
+ // Conversation should be deleted now, just go back
+ finish()
+ }
}
}
}
- private fun scrollToFirstUnreadMessageIfNeeded() {
+ private fun scrollToFirstUnreadMessageIfNeeded(isFirstLoad: Boolean = false, shouldHighlight: Boolean = false): Int {
val lastSeenTimestamp = threadDb.getLastSeenAndHasSent(viewModel.threadId).first()
- val lastSeenItemPosition = adapter.findLastSeenItemPosition(lastSeenTimestamp) ?: return
- if (lastSeenItemPosition <= 3) { return }
+ val lastSeenItemPosition = adapter.findLastSeenItemPosition(lastSeenTimestamp) ?: return -1
+
+ // If this is triggered when first opening a conversation then we want to position the top
+ // of the first unread message in the middle of the screen
+ if (isFirstLoad && !reverseMessageList) {
+ layoutManager?.scrollToPositionWithOffset(lastSeenItemPosition, ((layoutManager?.height ?: 0) / 2))
+
+ if (shouldHighlight) { highlightViewAtPosition(lastSeenItemPosition) }
+
+ return lastSeenItemPosition
+ }
+
+ if (lastSeenItemPosition <= 3) { return lastSeenItemPosition }
binding?.conversationRecyclerView?.scrollToPosition(lastSeenItemPosition)
+ return lastSeenItemPosition
+ }
+
+ private fun highlightViewAtPosition(position: Int) {
+ binding?.conversationRecyclerView?.post {
+ (layoutManager?.findViewByPosition(position) as? VisibleMessageView)?.playHighlight()
+ }
}
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
@@ -649,6 +797,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
// region Animation & Updating
override fun onModified(recipient: Recipient) {
+ viewModel.updateRecipient()
+
runOnUiThread {
val threadRecipient = viewModel.recipient ?: return@runOnUiThread
if (threadRecipient.isContactRecipient) {
@@ -657,8 +807,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
setUpMessageRequestsBar()
invalidateOptionsMenu()
updateSubtitle()
+ updateSendAfterApprovalText()
showOrHideInputIfNeeded()
- binding?.toolbarContent?.profilePictureView?.root?.update(threadRecipient)
+
+ binding?.toolbarContent?.profilePictureView?.update(threadRecipient)
binding?.toolbarContent?.conversationTitleView?.text = when {
threadRecipient.isLocalNumber -> getString(R.string.note_to_self)
else -> threadRecipient.toShortString()
@@ -666,6 +818,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
}
}
+ private fun updateSendAfterApprovalText() {
+ binding?.textSendAfterApproval?.isVisible = viewModel.showSendAfterApprovalText
+ }
+
private fun showOrHideInputIfNeeded() {
val recipient = viewModel.recipient
if (recipient != null && recipient.isClosedGroupRecipient) {
@@ -697,11 +853,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
private fun acceptMessageRequest() {
binding?.messageRequestBar?.isVisible = false
- binding?.conversationRecyclerView?.layoutManager =
- LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true)
- adapter.notifyDataSetChanged()
viewModel.acceptMessageRequest()
- LoaderManager.getInstance(this).restartLoader(0, null, this)
+
lifecycleScope.launch(Dispatchers.IO) {
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@ConversationActivityV2)
}
@@ -785,7 +938,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val recipient = viewModel.recipient ?: return
if (!isShowingMentionCandidatesView) {
additionalContentContainer.removeAllViews()
- val view = MentionCandidatesView(this)
+ val view = MentionCandidatesView(this).apply {
+ contentDescription = context.getString(R.string.AccessibilityId_mentions_list)
+ }
view.glide = glide
view.onCandidateSelected = { handleMentionSelected(it) }
additionalContentContainer.addView(view)
@@ -897,20 +1052,62 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
}
private fun handleRecyclerViewScrolled() {
- // FIXME: Checking isScrolledToBottom is a quick fix for an issue where the
- // typing indicator overlays the recycler view when scrolled up
val binding = binding ?: return
val wasTypingIndicatorVisibleBefore = binding.typingIndicatorViewContainer.isVisible
binding.typingIndicatorViewContainer.isVisible = wasTypingIndicatorVisibleBefore && isScrolledToBottom
- binding.typingIndicatorViewContainer.isVisible
- showOrHidScrollToBottomButton()
- val firstVisiblePosition = layoutManager?.findFirstVisibleItemPosition() ?: -1
- unreadCount = min(unreadCount, firstVisiblePosition).coerceAtLeast(0)
+ showScrollToBottomButtonIfApplicable()
+ val maybeTargetVisiblePosition = if (reverseMessageList) layoutManager?.findFirstVisibleItemPosition() else layoutManager?.findLastVisibleItemPosition()
+ val targetVisiblePosition = maybeTargetVisiblePosition ?: RecyclerView.NO_POSITION
+ if (!firstLoad.get() && targetVisiblePosition != RecyclerView.NO_POSITION) {
+ val visibleItemTimestamp = adapter.getTimestampForItemAt(targetVisiblePosition)
+ if (visibleItemTimestamp != null) {
+ bufferedLastSeenChannel.trySend(visibleItemTimestamp)
+ }
+ }
+
+ if (reverseMessageList) {
+ unreadCount = min(unreadCount, targetVisiblePosition).coerceAtLeast(0)
+ }
+ else {
+ val layoutUnreadCount = layoutManager?.let { (it.itemCount - 1) - it.findLastVisibleItemPosition() }
+ ?: RecyclerView.NO_POSITION
+ unreadCount = min(unreadCount, layoutUnreadCount).coerceAtLeast(0)
+ }
updateUnreadCountIndicator()
}
- private fun showOrHidScrollToBottomButton(show: Boolean = true) {
- binding?.scrollToBottomButton?.isVisible = show && !isScrolledToBottom && adapter.itemCount > 0
+ private fun updatePlaceholder() {
+ val recipient = viewModel.recipient
+ ?: return Log.w("Loki", "recipient was null in placeholder update")
+ val binding = binding ?: return
+ val openGroup = viewModel.openGroup
+ val (textResource, insertParam) = when {
+ recipient.isLocalNumber -> R.string.activity_conversation_empty_state_note_to_self to null
+ openGroup != null && !openGroup.canWrite -> R.string.activity_conversation_empty_state_read_only to recipient.toShortString()
+ else -> R.string.activity_conversation_empty_state_default to recipient.toShortString()
+ }
+ val showPlaceholder = adapter.itemCount == 0
+ binding.placeholderText.isVisible = showPlaceholder
+ if (showPlaceholder) {
+ if (insertParam != null) {
+ val span = getText(textResource) as SpannedString
+ val annotations = span.getSpans(0, span.length, StyleSpan::class.java)
+ val boldSpan = annotations.first()
+ val spannedParam = insertParam.toSpannable()
+ spannedParam[0 until spannedParam.length] = StyleSpan(boldSpan.style)
+ val originalStart = span.getSpanStart(boldSpan)
+ val originalEnd = span.getSpanEnd(boldSpan)
+ val newString = SpannableStringBuilder(span)
+ .replace(originalStart, originalEnd, spannedParam)
+ binding.placeholderText.text = newString
+ } else {
+ binding.placeholderText.setText(textResource)
+ }
+ }
+ }
+
+ private fun showScrollToBottomButtonIfApplicable() {
+ binding?.scrollToBottomButton?.isVisible = !emojiPickerVisible && !isScrolledToBottom && adapter.itemCount > 0
}
private fun updateUnreadCountIndicator() {
@@ -967,19 +1164,18 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
}
override fun block(deleteThread: Boolean) {
- val title = R.string.RecipientPreferenceActivity_block_this_contact_question
- val message = R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact
- AlertDialog.Builder(this)
- .setTitle(title)
- .setMessage(message)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(R.string.RecipientPreferenceActivity_block) { _, _ ->
+ showSessionDialog {
+ title(R.string.RecipientPreferenceActivity_block_this_contact_question)
+ text(R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact)
+ destructiveButton(R.string.RecipientPreferenceActivity_block, R.string.AccessibilityId_block_confirm) {
viewModel.block()
if (deleteThread) {
viewModel.deleteThread()
finish()
}
- }.show()
+ }
+ cancelButton()
+ }
}
override fun copySessionID(sessionId: String) {
@@ -989,33 +1185,44 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
}
+ override fun copyOpenGroupUrl(thread: Recipient) {
+ if (!thread.isOpenGroupRecipient) { return }
+
+ val threadId = threadDb.getThreadIdIfExistsFor(thread) ?: return
+ val openGroup = lokiThreadDb.getOpenGroupChat(threadId) ?: return
+
+ val clip = ClipData.newPlainText("Community URL", openGroup.joinURL)
+ val manager = getSystemService(PassphraseRequiredActionBarActivity.CLIPBOARD_SERVICE) as ClipboardManager
+ manager.setPrimaryClip(clip)
+ Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
+ }
+
override fun showExpiringMessagesDialog(thread: Recipient) {
if (thread.isClosedGroupRecipient) {
val group = groupDb.getGroup(thread.address.toGroupString()).orNull()
if (group?.isActive == false) { return }
}
- ExpirationDialog.show(this, thread.expireMessages) { expirationTime: Int ->
- recipientDb.setExpireMessages(thread, expirationTime)
+ showExpirationDialog(thread.expireMessages) { expirationTime ->
+ storage.setExpirationTimer(thread.address.serialize(), expirationTime)
val message = ExpirationTimerUpdate(expirationTime)
message.recipient = thread.address.serialize()
- message.sentTimestamp = System.currentTimeMillis()
- val expiringMessageManager = ApplicationContext.getInstance(this).expiringMessageManager
- expiringMessageManager.setExpirationTimer(message)
+ message.sentTimestamp = SnodeAPI.nowWithOffset
+ ApplicationContext.getInstance(this).expiringMessageManager.setExpirationTimer(message)
MessageSender.send(message, thread.address)
invalidateOptionsMenu()
}
}
override fun unblock() {
- val title = R.string.ConversationActivity_unblock_this_contact_question
- val message = R.string.ConversationActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact
- AlertDialog.Builder(this)
- .setTitle(title)
- .setMessage(message)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(R.string.ConversationActivity_unblock) { _, _ ->
- viewModel.unblock()
- }.show()
+ showSessionDialog {
+ title(R.string.ConversationActivity_unblock_this_contact_question)
+ text(R.string.ConversationActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact)
+ destructiveButton(
+ R.string.ConversationActivity_unblock,
+ R.string.AccessibilityId_block_confirm
+ ) { viewModel.unblock() }
+ cancelButton()
+ }
}
// `position` is the adapter position; not the visual position
@@ -1075,33 +1282,37 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
Log.e("Loki", "Failed to show emoji picker", e)
return
}
+
+ val binding = binding ?: return
+
+ emojiPickerVisible = true
ViewUtil.hideKeyboard(this, visibleMessageView)
- binding?.reactionsShade?.isVisible = true
- showOrHidScrollToBottomButton(false)
- binding?.conversationRecyclerView?.suppressLayout(true)
+ binding.reactionsShade.isVisible = true
+ binding.scrollToBottomButton.isVisible = false
+ binding.conversationRecyclerView.suppressLayout(true)
reactionDelegate.setOnActionSelectedListener(ReactionsToolbarListener(message))
reactionDelegate.setOnHideListener(object: ConversationReactionOverlay.OnHideListener {
override fun startHide() {
- binding?.reactionsShade?.let {
+ emojiPickerVisible = false
+ binding.reactionsShade.let {
ViewUtil.fadeOut(it, resources.getInteger(R.integer.reaction_scrubber_hide_duration), View.GONE)
}
- showOrHidScrollToBottomButton(true)
+ showScrollToBottomButtonIfApplicable()
}
override fun onHide() {
- binding?.conversationRecyclerView?.suppressLayout(false)
+ binding.conversationRecyclerView.suppressLayout(false)
WindowUtil.setLightStatusBarFromTheme(this@ConversationActivityV2);
WindowUtil.setLightNavigationBarFromTheme(this@ConversationActivityV2);
}
})
- val contentBounds = Rect()
- visibleMessageView.messageContentView.getGlobalVisibleRect(contentBounds)
+ val topLeft = intArrayOf(0, 0).also { visibleMessageView.messageContentView.getLocationInWindow(it) }
val selectedConversationModel = SelectedConversationModel(
messageContentBitmap,
- contentBounds.left.toFloat(),
- contentBounds.top.toFloat(),
+ topLeft[0].toFloat(),
+ topLeft[1].toFloat(),
visibleMessageView.messageContentView.width,
message.isOutgoing,
visibleMessageView.messageContentView
@@ -1127,7 +1338,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
// Create the message
val recipient = viewModel.recipient ?: return
val reactionMessage = VisibleMessage()
- val emojiTimestamp = System.currentTimeMillis()
+ val emojiTimestamp = SnodeAPI.nowWithOffset
reactionMessage.sentTimestamp = emojiTimestamp
val author = textSecurePreferences.getLocalNumber()!!
// Put the message in the database
@@ -1160,7 +1371,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
private fun sendEmojiRemoval(emoji: String, originalMessage: MessageRecord) {
val recipient = viewModel.recipient ?: return
val message = VisibleMessage()
- val emojiTimestamp = System.currentTimeMillis()
+ val emojiTimestamp = SnodeAPI.nowWithOffset
message.sentTimestamp = emojiTimestamp
val author = textSecurePreferences.getLocalNumber()!!
reactionDb.deleteReaction(emoji, MessageId(originalMessage.id, originalMessage.isMms), author, false)
@@ -1351,15 +1562,21 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
override fun sendMessage() {
val recipient = viewModel.recipient ?: return
if (recipient.isContactRecipient && recipient.isBlocked) {
- BlockedDialog(recipient).show(supportFragmentManager, "Blocked Dialog")
+ BlockedDialog(recipient, this).show(supportFragmentManager, "Blocked Dialog")
return
}
val binding = binding ?: return
- if (binding.inputBar.linkPreview != null || binding.inputBar.quote != null) {
+ val sentMessageInfo = if (binding.inputBar.linkPreview != null || binding.inputBar.quote != null) {
sendAttachments(listOf(), getMessageBody(), binding.inputBar.quote, binding.inputBar.linkPreview)
} else {
sendTextOnlyMessage()
}
+
+ // Jump to the newly sent message once it gets added
+ if (sentMessageInfo != null) {
+ messageToScrollAuthor.set(sentMessageInfo.first)
+ messageToScrollTimestamp.set(sentMessageInfo.second)
+ }
}
override fun commitInputContent(contentUri: Uri) {
@@ -1377,19 +1594,21 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
}
}
- private fun sendTextOnlyMessage(hasPermissionToSendSeed: Boolean = false) {
- val recipient = viewModel.recipient ?: return
+ private fun sendTextOnlyMessage(hasPermissionToSendSeed: Boolean = false): Pair? {
+ val recipient = viewModel.recipient ?: return null
+ val sentTimestamp = SnodeAPI.nowWithOffset
processMessageRequestApproval()
val text = getMessageBody()
val userPublicKey = textSecurePreferences.getLocalNumber()
val isNoteToSelf = (recipient.isContactRecipient && recipient.address.toString() == userPublicKey)
if (text.contains(seed) && !isNoteToSelf && !hasPermissionToSendSeed) {
val dialog = SendSeedDialog { sendTextOnlyMessage(true) }
- return dialog.show(supportFragmentManager, "Send Seed Dialog")
+ dialog.show(supportFragmentManager, "Send Seed Dialog")
+ return null
}
// Create the message
val message = VisibleMessage()
- message.sentTimestamp = System.currentTimeMillis()
+ message.sentTimestamp = sentTimestamp
message.text = text
val outgoingTextMessage = OutgoingTextMessage.from(message, recipient)
// Clear the input bar
@@ -1406,14 +1625,16 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
MessageSender.send(message, recipient.address)
// Send a typing stopped message
ApplicationContext.getInstance(this).typingStatusSender.onTypingStopped(viewModel.threadId)
+ return Pair(recipient.address, sentTimestamp)
}
- private fun sendAttachments(attachments: List, body: String?, quotedMessage: MessageRecord? = null, linkPreview: LinkPreview? = null) {
- val recipient = viewModel.recipient ?: return
+ private fun sendAttachments(attachments: List, body: String?, quotedMessage: MessageRecord? = null, linkPreview: LinkPreview? = null): Pair? {
+ val recipient = viewModel.recipient ?: return null
+ val sentTimestamp = SnodeAPI.nowWithOffset
processMessageRequestApproval()
// Create the message
val message = VisibleMessage()
- message.sentTimestamp = System.currentTimeMillis()
+ message.sentTimestamp = sentTimestamp
message.text = body
val quote = quotedMessage?.let {
val quotedAttachments = (it as? MmsMessageRecord)?.slideDeck?.asAttachments() ?: listOf()
@@ -1447,28 +1668,28 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
MessageSender.send(message, recipient.address, attachments, quote, linkPreview)
// Send a typing stopped message
ApplicationContext.getInstance(this).typingStatusSender.onTypingStopped(viewModel.threadId)
+ return Pair(recipient.address, sentTimestamp)
}
private fun showGIFPicker() {
val hasSeenGIFMetaDataWarning: Boolean = textSecurePreferences.hasSeenGIFMetaDataWarning()
if (!hasSeenGIFMetaDataWarning) {
- val builder = AlertDialog.Builder(this)
- builder.setTitle("Search GIFs?")
- builder.setMessage("You will not have full metadata protection when sending GIFs.")
- builder.setPositiveButton("OK") { dialog: DialogInterface, _: Int ->
- textSecurePreferences.setHasSeenGIFMetaDataWarning()
- AttachmentManager.selectGif(this, PICK_GIF)
- dialog.dismiss()
+ showSessionDialog {
+ title(R.string.giphy_permission_title)
+ text(R.string.giphy_permission_message)
+ button(R.string.continue_2) {
+ textSecurePreferences.setHasSeenGIFMetaDataWarning()
+ selectGif()
+ }
+ cancelButton()
}
- builder.setNegativeButton(
- "Cancel"
- ) { dialog: DialogInterface, _: Int -> dialog.dismiss() }
- builder.create().show()
} else {
- AttachmentManager.selectGif(this, PICK_GIF)
+ selectGif()
}
}
+ private fun selectGif() = AttachmentManager.selectGif(this, PICK_GIF)
+
private fun showDocumentPicker() {
AttachmentManager.selectDocument(this, PICK_DOCUMENT)
}
@@ -1568,7 +1789,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
showVoiceMessageUI()
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
audioRecorder.startRecording()
- stopAudioHandler.postDelayed(stopVoiceMessageRecordingTask, 60000) // Limit voice messages to 1 minute each
+ stopAudioHandler.postDelayed(stopVoiceMessageRecordingTask, 300000) // Limit voice messages to 5 minute each
} else {
Permissions.with(this)
.request(Manifest.permission.RECORD_AUDIO)
@@ -1615,35 +1836,23 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val allHasHash = messages.all { lokiMessageDb.getMessageServerHash(it.id) != null }
if (recipient.isOpenGroupRecipient) {
val messageCount = 1
- val builder = AlertDialog.Builder(this)
- builder.setTitle(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount))
- builder.setMessage(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount))
- builder.setCancelable(true)
- builder.setPositiveButton(R.string.delete) { _, _ ->
- for (message in messages) {
- viewModel.deleteForEveryone(message)
- }
- endActionMode()
+
+ showSessionDialog {
+ title(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount))
+ text(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount))
+ button(R.string.delete) { messages.forEach(viewModel::deleteForEveryone); endActionMode() }
+ cancelButton { endActionMode() }
}
- builder.setNegativeButton(android.R.string.cancel) { dialog, _ ->
- dialog.dismiss()
- endActionMode()
- }
- builder.show()
} else if (allSentByCurrentUser && allHasHash) {
val bottomSheet = DeleteOptionsBottomSheet()
bottomSheet.recipient = recipient
bottomSheet.onDeleteForMeTapped = {
- for (message in messages) {
- viewModel.deleteLocally(message)
- }
+ messages.forEach(viewModel::deleteLocally)
bottomSheet.dismiss()
endActionMode()
}
bottomSheet.onDeleteForEveryoneTapped = {
- for (message in messages) {
- viewModel.deleteForEveryone(message)
- }
+ messages.forEach(viewModel::deleteForEveryone)
bottomSheet.dismiss()
endActionMode()
}
@@ -1654,54 +1863,32 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
bottomSheet.show(supportFragmentManager, bottomSheet.tag)
} else {
val messageCount = 1
- val builder = AlertDialog.Builder(this)
- builder.setTitle(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount))
- builder.setMessage(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount))
- builder.setCancelable(true)
- builder.setPositiveButton(R.string.delete) { _, _ ->
- for (message in messages) {
- viewModel.deleteLocally(message)
- }
- endActionMode()
+
+ showSessionDialog {
+ title(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount))
+ text(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount))
+ button(R.string.delete) { messages.forEach(viewModel::deleteLocally); endActionMode() }
+ cancelButton(::endActionMode)
}
- builder.setNegativeButton(android.R.string.cancel) { dialog, _ ->
- dialog.dismiss()
- endActionMode()
- }
- builder.show()
}
}
override fun banUser(messages: Set) {
- val builder = AlertDialog.Builder(this)
- builder.setTitle(R.string.ConversationFragment_ban_selected_user)
- builder.setMessage("This will ban the selected user from this room. It won't ban them from other rooms.")
- builder.setCancelable(true)
- builder.setPositiveButton(R.string.ban) { _, _ ->
- viewModel.banUser(messages.first().individualRecipient)
- endActionMode()
+ showSessionDialog {
+ title(R.string.ConversationFragment_ban_selected_user)
+ text("This will ban the selected user from this room. It won't ban them from other rooms.")
+ button(R.string.ban) { viewModel.banUser(messages.first().individualRecipient); endActionMode() }
+ cancelButton(::endActionMode)
}
- builder.setNegativeButton(android.R.string.cancel) { dialog, _ ->
- dialog.dismiss()
- endActionMode()
- }
- builder.show()
}
override fun banAndDeleteAll(messages: Set) {
- val builder = AlertDialog.Builder(this)
- builder.setTitle(R.string.ConversationFragment_ban_selected_user)
- builder.setMessage("This will ban the selected user from this room and delete all messages sent by them. It won't ban them from other rooms or delete the messages they sent there.")
- builder.setCancelable(true)
- builder.setPositiveButton(R.string.ban) { _, _ ->
- viewModel.banAndDeleteAll(messages.first().individualRecipient)
- endActionMode()
+ showSessionDialog {
+ title(R.string.ConversationFragment_ban_selected_user)
+ text("This will ban the selected user from this room and delete all messages sent by them. It won't ban them from other rooms or delete the messages they sent there.")
+ button(R.string.ban) { viewModel.banAndDeleteAll(messages.first().individualRecipient); endActionMode() }
+ cancelButton(::endActionMode)
}
- builder.setNegativeButton(android.R.string.cancel) { dialog, _ ->
- dialog.dismiss()
- endActionMode()
- }
- builder.show()
}
override fun copyMessages(messages: Set) {
@@ -1742,6 +1929,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
endActionMode()
}
+ override fun resyncMessage(messages: Set) {
+ messages.iterator().forEach { messageRecord ->
+ ResendMessageUtilities.resend(this, messageRecord, viewModel.blindedPublicKey, isResync = true)
+ }
+ endActionMode()
+ }
+
override fun resendMessage(messages: Set) {
messages.iterator().forEach { messageRecord ->
ResendMessageUtilities.resend(this, messageRecord, viewModel.blindedPublicKey)
@@ -1749,16 +1943,30 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
endActionMode()
}
+ private val handleMessageDetail = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
+ val message = result.data?.extras?.getLong(MESSAGE_TIMESTAMP)
+ ?.let(mmsSmsDb::getMessageForTimestamp)
+
+ val set = setOfNotNull(message)
+
+ when (result.resultCode) {
+ ON_REPLY -> reply(set)
+ ON_RESEND -> resendMessage(set)
+ ON_DELETE -> deleteMessages(set)
+ }
+ }
+
override fun showMessageDetail(messages: Set) {
- val intent = Intent(this, MessageDetailActivity::class.java)
- intent.putExtra(MessageDetailActivity.MESSAGE_TIMESTAMP, messages.first().timestamp)
- push(intent)
+ Intent(this, MessageDetailActivity::class.java)
+ .apply { putExtra(MESSAGE_TIMESTAMP, messages.first().timestamp) }
+ .let { handleMessageDetail.launch(it) }
+
endActionMode()
}
override fun saveAttachment(messages: Set) {
val message = messages.first() as MmsMessageRecord
- SaveAttachmentTask.showWarningDialog(this, { _, _ ->
+ SaveAttachmentTask.showWarningDialog(this) {
Permissions.with(this)
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.maxSdkVersion(Build.VERSION_CODES.P)
@@ -1786,12 +1994,12 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
Toast.LENGTH_LONG).show()
}
.execute()
- })
+ }
}
override fun reply(messages: Set) {
val recipient = viewModel.recipient ?: return
- binding?.inputBar?.draftQuote(recipient, messages.first(), glide)
+ messages.firstOrNull()?.let { binding?.inputBar?.draftQuote(recipient, it, glide) }
endActionMode()
}
@@ -1810,7 +2018,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
private fun sendMediaSavedNotification() {
val recipient = viewModel.recipient ?: return
if (recipient.isGroupRecipient) { return }
- val timestamp = System.currentTimeMillis()
+ val timestamp = SnodeAPI.nowWithOffset
val kind = DataExtractionNotification.Kind.MediaSaved(timestamp)
val message = DataExtractionNotification(kind)
MessageSender.send(message, recipient.address)
@@ -1844,7 +2052,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
if (result == null) return@Observer
if (result.getResults().isNotEmpty()) {
result.getResults()[result.position]?.let {
- jumpToMessage(it.messageRecipient.address, it.sentTimestampMs) {
+ jumpToMessage(it.messageRecipient.address, it.sentTimestampMs, true) {
searchViewModel.onMissingResult() }
}
}
@@ -1881,15 +2089,21 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
this.searchViewModel.onMoveDown()
}
- private fun jumpToMessage(author: Address, timestamp: Long, onMessageNotFound: Runnable?) {
+ private fun jumpToMessage(author: Address, timestamp: Long, highlight: Boolean, onMessageNotFound: Runnable?) {
SimpleTask.run(lifecycle, {
- mmsSmsDb.getMessagePositionInConversation(viewModel.threadId, timestamp, author)
- }) { p: Int -> moveToMessagePosition(p, onMessageNotFound) }
+ mmsSmsDb.getMessagePositionInConversation(viewModel.threadId, timestamp, author, reverseMessageList)
+ }) { p: Int -> moveToMessagePosition(p, highlight, onMessageNotFound) }
}
- private fun moveToMessagePosition(position: Int, onMessageNotFound: Runnable?) {
+ private fun moveToMessagePosition(position: Int, highlight: Boolean, onMessageNotFound: Runnable?) {
if (position >= 0) {
binding?.conversationRecyclerView?.scrollToPosition(position)
+
+ if (highlight) {
+ runOnUiThread {
+ highlightViewAtPosition(position)
+ }
+ }
} else {
onMessageNotFound?.run()
}
@@ -1902,6 +2116,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val selectedItems = setOf(message)
when (action) {
ConversationReactionOverlay.Action.REPLY -> reply(selectedItems)
+ ConversationReactionOverlay.Action.RESYNC -> resyncMessage(selectedItems)
ConversationReactionOverlay.Action.RESEND -> resendMessage(selectedItems)
ConversationReactionOverlay.Action.DOWNLOAD -> saveAttachment(selectedItems)
ConversationReactionOverlay.Action.COPY_MESSAGE -> copyMessages(selectedItems)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt
index 85d3c8e6d..6013af5ba 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt
@@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.conversation.v2
-import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.database.Cursor
@@ -31,10 +30,15 @@ import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
+import org.thoughtcrime.securesms.showSessionDialog
+import java.util.concurrent.atomic.AtomicLong
+import kotlin.math.min
class ConversationAdapter(
context: Context,
cursor: Cursor,
+ originalLastSeen: Long,
+ private val isReversed: Boolean,
private val onItemPress: (MessageRecord, Int, VisibleMessageView, MotionEvent) -> Unit,
private val onItemSwipeToReply: (MessageRecord, Int) -> Unit,
private val onItemLongPress: (MessageRecord, Int, VisibleMessageView) -> Unit,
@@ -52,6 +56,8 @@ class ConversationAdapter(
private val updateQueue = Channel(1024, onBufferOverflow = BufferOverflow.DROP_OLDEST)
private val contactCache = SparseArray(100)
private val contactLoadedCache = SparseBooleanArray(100)
+ private val lastSeen = AtomicLong(originalLastSeen)
+
init {
lifecycleCoroutineScope.launch(IO) {
while (isActive) {
@@ -128,6 +134,7 @@ class ConversationAdapter(
searchQuery,
contact,
senderId,
+ lastSeen.get(),
visibleMessageViewDelegate,
onAttachmentNeedsDownload
)
@@ -146,17 +153,15 @@ class ConversationAdapter(
viewHolder.view.bind(message, messageBefore)
if (message.isCallLog && message.isFirstMissedCall) {
viewHolder.view.setOnClickListener {
- AlertDialog.Builder(context)
- .setTitle(R.string.CallNotificationBuilder_first_call_title)
- .setMessage(R.string.CallNotificationBuilder_first_call_message)
- .setPositiveButton(R.string.activity_settings_title) { _, _ ->
- val intent = Intent(context, PrivacySettingsActivity::class.java)
- context.startActivity(intent)
+ context.showSessionDialog {
+ title(R.string.CallNotificationBuilder_first_call_title)
+ text(R.string.CallNotificationBuilder_first_call_message)
+ button(R.string.activity_settings_title) {
+ Intent(context, PrivacySettingsActivity::class.java)
+ .let(context::startActivity)
}
- .setNeutralButton(R.string.cancel) { d, _ ->
- d.dismiss()
- }
- .show()
+ cancelButton()
+ }
}
} else {
viewHolder.view.setOnClickListener(null)
@@ -185,14 +190,18 @@ class ConversationAdapter(
private fun getMessageBefore(position: Int, cursor: Cursor): MessageRecord? {
// The message that's visually before the current one is actually after the current
// one for the cursor because the layout is reversed
- if (!cursor.moveToPosition(position + 1)) { return null }
+ if (isReversed && !cursor.moveToPosition(position + 1)) { return null }
+ if (!isReversed && !cursor.moveToPosition(position - 1)) { return null }
+
return messageDB.readerFor(cursor).current
}
private fun getMessageAfter(position: Int, cursor: Cursor): MessageRecord? {
// The message that's visually after the current one is actually before the current
// one for the cursor because the layout is reversed
- if (!cursor.moveToPosition(position - 1)) { return null }
+ if (isReversed && !cursor.moveToPosition(position - 1)) { return null }
+ if (!isReversed && !cursor.moveToPosition(position + 1)) { return null }
+
return messageDB.readerFor(cursor).current
}
@@ -219,11 +228,30 @@ class ConversationAdapter(
fun findLastSeenItemPosition(lastSeenTimestamp: Long): Int? {
val cursor = this.cursor
- if (lastSeenTimestamp <= 0L || cursor == null || !isActiveCursor) return null
+ if (cursor == null || !isActiveCursor) return null
+ if (lastSeenTimestamp == 0L) {
+ if (isReversed && cursor.moveToLast()) { return cursor.position }
+ if (!isReversed && cursor.moveToFirst()) { return cursor.position }
+ }
+
+ // Loop from the newest message to the oldest until we find one older (or equal to)
+ // the lastSeenTimestamp, then return that message index
for (i in 0 until itemCount) {
- cursor.moveToPosition(i)
- val message = messageDB.readerFor(cursor).current
- if (message.isOutgoing || message.dateReceived <= lastSeenTimestamp) { return i }
+ if (isReversed) {
+ cursor.moveToPosition(i)
+ val (outgoing, dateSent) = messageDB.timestampAndDirectionForCurrent(cursor)
+ if (outgoing || dateSent <= lastSeenTimestamp) {
+ return i
+ }
+ }
+ else {
+ val index = ((itemCount - 1) - i)
+ cursor.moveToPosition(index)
+ val (outgoing, dateSent) = messageDB.timestampAndDirectionForCurrent(cursor)
+ if (outgoing || dateSent <= lastSeenTimestamp) {
+ return min(itemCount - 1, (index + 1))
+ }
+ }
}
return null
}
@@ -233,8 +261,8 @@ class ConversationAdapter(
if (timestamp <= 0L || cursor == null || !isActiveCursor) return null
for (i in 0 until itemCount) {
cursor.moveToPosition(i)
- val message = messageDB.readerFor(cursor).current
- if (message.dateSent == timestamp) { return i }
+ val (_, dateSent) = messageDB.timestampAndDirectionForCurrent(cursor)
+ if (dateSent == timestamp) { return i }
}
return null
}
@@ -243,4 +271,11 @@ class ConversationAdapter(
this.searchQuery = query
notifyDataSetChanged()
}
+
+ fun getTimestampForItemAt(firstVisiblePosition: Int): Long? {
+ val cursor = this.cursor ?: return null
+ if (!cursor.moveToPosition(firstVisiblePosition)) return null
+ val message = messageDB.readerFor(cursor).current ?: return null
+ return message.timestamp
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java
index 995dcda2f..eee8b5ecd 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java
@@ -81,6 +81,8 @@ public final class ConversationReactionOverlay extends FrameLayout {
private View dropdownAnchor;
private LinearLayout conversationItem;
+ private View conversationBubble;
+ private TextView conversationTimestamp;
private View backgroundView;
private ConstraintLayout foregroundView;
private EmojiImageView[] emojiViews;
@@ -116,6 +118,8 @@ public final class ConversationReactionOverlay extends FrameLayout {
dropdownAnchor = findViewById(R.id.dropdown_anchor);
conversationItem = findViewById(R.id.conversation_item);
+ conversationBubble = conversationItem.findViewById(R.id.conversation_item_bubble);
+ conversationTimestamp = conversationItem.findViewById(R.id.conversation_item_timestamp);
backgroundView = findViewById(R.id.conversation_reaction_scrubber_background);
foregroundView = findViewById(R.id.conversation_reaction_scrubber_foreground);
@@ -165,10 +169,8 @@ public final class ConversationReactionOverlay extends FrameLayout {
Bitmap conversationItemSnapshot = selectedConversationModel.getBitmap();
- View conversationBubble = conversationItem.findViewById(R.id.conversation_item_bubble);
conversationBubble.setLayoutParams(new LinearLayout.LayoutParams(conversationItemSnapshot.getWidth(), conversationItemSnapshot.getHeight()));
conversationBubble.setBackground(new BitmapDrawable(getResources(), conversationItemSnapshot));
- TextView conversationTimestamp = conversationItem.findViewById(R.id.conversation_item_timestamp);
conversationTimestamp.setText(DateUtils.getDisplayFormattedTimeSpanString(getContext(), Locale.getDefault(), messageRecord.getTimestamp()));
updateConversationTimestamp(messageRecord);
@@ -190,12 +192,8 @@ public final class ConversationReactionOverlay extends FrameLayout {
}
private void updateConversationTimestamp(MessageRecord message) {
- View bubble = conversationItem.findViewById(R.id.conversation_item_bubble);
- View timestamp = conversationItem.findViewById(R.id.conversation_item_timestamp);
- conversationItem.removeAllViewsInLayout();
- conversationItem.addView(message.isOutgoing() ? timestamp : bubble);
- conversationItem.addView(message.isOutgoing() ? bubble : timestamp);
- conversationItem.requestLayout();
+ if (message.isOutgoing()) conversationBubble.bringToFront();
+ else conversationTimestamp.bringToFront();
}
private void showAfterLayout(@NonNull MessageRecord messageRecord,
@@ -203,10 +201,11 @@ public final class ConversationReactionOverlay extends FrameLayout {
boolean isMessageOnLeft) {
contextMenu = new ConversationContextMenu(dropdownAnchor, getMenuActionItems(messageRecord));
- float itemX = isMessageOnLeft ? scrubberHorizontalMargin :
+ float endX = isMessageOnLeft ? scrubberHorizontalMargin :
selectedConversationModel.getBubbleX() - conversationItem.getWidth() + selectedConversationModel.getBubbleWidth();
- conversationItem.setX(itemX);
- conversationItem.setY(selectedConversationModel.getBubbleY() - statusBarHeight);
+ float endY = selectedConversationModel.getBubbleY() - statusBarHeight;
+ conversationItem.setX(endX);
+ conversationItem.setY(endY);
Bitmap conversationItemSnapshot = selectedConversationModel.getBitmap();
boolean isWideLayout = contextMenu.getMaxWidth() + scrubberWidth < getWidth();
@@ -214,8 +213,6 @@ public final class ConversationReactionOverlay extends FrameLayout {
int overlayHeight = getHeight();
int bubbleWidth = selectedConversationModel.getBubbleWidth();
- float endX = itemX;
- float endY = conversationItem.getY();
float endApparentTop = endY;
float endScale = 1f;
@@ -265,9 +262,7 @@ public final class ConversationReactionOverlay extends FrameLayout {
}
} else {
endY = overlayHeight - contextMenu.getMaxHeight() - menuPadding - conversationItemSnapshot.getHeight();
-
- float contextMenuTop = endY + conversationItemSnapshot.getHeight();
- reactionBarBackgroundY = getReactionBarOffsetForTouch(selectedConversationModel.getBubbleY(), contextMenuTop, menuPadding, reactionBarOffset, reactionBarHeight, reactionBarTopPadding, endY);
+ reactionBarBackgroundY = endY - reactionBarHeight - menuPadding;
}
endApparentTop = endY;
@@ -354,11 +349,14 @@ public final class ConversationReactionOverlay extends FrameLayout {
int revealDuration = getContext().getResources().getInteger(R.integer.reaction_scrubber_reveal_duration);
+ conversationBubble.animate()
+ .scaleX(endScale)
+ .scaleY(endScale)
+ .setDuration(revealDuration);
+
conversationItem.animate()
.x(endX)
.y(endY)
- .scaleX(endScale)
- .scaleY(endScale)
.setDuration(revealDuration);
}
@@ -660,10 +658,15 @@ public final class ConversationReactionOverlay extends FrameLayout {
String userPublicKey = TextSecurePreferences.getLocalNumber(getContext());
// Select message
- items.add(new ActionItem(R.attr.menu_select_icon, getContext().getResources().getString(R.string.conversation_context__menu_select), () -> handleActionItemClicked(Action.SELECT)));
+ items.add(new ActionItem(R.attr.menu_select_icon, getContext().getResources().getString(R.string.conversation_context__menu_select), () -> handleActionItemClicked(Action.SELECT),
+ getContext().getResources().getString(R.string.AccessibilityId_select)));
// Reply
- if (!message.isPending() && !message.isFailed()) {
- items.add(new ActionItem(R.attr.menu_reply_icon, getContext().getResources().getString(R.string.conversation_context__menu_reply), () -> handleActionItemClicked(Action.REPLY)));
+ boolean canWrite = openGroup == null || openGroup.getCanWrite();
+ if (canWrite && !message.isPending() && !message.isFailed()) {
+ items.add(
+ new ActionItem(R.attr.menu_reply_icon, getContext().getResources().getString(R.string.conversation_context__menu_reply), () -> handleActionItemClicked(Action.REPLY),
+ getContext().getResources().getString(R.string.AccessibilityId_reply_message))
+ );
}
// Copy message text
if (!containsControlMessage && hasText) {
@@ -671,11 +674,17 @@ public final class ConversationReactionOverlay extends FrameLayout {
}
// Copy Session ID
if (recipient.isGroupRecipient() && !recipient.isOpenGroupRecipient() && !message.getRecipient().getAddress().toString().equals(userPublicKey)) {
- items.add(new ActionItem(R.attr.menu_copy_icon, getContext().getResources().getString(R.string.activity_conversation_menu_copy_session_id), () -> handleActionItemClicked(Action.COPY_SESSION_ID)));
+ items.add(new ActionItem(
+ R.attr.menu_copy_icon, getContext().getResources().getString(R.string.activity_conversation_menu_copy_session_id), () -> handleActionItemClicked(Action.COPY_SESSION_ID))
+ );
}
// Delete message
if (ConversationMenuItemHelper.userCanDeleteSelectedItems(getContext(), message, openGroup, userPublicKey, blindedPublicKey)) {
- items.add(new ActionItem(R.attr.menu_trash_icon, getContext().getResources().getString(R.string.delete), () -> handleActionItemClicked(Action.DELETE)));
+ items.add(new ActionItem(R.attr.menu_trash_icon, getContext().getResources().getString(R.string.delete),
+ () -> handleActionItemClicked(Action.DELETE),
+ getContext().getResources().getString(R.string.AccessibilityId_delete_message)
+ )
+ );
}
// Ban user
if (ConversationMenuItemHelper.userCanBanSelectedUsers(getContext(), message, openGroup, userPublicKey, blindedPublicKey)) {
@@ -686,16 +695,20 @@ public final class ConversationReactionOverlay extends FrameLayout {
items.add(new ActionItem(R.attr.menu_trash_icon, getContext().getResources().getString(R.string.conversation_context__menu_ban_and_delete_all), () -> handleActionItemClicked(Action.BAN_AND_DELETE_ALL)));
}
// Message detail
- if (message.isFailed()) {
- items.add(new ActionItem(R.attr.menu_info_icon, getContext().getResources().getString(R.string.conversation_context__menu_message_details), () -> handleActionItemClicked(Action.VIEW_INFO)));
- }
+ items.add(new ActionItem(R.attr.menu_info_icon, getContext().getResources().getString(R.string.conversation_context__menu_message_details), () -> handleActionItemClicked(Action.VIEW_INFO)));
// Resend
if (message.isFailed()) {
items.add(new ActionItem(R.attr.menu_reply_icon, getContext().getResources().getString(R.string.conversation_context__menu_resend_message), () -> handleActionItemClicked(Action.RESEND)));
}
+ // Resync
+ if (message.isSyncFailed()) {
+ items.add(new ActionItem(R.attr.menu_reply_icon, getContext().getResources().getString(R.string.conversation_context__menu_resync_message), () -> handleActionItemClicked(Action.RESYNC)));
+ }
// Save media
if (message.isMms() && ((MediaMmsMessageRecord)message).containsMediaSlide()) {
- items.add(new ActionItem(R.attr.menu_save_icon, getContext().getResources().getString(R.string.conversation_context_image__save_attachment), () -> handleActionItemClicked(Action.DOWNLOAD)));
+ items.add(new ActionItem(R.attr.menu_save_icon, getContext().getResources().getString(R.string.conversation_context_image__save_attachment), () -> handleActionItemClicked(Action.DOWNLOAD),
+ getContext().getResources().getString(R.string.AccessibilityId_save_attachment))
+ );
}
backgroundView.setVisibility(View.VISIBLE);
@@ -876,6 +889,7 @@ public final class ConversationReactionOverlay extends FrameLayout {
public enum Action {
REPLY,
RESEND,
+ RESYNC,
DOWNLOAD,
COPY_MESSAGE,
COPY_SESSION_ID,
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt
index 4a22113d2..94fe1e232 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt
@@ -1,8 +1,10 @@
package org.thoughtcrime.securesms.conversation.v2
+import android.content.ContentResolver
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
+import app.cash.copper.flow.observeQuery
import com.goterl.lazysodium.utils.KeyPair
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
@@ -20,6 +22,8 @@ import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.Log
+import org.thoughtcrime.securesms.database.DatabaseContentProviders
+import org.thoughtcrime.securesms.database.Storage
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.repository.ConversationRepository
import java.util.UUID
@@ -27,18 +31,28 @@ import java.util.UUID
class ConversationViewModel(
val threadId: Long,
val edKeyPair: KeyPair?,
+ private val contentResolver: ContentResolver,
private val repository: ConversationRepository,
private val storage: StorageProtocol
) : ViewModel() {
- private val _uiState = MutableStateFlow(ConversationUiState())
+ val showSendAfterApprovalText: Boolean
+ get() = recipient?.run { isContactRecipient && !isLocalNumber && !hasApprovedMe() } ?: false
+
+ private val _uiState = MutableStateFlow(ConversationUiState(conversationExists = true))
val uiState: StateFlow = _uiState
+ private var _recipient: RetrieveOnce = RetrieveOnce {
+ repository.maybeGetRecipientForThreadId(threadId)
+ }
val recipient: Recipient?
- get() = repository.maybeGetRecipientForThreadId(threadId)
+ get() = _recipient.value
+ private var _openGroup: RetrieveOnce = RetrieveOnce {
+ storage.getOpenGroup(threadId)
+ }
val openGroup: OpenGroup?
- get() = storage.getOpenGroup(threadId)
+ get() = _openGroup.value
val serverCapabilities: List
get() = openGroup?.let { storage.getServerCapabilities(it.server) } ?: listOf()
@@ -49,6 +63,18 @@ class ConversationViewModel(
?.let { SessionId(IdPrefix.BLINDED, it) }?.hexString
}
+ init {
+ viewModelScope.launch(Dispatchers.IO) {
+ contentResolver.observeQuery(DatabaseContentProviders.Conversation.getUriForThread(threadId))
+ .collect {
+ val recipientExists = storage.getRecipientForThread(threadId) != null
+ if (!recipientExists && _uiState.value.conversationExists) {
+ _uiState.update { it.copy(conversationExists = false) }
+ }
+ }
+ }
+ }
+
fun saveDraft(text: String) {
GlobalScope.launch(Dispatchers.IO) {
repository.saveDraft(threadId, text)
@@ -170,21 +196,26 @@ class ConversationViewModel(
return repository.hasReceived(threadId)
}
+ fun updateRecipient() {
+ _recipient.updateTo(repository.maybeGetRecipientForThreadId(threadId))
+ }
+
@dagger.assisted.AssistedFactory
interface AssistedFactory {
- fun create(threadId: Long, edKeyPair: KeyPair?): Factory
+ fun create(threadId: Long, edKeyPair: KeyPair?, contentResolver: ContentResolver): Factory
}
@Suppress("UNCHECKED_CAST")
class Factory @AssistedInject constructor(
@Assisted private val threadId: Long,
@Assisted private val edKeyPair: KeyPair?,
+ @Assisted private val contentResolver: ContentResolver,
private val repository: ConversationRepository,
private val storage: StorageProtocol
) : ViewModelProvider.Factory {
override fun create(modelClass: Class): T {
- return ConversationViewModel(threadId, edKeyPair, repository, storage) as T
+ return ConversationViewModel(threadId, edKeyPair, contentResolver, repository, storage) as T
}
}
}
@@ -193,5 +224,22 @@ data class UiMessage(val id: Long, val message: String)
data class ConversationUiState(
val uiMessages: List = emptyList(),
- val isMessageRequestAccepted: Boolean? = null
+ val isMessageRequestAccepted: Boolean? = null,
+ val conversationExists: Boolean
)
+
+data class RetrieveOnce(val retrieval: () -> T?) {
+ private var triedToRetrieve: Boolean = false
+ private var _value: T? = null
+
+ val value: T?
+ get() {
+ if (triedToRetrieve) { return _value }
+
+ triedToRetrieve = true
+ _value = retrieval()
+ return _value
+ }
+
+ fun updateTo(value: T?) { _value = value }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt
index 66f33cf29..b6212b854 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt
@@ -69,7 +69,6 @@ class DeleteOptionsBottomSheet : BottomSheetDialogFragment(), View.OnClickListen
override fun onStart() {
super.onStart()
val window = dialog?.window ?: return
- val isLightMode = UiModeUtilities.isDayUiMode(requireContext())
- window.setDimAmount(if (isLightMode) 0.1f else 0.75f)
+ window.setDimAmount(0.6f)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt
index d945b6a0a..b0d5e992a 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt
@@ -1,98 +1,401 @@
package org.thoughtcrime.securesms.conversation.v2
+import android.annotation.SuppressLint
+import android.content.Intent
import android.os.Bundle
-import android.view.View
-import androidx.core.view.isVisible
+import android.view.LayoutInflater
+import android.view.MotionEvent.ACTION_UP
+import androidx.activity.viewModels
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.FlowRow
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.PagerState
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Icon
+import androidx.compose.material.LocalTextStyle
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.lifecycleScope
+import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
+import com.bumptech.glide.integration.compose.GlideImage
import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.launch
import network.loki.messenger.R
-import network.loki.messenger.databinding.ActivityMessageDetailBinding
-import org.session.libsession.database.StorageProtocol
-import org.session.libsession.messaging.MessagingModuleConfiguration
-import org.session.libsession.messaging.open_groups.OpenGroupApi
-import org.session.libsession.messaging.utilities.SessionId
-import org.session.libsession.messaging.utilities.SodiumUtilities
-import org.session.libsession.utilities.Address
-import org.session.libsession.utilities.ExpirationUtil
-import org.session.libsession.utilities.TextSecurePreferences
-import org.session.libsignal.utilities.IdPrefix
+import network.loki.messenger.databinding.ViewVisibleMessageContentBinding
+import org.thoughtcrime.securesms.MediaPreviewActivity.getPreviewIntent
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
-import org.thoughtcrime.securesms.conversation.v2.utilities.ResendMessageUtilities
-import org.thoughtcrime.securesms.database.model.MessageRecord
-import org.thoughtcrime.securesms.dependencies.DatabaseComponent
-import org.thoughtcrime.securesms.util.DateUtils
-import java.text.SimpleDateFormat
-import java.util.*
+import org.thoughtcrime.securesms.database.Storage
+import org.thoughtcrime.securesms.ui.AppTheme
+import org.thoughtcrime.securesms.ui.Avatar
+import org.thoughtcrime.securesms.ui.CarouselNextButton
+import org.thoughtcrime.securesms.ui.CarouselPrevButton
+import org.thoughtcrime.securesms.ui.Cell
+import org.thoughtcrime.securesms.ui.CellNoMargin
+import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin
+import org.thoughtcrime.securesms.ui.Divider
+import org.thoughtcrime.securesms.ui.GetString
+import org.thoughtcrime.securesms.ui.HorizontalPagerIndicator
+import org.thoughtcrime.securesms.ui.ItemButton
+import org.thoughtcrime.securesms.ui.PreviewTheme
+import org.thoughtcrime.securesms.ui.ThemeResPreviewParameterProvider
+import org.thoughtcrime.securesms.ui.TitledText
+import org.thoughtcrime.securesms.ui.blackAlpha40
+import org.thoughtcrime.securesms.ui.colorDestructive
+import org.thoughtcrime.securesms.ui.destructiveButtonColors
import javax.inject.Inject
@AndroidEntryPoint
-class MessageDetailActivity: PassphraseRequiredActionBarActivity() {
- private lateinit var binding: ActivityMessageDetailBinding
- var messageRecord: MessageRecord? = null
+class MessageDetailActivity : PassphraseRequiredActionBarActivity() {
@Inject
lateinit var storage: StorageProtocol
- // region Settings
+ private val viewModel: MessageDetailsViewModel by viewModels()
+
companion object {
// Extras
const val MESSAGE_TIMESTAMP = "message_timestamp"
+
+ const val ON_REPLY = 1
+ const val ON_RESEND = 2
+ const val ON_DELETE = 3
}
- // endregion
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
super.onCreate(savedInstanceState, ready)
- binding = ActivityMessageDetailBinding.inflate(layoutInflater)
- setContentView(binding.root)
+
title = resources.getString(R.string.conversation_context__menu_message_details)
- val timestamp = intent.getLongExtra(MESSAGE_TIMESTAMP, -1L)
- // We only show this screen for messages fail to send,
- // so the author of the messages must be the current user.
- val author = Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!)
- messageRecord = DatabaseComponent.get(this).mmsSmsDatabase().getMessageFor(timestamp, author) ?: run {
- finish()
- return
- }
- val threadId = messageRecord!!.threadId
- val openGroup = storage.getOpenGroup(threadId)
- val blindedKey = openGroup?.let { group ->
- val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair() ?: return@let null
- val blindingEnabled = storage.getServerCapabilities(group.server).contains(OpenGroupApi.Capability.BLIND.name.lowercase())
- if (blindingEnabled) {
- SodiumUtilities.blindedKeyPair(group.publicKey, userEdKeyPair)?.publicKey?.asBytes
- ?.let { SessionId(IdPrefix.BLINDED, it) }?.hexString
- } else null
- }
- updateContent()
- binding.resendButton.setOnClickListener {
- ResendMessageUtilities.resend(this, messageRecord!!, blindedKey)
- finish()
+
+ viewModel.timestamp = intent.getLongExtra(MESSAGE_TIMESTAMP, -1L)
+
+ ComposeView(this)
+ .apply { setContent { MessageDetailsScreen() } }
+ .let(::setContentView)
+
+ lifecycleScope.launch {
+ viewModel.eventFlow.collect {
+ when (it) {
+ Event.Finish -> finish()
+ is Event.StartMediaPreview -> startActivity(
+ getPreviewIntent(this@MessageDetailActivity, it.args)
+ )
+ }
+ }
}
}
- fun updateContent() {
- val dateLocale = Locale.getDefault()
- val dateFormatter: SimpleDateFormat = DateUtils.getDetailedDateFormatter(this, dateLocale)
- binding.sentTime.text = dateFormatter.format(Date(messageRecord!!.dateSent))
-
- val errorMessage = DatabaseComponent.get(this).lokiMessageDatabase().getErrorMessage(messageRecord!!.getId())
- if (errorMessage != null) {
- binding.errorMessage.text = errorMessage
- binding.resendContainer.isVisible = true
- binding.errorContainer.isVisible = true
- } else {
- binding.errorContainer.isVisible = false
- binding.resendContainer.isVisible = false
- }
-
- if (messageRecord!!.expiresIn <= 0 || messageRecord!!.expireStarted <= 0) {
- binding.expiresContainer.visibility = View.GONE
- } else {
- binding.expiresContainer.visibility = View.VISIBLE
- val elapsed = System.currentTimeMillis() - messageRecord!!.expireStarted
- val remaining = messageRecord!!.expiresIn - elapsed
-
- val duration = ExpirationUtil.getExpirationDisplayValue(this, Math.max((remaining / 1000).toInt(), 1))
- binding.expiresIn.text = duration
+ @Composable
+ private fun MessageDetailsScreen() {
+ val state by viewModel.stateFlow.collectAsState()
+ AppTheme {
+ MessageDetails(
+ state = state,
+ onReply = { setResultAndFinish(ON_REPLY) },
+ onResend = state.error?.let { { setResultAndFinish(ON_RESEND) } },
+ onDelete = { setResultAndFinish(ON_DELETE) },
+ onClickImage = { viewModel.onClickImage(it) },
+ onAttachmentNeedsDownload = viewModel::onAttachmentNeedsDownload,
+ )
}
}
-}
\ No newline at end of file
+
+ private fun setResultAndFinish(code: Int) {
+ Bundle().apply { putLong(MESSAGE_TIMESTAMP, viewModel.timestamp) }
+ .let(Intent()::putExtras)
+ .let { setResult(code, it) }
+
+ finish()
+ }
+}
+
+@SuppressLint("ClickableViewAccessibility")
+@Composable
+fun MessageDetails(
+ state: MessageDetailsState,
+ onReply: () -> Unit = {},
+ onResend: (() -> Unit)? = null,
+ onDelete: () -> Unit = {},
+ onClickImage: (Int) -> Unit = {},
+ onAttachmentNeedsDownload: (Long, Long) -> Unit = { _, _ -> }
+) {
+ Column(
+ modifier = Modifier
+ .verticalScroll(rememberScrollState())
+ .padding(vertical = 16.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ state.record?.let { message ->
+ AndroidView(
+ modifier = Modifier.padding(horizontal = 32.dp),
+ factory = {
+ ViewVisibleMessageContentBinding.inflate(LayoutInflater.from(it)).mainContainerConstraint.apply {
+ bind(
+ message,
+ thread = state.thread!!,
+ onAttachmentNeedsDownload = onAttachmentNeedsDownload,
+ suppressThumbnails = true
+ )
+
+ setOnTouchListener { _, event ->
+ if (event.actionMasked == ACTION_UP) onContentClick(event)
+ true
+ }
+ }
+ }
+ )
+ }
+ Carousel(state.imageAttachments) { onClickImage(it) }
+ state.nonImageAttachmentFileDetails?.let { FileDetails(it) }
+ CellMetadata(state)
+ CellButtons(
+ onReply,
+ onResend,
+ onDelete,
+ )
+ }
+}
+
+@Composable
+fun CellMetadata(
+ state: MessageDetailsState,
+) {
+ state.apply {
+ if (listOfNotNull(sent, received, error, senderInfo).isEmpty()) return
+ CellWithPaddingAndMargin {
+ Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
+ TitledText(sent)
+ TitledText(received)
+ TitledErrorText(error)
+ senderInfo?.let {
+ TitledView(state.fromTitle) {
+ Row {
+ sender?.let { Avatar(it) }
+ TitledMonospaceText(it)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun CellButtons(
+ onReply: () -> Unit = {},
+ onResend: (() -> Unit)? = null,
+ onDelete: () -> Unit = {},
+) {
+ Cell {
+ Column {
+ ItemButton(
+ stringResource(R.string.reply),
+ R.drawable.ic_message_details__reply,
+ onClick = onReply
+ )
+ Divider()
+ onResend?.let {
+ ItemButton(
+ stringResource(R.string.resend),
+ R.drawable.ic_message_details__refresh,
+ onClick = it
+ )
+ Divider()
+ }
+ ItemButton(
+ stringResource(R.string.delete),
+ R.drawable.ic_message_details__trash,
+ colors = destructiveButtonColors(),
+ onClick = onDelete
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+fun Carousel(attachments: List, onClick: (Int) -> Unit) {
+ if (attachments.isEmpty()) return
+
+ val pagerState = rememberPagerState { attachments.size }
+
+ Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
+ Row {
+ CarouselPrevButton(pagerState)
+ Box(modifier = Modifier.weight(1f)) {
+ CellCarousel(pagerState, attachments, onClick)
+ HorizontalPagerIndicator(pagerState)
+ ExpandButton(
+ modifier = Modifier
+ .align(Alignment.BottomEnd)
+ .padding(8.dp)
+ ) { onClick(pagerState.currentPage) }
+ }
+ CarouselNextButton(pagerState)
+ }
+ attachments.getOrNull(pagerState.currentPage)?.fileDetails?.let { FileDetails(it) }
+ }
+}
+
+@OptIn(
+ ExperimentalFoundationApi::class,
+ ExperimentalGlideComposeApi::class
+)
+@Composable
+private fun CellCarousel(
+ pagerState: PagerState,
+ attachments: List,
+ onClick: (Int) -> Unit
+) {
+ CellNoMargin {
+ HorizontalPager(state = pagerState) { i ->
+ GlideImage(
+ contentScale = ContentScale.Crop,
+ modifier = Modifier
+ .aspectRatio(1f)
+ .clickable { onClick(i) },
+ model = attachments[i].uri,
+ contentDescription = attachments[i].fileName ?: stringResource(id = R.string.image)
+ )
+ }
+ }
+}
+
+@Composable
+fun ExpandButton(modifier: Modifier = Modifier, onClick: () -> Unit) {
+ Surface(
+ shape = CircleShape,
+ color = blackAlpha40,
+ modifier = modifier,
+ contentColor = Color.White,
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_expand),
+ contentDescription = stringResource(id = R.string.expand),
+ modifier = Modifier.clickable { onClick() },
+ )
+ }
+}
+
+
+@Preview
+@Composable
+fun PreviewMessageDetails(
+ @PreviewParameter(ThemeResPreviewParameterProvider::class) themeResId: Int
+) {
+ PreviewTheme(themeResId) {
+ MessageDetails(
+ state = MessageDetailsState(
+ nonImageAttachmentFileDetails = listOf(
+ TitledText(R.string.message_details_header__file_id, "Screen Shot 2023-07-06 at 11.35.50 am.png"),
+ TitledText(R.string.message_details_header__file_type, "image/png"),
+ TitledText(R.string.message_details_header__file_size, "195.6kB"),
+ TitledText(R.string.message_details_header__resolution, "342x312"),
+ ),
+ sent = TitledText(R.string.message_details_header__sent, "6:12 AM Tue, 09/08/2022"),
+ received = TitledText(R.string.message_details_header__received, "6:12 AM Tue, 09/08/2022"),
+ error = TitledText(R.string.message_details_header__error, "Message failed to send"),
+ senderInfo = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54"),
+ )
+ )
+ }
+}
+
+@OptIn(ExperimentalLayoutApi::class)
+@Composable
+fun FileDetails(fileDetails: List) {
+ if (fileDetails.isEmpty()) return
+
+ CellWithPaddingAndMargin(padding = 0.dp) {
+ FlowRow(
+ modifier = Modifier.padding(vertical = 24.dp, horizontal = 12.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ fileDetails.forEach {
+ BoxWithConstraints {
+ TitledText(
+ it,
+ modifier = Modifier
+ .widthIn(min = maxWidth.div(2))
+ .padding(horizontal = 12.dp)
+ .width(IntrinsicSize.Max)
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun TitledErrorText(titledText: TitledText?) {
+ TitledText(
+ titledText,
+ valueStyle = LocalTextStyle.current.copy(color = colorDestructive)
+ )
+}
+
+@Composable
+fun TitledMonospaceText(titledText: TitledText?) {
+ TitledText(
+ titledText,
+ valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace)
+ )
+}
+
+@Composable
+fun TitledText(
+ titledText: TitledText?,
+ modifier: Modifier = Modifier,
+ valueStyle: TextStyle = LocalTextStyle.current,
+) {
+ titledText?.apply {
+ TitledView(title, modifier) {
+ Text(text, style = valueStyle, modifier = Modifier.fillMaxWidth())
+ }
+ }
+}
+
+@Composable
+fun TitledView(title: GetString, modifier: Modifier = Modifier, content: @Composable () -> Unit) {
+ Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) {
+ Title(title)
+ content()
+ }
+}
+
+@Composable
+fun Title(title: GetString) {
+ Text(title.string(), fontWeight = FontWeight.Bold)
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt
new file mode 100644
index 000000000..a73fe4113
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt
@@ -0,0 +1,159 @@
+package org.thoughtcrime.securesms.conversation.v2
+
+import android.net.Uri
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.launch
+import network.loki.messenger.R
+import org.session.libsession.messaging.jobs.AttachmentDownloadJob
+import org.session.libsession.messaging.jobs.JobQueue
+import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress
+import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
+import org.session.libsession.utilities.Util
+import org.session.libsession.utilities.recipients.Recipient
+import org.thoughtcrime.securesms.MediaPreviewArgs
+import org.thoughtcrime.securesms.database.AttachmentDatabase
+import org.thoughtcrime.securesms.database.LokiMessageDatabase
+import org.thoughtcrime.securesms.database.MmsSmsDatabase
+import org.thoughtcrime.securesms.database.ThreadDatabase
+import org.thoughtcrime.securesms.database.model.MessageRecord
+import org.thoughtcrime.securesms.database.model.MmsMessageRecord
+import org.thoughtcrime.securesms.mms.ImageSlide
+import org.thoughtcrime.securesms.mms.Slide
+import org.thoughtcrime.securesms.ui.GetString
+import org.thoughtcrime.securesms.ui.TitledText
+import java.util.Date
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+@HiltViewModel
+class MessageDetailsViewModel @Inject constructor(
+ private val attachmentDb: AttachmentDatabase,
+ private val lokiMessageDatabase: LokiMessageDatabase,
+ private val mmsSmsDatabase: MmsSmsDatabase,
+ private val threadDb: ThreadDatabase,
+) : ViewModel() {
+
+ private val state = MutableStateFlow(MessageDetailsState())
+ val stateFlow = state.asStateFlow()
+
+ private val event = Channel()
+ val eventFlow = event.receiveAsFlow()
+
+ var timestamp: Long = 0L
+ set(value) {
+ field = value
+ val record = mmsSmsDatabase.getMessageForTimestamp(timestamp)
+
+ if (record == null) {
+ viewModelScope.launch { event.send(Event.Finish) }
+ return
+ }
+
+ val mmsRecord = record as? MmsMessageRecord
+
+ state.value = record.run {
+ val slides = mmsRecord?.slideDeck?.slides ?: emptyList()
+
+ MessageDetailsState(
+ attachments = slides.map(::Attachment),
+ record = record,
+ sent = dateSent.let(::Date).toString().let { TitledText(R.string.message_details_header__sent, it) },
+ received = dateReceived.let(::Date).toString().let { TitledText(R.string.message_details_header__received, it) },
+ error = lokiMessageDatabase.getErrorMessage(id)?.let { TitledText(R.string.message_details_header__error, it) },
+ senderInfo = individualRecipient.run { name?.let { TitledText(it, address.serialize()) } },
+ sender = individualRecipient,
+ thread = threadDb.getRecipientForThreadId(threadId)!!,
+ )
+ }
+ }
+
+ private val Slide.details: List
+ get() = listOfNotNull(
+ fileName.orNull()?.let { TitledText(R.string.message_details_header__file_id, it) },
+ TitledText(R.string.message_details_header__file_type, asAttachment().contentType),
+ TitledText(R.string.message_details_header__file_size, Util.getPrettyFileSize(fileSize)),
+ takeIf { it is ImageSlide }
+ ?.let(Slide::asAttachment)
+ ?.run { "${width}x$height" }
+ ?.let { TitledText(R.string.message_details_header__resolution, it) },
+ attachmentDb.duration(this)?.let { TitledText(R.string.message_details_header__duration, it) },
+ )
+
+ private fun AttachmentDatabase.duration(slide: Slide): String? =
+ slide.takeIf { it.hasAudio() }
+ ?.run { asAttachment() as? DatabaseAttachment }
+ ?.run { getAttachmentAudioExtras(attachmentId)?.durationMs }
+ ?.takeIf { it > 0 }
+ ?.let {
+ String.format(
+ "%01d:%02d",
+ TimeUnit.MILLISECONDS.toMinutes(it),
+ TimeUnit.MILLISECONDS.toSeconds(it) % 60
+ )
+ }
+
+ fun Attachment(slide: Slide): Attachment =
+ Attachment(slide.details, slide.fileName.orNull(), slide.uri, slide is ImageSlide)
+
+ fun onClickImage(index: Int) {
+ val state = state.value ?: return
+ val mmsRecord = state.mmsRecord ?: return
+ val slide = mmsRecord.slideDeck.slides[index] ?: return
+ // only open to downloaded images
+ if (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED) {
+ // Restart download here (on IO thread)
+ (slide.asAttachment() as? DatabaseAttachment)?.let { attachment ->
+ onAttachmentNeedsDownload(attachment.attachmentId.rowId, state.mmsRecord.getId())
+ }
+ }
+
+ if (slide.isInProgress) return
+
+ viewModelScope.launch {
+ MediaPreviewArgs(slide, state.mmsRecord, state.thread)
+ .let(Event::StartMediaPreview)
+ .let { event.send(it) }
+ }
+ }
+
+ fun onAttachmentNeedsDownload(attachmentId: Long, mmsId: Long) {
+ viewModelScope.launch(Dispatchers.IO) {
+ JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId))
+ }
+ }
+}
+
+data class MessageDetailsState(
+ val attachments: List = emptyList(),
+ val imageAttachments: List = attachments.filter { it.hasImage },
+ val nonImageAttachmentFileDetails: List? = attachments.firstOrNull { !it.hasImage }?.fileDetails,
+ val record: MessageRecord? = null,
+ val mmsRecord: MmsMessageRecord? = record as? MmsMessageRecord,
+ val sent: TitledText? = null,
+ val received: TitledText? = null,
+ val error: TitledText? = null,
+ val senderInfo: TitledText? = null,
+ val sender: Recipient? = null,
+ val thread: Recipient? = null,
+) {
+ val fromTitle = GetString(R.string.message_details_header__from)
+}
+
+data class Attachment(
+ val fileDetails: List,
+ val fileName: String?,
+ val uri: Uri?,
+ val hasImage: Boolean
+)
+
+sealed class Event {
+ object Finish: Event()
+ data class StartMediaPreview(val args: MediaPreviewArgs): Event()
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ModalUrlBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ModalUrlBottomSheet.kt
index 28c86b331..54deea1c8 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ModalUrlBottomSheet.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ModalUrlBottomSheet.kt
@@ -60,8 +60,7 @@ class ModalUrlBottomSheet(private val url: String): BottomSheetDialogFragment(),
override fun onStart() {
super.onStart()
val window = dialog?.window ?: return
- val isLightMode = UiModeUtilities.isDayUiMode(requireContext())
- window.setDimAmount(if (isLightMode) 0.1f else 0.75f)
+ window.setDimAmount(0.6f)
}
override fun onClick(v: View?) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/WindowUtil.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/WindowUtil.java
index 4bff4e76a..6083bb267 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/WindowUtil.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/WindowUtil.java
@@ -38,14 +38,10 @@ public final class WindowUtil {
}
public static void setNavigationBarColor(@NonNull Window window, @ColorInt int color) {
- if (Build.VERSION.SDK_INT < 21) return;
-
window.setNavigationBarColor(color);
}
public static void setLightStatusBarFromTheme(@NonNull Activity activity) {
- if (Build.VERSION.SDK_INT < 23) return;
-
final boolean isLightStatusBar = ThemeUtil.getThemedBoolean(activity, android.R.attr.windowLightStatusBar);
if (isLightStatusBar) setLightStatusBar(activity.getWindow());
@@ -53,20 +49,14 @@ public final class WindowUtil {
}
public static void clearLightStatusBar(@NonNull Window window) {
- if (Build.VERSION.SDK_INT < 23) return;
-
clearSystemUiFlags(window, View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
public static void setLightStatusBar(@NonNull Window window) {
- if (Build.VERSION.SDK_INT < 23) return;
-
setSystemUiFlags(window, View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
public static void setStatusBarColor(@NonNull Window window, @ColorInt int color) {
- if (Build.VERSION.SDK_INT < 21) return;
-
window.setStatusBarColor(color);
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt
index 834b77ecc..d54426391 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt
@@ -28,11 +28,10 @@ class MentionCandidateView : LinearLayout {
private fun update() = with(binding) {
mentionCandidateNameTextView.text = mentionCandidate.displayName
- profilePictureView.root.publicKey = mentionCandidate.publicKey
- profilePictureView.root.displayName = mentionCandidate.displayName
- profilePictureView.root.additionalPublicKey = null
- profilePictureView.root.glide = glide!!
- profilePictureView.root.update()
+ profilePictureView.publicKey = mentionCandidate.publicKey
+ profilePictureView.displayName = mentionCandidate.displayName
+ profilePictureView.additionalPublicKey = null
+ profilePictureView.update()
if (openGroupServer != null && openGroupRoom != null) {
val isUserModerator = OpenGroupManager.isUserModerator(context, "$openGroupRoom.$openGroupServer", mentionCandidate.publicKey)
moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/BlockedDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/BlockedDialog.kt
index 39ca7c691..c0ff1cbb1 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/BlockedDialog.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/BlockedDialog.kt
@@ -1,41 +1,42 @@
package org.thoughtcrime.securesms.conversation.v2.dialogs
+import android.app.Dialog
+import android.content.Context
import android.graphics.Typeface
+import android.os.Bundle
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.StyleSpan
-import android.view.LayoutInflater
-import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
import network.loki.messenger.R
-import network.loki.messenger.databinding.DialogBlockedBinding
+import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.recipients.Recipient
-import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
+import org.thoughtcrime.securesms.createSessionDialog
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
/** Shown upon sending a message to a user that's blocked. */
-class BlockedDialog(private val recipient: Recipient) : BaseDialog() {
+class BlockedDialog(private val recipient: Recipient, private val context: Context) : DialogFragment() {
- override fun setContentView(builder: AlertDialog.Builder) {
- val binding = DialogBlockedBinding.inflate(LayoutInflater.from(requireContext()))
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = createSessionDialog {
val contactDB = DatabaseComponent.get(requireContext()).sessionContactDatabase()
val sessionID = recipient.address.toString()
val contact = contactDB.getContactWithSessionID(sessionID)
val name = contact?.displayName(Contact.ContactContext.REGULAR) ?: sessionID
- val title = resources.getString(R.string.dialog_blocked_title, name)
- binding.blockedTitleTextView.text = title
+
val explanation = resources.getString(R.string.dialog_blocked_explanation, name)
val spannable = SpannableStringBuilder(explanation)
val startIndex = explanation.indexOf(name)
spannable.setSpan(StyleSpan(Typeface.BOLD), startIndex, startIndex + name.count(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- binding.blockedExplanationTextView.text = spannable
- binding.cancelButton.setOnClickListener { dismiss() }
- binding.unblockButton.setOnClickListener { unblock() }
- builder.setView(binding.root)
+
+ title(resources.getString(R.string.dialog_blocked_title, name))
+ text(spannable)
+ button(R.string.ConversationActivity_unblock) { unblock() }
+ cancelButton { dismiss() }
}
private fun unblock() {
- DatabaseComponent.get(requireContext()).recipientDatabase().setBlocked(recipient, false)
+ MessagingModuleConfiguration.shared.storage.setBlocked(listOf(recipient), false)
dismiss()
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt
index 1799a6e87..ceb9410df 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt
@@ -1,11 +1,12 @@
package org.thoughtcrime.securesms.conversation.v2.dialogs
+import android.app.Dialog
import android.graphics.Typeface
+import android.os.Bundle
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.StyleSpan
-import android.view.LayoutInflater
-import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.R
import network.loki.messenger.databinding.DialogDownloadBinding
@@ -13,7 +14,7 @@ import org.session.libsession.database.StorageProtocol
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.session.libsession.utilities.recipients.Recipient
-import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
+import org.thoughtcrime.securesms.createSessionDialog
import org.thoughtcrime.securesms.database.SessionContactDatabase
import javax.inject.Inject
@@ -22,13 +23,12 @@ import javax.inject.Inject
@AndroidEntryPoint
class AutoDownloadDialog(private val threadRecipient: Recipient,
private val databaseAttachment: DatabaseAttachment
-) : BaseDialog() {
+) : DialogFragment() {
@Inject lateinit var storage: StorageProtocol
@Inject lateinit var contactDB: SessionContactDatabase
- override fun setContentView(builder: AlertDialog.Builder) {
- val binding = DialogDownloadBinding.inflate(LayoutInflater.from(requireContext()))
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = createSessionDialog {
val threadId = storage.getThreadId(threadRecipient) ?: run {
dismiss()
return
@@ -39,25 +39,23 @@ class AutoDownloadDialog(private val threadRecipient: Recipient,
threadRecipient.isClosedGroupRecipient -> storage.getGroup(threadRecipient.address.toGroupString())?.title ?: "UNKNOWN"
else -> storage.getContactWithSessionID(threadRecipient.address.serialize())?.displayName(Contact.ContactContext.REGULAR) ?: "UNKNOWN"
}
- val title = resources.getString(R.string.dialog_auto_download_title)
- binding.downloadTitleTextView.text = title
+ title(resources.getString(R.string.dialog_auto_download_title))
+
val explanation = resources.getString(R.string.dialog_auto_download_explanation, displayName)
val spannable = SpannableStringBuilder(explanation)
- val startIndex = explanation.indexOf(displayName)
- spannable.setSpan(StyleSpan(Typeface.BOLD), startIndex, startIndex + displayName.count(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- binding.downloadExplanationTextView.text = spannable
- binding.no.setOnClickListener {
- setAutoDownload(false)
- dismiss()
- }
- binding.yes.setOnClickListener {
+ val startIndex = explanation.indexOf(name)
+ spannable.setSpan(StyleSpan(Typeface.BOLD), startIndex, startIndex + name.count(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ text(spannable)
+
+ button(R.string.dialog_download_button_title, R.string.AccessibilityId_download_media) {
setAutoDownload(true)
- dismiss()
}
- builder.setView(binding.root)
+ cancelButton {
+ setAutoDownload(false)
+ }
}
private fun setAutoDownload(shouldDownload: Boolean) {
storage.setAutoDownloadAttachments(threadRecipient, shouldDownload)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt
index 444c389e0..a886e8919 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt
@@ -1,46 +1,42 @@
package org.thoughtcrime.securesms.conversation.v2.dialogs
+import android.app.Dialog
import android.graphics.Typeface
+import android.os.Bundle
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.StyleSpan
-import android.view.LayoutInflater
import android.widget.Toast
-import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.DialogFragment
import network.loki.messenger.R
-import network.loki.messenger.databinding.DialogJoinOpenGroupBinding
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.utilities.OpenGroupUrlParser
import org.session.libsignal.utilities.ThreadUtils
-import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
+import org.thoughtcrime.securesms.createSessionDialog
import org.thoughtcrime.securesms.groups.OpenGroupManager
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
/** Shown upon tapping an open group invitation. */
-class JoinOpenGroupDialog(private val name: String, private val url: String) : BaseDialog() {
+class JoinOpenGroupDialog(private val name: String, private val url: String) : DialogFragment() {
- override fun setContentView(builder: AlertDialog.Builder) {
- val binding = DialogJoinOpenGroupBinding.inflate(LayoutInflater.from(requireContext()))
- val title = resources.getString(R.string.dialog_join_open_group_title, name)
- binding.joinOpenGroupTitleTextView.text = title
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = createSessionDialog {
+ title(resources.getString(R.string.dialog_join_open_group_title, name))
val explanation = resources.getString(R.string.dialog_join_open_group_explanation, name)
val spannable = SpannableStringBuilder(explanation)
val startIndex = explanation.indexOf(name)
spannable.setSpan(StyleSpan(Typeface.BOLD), startIndex, startIndex + name.count(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- binding.joinOpenGroupExplanationTextView.text = spannable
- binding.cancelButton.setOnClickListener { dismiss() }
- binding.joinButton.setOnClickListener { join() }
- builder.setView(binding.root)
+ text(spannable)
+ cancelButton { dismiss() }
+ button(R.string.open_group_invitation_view__join_accessibility_description) { join() }
}
private fun join() {
val openGroup = OpenGroupUrlParser.parseUrl(url)
- val activity = requireContext() as AppCompatActivity
+ val activity = requireActivity()
ThreadUtils.queue {
try {
- OpenGroupManager.add(openGroup.server, openGroup.room, openGroup.serverPublicKey, activity)
- MessagingModuleConfiguration.shared.storage.onOpenGroupAdded(openGroup.server)
+ openGroup.apply { OpenGroupManager.add(server, room, serverPublicKey, activity) }
+ MessagingModuleConfiguration.shared.storage.onOpenGroupAdded(openGroup.server, openGroup.room)
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(activity)
} catch (e: Exception) {
Toast.makeText(activity, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show()
@@ -48,4 +44,4 @@ class JoinOpenGroupDialog(private val name: String, private val url: String) : B
}
dismiss()
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/LinkPreviewDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/LinkPreviewDialog.kt
index a16ca86f7..996dd41f9 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/LinkPreviewDialog.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/LinkPreviewDialog.kt
@@ -1,20 +1,21 @@
package org.thoughtcrime.securesms.conversation.v2.dialogs
-import android.view.LayoutInflater
-import androidx.appcompat.app.AlertDialog
-import network.loki.messenger.databinding.DialogLinkPreviewBinding
+import android.app.Dialog
+import android.os.Bundle
+import androidx.fragment.app.DialogFragment
+import network.loki.messenger.R
import org.session.libsession.utilities.TextSecurePreferences
-import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
+import org.thoughtcrime.securesms.createSessionDialog
/** Shown the first time the user inputs a URL that could generate a link preview, to
* let them know that Session offers the ability to send and receive link previews. */
-class LinkPreviewDialog(private val onEnabled: () -> Unit) : BaseDialog() {
+class LinkPreviewDialog(private val onEnabled: () -> Unit) : DialogFragment() {
- override fun setContentView(builder: AlertDialog.Builder) {
- val binding = DialogLinkPreviewBinding.inflate(LayoutInflater.from(requireContext()))
- binding.cancelButton.setOnClickListener { dismiss() }
- binding.enableLinkPreviewsButton.setOnClickListener { enable() }
- builder.setView(binding.root)
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = createSessionDialog {
+ title(R.string.dialog_link_preview_title)
+ text(R.string.dialog_link_preview_explanation)
+ button(R.string.dialog_link_preview_enable_button_title) { enable() }
+ cancelButton { dismiss() }
}
private fun enable() {
@@ -22,4 +23,4 @@ class LinkPreviewDialog(private val onEnabled: () -> Unit) : BaseDialog() {
dismiss()
onEnabled()
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/SendSeedDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/SendSeedDialog.kt
index f51261d49..6abb0814d 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/SendSeedDialog.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/SendSeedDialog.kt
@@ -1,22 +1,23 @@
package org.thoughtcrime.securesms.conversation.v2.dialogs
-import android.view.LayoutInflater
-import androidx.appcompat.app.AlertDialog
-import network.loki.messenger.databinding.DialogSendSeedBinding
-import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
+import android.app.Dialog
+import android.os.Bundle
+import androidx.fragment.app.DialogFragment
+import network.loki.messenger.R
+import org.thoughtcrime.securesms.createSessionDialog
/** Shown if the user is about to send their recovery phrase to someone. */
-class SendSeedDialog(private val proceed: (() -> Unit)? = null) : BaseDialog() {
+class SendSeedDialog(private val proceed: (() -> Unit)? = null) : DialogFragment() {
- override fun setContentView(builder: AlertDialog.Builder) {
- val binding = DialogSendSeedBinding.inflate(LayoutInflater.from(requireContext()))
- binding.cancelButton.setOnClickListener { dismiss() }
- binding.sendSeedButton.setOnClickListener { send() }
- builder.setView(binding.root)
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = createSessionDialog {
+ title(R.string.dialog_send_seed_title)
+ text(R.string.dialog_send_seed_explanation)
+ button(R.string.dialog_send_seed_send_button_title) { send() }
+ cancelButton()
}
private fun send() {
proceed?.invoke()
dismiss()
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt
index 7ac70b843..73e2d571c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt
@@ -57,9 +57,9 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li
val attachmentButtonsContainerHeight: Int
get() = binding.attachmentsButtonContainer.height
- private val attachmentsButton by lazy { InputBarButton(context, R.drawable.ic_plus_24) }
- private val microphoneButton by lazy { InputBarButton(context, R.drawable.ic_microphone) }
- private val sendButton by lazy { InputBarButton(context, R.drawable.ic_arrow_up, true) }
+ private val attachmentsButton by lazy { InputBarButton(context, R.drawable.ic_plus_24).apply { contentDescription = context.getString(R.string.AccessibilityId_attachments_button)} }
+ private val microphoneButton by lazy { InputBarButton(context, R.drawable.ic_microphone).apply { contentDescription = context.getString(R.string.AccessibilityId_microphone_button)} }
+ private val sendButton by lazy { InputBarButton(context, R.drawable.ic_arrow_up, true).apply { contentDescription = context.getString(R.string.AccessibilityId_send_message_button)} }
// region Lifecycle
constructor(context: Context) : super(context) { initialize() }
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidateView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidateView.kt
index a21ba1b50..2d8f74596 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidateView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidateView.kt
@@ -28,11 +28,10 @@ class MentionCandidateView : RelativeLayout {
private fun update() = with(binding) {
mentionCandidateNameTextView.text = candidate.displayName
- profilePictureView.root.publicKey = candidate.publicKey
- profilePictureView.root.displayName = candidate.displayName
- profilePictureView.root.additionalPublicKey = null
- profilePictureView.root.glide = glide!!
- profilePictureView.root.update()
+ profilePictureView.publicKey = candidate.publicKey
+ profilePictureView.displayName = candidate.displayName
+ profilePictureView.additionalPublicKey = null
+ profilePictureView.update()
if (openGroupServer != null && openGroupRoom != null) {
val isUserModerator = OpenGroupManager.isUserModerator(context, "$openGroupRoom.$openGroupServer", candidate.publicKey)
moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidatesView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidatesView.kt
index 401ccaa3c..e62f7f8f8 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidatesView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidatesView.kt
@@ -7,6 +7,7 @@ import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ListView
import dagger.hilt.android.AndroidEntryPoint
+import network.loki.messenger.R
import org.session.libsession.messaging.mentions.Mention
import org.thoughtcrime.securesms.database.LokiThreadDatabase
import org.thoughtcrime.securesms.mms.GlideRequests
@@ -41,7 +42,9 @@ class MentionCandidatesView(context: Context, attrs: AttributeSet?, defStyleAttr
override fun getItem(position: Int): Mention { return candidates[position] }
override fun getView(position: Int, cellToBeReused: View?, parent: ViewGroup): View {
- val cell = cellToBeReused as MentionCandidateView? ?: MentionCandidateView(context)
+ val cell = cellToBeReused as MentionCandidateView? ?: MentionCandidateView(context).apply {
+ contentDescription = context.getString(R.string.AccessibilityId_contact)
+ }
val mentionCandidate = getItem(position)
cell.glide = glide
cell.candidate = mentionCandidate
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt
index d475a6444..3746aa52e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt
@@ -67,9 +67,11 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
menu.findItem(R.id.menu_context_copy_public_key).isVisible =
(thread.isGroupRecipient && !thread.isOpenGroupRecipient && selectedItems.size == 1 && firstMessage.individualRecipient.address.toString() != userPublicKey)
// Message detail
- menu.findItem(R.id.menu_message_details).isVisible = (selectedItems.size == 1 && firstMessage.isOutgoing)
+ menu.findItem(R.id.menu_message_details).isVisible = selectedItems.size == 1
// Resend
menu.findItem(R.id.menu_context_resend).isVisible = (selectedItems.size == 1 && firstMessage.isFailed)
+ // Resync
+ menu.findItem(R.id.menu_context_resync).isVisible = (selectedItems.size == 1 && firstMessage.isSyncFailed)
// Save media
menu.findItem(R.id.menu_context_save_attachment).isVisible = (selectedItems.size == 1
&& firstMessage.isMms && (firstMessage as MediaMmsMessageRecord).containsMediaSlide())
@@ -90,6 +92,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
R.id.menu_context_ban_and_delete_all -> delegate?.banAndDeleteAll(selectedItems)
R.id.menu_context_copy -> delegate?.copyMessages(selectedItems)
R.id.menu_context_copy_public_key -> delegate?.copySessionID(selectedItems)
+ R.id.menu_context_resync -> delegate?.resyncMessage(selectedItems)
R.id.menu_context_resend -> delegate?.resendMessage(selectedItems)
R.id.menu_message_details -> delegate?.showMessageDetail(selectedItems)
R.id.menu_context_save_attachment -> delegate?.saveAttachment(selectedItems)
@@ -113,6 +116,7 @@ interface ConversationActionModeCallbackDelegate {
fun banAndDeleteAll(messages: Set)
fun copyMessages(messages: Set)
fun copySessionID(messages: Set)
+ fun resyncMessage(messages: Set)
fun resendMessage(messages: Set)
fun showMessageDetail(messages: Set)
fun saveAttachment(messages: Set)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt
index 663dd2e25..02ee4ae45 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt
@@ -14,7 +14,6 @@ import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.ColorInt
-import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ContextThemeWrapper
import androidx.appcompat.widget.SearchView
@@ -33,7 +32,6 @@ import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.guava.Optional
import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.MediaOverviewActivity
-import org.thoughtcrime.securesms.MuteDialog
import org.thoughtcrime.securesms.ShortcutLauncherActivity
import org.thoughtcrime.securesms.calls.WebRtcCallActivity
import org.thoughtcrime.securesms.contacts.SelectContactsActivity
@@ -44,6 +42,8 @@ import org.thoughtcrime.securesms.groups.EditClosedGroupActivity
import org.thoughtcrime.securesms.groups.EditClosedGroupActivity.Companion.groupIDKey
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
import org.thoughtcrime.securesms.service.WebRtcCallService
+import org.thoughtcrime.securesms.showSessionDialog
+import org.thoughtcrime.securesms.showMuteDialog
import org.thoughtcrime.securesms.util.BitmapUtil
import java.io.IOException
@@ -63,26 +63,31 @@ object ConversationMenuHelper {
// Base menu (options that should always be present)
inflater.inflate(R.menu.menu_conversation, menu)
// Expiring messages
- if (!isOpenGroup && (thread.hasApprovedMe() || thread.isClosedGroupRecipient)) {
+ if (!isOpenGroup && (thread.hasApprovedMe() || thread.isClosedGroupRecipient) && !thread.isBlocked) {
if (thread.expireMessages > 0) {
inflater.inflate(R.menu.menu_conversation_expiration_on, menu)
val item = menu.findItem(R.id.menu_expiring_messages)
- val actionView = item.actionView
- val iconView = actionView.findViewById(R.id.menu_badge_icon)
- val badgeView = actionView.findViewById(R.id.expiration_badge)
- @ColorInt val color = context.getColorFromAttr(android.R.attr.textColorPrimary)
- iconView.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)
- badgeView.text = ExpirationUtil.getExpirationAbbreviatedDisplayValue(context, thread.expireMessages)
- actionView.setOnClickListener { onOptionsItemSelected(item) }
+ item.actionView?.let { actionView ->
+ val iconView = actionView.findViewById(R.id.menu_badge_icon)
+ val badgeView = actionView.findViewById(R.id.expiration_badge)
+ @ColorInt val color = context.getColorFromAttr(android.R.attr.textColorPrimary)
+ iconView.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)
+ badgeView.text = ExpirationUtil.getExpirationAbbreviatedDisplayValue(context, thread.expireMessages)
+ actionView.setOnClickListener { onOptionsItemSelected(item) }
+ }
} else {
inflater.inflate(R.menu.menu_conversation_expiration_off, menu)
}
}
+ // One-on-one chat menu allows copying the session id
+ if (thread.isContactRecipient) {
+ inflater.inflate(R.menu.menu_conversation_copy_session_id, menu)
+ }
// One-on-one chat menu (options that should only be present for one-on-one chats)
if (thread.isContactRecipient) {
if (thread.isBlocked) {
inflater.inflate(R.menu.menu_conversation_unblock, menu)
- } else {
+ } else if (!thread.isLocalNumber) {
inflater.inflate(R.menu.menu_conversation_block, menu)
}
}
@@ -154,6 +159,7 @@ object ConversationMenuHelper {
R.id.menu_block -> { block(context, thread, deleteThread = false) }
R.id.menu_block_delete -> { blockAndDelete(context, thread) }
R.id.menu_copy_session_id -> { copySessionID(context, thread) }
+ R.id.menu_copy_open_group_url -> { copyOpenGroupUrl(context, thread) }
R.id.menu_edit_group -> { editClosedGroup(context, thread) }
R.id.menu_leave_group -> { leaveClosedGroup(context, thread) }
R.id.menu_invite_to_open_group -> { inviteContacts(context, thread) }
@@ -180,26 +186,23 @@ object ConversationMenuHelper {
private fun call(context: Context, thread: Recipient) {
if (!TextSecurePreferences.isCallNotificationsEnabled(context)) {
- AlertDialog.Builder(context)
- .setTitle(R.string.ConversationActivity_call_title)
- .setMessage(R.string.ConversationActivity_call_prompt)
- .setPositiveButton(R.string.activity_settings_title) { _, _ ->
- val intent = Intent(context, PrivacySettingsActivity::class.java)
- context.startActivity(intent)
+ context.showSessionDialog {
+ title(R.string.ConversationActivity_call_title)
+ text(R.string.ConversationActivity_call_prompt)
+ button(R.string.activity_settings_title, R.string.AccessibilityId_settings) {
+ Intent(context, PrivacySettingsActivity::class.java).let(context::startActivity)
}
- .setNeutralButton(R.string.cancel) { d, _ ->
- d.dismiss()
- }.show()
+ cancelButton()
+ }
return
}
- val service = WebRtcCallService.createCall(context, thread)
- context.startService(service)
+ WebRtcCallService.createCall(context, thread)
+ .let(context::startService)
- val activity = Intent(context, WebRtcCallActivity::class.java).apply {
- flags = Intent.FLAG_ACTIVITY_NEW_TASK
- }
- context.startActivity(activity)
+ Intent(context, WebRtcCallActivity::class.java)
+ .apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }
+ .let(context::startActivity)
}
@@ -270,6 +273,12 @@ object ConversationMenuHelper {
listener.copySessionID(thread.address.toString())
}
+ private fun copyOpenGroupUrl(context: Context, thread: Recipient) {
+ if (!thread.isOpenGroupRecipient) { return }
+ val listener = context as? ConversationMenuListener ?: return
+ listener.copyOpenGroupUrl(thread)
+ }
+
private fun editClosedGroup(context: Context, thread: Recipient) {
if (!thread.isClosedGroupRecipient) { return }
val intent = Intent(context, EditClosedGroupActivity::class.java)
@@ -280,9 +289,7 @@ object ConversationMenuHelper {
private fun leaveClosedGroup(context: Context, thread: Recipient) {
if (!thread.isClosedGroupRecipient) { return }
- val builder = AlertDialog.Builder(context)
- builder.setTitle(context.resources.getString(R.string.ConversationActivity_leave_group))
- builder.setCancelable(true)
+
val group = DatabaseComponent.get(context).groupDatabase().getGroup(thread.address.toGroupString()).orNull()
val admins = group.admins
val sessionID = TextSecurePreferences.getLocalNumber(context)
@@ -292,29 +299,25 @@ object ConversationMenuHelper {
} else {
context.resources.getString(R.string.ConversationActivity_are_you_sure_you_want_to_leave_this_group)
}
- builder.setMessage(message)
- builder.setPositiveButton(R.string.yes) { _, _ ->
- var groupPublicKey: String?
- var isClosedGroup: Boolean
- try {
- groupPublicKey = doubleDecodeGroupID(thread.address.toString()).toHexString()
- isClosedGroup = DatabaseComponent.get(context).lokiAPIDatabase().isClosedGroup(groupPublicKey)
- } catch (e: IOException) {
- groupPublicKey = null
- isClosedGroup = false
- }
- try {
- if (isClosedGroup) {
- MessageSender.leave(groupPublicKey!!, true)
- } else {
- Toast.makeText(context, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show()
+
+ fun onLeaveFailed() = Toast.makeText(context, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show()
+
+ context.showSessionDialog {
+ title(R.string.ConversationActivity_leave_group)
+ text(message)
+ button(R.string.yes) {
+ try {
+ val groupPublicKey = doubleDecodeGroupID(thread.address.toString()).toHexString()
+ val isClosedGroup = DatabaseComponent.get(context).lokiAPIDatabase().isClosedGroup(groupPublicKey)
+
+ if (isClosedGroup) MessageSender.leave(groupPublicKey, notifyUser = false)
+ else onLeaveFailed()
+ } catch (e: Exception) {
+ onLeaveFailed()
}
- } catch (e: Exception) {
- Toast.makeText(context, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show()
}
+ button(R.string.no)
}
- builder.setNegativeButton(R.string.no, null)
- builder.show()
}
private fun inviteContacts(context: Context, thread: Recipient) {
@@ -329,7 +332,7 @@ object ConversationMenuHelper {
}
private fun mute(context: Context, thread: Recipient) {
- MuteDialog.show(ContextThemeWrapper(context, context.theme)) { until: Long ->
+ showMuteDialog(ContextThemeWrapper(context, context.theme)) { until ->
DatabaseComponent.get(context).recipientDatabase().setMuted(thread, until)
}
}
@@ -344,6 +347,7 @@ object ConversationMenuHelper {
fun block(deleteThread: Boolean = false)
fun unblock()
fun copySessionID(sessionId: String)
+ fun copyOpenGroupUrl(thread: Recipient)
fun showExpiringMessagesDialog(thread: Recipient)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt
index a4e4a52d5..3e370104e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt
@@ -31,6 +31,7 @@ class ControlMessageView : LinearLayout {
binding.dateBreakTextView.showDateBreak(message, previous)
binding.iconImageView.visibility = View.GONE
var messageBody: CharSequence = message.getDisplayBody(context)
+ binding.root.contentDescription= null
when {
message.isExpirationTimerUpdate -> {
binding.iconImageView.setImageDrawable(
@@ -46,6 +47,7 @@ class ControlMessageView : LinearLayout {
}
message.isMessageRequestResponse -> {
messageBody = context.getString(R.string.message_requests_accepted)
+ binding.root.contentDescription=context.getString(R.string.AccessibilityId_message_request_config_message)
}
message.isCallLog -> {
val drawable = when {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt
index 45d353cc3..967722389 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt
@@ -57,8 +57,12 @@ class LinkPreviewView : LinearLayout {
val cornerRadii = MessageBubbleUtilities.calculateRadii(context, isStartOfMessageCluster, isEndOfMessageCluster, message.isOutgoing)
cornerMask.setTopLeftRadius(cornerRadii[0])
cornerMask.setTopRightRadius(cornerRadii[1])
- cornerMask.setBottomRightRadius(cornerRadii[2])
- cornerMask.setBottomLeftRadius(cornerRadii[3])
+
+ // Only round the bottom corners if there is no body text
+ if (message.body.isEmpty()) {
+ cornerMask.setBottomRightRadius(cornerRadii[2])
+ cornerMask.setBottomLeftRadius(cornerRadii[3])
+ }
}
override fun dispatchDraw(canvas: Canvas) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt
index 060cd0b04..d23656616 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.conversation.v2.messages
import android.content.Context
import android.graphics.Color
import android.graphics.Rect
-import android.graphics.drawable.Drawable
import android.text.Spannable
import android.text.style.BackgroundColorSpan
import android.text.style.ForegroundColorSpan
@@ -15,11 +14,10 @@ import android.view.View
import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.core.content.res.ResourcesCompat
-import androidx.core.graphics.BlendModeColorFilterCompat
-import androidx.core.graphics.BlendModeCompat
+import androidx.core.graphics.ColorUtils
import androidx.core.text.getSpans
import androidx.core.text.toSpannable
+import androidx.core.view.children
import androidx.core.view.isVisible
import network.loki.messenger.R
import network.loki.messenger.databinding.ViewVisibleMessageContentBinding
@@ -27,6 +25,7 @@ import okhttp3.HttpUrl
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
+import org.session.libsession.utilities.ThemeUtil
import org.session.libsession.utilities.getColorFromAttr
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
@@ -36,7 +35,10 @@ import org.thoughtcrime.securesms.conversation.v2.utilities.ModalURLSpan
import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
+import org.thoughtcrime.securesms.database.model.SmsMessageRecord
+import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests
+import org.thoughtcrime.securesms.util.GlowViewUtilities
import org.thoughtcrime.securesms.util.SearchUtil
import org.thoughtcrime.securesms.util.getAccentColor
import java.util.Locale
@@ -44,7 +46,6 @@ import kotlin.math.roundToInt
class VisibleMessageContentView : ConstraintLayout {
private val binding: ViewVisibleMessageContentBinding by lazy { ViewVisibleMessageContentBinding.bind(this) }
- var onContentClick: MutableList<((event: MotionEvent) -> Unit)> = mutableListOf()
var onContentDoubleTap: (() -> Unit)? = null
var delegate: VisibleMessageViewDelegate? = null
var indexInAdapter: Int = -1
@@ -58,20 +59,20 @@ class VisibleMessageContentView : ConstraintLayout {
// region Updating
fun bind(
message: MessageRecord,
- isStartOfMessageCluster: Boolean,
- isEndOfMessageCluster: Boolean,
- glide: GlideRequests,
+ isStartOfMessageCluster: Boolean = true,
+ isEndOfMessageCluster: Boolean = true,
+ glide: GlideRequests = GlideApp.with(this),
thread: Recipient,
- searchQuery: String?,
- onAttachmentNeedsDownload: (Long, Long) -> Unit
+ searchQuery: String? = null,
+ contactIsTrusted: Boolean = true,
+ onAttachmentNeedsDownload: (Long, Long) -> Unit,
+ suppressThumbnails: Boolean = false
) {
// Background
- val background = getBackground(message.isOutgoing)
val color = if (message.isOutgoing) context.getAccentColor()
else context.getColorFromAttr(R.attr.message_received_background_color)
- val filter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_IN)
- background.colorFilter = filter
- binding.contentParent.background = background
+ binding.contentParent.mainColor = color
+ binding.contentParent.cornerRadius = resources.getDimension(R.dimen.message_corner_radius)
val mediaDownloaded = message is MmsMessageRecord && message.slideDeck.asAttachments().all { it.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE }
val mediaInProgress = message is MmsMessageRecord && message.slideDeck.asAttachments().any { it.isInProgress }
@@ -97,6 +98,9 @@ class VisibleMessageContentView : ConstraintLayout {
binding.deletedMessageView.root.isVisible = false
}
+ // Note: Need to clear the body to prevent the message bubble getting incorrectly
+ // sized based on text content from a recycled view
+ binding.bodyTextView.text = null
binding.quoteView.root.isVisible = message is MmsMessageRecord && message.quote != null
binding.linkPreviewView.root.isVisible = message is MmsMessageRecord && message.linkPreviews.isNotEmpty()
binding.pendingAttachmentView.root.isVisible = !mediaDownloaded && !mediaInProgress && message is MmsMessageRecord && message.quote == null && message.linkPreviews.isEmpty()
@@ -125,7 +129,6 @@ class VisibleMessageContentView : ConstraintLayout {
delegate?.scrollToMessageIfPossible(quote.id)
}
}
- val hasMedia = message.slideDeck.asAttachments().isNotEmpty()
}
if (message is MmsMessageRecord) {
@@ -152,7 +155,9 @@ class VisibleMessageContentView : ConstraintLayout {
message is MmsMessageRecord && message.linkPreviews.isNotEmpty() -> {
binding.linkPreviewView.root.bind(message, glide, isStartOfMessageCluster, isEndOfMessageCluster)
onContentClick.add { event -> binding.linkPreviewView.root.calculateHit(event) }
- // Body text view is inside the link preview for layout convenience
+
+ // When in a link preview ensure the bodyTextView can expand to the full width
+ binding.bodyTextView.maxWidth = binding.linkPreviewView.root.layoutParams.width
}
// AUDIO
message is MmsMessageRecord && message.slideDeck.audioSlide != null -> {
@@ -197,7 +202,7 @@ class VisibleMessageContentView : ConstraintLayout {
}
}
// IMAGE / VIDEO
- message is MmsMessageRecord && message.slideDeck.asAttachments().isNotEmpty() -> {
+ message is MmsMessageRecord && !suppressThumbnails && message.slideDeck.asAttachments().isNotEmpty() -> {
if (mediaDownloaded || mediaInProgress || message.isOutgoing) {
// isStart and isEnd of cluster needed for calculating the mask for full bubble image groups
// bind after add view because views are inflated and calculated during bind
@@ -237,6 +242,7 @@ class VisibleMessageContentView : ConstraintLayout {
}
binding.bodyTextView.isVisible = message.body.isNotEmpty() && !hideBody
+ binding.contentParent.apply { isVisible = children.any { it.isVisible } }
if (message.body.isNotEmpty() && !hideBody) {
val color = getTextColor(context, message)
@@ -255,14 +261,15 @@ class VisibleMessageContentView : ConstraintLayout {
binding.contentParent.layoutParams = layoutParams
}
+ private val onContentClick: MutableList<((event: MotionEvent) -> Unit)> = mutableListOf()
+
+ fun onContentClick(event: MotionEvent) {
+ onContentClick.forEach { clickHandler -> clickHandler.invoke(event) }
+ }
+
private fun ViewVisibleMessageContentBinding.barrierViewsGone(): Boolean =
listOf(albumThumbnailView.root, linkPreviewView.root, voiceMessageView.root, quoteView.root).none { it.isVisible }
- private fun getBackground(isOutgoing: Boolean): Drawable {
- val backgroundID = if (isOutgoing) R.drawable.message_bubble_background_sent_alone else R.drawable.message_bubble_background_received_alone
- return ResourcesCompat.getDrawable(resources, backgroundID, context.theme)!!
- }
-
fun recycle() {
arrayOf(
binding.deletedMessageView.root,
@@ -280,6 +287,15 @@ class VisibleMessageContentView : ConstraintLayout {
fun playVoiceMessage() {
binding.voiceMessageView.root.togglePlayback()
}
+
+ fun playHighlight() {
+ // Show the highlight colour immediately then slowly fade out
+ val targetColor = if (ThemeUtil.isDarkTheme(context)) context.getAccentColor() else resources.getColor(R.color.black, context.theme)
+ val clearTargetColor = ColorUtils.setAlphaComponent(targetColor, 0)
+ binding.contentParent.numShadowRenders = if (ThemeUtil.isDarkTheme(context)) 3 else 1
+ binding.contentParent.sessionShadowColor = targetColor
+ GlowViewUtilities.animateShadowColorChange(binding.contentParent, targetColor, clearTargetColor, 1600)
+ }
// endregion
// region Convenience
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt
index 5f730d311..352191339 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt
@@ -2,17 +2,21 @@ package org.thoughtcrime.securesms.conversation.v2.messages
import android.content.Context
import android.content.Intent
-import android.content.res.Resources
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.os.Handler
import android.os.Looper
import android.util.AttributeSet
+import android.view.Gravity
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import android.view.View
+import android.widget.FrameLayout
import android.widget.LinearLayout
+import androidx.annotation.ColorInt
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
@@ -26,6 +30,7 @@ import network.loki.messenger.databinding.ViewVisibleMessageBinding
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.contacts.Contact.ContactContext
import org.session.libsession.messaging.open_groups.OpenGroupApi
+import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.ViewUtil
import org.session.libsession.utilities.getColorFromAttr
@@ -42,6 +47,7 @@ import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.groups.OpenGroupManager
import org.thoughtcrime.securesms.home.UserDetailsBottomSheet
+import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.disableClipping
@@ -66,7 +72,6 @@ class VisibleMessageView : LinearLayout {
@Inject lateinit var mmsDb: MmsDatabase
private val binding by lazy { ViewVisibleMessageBinding.bind(this) }
- private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
private val swipeToReplyIcon = ContextCompat.getDrawable(context, R.drawable.ic_baseline_reply_24)!!.mutate()
private val swipeToReplyIconRect = Rect()
private var dx = 0.0f
@@ -107,7 +112,10 @@ class VisibleMessageView : LinearLayout {
private fun initialize() {
isHapticFeedbackEnabled = true
setWillNotDraw(false)
+ binding.root.disableClipping()
+ binding.mainContainer.disableClipping()
binding.messageInnerContainer.disableClipping()
+ binding.messageInnerLayout.disableClipping()
binding.messageContentView.root.disableClipping()
}
// endregion
@@ -115,13 +123,14 @@ class VisibleMessageView : LinearLayout {
// region Updating
fun bind(
message: MessageRecord,
- previous: MessageRecord?,
- next: MessageRecord?,
- glide: GlideRequests,
- searchQuery: String?,
- contact: Contact?,
+ previous: MessageRecord? = null,
+ next: MessageRecord? = null,
+ glide: GlideRequests = GlideApp.with(this),
+ searchQuery: String? = null,
+ contact: Contact? = null,
senderSessionID: String,
- delegate: VisibleMessageViewDelegate?,
+ lastSeen: Long,
+ delegate: VisibleMessageViewDelegate? = null,
onAttachmentNeedsDownload: (Long, Long) -> Unit
) {
val threadID = message.threadId
@@ -132,7 +141,7 @@ class VisibleMessageView : LinearLayout {
// Show profile picture and sender name if this is a group thread AND
// the message is incoming
binding.moderatorIconImageView.isVisible = false
- binding.profilePictureView.root.visibility = when {
+ binding.profilePictureView.visibility = when {
thread.isGroupRecipient && !message.isOutgoing && isEndOfMessageCluster -> View.VISIBLE
thread.isGroupRecipient -> View.INVISIBLE
else -> View.GONE
@@ -141,25 +150,25 @@ class VisibleMessageView : LinearLayout {
val bottomMargin = if (isEndOfMessageCluster) resources.getDimensionPixelSize(R.dimen.small_spacing)
else ViewUtil.dpToPx(context,2)
- if (binding.profilePictureView.root.visibility == View.GONE) {
+ if (binding.profilePictureView.visibility == View.GONE) {
val expirationParams = binding.messageInnerContainer.layoutParams as MarginLayoutParams
expirationParams.bottomMargin = bottomMargin
binding.messageInnerContainer.layoutParams = expirationParams
} else {
- val avatarLayoutParams = binding.profilePictureView.root.layoutParams as MarginLayoutParams
+ val avatarLayoutParams = binding.profilePictureView.layoutParams as MarginLayoutParams
avatarLayoutParams.bottomMargin = bottomMargin
- binding.profilePictureView.root.layoutParams = avatarLayoutParams
+ binding.profilePictureView.layoutParams = avatarLayoutParams
}
if (isGroupThread && !message.isOutgoing) {
if (isEndOfMessageCluster) {
- binding.profilePictureView.root.publicKey = senderSessionID
- binding.profilePictureView.root.glide = glide
- binding.profilePictureView.root.update(message.individualRecipient)
- binding.profilePictureView.root.setOnClickListener {
+ binding.profilePictureView.publicKey = senderSessionID
+ binding.profilePictureView.update(message.individualRecipient)
+ binding.profilePictureView.setOnClickListener {
if (thread.isOpenGroupRecipient) {
val openGroup = lokiThreadDb.getOpenGroupChat(threadID)
if (IdPrefix.fromValue(senderSessionID) == IdPrefix.BLINDED && openGroup?.canWrite == true) {
+ // TODO: support v2 soon
val intent = Intent(context, ConversationActivityV2::class.java)
intent.putExtra(ConversationActivityV2.FROM_GROUP_THREAD_ID, threadID)
intent.putExtra(ConversationActivityV2.ADDRESS, Address.fromSerialized(senderSessionID))
@@ -173,7 +182,7 @@ class VisibleMessageView : LinearLayout {
val openGroup = lokiThreadDb.getOpenGroupChat(threadID) ?: return
var standardPublicKey = ""
var blindedPublicKey: String? = null
- if (IdPrefix.fromValue(senderSessionID) == IdPrefix.BLINDED) {
+ if (IdPrefix.fromValue(senderSessionID)?.isBlinded() == true) {
blindedPublicKey = senderSessionID
} else {
standardPublicKey = senderSessionID
@@ -187,13 +196,15 @@ class VisibleMessageView : LinearLayout {
val contactContext =
if (thread.isOpenGroupRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR
binding.senderNameTextView.text = contact?.displayName(contactContext) ?: senderSessionID
+ // Unread marker
+ binding.unreadMarkerContainer.isVisible = lastSeen != -1L && message.timestamp > lastSeen && (previous == null || previous.timestamp <= lastSeen) && !message.isOutgoing
// Date break
val showDateBreak = isStartOfMessageCluster || snIsSelected
binding.dateBreakTextView.text = if (showDateBreak) DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp) else null
binding.dateBreakTextView.isVisible = showDateBreak
// Message status indicator
if (message.isOutgoing) {
- val (iconID, iconColor, textId) = getMessageStatusImage(message)
+ val (iconID, iconColor, textId, contentDescription) = getMessageStatusImage(message)
if (textId != null) {
binding.messageStatusTextView.setText(textId)
@@ -208,6 +219,7 @@ class VisibleMessageView : LinearLayout {
}
binding.messageStatusImageView.setImageDrawable(drawable)
}
+ binding.messageStatusImageView.contentDescription = contentDescription
val lastMessageID = mmsSmsDb.getLastMessageID(message.threadId)
binding.messageStatusTextView.isVisible = (
@@ -281,29 +293,63 @@ class VisibleMessageView : LinearLayout {
}
}
- private fun getMessageStatusImage(message: MessageRecord): Triple {
- return when {
- !message.isOutgoing -> Triple(null, null, null)
- message.isFailed ->
- Triple(R.drawable.ic_delivery_status_failed, resources.getColor(R.color.destructive, context.theme), R.string.delivery_status_failed)
- message.isPending ->
- Triple(R.drawable.ic_delivery_status_sending, context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_sending)
- message.isRead ->
- Triple(R.drawable.ic_delivery_status_read, context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_read)
- else ->
- Triple(R.drawable.ic_delivery_status_sent, context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_sent)
- }
+ data class MessageStatusInfo(@DrawableRes val iconId: Int?,
+ @ColorInt val iconTint: Int?,
+ @StringRes val messageText: Int?,
+ val contentDescription: String?)
+
+ private fun getMessageStatusImage(message: MessageRecord): MessageStatusInfo = when {
+ message.isFailed ->
+ MessageStatusInfo(
+ R.drawable.ic_delivery_status_failed,
+ resources.getColor(R.color.destructive, context.theme),
+ R.string.delivery_status_failed,
+ null
+ )
+ message.isSyncFailed ->
+ MessageStatusInfo(
+ R.drawable.ic_delivery_status_failed,
+ context.getColor(R.color.accent_orange),
+ R.string.delivery_status_sync_failed,
+ null
+ )
+ message.isPending ->
+ MessageStatusInfo(
+ R.drawable.ic_delivery_status_sending,
+ context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_sending,
+ context.getString(R.string.AccessibilityId_message_sent_status_pending)
+ )
+ message.isResyncing ->
+ MessageStatusInfo(
+ R.drawable.ic_delivery_status_sending,
+ context.getColor(R.color.accent_orange), R.string.delivery_status_syncing,
+ context.getString(R.string.AccessibilityId_message_sent_status_syncing)
+ )
+ message.isRead ->
+ MessageStatusInfo(
+ R.drawable.ic_delivery_status_read,
+ context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_read,
+ null
+ )
+ else ->
+ MessageStatusInfo(
+ R.drawable.ic_delivery_status_sent,
+ context.getColorFromAttr(R.attr.message_status_color),
+ R.string.delivery_status_sent,
+ context.getString(R.string.AccessibilityId_message_sent_status_tick)
+ )
}
private fun updateExpirationTimer(message: MessageRecord) {
val container = binding.messageInnerContainer
- val content = binding.messageContentView.root
- val expiration = binding.expirationTimerView
- val spacing = binding.messageContentSpacing
- container.removeAllViewsInLayout()
- container.addView(if (message.isOutgoing) expiration else content)
- container.addView(if (message.isOutgoing) content else expiration)
- container.addView(spacing, if (message.isOutgoing) 0 else 2)
+ val layout = binding.messageInnerLayout
+
+ if (message.isOutgoing) binding.messageContentView.root.bringToFront()
+ else binding.expirationTimerView.bringToFront()
+
+ layout.layoutParams = layout.layoutParams.let { it as FrameLayout.LayoutParams }
+ .apply { gravity = if (message.isOutgoing) Gravity.END else Gravity.START }
+
val containerParams = container.layoutParams as ConstraintLayout.LayoutParams
containerParams.horizontalBias = if (message.isOutgoing) 1f else 0f
container.layoutParams = containerParams
@@ -314,7 +360,7 @@ class VisibleMessageView : LinearLayout {
if (message.expireStarted > 0) {
binding.expirationTimerView.setExpirationTime(message.expireStarted, message.expiresIn)
binding.expirationTimerView.startAnimation()
- if (message.expireStarted + message.expiresIn <= System.currentTimeMillis()) {
+ if (message.expireStarted + message.expiresIn <= SnodeAPI.nowWithOffset) {
ApplicationContext.getInstance(context).expiringMessageManager.checkSchedule()
}
} else if (!message.isMediaPending) {
@@ -349,7 +395,7 @@ class VisibleMessageView : LinearLayout {
val spacing = context.resources.getDimensionPixelSize(R.dimen.small_spacing)
val iconSize = toPx(24, context.resources)
val left = binding.messageInnerContainer.left + binding.messageContentView.root.right + spacing
- val top = height - (binding.messageInnerContainer.height / 2) - binding.profilePictureView.root.marginBottom - (iconSize / 2)
+ val top = height - (binding.messageInnerContainer.height / 2) - binding.profilePictureView.marginBottom - (iconSize / 2)
val right = left + iconSize
val bottom = top + iconSize
swipeToReplyIconRect.left = left
@@ -369,9 +415,13 @@ class VisibleMessageView : LinearLayout {
}
fun recycle() {
- binding.profilePictureView.root.recycle()
+ binding.profilePictureView.recycle()
binding.messageContentView.root.recycle()
}
+
+ fun playHighlight() {
+ binding.messageContentView.root.playHighlight()
+ }
// endregion
// region Interaction
@@ -466,7 +516,7 @@ class VisibleMessageView : LinearLayout {
}
fun onContentClick(event: MotionEvent) {
- binding.messageContentView.root.onContentClick.iterator().forEach { clickHandler -> clickHandler.invoke(event) }
+ binding.messageContentView.root.onContentClick(event)
}
private fun onPress(event: MotionEvent) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt
index e1bf92c5f..2b829af15 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt
@@ -92,7 +92,7 @@ class VoiceMessageView : RelativeLayout, AudioSlidePlayer.Listener {
if (progress == 1.0) {
togglePlayback()
handleProgressChanged(0.0)
- delegate?.playVoiceMessageAtIndexIfPossible(indexInAdapter - 1)
+ delegate?.playVoiceMessageAtIndexIfPossible(indexInAdapter + 1)
} else {
handleProgressChanged(progress)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java
index dd90b699e..088685241 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java
@@ -25,6 +25,7 @@ import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Build;
import android.provider.OpenableColumns;
import android.text.TextUtils;
import android.util.Pair;
@@ -244,12 +245,17 @@ public class AttachmentManager {
}
public static void selectGallery(Activity activity, int requestCode, @NonNull Recipient recipient, @NonNull String body) {
- Permissions.with(activity)
- .request(Manifest.permission.READ_EXTERNAL_STORAGE)
- .withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio))
- .withRationaleDialog(activity.getString(R.string.ConversationActivity_to_send_photos_and_video_allow_signal_access_to_storage), R.drawable.ic_baseline_photo_library_24)
- .onAllGranted(() -> activity.startActivityForResult(MediaSendActivity.buildGalleryIntent(activity, recipient, body), requestCode))
- .execute();
+ Permissions.PermissionsBuilder builder = Permissions.with(activity);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ builder = builder.request(Manifest.permission.READ_MEDIA_VIDEO)
+ .request(Manifest.permission.READ_MEDIA_IMAGES);
+ } else {
+ builder = builder.request(Manifest.permission.READ_EXTERNAL_STORAGE);
+ }
+ builder.withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio))
+ .withRationaleDialog(activity.getString(R.string.ConversationActivity_to_send_photos_and_video_allow_signal_access_to_storage), R.drawable.ic_baseline_photo_library_24)
+ .onAllGranted(() -> activity.startActivityForResult(MediaSendActivity.buildGalleryIntent(activity, recipient, body), requestCode))
+ .execute();
}
public static void selectAudio(Activity activity, int requestCode) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/BaseDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/BaseDialog.kt
deleted file mode 100644
index e1456a7f9..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/BaseDialog.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.thoughtcrime.securesms.conversation.v2.utilities
-
-import android.app.Dialog
-import android.graphics.Color
-import android.graphics.drawable.ColorDrawable
-import android.os.Bundle
-import androidx.appcompat.app.AlertDialog
-import androidx.fragment.app.DialogFragment
-import org.thoughtcrime.securesms.util.UiModeUtilities
-
-open class BaseDialog : DialogFragment() {
-
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val builder = AlertDialog.Builder(requireContext())
- setContentView(builder)
- val result = builder.create()
- result.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
- val isLightMode = UiModeUtilities.isDayUiMode(requireContext())
- result.window?.setDimAmount(if (isLightMode) 0.1f else 0.75f)
- return result
- }
-
- open fun setContentView(builder: AlertDialog.Builder) {
- // To be overridden by subclasses
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/NotificationUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/NotificationUtils.kt
index dbbcfb51e..c0ce83f63 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/NotificationUtils.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/NotificationUtils.kt
@@ -1,21 +1,18 @@
package org.thoughtcrime.securesms.conversation.v2.utilities
import android.content.Context
-import androidx.appcompat.app.AlertDialog
import network.loki.messenger.R
import org.session.libsession.utilities.recipients.Recipient
+import org.thoughtcrime.securesms.showSessionDialog
object NotificationUtils {
fun showNotifyDialog(context: Context, thread: Recipient, notifyTypeHandler: (Int)->Unit) {
- val notifyTypes = context.resources.getStringArray(R.array.notify_types)
- val currentSelected = thread.notifyType
-
- AlertDialog.Builder(context)
- .setSingleChoiceItems(notifyTypes,currentSelected) { d, newSelection ->
- notifyTypeHandler(newSelection)
- d.dismiss()
- }
- .setTitle(R.string.RecipientPreferenceActivity_notification_settings)
- .show()
+ context.showSessionDialog {
+ title(R.string.RecipientPreferenceActivity_notification_settings)
+ singleChoiceItems(
+ context.resources.getStringArray(R.array.notify_types),
+ thread.notifyType
+ ) { notifyTypeHandler(it) }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt
index 80f4cc0bf..e01a75b30 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2.utilities
import android.content.Context
import org.session.libsession.messaging.MessagingModuleConfiguration
+import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.visible.LinkPreview
import org.session.libsession.messaging.messages.visible.OpenGroupInvitation
import org.session.libsession.messaging.messages.visible.Quote
@@ -15,7 +16,7 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord
object ResendMessageUtilities {
- fun resend(context: Context, messageRecord: MessageRecord, userBlindedKey: String?) {
+ fun resend(context: Context, messageRecord: MessageRecord, userBlindedKey: String?, isResync: Boolean = false) {
val recipient: Recipient = messageRecord.recipient
val message = VisibleMessage()
message.id = messageRecord.getId()
@@ -55,8 +56,13 @@ object ResendMessageUtilities {
val sentTimestamp = message.sentTimestamp
val sender = MessagingModuleConfiguration.shared.storage.getUserPublicKey()
if (sentTimestamp != null && sender != null) {
- MessagingModuleConfiguration.shared.storage.markAsSending(sentTimestamp, sender)
+ if (isResync) {
+ MessagingModuleConfiguration.shared.storage.markAsResyncing(sentTimestamp, sender)
+ MessageSender.send(message, Destination.from(recipient.address), isSyncMessage = true)
+ } else {
+ MessagingModuleConfiguration.shared.storage.markAsSending(sentTimestamp, sender)
+ MessageSender.send(message, recipient.address)
+ }
}
- MessageSender.send(message, recipient.address)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/TextUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/TextUtilities.kt
index 800ace54c..7a47b9275 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/TextUtilities.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/TextUtilities.kt
@@ -38,13 +38,12 @@ object TextUtilities {
fun TextView.getIntersectedModalSpans(hitRect: Rect): List {
val textLayout = layout ?: return emptyList()
val lineRect = Rect()
- val bodyTextRect = Rect()
- getGlobalVisibleRect(bodyTextRect)
+ val offset = intArrayOf(0, 0).also { getLocationOnScreen(it) }
val textSpannable = text.toSpannable()
return (0 until textLayout.lineCount).flatMap { line ->
textLayout.getLineBounds(line, lineRect)
- lineRect.offset(bodyTextRect.left + totalPaddingLeft, bodyTextRect.top + totalPaddingTop)
- if ((Rect(lineRect)).contains(hitRect)) {
+ lineRect.offset(offset[0] + totalPaddingLeft, offset[1] + totalPaddingTop)
+ if (lineRect.contains(hitRect)) {
// calculate the url span intersected with (if any)
val off = textLayout.getOffsetForHorizontal(line, hitRect.left.toFloat()) // left and right will be the same
textSpannable.getSpans(off, off).toList()
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt
index e15855667..4a9986d6e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt
@@ -123,10 +123,10 @@ open class ThumbnailView: FrameLayout {
when {
slide.thumbnailUri != null -> {
- buildThumbnailGlideRequest(glide, slide).into(GlideDrawableListeningTarget(binding.thumbnailImage, result))
+ buildThumbnailGlideRequest(glide, slide).into(GlideDrawableListeningTarget(binding.thumbnailImage, binding.thumbnailLoadIndicator, result))
}
slide.hasPlaceholder() -> {
- buildPlaceholderGlideRequest(glide, slide).into(GlideBitmapListeningTarget(binding.thumbnailImage, result))
+ buildPlaceholderGlideRequest(glide, slide).into(GlideBitmapListeningTarget(binding.thumbnailImage, null, result))
}
else -> {
glide.clear(binding.thumbnailImage)
@@ -190,7 +190,7 @@ open class ThumbnailView: FrameLayout {
request.transforms(CenterCrop())
}
- request.into(GlideDrawableListeningTarget(binding.thumbnailImage, future))
+ request.into(GlideDrawableListeningTarget(binding.thumbnailImage, binding.thumbnailLoadIndicator, future))
return future
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java
index a5333ef5d..62aaf58f1 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java
@@ -52,6 +52,7 @@ public class IdentityKeyUtil {
public static final String IDENTITY_PRIVATE_KEY_PREF = "pref_identity_private_v3";
public static final String ED25519_PUBLIC_KEY = "pref_ed25519_public_key";
public static final String ED25519_SECRET_KEY = "pref_ed25519_secret_key";
+ public static final String NOTIFICATION_KEY = "pref_notification_key";
public static final String LOKI_SEED = "loki_seed";
public static final String HAS_MIGRATED_KEY = "has_migrated_keys";
diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java
index 43e986559..7f0edddeb 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java
@@ -1,13 +1,13 @@
package org.thoughtcrime.securesms.crypto;
-import android.os.Build;
+import static org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK;
+
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;
import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
@@ -45,44 +45,50 @@ public final class KeyStoreHelper {
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
private static final String KEY_ALIAS = "SignalSecret";
- @RequiresApi(Build.VERSION_CODES.M)
public static SealedData seal(@NonNull byte[] input) {
SecretKey secretKey = getOrCreateKeyStoreEntry();
try {
- Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
- cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+ // Cipher operations are not thread-safe so we synchronize over them through doFinal to
+ // prevent crashes with quickly repeated encrypt/decrypt operations
+ // https://github.com/mozilla-mobile/android-components/issues/5342
+ synchronized (CIPHER_LOCK) {
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey);
- byte[] iv = cipher.getIV();
- byte[] data = cipher.doFinal(input);
+ byte[] iv = cipher.getIV();
+ byte[] data = cipher.doFinal(input);
- return new SealedData(iv, data);
+ return new SealedData(iv, data);
+ }
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e);
}
}
- @RequiresApi(Build.VERSION_CODES.M)
public static byte[] unseal(@NonNull SealedData sealedData) {
SecretKey secretKey = getKeyStoreEntry();
try {
- Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
- cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, sealedData.iv));
+ // Cipher operations are not thread-safe so we synchronize over them through doFinal to
+ // prevent crashes with quickly repeated encrypt/decrypt operations
+ // https://github.com/mozilla-mobile/android-components/issues/5342
+ synchronized (CIPHER_LOCK) {
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+ cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, sealedData.iv));
- return cipher.doFinal(sealedData.data);
+ return cipher.doFinal(sealedData.data);
+ }
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e);
}
}
- @RequiresApi(Build.VERSION_CODES.M)
private static SecretKey getOrCreateKeyStoreEntry() {
if (hasKeyStoreEntry()) return getKeyStoreEntry();
else return createKeyStoreEntry();
}
- @RequiresApi(Build.VERSION_CODES.M)
private static SecretKey createKeyStoreEntry() {
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
@@ -99,7 +105,6 @@ public final class KeyStoreHelper {
}
}
- @RequiresApi(Build.VERSION_CODES.M)
private static SecretKey getKeyStoreEntry() {
KeyStore keyStore = getKeyStore();
@@ -137,7 +142,6 @@ public final class KeyStoreHelper {
}
}
- @RequiresApi(Build.VERSION_CODES.M)
private static boolean hasKeyStoreEntry() {
try {
KeyStore ks = KeyStore.getInstance(ANDROID_KEY_STORE);
@@ -202,7 +206,5 @@ public final class KeyStoreHelper {
return Base64.decode(p.getValueAsString(), Base64.NO_WRAP | Base64.NO_PADDING);
}
}
-
}
-
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ConfigDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ConfigDatabase.kt
new file mode 100644
index 000000000..19a511bfd
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/ConfigDatabase.kt
@@ -0,0 +1,53 @@
+package org.thoughtcrime.securesms.database
+
+import android.content.Context
+import androidx.core.content.contentValuesOf
+import androidx.core.database.getBlobOrNull
+import androidx.core.database.getLongOrNull
+import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
+
+class ConfigDatabase(context: Context, helper: SQLCipherOpenHelper): Database(context, helper) {
+
+ companion object {
+ private const val VARIANT = "variant"
+ private const val PUBKEY = "publicKey"
+ private const val DATA = "data"
+ private const val TIMESTAMP = "timestamp" // Milliseconds
+
+ private const val TABLE_NAME = "configs_table"
+
+ const val CREATE_CONFIG_TABLE_COMMAND =
+ "CREATE TABLE $TABLE_NAME ($VARIANT TEXT NOT NULL, $PUBKEY TEXT NOT NULL, $DATA BLOB, $TIMESTAMP INTEGER NOT NULL DEFAULT 0, PRIMARY KEY($VARIANT, $PUBKEY));"
+
+ private const val VARIANT_AND_PUBKEY_WHERE = "$VARIANT = ? AND $PUBKEY = ?"
+ }
+
+ fun storeConfig(variant: String, publicKey: String, data: ByteArray, timestamp: Long) {
+ val db = writableDatabase
+ val contentValues = contentValuesOf(
+ VARIANT to variant,
+ PUBKEY to publicKey,
+ DATA to data,
+ TIMESTAMP to timestamp
+ )
+ db.insertOrUpdate(TABLE_NAME, contentValues, VARIANT_AND_PUBKEY_WHERE, arrayOf(variant, publicKey))
+ }
+
+ fun retrieveConfigAndHashes(variant: String, publicKey: String): ByteArray? {
+ val db = readableDatabase
+ val query = db.query(TABLE_NAME, arrayOf(DATA), VARIANT_AND_PUBKEY_WHERE, arrayOf(variant, publicKey),null, null, null)
+ return query?.use { cursor ->
+ if (!cursor.moveToFirst()) return@use null
+ val bytes = cursor.getBlobOrNull(cursor.getColumnIndex(DATA)) ?: return@use null
+ bytes
+ }
+ }
+
+ fun retrieveConfigLastUpdateTimestamp(variant: String, publicKey: String): Long {
+ val db = readableDatabase
+ val cursor = db.query(TABLE_NAME, arrayOf(TIMESTAMP), VARIANT_AND_PUBKEY_WHERE, arrayOf(variant, publicKey),null, null, null)
+ if (cursor == null) return 0
+ if (!cursor.moveToFirst()) return 0
+ return (cursor.getLongOrNull(cursor.getColumnIndex(TIMESTAMP)) ?: 0)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/FastCursorRecyclerViewAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/database/FastCursorRecyclerViewAdapter.java
deleted file mode 100644
index 4dfe6a20b..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/database/FastCursorRecyclerViewAdapter.java
+++ /dev/null
@@ -1,110 +0,0 @@
-package org.thoughtcrime.securesms.database;
-
-
-import android.content.Context;
-import android.database.Cursor;
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-
-public abstract class FastCursorRecyclerViewAdapter
- extends CursorRecyclerViewAdapter
-{
- private static final String TAG = FastCursorRecyclerViewAdapter.class.getSimpleName();
-
- private final LinkedList fastRecords = new LinkedList<>();
- private final List releasedRecordIds = new LinkedList<>();
-
- protected FastCursorRecyclerViewAdapter(Context context, Cursor cursor) {
- super(context, cursor);
- }
-
- public void addFastRecord(@NonNull T record) {
- fastRecords.addFirst(record);
- notifyDataSetChanged();
- }
-
- public void releaseFastRecord(long id) {
- synchronized (releasedRecordIds) {
- releasedRecordIds.add(id);
- }
- }
-
- protected void cleanFastRecords() {
- synchronized (releasedRecordIds) {
- Iterator releaseIdIterator = releasedRecordIds.iterator();
-
- while (releaseIdIterator.hasNext()) {
- long releasedId = releaseIdIterator.next();
- Iterator fastRecordIterator = fastRecords.iterator();
-
- while (fastRecordIterator.hasNext()) {
- if (isRecordForId(fastRecordIterator.next(), releasedId)) {
- fastRecordIterator.remove();
- releaseIdIterator.remove();
- break;
- }
- }
- }
- }
- }
-
- protected abstract T getRecordFromCursor(@NonNull Cursor cursor);
- protected abstract void onBindItemViewHolder(VH viewHolder, @NonNull T record);
- protected abstract long getItemId(@NonNull T record);
- protected abstract int getItemViewType(@NonNull T record);
- protected abstract boolean isRecordForId(@NonNull T record, long id);
-
- @Override
- public int getItemViewType(@NonNull Cursor cursor) {
- T record = getRecordFromCursor(cursor);
- return getItemViewType(record);
- }
-
- @Override
- public void onBindItemViewHolder(VH viewHolder, @NonNull Cursor cursor) {
- T record = getRecordFromCursor(cursor);
- onBindItemViewHolder(viewHolder, record);
- }
-
- @Override
- public void onBindFastAccessItemViewHolder(VH viewHolder, int position) {
- int calculatedPosition = getCalculatedPosition(position);
- onBindItemViewHolder(viewHolder, fastRecords.get(calculatedPosition));
- }
-
- @Override
- protected int getFastAccessSize() {
- return fastRecords.size();
- }
-
- protected T getRecordForPositionOrThrow(int position) {
- if (isFastAccessPosition(position)) {
- return fastRecords.get(getCalculatedPosition(position));
- } else {
- Cursor cursor = getCursorAtPositionOrThrow(position);
- return getRecordFromCursor(cursor);
- }
- }
-
- protected int getFastAccessItemViewType(int position) {
- return getItemViewType(fastRecords.get(getCalculatedPosition(position)));
- }
-
- protected boolean isFastAccessPosition(int position) {
- position = getCalculatedPosition(position);
- return position >= 0 && position < fastRecords.size();
- }
-
- protected long getFastAccessItemId(int position) {
- return getItemId(fastRecords.get(getCalculatedPosition(position)));
- }
-
- private int getCalculatedPosition(int position) {
- return hasHeaderView() ? position - 1 : position;
- }
-
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java
index 584bf3a71..66d01114e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java
@@ -36,9 +36,9 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
@SuppressWarnings("unused")
private static final String TAG = GroupDatabase.class.getSimpleName();
- static final String TABLE_NAME = "groups";
+ public static final String TABLE_NAME = "groups";
private static final String ID = "_id";
- static final String GROUP_ID = "group_id";
+ public static final String GROUP_ID = "group_id";
private static final String TITLE = "title";
private static final String MEMBERS = "members";
private static final String ZOMBIE_MEMBERS = "zombie_members";
@@ -133,12 +133,12 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
return new Reader(cursor);
}
- public List getAllGroups() {
+ public List getAllGroups(boolean includeInactive) {
Reader reader = getGroups();
GroupRecord record;
List groups = new LinkedList<>();
while ((record = reader.getNext()) != null) {
- if (record.isActive()) { groups.add(record); }
+ if (record.isActive() || includeInactive) { groups.add(record); }
}
reader.close();
return groups;
@@ -318,6 +318,25 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
notifyConversationListListeners();
}
+ @Override
+ public void removeProfilePicture(String groupID) {
+ databaseHelper.getWritableDatabase()
+ .execSQL("UPDATE " + TABLE_NAME +
+ " SET " + AVATAR + " = NULL, " +
+ AVATAR_ID + " = NULL, " +
+ AVATAR_KEY + " = NULL, " +
+ AVATAR_CONTENT_TYPE + " = NULL, " +
+ AVATAR_RELAY + " = NULL, " +
+ AVATAR_DIGEST + " = NULL, " +
+ AVATAR_URL + " = NULL" +
+ " WHERE " +
+ GROUP_ID + " = ?",
+ new String[] {groupID});
+
+ Recipient.applyCached(Address.fromSerialized(groupID), recipient -> recipient.setGroupAvatarId(null));
+ notifyConversationListListeners();
+ }
+
public boolean hasDownloadedProfilePicture(String groupId) {
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[]{AVATAR}, GROUP_ID + " = ?",
new String[] {groupId},
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java
deleted file mode 100644
index ef4746923..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java
+++ /dev/null
@@ -1,249 +0,0 @@
-package org.thoughtcrime.securesms.database;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import androidx.annotation.NonNull;
-
-import net.zetetic.database.sqlcipher.SQLiteDatabase;
-
-import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
-import org.thoughtcrime.securesms.jobmanager.persistence.ConstraintSpec;
-import org.thoughtcrime.securesms.jobmanager.persistence.DependencySpec;
-import org.thoughtcrime.securesms.jobmanager.persistence.FullSpec;
-import org.thoughtcrime.securesms.jobmanager.persistence.JobSpec;
-
-import java.util.LinkedList;
-import java.util.List;
-
-public class JobDatabase extends Database {
-
- public static final String[] CREATE_TABLE = new String[] { Jobs.CREATE_TABLE,
- Constraints.CREATE_TABLE,
- Dependencies.CREATE_TABLE };
-
- public static final class Jobs {
- public static final String TABLE_NAME = "job_spec";
- private static final String ID = "_id";
- private static final String JOB_SPEC_ID = "job_spec_id";
- private static final String FACTORY_KEY = "factory_key";
- private static final String QUEUE_KEY = "queue_key";
- private static final String CREATE_TIME = "create_time";
- private static final String NEXT_RUN_ATTEMPT_TIME = "next_run_attempt_time";
- private static final String RUN_ATTEMPT = "run_attempt";
- private static final String MAX_ATTEMPTS = "max_attempts";
- private static final String MAX_BACKOFF = "max_backoff";
- private static final String MAX_INSTANCES = "max_instances";
- private static final String LIFESPAN = "lifespan";
- private static final String SERIALIZED_DATA = "serialized_data";
- private static final String IS_RUNNING = "is_running";
-
- private static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
- JOB_SPEC_ID + " TEXT UNIQUE, " +
- FACTORY_KEY + " TEXT, " +
- QUEUE_KEY + " TEXT, " +
- CREATE_TIME + " INTEGER, " +
- NEXT_RUN_ATTEMPT_TIME + " INTEGER, " +
- RUN_ATTEMPT + " INTEGER, " +
- MAX_ATTEMPTS + " INTEGER, " +
- MAX_BACKOFF + " INTEGER, " +
- MAX_INSTANCES + " INTEGER, " +
- LIFESPAN + " INTEGER, " +
- SERIALIZED_DATA + " TEXT, " +
- IS_RUNNING + " INTEGER)";
- }
-
- public static final class Constraints {
- public static final String TABLE_NAME = "constraint_spec";
- private static final String ID = "_id";
- private static final String JOB_SPEC_ID = "job_spec_id";
- private static final String FACTORY_KEY = "factory_key";
-
- private static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
- JOB_SPEC_ID + " TEXT, " +
- FACTORY_KEY + " TEXT, " +
- "UNIQUE(" + JOB_SPEC_ID + ", " + FACTORY_KEY + "))";
- }
-
- public static final class Dependencies {
- public static final String TABLE_NAME = "dependency_spec";
- private static final String ID = "_id";
- private static final String JOB_SPEC_ID = "job_spec_id";
- private static final String DEPENDS_ON_JOB_SPEC_ID = "depends_on_job_spec_id";
-
- private static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
- JOB_SPEC_ID + " TEXT, " +
- DEPENDS_ON_JOB_SPEC_ID + " TEXT, " +
- "UNIQUE(" + JOB_SPEC_ID + ", " + DEPENDS_ON_JOB_SPEC_ID + "))";
- }
-
-
- public JobDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
- super(context, databaseHelper);
- }
-
- public synchronized void insertJobs(@NonNull List fullSpecs) {
- SQLiteDatabase db = databaseHelper.getWritableDatabase();
-
- db.beginTransaction();
-
- try {
- for (FullSpec fullSpec : fullSpecs) {
- insertJobSpec(db, fullSpec.getJobSpec());
- insertConstraintSpecs(db, fullSpec.getConstraintSpecs());
- insertDependencySpecs(db, fullSpec.getDependencySpecs());
- }
-
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
-
- public synchronized @NonNull List getAllJobSpecs() {
- List jobs = new LinkedList<>();
-
- try (Cursor cursor = databaseHelper.getReadableDatabase().query(Jobs.TABLE_NAME, null, null, null, null, null, Jobs.CREATE_TIME + ", " + Jobs.ID + " ASC")) {
- while (cursor != null && cursor.moveToNext()) {
- jobs.add(jobSpecFromCursor(cursor));
- }
- }
-
- return jobs;
- }
-
- public synchronized void updateJobRunningState(@NonNull String id, boolean isRunning) {
- ContentValues contentValues = new ContentValues();
- contentValues.put(Jobs.IS_RUNNING, isRunning ? 1 : 0);
-
- String query = Jobs.JOB_SPEC_ID + " = ?";
- String[] args = new String[]{ id };
-
- databaseHelper.getWritableDatabase().update(Jobs.TABLE_NAME, contentValues, query, args);
- }
-
- public synchronized void updateJobAfterRetry(@NonNull String id, boolean isRunning, int runAttempt, long nextRunAttemptTime) {
- ContentValues contentValues = new ContentValues();
- contentValues.put(Jobs.IS_RUNNING, isRunning ? 1 : 0);
- contentValues.put(Jobs.RUN_ATTEMPT, runAttempt);
- contentValues.put(Jobs.NEXT_RUN_ATTEMPT_TIME, nextRunAttemptTime);
-
- String query = Jobs.JOB_SPEC_ID + " = ?";
- String[] args = new String[]{ id };
-
- databaseHelper.getWritableDatabase().update(Jobs.TABLE_NAME, contentValues, query, args);
- }
-
- public synchronized void updateAllJobsToBePending() {
- ContentValues contentValues = new ContentValues();
- contentValues.put(Jobs.IS_RUNNING, 0);
-
- databaseHelper.getWritableDatabase().update(Jobs.TABLE_NAME, contentValues, null, null);
- }
-
- public synchronized void deleteJobs(@NonNull List jobIds) {
- SQLiteDatabase db = databaseHelper.getWritableDatabase();
-
- db.beginTransaction();
-
- try {
- for (String jobId : jobIds) {
- String[] arg = new String[]{jobId};
-
- db.delete(Jobs.TABLE_NAME, Jobs.JOB_SPEC_ID + " = ?", arg);
- db.delete(Constraints.TABLE_NAME, Constraints.JOB_SPEC_ID + " = ?", arg);
- db.delete(Dependencies.TABLE_NAME, Dependencies.JOB_SPEC_ID + " = ?", arg);
- db.delete(Dependencies.TABLE_NAME, Dependencies.DEPENDS_ON_JOB_SPEC_ID + " = ?", arg);
- }
-
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
-
- public synchronized @NonNull List getAllConstraintSpecs() {
- List constraints = new LinkedList<>();
-
- try (Cursor cursor = databaseHelper.getReadableDatabase().query(Constraints.TABLE_NAME, null, null, null, null, null, null)) {
- while (cursor != null && cursor.moveToNext()) {
- constraints.add(constraintSpecFromCursor(cursor));
- }
- }
-
- return constraints;
- }
-
- public synchronized @NonNull List getAllDependencySpecs() {
- List dependencies = new LinkedList<>();
-
- try (Cursor cursor = databaseHelper.getReadableDatabase().query(Dependencies.TABLE_NAME, null, null, null, null, null, null)) {
- while (cursor != null && cursor.moveToNext()) {
- dependencies.add(dependencySpecFromCursor(cursor));
- }
- }
-
- return dependencies;
- }
-
- private void insertJobSpec(@NonNull SQLiteDatabase db, @NonNull JobSpec job) {
- ContentValues contentValues = new ContentValues();
- contentValues.put(Jobs.JOB_SPEC_ID, job.getId());
- contentValues.put(Jobs.FACTORY_KEY, job.getFactoryKey());
- contentValues.put(Jobs.QUEUE_KEY, job.getQueueKey());
- contentValues.put(Jobs.CREATE_TIME, job.getCreateTime());
- contentValues.put(Jobs.NEXT_RUN_ATTEMPT_TIME, job.getNextRunAttemptTime());
- contentValues.put(Jobs.RUN_ATTEMPT, job.getRunAttempt());
- contentValues.put(Jobs.MAX_ATTEMPTS, job.getMaxAttempts());
- contentValues.put(Jobs.MAX_BACKOFF, job.getMaxBackoff());
- contentValues.put(Jobs.MAX_INSTANCES, job.getMaxInstances());
- contentValues.put(Jobs.LIFESPAN, job.getLifespan());
- contentValues.put(Jobs.SERIALIZED_DATA, job.getSerializedData());
- contentValues.put(Jobs.IS_RUNNING, job.isRunning() ? 1 : 0);
-
- db.insertWithOnConflict(Jobs.TABLE_NAME, null, contentValues, SQLiteDatabase.CONFLICT_IGNORE);
- }
-
- private void insertConstraintSpecs(@NonNull SQLiteDatabase db, @NonNull List constraints) {
- for (ConstraintSpec constraintSpec : constraints) {
- ContentValues contentValues = new ContentValues();
- contentValues.put(Constraints.JOB_SPEC_ID, constraintSpec.getJobSpecId());
- contentValues.put(Constraints.FACTORY_KEY, constraintSpec.getFactoryKey());
- db.insertWithOnConflict(Constraints.TABLE_NAME, null ,contentValues, SQLiteDatabase.CONFLICT_IGNORE);
- }
- }
-
- private void insertDependencySpecs(@NonNull SQLiteDatabase db, @NonNull List dependencies) {
- for (DependencySpec dependencySpec : dependencies) {
- ContentValues contentValues = new ContentValues();
- contentValues.put(Dependencies.JOB_SPEC_ID, dependencySpec.getJobId());
- contentValues.put(Dependencies.DEPENDS_ON_JOB_SPEC_ID, dependencySpec.getDependsOnJobId());
- db.insertWithOnConflict(Dependencies.TABLE_NAME, null, contentValues, SQLiteDatabase.CONFLICT_IGNORE);
- }
- }
-
- private @NonNull JobSpec jobSpecFromCursor(@NonNull Cursor cursor) {
- return new JobSpec(cursor.getString(cursor.getColumnIndexOrThrow(Jobs.JOB_SPEC_ID)),
- cursor.getString(cursor.getColumnIndexOrThrow(Jobs.FACTORY_KEY)),
- cursor.getString(cursor.getColumnIndexOrThrow(Jobs.QUEUE_KEY)),
- cursor.getLong(cursor.getColumnIndexOrThrow(Jobs.CREATE_TIME)),
- cursor.getLong(cursor.getColumnIndexOrThrow(Jobs.NEXT_RUN_ATTEMPT_TIME)),
- cursor.getInt(cursor.getColumnIndexOrThrow(Jobs.RUN_ATTEMPT)),
- cursor.getInt(cursor.getColumnIndexOrThrow(Jobs.MAX_ATTEMPTS)),
- cursor.getLong(cursor.getColumnIndexOrThrow(Jobs.MAX_BACKOFF)),
- cursor.getLong(cursor.getColumnIndexOrThrow(Jobs.LIFESPAN)),
- cursor.getInt(cursor.getColumnIndexOrThrow(Jobs.MAX_INSTANCES)),
- cursor.getString(cursor.getColumnIndexOrThrow(Jobs.SERIALIZED_DATA)),
- cursor.getInt(cursor.getColumnIndexOrThrow(Jobs.IS_RUNNING)) == 1);
- }
-
- private @NonNull ConstraintSpec constraintSpecFromCursor(@NonNull Cursor cursor) {
- return new ConstraintSpec(cursor.getString(cursor.getColumnIndexOrThrow(Constraints.JOB_SPEC_ID)),
- cursor.getString(cursor.getColumnIndexOrThrow(Constraints.FACTORY_KEY)));
- }
-
- private @NonNull DependencySpec dependencySpecFromCursor(@NonNull Cursor cursor) {
- return new DependencySpec(cursor.getString(cursor.getColumnIndexOrThrow(Dependencies.JOB_SPEC_ID)),
- cursor.getString(cursor.getColumnIndexOrThrow(Dependencies.DEPENDS_ON_JOB_SPEC_ID)));
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt
index b0f6a676c..53f4ea319 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt
@@ -458,9 +458,8 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
return ECKeyPair(DjbECPublicKey(keyPair.publicKey.serialize().removingIdPrefixIfNeeded()), DjbECPrivateKey(keyPair.privateKey.serialize()))
}
- fun addClosedGroupEncryptionKeyPair(encryptionKeyPair: ECKeyPair, groupPublicKey: String) {
+ fun addClosedGroupEncryptionKeyPair(encryptionKeyPair: ECKeyPair, groupPublicKey: String, timestamp: Long) {
val database = databaseHelper.writableDatabase
- val timestamp = Date().time.toString()
val index = "$groupPublicKey-$timestamp"
val encryptionKeyPairPublicKey = encryptionKeyPair.publicKey.serialize().toHexString().removingIdPrefixIfNeeded()
val encryptionKeyPairPrivateKey = encryptionKeyPair.privateKey.serialize().toHexString()
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt
index 300217fab..1cbbf34c9 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt
@@ -4,11 +4,8 @@ import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import org.session.libsession.messaging.open_groups.OpenGroup
-import org.session.libsession.utilities.Address
-import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.JsonUtil
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
-import org.thoughtcrime.securesms.dependencies.DatabaseComponent
class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) {
@@ -24,12 +21,6 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
val createPublicChatTableCommand = "CREATE TABLE $publicChatTable ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);"
}
- fun getThreadID(hexEncodedPublicKey: String): Long {
- val address = Address.fromSerialized(hexEncodedPublicKey)
- val recipient = Recipient.from(context, address, false)
- return DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient)
- }
-
fun getAllOpenGroups(): Map {
val database = databaseHelper.readableDatabase
var cursor: Cursor? = null
@@ -61,6 +52,13 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
}
}
+ fun getThreadId(openGroup: OpenGroup): Long? {
+ val database = databaseHelper.readableDatabase
+ return database.get(publicChatTable, "$publicChat = ?", arrayOf(JsonUtil.toJson(openGroup.toJson()))) { cursor ->
+ cursor.getLong(threadID)
+ }
+ }
+
fun setOpenGroupChat(openGroup: OpenGroup, threadID: Long) {
if (threadID < 0) {
return
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java
index d3ba31747..edc6bc1a6 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java
@@ -37,6 +37,13 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn
public abstract void markExpireStarted(long messageId, long startTime);
public abstract void markAsSent(long messageId, boolean secure);
+
+ public abstract void markAsSyncing(long id);
+
+ public abstract void markAsResyncing(long id);
+
+ public abstract void markAsSyncFailed(long id);
+
public abstract void markUnidentified(long messageId, boolean unidentified);
public abstract void markAsDeleted(long messageId, boolean read, boolean hasMention);
@@ -199,7 +206,6 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn
contentValues.put(THREAD_ID, newThreadId);
db.update(getTableName(), contentValues, where, args);
}
-
public static class SyncMessageId {
private final Address address;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt
index fb815107a..d14e63217 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt
@@ -20,13 +20,11 @@ import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import com.annimon.stream.Stream
-import com.google.android.mms.pdu_alt.NotificationInd
import com.google.android.mms.pdu_alt.PduHeaders
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import org.session.libsession.messaging.messages.signal.IncomingMediaMessage
-import org.session.libsession.messaging.messages.signal.OutgoingExpirationUpdateMessage
import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage
import org.session.libsession.messaging.messages.signal.OutgoingSecureMediaMessage
@@ -35,21 +33,19 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
+import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Address.Companion.UNKNOWN
import org.session.libsession.utilities.Address.Companion.fromExternal
import org.session.libsession.utilities.Address.Companion.fromSerialized
import org.session.libsession.utilities.Contact
-import org.session.libsession.utilities.GroupUtil.doubleEncodeGroupID
import org.session.libsession.utilities.IdentityKeyMismatch
import org.session.libsession.utilities.IdentityKeyMismatchList
import org.session.libsession.utilities.NetworkFailure
import org.session.libsession.utilities.NetworkFailureList
import org.session.libsession.utilities.TextSecurePreferences.Companion.isReadReceiptsEnabled
import org.session.libsession.utilities.Util.toIsoBytes
-import org.session.libsession.utilities.Util.toIsoString
import org.session.libsession.utilities.recipients.Recipient
-import org.session.libsession.utilities.recipients.RecipientFormattingException
import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.ThreadUtils.queue
@@ -59,11 +55,13 @@ import org.thoughtcrime.securesms.database.SmsDatabase.InsertListener
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import org.thoughtcrime.securesms.database.model.MessageRecord
+import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord
import org.thoughtcrime.securesms.database.model.Quote
import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get
import org.thoughtcrime.securesms.mms.MmsException
import org.thoughtcrime.securesms.mms.SlideDeck
+import org.thoughtcrime.securesms.util.asSequence
import java.io.Closeable
import java.io.IOException
import java.security.SecureRandom
@@ -90,54 +88,22 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
return 0
}
- fun addFailures(messageId: Long, failure: List) {
- try {
- addToDocument(messageId, NETWORK_FAILURE, failure, NetworkFailureList::class.java)
- } catch (e: IOException) {
- Log.w(TAG, e)
+ fun isOutgoingMessage(timestamp: Long): Boolean =
+ databaseHelper.writableDatabase.query(
+ TABLE_NAME,
+ arrayOf(ID, THREAD_ID, MESSAGE_BOX, ADDRESS),
+ DATE_SENT + " = ?",
+ arrayOf(timestamp.toString()),
+ null,
+ null,
+ null,
+ null
+ ).use { cursor ->
+ cursor.asSequence()
+ .map { cursor.getColumnIndexOrThrow(MESSAGE_BOX) }
+ .map(cursor::getLong)
+ .any { MmsSmsColumns.Types.isOutgoingMessageType(it) }
}
- }
-
- fun removeFailure(messageId: Long, failure: NetworkFailure?) {
- try {
- removeFromDocument(messageId, NETWORK_FAILURE, failure, NetworkFailureList::class.java)
- } catch (e: IOException) {
- Log.w(TAG, e)
- }
- }
-
- fun isOutgoingMessage(timestamp: Long): Boolean {
- val database = databaseHelper.writableDatabase
- var cursor: Cursor? = null
- var isOutgoing = false
- try {
- cursor = database.query(
- TABLE_NAME,
- arrayOf(ID, THREAD_ID, MESSAGE_BOX, ADDRESS),
- DATE_SENT + " = ?",
- arrayOf(timestamp.toString()),
- null,
- null,
- null,
- null
- )
- while (cursor.moveToNext()) {
- if (MmsSmsColumns.Types.isOutgoingMessageType(
- cursor.getLong(
- cursor.getColumnIndexOrThrow(
- MESSAGE_BOX
- )
- )
- )
- ) {
- isOutgoing = true
- }
- }
- } finally {
- cursor?.close()
- }
- return isOutgoing
- }
fun incrementReceiptCount(
messageId: SyncMessageId,
@@ -191,7 +157,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
)
get(context).groupReceiptDatabase()
.update(ourAddress, id, status, timestamp)
- get(context).threadDatabase().update(threadId, false)
+ get(context).threadDatabase().update(threadId, false, true)
notifyConversationListeners(threadId)
}
}
@@ -234,34 +200,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
}
}
- @Throws(RecipientFormattingException::class, MmsException::class)
- private fun getThreadIdFor(retrieved: IncomingMediaMessage): Long {
- return if (retrieved.groupId != null) {
- val groupRecipients = Recipient.from(
- context,
- retrieved.groupId,
- true
- )
- get(context).threadDatabase().getOrCreateThreadIdFor(groupRecipients)
- } else {
- val sender = Recipient.from(
- context,
- retrieved.from,
- true
- )
- get(context).threadDatabase().getOrCreateThreadIdFor(sender)
- }
- }
-
- private fun getThreadIdFor(notification: NotificationInd): Long {
- val fromString =
- if (notification.from != null && notification.from.textString != null) toIsoString(
- notification.from.textString
- ) else ""
- val recipient = Recipient.from(context, fromExternal(context, fromString), false)
- return get(context).threadDatabase().getOrCreateThreadIdFor(recipient)
- }
-
private fun rawQuery(where: String, arguments: Array?): Cursor {
val database = databaseHelper.readableDatabase
return database.rawQuery(
@@ -272,10 +210,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
)
}
- fun getMessages(idsAsString: String): Cursor {
- return rawQuery(idsAsString, null)
- }
-
fun getMessage(messageId: Long): Cursor {
val cursor = rawQuery(RAW_ID_WHERE, arrayOf(messageId.toString()))
setNotifyConverationListeners(cursor, getThreadIdForMessage(messageId))
@@ -301,52 +235,44 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
" WHERE " + ID + " = ?", arrayOf(id.toString() + "")
)
if (threadId.isPresent) {
- get(context).threadDatabase().update(threadId.get(), false)
+ get(context).threadDatabase().update(threadId.get(), false, true)
}
}
- fun markAsPendingInsecureSmsFallback(messageId: Long) {
- val threadId = getThreadIdForMessage(messageId)
+ private fun markAs(
+ messageId: Long,
+ baseType: Long,
+ threadId: Long = getThreadIdForMessage(messageId)
+ ) {
updateMailboxBitmask(
messageId,
MmsSmsColumns.Types.BASE_TYPE_MASK,
- MmsSmsColumns.Types.BASE_PENDING_INSECURE_SMS_FALLBACK,
+ baseType,
Optional.of(threadId)
)
notifyConversationListeners(threadId)
}
+ override fun markAsSyncing(messageId: Long) {
+ markAs(messageId, MmsSmsColumns.Types.BASE_SYNCING_TYPE)
+ }
+ override fun markAsResyncing(messageId: Long) {
+ markAs(messageId, MmsSmsColumns.Types.BASE_RESYNCING_TYPE)
+ }
+ override fun markAsSyncFailed(messageId: Long) {
+ markAs(messageId, MmsSmsColumns.Types.BASE_SYNC_FAILED_TYPE)
+ }
+
fun markAsSending(messageId: Long) {
- val threadId = getThreadIdForMessage(messageId)
- updateMailboxBitmask(
- messageId,
- MmsSmsColumns.Types.BASE_TYPE_MASK,
- MmsSmsColumns.Types.BASE_SENDING_TYPE,
- Optional.of(threadId)
- )
- notifyConversationListeners(threadId)
+ markAs(messageId, MmsSmsColumns.Types.BASE_SENDING_TYPE)
}
fun markAsSentFailed(messageId: Long) {
- val threadId = getThreadIdForMessage(messageId)
- updateMailboxBitmask(
- messageId,
- MmsSmsColumns.Types.BASE_TYPE_MASK,
- MmsSmsColumns.Types.BASE_SENT_FAILED_TYPE,
- Optional.of(threadId)
- )
- notifyConversationListeners(threadId)
+ markAs(messageId, MmsSmsColumns.Types.BASE_SENT_FAILED_TYPE)
}
override fun markAsSent(messageId: Long, secure: Boolean) {
- val threadId = getThreadIdForMessage(messageId)
- updateMailboxBitmask(
- messageId,
- MmsSmsColumns.Types.BASE_TYPE_MASK,
- MmsSmsColumns.Types.BASE_SENT_TYPE or if (secure) MmsSmsColumns.Types.PUSH_MESSAGE_BIT or MmsSmsColumns.Types.SECURE_MESSAGE_BIT else 0,
- Optional.of(threadId)
- )
- notifyConversationListeners(threadId)
+ markAs(messageId, MmsSmsColumns.Types.BASE_SENT_TYPE or if (secure) MmsSmsColumns.Types.PUSH_MESSAGE_BIT or MmsSmsColumns.Types.SECURE_MESSAGE_BIT else 0)
}
override fun markUnidentified(messageId: Long, unidentified: Boolean) {
@@ -366,21 +292,12 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
val attachmentDatabase = get(context).attachmentDatabase()
queue(Runnable { attachmentDatabase.deleteAttachmentsForMessage(messageId) })
val threadId = getThreadIdForMessage(messageId)
- if (!read) {
- val mentionChange = if (hasMention) { 1 } else { 0 }
- get(context).threadDatabase().decrementUnread(threadId, 1, mentionChange)
- }
- updateMailboxBitmask(
- messageId,
- MmsSmsColumns.Types.BASE_TYPE_MASK,
- MmsSmsColumns.Types.BASE_DELETED_TYPE,
- Optional.of(threadId)
- )
- notifyConversationListeners(threadId)
+
+ markAs(messageId, MmsSmsColumns.Types.BASE_DELETED_TYPE, threadId)
}
override fun markExpireStarted(messageId: Long) {
- markExpireStarted(messageId, System.currentTimeMillis())
+ markExpireStarted(messageId, SnodeAPI.nowWithOffset)
}
override fun markExpireStarted(messageId: Long, startedTimestamp: Long) {
@@ -399,6 +316,13 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
database.update(TABLE_NAME, contentValues, ID_WHERE, arrayOf(id.toString()))
}
+ fun setMessagesRead(threadId: Long, beforeTime: Long): List {
+ return setMessagesRead(
+ THREAD_ID + " = ? AND (" + READ + " = 0 OR " + REACTIONS_UNREAD + " = 1) AND " + DATE_SENT + " <= ?",
+ arrayOf(threadId.toString(), beforeTime.toString())
+ )
+ }
+
fun setMessagesRead(threadId: Long): List {
return setMessagesRead(
THREAD_ID + " = ? AND (" + READ + " = 0 OR " + REACTIONS_UNREAD + " = 1)",
@@ -406,10 +330,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
)
}
- fun setAllMessagesRead(): List {
- return setMessagesRead(READ + " = 0", null)
- }
-
private fun setMessagesRead(where: String, arguments: Array?): List {
val database = databaseHelper.writableDatabase
val result: MutableList = LinkedList()
@@ -418,7 +338,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
try {
cursor = database.query(
TABLE_NAME,
- arrayOf(ID, ADDRESS, DATE_SENT, MESSAGE_BOX, EXPIRES_IN, EXPIRE_STARTED),
+ arrayOf(ID, ADDRESS, DATE_SENT, MESSAGE_BOX, EXPIRES_IN, EXPIRE_STARTED),
where,
arguments,
null,
@@ -627,18 +547,9 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
contentLocation: String,
threadId: Long, mailbox: Long,
serverTimestamp: Long,
- runIncrement: Boolean,
runThreadUpdate: Boolean
): Optional {
- var threadId = threadId
- if (threadId == -1L || retrieved.isGroupMessage) {
- try {
- threadId = getThreadIdFor(retrieved)
- } catch (e: RecipientFormattingException) {
- Log.w("MmsDatabase", e)
- if (threadId == -1L) throw MmsException(e)
- }
- }
+ if (threadId < 0 ) throw MmsException("No thread ID supplied!")
val contentValues = ContentValues()
contentValues.put(DATE_SENT, retrieved.sentTimeMillis)
contentValues.put(ADDRESS, retrieved.from.serialize())
@@ -692,12 +603,8 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
null,
)
if (!MmsSmsColumns.Types.isExpirationTimerUpdate(mailbox)) {
- if (runIncrement) {
- val mentionAmount = if (retrieved.hasMention()) { 1 } else { 0 }
- get(context).threadDatabase().incrementUnread(threadId, 1, mentionAmount)
- }
if (runThreadUpdate) {
- get(context).threadDatabase().update(threadId, true)
+ get(context).threadDatabase().update(threadId, true, true)
}
}
notifyConversationListeners(threadId)
@@ -711,27 +618,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
serverTimestamp: Long,
runThreadUpdate: Boolean
): Optional {
- var threadId = threadId
- if (threadId == -1L) {
- if (retrieved.isGroup) {
- val decodedGroupId: String = if (retrieved is OutgoingExpirationUpdateMessage) {
- retrieved.groupId
- } else {
- (retrieved as OutgoingGroupMediaMessage).groupId
- }
- val groupId: String
- groupId = try {
- doubleEncodeGroupID(decodedGroupId)
- } catch (e: IOException) {
- Log.e(TAG, "Couldn't encrypt group ID")
- throw MmsException(e)
- }
- val group = Recipient.from(context, fromSerialized(groupId), false)
- threadId = get(context).threadDatabase().getOrCreateThreadIdFor(group)
- } else {
- threadId = get(context).threadDatabase().getOrCreateThreadIdFor(retrieved.recipient)
- }
- }
+ if (threadId < 0 ) throw MmsException("No thread ID supplied!")
val messageId = insertMessageOutbox(retrieved, threadId, false, null, serverTimestamp, runThreadUpdate)
if (messageId == -1L) {
return Optional.absent()
@@ -746,7 +633,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
retrieved: IncomingMediaMessage,
threadId: Long,
serverTimestamp: Long = 0,
- runIncrement: Boolean,
runThreadUpdate: Boolean
): Optional {
var type = MmsSmsColumns.Types.BASE_INBOX_TYPE or MmsSmsColumns.Types.SECURE_MESSAGE_BIT
@@ -765,7 +651,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
if (retrieved.isMessageRequestResponse) {
type = type or MmsSmsColumns.Types.MESSAGE_REQUEST_RESPONSE_BIT
}
- return insertMessageInbox(retrieved, "", threadId, type, serverTimestamp, runIncrement, runThreadUpdate)
+ return insertMessageInbox(retrieved, "", threadId, type, serverTimestamp, runThreadUpdate)
}
@JvmOverloads
@@ -798,7 +684,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
// In open groups messages should be sorted by their server timestamp
var receivedTimestamp = serverTimestamp
if (serverTimestamp == 0L) {
- receivedTimestamp = System.currentTimeMillis()
+ receivedTimestamp = SnodeAPI.nowWithOffset
}
contentValues.put(DATE_RECEIVED, receivedTimestamp)
contentValues.put(SUBSCRIPTION_ID, message.subscriptionId)
@@ -854,10 +740,13 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
)
}
with (get(context).threadDatabase()) {
- setLastSeen(threadId)
+ val lastSeen = getLastSeenAndHasSent(threadId).first()
+ if (lastSeen < message.sentTimeMillis) {
+ setLastSeen(threadId, message.sentTimeMillis)
+ }
setHasSent(threadId, true)
if (runThreadUpdate) {
- update(threadId, true)
+ update(threadId, true, true)
}
}
return messageId
@@ -975,7 +864,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
groupReceiptDatabase.deleteRowsForMessage(messageId)
val database = databaseHelper.writableDatabase
database!!.delete(TABLE_NAME, ID_WHERE, arrayOf(messageId.toString()))
- val threadDeleted = get(context).threadDatabase().update(threadId, false)
+ val threadDeleted = get(context).threadDatabase().update(threadId, false, true)
notifyConversationListeners(threadId)
notifyStickerListeners()
notifyStickerPackListeners()
@@ -992,7 +881,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
val database = databaseHelper.writableDatabase
database!!.delete(TABLE_NAME, ID_IN, arrayOf(messageIds.joinToString(",")))
- val threadDeleted = get(context).threadDatabase().update(threadId, false)
+ val threadDeleted = get(context).threadDatabase().update(threadId, false, true)
notifyConversationListeners(threadId)
notifyStickerListeners()
notifyStickerPackListeners()
@@ -1245,7 +1134,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
}
val threadDb = get(context).threadDatabase()
for (threadId in threadIds) {
- val threadDeleted = threadDb.update(threadId, false)
+ val threadDeleted = threadDb.update(threadId, false, true)
notifyConversationListeners(threadId)
}
notifyStickerListeners()
@@ -1315,7 +1204,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
val slideDeck = SlideDeck(context, message!!.attachments)
return MediaMmsMessageRecord(
id, message.recipient, message.recipient,
- 1, System.currentTimeMillis(), System.currentTimeMillis(),
+ 1, SnodeAPI.nowWithOffset, SnodeAPI.nowWithOffset,
0, threadId, message.body,
slideDeck, slideDeck.slides.size,
if (message.isSecure) MmsSmsColumns.Types.getOutgoingEncryptedMessageType() else MmsSmsColumns.Types.getOutgoingSmsMessageType(),
@@ -1323,7 +1212,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
LinkedList(),
message.subscriptionId,
message.expiresIn,
- System.currentTimeMillis(), 0,
+ SnodeAPI.nowWithOffset, 0,
if (message.outgoingQuote != null) Quote(
message.outgoingQuote!!.id,
message.outgoingQuote!!.author,
@@ -1437,25 +1326,16 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
val attachments = get(context).attachmentDatabase().getAttachment(
cursor
)
- val contacts: List = getSharedContacts(
- cursor, attachments
- )
- val contactAttachments =
- contacts.map { obj: Contact? -> obj!!.avatarAttachment }
- .filter { a: Attachment? -> a != null }
- .toSet()
- val previews: List = getLinkPreviews(
- cursor, attachments
- )
- val previewAttachments =
- previews.filter { lp: LinkPreview? -> lp!!.getThumbnail().isPresent }
- .map { lp: LinkPreview? -> lp!!.getThumbnail().get() }
- .toSet()
+ val contacts: List = getSharedContacts(cursor, attachments)
+ val contactAttachments: Set =
+ contacts.mapNotNull { it?.avatarAttachment }.toSet()
+ val previews: List = getLinkPreviews(cursor, attachments)
+ val previewAttachments: Set =
+ previews.mapNotNull { it?.getThumbnail()?.orNull() }.toSet()
val slideDeck = getSlideDeck(
- Stream.of(attachments)
- .filterNot { o: DatabaseAttachment? -> contactAttachments.contains(o) }
- .filterNot { o: DatabaseAttachment? -> previewAttachments.contains(o) }
- .toList()
+ attachments
+ .filterNot { o: DatabaseAttachment? -> o in contactAttachments }
+ .filterNot { o: DatabaseAttachment? -> o in previewAttachments }
)
val quote = getQuote(cursor)
val reactions = get(context).reactionDatabase().getReactions(cursor)
@@ -1513,11 +1393,13 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
val retrievedQuote = get(context).mmsSmsDatabase().getMessageFor(quoteId, quoteAuthor)
val quoteText = retrievedQuote?.body
val quoteMissing = retrievedQuote == null
- val attachments = get(context).attachmentDatabase().getAttachment(cursor)
- val quoteAttachments: List? =
- Stream.of(attachments).filter { obj: DatabaseAttachment? -> obj!!.isQuote }
+ val quoteDeck = (
+ (retrievedQuote as? MmsMessageRecord)?.slideDeck ?:
+ Stream.of(get(context).attachmentDatabase().getAttachment(cursor))
+ .filter { obj: DatabaseAttachment? -> obj!!.isQuote }
.toList()
- val quoteDeck = SlideDeck(context, quoteAttachments!!)
+ .let { SlideDeck(context, it) }
+ )
return Quote(
quoteId,
fromExternal(context, quoteAuthor),
@@ -1617,6 +1499,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
SHARED_CONTACTS,
LINK_PREVIEWS,
UNIDENTIFIED,
+ HAS_MENTION,
"json_group_array(json_object(" +
"'" + AttachmentDatabase.ROW_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + ", " +
"'" + AttachmentDatabase.UNIQUE_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UNIQUE_ID + ", " +
@@ -1659,4 +1542,4 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
const val CREATE_REACTIONS_LAST_SEEN_COMMAND = "ALTER TABLE $TABLE_NAME ADD COLUMN $REACTIONS_LAST_SEEN INTEGER DEFAULT 0;"
const val CREATE_HAS_MENTION_COMMAND = "ALTER TABLE $TABLE_NAME ADD COLUMN $HAS_MENTION INTEGER DEFAULT 0;"
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java
index f3110a5c7..1e1cc5089 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java
@@ -47,8 +47,13 @@ public interface MmsSmsColumns {
protected static final long BASE_PENDING_INSECURE_SMS_FALLBACK = 26;
public static final long BASE_DRAFT_TYPE = 27;
protected static final long BASE_DELETED_TYPE = 28;
+ protected static final long BASE_SYNCING_TYPE = 29;
+ protected static final long BASE_RESYNCING_TYPE = 30;
+ protected static final long BASE_SYNC_FAILED_TYPE = 31;
protected static final long[] OUTGOING_MESSAGE_TYPES = {BASE_OUTBOX_TYPE, BASE_SENT_TYPE,
+ BASE_SYNCING_TYPE, BASE_RESYNCING_TYPE,
+ BASE_SYNC_FAILED_TYPE,
BASE_SENDING_TYPE, BASE_SENT_FAILED_TYPE,
BASE_PENDING_SECURE_SMS_FALLBACK,
BASE_PENDING_INSECURE_SMS_FALLBACK,
@@ -109,6 +114,18 @@ public interface MmsSmsColumns {
return (type & BASE_TYPE_MASK) == BASE_DRAFT_TYPE;
}
+ public static boolean isResyncingType(long type) {
+ return (type & BASE_TYPE_MASK) == BASE_RESYNCING_TYPE;
+ }
+
+ public static boolean isSyncingType(long type) {
+ return (type & BASE_TYPE_MASK) == BASE_SYNCING_TYPE;
+ }
+
+ public static boolean isSyncFailedMessageType(long type) {
+ return (type & BASE_TYPE_MASK) == BASE_SYNC_FAILED_TYPE;
+ }
+
public static boolean isFailedMessageType(long type) {
return (type & BASE_TYPE_MASK) == BASE_SENT_FAILED_TYPE;
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
index c7f9d6132..0db4dd00e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
@@ -16,6 +16,8 @@
*/
package org.thoughtcrime.securesms.database;
+import static org.thoughtcrime.securesms.database.MmsDatabase.MESSAGE_BOX;
+
import android.content.Context;
import android.database.Cursor;
@@ -25,6 +27,7 @@ import androidx.annotation.Nullable;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteQueryBuilder;
+import org.jetbrains.annotations.NotNull;
import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.Util;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
@@ -36,6 +39,8 @@ import java.io.Closeable;
import java.util.HashSet;
import java.util.Set;
+import kotlin.Pair;
+
public class MmsSmsDatabase extends Database {
@SuppressWarnings("unused")
@@ -259,8 +264,8 @@ public class MmsSmsDatabase extends Database {
return -1;
}
- public int getMessagePositionInConversation(long threadId, long sentTimestamp, @NonNull Address address) {
- String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC";
+ public int getMessagePositionInConversation(long threadId, long sentTimestamp, @NonNull Address address, boolean reverse) {
+ String order = MmsSmsColumns.NORMALIZED_DATE_SENT + (reverse ? " DESC" : " ASC");
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
try (Cursor cursor = queryTables(new String[]{ MmsSmsColumns.NORMALIZED_DATE_SENT, MmsSmsColumns.ADDRESS }, selection, order, null)) {
@@ -512,6 +517,23 @@ public class MmsSmsDatabase extends Database {
return new Reader(cursor);
}
+ @NotNull
+ public Pair timestampAndDirectionForCurrent(@NotNull Cursor cursor) {
+ int sentColumn = cursor.getColumnIndex(MmsSmsColumns.NORMALIZED_DATE_SENT);
+ String msgType = cursor.getString(cursor.getColumnIndexOrThrow(TRANSPORT));
+ long sentTime = cursor.getLong(sentColumn);
+ long type = 0;
+ if (MmsSmsDatabase.MMS_TRANSPORT.equals(msgType)) {
+ int typeIndex = cursor.getColumnIndex(MESSAGE_BOX);
+ type = cursor.getLong(typeIndex);
+ } else if (MmsSmsDatabase.SMS_TRANSPORT.equals(msgType)) {
+ int typeIndex = cursor.getColumnIndex(SmsDatabase.TYPE);
+ type = cursor.getLong(typeIndex);
+ }
+
+ return new Pair(MmsSmsColumns.Types.isOutgoingMessageType(type), sentTime);
+ }
+
public class Reader implements Closeable {
private final Cursor cursor;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java
index ab4cb9f2e..28fe60897 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java
@@ -62,6 +62,7 @@ public class RecipientDatabase extends Database {
private static final String UNIDENTIFIED_ACCESS_MODE = "unidentified_access_mode";
private static final String FORCE_SMS_SELECTION = "force_sms_selection";
private static final String NOTIFY_TYPE = "notify_type"; // all, mentions only, none
+ private static final String WRAPPER_HASH = "wrapper_hash";
private static final String AUTO_DOWNLOAD = "auto_download"; // 1 / 0 / -1 flag for whether to auto-download in a conversation, or if the user hasn't selected a preference
private static final String[] RECIPIENT_PROJECTION = new String[] {
@@ -69,7 +70,7 @@ public class RecipientDatabase extends Database {
PROFILE_KEY, SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_CONTACT_URI,
SIGNAL_PROFILE_NAME, SIGNAL_PROFILE_AVATAR, PROFILE_SHARING, NOTIFICATION_CHANNEL,
UNIDENTIFIED_ACCESS_MODE,
- FORCE_SMS_SELECTION, NOTIFY_TYPE, AUTO_DOWNLOAD,
+ FORCE_SMS_SELECTION, NOTIFY_TYPE, WRAPPER_HASH, AUTO_DOWNLOAD,
};
static final List TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION)
@@ -148,6 +149,11 @@ public class RecipientDatabase extends Database {
"OR "+ADDRESS+" IN (SELECT "+GroupDatabase.TABLE_NAME+"."+GroupDatabase.ADMINS+" FROM "+GroupDatabase.TABLE_NAME+")))";
}
+ public static String getAddWrapperHash() {
+ return "ALTER TABLE "+TABLE_NAME+" "+
+ "ADD COLUMN "+WRAPPER_HASH+" TEXT DEFAULT NULL;";
+ }
+
public static final int NOTIFY_TYPE_ALL = 0;
public static final int NOTIFY_TYPE_MENTIONS = 1;
public static final int NOTIFY_TYPE_NONE = 2;
@@ -166,18 +172,14 @@ public class RecipientDatabase extends Database {
public Optional getRecipientSettings(@NonNull Address address) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
- Cursor cursor = null;
- try {
- cursor = database.query(TABLE_NAME, null, ADDRESS + " = ?", new String[] {address.serialize()}, null, null, null);
+ try (Cursor cursor = database.query(TABLE_NAME, null, ADDRESS + " = ?", new String[]{address.serialize()}, null, null, null)) {
if (cursor != null && cursor.moveToNext()) {
return getRecipientSettings(cursor);
}
return Optional.absent();
- } finally {
- if (cursor != null) cursor.close();
}
}
@@ -207,6 +209,7 @@ public class RecipientDatabase extends Database {
String notificationChannel = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION_CHANNEL));
int unidentifiedAccessMode = cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED_ACCESS_MODE));
boolean forceSmsSelection = cursor.getInt(cursor.getColumnIndexOrThrow(FORCE_SMS_SELECTION)) == 1;
+ String wrapperHash = cursor.getString(cursor.getColumnIndexOrThrow(WRAPPER_HASH));
MaterialColor color;
byte[] profileKey = null;
@@ -238,7 +241,7 @@ public class RecipientDatabase extends Database {
systemPhoneLabel, systemContactUri,
signalProfileName, signalProfileAvatar, profileSharing,
notificationChannel, Recipient.UnidentifiedAccessMode.fromMode(unidentifiedAccessMode),
- forceSmsSelection));
+ forceSmsSelection, wrapperHash));
}
public boolean isAutoDownloadFlagSet(Recipient recipient) {
@@ -281,6 +284,24 @@ public class RecipientDatabase extends Database {
notifyRecipientListeners();
}
+ public boolean getApproved(@NonNull Address address) {
+ SQLiteDatabase db = getReadableDatabase();
+ try (Cursor cursor = db.query(TABLE_NAME, new String[]{APPROVED}, ADDRESS + " = ?", new String[]{address.serialize()}, null, null, null)) {
+ if (cursor != null && cursor.moveToNext()) {
+ return cursor.getInt(cursor.getColumnIndexOrThrow(APPROVED)) == 1;
+ }
+ }
+ return false;
+ }
+
+ public void setRecipientHash(@NonNull Recipient recipient, String recipientHash) {
+ ContentValues values = new ContentValues();
+ values.put(WRAPPER_HASH, recipientHash);
+ updateOrInsert(recipient.getAddress(), values);
+ recipient.resolve().setWrapperHash(recipientHash);
+ notifyRecipientListeners();
+ }
+
public void setApproved(@NonNull Recipient recipient, boolean approved) {
ContentValues values = new ContentValues();
values.put(APPROVED, approved ? 1 : 0);
@@ -297,15 +318,7 @@ public class RecipientDatabase extends Database {
notifyRecipientListeners();
}
- public void setBlocked(@NonNull Recipient recipient, boolean blocked) {
- ContentValues values = new ContentValues();
- values.put(BLOCK, blocked ? 1 : 0);
- updateOrInsert(recipient.getAddress(), values);
- recipient.resolve().setBlocked(blocked);
- notifyRecipientListeners();
- }
-
- public void setBlocked(@NonNull List recipients, boolean blocked) {
+ public void setBlocked(@NonNull Iterable recipients, boolean blocked) {
SQLiteDatabase db = getWritableDatabase();
db.beginTransaction();
try {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt
index 7080d9cb8..51365bb04 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt
@@ -3,8 +3,11 @@ package org.thoughtcrime.securesms.database
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
+import androidx.core.database.getStringOrNull
import org.session.libsession.messaging.contacts.Contact
+import org.session.libsession.messaging.utilities.SessionId
import org.session.libsignal.utilities.Base64
+import org.session.libsignal.utilities.IdPrefix
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
class SessionContactDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) {
@@ -42,6 +45,9 @@ class SessionContactDatabase(context: Context, helper: SQLCipherOpenHelper) : Da
val database = databaseHelper.readableDatabase
return database.getAll(sessionContactTable, null, null) { cursor ->
contactFromCursor(cursor)
+ }.filter { contact ->
+ val sessionId = SessionId(contact.sessionID)
+ sessionId.prefix == IdPrefix.STANDARD
}.toSet()
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt
index 4425e3d85..6221446aa 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt
@@ -46,7 +46,7 @@ class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
databaseHelper.writableDatabase.delete(sessionJobTable, "${Companion.jobID} = ?", arrayOf( jobID ))
}
- fun getAllPendingJobs(type: String): Map {
+ fun getAllJobs(type: String): Map {
val database = databaseHelper.readableDatabase
return database.getAll(sessionJobTable, "$jobType = ?", arrayOf( type )) { cursor ->
val jobID = cursor.getString(jobID)
@@ -83,16 +83,17 @@ class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
}
}
- fun getGroupAvatarDownloadJob(server: String, room: String): GroupAvatarDownloadJob? {
+ fun getGroupAvatarDownloadJob(server: String, room: String, imageId: String?): GroupAvatarDownloadJob? {
val database = databaseHelper.readableDatabase
return database.getAll(sessionJobTable, "$jobType = ?", arrayOf(GroupAvatarDownloadJob.KEY)) {
jobFromCursor(it) as GroupAvatarDownloadJob?
- }.filterNotNull().find { it.server == server && it.room == room }
+ }.filterNotNull().find { it.server == server && it.room == room && (imageId == null || it.imageId == imageId) }
}
fun cancelPendingMessageSendJobs(threadID: Long) {
val database = databaseHelper.writableDatabase
val attachmentUploadJobKeys = mutableListOf()
+ database.beginTransaction()
database.getAll(sessionJobTable, "$jobType = ?", arrayOf( AttachmentUploadJob.KEY )) { cursor ->
val job = jobFromCursor(cursor) as AttachmentUploadJob?
if (job != null && job.threadID == threadID.toString()) { attachmentUploadJobKeys.add(job.id!!) }
@@ -103,15 +104,19 @@ class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
if (job != null && job.message.threadID == threadID) { messageSendJobKeys.add(job.id!!) }
}
if (attachmentUploadJobKeys.isNotEmpty()) {
- val attachmentUploadJobKeysAsString = attachmentUploadJobKeys.joinToString(", ")
- database.delete(sessionJobTable, "${Companion.jobType} = ? AND ${Companion.jobID} IN (?)",
- arrayOf( AttachmentUploadJob.KEY, attachmentUploadJobKeysAsString ))
+ attachmentUploadJobKeys.forEach {
+ database.delete(sessionJobTable, "${Companion.jobType} = ? AND ${Companion.jobID} = ?",
+ arrayOf( AttachmentUploadJob.KEY, it ))
+ }
}
if (messageSendJobKeys.isNotEmpty()) {
- val messageSendJobKeysAsString = messageSendJobKeys.joinToString(", ")
- database.delete(sessionJobTable, "${Companion.jobType} = ? AND ${Companion.jobID} IN (?)",
- arrayOf( MessageSendJob.KEY, messageSendJobKeysAsString ))
+ messageSendJobKeys.forEach {
+ database.delete(sessionJobTable, "${Companion.jobType} = ? AND ${Companion.jobID} = ?",
+ arrayOf( MessageSendJob.KEY, it ))
+ }
}
+ database.setTransactionSuccessful()
+ database.endTransaction()
}
fun isJobCanceled(job: Job): Boolean {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java
index 76c3e6b9c..e48f27d49 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java
@@ -36,6 +36,7 @@ import org.session.libsession.messaging.calls.CallMessageType;
import org.session.libsession.messaging.messages.signal.IncomingGroupMessage;
import org.session.libsession.messaging.messages.signal.IncomingTextMessage;
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage;
+import org.session.libsession.snode.SnodeAPI;
import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.IdentityKeyMismatch;
import org.session.libsession.utilities.IdentityKeyMismatchList;
@@ -105,7 +106,7 @@ public class SmsDatabase extends MessagingDatabase {
PROTOCOL, READ, STATUS, TYPE,
REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER, DELIVERY_RECEIPT_COUNT,
MISMATCHED_IDENTITIES, SUBSCRIPTION_ID, EXPIRES_IN, EXPIRE_STARTED,
- NOTIFIED, READ_RECEIPT_COUNT, UNIDENTIFIED,
+ NOTIFIED, READ_RECEIPT_COUNT, UNIDENTIFIED, HAS_MENTION,
"json_group_array(json_object(" +
"'" + ReactionDatabase.ROW_ID + "', " + ReactionDatabase.TABLE_NAME + "." + ReactionDatabase.ROW_ID + ", " +
"'" + ReactionDatabase.MESSAGE_ID + "', " + ReactionDatabase.TABLE_NAME + "." + ReactionDatabase.MESSAGE_ID + ", " +
@@ -147,7 +148,7 @@ public class SmsDatabase extends MessagingDatabase {
long threadId = getThreadIdForMessage(id);
- DatabaseComponent.get(context).threadDatabase().update(threadId, false);
+ DatabaseComponent.get(context).threadDatabase().update(threadId, false, true);
notifyConversationListeners(threadId);
}
@@ -201,6 +202,21 @@ public class SmsDatabase extends MessagingDatabase {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SENDING_TYPE);
}
+ @Override
+ public void markAsSyncing(long id) {
+ updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SYNCING_TYPE);
+ }
+
+ @Override
+ public void markAsResyncing(long id) {
+ updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_RESYNCING_TYPE);
+ }
+
+ @Override
+ public void markAsSyncFailed(long id) {
+ updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SYNC_FAILED_TYPE);
+ }
+
@Override
public void markUnidentified(long id, boolean unidentified) {
ContentValues contentValues = new ContentValues(1);
@@ -218,16 +234,12 @@ public class SmsDatabase extends MessagingDatabase {
contentValues.put(BODY, "");
contentValues.put(HAS_MENTION, 0);
database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)});
- long threadId = getThreadIdForMessage(messageId);
- if (!read) {
- DatabaseComponent.get(context).threadDatabase().decrementUnread(threadId, 1, (hasMention ? 1 : 0));
- }
updateTypeBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_DELETED_TYPE);
}
@Override
public void markExpireStarted(long id) {
- markExpireStarted(id, System.currentTimeMillis());
+ markExpireStarted(id, SnodeAPI.getNowWithOffset());
}
@Override
@@ -240,7 +252,7 @@ public class SmsDatabase extends MessagingDatabase {
long threadId = getThreadIdForMessage(id);
- DatabaseComponent.get(context).threadDatabase().update(threadId, false);
+ DatabaseComponent.get(context).threadDatabase().update(threadId, false, true);
notifyConversationListeners(threadId);
}
@@ -303,7 +315,7 @@ public class SmsDatabase extends MessagingDatabase {
ID + " = ?",
new String[] {String.valueOf(cursor.getLong(cursor.getColumnIndexOrThrow(ID)))});
- DatabaseComponent.get(context).threadDatabase().update(threadId, false);
+ DatabaseComponent.get(context).threadDatabase().update(threadId, false, true);
notifyConversationListeners(threadId);
foundMessage = true;
}
@@ -321,6 +333,9 @@ public class SmsDatabase extends MessagingDatabase {
}
}
+ public List setMessagesRead(long threadId, long beforeTime) {
+ return setMessagesRead(THREAD_ID + " = ? AND (" + READ + " = 0 OR " + REACTIONS_UNREAD + " = 1) AND " + DATE_SENT + " <= ?", new String[]{threadId+"", beforeTime+""});
+ }
public List setMessagesRead(long threadId) {
return setMessagesRead(THREAD_ID + " = ? AND (" + READ + " = 0 OR " + REACTIONS_UNREAD + " = 1)", new String[] {String.valueOf(threadId)});
}
@@ -384,14 +399,14 @@ public class SmsDatabase extends MessagingDatabase {
long threadId = getThreadIdForMessage(messageId);
- DatabaseComponent.get(context).threadDatabase().update(threadId, true);
+ DatabaseComponent.get(context).threadDatabase().update(threadId, true, true);
notifyConversationListeners(threadId);
notifyConversationListListeners();
return new Pair<>(messageId, threadId);
}
- protected Optional insertMessageInbox(IncomingTextMessage message, long type, long serverTimestamp, boolean runIncrement, boolean runThreadUpdate) {
+ protected Optional insertMessageInbox(IncomingTextMessage message, long type, long serverTimestamp, boolean runThreadUpdate) {
if (message.isSecureMessage()) {
type |= Types.SECURE_MESSAGE_BIT;
} else if (message.isGroup()) {
@@ -470,12 +485,8 @@ public class SmsDatabase extends MessagingDatabase {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long messageId = db.insert(TABLE_NAME, null, values);
- if (unread && runIncrement) {
- DatabaseComponent.get(context).threadDatabase().incrementUnread(threadId, 1, (message.hasMention() ? 1 : 0));
- }
-
if (runThreadUpdate) {
- DatabaseComponent.get(context).threadDatabase().update(threadId, true);
+ DatabaseComponent.get(context).threadDatabase().update(threadId, true, true);
}
if (message.getSubscriptionId() != -1) {
@@ -488,16 +499,16 @@ public class SmsDatabase extends MessagingDatabase {
}
}
- public Optional insertMessageInbox(IncomingTextMessage message, boolean runIncrement, boolean runThreadUpdate) {
- return insertMessageInbox(message, Types.BASE_INBOX_TYPE, 0, runIncrement, runThreadUpdate);
+ public Optional insertMessageInbox(IncomingTextMessage message, boolean runThreadUpdate) {
+ return insertMessageInbox(message, Types.BASE_INBOX_TYPE, 0, runThreadUpdate);
}
public Optional insertCallMessage(IncomingTextMessage message) {
- return insertMessageInbox(message, 0, 0, true, true);
+ return insertMessageInbox(message, 0, 0, true);
}
- public Optional insertMessageInbox(IncomingTextMessage message, long serverTimestamp, boolean runIncrement, boolean runThreadUpdate) {
- return insertMessageInbox(message, Types.BASE_INBOX_TYPE, serverTimestamp, runIncrement, runThreadUpdate);
+ public Optional insertMessageInbox(IncomingTextMessage message, long serverTimestamp, boolean runThreadUpdate) {
+ return insertMessageInbox(message, Types.BASE_INBOX_TYPE, serverTimestamp, runThreadUpdate);
}
public Optional insertMessageOutbox(long threadId, OutgoingTextMessage message, long serverTimestamp, boolean runThreadUpdate) {
@@ -530,7 +541,7 @@ public class SmsDatabase extends MessagingDatabase {
contentValues.put(ADDRESS, address.serialize());
contentValues.put(THREAD_ID, threadId);
contentValues.put(BODY, message.getMessageBody());
- contentValues.put(DATE_RECEIVED, System.currentTimeMillis());
+ contentValues.put(DATE_RECEIVED, SnodeAPI.getNowWithOffset());
contentValues.put(DATE_SENT, message.getSentTimestampMillis());
contentValues.put(READ, 1);
contentValues.put(TYPE, type);
@@ -551,9 +562,12 @@ public class SmsDatabase extends MessagingDatabase {
}
if (runThreadUpdate) {
- DatabaseComponent.get(context).threadDatabase().update(threadId, true);
+ DatabaseComponent.get(context).threadDatabase().update(threadId, true, true);
+ }
+ long lastSeen = DatabaseComponent.get(context).threadDatabase().getLastSeenAndHasSent(threadId).first();
+ if (lastSeen < message.getSentTimestampMillis()) {
+ DatabaseComponent.get(context).threadDatabase().setLastSeen(threadId, message.getSentTimestampMillis());
}
- DatabaseComponent.get(context).threadDatabase().setLastSeen(threadId);
DatabaseComponent.get(context).threadDatabase().setHasSent(threadId, true);
@@ -600,7 +614,7 @@ public class SmsDatabase extends MessagingDatabase {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long threadId = getThreadIdForMessage(messageId);
db.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""});
- boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false);
+ boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false, true);
notifyConversationListeners(threadId);
return threadDeleted;
}
@@ -624,7 +638,7 @@ public class SmsDatabase extends MessagingDatabase {
ID + " IN (" + StringUtils.join(argsArray, ',') + ")",
argValues
);
- boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false);
+ boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false, true);
notifyConversationListeners(threadId);
return threadDeleted;
}
@@ -770,11 +784,11 @@ public class SmsDatabase extends MessagingDatabase {
public MessageRecord getCurrent() {
return new SmsMessageRecord(id, message.getMessageBody(),
message.getRecipient(), message.getRecipient(),
- System.currentTimeMillis(), System.currentTimeMillis(),
+ SnodeAPI.getNowWithOffset(), SnodeAPI.getNowWithOffset(),
0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(),
threadId, 0, new LinkedList(),
message.getExpiresIn(),
- System.currentTimeMillis(), 0, false, Collections.emptyList(), false);
+ SnodeAPI.getNowWithOffset(), 0, false, Collections.emptyList(), false);
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
index e43102086..f42cfc00e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
@@ -2,16 +2,43 @@ package org.thoughtcrime.securesms.database
import android.content.Context
import android.net.Uri
+import network.loki.messenger.libsession_util.ConfigBase
+import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN
+import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_PINNED
+import network.loki.messenger.libsession_util.Contacts
+import network.loki.messenger.libsession_util.ConversationVolatileConfig
+import network.loki.messenger.libsession_util.UserGroupsConfig
+import network.loki.messenger.libsession_util.UserProfile
+import network.loki.messenger.libsession_util.util.BaseCommunityInfo
+import network.loki.messenger.libsession_util.util.Conversation
+import network.loki.messenger.libsession_util.util.ExpiryMode
+import network.loki.messenger.libsession_util.util.GroupInfo
+import network.loki.messenger.libsession_util.util.UserPic
import org.session.libsession.avatars.AvatarHelper
import org.session.libsession.database.StorageProtocol
import org.session.libsession.messaging.BlindedIdMapping
import org.session.libsession.messaging.calls.CallMessageType
import org.session.libsession.messaging.contacts.Contact
-import org.session.libsession.messaging.jobs.*
+import org.session.libsession.messaging.jobs.AttachmentUploadJob
+import org.session.libsession.messaging.jobs.BackgroundGroupAddJob
+import org.session.libsession.messaging.jobs.ConfigurationSyncJob
+import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob
+import org.session.libsession.messaging.jobs.Job
+import org.session.libsession.messaging.jobs.JobQueue
+import org.session.libsession.messaging.jobs.MessageReceiveJob
+import org.session.libsession.messaging.jobs.MessageSendJob
+import org.session.libsession.messaging.jobs.RetrieveProfileAvatarJob
+import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.messages.control.ConfigurationMessage
import org.session.libsession.messaging.messages.control.MessageRequestResponse
-import org.session.libsession.messaging.messages.signal.*
+import org.session.libsession.messaging.messages.signal.IncomingEncryptedMessage
+import org.session.libsession.messaging.messages.signal.IncomingGroupMessage
+import org.session.libsession.messaging.messages.signal.IncomingMediaMessage
+import org.session.libsession.messaging.messages.signal.IncomingTextMessage
+import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage
+import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage
+import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
import org.session.libsession.messaging.messages.visible.Attachment
import org.session.libsession.messaging.messages.visible.Profile
import org.session.libsession.messaging.messages.visible.Reaction
@@ -23,12 +50,15 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
+import org.session.libsession.messaging.sending_receiving.notifications.PushRegistryV1
+import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
import org.session.libsession.messaging.utilities.SessionId
import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.messaging.utilities.UpdateMessageData
import org.session.libsession.snode.OnionRequestAPI
-import org.session.libsession.utilities.*
+import org.session.libsession.snode.SnodeAPI
+import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Address.Companion.fromSerialized
import org.session.libsession.utilities.GroupRecord
import org.session.libsession.utilities.GroupUtil
@@ -36,25 +66,104 @@ import org.session.libsession.utilities.ProfileKeyUtil
import org.session.libsession.utilities.SSKEnvironment
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient
+import org.session.libsignal.crypto.ecc.DjbECPrivateKey
+import org.session.libsignal.crypto.ecc.DjbECPublicKey
import org.session.libsignal.crypto.ecc.ECKeyPair
import org.session.libsignal.messages.SignalServiceAttachmentPointer
import org.session.libsignal.messages.SignalServiceGroup
+import org.session.libsignal.utilities.Base64
+import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.KeyHelper
+import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.guava.Optional
-import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.ReactionRecord
+import org.thoughtcrime.securesms.dependencies.ConfigFactory
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
+import org.thoughtcrime.securesms.groups.ClosedGroupManager
+import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.groups.OpenGroupManager
-import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
import org.thoughtcrime.securesms.mms.PartAuthority
+import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
import org.thoughtcrime.securesms.util.SessionMetaProtocol
import java.security.MessageDigest
+import network.loki.messenger.libsession_util.util.Contact as LibSessionContact
+
+open class Storage(context: Context, helper: SQLCipherOpenHelper, private val configFactory: ConfigFactory) : Database(context, helper), StorageProtocol,
+ ThreadDatabase.ConversationThreadUpdateListener {
+
+ override fun threadCreated(address: Address, threadId: Long) {
+ val localUserAddress = getUserPublicKey() ?: return
+ if (!getRecipientApproved(address) && localUserAddress != address.serialize()) return // don't store unapproved / message requests
+
+ val volatile = configFactory.convoVolatile ?: return
+ if (address.isGroup) {
+ val groups = configFactory.userGroups ?: return
+ if (address.isClosedGroup) {
+ val sessionId = GroupUtil.doubleDecodeGroupId(address.serialize())
+ val closedGroup = getGroup(address.toGroupString())
+ if (closedGroup != null && closedGroup.isActive) {
+ val legacyGroup = groups.getOrConstructLegacyGroupInfo(sessionId)
+ groups.set(legacyGroup)
+ val newVolatileParams = volatile.getOrConstructLegacyGroup(sessionId).copy(
+ lastRead = SnodeAPI.nowWithOffset,
+ )
+ volatile.set(newVolatileParams)
+ }
+ } else if (address.isOpenGroup) {
+ // these should be added on the group join / group info fetch
+ Log.w("Loki", "Thread created called for open group address, not adding any extra information")
+ }
+ } else if (address.isContact) {
+ // non-standard contact prefixes: 15, 00 etc shouldn't be stored in config
+ if (SessionId(address.serialize()).prefix != IdPrefix.STANDARD) return
+ // don't update our own address into the contacts DB
+ if (getUserPublicKey() != address.serialize()) {
+ val contacts = configFactory.contacts ?: return
+ contacts.upsertContact(address.serialize()) {
+ priority = ConfigBase.PRIORITY_VISIBLE
+ }
+ } else {
+ val userProfile = configFactory.user ?: return
+ userProfile.setNtsPriority(ConfigBase.PRIORITY_VISIBLE)
+ DatabaseComponent.get(context).threadDatabase().setHasSent(threadId, true)
+ }
+ val newVolatileParams = volatile.getOrConstructOneToOne(address.serialize())
+ volatile.set(newVolatileParams)
+ }
+ }
+
+ override fun threadDeleted(address: Address, threadId: Long) {
+ val volatile = configFactory.convoVolatile ?: return
+ if (address.isGroup) {
+ val groups = configFactory.userGroups ?: return
+ if (address.isClosedGroup) {
+ val sessionId = GroupUtil.doubleDecodeGroupId(address.serialize())
+ volatile.eraseLegacyClosedGroup(sessionId)
+ groups.eraseLegacyGroup(sessionId)
+ } else if (address.isOpenGroup) {
+ // these should be removed in the group leave / handling new configs
+ Log.w("Loki", "Thread delete called for open group address, expecting to be handled elsewhere")
+ }
+ } else {
+ // non-standard contact prefixes: 15, 00 etc shouldn't be stored in config
+ if (SessionId(address.serialize()).prefix != IdPrefix.STANDARD) return
+ volatile.eraseOneToOne(address.serialize())
+ if (getUserPublicKey() != address.serialize()) {
+ val contacts = configFactory.contacts ?: return
+ contacts.upsertContact(address.serialize()) {
+ priority = PRIORITY_HIDDEN
+ }
+ } else {
+ val userProfile = configFactory.user ?: return
+ userProfile.setNtsPriority(PRIORITY_HIDDEN)
+ }
+ }
+ ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
+ }
-class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol {
-
override fun getUserPublicKey(): String? {
return TextSecurePreferences.getLocalNumber(context)
}
@@ -70,13 +179,28 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
return Profile(displayName, profileKey, profilePictureUrl)
}
- override fun setUserProfilePictureURL(newValue: String) {
+ override fun setProfileAvatar(recipient: Recipient, profileAvatar: String?) {
+ val database = DatabaseComponent.get(context).recipientDatabase()
+ database.setProfileAvatar(recipient, profileAvatar)
+ }
+
+ override fun setProfilePicture(recipient: Recipient, newProfilePicture: String?, newProfileKey: ByteArray?) {
+ val db = DatabaseComponent.get(context).recipientDatabase()
+ db.setProfileAvatar(recipient, newProfilePicture)
+ db.setProfileKey(recipient, newProfileKey)
+ }
+
+ override fun setUserProfilePicture(newProfilePicture: String?, newProfileKey: ByteArray?) {
val ourRecipient = fromSerialized(getUserPublicKey()!!).let {
Recipient.from(context, it, false)
}
- TextSecurePreferences.setProfilePictureURL(context, newValue)
- RetrieveProfileAvatarJob(ourRecipient, newValue)
- ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(ourRecipient, newValue))
+ ourRecipient.resolve().profileKey = newProfileKey
+ TextSecurePreferences.setProfileKey(context, newProfileKey?.let { Base64.encodeBytes(it) })
+ TextSecurePreferences.setProfilePictureURL(context, newProfilePicture)
+
+ if (newProfileKey != null) {
+ JobQueue.shared.add(RetrieveProfileAvatarJob(newProfilePicture, ourRecipient.address))
+ }
}
override fun getOrGenerateRegistrationID(): Int {
@@ -99,19 +223,56 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
return database.getAttachmentsForMessage(messageID)
}
- override fun markConversationAsRead(threadId: Long, updateLastSeen: Boolean) {
+ override fun getLastSeen(threadId: Long): Long {
val threadDb = DatabaseComponent.get(context).threadDatabase()
- threadDb.setRead(threadId, updateLastSeen)
+ return threadDb.getLastSeenAndHasSent(threadId)?.first() ?: 0L
}
- override fun incrementUnread(threadId: Long, amount: Int, unreadMentionAmount: Int) {
+ override fun markConversationAsRead(threadId: Long, lastSeenTime: Long, force: Boolean) {
val threadDb = DatabaseComponent.get(context).threadDatabase()
- threadDb.incrementUnread(threadId, amount, unreadMentionAmount)
+ getRecipientForThread(threadId)?.let { recipient ->
+ val currentLastRead = threadDb.getLastSeenAndHasSent(threadId).first()
+ // don't set the last read in the volatile if we didn't set it in the DB
+ if (!threadDb.markAllAsRead(threadId, recipient.isGroupRecipient, lastSeenTime, force) && !force) return
+
+ // don't process configs for inbox recipients
+ if (recipient.isOpenGroupInboxRecipient) return
+
+ configFactory.convoVolatile?.let { config ->
+ val convo = when {
+ // recipient closed group
+ recipient.isClosedGroupRecipient -> config.getOrConstructLegacyGroup(GroupUtil.doubleDecodeGroupId(recipient.address.serialize()))
+ // recipient is open group
+ recipient.isOpenGroupRecipient -> {
+ val openGroupJoinUrl = getOpenGroup(threadId)?.joinURL ?: return
+ BaseCommunityInfo.parseFullUrl(openGroupJoinUrl)?.let { (base, room, pubKey) ->
+ config.getOrConstructCommunity(base, room, pubKey)
+ } ?: return
+ }
+ // otherwise recipient is one to one
+ recipient.isContactRecipient -> {
+ // don't process non-standard session IDs though
+ val sessionId = SessionId(recipient.address.serialize())
+ if (sessionId.prefix != IdPrefix.STANDARD) return
+
+ config.getOrConstructOneToOne(recipient.address.serialize())
+ }
+ else -> throw NullPointerException("Weren't expecting to have a convo with address ${recipient.address.serialize()}")
+ }
+ convo.lastRead = lastSeenTime
+ if (convo.unread) {
+ convo.unread = lastSeenTime <= currentLastRead
+ notifyConversationListListeners()
+ }
+ config.set(convo)
+ ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
+ }
+ }
}
override fun updateThread(threadId: Long, unarchive: Boolean) {
val threadDb = DatabaseComponent.get(context).threadDatabase()
- threadDb.update(threadId, unarchive)
+ threadDb.update(threadId, unarchive, false)
}
override fun persist(message: VisibleMessage,
@@ -120,7 +281,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
groupPublicKey: String?,
openGroupID: String?,
attachments: List,
- runIncrement: Boolean,
runThreadUpdate: Boolean): Long? {
var messageID: Long? = null
val senderAddress = fromSerialized(message.sender!!)
@@ -147,13 +307,16 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
}
val targetRecipient = Recipient.from(context, targetAddress, false)
if (!targetRecipient.isGroupRecipient) {
- val recipientDb = DatabaseComponent.get(context).recipientDatabase()
if (isUserSender || isUserBlindedSender) {
- recipientDb.setApproved(targetRecipient, true)
+ setRecipientApproved(targetRecipient, true)
} else {
- recipientDb.setApprovedMe(targetRecipient, true)
+ setRecipientApprovedMe(targetRecipient, true)
}
}
+ if (message.threadID == null && !targetRecipient.isOpenGroupRecipient) {
+ // open group recipients should explicitly create threads
+ message.threadID = getOrCreateThreadIdFor(targetAddress)
+ }
if (message.isMediaMessage() || attachments.isNotEmpty()) {
val quote: Optional = if (quotes != null) Optional.of(quotes) else Optional.absent()
val linkPreviews: Optional> = if (linkPreview.isEmpty()) Optional.absent() else Optional.of(linkPreview.mapNotNull { it!! })
@@ -167,7 +330,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
it.toSignalPointer()
}
val mediaMessage = IncomingMediaMessage.from(message, senderAddress, targetRecipient.expireMessages * 1000L, group, signalServiceAttachments, quote, linkPreviews)
- mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.receivedTimestamp ?: 0, runIncrement, runThreadUpdate)
+ mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID!!, message.receivedTimestamp ?: 0, runThreadUpdate)
}
if (insertResult.isPresent) {
messageID = insertResult.get().messageId
@@ -184,7 +347,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
val textMessage = if (isOpenGroupInvitation) IncomingTextMessage.fromOpenGroupInvitation(message.openGroupInvitation, senderAddress, message.sentTimestamp)
else IncomingTextMessage.from(message, senderAddress, group, targetRecipient.expireMessages * 1000L)
val encrypted = IncomingEncryptedMessage(textMessage, textMessage.messageBody)
- smsDatabase.insertMessageInbox(encrypted, message.receivedTimestamp ?: 0, runIncrement, runThreadUpdate)
+ smsDatabase.insertMessageInbox(encrypted, message.receivedTimestamp ?: 0, runThreadUpdate)
}
insertResult.orNull()?.let { result ->
messageID = result.messageId
@@ -211,7 +374,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
}
override fun getAllPendingJobs(type: String): Map {
- return DatabaseComponent.get(context).sessionJobDatabase().getAllPendingJobs(type)
+ return DatabaseComponent.get(context).sessionJobDatabase().getAllJobs(type)
}
override fun getAttachmentUploadJob(attachmentID: Long): AttachmentUploadJob? {
@@ -226,8 +389,14 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
return DatabaseComponent.get(context).sessionJobDatabase().getMessageReceiveJob(messageReceiveJobID)
}
- override fun getGroupAvatarDownloadJob(server: String, room: String): GroupAvatarDownloadJob? {
- return DatabaseComponent.get(context).sessionJobDatabase().getGroupAvatarDownloadJob(server, room)
+ override fun getGroupAvatarDownloadJob(server: String, room: String, imageId: String?): GroupAvatarDownloadJob? {
+ return DatabaseComponent.get(context).sessionJobDatabase().getGroupAvatarDownloadJob(server, room, imageId)
+ }
+
+ override fun getConfigSyncJob(destination: Destination): Job? {
+ return DatabaseComponent.get(context).sessionJobDatabase().getAllJobs(ConfigurationSyncJob.KEY).values.firstOrNull {
+ (it as? ConfigurationSyncJob)?.destination == destination
+ }
}
override fun resumeMessageSendJobIfNeeded(messageSendJobID: String) {
@@ -239,11 +408,201 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
return DatabaseComponent.get(context).sessionJobDatabase().isJobCanceled(job)
}
+ override fun cancelPendingMessageSendJobs(threadID: Long) {
+ val jobDb = DatabaseComponent.get(context).sessionJobDatabase()
+ jobDb.cancelPendingMessageSendJobs(threadID)
+ }
+
override fun getAuthToken(room: String, server: String): String? {
val id = "$server.$room"
return DatabaseComponent.get(context).lokiAPIDatabase().getAuthToken(id)
}
+ override fun notifyConfigUpdates(forConfigObject: ConfigBase) {
+ notifyUpdates(forConfigObject)
+ }
+
+ override fun conversationInConfig(publicKey: String?, groupPublicKey: String?, openGroupId: String?, visibleOnly: Boolean): Boolean {
+ return configFactory.conversationInConfig(publicKey, groupPublicKey, openGroupId, visibleOnly)
+ }
+
+ override fun canPerformConfigChange(variant: String, publicKey: String, changeTimestampMs: Long): Boolean {
+ return configFactory.canPerformChange(variant, publicKey, changeTimestampMs)
+ }
+
+ fun notifyUpdates(forConfigObject: ConfigBase) {
+ when (forConfigObject) {
+ is UserProfile -> updateUser(forConfigObject)
+ is Contacts -> updateContacts(forConfigObject)
+ is ConversationVolatileConfig -> updateConvoVolatile(forConfigObject)
+ is UserGroupsConfig -> updateUserGroups(forConfigObject)
+ }
+ }
+
+ private fun updateUser(userProfile: UserProfile) {
+ val userPublicKey = getUserPublicKey() ?: return
+ // would love to get rid of recipient and context from this
+ val recipient = Recipient.from(context, fromSerialized(userPublicKey), false)
+ // update name
+ val name = userProfile.getName() ?: return
+ val userPic = userProfile.getPic()
+ val profileManager = SSKEnvironment.shared.profileManager
+ if (name.isNotEmpty()) {
+ TextSecurePreferences.setProfileName(context, name)
+ profileManager.setName(context, recipient, name)
+ }
+
+ // update pfp
+ if (userPic == UserPic.DEFAULT) {
+ clearUserPic()
+ } else if (userPic.key.isNotEmpty() && userPic.url.isNotEmpty()
+ && TextSecurePreferences.getProfilePictureURL(context) != userPic.url) {
+ setUserProfilePicture(userPic.url, userPic.key)
+ }
+ if (userProfile.getNtsPriority() == PRIORITY_HIDDEN) {
+ // delete nts thread if needed
+ val ourThread = getThreadId(recipient) ?: return
+ deleteConversation(ourThread)
+ } else {
+ // create note to self thread if needed (?)
+ val ourThread = getOrCreateThreadIdFor(recipient.address)
+ DatabaseComponent.get(context).threadDatabase().setHasSent(ourThread, true)
+ setPinned(ourThread, userProfile.getNtsPriority() > 0)
+ }
+
+ }
+
+ private fun updateContacts(contacts: Contacts) {
+ val extracted = contacts.all().toList()
+ addLibSessionContacts(extracted)
+ }
+
+ override fun clearUserPic() {
+ val userPublicKey = getUserPublicKey() ?: return
+ val recipientDatabase = DatabaseComponent.get(context).recipientDatabase()
+ // would love to get rid of recipient and context from this
+ val recipient = Recipient.from(context, fromSerialized(userPublicKey), false)
+ // clear picture if userPic is null
+ TextSecurePreferences.setProfileKey(context, null)
+ ProfileKeyUtil.setEncodedProfileKey(context, null)
+ recipientDatabase.setProfileAvatar(recipient, null)
+ TextSecurePreferences.setProfileAvatarId(context, 0)
+ TextSecurePreferences.setProfilePictureURL(context, null)
+
+ Recipient.removeCached(fromSerialized(userPublicKey))
+ configFactory.user?.setPic(UserPic.DEFAULT)
+ ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
+ }
+
+ private fun updateConvoVolatile(convos: ConversationVolatileConfig) {
+ val extracted = convos.all()
+ for (conversation in extracted) {
+ val threadId = when (conversation) {
+ is Conversation.OneToOne -> getThreadIdFor(conversation.sessionId, null, null, createThread = false)
+ is Conversation.LegacyGroup -> getThreadIdFor("", conversation.groupId,null, createThread = false)
+ is Conversation.Community -> getThreadIdFor("",null, "${conversation.baseCommunityInfo.baseUrl.removeSuffix("/")}.${conversation.baseCommunityInfo.room}", createThread = false)
+ }
+ if (threadId != null) {
+ if (conversation.lastRead > getLastSeen(threadId)) {
+ markConversationAsRead(threadId, conversation.lastRead, force = true)
+ }
+ updateThread(threadId, false)
+ }
+ }
+ }
+
+ private fun updateUserGroups(userGroups: UserGroupsConfig) {
+ val threadDb = DatabaseComponent.get(context).threadDatabase()
+ val localUserPublicKey = getUserPublicKey() ?: return Log.w(
+ "Loki",
+ "No user public key when trying to update user groups from config"
+ )
+ val communities = userGroups.allCommunityInfo()
+ val lgc = userGroups.allLegacyGroupInfo()
+ val allOpenGroups = getAllOpenGroups()
+ val toDeleteCommunities = allOpenGroups.filter {
+ Conversation.Community(BaseCommunityInfo(it.value.server, it.value.room, it.value.publicKey), 0, false).baseCommunityInfo.fullUrl() !in communities.map { it.community.fullUrl() }
+ }
+
+ val existingCommunities: Map = allOpenGroups.filterKeys { it !in toDeleteCommunities.keys }
+ val toAddCommunities = communities.filter { it.community.fullUrl() !in existingCommunities.map { it.value.joinURL } }
+ val existingJoinUrls = existingCommunities.values.map { it.joinURL }
+
+ val existingClosedGroups = getAllGroups(includeInactive = true).filter { it.isClosedGroup }
+ val lgcIds = lgc.map { it.sessionId }
+ val toDeleteClosedGroups = existingClosedGroups.filter { group ->
+ GroupUtil.doubleDecodeGroupId(group.encodedId) !in lgcIds
+ }
+
+ // delete the ones which are not listed in the config
+ toDeleteCommunities.values.forEach { openGroup ->
+ OpenGroupManager.delete(openGroup.server, openGroup.room, context)
+ }
+
+ toDeleteClosedGroups.forEach { deleteGroup ->
+ val threadId = getThreadId(deleteGroup.encodedId)
+ if (threadId != null) {
+ ClosedGroupManager.silentlyRemoveGroup(context,threadId,GroupUtil.doubleDecodeGroupId(deleteGroup.encodedId), deleteGroup.encodedId, localUserPublicKey, delete = true)
+ }
+ }
+
+ toAddCommunities.forEach { toAddCommunity ->
+ val joinUrl = toAddCommunity.community.fullUrl()
+ if (!hasBackgroundGroupAddJob(joinUrl)) {
+ JobQueue.shared.add(BackgroundGroupAddJob(joinUrl))
+ }
+ }
+
+ for (groupInfo in communities) {
+ val groupBaseCommunity = groupInfo.community
+ if (groupBaseCommunity.fullUrl() in existingJoinUrls) {
+ // add it
+ val (threadId, _) = existingCommunities.entries.first { (_, v) -> v.joinURL == groupInfo.community.fullUrl() }
+ threadDb.setPinned(threadId, groupInfo.priority == PRIORITY_PINNED)
+ }
+ }
+
+ for (group in lgc) {
+ val existingGroup = existingClosedGroups.firstOrNull { GroupUtil.doubleDecodeGroupId(it.encodedId) == group.sessionId }
+ val existingThread = existingGroup?.let { getThreadId(existingGroup.encodedId) }
+ if (existingGroup != null) {
+ if (group.priority == PRIORITY_HIDDEN && existingThread != null) {
+ ClosedGroupManager.silentlyRemoveGroup(context,existingThread,GroupUtil.doubleDecodeGroupId(existingGroup.encodedId), existingGroup.encodedId, localUserPublicKey, delete = true)
+ } else if (existingThread == null) {
+ Log.w("Loki-DBG", "Existing group had no thread to hide")
+ } else {
+ Log.d("Loki-DBG", "Setting existing group pinned status to ${group.priority}")
+ threadDb.setPinned(existingThread, group.priority == PRIORITY_PINNED)
+ }
+ } else {
+ val members = group.members.keys.map { Address.fromSerialized(it) }
+ val admins = group.members.filter { it.value /*admin = true*/ }.keys.map { Address.fromSerialized(it) }
+ val groupId = GroupUtil.doubleEncodeGroupID(group.sessionId)
+ val title = group.name
+ val formationTimestamp = (group.joinedAt * 1000L)
+ createGroup(groupId, title, admins + members, null, null, admins, formationTimestamp)
+ setProfileSharing(Address.fromSerialized(groupId), true)
+ // Add the group to the user's set of public keys to poll for
+ addClosedGroupPublicKey(group.sessionId)
+ // Store the encryption key pair
+ val keyPair = ECKeyPair(DjbECPublicKey(group.encPubKey), DjbECPrivateKey(group.encSecKey))
+ addClosedGroupEncryptionKeyPair(keyPair, group.sessionId, SnodeAPI.nowWithOffset)
+ // Set expiration timer
+ val expireTimer = group.disappearingTimer
+ setExpirationTimer(groupId, expireTimer.toInt())
+ // Notify the PN server
+ PushRegistryV1.subscribeGroup(group.sessionId, publicKey = localUserPublicKey)
+ // Notify the user
+ val threadID = getOrCreateThreadIdFor(Address.fromSerialized(groupId))
+ threadDb.setDate(threadID, formationTimestamp)
+ insertOutgoingInfoMessage(context, groupId, SignalServiceGroup.Type.CREATION, title, members.map { it.serialize() }, admins.map { it.serialize() }, threadID, formationTimestamp)
+ // Don't create config group here, it's from a config update
+ // Start polling
+ ClosedGroupPollerV2.shared.startPolling(group.sessionId)
+ }
+ }
+ }
+
override fun setAuthToken(room: String, server: String, newValue: String) {
val id = "$server.$room"
DatabaseComponent.get(context).lokiAPIDatabase().setAuthToken(id, newValue)
@@ -324,6 +683,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
DatabaseComponent.get(context).groupDatabase().updateProfilePicture(groupID, newValue)
}
+ override fun removeProfilePicture(groupID: String) {
+ DatabaseComponent.get(context).groupDatabase().removeProfilePicture(groupID)
+ }
+
override fun hasDownloadedProfilePicture(groupID: String): Boolean {
return DatabaseComponent.get(context).groupDatabase().hasDownloadedProfilePicture(groupID)
}
@@ -373,6 +736,22 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
}
}
+ override fun markAsSyncing(timestamp: Long, author: String) {
+ DatabaseComponent.get(context).mmsSmsDatabase()
+ .getMessageFor(timestamp, author)
+ ?.run { getMmsDatabaseElseSms(isMms).markAsSyncing(id) }
+ }
+
+ private fun getMmsDatabaseElseSms(isMms: Boolean) =
+ if (isMms) DatabaseComponent.get(context).mmsDatabase()
+ else DatabaseComponent.get(context).smsDatabase()
+
+ override fun markAsResyncing(timestamp: Long, author: String) {
+ DatabaseComponent.get(context).mmsSmsDatabase()
+ .getMessageFor(timestamp, author)
+ ?.run { getMmsDatabaseElseSms(isMms).markAsResyncing(id) }
+ }
+
override fun markAsSending(timestamp: Long, author: String) {
val database = DatabaseComponent.get(context).mmsSmsDatabase()
val messageRecord = database.getMessageFor(timestamp, author) ?: return
@@ -398,7 +777,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
}
}
- override fun setErrorMessage(timestamp: Long, author: String, error: Exception) {
+ override fun markAsSentFailed(timestamp: Long, author: String, error: Exception) {
val database = DatabaseComponent.get(context).mmsSmsDatabase()
val messageRecord = database.getMessageFor(timestamp, author) ?: return
if (messageRecord.isMms) {
@@ -421,6 +800,26 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
}
}
+ override fun markAsSyncFailed(timestamp: Long, author: String, error: Exception) {
+ val database = DatabaseComponent.get(context).mmsSmsDatabase()
+ val messageRecord = database.getMessageFor(timestamp, author) ?: return
+
+ database.getMessageFor(timestamp, author)
+ ?.run { getMmsDatabaseElseSms(isMms).markAsSyncFailed(id) }
+
+ if (error.localizedMessage != null) {
+ val message: String
+ if (error is OnionRequestAPI.HTTPRequestFailedAtDestinationException && error.statusCode == 429) {
+ message = "429: Rate limited."
+ } else {
+ message = error.localizedMessage!!
+ }
+ DatabaseComponent.get(context).lokiMessageDatabase().setErrorMessage(messageRecord.getId(), message)
+ } else {
+ DatabaseComponent.get(context).lokiMessageDatabase().setErrorMessage(messageRecord.getId(), error.javaClass.simpleName)
+ }
+ }
+
override fun clearErrorMessage(messageID: Long) {
val db = DatabaseComponent.get(context).lokiMessageDatabase()
db.clearErrorMessage(messageID)
@@ -439,6 +838,59 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
DatabaseComponent.get(context).groupDatabase().create(groupId, title, members, avatar, relay, admins, formationTimestamp)
}
+ override fun createInitialConfigGroup(groupPublicKey: String, name: String, members: Map, formationTimestamp: Long, encryptionKeyPair: ECKeyPair) {
+ val volatiles = configFactory.convoVolatile ?: return
+ val userGroups = configFactory.userGroups ?: return
+ val groupVolatileConfig = volatiles.getOrConstructLegacyGroup(groupPublicKey)
+ groupVolatileConfig.lastRead = formationTimestamp
+ volatiles.set(groupVolatileConfig)
+ val groupInfo = GroupInfo.LegacyGroupInfo(
+ sessionId = groupPublicKey,
+ name = name,
+ members = members,
+ priority = ConfigBase.PRIORITY_VISIBLE,
+ encPubKey = (encryptionKeyPair.publicKey as DjbECPublicKey).publicKey, // 'serialize()' inserts an extra byte
+ encSecKey = encryptionKeyPair.privateKey.serialize(),
+ disappearingTimer = 0L,
+ joinedAt = (formationTimestamp / 1000L)
+ )
+ // shouldn't exist, don't use getOrConstruct + copy
+ userGroups.set(groupInfo)
+ ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
+ }
+
+ override fun updateGroupConfig(groupPublicKey: String) {
+ val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
+ val groupAddress = fromSerialized(groupID)
+ // TODO: probably add a check in here for isActive?
+ // TODO: also check if local user is a member / maybe run delete otherwise?
+ val existingGroup = getGroup(groupID)
+ ?: return Log.w("Loki-DBG", "No existing group for ${groupPublicKey.take(4)}} when updating group config")
+ val userGroups = configFactory.userGroups ?: return
+ if (!existingGroup.isActive) {
+ userGroups.eraseLegacyGroup(groupPublicKey)
+ return
+ }
+ val name = existingGroup.title
+ val admins = existingGroup.admins.map { it.serialize() }
+ val members = existingGroup.members.map { it.serialize() }
+ val membersMap = GroupUtil.createConfigMemberMap(admins = admins, members = members)
+ val latestKeyPair = getLatestClosedGroupEncryptionKeyPair(groupPublicKey)
+ ?: return Log.w("Loki-DBG", "No latest closed group encryption key pair for ${groupPublicKey.take(4)}} when updating group config")
+ val recipientSettings = getRecipientSettings(groupAddress) ?: return
+ val threadID = getThreadId(groupAddress) ?: return
+ val groupInfo = userGroups.getOrConstructLegacyGroupInfo(groupPublicKey).copy(
+ name = name,
+ members = membersMap,
+ encPubKey = (latestKeyPair.publicKey as DjbECPublicKey).publicKey, // 'serialize()' inserts an extra byte
+ encSecKey = latestKeyPair.privateKey.serialize(),
+ priority = if (isPinned(threadID)) PRIORITY_PINNED else ConfigBase.PRIORITY_VISIBLE,
+ disappearingTimer = recipientSettings.expireMessages.toLong(),
+ joinedAt = (existingGroup.formationTimestamp / 1000L)
+ )
+ userGroups.set(groupInfo)
+ }
+
override fun isGroupActive(groupPublicKey: String): Boolean {
return DatabaseComponent.get(context).groupDatabase().getGroup(GroupUtil.doubleEncodeGroupID(groupPublicKey)).orNull()?.isActive == true
}
@@ -469,7 +921,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON()
val infoMessage = IncomingGroupMessage(m, groupID, updateData, true)
val smsDB = DatabaseComponent.get(context).smsDatabase()
- smsDB.insertMessageInbox(infoMessage, true, true)
+ smsDB.insertMessageInbox(infoMessage, true)
}
override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection, admins: Collection, threadID: Long, sentTimestamp: Long) {
@@ -517,8 +969,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
DatabaseComponent.get(context).lokiAPIDatabase().removeClosedGroupPublicKey(groupPublicKey)
}
- override fun addClosedGroupEncryptionKeyPair(encryptionKeyPair: ECKeyPair, groupPublicKey: String) {
- DatabaseComponent.get(context).lokiAPIDatabase().addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey)
+ override fun addClosedGroupEncryptionKeyPair(encryptionKeyPair: ECKeyPair, groupPublicKey: String, timestamp: Long) {
+ DatabaseComponent.get(context).lokiAPIDatabase().addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey, timestamp)
}
override fun removeAllClosedGroupEncryptionKeyPairs(groupPublicKey: String) {
@@ -535,9 +987,21 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
.updateTimestampUpdated(groupID, updatedTimestamp)
}
- override fun setExpirationTimer(groupID: String, duration: Int) {
- val recipient = Recipient.from(context, fromSerialized(groupID), false)
- DatabaseComponent.get(context).recipientDatabase().setExpireMessages(recipient, duration);
+ override fun setExpirationTimer(address: String, duration: Int) {
+ val recipient = Recipient.from(context, fromSerialized(address), false)
+ DatabaseComponent.get(context).recipientDatabase().setExpireMessages(recipient, duration)
+ if (recipient.isContactRecipient && !recipient.isLocalNumber) {
+ configFactory.contacts?.upsertContact(address) {
+ this.expiryMode = if (duration != 0) {
+ ExpiryMode.AfterRead(duration.toLong())
+ } else { // = 0 / delete
+ ExpiryMode.NONE
+ }
+ }
+ if (configFactory.contacts?.needsPush() == true) {
+ ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
+ }
+ }
}
override fun setServerCapabilities(server: String, capabilities: List) {
@@ -556,16 +1020,29 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
OpenGroupManager.updateOpenGroup(openGroup, context)
}
- override fun getAllGroups(): List {
- return DatabaseComponent.get(context).groupDatabase().allGroups
+ override fun getAllGroups(includeInactive: Boolean): List {
+ return DatabaseComponent.get(context).groupDatabase().getAllGroups(includeInactive)
}
override fun addOpenGroup(urlAsString: String): OpenGroupApi.RoomInfo? {
return OpenGroupManager.addOpenGroup(urlAsString, context)
}
- override fun onOpenGroupAdded(server: String) {
+ override fun onOpenGroupAdded(server: String, room: String) {
OpenGroupManager.restartPollerForServer(server.removeSuffix("/"))
+ val groups = configFactory.userGroups ?: return
+ val volatileConfig = configFactory.convoVolatile ?: return
+ val openGroup = getOpenGroup(room, server) ?: return
+ val (infoServer, infoRoom, pubKey) = BaseCommunityInfo.parseFullUrl(openGroup.joinURL) ?: return
+ val pubKeyHex = Hex.toStringCondensed(pubKey)
+ val communityInfo = groups.getOrConstructCommunityInfo(infoServer, infoRoom, pubKeyHex)
+ groups.set(communityInfo)
+ val volatile = volatileConfig.getOrConstructCommunity(infoServer, infoRoom, pubKey)
+ if (volatile.lastRead != 0L) {
+ val threadId = getThreadId(openGroup) ?: return
+ markConversationAsRead(threadId, volatile.lastRead, force = true)
+ }
+ volatileConfig.set(volatile)
}
override fun hasBackgroundGroupAddJob(groupJoinUrl: String): Boolean {
@@ -583,17 +1060,19 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
return DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient)
}
- override fun getOrCreateThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?): Long {
+ override fun getThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?, createThread: Boolean): Long? {
val database = DatabaseComponent.get(context).threadDatabase()
return if (!openGroupID.isNullOrEmpty()) {
val recipient = Recipient.from(context, fromSerialized(GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray())), false)
- database.getThreadIdIfExistsFor(recipient)
+ database.getThreadIdIfExistsFor(recipient).let { if (it == -1L) null else it }
} else if (!groupPublicKey.isNullOrEmpty()) {
val recipient = Recipient.from(context, fromSerialized(GroupUtil.doubleEncodeGroupID(groupPublicKey)), false)
- database.getOrCreateThreadIdFor(recipient)
+ if (createThread) database.getOrCreateThreadIdFor(recipient)
+ else database.getThreadIdIfExistsFor(recipient).let { if (it == -1L) null else it }
} else {
val recipient = Recipient.from(context, fromSerialized(publicKey), false)
- database.getOrCreateThreadIdFor(recipient)
+ if (createThread) database.getOrCreateThreadIdFor(recipient)
+ else database.getThreadIdIfExistsFor(recipient).let { if (it == -1L) null else it }
}
}
@@ -602,6 +1081,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
return getThreadId(address)
}
+ override fun getThreadId(openGroup: OpenGroup): Long? {
+ return GroupManager.getOpenGroupThreadID("${openGroup.server.removeSuffix("/")}.${openGroup.room}", context)
+ }
+
override fun getThreadId(address: Address): Long? {
val recipient = Recipient.from(context, address, false)
return getThreadId(recipient)
@@ -631,6 +1114,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
override fun setContact(contact: Contact) {
DatabaseComponent.get(context).sessionContactDatabase().setContact(contact)
+ val address = fromSerialized(contact.sessionID)
+ if (!getRecipientApproved(address)) return
+ val recipientHash = SSKEnvironment.shared.profileManager.contactUpdatedInternal(contact)
+ val recipient = Recipient.from(context, address, false)
+ setRecipientHash(recipient, recipientHash)
}
override fun getRecipientForThread(threadId: Long): Recipient? {
@@ -646,6 +1134,51 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
return DatabaseComponent.get(context).recipientDatabase().isAutoDownloadFlagSet(recipient)
}
+ override fun addLibSessionContacts(contacts: List) {
+ val mappingDb = DatabaseComponent.get(context).blindedIdMappingDatabase()
+ val moreContacts = contacts.filter { contact ->
+ val id = SessionId(contact.id)
+ id.prefix?.isBlinded() == false || mappingDb.getBlindedIdMapping(contact.id).none { it.sessionId != null }
+ }
+ val profileManager = SSKEnvironment.shared.profileManager
+ moreContacts.forEach { contact ->
+ val address = fromSerialized(contact.id)
+ val recipient = Recipient.from(context, address, false)
+ setBlocked(listOf(recipient), contact.blocked, fromConfigUpdate = true)
+ setRecipientApproved(recipient, contact.approved)
+ setRecipientApprovedMe(recipient, contact.approvedMe)
+ if (contact.name.isNotEmpty()) {
+ profileManager.setName(context, recipient, contact.name)
+ } else {
+ profileManager.setName(context, recipient, null)
+ }
+ if (contact.nickname.isNotEmpty()) {
+ profileManager.setNickname(context, recipient, contact.nickname)
+ } else {
+ profileManager.setNickname(context, recipient, null)
+ }
+
+ if (contact.profilePicture != UserPic.DEFAULT) {
+ val (url, key) = contact.profilePicture
+ if (key.size != ProfileKeyUtil.PROFILE_KEY_BYTES) return@forEach
+ profileManager.setProfilePicture(context, recipient, url, key)
+ profileManager.setUnidentifiedAccessMode(context, recipient, Recipient.UnidentifiedAccessMode.UNKNOWN)
+ } else {
+ profileManager.setProfilePicture(context, recipient, null, null)
+ }
+ if (contact.priority == PRIORITY_HIDDEN) {
+ getThreadId(fromSerialized(contact.id))?.let { conversationThreadId ->
+ deleteConversation(conversationThreadId)
+ }
+ } else {
+ getThreadId(fromSerialized(contact.id))?.let { conversationThreadId ->
+ setPinned(conversationThreadId, contact.priority == PRIORITY_PINNED)
+ }
+ }
+ setRecipientHash(recipient, contact.hashCode().toString())
+ }
+ }
+
override fun addContacts(contacts: List) {
val recipientDatabase = DatabaseComponent.get(context).recipientDatabase()
val threadDatabase = DatabaseComponent.get(context).threadDatabase()
@@ -669,17 +1202,18 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
recipientDatabase.setProfileSharing(recipient, true)
recipientDatabase.setRegistered(recipient, Recipient.RegisteredState.REGISTERED)
// create Thread if needed
- val threadId = threadDatabase.getOrCreateThreadIdFor(recipient)
+ val threadId = threadDatabase.getThreadIdIfExistsFor(recipient)
if (contact.didApproveMe == true) {
recipientDatabase.setApprovedMe(recipient, true)
}
- if (contact.isApproved == true) {
- recipientDatabase.setApproved(recipient, true)
+ if (contact.isApproved == true && threadId != -1L) {
+ setRecipientApproved(recipient, true)
threadDatabase.setHasSent(threadId, true)
}
- if (contact.isBlocked == true) {
- recipientDatabase.setBlocked(recipient, true)
- threadDatabase.deleteConversation(threadId)
+
+ val contactIsBlocked: Boolean? = contact.isBlocked
+ if (contactIsBlocked != null && recipient.isBlocked != contactIsBlocked) {
+ setBlocked(listOf(recipient), contactIsBlocked, fromConfigUpdate = true)
}
}
if (contacts.isNotEmpty()) {
@@ -699,6 +1233,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
recipientDb.setAutoDownloadAttachments(recipient, shouldAutoDownloadAttachments)
}
+ override fun setRecipientHash(recipient: Recipient, recipientHash: String?) {
+ val recipientDb = DatabaseComponent.get(context).recipientDatabase()
+ recipientDb.setRecipientHash(recipient, recipientHash)
+ }
+
override fun getLastUpdated(threadID: Long): Long {
val threadDB = DatabaseComponent.get(context).threadDatabase()
return threadDB.getLastUpdated(threadID)
@@ -719,14 +1258,77 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
return mmsSmsDb.getConversationCount(threadID)
}
- override fun setThreadPinned(threadID: Long, isPinned: Boolean) {
- val threadDb = DatabaseComponent.get(context).threadDatabase()
- threadDb.setPinned(threadID, isPinned)
+ override fun setPinned(threadID: Long, isPinned: Boolean) {
+ val threadDB = DatabaseComponent.get(context).threadDatabase()
+ threadDB.setPinned(threadID, isPinned)
+ val threadRecipient = getRecipientForThread(threadID) ?: return
+ if (threadRecipient.isLocalNumber) {
+ val user = configFactory.user ?: return
+ user.setNtsPriority(if (isPinned) PRIORITY_PINNED else ConfigBase.PRIORITY_VISIBLE)
+ } else if (threadRecipient.isContactRecipient) {
+ val contacts = configFactory.contacts ?: return
+ contacts.upsertContact(threadRecipient.address.serialize()) {
+ priority = if (isPinned) PRIORITY_PINNED else ConfigBase.PRIORITY_VISIBLE
+ }
+ } else if (threadRecipient.isGroupRecipient) {
+ val groups = configFactory.userGroups ?: return
+ if (threadRecipient.isClosedGroupRecipient) {
+ val sessionId = GroupUtil.doubleDecodeGroupId(threadRecipient.address.serialize())
+ val newGroupInfo = groups.getOrConstructLegacyGroupInfo(sessionId).copy (
+ priority = if (isPinned) PRIORITY_PINNED else ConfigBase.PRIORITY_VISIBLE
+ )
+ groups.set(newGroupInfo)
+ } else if (threadRecipient.isOpenGroupRecipient) {
+ val openGroup = getOpenGroup(threadID) ?: return
+ val (baseUrl, room, pubKeyHex) = BaseCommunityInfo.parseFullUrl(openGroup.joinURL) ?: return
+ val newGroupInfo = groups.getOrConstructCommunityInfo(baseUrl, room, Hex.toStringCondensed(pubKeyHex)).copy (
+ priority = if (isPinned) PRIORITY_PINNED else ConfigBase.PRIORITY_VISIBLE
+ )
+ groups.set(newGroupInfo)
+ }
+ }
+ ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
}
- override fun isThreadPinned(threadID: Long): Boolean {
+ override fun isPinned(threadID: Long): Boolean {
+ val threadDB = DatabaseComponent.get(context).threadDatabase()
+ return threadDB.isPinned(threadID)
+ }
+
+ override fun setThreadDate(threadId: Long, newDate: Long) {
val threadDb = DatabaseComponent.get(context).threadDatabase()
- return threadDb.getPinned(threadID)
+ threadDb.setDate(threadId, newDate)
+ }
+
+ override fun deleteConversation(threadID: Long) {
+ val recipient = getRecipientForThread(threadID)
+ val threadDB = DatabaseComponent.get(context).threadDatabase()
+ val groupDB = DatabaseComponent.get(context).groupDatabase()
+ threadDB.deleteConversation(threadID)
+ if (recipient != null) {
+ if (recipient.isContactRecipient) {
+ if (recipient.isLocalNumber) return
+ val contacts = configFactory.contacts ?: return
+ contacts.upsertContact(recipient.address.serialize()) {
+ this.priority = PRIORITY_HIDDEN
+ }
+ ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
+ } else if (recipient.isClosedGroupRecipient) {
+ // TODO: handle closed group
+ val volatile = configFactory.convoVolatile ?: return
+ val groups = configFactory.userGroups ?: return
+ val groupID = recipient.address.toGroupString()
+ val closedGroup = getGroup(groupID)
+ val groupPublicKey = GroupUtil.doubleDecodeGroupId(recipient.address.serialize())
+ if (closedGroup != null) {
+ groupDB.delete(groupID) // TODO: Should we delete the group? (seems odd to leave it)
+ volatile.eraseLegacyClosedGroup(groupPublicKey)
+ groups.eraseLegacyGroup(groupPublicKey)
+ } else {
+ Log.w("Loki-DBG", "Failed to find a closed group for ${groupPublicKey.take(4)}")
+ }
+ }
+ }
}
override fun clearMessages(threadID: Long, fromUser: Address?): Boolean {
@@ -765,6 +1367,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
if (recipient.isBlocked) return
+ val threadId = getThreadId(recipient) ?: return
+
val mediaMessage = IncomingMediaMessage(
address,
sentTimestamp,
@@ -783,14 +1387,21 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
Optional.of(message)
)
- database.insertSecureDecryptedMessageInbox(mediaMessage, -1, runIncrement = true, runThreadUpdate = true)
+ database.insertSecureDecryptedMessageInbox(mediaMessage, threadId, runThreadUpdate = true)
}
override fun insertMessageRequestResponse(response: MessageRequestResponse) {
val userPublicKey = getUserPublicKey()
val senderPublicKey = response.sender!!
val recipientPublicKey = response.recipient!!
- if (userPublicKey == null || (userPublicKey != recipientPublicKey && userPublicKey != senderPublicKey)) return
+
+ if (
+ userPublicKey == null
+ || (userPublicKey != recipientPublicKey && userPublicKey != senderPublicKey)
+ // this is true if it is a sync message
+ || (userPublicKey == recipientPublicKey && userPublicKey == senderPublicKey)
+ ) return
+
val recipientDb = DatabaseComponent.get(context).recipientDatabase()
val threadDB = DatabaseComponent.get(context).threadDatabase()
if (userPublicKey == senderPublicKey) {
@@ -802,7 +1413,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
val mmsDb = DatabaseComponent.get(context).mmsDatabase()
val smsDb = DatabaseComponent.get(context).smsDatabase()
val sender = Recipient.from(context, fromSerialized(senderPublicKey), false)
- val threadId = threadDB.getOrCreateThreadIdFor(sender)
+ val threadId = getOrCreateThreadIdFor(sender.address)
val profile = response.profile
if (profile != null) {
val profileManager = SSKEnvironment.shared.profileManager
@@ -817,9 +1428,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
val profileKeyChanged = (sender.profileKey == null || !MessageDigest.isEqual(sender.profileKey, newProfileKey))
if ((profileKeyValid && profileKeyChanged) || (profileKeyValid && needsProfilePicture)) {
- profileManager.setProfileKey(context, sender, newProfileKey!!)
+ profileManager.setProfilePicture(context, sender, profile.profilePictureURL!!, newProfileKey!!)
profileManager.setUnidentifiedAccessMode(context, sender, Recipient.UnidentifiedAccessMode.UNKNOWN)
- profileManager.setProfilePictureURL(context, sender, profile.profilePictureURL!!)
}
}
threadDB.setHasSent(threadId, true)
@@ -876,16 +1486,28 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
Optional.absent(),
Optional.absent()
)
- mmsDb.insertSecureDecryptedMessageInbox(message, threadId, runIncrement = true, runThreadUpdate = true)
+ mmsDb.insertSecureDecryptedMessageInbox(message, threadId, runThreadUpdate = true)
}
}
+ override fun getRecipientApproved(address: Address): Boolean {
+ return DatabaseComponent.get(context).recipientDatabase().getApproved(address)
+ }
+
override fun setRecipientApproved(recipient: Recipient, approved: Boolean) {
DatabaseComponent.get(context).recipientDatabase().setApproved(recipient, approved)
+ if (recipient.isLocalNumber || !recipient.isContactRecipient) return
+ configFactory.contacts?.upsertContact(recipient.address.serialize()) {
+ this.approved = approved
+ }
}
override fun setRecipientApprovedMe(recipient: Recipient, approvedMe: Boolean) {
DatabaseComponent.get(context).recipientDatabase().setApprovedMe(recipient, approvedMe)
+ if (recipient.isLocalNumber || !recipient.isContactRecipient) return
+ configFactory.contacts?.upsertContact(recipient.address.serialize()) {
+ this.approvedMe = approvedMe
+ }
}
override fun insertCallMessage(senderPublicKey: String, callMessageType: CallMessageType, sentTimestamp: Long) {
@@ -1015,14 +1637,22 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
DatabaseComponent.get(context).reactionDatabase().deleteMessageReactions(MessageId(messageId, mms))
}
- override fun unblock(toUnblock: List) {
+ override fun setBlocked(recipients: Iterable, isBlocked: Boolean, fromConfigUpdate: Boolean) {
val recipientDb = DatabaseComponent.get(context).recipientDatabase()
- recipientDb.setBlocked(toUnblock, false)
+ recipientDb.setBlocked(recipients, isBlocked)
+ recipients.filter { it.isContactRecipient && !it.isLocalNumber }.forEach { recipient ->
+ configFactory.contacts?.upsertContact(recipient.address.serialize()) {
+ this.blocked = isBlocked
+ }
+ }
+ val contactsConfig = configFactory.contacts ?: return
+ if (contactsConfig.needsPush() && !fromConfigUpdate) {
+ ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
+ }
}
override fun blockedContacts(): List {
val recipientDb = DatabaseComponent.get(context).recipientDatabase()
return recipientDb.blockedContacts
}
-
}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java
index 959cd82da..bd425ed93 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java
@@ -35,6 +35,7 @@ import com.annimon.stream.Stream;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.jetbrains.annotations.NotNull;
+import org.session.libsession.snode.SnodeAPI;
import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.Contact;
import org.session.libsession.utilities.DelimiterUtil;
@@ -49,7 +50,7 @@ import org.session.libsignal.utilities.Log;
import org.session.libsignal.utilities.Pair;
import org.session.libsignal.utilities.guava.Optional;
import org.thoughtcrime.securesms.ApplicationContext;
-import org.thoughtcrime.securesms.contactshare.ContactUtil;
+import org.thoughtcrime.securesms.contacts.ContactUtil;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
@@ -57,14 +58,12 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
-import org.thoughtcrime.securesms.groups.OpenGroupMigrator;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
import org.thoughtcrime.securesms.util.SessionMetaProtocol;
import java.io.Closeable;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
@@ -74,6 +73,11 @@ import java.util.Set;
public class ThreadDatabase extends Database {
+ public interface ConversationThreadUpdateListener {
+ void threadCreated(@NonNull Address address, long threadId);
+ void threadDeleted(@NonNull Address address, long threadId);
+ }
+
private static final String TAG = ThreadDatabase.class.getSimpleName();
private final Map addressCache = new HashMap<>();
@@ -141,13 +145,19 @@ public class ThreadDatabase extends Database {
"ADD COLUMN " + UNREAD_MENTION_COUNT + " INTEGER DEFAULT 0;";
}
+ private ConversationThreadUpdateListener updateListener;
+
public ThreadDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
}
+ public void setUpdateListener(ConversationThreadUpdateListener updateListener) {
+ this.updateListener = updateListener;
+ }
+
private long createThreadForRecipient(Address address, boolean group, int distributionType) {
ContentValues contentValues = new ContentValues(4);
- long date = System.currentTimeMillis();
+ long date = SnodeAPI.getNowWithOffset();
contentValues.put(DATE, date - date % 1000);
contentValues.put(ADDRESS, address.serialize());
@@ -207,10 +217,14 @@ public class ThreadDatabase extends Database {
}
private void deleteThread(long threadId) {
+ Recipient recipient = getRecipientForThreadId(threadId);
SQLiteDatabase db = databaseHelper.getWritableDatabase();
- db.delete(TABLE_NAME, ID_WHERE, new String[] {threadId + ""});
+ int numberRemoved = db.delete(TABLE_NAME, ID_WHERE, new String[] {threadId + ""});
addressCache.remove(threadId);
notifyConversationListListeners();
+ if (updateListener != null && numberRemoved > 0 && recipient != null) {
+ updateListener.threadDeleted(recipient.getAddress(), threadId);
+ }
}
private void deleteThreads(Set threadIds) {
@@ -278,7 +292,7 @@ public class ThreadDatabase extends Database {
DatabaseComponent.get(context).smsDatabase().deleteMessagesInThreadBeforeDate(threadId, lastTweetDate);
DatabaseComponent.get(context).mmsDatabase().deleteMessagesInThreadBeforeDate(threadId, lastTweetDate);
- update(threadId, false);
+ update(threadId, false, true);
notifyConversationListeners(threadId);
}
} finally {
@@ -291,10 +305,34 @@ public class ThreadDatabase extends Database {
Log.i("ThreadDatabase", "Trimming thread: " + threadId + " before :"+timestamp);
DatabaseComponent.get(context).smsDatabase().deleteMessagesInThreadBeforeDate(threadId, timestamp);
DatabaseComponent.get(context).mmsDatabase().deleteMessagesInThreadBeforeDate(threadId, timestamp);
- update(threadId, false);
+ update(threadId, false, true);
notifyConversationListeners(threadId);
}
+ public List setRead(long threadId, long lastReadTime) {
+
+ final List smsRecords = DatabaseComponent.get(context).smsDatabase().setMessagesRead(threadId, lastReadTime);
+ final List mmsRecords = DatabaseComponent.get(context).mmsDatabase().setMessagesRead(threadId, lastReadTime);
+
+ if (smsRecords.isEmpty() && mmsRecords.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ ContentValues contentValues = new ContentValues(2);
+ contentValues.put(READ, smsRecords.isEmpty() && mmsRecords.isEmpty());
+ contentValues.put(LAST_SEEN, lastReadTime);
+
+ SQLiteDatabase db = databaseHelper.getWritableDatabase();
+ db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId+""});
+
+ notifyConversationListListeners();
+
+ return new LinkedList() {{
+ addAll(smsRecords);
+ addAll(mmsRecords);
+ }};
+ }
+
public List setRead(long threadId, boolean lastSeen) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(READ, 1);
@@ -302,7 +340,7 @@ public class ThreadDatabase extends Database {
contentValues.put(UNREAD_MENTION_COUNT, 0);
if (lastSeen) {
- contentValues.put(LAST_SEEN, System.currentTimeMillis());
+ contentValues.put(LAST_SEEN, SnodeAPI.getNowWithOffset());
}
SQLiteDatabase db = databaseHelper.getWritableDatabase();
@@ -319,30 +357,6 @@ public class ThreadDatabase extends Database {
}};
}
- public void incrementUnread(long threadId, int amount, int unreadMentionAmount) {
- SQLiteDatabase db = databaseHelper.getWritableDatabase();
- db.execSQL("UPDATE " + TABLE_NAME + " SET " + READ + " = 0, " +
- UNREAD_COUNT + " = " + UNREAD_COUNT + " + ?, " +
- UNREAD_MENTION_COUNT + " = " + UNREAD_MENTION_COUNT + " + ? WHERE " + ID + " = ?",
- new String[] {
- String.valueOf(amount),
- String.valueOf(unreadMentionAmount),
- String.valueOf(threadId)
- });
- }
-
- public void decrementUnread(long threadId, int amount, int unreadMentionAmount) {
- SQLiteDatabase db = databaseHelper.getWritableDatabase();
- db.execSQL("UPDATE " + TABLE_NAME + " SET " + READ + " = 0, " +
- UNREAD_COUNT + " = " + UNREAD_COUNT + " - ?, " +
- UNREAD_MENTION_COUNT + " = " + UNREAD_MENTION_COUNT + " - ? WHERE " + ID + " = ? AND " + UNREAD_COUNT + " > 0",
- new String[] {
- String.valueOf(amount),
- String.valueOf(unreadMentionAmount),
- String.valueOf(threadId)
- });
- }
-
public void setDistributionType(long threadId, int distributionType) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(TYPE, distributionType);
@@ -352,6 +366,14 @@ public class ThreadDatabase extends Database {
notifyConversationListListeners();
}
+ public void setDate(long threadId, long date) {
+ ContentValues contentValues = new ContentValues(1);
+ contentValues.put(DATE, date);
+ SQLiteDatabase db = databaseHelper.getWritableDatabase();
+ int updated = db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId+""});
+ if (updated > 0) notifyConversationListListeners();
+ }
+
public int getDistributionType(long threadId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = db.query(TABLE_NAME, new String[]{TYPE}, ID_WHERE, new String[]{String.valueOf(threadId)}, null, null, null);
@@ -427,9 +449,9 @@ public class ThreadDatabase extends Database {
" ON " + TABLE_NAME + "." + ADDRESS + " = " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.ADDRESS +
" LEFT OUTER JOIN " + GroupDatabase.TABLE_NAME +
" ON " + TABLE_NAME + "." + ADDRESS + " = " + GroupDatabase.TABLE_NAME + "." + GROUP_ID +
- " WHERE " + MESSAGE_COUNT + " != 0 AND " + ARCHIVED + " = 0 AND " + HAS_SENT + " = 0 AND " + MESSAGE_COUNT + " = " + UNREAD_COUNT + " AND " +
- RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.BLOCK + " = 0 AND " +
+ " WHERE " + MESSAGE_COUNT + " != 0 AND " + ARCHIVED + " = 0 AND " + HAS_SENT + " = 0 AND " +
RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.APPROVED + " = 0 AND " +
+ RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.BLOCK + " = 0 AND " +
GroupDatabase.TABLE_NAME + "." + GROUP_ID + " IS NULL";
cursor = db.rawQuery(query, null);
@@ -517,21 +539,50 @@ public class ThreadDatabase extends Database {
return db.rawQuery(query, null);
}
- public void setLastSeen(long threadId, long timestamp) {
- SQLiteDatabase db = databaseHelper.getWritableDatabase();
- ContentValues contentValues = new ContentValues(1);
- if (timestamp == -1) {
- contentValues.put(LAST_SEEN, System.currentTimeMillis());
- } else {
- contentValues.put(LAST_SEEN, timestamp);
- }
+ /**
+ * @param threadId
+ * @param timestamp
+ * @return true if we have set the last seen for the thread, false if there were no messages in the thread
+ */
+ public boolean setLastSeen(long threadId, long timestamp) {
+ // edge case where we set the last seen time for a conversation before it loads messages (joining community for example)
+ MmsSmsDatabase mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase();
+ Recipient forThreadId = getRecipientForThreadId(threadId);
+ if (mmsSmsDatabase.getConversationCount(threadId) <= 0 && forThreadId != null && forThreadId.isOpenGroupRecipient()) return false;
+ SQLiteDatabase db = databaseHelper.getWritableDatabase();
+
+ ContentValues contentValues = new ContentValues(1);
+ long lastSeenTime = timestamp == -1 ? SnodeAPI.getNowWithOffset() : timestamp;
+ contentValues.put(LAST_SEEN, lastSeenTime);
+ db.beginTransaction();
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(threadId)});
+ String smsCountSubQuery = "SELECT COUNT(*) FROM "+SmsDatabase.TABLE_NAME+" AS s WHERE t."+ID+" = s."+SmsDatabase.THREAD_ID+" AND s."+SmsDatabase.DATE_SENT+" > t."+LAST_SEEN+" AND s."+SmsDatabase.READ+" = 0";
+ String smsMentionCountSubQuery = "SELECT COUNT(*) FROM "+SmsDatabase.TABLE_NAME+" AS s WHERE t."+ID+" = s."+SmsDatabase.THREAD_ID+" AND s."+SmsDatabase.DATE_SENT+" > t."+LAST_SEEN+" AND s."+SmsDatabase.READ+" = 0 AND s."+SmsDatabase.HAS_MENTION+" = 1";
+ String smsReactionCountSubQuery = "SELECT COUNT(*) FROM "+SmsDatabase.TABLE_NAME+" AS s WHERE t."+ID+" = s."+SmsDatabase.THREAD_ID+" AND s."+SmsDatabase.DATE_SENT+" > t."+LAST_SEEN+" AND s."+SmsDatabase.REACTIONS_UNREAD+" = 1";
+ String mmsCountSubQuery = "SELECT COUNT(*) FROM "+MmsDatabase.TABLE_NAME+" AS m WHERE t."+ID+" = m."+MmsDatabase.THREAD_ID+" AND m."+MmsDatabase.DATE_SENT+" > t."+LAST_SEEN+" AND m."+MmsDatabase.READ+" = 0";
+ String mmsMentionCountSubQuery = "SELECT COUNT(*) FROM "+MmsDatabase.TABLE_NAME+" AS m WHERE t."+ID+" = m."+MmsDatabase.THREAD_ID+" AND m."+MmsDatabase.DATE_SENT+" > t."+LAST_SEEN+" AND m."+MmsDatabase.READ+" = 0 AND m."+MmsDatabase.HAS_MENTION+" = 1";
+ String mmsReactionCountSubQuery = "SELECT COUNT(*) FROM "+MmsDatabase.TABLE_NAME+" AS m WHERE t."+ID+" = m."+MmsDatabase.THREAD_ID+" AND m."+MmsDatabase.DATE_SENT+" > t."+LAST_SEEN+" AND m."+MmsDatabase.REACTIONS_UNREAD+" = 1";
+ String allSmsUnread = "(("+smsCountSubQuery+") + ("+smsReactionCountSubQuery+"))";
+ String allMmsUnread = "(("+mmsCountSubQuery+") + ("+mmsReactionCountSubQuery+"))";
+ String allUnread = "(("+allSmsUnread+") + ("+allMmsUnread+"))";
+ String allUnreadMention = "(("+smsMentionCountSubQuery+") + ("+mmsMentionCountSubQuery+"))";
+
+ String reflectUpdates = "UPDATE "+TABLE_NAME+" AS t SET "+UNREAD_COUNT+" = "+allUnread+", "+UNREAD_MENTION_COUNT+" = "+allUnreadMention+" WHERE "+ID+" = ?";
+ db.execSQL(reflectUpdates, new Object[]{threadId});
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ notifyConversationListeners(threadId);
notifyConversationListListeners();
+ return true;
}
- public void setLastSeen(long threadId) {
- setLastSeen(threadId, -1);
+ /**
+ * @param threadId
+ * @return true if we have set the last seen for the thread, false if there were no messages in the thread
+ */
+ public boolean setLastSeen(long threadId) {
+ return setLastSeen(threadId, -1);
}
public Pair getLastSeenAndHasSent(long threadId) {
@@ -634,13 +685,19 @@ public class ThreadDatabase extends Database {
try {
cursor = db.query(TABLE_NAME, new String[]{ID}, where, recipientsArg, null, null, null);
-
+ long threadId;
+ boolean created = false;
if (cursor != null && cursor.moveToFirst()) {
- return cursor.getLong(cursor.getColumnIndexOrThrow(ID));
+ threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
} else {
DatabaseComponent.get(context).recipientDatabase().setProfileSharing(recipient, true);
- return createThreadForRecipient(recipient.getAddress(), recipient.isGroupRecipient(), distributionType);
+ threadId = createThreadForRecipient(recipient.getAddress(), recipient.isGroupRecipient(), distributionType);
+ created = true;
}
+ if (created && updateListener != null) {
+ updateListener.threadCreated(recipient.getAddress(), threadId);
+ }
+ return threadId;
} finally {
if (cursor != null)
cursor.close();
@@ -679,13 +736,14 @@ public class ThreadDatabase extends Database {
new String[] {String.valueOf(threadId)});
notifyConversationListeners(threadId);
+ notifyConversationListListeners();
}
- public boolean update(long threadId, boolean unarchive) {
+ public boolean update(long threadId, boolean unarchive, boolean shouldDeleteOnEmpty) {
MmsSmsDatabase mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase();
long count = mmsSmsDatabase.getConversationCount(threadId);
- boolean shouldDeleteEmptyThread = deleteThreadOnEmpty(threadId);
+ boolean shouldDeleteEmptyThread = shouldDeleteOnEmpty && deleteThreadOnEmpty(threadId);
if (count == 0 && shouldDeleteEmptyThread) {
deleteThread(threadId);
@@ -708,12 +766,10 @@ public class ThreadDatabase extends Database {
updateThread(threadId, count, getFormattedBodyFor(record), getAttachmentUriFor(record),
record.getTimestamp(), record.getDeliveryStatus(), record.getDeliveryReceiptCount(),
record.getType(), unarchive, record.getExpiresIn(), record.getReadReceiptCount());
- notifyConversationListListeners();
return false;
} else {
if (shouldDeleteEmptyThread) {
deleteThread(threadId);
- notifyConversationListListeners();
return true;
} else {
updateThread(threadId, 0, "", null, System.currentTimeMillis(), 0, 0, 0, false, 0, 0);
@@ -723,6 +779,8 @@ public class ThreadDatabase extends Database {
} finally {
if (reader != null)
reader.close();
+ notifyConversationListListeners();
+ notifyConversationListeners(threadId);
}
}
@@ -734,17 +792,32 @@ public class ThreadDatabase extends Database {
new String[] {String.valueOf(threadId)});
notifyConversationListeners(threadId);
+ notifyConversationListListeners();
}
- public boolean getPinned(long threadId) {
- Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[]{IS_PINNED}, ID_WHERE, new String[] {""+threadId},null, null, null);
- boolean isPinned = cursor.moveToNext() && cursor.getInt(0) == 1;
- cursor.close();
- return isPinned;
+ public boolean isPinned(long threadId) {
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor cursor = db.query(TABLE_NAME, new String[]{IS_PINNED}, ID_WHERE, new String[]{String.valueOf(threadId)}, null, null, null);
+ try {
+ if (cursor != null && cursor.moveToFirst()) {
+ return cursor.getInt(0) == 1;
+ }
+ return false;
+ } finally {
+ if (cursor != null) cursor.close();
+ }
}
- public void markAllAsRead(long threadId, boolean isGroupRecipient) {
- List messages = setRead(threadId, true);
+ /**
+ * @param threadId
+ * @param isGroupRecipient
+ * @param lastSeenTime
+ * @return true if we have set the last seen for the thread, false if there were no messages in the thread
+ */
+ public boolean markAllAsRead(long threadId, boolean isGroupRecipient, long lastSeenTime, boolean force) {
+ MmsSmsDatabase mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase();
+ if (mmsSmsDatabase.getConversationCount(threadId) <= 0 && !force) return false;
+ List messages = setRead(threadId, lastSeenTime);
if (isGroupRecipient) {
for (MarkedMessageInfo message: messages) {
MarkReadReceiver.scheduleDeletion(context, message.getExpirationInfo());
@@ -752,7 +825,8 @@ public class ThreadDatabase extends Database {
} else {
MarkReadReceiver.process(context, messages);
}
- ApplicationContext.getInstance(context).messageNotifier.updateNotification(context, false, 0);
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context, threadId);
+ return setLastSeen(threadId, lastSeenTime);
}
private boolean deleteThreadOnEmpty(long threadId) {
@@ -810,77 +884,6 @@ public class ThreadDatabase extends Database {
return query;
}
- @NotNull
- public List getHttpOxenOpenGroups() {
- String where = TABLE_NAME+"."+ADDRESS+" LIKE ?";
- String selection = OpenGroupMigrator.HTTP_PREFIX+OpenGroupMigrator.OPEN_GET_SESSION_TRAILING_DOT_ENCODED +"%";
- SQLiteDatabase db = databaseHelper.getReadableDatabase();
- String query = createQuery(where, 0);
- Cursor cursor = db.rawQuery(query, new String[]{selection});
-
- if (cursor == null) {
- return Collections.emptyList();
- }
- List threads = new ArrayList<>();
- try {
- Reader reader = readerFor(cursor);
- ThreadRecord record;
- while ((record = reader.getNext()) != null) {
- threads.add(record);
- }
- } finally {
- cursor.close();
- }
- return threads;
- }
-
- @NotNull
- public List getLegacyOxenOpenGroups() {
- String where = TABLE_NAME+"."+ADDRESS+" LIKE ?";
- String selection = OpenGroupMigrator.LEGACY_GROUP_ENCODED_ID+"%";
- SQLiteDatabase db = databaseHelper.getReadableDatabase();
- String query = createQuery(where, 0);
- Cursor cursor = db.rawQuery(query, new String[]{selection});
-
- if (cursor == null) {
- return Collections.emptyList();
- }
- List threads = new ArrayList<>();
- try {
- Reader reader = readerFor(cursor);
- ThreadRecord record;
- while ((record = reader.getNext()) != null) {
- threads.add(record);
- }
- } finally {
- cursor.close();
- }
- return threads;
- }
-
- @NotNull
- public List getHttpsOxenOpenGroups() {
- String where = TABLE_NAME+"."+ADDRESS+" LIKE ?";
- String selection = OpenGroupMigrator.NEW_GROUP_ENCODED_ID+"%";
- SQLiteDatabase db = databaseHelper.getReadableDatabase();
- String query = createQuery(where, 0);
- Cursor cursor = db.rawQuery(query, new String[]{selection});
- if (cursor == null) {
- return Collections.emptyList();
- }
- List threads = new ArrayList<>();
- try {
- Reader reader = readerFor(cursor);
- ThreadRecord record;
- while ((record = reader.getNext()) != null) {
- threads.add(record);
- }
- } finally {
- cursor.close();
- }
- return threads;
- }
-
public void migrateEncodedGroup(long threadId, @NotNull String newEncodedGroupId) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(ADDRESS, newEncodedGroupId);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
index fe1b9f785..0c0ebb01d 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
@@ -11,7 +11,6 @@ import androidx.core.app.NotificationCompat;
import net.zetetic.database.sqlcipher.SQLiteConnection;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteDatabaseHook;
-import net.zetetic.database.sqlcipher.SQLiteException;
import net.zetetic.database.sqlcipher.SQLiteOpenHelper;
import org.session.libsession.utilities.TextSecurePreferences;
@@ -19,12 +18,12 @@ import org.session.libsignal.utilities.Log;
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.BlindedIdMappingDatabase;
+import org.thoughtcrime.securesms.database.ConfigDatabase;
import org.thoughtcrime.securesms.database.DraftDatabase;
import org.thoughtcrime.securesms.database.EmojiSearchDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupMemberDatabase;
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
-import org.thoughtcrime.securesms.database.JobDatabase;
import org.thoughtcrime.securesms.database.LokiAPIDatabase;
import org.thoughtcrime.securesms.database.LokiBackupFilesDatabase;
import org.thoughtcrime.securesms.database.LokiMessageDatabase;
@@ -40,6 +39,7 @@ import org.thoughtcrime.securesms.database.SessionJobDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
+import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities;
import java.io.File;
@@ -87,9 +87,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int lokiV39 = 60;
private static final int lokiV40 = 61;
private static final int lokiV41 = 62;
+ private static final int lokiV42 = 63;
+ private static final int lokiV43 = 64;
// Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
- private static final int DATABASE_VERSION = lokiV41;
+ private static final int DATABASE_VERSION = lokiV43;
private static final int MIN_DATABASE_VERSION = lokiV7;
private static final String CIPHER3_DATABASE_NAME = "signal.db";
public static final String DATABASE_NAME = "signal_v4.db";
@@ -98,25 +100,40 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private final DatabaseSecret databaseSecret;
public SQLCipherOpenHelper(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) {
- super(context, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, MIN_DATABASE_VERSION, null, new SQLiteDatabaseHook() {
- @Override
- public void preKey(SQLiteConnection connection) {
- SQLCipherOpenHelper.applySQLCipherPragmas(connection, true);
- }
-
- @Override
- public void postKey(SQLiteConnection connection) {
- SQLCipherOpenHelper.applySQLCipherPragmas(connection, true);
-
- // if not vacuumed in a while, perform that operation
- long currentTime = System.currentTimeMillis();
- // 7 days
- if (currentTime - TextSecurePreferences.getLastVacuumTime(context) > 604_800_000) {
- connection.execute("VACUUM;", null, null);
- TextSecurePreferences.setLastVacuumNow(context);
+ super(
+ context,
+ DATABASE_NAME,
+ databaseSecret.asString(),
+ null,
+ DATABASE_VERSION,
+ MIN_DATABASE_VERSION,
+ null,
+ new SQLiteDatabaseHook() {
+ @Override
+ public void preKey(SQLiteConnection connection) {
+ SQLCipherOpenHelper.applySQLCipherPragmas(connection, true);
}
- }
- }, true);
+
+ @Override
+ public void postKey(SQLiteConnection connection) {
+ SQLCipherOpenHelper.applySQLCipherPragmas(connection, true);
+
+ // if not vacuumed in a while, perform that operation
+ long currentTime = System.currentTimeMillis();
+ // 7 days
+ if (currentTime - TextSecurePreferences.getLastVacuumTime(context) > 604_800_000) {
+ connection.execute("VACUUM;", null, null);
+ TextSecurePreferences.setLastVacuumNow(context);
+ }
+ }
+ },
+ // Note: Now that we support concurrent database reads the migrations are actually non-blocking
+ // because of this we need to initially open the database with writeAheadLogging (WAL mode) disabled
+ // and enable it once the database officially opens it's connection (which will cause it to re-connect
+ // in WAL mode) - this is a little inefficient but will prevent SQL-related errors/crashes due to
+ // incomplete migrations
+ false
+ );
this.context = context.getApplicationContext();
this.databaseSecret = databaseSecret;
@@ -134,7 +151,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
connection.execute("PRAGMA cipher_page_size = 4096;", null, null);
}
- private static SQLiteDatabase open(String path, DatabaseSecret databaseSecret, boolean useSQLCipher4) throws SQLiteException {
+ private static SQLiteDatabase open(String path, DatabaseSecret databaseSecret, boolean useSQLCipher4) {
return SQLiteDatabase.openDatabase(path, databaseSecret.asString(), null, SQLiteDatabase.OPEN_READWRITE, new SQLiteDatabaseHook() {
@Override
public void preKey(SQLiteConnection connection) { SQLCipherOpenHelper.applySQLCipherPragmas(connection, useSQLCipher4); }
@@ -151,11 +168,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
// If the old SQLCipher3 database file doesn't exist then no need to do anything
if (!oldDbFile.exists()) { return; }
- try {
- // Define the location for the new database
- String newDbPath = context.getDatabasePath(DATABASE_NAME).getPath();
- File newDbFile = new File(newDbPath);
+ // Define the location for the new database
+ String newDbPath = context.getDatabasePath(DATABASE_NAME).getPath();
+ File newDbFile = new File(newDbPath);
+ try {
// If the new database file already exists then check if it's valid first, if it's in an
// invalid state we should delete it and try to migrate again
if (newDbFile.exists()) {
@@ -163,10 +180,24 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
// assume the user hasn't downgraded for some reason and made changes to the old database and
// can remove the old database file (it won't be used anymore)
if (oldDbFile.lastModified() <= newDbFile.lastModified()) {
- // TODO: Delete 'CIPHER3_DATABASE_NAME' once enough time has past
-// //noinspection ResultOfMethodCallIgnored
-// oldDbFile.delete();
- return;
+ try {
+ SQLiteDatabase newDb = SQLCipherOpenHelper.open(newDbPath, databaseSecret, true);
+ int version = newDb.getVersion();
+ newDb.close();
+
+ // Make sure the new database has it's version set correctly (if not then the migration didn't
+ // fully succeed and the database will try to create all it's tables and immediately fail so
+ // we will need to remove and remigrate)
+ if (version > 0) {
+ // TODO: Delete 'CIPHER3_DATABASE_NAME' once enough time has past
+// //noinspection ResultOfMethodCallIgnored
+// oldDbFile.delete();
+ return;
+ }
+ }
+ catch (Exception e) {
+ Log.i(TAG, "Failed to retrieve version from new database, assuming invalid and remigrating");
+ }
}
// If the old database does have newer changes then the new database could have stale/invalid
@@ -208,6 +239,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
catch (Exception e) {
Log.e(TAG, "Migration from SQLCipher3 to SQLCipher4 failed", e);
+ // If an exception was thrown then we should remove the new database file (it's probably invalid)
+ if (!newDbFile.delete()) {
+ Log.e(TAG, "Unable to delete invalid new database file");
+ }
+
// Notify the user of the issue so they know they can downgrade until the issue is fixed
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
String channelId = context.getString(R.string.NotificationChannel_failures);
@@ -251,9 +287,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
for (String sql : SearchDatabase.CREATE_TABLE) {
db.execSQL(sql);
}
- for (String sql : JobDatabase.CREATE_TABLE) {
- db.execSQL(sql);
- }
db.execSQL(LokiAPIDatabase.getCreateSnodePoolTableCommand());
db.execSQL(LokiAPIDatabase.getCreateOnionRequestPathTableCommand());
db.execSQL(LokiAPIDatabase.getCreateSwarmTableCommand());
@@ -311,6 +344,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(ThreadDatabase.getUnreadMentionCountCommand());
db.execSQL(SmsDatabase.CREATE_HAS_MENTION_COMMAND);
db.execSQL(MmsDatabase.CREATE_HAS_MENTION_COMMAND);
+ db.execSQL(ConfigDatabase.CREATE_CONFIG_TABLE_COMMAND);
executeStatements(db, SmsDatabase.CREATE_INDEXS);
executeStatements(db, MmsDatabase.CREATE_INDEXS);
@@ -322,6 +356,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
executeStatements(db, ReactionDatabase.CREATE_INDEXS);
executeStatements(db, ReactionDatabase.CREATE_REACTION_TRIGGERS);
+ db.execSQL(RecipientDatabase.getAddWrapperHash());
db.execSQL(RecipientDatabase.getCreateAutoDownloadCommand());
db.execSQL(RecipientDatabase.getUpdateAutoDownloadValuesCommand());
@@ -558,6 +593,16 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
}
if (oldVersion < lokiV41) {
+ db.execSQL(ConfigDatabase.CREATE_CONFIG_TABLE_COMMAND);
+ db.execSQL(ConfigurationMessageUtilities.DELETE_INACTIVE_GROUPS);
+ db.execSQL(ConfigurationMessageUtilities.DELETE_INACTIVE_ONE_TO_ONES);
+ }
+
+ if (oldVersion < lokiV42) {
+ db.execSQL(RecipientDatabase.getAddWrapperHash());
+ }
+
+ if (oldVersion < lokiV43) {
db.execSQL(RecipientDatabase.getCreateAutoDownloadCommand());
db.execSQL(RecipientDatabase.getUpdateAutoDownloadValuesCommand());
}
@@ -568,6 +613,15 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
}
}
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ super.onOpen(db);
+
+ // Now that the database is officially open (ie. the migrations are completed) we want to enable
+ // write ahead logging (WAL mode) to officially support concurrent read connections
+ db.enableWriteAheadLogging();
+ }
+
public void markCurrent(SQLiteDatabase db) {
db.setVersion(DATABASE_VERSION);
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java
index ef0f4b54f..39fba182a 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java
@@ -80,6 +80,18 @@ public abstract class DisplayRecord {
return !isFailed() && !isPending();
}
+ public boolean isSyncing() {
+ return MmsSmsColumns.Types.isSyncingType(type);
+ }
+
+ public boolean isResyncing() {
+ return MmsSmsColumns.Types.isResyncingType(type);
+ }
+
+ public boolean isSyncFailed() {
+ return MmsSmsColumns.Types.isSyncFailedMessageType(type);
+ }
+
public boolean isFailed() {
return MmsSmsColumns.Types.isFailedMessageType(type)
|| MmsSmsColumns.Types.isPendingSecureSmsFallbackType(type)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java
index dfc4c1bc8..f3e72a874 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java
@@ -51,6 +51,7 @@ public class ThreadRecord extends DisplayRecord {
private final long expiresIn;
private final long lastSeen;
private final boolean pinned;
+ private final int initialRecipientHash;
public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri,
@NonNull Recipient recipient, long date, long count, int unreadCount,
@@ -68,6 +69,7 @@ public class ThreadRecord extends DisplayRecord {
this.expiresIn = expiresIn;
this.lastSeen = lastSeen;
this.pinned = pinned;
+ this.initialRecipientHash = recipient.hashCode();
}
public @Nullable Uri getSnippetUri() {
@@ -176,4 +178,8 @@ public class ThreadRecord extends DisplayRecord {
public boolean isPinned() {
return pinned;
}
+
+ public int getInitialRecipientHash() {
+ return initialRecipientHash;
+ }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt
index 6f26c6ae3..936e4f287 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppModule.kt
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.dependencies
import dagger.Binds
import dagger.Module
+import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import org.session.libsession.utilities.AppTextSecurePreferences
@@ -19,4 +20,10 @@ abstract class AppModule {
@Binds
abstract fun bindConversationRepository(repository: DefaultConversationRepository): ConversationRepository
+}
+
+@EntryPoint
+@InstallIn(SingletonComponent::class)
+interface AppComponent {
+ fun getPrefs(): TextSecurePreferences
}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt
new file mode 100644
index 000000000..d664ffedb
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt
@@ -0,0 +1,251 @@
+package org.thoughtcrime.securesms.dependencies
+
+import android.content.Context
+import android.os.Trace
+import network.loki.messenger.libsession_util.ConfigBase
+import network.loki.messenger.libsession_util.Contacts
+import network.loki.messenger.libsession_util.ConversationVolatileConfig
+import network.loki.messenger.libsession_util.UserGroupsConfig
+import network.loki.messenger.libsession_util.UserProfile
+import org.session.libsession.snode.SnodeAPI
+import org.session.libsession.utilities.ConfigFactoryProtocol
+import org.session.libsession.utilities.ConfigFactoryUpdateListener
+import org.session.libsession.utilities.TextSecurePreferences
+import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage
+import org.session.libsignal.utilities.Log
+import org.thoughtcrime.securesms.database.ConfigDatabase
+import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get
+import org.thoughtcrime.securesms.groups.GroupManager
+import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
+
+class ConfigFactory(
+ private val context: Context,
+ private val configDatabase: ConfigDatabase,
+ private val maybeGetUserInfo: () -> Pair?
+) :
+ ConfigFactoryProtocol {
+ companion object {
+ // This is a buffer period within which we will process messages which would result in a
+ // config change, any message which would normally result in a config change which was sent
+ // before `lastConfigMessage.timestamp - configChangeBufferPeriod` will not actually have
+ // it's changes applied (control text will still be added though)
+ val configChangeBufferPeriod: Long = (2 * 60 * 1000)
+ }
+
+ fun keyPairChanged() { // this should only happen restoring or clearing data
+ _userConfig?.free()
+ _contacts?.free()
+ _convoVolatileConfig?.free()
+ _userConfig = null
+ _contacts = null
+ _convoVolatileConfig = null
+ }
+
+ private val userLock = Object()
+ private var _userConfig: UserProfile? = null
+ private val contactsLock = Object()
+ private var _contacts: Contacts? = null
+ private val convoVolatileLock = Object()
+ private var _convoVolatileConfig: ConversationVolatileConfig? = null
+ private val userGroupsLock = Object()
+ private var _userGroups: UserGroupsConfig? = null
+
+ private val isConfigForcedOn by lazy { TextSecurePreferences.hasForcedNewConfig(context) }
+
+ private val listeners: MutableList = mutableListOf()
+ fun registerListener(listener: ConfigFactoryUpdateListener) {
+ listeners += listener
+ }
+
+ fun unregisterListener(listener: ConfigFactoryUpdateListener) {
+ listeners -= listener
+ }
+
+ private inline fun synchronizedWithLog(lock: Any, body: ()->T): T {
+ Trace.beginSection("synchronizedWithLog")
+ val result = synchronized(lock) {
+ body()
+ }
+ Trace.endSection()
+ return result
+ }
+
+ override val user: UserProfile?
+ get() = synchronizedWithLog(userLock) {
+ if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null
+ if (_userConfig == null) {
+ val (secretKey, publicKey) = maybeGetUserInfo() ?: return null
+ val userDump = configDatabase.retrieveConfigAndHashes(
+ SharedConfigMessage.Kind.USER_PROFILE.name,
+ publicKey
+ )
+ _userConfig = if (userDump != null) {
+ UserProfile.newInstance(secretKey, userDump)
+ } else {
+ ConfigurationMessageUtilities.generateUserProfileConfigDump()?.let { dump ->
+ UserProfile.newInstance(secretKey, dump)
+ } ?: UserProfile.newInstance(secretKey)
+ }
+ }
+ _userConfig
+ }
+
+ override val contacts: Contacts?
+ get() = synchronizedWithLog(contactsLock) {
+ if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null
+ if (_contacts == null) {
+ val (secretKey, publicKey) = maybeGetUserInfo() ?: return null
+ val contactsDump = configDatabase.retrieveConfigAndHashes(
+ SharedConfigMessage.Kind.CONTACTS.name,
+ publicKey
+ )
+ _contacts = if (contactsDump != null) {
+ Contacts.newInstance(secretKey, contactsDump)
+ } else {
+ ConfigurationMessageUtilities.generateContactConfigDump()?.let { dump ->
+ Contacts.newInstance(secretKey, dump)
+ } ?: Contacts.newInstance(secretKey)
+ }
+ }
+ _contacts
+ }
+
+ override val convoVolatile: ConversationVolatileConfig?
+ get() = synchronizedWithLog(convoVolatileLock) {
+ if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null
+ if (_convoVolatileConfig == null) {
+ val (secretKey, publicKey) = maybeGetUserInfo() ?: return null
+ val convoDump = configDatabase.retrieveConfigAndHashes(
+ SharedConfigMessage.Kind.CONVO_INFO_VOLATILE.name,
+ publicKey
+ )
+ _convoVolatileConfig = if (convoDump != null) {
+ ConversationVolatileConfig.newInstance(secretKey, convoDump)
+ } else {
+ ConfigurationMessageUtilities.generateConversationVolatileDump(context)
+ ?.let { dump ->
+ ConversationVolatileConfig.newInstance(secretKey, dump)
+ } ?: ConversationVolatileConfig.newInstance(secretKey)
+ }
+ }
+ _convoVolatileConfig
+ }
+
+ override val userGroups: UserGroupsConfig?
+ get() = synchronizedWithLog(userGroupsLock) {
+ if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null
+ if (_userGroups == null) {
+ val (secretKey, publicKey) = maybeGetUserInfo() ?: return null
+ val userGroupsDump = configDatabase.retrieveConfigAndHashes(
+ SharedConfigMessage.Kind.GROUPS.name,
+ publicKey
+ )
+ _userGroups = if (userGroupsDump != null) {
+ UserGroupsConfig.Companion.newInstance(secretKey, userGroupsDump)
+ } else {
+ ConfigurationMessageUtilities.generateUserGroupDump(context)?.let { dump ->
+ UserGroupsConfig.Companion.newInstance(secretKey, dump)
+ } ?: UserGroupsConfig.newInstance(secretKey)
+ }
+ }
+ _userGroups
+ }
+
+ override fun getUserConfigs(): List =
+ listOfNotNull(user, contacts, convoVolatile, userGroups)
+
+
+ private fun persistUserConfigDump(timestamp: Long) = synchronized(userLock) {
+ val dumped = user?.dump() ?: return
+ val (_, publicKey) = maybeGetUserInfo() ?: return
+ configDatabase.storeConfig(SharedConfigMessage.Kind.USER_PROFILE.name, publicKey, dumped, timestamp)
+ }
+
+ private fun persistContactsConfigDump(timestamp: Long) = synchronized(contactsLock) {
+ val dumped = contacts?.dump() ?: return
+ val (_, publicKey) = maybeGetUserInfo() ?: return
+ configDatabase.storeConfig(SharedConfigMessage.Kind.CONTACTS.name, publicKey, dumped, timestamp)
+ }
+
+ private fun persistConvoVolatileConfigDump(timestamp: Long) = synchronized(convoVolatileLock) {
+ val dumped = convoVolatile?.dump() ?: return
+ val (_, publicKey) = maybeGetUserInfo() ?: return
+ configDatabase.storeConfig(
+ SharedConfigMessage.Kind.CONVO_INFO_VOLATILE.name,
+ publicKey,
+ dumped,
+ timestamp
+ )
+ }
+
+ private fun persistUserGroupsConfigDump(timestamp: Long) = synchronized(userGroupsLock) {
+ val dumped = userGroups?.dump() ?: return
+ val (_, publicKey) = maybeGetUserInfo() ?: return
+ configDatabase.storeConfig(SharedConfigMessage.Kind.GROUPS.name, publicKey, dumped, timestamp)
+ }
+
+ override fun persist(forConfigObject: ConfigBase, timestamp: Long) {
+ try {
+ listeners.forEach { listener ->
+ listener.notifyUpdates(forConfigObject)
+ }
+ when (forConfigObject) {
+ is UserProfile -> persistUserConfigDump(timestamp)
+ is Contacts -> persistContactsConfigDump(timestamp)
+ is ConversationVolatileConfig -> persistConvoVolatileConfigDump(timestamp)
+ is UserGroupsConfig -> persistUserGroupsConfigDump(timestamp)
+ else -> throw UnsupportedOperationException("Can't support type of ${forConfigObject::class.simpleName} yet")
+ }
+ } catch (e: Exception) {
+ Log.e("Loki", "failed to persist ${forConfigObject.javaClass.simpleName}", e)
+ }
+ }
+
+ override fun conversationInConfig(
+ publicKey: String?,
+ groupPublicKey: String?,
+ openGroupId: String?,
+ visibleOnly: Boolean
+ ): Boolean {
+ if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return true
+
+ val (_, userPublicKey) = maybeGetUserInfo() ?: return true
+
+ if (openGroupId != null) {
+ val userGroups = userGroups ?: return false
+ val threadId = GroupManager.getOpenGroupThreadID(openGroupId, context)
+ val openGroup = get(context).lokiThreadDatabase().getOpenGroupChat(threadId) ?: return false
+
+ // Not handling the `hidden` behaviour for communities so just indicate the existence
+ return (userGroups.getCommunityInfo(openGroup.server, openGroup.room) != null)
+ }
+ else if (groupPublicKey != null) {
+ val userGroups = userGroups ?: return false
+
+ // Not handling the `hidden` behaviour for legacy groups so just indicate the existence
+ return (userGroups.getLegacyGroupInfo(groupPublicKey) != null)
+ }
+ else if (publicKey == userPublicKey) {
+ val user = user ?: return false
+
+ return (!visibleOnly || user.getNtsPriority() != ConfigBase.PRIORITY_HIDDEN)
+ }
+ else if (publicKey != null) {
+ val contacts = contacts ?: return false
+ val targetContact = contacts.get(publicKey) ?: return false
+
+ return (!visibleOnly || targetContact.priority != ConfigBase.PRIORITY_HIDDEN)
+ }
+
+ return false
+ }
+
+ override fun canPerformChange(variant: String, publicKey: String, changeTimestampMs: Long): Boolean {
+ if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return true
+
+ val lastUpdateTimestampMs = configDatabase.retrieveConfigLastUpdateTimestamp(variant, publicKey)
+
+ // Ensure the change occurred after the last config message was handled (minus the buffer period)
+ return (changeTimestampMs >= (lastUpdateTimestampMs - ConfigFactory.configChangeBufferPeriod))
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseComponent.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseComponent.kt
index 6ea3b0fb9..eddd61c83 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseComponent.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseComponent.kt
@@ -33,7 +33,6 @@ interface DatabaseComponent {
fun recipientDatabase(): RecipientDatabase
fun groupReceiptDatabase(): GroupReceiptDatabase
fun searchDatabase(): SearchDatabase
- fun jobDatabase(): JobDatabase
fun lokiAPIDatabase(): LokiAPIDatabase
fun lokiMessageDatabase(): LokiMessageDatabase
fun lokiThreadDatabase(): LokiThreadDatabase
@@ -47,4 +46,5 @@ interface DatabaseComponent {
fun attachmentProvider(): MessageDataProvider
fun blindedIdMappingDatabase(): BlindedIdMappingDatabase
fun groupMemberDatabase(): GroupMemberDatabase
+ fun configDatabase(): ConfigDatabase
}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt
index 35ccb0f55..6580c5c92 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt
@@ -6,7 +6,6 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
-import net.zetetic.database.sqlcipher.SQLiteDatabase
import org.session.libsession.database.MessageDataProvider
import org.session.libsession.database.StorageProtocol
import org.thoughtcrime.securesms.attachments.DatabaseAttachmentProvider
@@ -87,10 +86,6 @@ object DatabaseModule {
@Singleton
fun searchDatabase(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper) = SearchDatabase(context,openHelper)
- @Provides
- @Singleton
- fun provideJobDatabase(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper) = JobDatabase(context, openHelper)
-
@Provides
@Singleton
fun provideLokiApiDatabase(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper) = LokiAPIDatabase(context,openHelper)
@@ -137,10 +132,18 @@ object DatabaseModule {
@Provides
@Singleton
- fun provideStorage(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper): StorageProtocol = Storage(context,openHelper)
+ fun provideStorage(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper, configFactory: ConfigFactory, threadDatabase: ThreadDatabase): Storage {
+ val storage = Storage(context,openHelper, configFactory)
+ threadDatabase.setUpdateListener(storage)
+ return storage
+ }
@Provides
@Singleton
fun provideAttachmentProvider(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper): MessageDataProvider = DatabaseAttachmentProvider(context, openHelper)
+ @Provides
+ @Singleton
+ fun provideConfigDatabase(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper): ConfigDatabase = ConfigDatabase(context, openHelper)
+
}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/InjectableType.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/InjectableType.java
deleted file mode 100644
index 033b3ef45..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/InjectableType.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package org.thoughtcrime.securesms.dependencies;
-
-public interface InjectableType {
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/SessionUtilModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/SessionUtilModule.kt
new file mode 100644
index 000000000..cd4b07133
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/SessionUtilModule.kt
@@ -0,0 +1,36 @@
+package org.thoughtcrime.securesms.dependencies
+
+import android.content.Context
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import org.session.libsession.utilities.ConfigFactoryUpdateListener
+import org.session.libsession.utilities.TextSecurePreferences
+import org.thoughtcrime.securesms.crypto.KeyPairUtilities
+import org.thoughtcrime.securesms.database.ConfigDatabase
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object SessionUtilModule {
+
+ private fun maybeUserEdSecretKey(context: Context): ByteArray? {
+ val edKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return null
+ return edKey.secretKey.asBytes
+ }
+
+ @Provides
+ @Singleton
+ fun provideConfigFactory(@ApplicationContext context: Context, configDatabase: ConfigDatabase): ConfigFactory =
+ ConfigFactory(context, configDatabase) {
+ val localUserPublicKey = TextSecurePreferences.getLocalNumber(context)
+ val secretKey = maybeUserEdSecretKey(context)
+ if (localUserPublicKey == null || secretKey == null) null
+ else secretKey to localUserPublicKey
+ }.apply {
+ registerListener(context as ConfigFactoryUpdateListener)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/dms/NewMessageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/dms/NewMessageFragment.kt
index 8b880d218..74e2cac4c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/dms/NewMessageFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/dms/NewMessageFragment.kt
@@ -98,7 +98,7 @@ class NewMessageFragment : Fragment() {
private fun hideLoader() {
binding.loader.animate().setDuration(150).alpha(0.0f).setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
binding.loader.visibility = View.GONE
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ClosedGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/ClosedGroupManager.kt
new file mode 100644
index 000000000..f8e64dd38
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ClosedGroupManager.kt
@@ -0,0 +1,64 @@
+package org.thoughtcrime.securesms.groups
+
+import android.content.Context
+import network.loki.messenger.libsession_util.ConfigBase
+import org.session.libsession.messaging.MessagingModuleConfiguration
+import org.session.libsession.messaging.sending_receiving.notifications.PushRegistryV1
+import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2
+import org.session.libsession.utilities.Address
+import org.session.libsession.utilities.GroupRecord
+import org.session.libsession.utilities.GroupUtil
+import org.session.libsession.utilities.recipients.Recipient
+import org.session.libsignal.crypto.ecc.DjbECPublicKey
+import org.thoughtcrime.securesms.ApplicationContext
+import org.thoughtcrime.securesms.dependencies.ConfigFactory
+
+object ClosedGroupManager {
+
+ fun silentlyRemoveGroup(context: Context, threadId: Long, groupPublicKey: String, groupID: String, userPublicKey: String, delete: Boolean = true) {
+ val storage = MessagingModuleConfiguration.shared.storage
+ // Mark the group as inactive
+ storage.setActive(groupID, false)
+ storage.removeClosedGroupPublicKey(groupPublicKey)
+ // Remove the key pairs
+ storage.removeAllClosedGroupEncryptionKeyPairs(groupPublicKey)
+ storage.removeMember(groupID, Address.fromSerialized(userPublicKey))
+ // Notify the PN server
+ PushRegistryV1.unsubscribeGroup(closedGroupPublicKey = groupPublicKey, publicKey = userPublicKey)
+ // Stop polling
+ ClosedGroupPollerV2.shared.stopPolling(groupPublicKey)
+ storage.cancelPendingMessageSendJobs(threadId)
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context)
+ if (delete) {
+ storage.deleteConversation(threadId)
+ }
+ }
+
+ fun ConfigFactory.removeLegacyGroup(group: GroupRecord): Boolean {
+ val groups = userGroups ?: return false
+ if (!group.isClosedGroup) return false
+ val groupPublicKey = GroupUtil.doubleEncodeGroupID(group.getId())
+ return groups.eraseLegacyGroup(groupPublicKey)
+ }
+
+ fun ConfigFactory.updateLegacyGroup(groupRecipientSettings: Recipient.RecipientSettings, group: GroupRecord) {
+ val groups = userGroups ?: return
+ if (!group.isClosedGroup) return
+ val storage = MessagingModuleConfiguration.shared.storage
+ val threadId = storage.getThreadId(group.encodedId) ?: return
+ val groupPublicKey = GroupUtil.doubleEncodeGroupID(group.getId())
+ val latestKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: return
+ val legacyInfo = groups.getOrConstructLegacyGroupInfo(groupPublicKey)
+ val latestMemberMap = GroupUtil.createConfigMemberMap(group.members.map(Address::serialize), group.admins.map(Address::serialize))
+ val toSet = legacyInfo.copy(
+ members = latestMemberMap,
+ name = group.title,
+ disappearingTimer = groupRecipientSettings.expireMessages.toLong(),
+ priority = if (storage.isPinned(threadId)) ConfigBase.PRIORITY_PINNED else ConfigBase.PRIORITY_VISIBLE,
+ encPubKey = (latestKeyPair.publicKey as DjbECPublicKey).publicKey, // 'serialize()' inserts an extra byte
+ encSecKey = latestKeyPair.privateKey.serialize()
+ )
+ groups.set(toSet)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt
index ead979b77..ecd40938a 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt
@@ -21,6 +21,7 @@ import nl.komponents.kovenant.ui.successUi
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.sending_receiving.groupSizeLimit
import org.session.libsession.utilities.Address
+import org.session.libsession.utilities.Device
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.contacts.SelectContactsAdapter
@@ -31,10 +32,14 @@ import org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.util.fadeIn
import org.thoughtcrime.securesms.util.fadeOut
+import javax.inject.Inject
@AndroidEntryPoint
class CreateGroupFragment : Fragment() {
+ @Inject
+ lateinit var device: Device
+
private lateinit var binding: FragmentCreateGroupBinding
private val viewModel: CreateGroupViewModel by viewModels()
@@ -86,7 +91,7 @@ class CreateGroupFragment : Fragment() {
val userPublicKey = TextSecurePreferences.getLocalNumber(requireContext())!!
isLoading = true
binding.loaderContainer.fadeIn()
- MessageSender.createClosedGroup(name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID ->
+ MessageSender.createClosedGroup(device, name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID ->
binding.loaderContainer.fadeOut()
isLoading = false
val threadID = DatabaseComponent.get(requireContext()).threadDatabase().getOrCreateThreadIdFor(Recipient.from(requireContext(), Address.fromSerialized(groupID), false))
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupActivity.kt
index 62e762316..9fee8adaf 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupActivity.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupActivity.kt
@@ -16,6 +16,7 @@ import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
+import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.R
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.task
@@ -28,16 +29,28 @@ import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.ThemeUtil
import org.session.libsession.utilities.recipients.Recipient
+import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.contacts.SelectContactsActivity
+import org.thoughtcrime.securesms.database.Storage
+import org.thoughtcrime.securesms.dependencies.ConfigFactory
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
+import org.thoughtcrime.securesms.groups.ClosedGroupManager.updateLegacyGroup
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.util.fadeIn
import org.thoughtcrime.securesms.util.fadeOut
import java.io.IOException
+import javax.inject.Inject
+@AndroidEntryPoint
class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
+
+ @Inject
+ lateinit var groupConfigFactory: ConfigFactory
+ @Inject
+ lateinit var storage: Storage
+
private val originalMembers = HashSet()
private val zombies = HashSet()
private val members = HashSet()
@@ -289,7 +302,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
isLoading = true
loaderContainer.fadeIn()
val promise: Promise = if (!members.contains(Recipient.from(this, Address.fromSerialized(userPublicKey), false))) {
- MessageSender.explicitLeave(groupPublicKey!!, true)
+ MessageSender.explicitLeave(groupPublicKey!!, false)
} else {
task {
if (hasNameChanged) {
@@ -306,6 +319,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
promise.successUi {
loaderContainer.fadeOut()
isLoading = false
+ updateGroupConfig()
finish()
}.failUi { exception ->
val message = if (exception is MessageSender.Error) exception.description else "An error occurred"
@@ -316,5 +330,13 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
}
}
- class GroupMembers(val members: List, val zombieMembers: List) { }
+ private fun updateGroupConfig() {
+ val latestRecipient = storage.getRecipientSettings(Address.fromSerialized(groupID))
+ ?: return Log.w("Loki", "No recipient settings when trying to update group config")
+ val latestGroup = storage.getGroup(groupID)
+ ?: return Log.w("Loki", "No group record when trying to update group config")
+ groupConfigFactory.updateLegacyGroup(latestRecipient, latestGroup)
+ }
+
+ class GroupMembers(val members: List, val zombieMembers: List)
}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java
index a3d0e6d25..d4c5acf4e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java
@@ -6,6 +6,7 @@ import android.graphics.Bitmap;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import org.session.libsession.messaging.MessagingModuleConfiguration;
import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.DistributionTypes;
import org.session.libsession.utilities.GroupUtil;
@@ -16,11 +17,14 @@ import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
import org.thoughtcrime.securesms.util.BitmapUtil;
+import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Objects;
import java.util.Set;
+import network.loki.messenger.libsession_util.UserGroupsConfig;
+
public class GroupManager {
public static long getOpenGroupThreadID(String id, @NonNull Context context) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt
index d37b17ef9..ae59c3833 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt
@@ -55,7 +55,7 @@ class JoinCommunityFragment : Fragment() {
fun hideLoader() {
binding.loader.animate().setDuration(150).alpha(0.0f).setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
binding.loader.visibility = View.GONE
}
@@ -79,7 +79,7 @@ class JoinCommunityFragment : Fragment() {
val openGroupID = "$sanitizedServer.${openGroup.room}"
OpenGroupManager.add(sanitizedServer, openGroup.room, openGroup.serverPublicKey, requireContext())
val storage = MessagingModuleConfiguration.shared.storage
- storage.onOpenGroupAdded(sanitizedServer)
+ storage.onOpenGroupAdded(sanitizedServer, openGroup.room)
val threadID = GroupManager.getOpenGroupThreadID(openGroupID, requireContext())
val groupID = GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray())
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt
index ef4726910..2754c70f6 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt
@@ -9,13 +9,13 @@ import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller
import org.session.libsignal.utilities.Log
-import org.session.libsignal.utilities.ThreadUtils
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
+import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
import java.util.concurrent.Executors
object OpenGroupManager {
private val executorService = Executors.newScheduledThreadPool(4)
- private var pollers = mutableMapOf